From 937e616319012554e6b5f922c1ff09c502e4de36 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 5 Jun 2023 15:03:48 +0200 Subject: [PATCH 01/61] Improve composable for icons and create RoundedIconAtom. --- .../atomic/atoms/RoundedIconAtom.kt | 115 ++++++++++++++++++ .../molecules/IconTitleSubtitleMolecule.kt | 38 +++--- .../designsystem/theme/components/Icon.kt | 37 ++++++ 3 files changed, 166 insertions(+), 24 deletions(-) create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt new file mode 100644 index 0000000000..e73af11254 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt @@ -0,0 +1,115 @@ +/* + * 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.libraries.designsystem.atomic.atoms + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Home +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.vector.ImageVector +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.LocalColors +import io.element.android.libraries.designsystem.theme.components.Icon + +enum class RoundedIconAtomSize { + Medium, + Large +} + +@Composable +fun RoundedIconAtom( + modifier: Modifier = Modifier, + size: RoundedIconAtomSize = RoundedIconAtomSize.Large, + resourceId: Int? = null, + imageVector: ImageVector? = null, +) { + Box( + modifier = modifier + .size(size.toContainerSize()) + .background( + color = LocalColors.current.quinary, + shape = RoundedCornerShape(size.toCornerSize()) + ) + ) { + Icon( + modifier = Modifier + .align(Alignment.Center) + .size(size.toIconSize()), + tint = MaterialTheme.colorScheme.secondary, + resourceId = resourceId, + imageVector = imageVector, + contentDescription = "", + ) + } +} + +private fun RoundedIconAtomSize.toContainerSize(): Dp { + return when (this) { + RoundedIconAtomSize.Medium -> 30.dp + RoundedIconAtomSize.Large -> 70.dp + } +} + +private fun RoundedIconAtomSize.toCornerSize(): Dp { + return when (this) { + RoundedIconAtomSize.Medium -> 8.dp + RoundedIconAtomSize.Large -> 14.dp + } +} + +private fun RoundedIconAtomSize.toIconSize(): Dp { + return when (this) { + RoundedIconAtomSize.Medium -> 16.dp + RoundedIconAtomSize.Large -> 48.dp + } +} + +@Preview +@Composable +internal fun RoundedIconAtomLightPreview() = + ElementPreviewLight { ContentToPreview() } + +@Preview +@Composable +internal fun RoundedIconAtomDarkPreview() = + ElementPreviewDark { ContentToPreview() } + +@Composable +private fun ContentToPreview() { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + RoundedIconAtom( + size = RoundedIconAtomSize.Medium, + imageVector = Icons.Filled.Home, + ) + RoundedIconAtom( + size = RoundedIconAtomSize.Large, + imageVector = Icons.Filled.Home, + ) + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt index bda10b8ba2..171e3655a0 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt @@ -16,55 +16,45 @@ package io.element.android.libraries.designsystem.atomic.molecules -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape 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.vector.ImageVector import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.element.android.libraries.designsystem.ElementTextStyles import io.element.android.libraries.designsystem.R +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.LocalColors -import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text +/** + * Provide either an `iconResourceId` or an `iconImageVector` + */ @Composable fun IconTitleSubtitleMolecule( - iconResourceId: Int, title: String, subTitle: String, modifier: Modifier = Modifier, + iconResourceId: Int? = null, + iconImageVector: ImageVector? = null, ) { Column(modifier) { - Box( + RoundedIconAtom( modifier = Modifier - .size(width = 70.dp, height = 70.dp) - .align(Alignment.CenterHorizontally) - .background( - color = LocalColors.current.quinary, - shape = RoundedCornerShape(14.dp) - ) - ) { - Icon( - modifier = Modifier - .align(Alignment.Center) - .size(width = 48.dp, height = 48.dp), - tint = MaterialTheme.colorScheme.secondary, - resourceId = iconResourceId, - contentDescription = "", - ) - } + .align(Alignment.CenterHorizontally), + size = RoundedIconAtomSize.Large, + resourceId = iconResourceId, + imageVector = iconImageVector, + ) Spacer(modifier = Modifier.height(16.dp)) Text( text = title, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt index dedf2af060..1a9bfe3a0a 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt @@ -30,6 +30,43 @@ import androidx.compose.ui.tooling.preview.Preview import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup +@Composable +fun Icon( + contentDescription: String?, + modifier: Modifier = Modifier, + tint: Color = LocalContentColor.current, + imageVector: ImageVector? = null, + bitmap: ImageBitmap? = null, + @DrawableRes resourceId: Int? = null, +) { + when { + imageVector != null -> { + Icon( + imageVector = imageVector, + contentDescription = contentDescription, + modifier = modifier, + tint = tint + ) + } + bitmap != null -> { + Icon( + bitmap = bitmap, + contentDescription = contentDescription, + modifier = modifier, + tint = tint + ) + } + resourceId != null -> { + Icon( + resourceId = resourceId, + contentDescription = contentDescription, + modifier = modifier, + tint = tint + ) + } + } +} + @Composable fun Icon( imageVector: ImageVector, From 96f9eb839774a10a1dea5e03ad29fd4bf5e36f0f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 5 Jun 2023 17:52:16 +0200 Subject: [PATCH 02/61] Change account provider screen. --- .../ChangeAccountProviderEvents.kt | 23 +++ .../ChangeAccountProviderNode.kt | 46 +++++ .../ChangeAccountProviderPresenter.kt | 79 ++++++++ .../ChangeAccountProviderState.kt | 29 +++ .../ChangeAccountProviderStateProvider.kt | 34 ++++ .../ChangeAccountProviderView.kt | 178 ++++++++++++++++++ .../item/ChangeAccountProviderItem.kt | 24 +++ .../item/ChangeAccountProviderItemProvider.kt | 36 ++++ .../item/ChangeAccountProviderItemView.kt | 131 +++++++++++++ .../impl/src/main/res/drawable/ic_matrix.xml | 12 ++ .../impl/src/main/res/drawable/ic_public.xml | 12 ++ .../impl/src/main/res/values/localazy.xml | 4 + .../atomic/atoms/RoundedIconAtom.kt | 4 +- .../atomic/atoms/SeparatorAtom.kt | 62 ++++++ tools/localazy/config.json | 3 +- 15 files changed, 675 insertions(+), 2 deletions(-) create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderEvents.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItem.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemProvider.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemView.kt create mode 100644 features/login/impl/src/main/res/drawable/ic_matrix.xml create mode 100644 features/login/impl/src/main/res/drawable/ic_public.xml create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/SeparatorAtom.kt diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderEvents.kt new file mode 100644 index 0000000000..73b9929dca --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderEvents.kt @@ -0,0 +1,23 @@ +/* + * 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 + +sealed interface ChangeAccountProviderEvents { + data class SetServer(val server: String) : ChangeAccountProviderEvents + object Submit : ChangeAccountProviderEvents + object ClearError : ChangeAccountProviderEvents +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt new file mode 100644 index 0000000000..00b9d312c0 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt @@ -0,0 +1,46 @@ +/* + * 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 + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.AppScope + +@ContributesNode(AppScope::class) +class ChangeAccountProviderNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: ChangeAccountProviderPresenter, +) : Node(buildContext, plugins = plugins) { + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + ChangeAccountProviderView( + state = state, + modifier = modifier, + // TODO + onBackPressed = {} + ) + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt new file mode 100644 index 0000000000..b4fbdac771 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt @@ -0,0 +1,79 @@ +/* + * 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 + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import io.element.android.features.login.impl.changeserver.ChangeServerError +import io.element.android.features.login.impl.util.LoginConstants +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.architecture.execute +import io.element.android.libraries.core.data.tryOrNull +import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import java.net.URL +import javax.inject.Inject + +class ChangeAccountProviderPresenter @Inject constructor( + private val authenticationService: MatrixAuthenticationService +) : Presenter { + + @Composable + override fun present(): ChangeAccountProviderState { + val localCoroutineScope = rememberCoroutineScope() + + val homeserver = rememberSaveable { + mutableStateOf(authenticationService.getHomeserverDetails().value?.url ?: LoginConstants.DEFAULT_HOMESERVER_URL) + } + val changeServerAction: MutableState> = remember { + mutableStateOf(Async.Uninitialized) + } + + fun handleEvents(event: ChangeAccountProviderEvents) { + when (event) { + is ChangeAccountProviderEvents.SetServer -> { + homeserver.value = event.server + handleEvents(ChangeAccountProviderEvents.ClearError) + } + ChangeAccountProviderEvents.Submit -> { + localCoroutineScope.submit(homeserver, changeServerAction) + } + ChangeAccountProviderEvents.ClearError -> changeServerAction.value = Async.Uninitialized + } + } + + return ChangeAccountProviderState( + homeserver = homeserver.value, + changeServerAction = changeServerAction.value, + eventSink = ::handleEvents + ) + } + + private fun CoroutineScope.submit(homeserverUrl: MutableState, changeServerAction: MutableState>) = launch { + suspend { + val domain = tryOrNull { URL(homeserverUrl.value) }?.host ?: homeserverUrl.value + authenticationService.setHomeserver(domain).getOrThrow() + homeserverUrl.value = domain + }.execute(changeServerAction, errorMapping = ChangeServerError::from) + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt new file mode 100644 index 0000000000..98f95ca357 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt @@ -0,0 +1,29 @@ +/* + * 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 + +import io.element.android.libraries.architecture.Async + +// Do not use default value, so no member get forgotten in the presenters. +data class ChangeAccountProviderState( + val homeserver: String, + val changeServerAction: Async, + val eventSink: (ChangeAccountProviderEvents) -> Unit +) { + // TODO Remove + val submitEnabled: Boolean get() = changeServerAction is Async.Uninitialized || changeServerAction is Async.Loading +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt new file mode 100644 index 0000000000..a176de572e --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt @@ -0,0 +1,34 @@ +/* + * 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 + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.Async + +open class ChangeAccountProviderStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aChangeAccountProviderState(), + // Add other state here + ) +} + +fun aChangeAccountProviderState() = ChangeAccountProviderState( + homeserver = "", + changeServerAction = Async.Uninitialized, + eventSink = {} +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt new file mode 100644 index 0000000000..d778bb3c35 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt @@ -0,0 +1,178 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) + +package io.element.android.features.login.impl.changeaccountprovider + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Home +import androidx.compose.material3.ExperimentalMaterial3Api +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.platform.LocalFocusManager +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.changeaccountprovider.item.ChangeAccountProviderItem +import io.element.android.features.login.impl.changeaccountprovider.item.ChangeAccountProviderItemView +import io.element.android.features.login.impl.changeserver.ChangeServerError +import io.element.android.features.login.impl.changeserver.SlidingSyncNotSupportedDialog +import io.element.android.libraries.architecture.Async +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.ButtonWithProgress +import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.components.Scaffold +import io.element.android.libraries.designsystem.theme.components.TopAppBar +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 +fun ChangeAccountProviderView( + state: ChangeAccountProviderState, + modifier: Modifier = Modifier, + onBackPressed: () -> Unit, + onOtherProviderClicked: () -> Unit = {}, + onChangeServerSuccess: () -> Unit = {}, +) { + val eventSink = state.eventSink + val scrollState = rememberScrollState() + val isLoading by remember(state.changeServerAction) { + derivedStateOf { + state.changeServerAction is Async.Loading + } + } + val invalidHomeserverError = (state.changeServerAction as? Async.Failure)?.error as? ChangeServerError.InlineErrorMessage + val slidingSyncNotSupportedError = (state.changeServerAction as? Async.Failure)?.error as? ChangeServerError.SlidingSyncAlert + val focusManager = LocalFocusManager.current + + fun submit() { + // Clear focus to prevent keyboard issues with textfields + focusManager.clearFocus(force = true) + + eventSink(ChangeAccountProviderEvents.Submit) + } + + Scaffold( + topBar = { + TopAppBar( + title = {}, + navigationIcon = { BackButton(onClick = onBackPressed) } + ) + } + ) { padding -> + Box( + modifier = modifier + .fillMaxSize() + .imePadding() + .padding(padding) + .consumeWindowInsets(padding) + ) { + Column( + modifier = Modifier + .verticalScroll( + state = scrollState, + ) + ) { + IconTitleSubtitleMolecule( + modifier = Modifier.padding(top = 16.dp, bottom = 32.dp, start = 16.dp, end = 16.dp), + iconImageVector = Icons.Filled.Home, + title = stringResource(id = R.string.screen_change_account_provider_title), + subTitle = stringResource(id = R.string.screen_change_account_provider_subtitle) + ) + + if (slidingSyncNotSupportedError != null) { + SlidingSyncNotSupportedDialog(onLearnMoreClicked = { + eventSink(ChangeAccountProviderEvents.ClearError) + }, onDismiss = { + eventSink(ChangeAccountProviderEvents.ClearError) + }) + } + ChangeAccountProviderItemView( + item = ChangeAccountProviderItem( + title = "matrix.org", + subtitle = stringResource(id = R.string.screen_change_account_provider_matrix_org_subtitle), + isPublic = true, + isMatrix = true, + ), + onClick = { + TODO() + } + ) + ChangeAccountProviderItemView( + item = ChangeAccountProviderItem( + title = stringResource(id = R.string.screen_change_account_provider_other), + ), + onClick = onOtherProviderClicked + ) + + Spacer(Modifier.height(32.dp)) + ButtonWithProgress( + text = stringResource(id = R.string.screen_change_server_submit), + showProgress = isLoading, + onClick = ::submit, + enabled = state.submitEnabled, + modifier = Modifier + .fillMaxWidth() + .testTag(TestTags.changeServerContinue) + ) + if (state.changeServerAction is Async.Success) { + onChangeServerSuccess() + } + } + } + } +} + +@Preview +@Composable +fun ChangeAccountProviderViewLightPreview(@PreviewParameter(ChangeAccountProviderStateProvider::class) state: ChangeAccountProviderState) = + ElementPreviewLight { ContentToPreview(state) } + +@Preview +@Composable +fun ChangeAccountProviderViewDarkPreview(@PreviewParameter(ChangeAccountProviderStateProvider::class) state: ChangeAccountProviderState) = + ElementPreviewDark { ContentToPreview(state) } + +@Composable +private fun ContentToPreview(state: ChangeAccountProviderState) { + ChangeAccountProviderView( + state = state, + onBackPressed = { } + ) +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItem.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItem.kt new file mode 100644 index 0000000000..c913923205 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItem.kt @@ -0,0 +1,24 @@ +/* + * 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.item + +data class ChangeAccountProviderItem( + val title: String, + val subtitle: String? = null, + val isPublic: Boolean = false, + val isMatrix: Boolean = false, +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemProvider.kt new file mode 100644 index 0000000000..bf7a92cf08 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemProvider.kt @@ -0,0 +1,36 @@ +/* + * 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.item + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider + +open class ChangeAccountProviderItemProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aChangeAccountProviderItem(), + aChangeAccountProviderItem().copy(subtitle = null), + aChangeAccountProviderItem().copy(title = "Other", subtitle = null, isPublic = false, isMatrix = false), + // Add other state here + ) +} + +fun aChangeAccountProviderItem() = ChangeAccountProviderItem( + title = "matrix.org", + subtitle = "Matrix.org is an open network for secure, decentralized communication.", + isPublic = true, + isMatrix = true, +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemView.kt new file mode 100644 index 0000000000..96d7a26e7c --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemView.kt @@ -0,0 +1,131 @@ +/* + * 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.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.atomic.atoms.SeparatorAtom +import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +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 ChangeAccountProviderItemView( + item: ChangeAccountProviderItem, + modifier: Modifier = Modifier, + onClick: () -> Unit, +) { + Column(modifier = modifier + .fillMaxWidth() + .clickable { onClick() }) { + SeparatorAtom() + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp, horizontal = 16.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .heightIn(min = 44.dp), + verticalAlignment = Alignment.CenterVertically + ) { + if (item.isMatrix) { + RoundedIconAtom( + size = RoundedIconAtomSize.Medium, + resourceId = R.drawable.ic_matrix, + tint = Color.Unspecified, + ) + } else { + RoundedIconAtom( + size = RoundedIconAtomSize.Medium, + imageVector = Icons.Filled.Search, + ) + } + 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 ChangeAccountProviderViewLightPreview(@PreviewParameter(ChangeAccountProviderItemProvider::class) item: ChangeAccountProviderItem) = + ElementPreviewLight { ContentToPreview(item) } + +@Preview +@Composable +fun ChangeAccountProviderViewDarkPreview(@PreviewParameter(ChangeAccountProviderItemProvider::class) item: ChangeAccountProviderItem) = + ElementPreviewDark { ContentToPreview(item) } + +@Composable +private fun ContentToPreview(item: ChangeAccountProviderItem) { + ChangeAccountProviderItemView( + item = item, + onClick = { } + ) +} diff --git a/features/login/impl/src/main/res/drawable/ic_matrix.xml b/features/login/impl/src/main/res/drawable/ic_matrix.xml new file mode 100644 index 0000000000..dbc788a031 --- /dev/null +++ b/features/login/impl/src/main/res/drawable/ic_matrix.xml @@ -0,0 +1,12 @@ + + + + diff --git a/features/login/impl/src/main/res/drawable/ic_public.xml b/features/login/impl/src/main/res/drawable/ic_public.xml new file mode 100644 index 0000000000..fc1eacbc9f --- /dev/null +++ b/features/login/impl/src/main/res/drawable/ic_public.xml @@ -0,0 +1,12 @@ + + + + diff --git a/features/login/impl/src/main/res/values/localazy.xml b/features/login/impl/src/main/res/values/localazy.xml index cf59844e89..39c35ac92f 100644 --- a/features/login/impl/src/main/res/values/localazy.xml +++ b/features/login/impl/src/main/res/values/localazy.xml @@ -1,5 +1,9 @@ + "Matrix.org is an open network for secure, decentralized communication." + "Other" + "Use a different account provider, such as your own private server or a work account." + "Change account provider" "We couldn\'t reach this homeserver. Please check that you have entered the homeserver URL correctly. If the URL is correct, contact your homeserver administrator for further help." "This server currently doesn’t support sliding sync." "Homeserver URL" diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt index e73af11254..01fa2355d3 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt @@ -28,6 +28,7 @@ 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.graphics.vector.ImageVector import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp @@ -48,6 +49,7 @@ fun RoundedIconAtom( size: RoundedIconAtomSize = RoundedIconAtomSize.Large, resourceId: Int? = null, imageVector: ImageVector? = null, + tint: Color = MaterialTheme.colorScheme.secondary ) { Box( modifier = modifier @@ -61,7 +63,7 @@ fun RoundedIconAtom( modifier = Modifier .align(Alignment.Center) .size(size.toIconSize()), - tint = MaterialTheme.colorScheme.secondary, + tint = tint, resourceId = resourceId, imageVector = imageVector, contentDescription = "", diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/SeparatorAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/SeparatorAtom.kt new file mode 100644 index 0000000000..fe15ae5480 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/SeparatorAtom.kt @@ -0,0 +1,62 @@ +/* + * 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.libraries.designsystem.atomic.atoms + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.LocalColors + +@Composable +fun SeparatorAtom( + modifier: Modifier = Modifier, + horizontalPadding: Dp = 0.dp, +) { + Spacer( + modifier = modifier + .height(0.5f.dp) + .fillMaxWidth() + .padding(horizontal = horizontalPadding) + .background( + color = LocalColors.current.quinary, + ) + ) +} + +@Preview +@Composable +internal fun SeparatorAtomLightPreview() = + ElementPreviewLight { ContentToPreview() } + +@Preview +@Composable +internal fun SeparatorAtomDarkPreview() = + ElementPreviewDark { ContentToPreview() } + +@Composable +private fun ContentToPreview() { + SeparatorAtom(horizontalPadding = 20.dp) +} diff --git a/tools/localazy/config.json b/tools/localazy/config.json index a23d652cd8..23908ecad8 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -73,7 +73,8 @@ "name": ":features:login:impl", "includeRegex": [ "screen_login_.*", - "screen_change_server_.*" + "screen_change_server_.*", + "screen_change_account_provider_.*" ] }, { From 9809969b64b12dcf107925ddba117e6f9323b676 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 6 Jun 2023 10:07:29 +0200 Subject: [PATCH 03/61] Account provider screen. - Crashing --- .../android/appnav/NotLoggedInFlowNode.kt | 14 ++- .../features/login/api/LoginEntryPoint.kt | 17 ++- .../login/impl/DefaultLoginEntryPoint.kt | 17 ++- .../features/login/impl/LoginFlowNode.kt | 41 ++++++- .../accountprovider/AccountProviderEvents.kt | 22 ++++ .../accountprovider/AccountProviderNode.kt | 77 ++++++++++++ .../AccountProviderPresenter.kt | 56 +++++++++ .../accountprovider/AccountProviderState.kt | 25 ++++ .../AccountProviderStateProvider.kt | 34 ++++++ .../accountprovider/AccountProviderView.kt | 113 ++++++++++++++++++ .../ChangeAccountProviderView.kt | 21 +--- .../item/ChangeAccountProviderItemView.kt | 1 + .../impl/src/main/res/values/localazy.xml | 5 + .../onboarding/impl/OnBoardingNode.kt | 1 + .../molecules/IconTitleSubtitleMolecule.kt | 3 + tools/localazy/config.json | 3 +- 16 files changed, 422 insertions(+), 28 deletions(-) create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderEvents.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderNode.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderState.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderStateProvider.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt diff --git a/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt index 3d99f03fa0..4b89b442d7 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt @@ -63,7 +63,9 @@ class NotLoggedInFlowNode @AssistedInject constructor( object OnBoarding : NavTarget @Parcelize - object LoginFlow : NavTarget + data class LoginFlow( + val isAccountCreation: Boolean, + ) : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { @@ -71,11 +73,11 @@ class NotLoggedInFlowNode @AssistedInject constructor( NavTarget.OnBoarding -> { val callback = object : OnBoardingEntryPoint.Callback { override fun onSignUp() { - //NOOP + backstack.push(NavTarget.LoginFlow(isAccountCreation = true)) } override fun onSignIn() { - backstack.push(NavTarget.LoginFlow) + backstack.push(NavTarget.LoginFlow(isAccountCreation = false)) } } onBoardingEntryPoint @@ -83,8 +85,10 @@ class NotLoggedInFlowNode @AssistedInject constructor( .callback(callback) .build() } - NavTarget.LoginFlow -> { - loginEntryPoint.createNode(this, buildContext) + is NavTarget.LoginFlow -> { + loginEntryPoint.nodeBuilder(this, buildContext) + .params(LoginEntryPoint.Params(isAccountCreation = navTarget.isAccountCreation)) + .build() } } } diff --git a/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginEntryPoint.kt b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginEntryPoint.kt index 0eac558ba5..07a546192d 100644 --- a/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginEntryPoint.kt +++ b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginEntryPoint.kt @@ -16,6 +16,19 @@ package io.element.android.features.login.api -import io.element.android.libraries.architecture.SimpleFeatureEntryPoint +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.libraries.architecture.FeatureEntryPoint -interface LoginEntryPoint : SimpleFeatureEntryPoint +interface LoginEntryPoint : FeatureEntryPoint { + data class Params( + val isAccountCreation: Boolean, + ) + + fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder + + interface NodeBuilder { + fun params(params: Params): NodeBuilder + fun build(): Node + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt index 64c30b7727..a4290825fb 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt @@ -18,6 +18,7 @@ package io.element.android.features.login.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin import com.squareup.anvil.annotations.ContributesBinding import io.element.android.features.login.api.LoginEntryPoint import io.element.android.libraries.architecture.createNode @@ -26,7 +27,19 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class DefaultLoginEntryPoint @Inject constructor() : LoginEntryPoint { - override fun createNode(parentNode: Node, buildContext: BuildContext): Node { - return parentNode.createNode(buildContext) + override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): LoginEntryPoint.NodeBuilder { + val plugins = ArrayList() + + return object : LoginEntryPoint.NodeBuilder { + + override fun params(params: LoginEntryPoint.Params): LoginEntryPoint.NodeBuilder { + plugins += LoginFlowNode.Inputs(isAccountCreation = params.isAccountCreation) + return this + } + + override fun build(): Node { + return parentNode.createNode(buildContext, plugins) + } + } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt index 36153a33ba..ad81d7c441 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt @@ -20,7 +20,6 @@ import android.app.Activity import android.os.Parcelable import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import com.bumble.appyx.core.composable.Children @@ -32,14 +31,18 @@ import com.bumble.appyx.navmodel.backstack.operation.push import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.login.impl.accountprovider.AccountProviderNode import io.element.android.features.login.impl.changeserver.ChangeServerNode 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.webview.OidcNode import io.element.android.features.login.impl.root.LoginRootNode +import io.element.android.features.login.impl.util.LoginConstants import io.element.android.libraries.architecture.BackstackNode +import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.architecture.inputs import io.element.android.libraries.designsystem.theme.ElementTheme import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.auth.OidcDetails @@ -53,7 +56,7 @@ class LoginFlowNode @AssistedInject constructor( private val customTabHandler: CustomTabHandler, ) : BackstackNode( backstack = BackStack( - initialElement = NavTarget.Root, + initialElement = NavTarget.AccountProvider, // NavTarget.Root, savedStateMap = buildContext.savedStateMap, ), buildContext = buildContext, @@ -62,10 +65,24 @@ class LoginFlowNode @AssistedInject constructor( private var activity: Activity? = null private var darkTheme: Boolean = false + data class Inputs( + val isAccountCreation: Boolean, + ) : NodeInputs + + private val inputs: Inputs = inputs() + sealed interface NavTarget : Parcelable { + // Not used anymore @Parcelize object Root : NavTarget + @Parcelize + object AccountProvider : NavTarget + + @Parcelize + object ChangeAccountProvider : NavTarget + + // Not used anymore @Parcelize object ChangeServer : NavTarget @@ -99,6 +116,26 @@ class LoginFlowNode @AssistedInject constructor( val input = OidcNode.Inputs(navTarget.oidcDetails) createNode(buildContext, plugins = listOf(input)) } + NavTarget.AccountProvider -> { + val inputs = AccountProviderNode.Inputs( + homeserver = LoginConstants.DEFAULT_HOMESERVER_URL, + isMatrixOrg = LoginConstants.DEFAULT_HOMESERVER_URL == "matrix.org", + isAccountCreation = inputs.isAccountCreation + ) + val callback = object : AccountProviderNode.Callback { + override fun onContinue() { + TODO("Not yet implemented") + } + + override fun onChangeAccountProvider() { + backstack.push(NavTarget.ChangeAccountProvider) + } + } + createNode(buildContext, plugins = listOf(inputs, callback)) + } + NavTarget.ChangeAccountProvider -> { + TODO() + } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderEvents.kt new file mode 100644 index 0000000000..7a4cbf0366 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderEvents.kt @@ -0,0 +1,22 @@ +/* + * 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 + +// TODO Add your events or remove the file completely if no events +sealed interface AccountProviderEvents { + object MyEvent : AccountProviderEvents +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderNode.kt new file mode 100644 index 0000000000..bd1dbf24c2 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderNode.kt @@ -0,0 +1,77 @@ +/* + * 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 + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.di.AppScope + +@ContributesNode(AppScope::class) +class AccountProviderNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + presenterFactory: AccountProviderPresenter.Factory, +) : Node(buildContext, plugins = plugins) { + + data class Inputs( + val homeserver: String, + val isMatrixOrg: Boolean, + val isAccountCreation: Boolean, + ) : NodeInputs + + private val inputs: Inputs = inputs() + private val presenter = presenterFactory.create( + AccountProviderPresenterParams( + homeserver = inputs.homeserver, + isMatrixOrg = inputs.isMatrixOrg, + isAccountCreation = inputs.isAccountCreation, + ) + ) + + interface Callback : Plugin { + fun onContinue() + fun onChangeAccountProvider() + } + + private fun onContinue() { + plugins().forEach { it.onContinue() } + } + + private fun onChangeAccountProvider() { + plugins().forEach { it.onChangeAccountProvider() } + } + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + AccountProviderView( + state = state, + modifier = modifier, + onContinue = ::onContinue, + onChange = ::onChangeAccountProvider, + ) + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt new file mode 100644 index 0000000000..577d22ec7c --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt @@ -0,0 +1,56 @@ +/* + * 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 + +import androidx.compose.runtime.Composable +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import io.element.android.libraries.architecture.Presenter + +data class AccountProviderPresenterParams( + val homeserver: String, + val isMatrixOrg: Boolean, + val isAccountCreation: Boolean, +) + +class AccountProviderPresenter @AssistedInject constructor( + @Assisted private val params: AccountProviderPresenterParams, +) : Presenter { + + @AssistedFactory + interface Factory { + fun create(params: AccountProviderPresenterParams): AccountProviderPresenter + } + + @Composable + override fun present(): AccountProviderState { + + fun handleEvents(event: AccountProviderEvents) { + when (event) { + AccountProviderEvents.MyEvent -> Unit + } + } + + return AccountProviderState( + homeserver = params.homeserver, + isMatrix = params.isMatrixOrg, + isAccountCreation = params.isAccountCreation, + eventSink = ::handleEvents + ) + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderState.kt new file mode 100644 index 0000000000..1bee64e88d --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderState.kt @@ -0,0 +1,25 @@ +/* + * 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 + +// Do not use default value, so no member get forgotten in the presenters. +data class AccountProviderState( + val homeserver: String, + val isMatrix: Boolean, + val isAccountCreation: Boolean, + val eventSink: (AccountProviderEvents) -> Unit +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderStateProvider.kt new file mode 100644 index 0000000000..35452cd256 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderStateProvider.kt @@ -0,0 +1,34 @@ +/* + * 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 + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider + +open class AccountProviderStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aAccountProviderState(), + // Add other state here + ) +} + +fun aAccountProviderState() = AccountProviderState( + homeserver = "matrix.org", + isMatrix = true, + isAccountCreation = false, + eventSink = {} +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt new file mode 100644 index 0000000000..60f21d0878 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt @@ -0,0 +1,113 @@ +/* + * 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 + +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.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.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.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.TextButton + +@Composable +fun AccountProviderView( + state: AccountProviderState, + modifier: Modifier = Modifier, + onContinue: () -> Unit = {}, + onChange: () -> Unit = {}, +) { + 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 + } + ), + 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 { + Button( + onClick = { + onContinue() + }, + enabled = true, + modifier = Modifier + .fillMaxWidth() + ) { + Text(text = stringResource(id = R.string.screen_account_provider_continue)) + } + TextButton( + onClick = { + onChange() + }, + enabled = true, + modifier = Modifier + .fillMaxWidth() + ) { + Text(text = stringResource(id = R.string.screen_account_provider_change)) + } + } + } + ) { + // No content + } +} + +@Preview +@Composable +fun AccountProviderViewLightPreview(@PreviewParameter(AccountProviderStateProvider::class) state: AccountProviderState) = + ElementPreviewLight { ContentToPreview(state) } + +@Preview +@Composable +fun AccountProviderViewDarkPreview(@PreviewParameter(AccountProviderStateProvider::class) state: AccountProviderState) = + ElementPreviewDark { ContentToPreview(state) } + +@Composable +private fun ContentToPreview(state: AccountProviderState) { + AccountProviderView( + state = state, + ) +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt index d778bb3c35..c31593b766 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt @@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding @@ -33,6 +32,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Home import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -51,13 +51,10 @@ import io.element.android.features.login.impl.changeserver.SlidingSyncNotSupport import io.element.android.libraries.architecture.Async 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.ButtonWithProgress import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.TopAppBar -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 @@ -89,6 +86,7 @@ fun ChangeAccountProviderView( } Scaffold( + modifier = modifier, topBar = { TopAppBar( title = {}, @@ -97,7 +95,7 @@ fun ChangeAccountProviderView( } ) { padding -> Box( - modifier = modifier + modifier = Modifier .fillMaxSize() .imePadding() .padding(padding) @@ -112,8 +110,9 @@ fun ChangeAccountProviderView( IconTitleSubtitleMolecule( modifier = Modifier.padding(top = 16.dp, bottom = 32.dp, start = 16.dp, end = 16.dp), iconImageVector = Icons.Filled.Home, + iconTint = MaterialTheme.colorScheme.primary, title = stringResource(id = R.string.screen_change_account_provider_title), - subTitle = stringResource(id = R.string.screen_change_account_provider_subtitle) + subTitle = stringResource(id = R.string.screen_change_account_provider_subtitle), ) if (slidingSyncNotSupportedError != null) { @@ -140,17 +139,7 @@ fun ChangeAccountProviderView( ), onClick = onOtherProviderClicked ) - Spacer(Modifier.height(32.dp)) - ButtonWithProgress( - text = stringResource(id = R.string.screen_change_server_submit), - showProgress = isLoading, - onClick = ::submit, - enabled = state.submitEnabled, - modifier = Modifier - .fillMaxWidth() - .testTag(TestTags.changeServerContinue) - ) if (state.changeServerAction is Async.Success) { onChangeServerSuccess() } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemView.kt index 96d7a26e7c..2c0d66c23b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemView.kt @@ -78,6 +78,7 @@ fun ChangeAccountProviderItemView( RoundedIconAtom( size = RoundedIconAtomSize.Medium, imageVector = Icons.Filled.Search, + tint = MaterialTheme.colorScheme.primary, ) } Text( diff --git a/features/login/impl/src/main/res/values/localazy.xml b/features/login/impl/src/main/res/values/localazy.xml index 39c35ac92f..0238d788f5 100644 --- a/features/login/impl/src/main/res/values/localazy.xml +++ b/features/login/impl/src/main/res/values/localazy.xml @@ -1,5 +1,10 @@ + "Change account provider" + "Continue" + "You’re about to sign in to %s" + "This is where you conversations will live — just like you would use an email provider to keep your emails." + "You’re about to create an account on %s" "Matrix.org is an open network for secure, decentralized communication." "Other" "Use a different account provider, such as your own private server or a work account." diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingNode.kt b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingNode.kt index a081c0b7ab..d86623cae2 100644 --- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingNode.kt +++ b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingNode.kt @@ -53,6 +53,7 @@ class OnBoardingNode @AssistedInject constructor( state = state, modifier = modifier, onSignIn = ::onSignIn, + onCreateAccount = ::onSignUp, ) } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt index 171e3655a0..c1365d4954 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt @@ -24,6 +24,7 @@ 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.graphics.vector.ImageVector import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -46,6 +47,7 @@ fun IconTitleSubtitleMolecule( modifier: Modifier = Modifier, iconResourceId: Int? = null, iconImageVector: ImageVector? = null, + iconTint: Color = Color.Unspecified, ) { Column(modifier) { RoundedIconAtom( @@ -54,6 +56,7 @@ fun IconTitleSubtitleMolecule( size = RoundedIconAtomSize.Large, resourceId = iconResourceId, imageVector = iconImageVector, + tint = iconTint, ) Spacer(modifier = Modifier.height(16.dp)) Text( diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 23908ecad8..8fa70179e3 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -74,7 +74,8 @@ "includeRegex": [ "screen_login_.*", "screen_change_server_.*", - "screen_change_account_provider_.*" + "screen_change_account_provider_.*", + "screen_account_provider_.*" ] }, { From 7ac8843bf8ed14e8885d7ac4384907763931751b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 6 Jun 2023 10:07:51 +0200 Subject: [PATCH 04/61] Add personal dict. --- .idea/dictionaries/bmarty.xml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .idea/dictionaries/bmarty.xml diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml new file mode 100644 index 0000000000..dd650c15e1 --- /dev/null +++ b/.idea/dictionaries/bmarty.xml @@ -0,0 +1,7 @@ + + + + homeserver + + + \ No newline at end of file From d8db9edafc18c571a3ea28d39cd4a76f3da30cbf Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 6 Jun 2023 14:40:16 +0200 Subject: [PATCH 05/61] Account provider form screen. --- features/login/impl/build.gradle.kts | 6 +- .../features/login/impl/LoginFlowNode.kt | 27 ++- .../accountprovider/AccountProviderView.kt | 3 +- .../ChangeAccountProviderEvents.kt | 3 - .../ChangeAccountProviderNode.kt | 20 +- .../ChangeAccountProviderPresenter.kt | 58 +---- .../ChangeAccountProviderState.kt | 13 +- .../ChangeAccountProviderStateProvider.kt | 13 +- .../ChangeAccountProviderView.kt | 65 ++---- .../form/ChangeAccountProviderFormEvents.kt | 24 +++ .../form/ChangeAccountProviderFormNode.kt | 56 +++++ .../ChangeAccountProviderFormPresenter.kt | 71 ++++++ .../form/ChangeAccountProviderFormView.kt | 202 ++++++++++++++++++ .../form/ChangeAccountProviderState.kt | 26 +++ .../ChangeAccountProviderStateProvider.kt | 53 +++++ .../form/HomeserverData.kt | 26 +++ .../form/HomeserverResolver.kt | 95 ++++++++ .../form/network/WellKnown.kt | 43 ++++ .../form/network/WellKnownBaseConfig.kt | 35 +++ .../form/network/WellknownAPI.kt | 23 ++ .../form/network/WellknownRequest.kt | 46 ++++ ...ProviderItem.kt => AccountProviderItem.kt} | 4 +- .../item/ChangeAccountProviderItemProvider.kt | 10 +- .../item/ChangeAccountProviderItemView.kt | 10 +- .../impl/src/main/res/values/localazy.xml | 6 +- 25 files changed, 810 insertions(+), 128 deletions(-) create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormEvents.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormNode.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderState.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderStateProvider.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverData.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverResolver.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnown.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownBaseConfig.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownAPI.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownRequest.kt rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/{ChangeAccountProviderItem.kt => AccountProviderItem.kt} (90%) diff --git a/features/login/impl/build.gradle.kts b/features/login/impl/build.gradle.kts index c43a5c2cd3..5f4563c3a9 100644 --- a/features/login/impl/build.gradle.kts +++ b/features/login/impl/build.gradle.kts @@ -19,7 +19,7 @@ plugins { alias(libs.plugins.anvil) alias(libs.plugins.ksp) id("kotlin-parcelize") -} + kotlin("plugin.serialization") version "1.8.21"} android { namespace = "io.element.android.features.login.impl" @@ -41,11 +41,15 @@ dependencies { implementation(projects.libraries.core) implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.network) implementation(projects.libraries.designsystem) implementation(projects.libraries.elementresources) implementation(projects.libraries.testtags) implementation(projects.libraries.uiStrings) implementation(libs.androidx.browser) + implementation(libs.network.retrofit) + implementation(libs.serialization.json) api(projects.features.login.api) ksp(libs.showkase.processor) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt index ad81d7c441..2d85ec7677 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt @@ -32,6 +32,9 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.features.login.impl.accountprovider.AccountProviderNode +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.changeaccountprovider.item.AccountProviderItem import io.element.android.features.login.impl.changeserver.ChangeServerNode import io.element.android.features.login.impl.oidc.CustomTabAvailabilityChecker import io.element.android.features.login.impl.oidc.customtab.CustomTabHandler @@ -82,6 +85,9 @@ class LoginFlowNode @AssistedInject constructor( @Parcelize object ChangeAccountProvider : NavTarget + @Parcelize + object ChangeAccountProviderForm : NavTarget + // Not used anymore @Parcelize object ChangeServer : NavTarget @@ -134,7 +140,26 @@ class LoginFlowNode @AssistedInject constructor( createNode(buildContext, plugins = listOf(inputs, callback)) } NavTarget.ChangeAccountProvider -> { - TODO() + val callback = object : ChangeAccountProviderNode.Callback { + override fun onAccountProviderItemClicked(data: AccountProviderItem) { + TODO("Not yet implemented") + } + + override fun onOtherClicked() { + backstack.push(NavTarget.ChangeAccountProviderForm) + } + } + + createNode(buildContext, plugins = listOf(callback)) + } + NavTarget.ChangeAccountProviderForm -> { + val callback = object : ChangeAccountProviderFormNode.Callback { + override fun onAccountProviderItemClicked(data: AccountProviderItem) { + TODO("Not yet implemented") + } + } + + createNode(buildContext, plugins = listOf(callback)) } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt index 60f21d0878..73f03a75e4 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt @@ -54,7 +54,8 @@ fun AccountProviderView( R.string.screen_account_provider_signup_title } else { R.string.screen_account_provider_signin_title - } + }, + state.homeserver ), subTitle = stringResource( id = if (state.isAccountCreation) { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderEvents.kt index 73b9929dca..0579006dfc 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderEvents.kt @@ -17,7 +17,4 @@ package io.element.android.features.login.impl.changeaccountprovider sealed interface ChangeAccountProviderEvents { - data class SetServer(val server: String) : ChangeAccountProviderEvents - object Submit : ChangeAccountProviderEvents - object ClearError : ChangeAccountProviderEvents } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt index 00b9d312c0..e54d4347d2 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt @@ -21,9 +21,11 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.login.impl.changeaccountprovider.item.AccountProviderItem import io.element.android.libraries.di.AppScope @ContributesNode(AppScope::class) @@ -33,14 +35,28 @@ class ChangeAccountProviderNode @AssistedInject constructor( private val presenter: ChangeAccountProviderPresenter, ) : Node(buildContext, plugins = plugins) { + interface Callback : Plugin { + fun onAccountProviderItemClicked(data: AccountProviderItem) + fun onOtherClicked() + } + + private fun onAccountProviderItemClicked(data: AccountProviderItem) { + plugins().forEach { it.onAccountProviderItemClicked(data) } + } + + private fun onOtherClicked() { + plugins().forEach { it.onOtherClicked() } + } + @Composable override fun View(modifier: Modifier) { val state = presenter.present() ChangeAccountProviderView( state = state, modifier = modifier, - // TODO - onBackPressed = {} + onBackPressed = ::navigateUp, + onAccountProviderItemClicked = ::onAccountProviderItemClicked, + onOtherProviderClicked = ::onOtherClicked, ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt index b4fbdac771..8749fbec06 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt @@ -17,63 +17,25 @@ package io.element.android.features.login.impl.changeaccountprovider import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable -import io.element.android.features.login.impl.changeserver.ChangeServerError -import io.element.android.features.login.impl.util.LoginConstants -import io.element.android.libraries.architecture.Async +import io.element.android.features.login.impl.changeaccountprovider.item.AccountProviderItem import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.architecture.execute -import io.element.android.libraries.core.data.tryOrNull -import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import java.net.URL import javax.inject.Inject class ChangeAccountProviderPresenter @Inject constructor( - private val authenticationService: MatrixAuthenticationService ) : Presenter { @Composable override fun present(): ChangeAccountProviderState { - val localCoroutineScope = rememberCoroutineScope() - - val homeserver = rememberSaveable { - mutableStateOf(authenticationService.getHomeserverDetails().value?.url ?: LoginConstants.DEFAULT_HOMESERVER_URL) - } - val changeServerAction: MutableState> = remember { - mutableStateOf(Async.Uninitialized) - } - - fun handleEvents(event: ChangeAccountProviderEvents) { - when (event) { - is ChangeAccountProviderEvents.SetServer -> { - homeserver.value = event.server - handleEvents(ChangeAccountProviderEvents.ClearError) - } - ChangeAccountProviderEvents.Submit -> { - localCoroutineScope.submit(homeserver, changeServerAction) - } - ChangeAccountProviderEvents.ClearError -> changeServerAction.value = Async.Uninitialized - } - } - return ChangeAccountProviderState( - homeserver = homeserver.value, - changeServerAction = changeServerAction.value, - eventSink = ::handleEvents + // Just matrix.org by default for now + accountProviderItems = listOf( + AccountProviderItem( + title = "matrix.org", + subtitle = null, + isPublic = true, + isMatrixOrg = true, + ) + ), ) } - - private fun CoroutineScope.submit(homeserverUrl: MutableState, changeServerAction: MutableState>) = launch { - suspend { - val domain = tryOrNull { URL(homeserverUrl.value) }?.host ?: homeserverUrl.value - authenticationService.setHomeserver(domain).getOrThrow() - homeserverUrl.value = domain - }.execute(changeServerAction, errorMapping = ChangeServerError::from) - } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt index 98f95ca357..62c756ad07 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt @@ -16,14 +16,9 @@ package io.element.android.features.login.impl.changeaccountprovider -import io.element.android.libraries.architecture.Async +import io.element.android.features.login.impl.changeaccountprovider.item.AccountProviderItem // Do not use default value, so no member get forgotten in the presenters. -data class ChangeAccountProviderState( - val homeserver: String, - val changeServerAction: Async, - val eventSink: (ChangeAccountProviderEvents) -> Unit -) { - // TODO Remove - val submitEnabled: Boolean get() = changeServerAction is Async.Uninitialized || changeServerAction is Async.Loading -} +data class ChangeAccountProviderState constructor( + val accountProviderItems: List, +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt index a176de572e..7707c66945 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt @@ -17,7 +17,7 @@ package io.element.android.features.login.impl.changeaccountprovider import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async +import io.element.android.features.login.impl.changeaccountprovider.item.AccountProviderItem open class ChangeAccountProviderStateProvider : PreviewParameterProvider { override val values: Sequence @@ -28,7 +28,12 @@ open class ChangeAccountProviderStateProvider : PreviewParameterProvider Unit, + onAccountProviderItemClicked: (AccountProviderItem) -> Unit = {}, onOtherProviderClicked: () -> Unit = {}, - onChangeServerSuccess: () -> Unit = {}, ) { - val eventSink = state.eventSink val scrollState = rememberScrollState() - val isLoading by remember(state.changeServerAction) { - derivedStateOf { - state.changeServerAction is Async.Loading - } - } - val invalidHomeserverError = (state.changeServerAction as? Async.Failure)?.error as? ChangeServerError.InlineErrorMessage - val slidingSyncNotSupportedError = (state.changeServerAction as? Async.Failure)?.error as? ChangeServerError.SlidingSyncAlert - val focusManager = LocalFocusManager.current - - fun submit() { - // Clear focus to prevent keyboard issues with textfields - focusManager.clearFocus(force = true) - - eventSink(ChangeAccountProviderEvents.Submit) - } Scaffold( modifier = modifier, @@ -115,34 +92,30 @@ fun ChangeAccountProviderView( subTitle = stringResource(id = R.string.screen_change_account_provider_subtitle), ) - if (slidingSyncNotSupportedError != null) { - SlidingSyncNotSupportedDialog(onLearnMoreClicked = { - eventSink(ChangeAccountProviderEvents.ClearError) - }, onDismiss = { - eventSink(ChangeAccountProviderEvents.ClearError) - }) - } - ChangeAccountProviderItemView( - item = ChangeAccountProviderItem( - title = "matrix.org", - subtitle = stringResource(id = R.string.screen_change_account_provider_matrix_org_subtitle), - isPublic = true, - isMatrix = true, - ), - onClick = { - TODO() + state.accountProviderItems.forEach { item -> + val alteredItem = if (item.isMatrixOrg) { + // Set the subtitle from the resource + item.copy( + subtitle = stringResource(id = R.string.screen_change_account_provider_matrix_org_subtitle), + ) + } else { + item } - ) + ChangeAccountProviderItemView( + item = alteredItem, + onClick = { + onAccountProviderItemClicked(alteredItem) + } + ) + } + // Other ChangeAccountProviderItemView( - item = ChangeAccountProviderItem( + item = AccountProviderItem( title = stringResource(id = R.string.screen_change_account_provider_other), ), onClick = onOtherProviderClicked ) Spacer(Modifier.height(32.dp)) - if (state.changeServerAction is Async.Success) { - onChangeServerSuccess() - } } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormEvents.kt new file mode 100644 index 0000000000..2e1180a211 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormEvents.kt @@ -0,0 +1,24 @@ +/* + * 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 + +sealed interface ChangeAccountProviderFormEvents { + /** + * The user has typed something, expect to get a list of result in the state + */ + data class UserInput(val input: String) : ChangeAccountProviderFormEvents +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormNode.kt new file mode 100644 index 0000000000..28e48f7263 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormNode.kt @@ -0,0 +1,56 @@ +/* + * 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 androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.login.impl.changeaccountprovider.item.AccountProviderItem +import io.element.android.libraries.di.AppScope + +@ContributesNode(AppScope::class) +class ChangeAccountProviderFormNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: ChangeAccountProviderFormPresenter, +) : Node(buildContext, plugins = plugins) { + + interface Callback : Plugin { + fun onAccountProviderItemClicked(data: AccountProviderItem) + } + + private fun onAccountProviderItemClicked(data: AccountProviderItem) { + plugins().forEach { it.onAccountProviderItemClicked(data) } + } + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + ChangeAccountProviderFormView( + state = state, + modifier = modifier, + onBackPressed = ::navigateUp, + onProviderClicked = ::onAccountProviderItemClicked + ) + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt new file mode 100644 index 0000000000..24da7892ee --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt @@ -0,0 +1,71 @@ +/* + * 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 androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.architecture.execute +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import javax.inject.Inject + +class ChangeAccountProviderFormPresenter @Inject constructor( + private val homeserverResolver: HomeserverResolver, +) : Presenter { + + @Composable + override fun present(): ChangeAccountProviderFormState { + val localCoroutineScope = rememberCoroutineScope() + + var currentJob: Job? = remember { null } + + val userInput = rememberSaveable { + mutableStateOf("") + } + val userInputResult: MutableState>> = remember { + mutableStateOf(Async.Uninitialized) + } + + fun handleEvents(event: ChangeAccountProviderFormEvents) { + when (event) { + is ChangeAccountProviderFormEvents.UserInput -> { + currentJob?.cancel() + currentJob = localCoroutineScope.userInput(event.input, userInputResult) + } + } + } + + return ChangeAccountProviderFormState( + userInput = userInput.value, + userInputResult = userInputResult.value, + eventSink = ::handleEvents + ) + } + + private fun CoroutineScope.userInput(userInput: String, userInputResult: MutableState>>) = launch { + suspend { + homeserverResolver.resolve(userInput) + }.execute(userInputResult) + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt new file mode 100644 index 0000000000..54ddd5f10e --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt @@ -0,0 +1,202 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class) + +package io.element.android.features.login.impl.changeaccountprovider.form + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Home +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +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.changeaccountprovider.item.AccountProviderItem +import io.element.android.features.login.impl.changeaccountprovider.item.ChangeAccountProviderItemView +import io.element.android.libraries.architecture.Async +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.form.textFieldState +import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.IconButton +import io.element.android.libraries.designsystem.theme.components.OutlinedTextField +import io.element.android.libraries.designsystem.theme.components.Text + +/** + * https://www.figma.com/file/o9p34zmiuEpZRyvZXJZAYL/FTUE?type=design&node-id=611-61435 + */ +@Composable +fun ChangeAccountProviderFormView( + state: ChangeAccountProviderFormState, + modifier: Modifier = Modifier, + onBackPressed: () -> Unit, + onProviderClicked: (AccountProviderItem) -> Unit = {}, +) { + val eventSink = state.eventSink + val scrollState = rememberScrollState() + + Scaffold( + modifier = modifier, + topBar = { + TopAppBar( + title = {}, + navigationIcon = { BackButton(onClick = onBackPressed) } + ) + } + ) { padding -> + Box( + modifier = Modifier + .fillMaxSize() + .imePadding() + .padding(padding) + .consumeWindowInsets(padding) + ) { + Column( + modifier = Modifier + .verticalScroll( + state = scrollState, + ) + ) { + IconTitleSubtitleMolecule( + modifier = Modifier.padding(top = 16.dp, bottom = 40.dp, start = 16.dp, end = 16.dp), + iconImageVector = Icons.Filled.Home, + iconTint = MaterialTheme.colorScheme.primary, + title = stringResource(id = R.string.screen_account_provider_form_title), + subTitle = stringResource(id = R.string.screen_account_provider_form_subtitle), + ) + + // TextInput + var userInputState by textFieldState(stateValue = state.userInput) + + OutlinedTextField( + value = userInputState, + // readOnly = isLoading, + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, bottom = 30.dp), + onValueChange = { + userInputState = it + eventSink(ChangeAccountProviderFormEvents.UserInput(it)) + }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Uri, + imeAction = ImeAction.Done, + ), + singleLine = true, + maxLines = 1, + trailingIcon = if (userInputState.isNotEmpty()) { + { + IconButton(onClick = { + userInputState = "" + eventSink(ChangeAccountProviderFormEvents.UserInput("")) + }) { + Icon( + imageVector = Icons.Filled.Close, + contentDescription = stringResource(io.element.android.libraries.ui.strings.R.string.action_clear) + ) + } + } + } else null, + supportingText = { + Text(text = stringResource(id = R.string.screen_account_provider_form_notice), color = MaterialTheme.colorScheme.secondary) + } + ) + + when (state.userInputResult) { + is Async.Failure -> { + // Ignore errors (let the user type more chars) + } + is Async.Loading -> { + Box( + modifier = Modifier + .fillMaxSize() + ) { + CircularProgressIndicator( + modifier = Modifier.align(Alignment.Center) + ) + } + } + is Async.Success -> { + state.userInputResult.state.forEach { homeserverData -> + val isMatrixOrg = homeserverData.homeserverUrl == "https://matrix.org" + val item = AccountProviderItem( + title = homeserverData.homeserverUrl.removePrefix("http://").removePrefix("https://"), + subtitle = if (isMatrixOrg) stringResource(id = R.string.screen_change_account_provider_matrix_org_subtitle) else null, + isPublic = isMatrixOrg, // There is no need to know for other servers right now + isMatrixOrg = isMatrixOrg, + ) + ChangeAccountProviderItemView( + item = item, + onClick = { + onProviderClicked(item) + } + ) + } + } + Async.Uninitialized -> Unit + } + Spacer(Modifier.height(32.dp)) + } + } + } +} + +@Preview +@Composable +fun ChangeAccountProviderFormViewLightPreview(@PreviewParameter(ChangeAccountProviderStateFormProvider::class) state: ChangeAccountProviderFormState) = + ElementPreviewLight { ContentToPreview(state) } + +@Preview +@Composable +fun ChangeAccountProviderFormViewDarkPreview(@PreviewParameter(ChangeAccountProviderStateFormProvider::class) state: ChangeAccountProviderFormState) = + ElementPreviewDark { ContentToPreview(state) } + +@Composable +private fun ContentToPreview(state: ChangeAccountProviderFormState) { + ChangeAccountProviderFormView( + state = state, + onBackPressed = { } + ) +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderState.kt new file mode 100644 index 0000000000..e87180b954 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderState.kt @@ -0,0 +1,26 @@ +/* + * 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 + +// Do not use default value, so no member get forgotten in the presenters. +data class ChangeAccountProviderFormState( + val userInput: String, + val userInputResult: Async>, + val eventSink: (ChangeAccountProviderFormEvents) -> Unit +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderStateProvider.kt new file mode 100644 index 0000000000..c61e03cca3 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderStateProvider.kt @@ -0,0 +1,53 @@ +/* + * 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 androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.Async + +open class ChangeAccountProviderStateFormProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aChangeAccountProviderFormState(), + aChangeAccountProviderFormState(userInputResult = Async.Success(aHomeserverDataList())), + // Add other state here + ) +} + +fun aChangeAccountProviderFormState( + userInput: String = "", + userInputResult: Async> = Async.Uninitialized, +) = ChangeAccountProviderFormState( + userInput = userInput, + userInputResult = userInputResult, + eventSink = {} +) + +fun aHomeserverDataList(): List { + return listOf( + HomeserverData( + userInput = "matrix", + homeserverUrl = "https://matrix.org", + isWellknownValid = true, + ), + HomeserverData( + userInput = "matrix", + homeserverUrl = "https://matrix.io", + isWellknownValid = false, + ) + ) +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverData.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverData.kt new file mode 100644 index 0000000000..6d254e3738 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverData.kt @@ -0,0 +1,26 @@ +/* + * 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 + +data class HomeserverData( + // What the user has entered + val userInput: String, + // The computed homeserver url, for which a wellknown file has been retrieved, or just a valid Url + val homeserverUrl: String, + // True if a wellknown file has been found and is valid. If false, it means that the [homeserverUrl] is valid + val isWellknownValid: Boolean, +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverResolver.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverResolver.kt new file mode 100644 index 0000000000..fceddaf42f --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverResolver.kt @@ -0,0 +1,95 @@ +/* + * 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.features.login.impl.changeaccountprovider.form.network.WellknownRequest +import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.uri.ensureProtocol +import io.element.android.libraries.core.uri.isValidUrl +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.withContext +import javax.inject.Inject + +/** + * Resolve homeserver base on search terms + */ +class HomeserverResolver @Inject constructor( + private val dispatchers: CoroutineDispatchers, + private val wellknownRequest: WellknownRequest, +) { + + suspend fun resolve(userInput: String): List { + return withContext(dispatchers.io) { + val cleanedUpUserInput = userInput.trim() + if (cleanedUpUserInput.length < 4) { + // Wait for more chars + emptyList() + } else { + val list = getUrlCandidate(cleanedUpUserInput) + val resolvedList = resolveList(userInput, list) + + // If list is empty, and the user as entered an URL, do not block the user. + if (resolvedList.isEmpty() && userInput.isValidUrl()) { + listOf( + HomeserverData( + userInput = userInput, + homeserverUrl = userInput, + isWellknownValid = false + ) + ) + } else { + resolvedList + } + } + } + } + + private suspend fun resolveList(userInput: String, list: List): List { + return coroutineScope { + buildList { + list.map { + async { + val isValid = wellknownRequest.execute(it) + if (isValid) { + add(HomeserverData(userInput, it, true)) + } + } + }.joinAll() + } + } + } + + private fun getUrlCandidate(data: String): List { + return buildList { + val s = data.ensureProtocol() + .removeSuffix("/") + + // Always try what the user has entered + add(s) + + if (s.contains(".")) { + // TLD detected? + } else { + add("$s.org") + add("$s.com") + add("$s.io") + } + } + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnown.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnown.kt new file mode 100644 index 0000000000..a78f41a266 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnown.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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.network + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery + *
+ * {
+ *     "m.homeserver": {
+ *         "base_url": "https://matrix.org"
+ *     },
+ *     "m.identity_server": {
+ *         "base_url": "https://vector.im"
+ *     }
+ * }
+ * 
+ * . + */ +@Serializable +data class WellKnown( + @SerialName("m.homeserver") + val homeServer: WellKnownBaseConfig? = null, + + @SerialName("m.identity_server") + val identityServer: WellKnownBaseConfig? = null, +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownBaseConfig.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownBaseConfig.kt new file mode 100644 index 0000000000..b0d33e1854 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownBaseConfig.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * 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.network + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery + *
+ * {
+ *     "base_url": "https://element.io"
+ * }
+ * 
+ * . + */ +@Serializable +data class WellKnownBaseConfig( + @SerialName("base_url") + val baseURL: String? = null +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownAPI.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownAPI.kt new file mode 100644 index 0000000000..13153cfabb --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownAPI.kt @@ -0,0 +1,23 @@ +/* + * 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.network + +import retrofit2.http.GET + +internal interface WellknownAPI { + @GET(".well-known/matrix/client") + suspend fun getWellKnown(): WellKnown +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownRequest.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownRequest.kt new file mode 100644 index 0000000000..a8d911ee86 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownRequest.kt @@ -0,0 +1,46 @@ +/* + * 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.network + +import io.element.android.libraries.core.bool.orFalse +import io.element.android.libraries.network.RetrofitFactory +import timber.log.Timber +import javax.inject.Inject + +class WellknownRequest @Inject constructor( + private val retrofitFactory: RetrofitFactory, +) { + /** + * Return true if the wellknown can be retrieved and is valid + * @param baseUrl for instance https://matrix.org + */ + suspend fun execute(baseUrl: String): Boolean { + val wellknownApi = retrofitFactory.create(baseUrl) + .create(WellknownAPI::class.java) + + return try { + val response = wellknownApi.getWellKnown() + response.isValid() + } catch (throwable: Throwable) { + Timber.e(throwable) + false + } + } +} + +private fun WellKnown.isValid(): Boolean { + return homeServer?.baseURL?.isNotBlank().orFalse() +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItem.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/AccountProviderItem.kt similarity index 90% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItem.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/AccountProviderItem.kt index c913923205..22c8079c48 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItem.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/AccountProviderItem.kt @@ -16,9 +16,9 @@ package io.element.android.features.login.impl.changeaccountprovider.item -data class ChangeAccountProviderItem( +data class AccountProviderItem constructor( val title: String, val subtitle: String? = null, val isPublic: Boolean = false, - val isMatrix: Boolean = false, + val isMatrixOrg: Boolean = false, ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemProvider.kt index bf7a92cf08..523b4143d3 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemProvider.kt @@ -18,19 +18,19 @@ package io.element.android.features.login.impl.changeaccountprovider.item import androidx.compose.ui.tooling.preview.PreviewParameterProvider -open class ChangeAccountProviderItemProvider : PreviewParameterProvider { - override val values: Sequence +open class ChangeAccountProviderItemProvider : PreviewParameterProvider { + override val values: Sequence get() = sequenceOf( aChangeAccountProviderItem(), aChangeAccountProviderItem().copy(subtitle = null), - aChangeAccountProviderItem().copy(title = "Other", subtitle = null, isPublic = false, isMatrix = false), + aChangeAccountProviderItem().copy(title = "Other", subtitle = null, isPublic = false, isMatrixOrg = false), // Add other state here ) } -fun aChangeAccountProviderItem() = ChangeAccountProviderItem( +fun aChangeAccountProviderItem() = AccountProviderItem( title = "matrix.org", subtitle = "Matrix.org is an open network for secure, decentralized communication.", isPublic = true, - isMatrix = true, + isMatrixOrg = true, ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemView.kt index 2c0d66c23b..867666e59c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemView.kt @@ -49,7 +49,7 @@ import io.element.android.libraries.designsystem.theme.components.Text */ @Composable fun ChangeAccountProviderItemView( - item: ChangeAccountProviderItem, + item: AccountProviderItem, modifier: Modifier = Modifier, onClick: () -> Unit, ) { @@ -68,7 +68,7 @@ fun ChangeAccountProviderItemView( .heightIn(min = 44.dp), verticalAlignment = Alignment.CenterVertically ) { - if (item.isMatrix) { + if (item.isMatrixOrg) { RoundedIconAtom( size = RoundedIconAtomSize.Medium, resourceId = R.drawable.ic_matrix, @@ -115,16 +115,16 @@ fun ChangeAccountProviderItemView( @Preview @Composable -fun ChangeAccountProviderViewLightPreview(@PreviewParameter(ChangeAccountProviderItemProvider::class) item: ChangeAccountProviderItem) = +fun ChangeAccountProviderViewLightPreview(@PreviewParameter(ChangeAccountProviderItemProvider::class) item: AccountProviderItem) = ElementPreviewLight { ContentToPreview(item) } @Preview @Composable -fun ChangeAccountProviderViewDarkPreview(@PreviewParameter(ChangeAccountProviderItemProvider::class) item: ChangeAccountProviderItem) = +fun ChangeAccountProviderViewDarkPreview(@PreviewParameter(ChangeAccountProviderItemProvider::class) item: AccountProviderItem) = ElementPreviewDark { ContentToPreview(item) } @Composable -private fun ContentToPreview(item: ChangeAccountProviderItem) { +private fun ContentToPreview(item: AccountProviderItem) { ChangeAccountProviderItemView( item = item, onClick = { } diff --git a/features/login/impl/src/main/res/values/localazy.xml b/features/login/impl/src/main/res/values/localazy.xml index 0238d788f5..96d05df794 100644 --- a/features/login/impl/src/main/res/values/localazy.xml +++ b/features/login/impl/src/main/res/values/localazy.xml @@ -2,6 +2,10 @@ "Change account provider" "Continue" + "Homeserver address" + "Enter a search term or a domain address." + "Search for a company, community, or private server." + "Find an account provider" "You’re about to sign in to %s" "This is where you conversations will live — just like you would use an email provider to keep your emails." "You’re about to create an account on %s" @@ -27,4 +31,4 @@ "Password" "Continue" "Username" - \ No newline at end of file +
From 22ed3b7bfdd7c660a3539d3ba05d3031ae298b7c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 6 Jun 2023 15:44:06 +0200 Subject: [PATCH 06/61] Navigation --- .../features/login/impl/LoginFlowNode.kt | 17 ++++-- .../accountprovider/AccountProviderEvents.kt | 4 +- .../accountprovider/AccountProviderNode.kt | 25 +++++--- .../AccountProviderPresenter.kt | 48 +++++++++++++-- .../accountprovider/AccountProviderState.kt | 8 ++- .../AccountProviderStateProvider.kt | 2 + .../accountprovider/AccountProviderView.kt | 60 +++++++++++++++---- .../datasource/AccountProviderDataSource.kt | 42 +++++++++++++ .../login/impl/util/LoginConstants.kt | 10 ++++ 9 files changed, 183 insertions(+), 33 deletions(-) create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/datasource/AccountProviderDataSource.kt diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt index 2d85ec7677..75681099b1 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt @@ -28,6 +28,7 @@ import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.push +import com.bumble.appyx.navmodel.backstack.operation.singleTop import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode @@ -36,11 +37,11 @@ import io.element.android.features.login.impl.changeaccountprovider.ChangeAccoun import io.element.android.features.login.impl.changeaccountprovider.form.ChangeAccountProviderFormNode import io.element.android.features.login.impl.changeaccountprovider.item.AccountProviderItem import io.element.android.features.login.impl.changeserver.ChangeServerNode +import io.element.android.features.login.impl.datasource.AccountProviderDataSource 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.webview.OidcNode import io.element.android.features.login.impl.root.LoginRootNode -import io.element.android.features.login.impl.util.LoginConstants import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler @@ -57,6 +58,7 @@ class LoginFlowNode @AssistedInject constructor( @Assisted plugins: List, private val customTabAvailabilityChecker: CustomTabAvailabilityChecker, private val customTabHandler: CustomTabHandler, + private val accountProviderDataSource: AccountProviderDataSource, ) : BackstackNode( backstack = BackStack( initialElement = NavTarget.AccountProvider, // NavTarget.Root, @@ -124,12 +126,11 @@ class LoginFlowNode @AssistedInject constructor( } NavTarget.AccountProvider -> { val inputs = AccountProviderNode.Inputs( - homeserver = LoginConstants.DEFAULT_HOMESERVER_URL, - isMatrixOrg = LoginConstants.DEFAULT_HOMESERVER_URL == "matrix.org", isAccountCreation = inputs.isAccountCreation ) val callback = object : AccountProviderNode.Callback { - override fun onContinue() { + override fun onServerValidated() { + // TODO TODO("Not yet implemented") } @@ -142,7 +143,9 @@ class LoginFlowNode @AssistedInject constructor( NavTarget.ChangeAccountProvider -> { val callback = object : ChangeAccountProviderNode.Callback { override fun onAccountProviderItemClicked(data: AccountProviderItem) { - TODO("Not yet implemented") + accountProviderDataSource.userSelection(data) + // Go back to the Account Provider screen + backstack.singleTop(NavTarget.AccountProvider) } override fun onOtherClicked() { @@ -155,7 +158,9 @@ class LoginFlowNode @AssistedInject constructor( NavTarget.ChangeAccountProviderForm -> { val callback = object : ChangeAccountProviderFormNode.Callback { override fun onAccountProviderItemClicked(data: AccountProviderItem) { - TODO("Not yet implemented") + accountProviderDataSource.userSelection(data) + // Go back to the Account Provider screen + backstack.singleTop(NavTarget.AccountProvider) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderEvents.kt index 7a4cbf0366..ec3a879c89 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderEvents.kt @@ -16,7 +16,7 @@ package io.element.android.features.login.impl.accountprovider -// TODO Add your events or remove the file completely if no events sealed interface AccountProviderEvents { - object MyEvent : AccountProviderEvents + object Continue : AccountProviderEvents + object ClearError : AccountProviderEvents } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderNode.kt index bd1dbf24c2..3331080f23 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderNode.kt @@ -16,8 +16,12 @@ package io.element.android.features.login.impl.accountprovider +import android.content.Context +import android.content.Intent +import android.net.Uri import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin @@ -25,8 +29,10 @@ import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.login.impl.util.LoginConstants import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.di.AppScope @ContributesNode(AppScope::class) @@ -37,41 +43,44 @@ class AccountProviderNode @AssistedInject constructor( ) : Node(buildContext, plugins = plugins) { data class Inputs( - val homeserver: String, - val isMatrixOrg: Boolean, val isAccountCreation: Boolean, ) : NodeInputs private val inputs: Inputs = inputs() private val presenter = presenterFactory.create( AccountProviderPresenterParams( - homeserver = inputs.homeserver, - isMatrixOrg = inputs.isMatrixOrg, isAccountCreation = inputs.isAccountCreation, ) ) interface Callback : Plugin { - fun onContinue() + fun onServerValidated() fun onChangeAccountProvider() } - private fun onContinue() { - plugins().forEach { it.onContinue() } + private fun onServerValidated() { + plugins().forEach { it.onServerValidated() } } private fun onChangeAccountProvider() { plugins().forEach { it.onChangeAccountProvider() } } + private fun openLearnMorePage(context: Context) { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(LoginConstants.SLIDING_SYNC_READ_MORE_URL)) + tryOrNull { context.startActivity(intent) } + } + @Composable override fun View(modifier: Modifier) { val state = presenter.present() + val context = LocalContext.current AccountProviderView( state = state, modifier = modifier, - onContinue = ::onContinue, + onChangeServerSuccess = ::onServerValidated, onChange = ::onChangeAccountProvider, + onLearnMoreClicked = { openLearnMorePage(context) }, ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt index 577d22ec7c..3561e2f19a 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt @@ -17,19 +17,35 @@ package io.element.android.features.login.impl.accountprovider import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import io.element.android.features.login.impl.changeserver.ChangeServerError +import io.element.android.features.login.impl.datasource.AccountProviderDataSource +import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.architecture.execute +import io.element.android.libraries.core.data.tryOrNull +import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import java.net.URL data class AccountProviderPresenterParams( - val homeserver: String, - val isMatrixOrg: Boolean, val isAccountCreation: Boolean, ) class AccountProviderPresenter @AssistedInject constructor( @Assisted private val params: AccountProviderPresenterParams, + private val accountProviderDataSource: AccountProviderDataSource, + private val authenticationService: MatrixAuthenticationService ) : Presenter { @AssistedFactory @@ -39,18 +55,40 @@ class AccountProviderPresenter @AssistedInject constructor( @Composable override fun present(): AccountProviderState { + val accountProvider by accountProviderDataSource.flow().collectAsState() + + val localCoroutineScope = rememberCoroutineScope() + + val homeserver = rememberSaveable { + mutableStateOf(accountProvider.title /* TODO There is a mix of data and UI here, which is not nice */) + } + val changeServerAction: MutableState> = remember { + mutableStateOf(Async.Uninitialized) + } fun handleEvents(event: AccountProviderEvents) { when (event) { - AccountProviderEvents.MyEvent -> Unit + AccountProviderEvents.Continue -> { + localCoroutineScope.submit(homeserver, changeServerAction) + } + AccountProviderEvents.ClearError -> changeServerAction.value = Async.Uninitialized } } return AccountProviderState( - homeserver = params.homeserver, - isMatrix = params.isMatrixOrg, + homeserver = accountProvider.title, + isMatrix = accountProvider.isMatrixOrg, isAccountCreation = params.isAccountCreation, + changeServerAction = changeServerAction.value, eventSink = ::handleEvents ) } + + private fun CoroutineScope.submit(homeserverUrl: MutableState, changeServerAction: MutableState>) = launch { + suspend { + val domain = tryOrNull { URL(homeserverUrl.value) }?.host ?: homeserverUrl.value + authenticationService.setHomeserver(domain).getOrThrow() + homeserverUrl.value = domain + }.execute(changeServerAction, errorMapping = ChangeServerError::from) + } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderState.kt index 1bee64e88d..d7462895d4 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderState.kt @@ -16,10 +16,16 @@ package io.element.android.features.login.impl.accountprovider +import io.element.android.libraries.architecture.Async + // Do not use default value, so no member get forgotten in the presenters. data class AccountProviderState( val homeserver: String, val isMatrix: Boolean, val isAccountCreation: Boolean, + // TODO Rename + val changeServerAction: Async, val eventSink: (AccountProviderEvents) -> Unit -) +) { + val submitEnabled: Boolean get() = homeserver.isNotEmpty() && (changeServerAction is Async.Uninitialized || changeServerAction is Async.Loading) +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderStateProvider.kt index 35452cd256..908888f2a1 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderStateProvider.kt @@ -17,6 +17,7 @@ package io.element.android.features.login.impl.accountprovider import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.Async open class AccountProviderStateProvider : PreviewParameterProvider { override val values: Sequence @@ -30,5 +31,6 @@ fun aAccountProviderState() = AccountProviderState( homeserver = "matrix.org", isMatrix = true, isAccountCreation = false, + changeServerAction = Async.Uninitialized, eventSink = {} ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt index 73f03a75e4..5989dfa8bd 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt @@ -21,28 +21,49 @@ 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.changeserver.ChangeServerError +import io.element.android.features.login.impl.changeserver.ChangeServerEvents +import io.element.android.features.login.impl.changeserver.SlidingSyncNotSupportedDialog +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.Button import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextButton +import io.element.android.libraries.testtags.TestTags +import io.element.android.libraries.testtags.testTag @Composable fun AccountProviderView( state: AccountProviderState, modifier: Modifier = Modifier, - onContinue: () -> Unit = {}, + // TODO Rename + onChangeServerSuccess: () -> Unit = {}, + onLearnMoreClicked: () -> Unit = {}, onChange: () -> Unit = {}, ) { + val isLoading by remember(state.changeServerAction) { + derivedStateOf { + state.changeServerAction is Async.Loading + } + } + val eventSink = state.eventSink + val invalidHomeserverError = (state.changeServerAction as? Async.Failure)?.error as? ChangeServerError.InlineErrorMessage + val slidingSyncNotSupportedError = (state.changeServerAction as? Async.Failure)?.error as? ChangeServerError.SlidingSyncAlert + HeaderFooterPage( modifier = modifier, header = { @@ -69,16 +90,15 @@ fun AccountProviderView( }, footer = { ButtonColumnMolecule { - Button( - onClick = { - onContinue() - }, - enabled = true, + ButtonWithProgress( + text = stringResource(id = R.string.screen_account_provider_continue), + showProgress = isLoading, + onClick = { eventSink.invoke(AccountProviderEvents.Continue) }, + enabled = state.submitEnabled, modifier = Modifier .fillMaxWidth() - ) { - Text(text = stringResource(id = R.string.screen_account_provider_continue)) - } + .testTag(TestTags.changeServerContinue) + ) TextButton( onClick = { onChange() @@ -92,7 +112,25 @@ fun AccountProviderView( } } ) { - // No content + if (slidingSyncNotSupportedError != null) { + SlidingSyncNotSupportedDialog(onLearnMoreClicked = { + onLearnMoreClicked() + eventSink(AccountProviderEvents.ClearError) + }, onDismiss = { + eventSink(AccountProviderEvents.ClearError) + }) + } + if (invalidHomeserverError != null) { + ErrorDialog( + content = invalidHomeserverError.message(), + onDismiss = { + eventSink.invoke(AccountProviderEvents.ClearError) + } + ) + } + } + if (state.changeServerAction is Async.Success) { + onChangeServerSuccess() } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/datasource/AccountProviderDataSource.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/datasource/AccountProviderDataSource.kt new file mode 100644 index 0000000000..9a548f7aab --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/datasource/AccountProviderDataSource.kt @@ -0,0 +1,42 @@ +/* + * 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.datasource + +import io.element.android.features.login.impl.changeaccountprovider.item.AccountProviderItem +import io.element.android.features.login.impl.util.defaultAccountProviderItem +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.SingleIn +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import javax.inject.Inject + +@SingleIn(AppScope::class) +class AccountProviderDataSource @Inject constructor( +) { + private val accountProvider: MutableStateFlow = MutableStateFlow( + defaultAccountProviderItem + ) + + fun flow(): StateFlow { + return accountProvider.asStateFlow() + } + + fun userSelection(data: AccountProviderItem) { + accountProvider.tryEmit(data) + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/LoginConstants.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/LoginConstants.kt index cb01f8095a..06ffcb94f4 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/LoginConstants.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/LoginConstants.kt @@ -16,8 +16,18 @@ package io.element.android.features.login.impl.util +import io.element.android.features.login.impl.changeaccountprovider.item.AccountProviderItem + object LoginConstants { + const val MATRIX_ORG_URL = "matrix.org" const val DEFAULT_HOMESERVER_URL = "matrix.org" // TODO Oidc "synapse-oidc.lab.element.dev" const val SLIDING_SYNC_READ_MORE_URL = "https://github.com/matrix-org/sliding-sync/blob/main/docs/Landing.md" } + +val defaultAccountProviderItem = AccountProviderItem( + title = LoginConstants.DEFAULT_HOMESERVER_URL, + subtitle = null, + isPublic = LoginConstants.DEFAULT_HOMESERVER_URL == LoginConstants.MATRIX_ORG_URL, + isMatrixOrg = LoginConstants.DEFAULT_HOMESERVER_URL == LoginConstants.MATRIX_ORG_URL, +) From 32f953aa83abe043cbd4acf7180774c496633ec3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 6 Jun 2023 18:16:35 +0200 Subject: [PATCH 07/61] LoginPasswordNode --- .../features/login/impl/LoginFlowNode.kt | 22 +- .../accountprovider/AccountProviderNode.kt | 15 +- .../AccountProviderPresenter.kt | 41 ++- .../accountprovider/AccountProviderState.kt | 11 +- .../AccountProviderStateProvider.kt | 2 +- .../accountprovider/AccountProviderView.kt | 37 ++- .../impl/loginpassword/LoginPasswordEvents.kt | 24 ++ .../impl/loginpassword/LoginPasswordNode.kt | 45 +++ .../loginpassword/LoginPasswordPresenter.kt | 84 +++++ .../impl/loginpassword/LoginPasswordState.kt | 43 +++ .../LoginPasswordStateProvider.kt | 37 +++ .../impl/loginpassword/LoginPasswordView.kt | 305 ++++++++++++++++++ 12 files changed, 636 insertions(+), 30 deletions(-) create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordEvents.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordNode.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordPresenter.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordState.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordStateProvider.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt index 75681099b1..8f0564cf33 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt @@ -38,6 +38,7 @@ import io.element.android.features.login.impl.changeaccountprovider.form.ChangeA import io.element.android.features.login.impl.changeaccountprovider.item.AccountProviderItem import io.element.android.features.login.impl.changeserver.ChangeServerNode 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.customtab.CustomTabHandler import io.element.android.features.login.impl.oidc.webview.OidcNode @@ -90,6 +91,9 @@ class LoginFlowNode @AssistedInject constructor( @Parcelize object ChangeAccountProviderForm : NavTarget + @Parcelize + object LoginPasswordForm : NavTarget + // Not used anymore @Parcelize object ChangeServer : NavTarget @@ -129,9 +133,18 @@ class LoginFlowNode @AssistedInject constructor( isAccountCreation = inputs.isAccountCreation ) val callback = object : AccountProviderNode.Callback { - override fun onServerValidated() { - // TODO - TODO("Not yet implemented") + override fun onOidcDetails(oidcDetails: OidcDetails) { + if (customTabAvailabilityChecker.supportCustomTab()) { + // In this case open a Chrome Custom tab + activity?.let { customTabHandler.open(it, darkTheme, oidcDetails.url) } + } else { + // Fallback to WebView mode + backstack.push(NavTarget.OidcView(oidcDetails)) + } + } + + override fun onLoginPasswordNeeded() { + backstack.push(NavTarget.LoginPasswordForm) } override fun onChangeAccountProvider() { @@ -166,6 +179,9 @@ class LoginFlowNode @AssistedInject constructor( createNode(buildContext, plugins = listOf(callback)) } + NavTarget.LoginPasswordForm -> { + createNode(buildContext, plugins = listOf()) + } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderNode.kt index 3331080f23..aac5068294 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderNode.kt @@ -34,6 +34,7 @@ import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.inputs import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.auth.OidcDetails @ContributesNode(AppScope::class) class AccountProviderNode @AssistedInject constructor( @@ -54,12 +55,17 @@ class AccountProviderNode @AssistedInject constructor( ) interface Callback : Plugin { - fun onServerValidated() + fun onLoginPasswordNeeded() + fun onOidcDetails(oidcDetails: OidcDetails) fun onChangeAccountProvider() } - private fun onServerValidated() { - plugins().forEach { it.onServerValidated() } + private fun onOidcDetails(data: OidcDetails) { + plugins().forEach { it.onOidcDetails(data) } + } + + private fun onLoginPasswordNeeded() { + plugins().forEach { it.onLoginPasswordNeeded() } } private fun onChangeAccountProvider() { @@ -78,7 +84,8 @@ class AccountProviderNode @AssistedInject constructor( AccountProviderView( state = state, modifier = modifier, - onChangeServerSuccess = ::onServerValidated, + onOidcDetails = ::onOidcDetails, + onLoginPasswordNeeded = ::onLoginPasswordNeeded, onChange = ::onChangeAccountProvider, onLearnMoreClicked = { openLearnMorePage(context) }, ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt index 3561e2f19a..b20a69fcae 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt @@ -29,11 +29,13 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.element.android.features.login.impl.changeserver.ChangeServerError import io.element.android.features.login.impl.datasource.AccountProviderDataSource +import io.element.android.features.login.impl.util.LoginConstants import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.execute import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService +import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import java.net.URL @@ -56,22 +58,29 @@ class AccountProviderPresenter @AssistedInject constructor( @Composable override fun present(): AccountProviderState { val accountProvider by accountProviderDataSource.flow().collectAsState() - + val currentHomeServerDetails = authenticationService.getHomeserverDetails().collectAsState().value + val getHomeServerDetailsAction: MutableState> = remember { + if (currentHomeServerDetails != null) { + mutableStateOf(Async.Success(currentHomeServerDetails)) + } else { + mutableStateOf(Async.Uninitialized) + } + } val localCoroutineScope = rememberCoroutineScope() val homeserver = rememberSaveable { - mutableStateOf(accountProvider.title /* TODO There is a mix of data and UI here, which is not nice */) + mutableStateOf(currentHomeServerDetails?.url ?: LoginConstants.DEFAULT_HOMESERVER_URL) } - val changeServerAction: MutableState> = remember { + val loginFlowAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } fun handleEvents(event: AccountProviderEvents) { when (event) { AccountProviderEvents.Continue -> { - localCoroutineScope.submit(homeserver, changeServerAction) + localCoroutineScope.submit(homeserver, loginFlowAction) } - AccountProviderEvents.ClearError -> changeServerAction.value = Async.Uninitialized + AccountProviderEvents.ClearError -> loginFlowAction.value = Async.Uninitialized } } @@ -79,16 +88,30 @@ class AccountProviderPresenter @AssistedInject constructor( homeserver = accountProvider.title, isMatrix = accountProvider.isMatrixOrg, isAccountCreation = params.isAccountCreation, - changeServerAction = changeServerAction.value, + loginFlow = loginFlowAction.value, eventSink = ::handleEvents ) } - private fun CoroutineScope.submit(homeserverUrl: MutableState, changeServerAction: MutableState>) = launch { + private fun CoroutineScope.submit( + homeserverUrl: MutableState, + loginFlowAction: MutableState>, + ) = launch { suspend { val domain = tryOrNull { URL(homeserverUrl.value) }?.host ?: homeserverUrl.value - authenticationService.setHomeserver(domain).getOrThrow() homeserverUrl.value = domain - }.execute(changeServerAction, errorMapping = ChangeServerError::from) + authenticationService.setHomeserver(domain).map { + authenticationService.getHomeserverDetails().value!! + }.map { + if (it.supportsOidcLogin) { + // Retrieve the details right now + LoginFlow.OidcFlow(authenticationService.getOidcUrl().getOrThrow()) + } else if (it.supportsPasswordLogin) { + LoginFlow.PasswordLogin + } else { + throw IllegalStateException("Unsupported login flow") + } + }.getOrThrow() + }.execute(loginFlowAction, errorMapping = ChangeServerError::from) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderState.kt index d7462895d4..fd6a7914fb 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderState.kt @@ -17,15 +17,20 @@ package io.element.android.features.login.impl.accountprovider import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.auth.OidcDetails // Do not use default value, so no member get forgotten in the presenters. data class AccountProviderState( val homeserver: String, val isMatrix: Boolean, val isAccountCreation: Boolean, - // TODO Rename - val changeServerAction: Async, + val loginFlow: Async, val eventSink: (AccountProviderEvents) -> Unit ) { - val submitEnabled: Boolean get() = homeserver.isNotEmpty() && (changeServerAction is Async.Uninitialized || changeServerAction is Async.Loading) + val submitEnabled: Boolean get() = homeserver.isNotEmpty() && (loginFlow is Async.Uninitialized || loginFlow is Async.Loading) +} + +sealed interface LoginFlow { + object PasswordLogin : LoginFlow + data class OidcFlow(val oidcDetails: OidcDetails) : LoginFlow } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderStateProvider.kt index 908888f2a1..9d68942dae 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderStateProvider.kt @@ -31,6 +31,6 @@ fun aAccountProviderState() = AccountProviderState( homeserver = "matrix.org", isMatrix = true, isAccountCreation = false, - changeServerAction = Async.Uninitialized, + loginFlow = Async.Uninitialized, eventSink = {} ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt index 5989dfa8bd..3548504802 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt @@ -31,18 +31,20 @@ 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.changeserver.ChangeServerError -import io.element.android.features.login.impl.changeserver.ChangeServerEvents import io.element.android.features.login.impl.changeserver.SlidingSyncNotSupportedDialog 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.async.AsyncFailure +import io.element.android.libraries.designsystem.components.async.AsyncLoading 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 @@ -50,19 +52,19 @@ import io.element.android.libraries.testtags.testTag fun AccountProviderView( state: AccountProviderState, modifier: Modifier = Modifier, - // TODO Rename - onChangeServerSuccess: () -> Unit = {}, + onOidcDetails: (OidcDetails) -> Unit = {}, + onLoginPasswordNeeded: () -> Unit = {}, onLearnMoreClicked: () -> Unit = {}, onChange: () -> Unit = {}, ) { - val isLoading by remember(state.changeServerAction) { + val isLoading by remember(state.loginFlow) { derivedStateOf { - state.changeServerAction is Async.Loading + state.loginFlow is Async.Loading } } val eventSink = state.eventSink - val invalidHomeserverError = (state.changeServerAction as? Async.Failure)?.error as? ChangeServerError.InlineErrorMessage - val slidingSyncNotSupportedError = (state.changeServerAction as? Async.Failure)?.error as? ChangeServerError.SlidingSyncAlert + val invalidHomeserverError = (state.loginFlow as? Async.Failure)?.error as? ChangeServerError.InlineErrorMessage + val slidingSyncNotSupportedError = (state.loginFlow as? Async.Failure)?.error as? ChangeServerError.SlidingSyncAlert HeaderFooterPage( modifier = modifier, @@ -112,6 +114,24 @@ fun AccountProviderView( } } ) { + when (state.loginFlow) { + is Async.Failure -> { + AsyncFailure( + throwable = state.loginFlow.error, + onRetry = { + state.eventSink.invoke(AccountProviderEvents.Continue) + } + ) + } + is Async.Loading -> AsyncLoading() + is Async.Success -> { + when (val loginFlowState = state.loginFlow.state) { + is LoginFlow.OidcFlow -> onOidcDetails(loginFlowState.oidcDetails) + LoginFlow.PasswordLogin -> onLoginPasswordNeeded() + } + } + Async.Uninitialized -> Unit + } if (slidingSyncNotSupportedError != null) { SlidingSyncNotSupportedDialog(onLearnMoreClicked = { onLearnMoreClicked() @@ -129,9 +149,6 @@ fun AccountProviderView( ) } } - if (state.changeServerAction is Async.Success) { - onChangeServerSuccess() - } } @Preview diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordEvents.kt new file mode 100644 index 0000000000..44b442dd08 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordEvents.kt @@ -0,0 +1,24 @@ +/* + * 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.loginpassword + +sealed interface LoginPasswordEvents { + data class SetLogin(val login: String) : LoginPasswordEvents + data class SetPassword(val password: String) : LoginPasswordEvents + object Submit : LoginPasswordEvents + object ClearError : LoginPasswordEvents +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordNode.kt new file mode 100644 index 0000000000..ea731f379b --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordNode.kt @@ -0,0 +1,45 @@ +/* + * 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.loginpassword + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.AppScope + +@ContributesNode(AppScope::class) +class LoginPasswordNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: LoginPasswordPresenter, +) : Node(buildContext, plugins = plugins) { + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + LoginPasswordView( + state = state, + modifier = modifier, + onBackPressed = ::navigateUp + ) + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordPresenter.kt new file mode 100644 index 0000000000..af495eb106 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordPresenter.kt @@ -0,0 +1,84 @@ +/* + * 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.loginpassword + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService +import io.element.android.libraries.matrix.api.core.SessionId +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import javax.inject.Inject + +class LoginPasswordPresenter @Inject constructor( + private val authenticationService: MatrixAuthenticationService, +) : Presenter { + + @Composable + override fun present(): LoginPasswordState { + val localCoroutineScope = rememberCoroutineScope() + val loginAction: MutableState> = remember { + mutableStateOf(Async.Uninitialized) + } + + val formState = rememberSaveable { + mutableStateOf(LoginFormState.Default) + } + + fun handleEvents(event: LoginPasswordEvents) { + when (event) { + is LoginPasswordEvents.SetLogin -> updateFormState(formState) { + copy(login = event.login) + } + is LoginPasswordEvents.SetPassword -> updateFormState(formState) { + copy(password = event.password) + } + LoginPasswordEvents.Submit -> { + localCoroutineScope.submit(formState.value, loginAction) + } + LoginPasswordEvents.ClearError -> loginAction.value = Async.Uninitialized + } + } + + return LoginPasswordState( + formState = formState.value, + loginAction = loginAction.value, + eventSink = ::handleEvents + ) + } + + private fun CoroutineScope.submit(formState: LoginFormState, loggedInState: MutableState>) = launch { + loggedInState.value = Async.Loading() + authenticationService.login(formState.login.trim(), formState.password) + .onSuccess { sessionId -> + loggedInState.value = Async.Success(sessionId) + } + .onFailure { failure -> + loggedInState.value = Async.Failure(failure) + } + } + + private fun updateFormState(formState: MutableState, updateLambda: LoginFormState.() -> LoginFormState) { + formState.value = updateLambda(formState.value) + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordState.kt new file mode 100644 index 0000000000..eac406a1c4 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordState.kt @@ -0,0 +1,43 @@ +/* + * 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.loginpassword + +import android.os.Parcelable +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.core.SessionId +import kotlinx.parcelize.Parcelize + +data class LoginPasswordState( + val formState: LoginFormState, + val loginAction: Async, + val eventSink: (LoginPasswordEvents) -> Unit +) { + val submitEnabled: Boolean + get() = loginAction !is Async.Failure && + ((formState.login.isNotEmpty() && formState.password.isNotEmpty())) +} + +@Parcelize +data class LoginFormState( + val login: String, + val password: String +) : Parcelable { + + companion object { + val Default = LoginFormState("", "") + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordStateProvider.kt new file mode 100644 index 0000000000..deb7e86acc --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordStateProvider.kt @@ -0,0 +1,37 @@ +/* + * 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.loginpassword + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.Async + +open class LoginPasswordStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aLoginPasswordState(), + // Loading + aLoginPasswordState().copy(loginAction = Async.Loading()), + // Error + aLoginPasswordState().copy(loginAction = Async.Failure(Exception("An error occurred"))), + ) +} + +fun aLoginPasswordState() = LoginPasswordState( + formState = LoginFormState.Default, + loginAction = Async.Uninitialized, + eventSink = {} +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt new file mode 100644 index 0000000000..f8b0345c01 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2022 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.loginpassword + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Visibility +import androidx.compose.material.icons.filled.VisibilityOff +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.autofill.AutofillType +import androidx.compose.ui.focus.FocusDirection +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +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.error.loginError +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.designsystem.ElementTextStyles +import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.components.button.ButtonWithProgress +import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog +import io.element.android.libraries.designsystem.components.form.textFieldState +import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.IconButton +import io.element.android.libraries.designsystem.theme.components.Scaffold +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.TextField +import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.designsystem.theme.components.autofill +import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext +import io.element.android.libraries.testtags.TestTags +import io.element.android.libraries.testtags.testTag +import io.element.android.libraries.ui.strings.R as StringR + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) +@Composable +fun LoginPasswordView( + state: LoginPasswordState, + modifier: Modifier = Modifier, + onBackPressed: () -> Unit, +) { + val isLoading by remember(state.loginAction) { + derivedStateOf { + state.loginAction is Async.Loading + } + } + val focusManager = LocalFocusManager.current + + fun submit() { + // Clear focus to prevent keyboard issues with textfields + focusManager.clearFocus(force = true) + + state.eventSink(LoginPasswordEvents.Submit) + } + + Scaffold( + topBar = { + TopAppBar( + title = {}, + navigationIcon = { BackButton(onClick = onBackPressed) }, + ) + } + ) { padding -> + Box( + modifier = modifier + .fillMaxSize() + .imePadding() + .padding(padding) + .consumeWindowInsets(padding) + ) { + val scrollState = rememberScrollState() + + Column( + modifier = Modifier + .verticalScroll(state = scrollState) + .padding(horizontal = 16.dp), + ) { + Spacer(Modifier.height(16.dp)) + // Title + Text( + text = stringResource(id = R.string.screen_login_title), + modifier = Modifier + .fillMaxWidth(), + style = ElementTextStyles.Bold.title1, + color = MaterialTheme.colorScheme.primary, + ) + Spacer(Modifier.height(32.dp)) + ServerDetailForm( + state = state, + isLoading = isLoading, + submit = ::submit + ) + } + } + } + + if (state.loginAction is Async.Failure) { + LoginErrorDialog(error = state.loginAction.error, onDismiss = { + state.eventSink(LoginPasswordEvents.ClearError) + }) + } +} + +@Composable +fun ServerDetailForm( + state: LoginPasswordState, + isLoading: Boolean, + submit: () -> Unit, + modifier: Modifier = Modifier, +) { + LoginForm(state = state, isLoading = isLoading, onSubmit = submit, modifier = modifier) + + Spacer(Modifier.height(28.dp)) + + // Submit + ButtonWithProgress( + text = stringResource(R.string.screen_login_submit), + showProgress = isLoading, + onClick = submit, + enabled = state.submitEnabled, + modifier = Modifier + .fillMaxWidth() + .testTag(TestTags.loginContinue) + ) + Spacer(modifier = Modifier.height(32.dp)) +} + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +internal fun LoginForm( + state: LoginPasswordState, + isLoading: Boolean, + onSubmit: () -> Unit, + modifier: Modifier = Modifier +) { + var loginFieldState by textFieldState(stateValue = state.formState.login) + var passwordFieldState by textFieldState(stateValue = state.formState.password) + + val focusManager = LocalFocusManager.current + val eventSink = state.eventSink + + Column(modifier) { + Text( + text = stringResource(R.string.screen_login_form_header), + modifier = Modifier.padding(start = 16.dp), + style = ElementTextStyles.Regular.formHeader + ) + + Spacer(modifier = Modifier.height(8.dp)) + TextField( + value = loginFieldState, + readOnly = isLoading, + modifier = Modifier + .fillMaxWidth() + .onTabOrEnterKeyFocusNext(focusManager) + .testTag(TestTags.loginEmailUsername) + .autofill(autofillTypes = listOf(AutofillType.Username), onFill = { + loginFieldState = it + eventSink(LoginPasswordEvents.SetLogin(it)) + }), + label = { + Text(text = stringResource(R.string.screen_login_username_hint)) + }, + onValueChange = { + loginFieldState = it + eventSink(LoginPasswordEvents.SetLogin(it)) + }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Email, + imeAction = ImeAction.Next + ), + keyboardActions = KeyboardActions(onNext = { + focusManager.moveFocus(FocusDirection.Down) + }), + singleLine = true, + maxLines = 1, + trailingIcon = if (loginFieldState.isNotEmpty()) { + { + IconButton(onClick = { + loginFieldState = "" + }) { + Icon(imageVector = Icons.Filled.Close, contentDescription = stringResource(StringR.string.action_clear)) + } + } + } else null, + ) + + var passwordVisible by remember { mutableStateOf(false) } + if (state.loginAction is Async.Loading) { + // Ensure password is hidden when user submits the form + passwordVisible = false + } + Spacer(Modifier.height(20.dp)) + TextField( + value = passwordFieldState, + readOnly = isLoading, + modifier = Modifier + .fillMaxWidth() + .onTabOrEnterKeyFocusNext(focusManager) + .testTag(TestTags.loginPassword) + .autofill(autofillTypes = listOf(AutofillType.Password), onFill = { + passwordFieldState = it + eventSink(LoginPasswordEvents.SetPassword(it)) + }), + onValueChange = { + passwordFieldState = it + eventSink(LoginPasswordEvents.SetPassword(it)) + }, + label = { + Text(text = stringResource(R.string.screen_login_password_hint)) + }, + visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), + trailingIcon = { + val image = + if (passwordVisible) Icons.Filled.Visibility else Icons.Filled.VisibilityOff + val description = + if (passwordVisible) stringResource(StringR.string.a11y_hide_password) else stringResource(StringR.string.a11y_show_password) + + IconButton(onClick = { passwordVisible = !passwordVisible }) { + Icon(imageVector = image, description) + } + }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Done, + ), + keyboardActions = KeyboardActions( + onDone = { onSubmit() } + ), + singleLine = true, + maxLines = 1, + ) + } +} + +@Composable +internal fun LoginErrorDialog(error: Throwable, onDismiss: () -> Unit) { + ErrorDialog( + content = stringResource(loginError(error)), + onDismiss = onDismiss + ) +} + +@Preview +@Composable +internal fun LoginRootScreenLightPreview(@PreviewParameter(LoginPasswordStateProvider::class) state: LoginPasswordState) = + ElementPreviewLight { ContentToPreview(state) } + +@Preview +@Composable +internal fun LoginRootScreenDarkPreview(@PreviewParameter(LoginPasswordStateProvider::class) state: LoginPasswordState) = + ElementPreviewDark { ContentToPreview(state) } + +@Composable +private fun ContentToPreview(state: LoginPasswordState) { + LoginPasswordView( + state = state, + onBackPressed = {} + ) +} From 6eeac4dc414ad8352fb3b82ea1c4ce31e83b57ac Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 6 Jun 2023 18:46:31 +0200 Subject: [PATCH 08/61] Improve screen --- .../impl/loginpassword/LoginPasswordView.kt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt index f8b0345c01..8509e718f5 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt @@ -31,11 +31,11 @@ import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Visibility import androidx.compose.material.icons.filled.VisibilityOff import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -59,6 +59,7 @@ import io.element.android.features.login.impl.R import io.element.android.features.login.impl.error.loginError import io.element.android.libraries.architecture.Async import io.element.android.libraries.designsystem.ElementTextStyles +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.ButtonWithProgress import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog @@ -122,12 +123,14 @@ fun LoginPasswordView( ) { Spacer(Modifier.height(16.dp)) // Title - Text( - text = stringResource(id = R.string.screen_login_title), - modifier = Modifier - .fillMaxWidth(), - style = ElementTextStyles.Bold.title1, - color = MaterialTheme.colorScheme.primary, + IconTitleSubtitleMolecule( + modifier = Modifier.padding(top = 60.dp), + iconImageVector = Icons.Filled.AccountCircle, + title = stringResource( + id = R.string.screen_account_provider_signin_title, + "state.homeserver" // TODO + ), + subTitle = stringResource(id = R.string.screen_login_form_header) ) Spacer(Modifier.height(32.dp)) ServerDetailForm( From 7b6a5d3af43a079af2dca21d3cc64f4b9e197453 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Jun 2023 10:01:41 +0200 Subject: [PATCH 09/61] Cleanup --- .../android/features/login/impl/LoginFlowNode.kt | 6 +++--- .../accountprovider/AccountProviderPresenter.kt | 13 ++----------- .../impl/accountprovider/AccountProviderState.kt | 6 +++--- .../AccountProviderStateProvider.kt | 8 ++++---- .../impl/accountprovider/AccountProviderView.kt | 7 +++---- .../item/AccountProvider.kt} | 4 ++-- .../item/AccountProviderProvider.kt} | 14 +++++++------- .../item/AccountProviderView.kt} | 14 +++++++------- .../ChangeAccountProviderNode.kt | 10 +++++----- .../ChangeAccountProviderPresenter.kt | 6 +++--- .../ChangeAccountProviderState.kt | 4 ++-- .../ChangeAccountProviderStateProvider.kt | 11 +++-------- .../ChangeAccountProviderView.kt | 16 ++++++++-------- .../form/ChangeAccountProviderFormNode.kt | 10 +++++----- .../form/ChangeAccountProviderFormView.kt | 12 ++++++------ .../login/impl/changeserver/ChangeServerNode.kt | 3 +++ .../impl/changeserver/ChangeServerPresenter.kt | 1 + .../changeserver/ChangeServerStateProvider.kt | 4 +++- .../login/impl/changeserver/ChangeServerView.kt | 1 + .../impl/datasource/AccountProviderDataSource.kt | 12 ++++++------ .../{changeserver => error}/ChangeServerError.kt | 2 +- .../features/login/impl/error/ErrorFormatter.kt | 2 ++ .../impl/loginpassword/LoginPasswordPresenter.kt | 6 ++++++ .../impl/loginpassword/LoginPasswordState.kt | 2 ++ .../loginpassword/LoginPasswordStateProvider.kt | 2 ++ .../impl/loginpassword/LoginPasswordView.kt | 8 ++++---- .../features/login/impl/root/LoginRootNode.kt | 3 +++ .../features/login/impl/util/LoginConstants.kt | 4 ++-- 28 files changed, 99 insertions(+), 92 deletions(-) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/item/AccountProviderItem.kt => accountprovider/item/AccountProvider.kt} (86%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/item/ChangeAccountProviderItemProvider.kt => accountprovider/item/AccountProviderProvider.kt} (64%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/item/ChangeAccountProviderItemView.kt => accountprovider/item/AccountProviderView.kt} (90%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeserver => error}/ChangeServerError.kt (95%) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt index 8f0564cf33..67a28d1569 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt @@ -33,9 +33,9 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.features.login.impl.accountprovider.AccountProviderNode +import io.element.android.features.login.impl.accountprovider.item.AccountProvider 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.changeaccountprovider.item.AccountProviderItem import io.element.android.features.login.impl.changeserver.ChangeServerNode import io.element.android.features.login.impl.datasource.AccountProviderDataSource import io.element.android.features.login.impl.loginpassword.LoginPasswordNode @@ -155,7 +155,7 @@ class LoginFlowNode @AssistedInject constructor( } NavTarget.ChangeAccountProvider -> { val callback = object : ChangeAccountProviderNode.Callback { - override fun onAccountProviderItemClicked(data: AccountProviderItem) { + override fun onAccountProviderClicked(data: AccountProvider) { accountProviderDataSource.userSelection(data) // Go back to the Account Provider screen backstack.singleTop(NavTarget.AccountProvider) @@ -170,7 +170,7 @@ class LoginFlowNode @AssistedInject constructor( } NavTarget.ChangeAccountProviderForm -> { val callback = object : ChangeAccountProviderFormNode.Callback { - override fun onAccountProviderItemClicked(data: AccountProviderItem) { + override fun onAccountProviderClicked(data: AccountProvider) { accountProviderDataSource.userSelection(data) // Go back to the Account Provider screen backstack.singleTop(NavTarget.AccountProvider) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt index b20a69fcae..9302ed7a48 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt @@ -27,7 +27,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import io.element.android.features.login.impl.changeserver.ChangeServerError +import io.element.android.features.login.impl.error.ChangeServerError import io.element.android.features.login.impl.datasource.AccountProviderDataSource import io.element.android.features.login.impl.util.LoginConstants import io.element.android.libraries.architecture.Async @@ -35,7 +35,6 @@ import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.execute import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService -import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import java.net.URL @@ -59,13 +58,6 @@ class AccountProviderPresenter @AssistedInject constructor( override fun present(): AccountProviderState { val accountProvider by accountProviderDataSource.flow().collectAsState() val currentHomeServerDetails = authenticationService.getHomeserverDetails().collectAsState().value - val getHomeServerDetailsAction: MutableState> = remember { - if (currentHomeServerDetails != null) { - mutableStateOf(Async.Success(currentHomeServerDetails)) - } else { - mutableStateOf(Async.Uninitialized) - } - } val localCoroutineScope = rememberCoroutineScope() val homeserver = rememberSaveable { @@ -85,8 +77,7 @@ class AccountProviderPresenter @AssistedInject constructor( } return AccountProviderState( - homeserver = accountProvider.title, - isMatrix = accountProvider.isMatrixOrg, + accountProvider = accountProvider, isAccountCreation = params.isAccountCreation, loginFlow = loginFlowAction.value, eventSink = ::handleEvents diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderState.kt index fd6a7914fb..625ef0070d 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderState.kt @@ -16,18 +16,18 @@ package io.element.android.features.login.impl.accountprovider +import io.element.android.features.login.impl.accountprovider.item.AccountProvider import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.api.auth.OidcDetails // Do not use default value, so no member get forgotten in the presenters. data class AccountProviderState( - val homeserver: String, - val isMatrix: Boolean, + val accountProvider: AccountProvider, val isAccountCreation: Boolean, val loginFlow: Async, val eventSink: (AccountProviderEvents) -> Unit ) { - val submitEnabled: Boolean get() = homeserver.isNotEmpty() && (loginFlow is Async.Uninitialized || loginFlow is Async.Loading) + val submitEnabled: Boolean get() = accountProvider.title.isNotEmpty() && (loginFlow is Async.Uninitialized || loginFlow is Async.Loading) } sealed interface LoginFlow { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderStateProvider.kt index 9d68942dae..56584c1935 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderStateProvider.kt @@ -17,19 +17,19 @@ package io.element.android.features.login.impl.accountprovider import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.login.impl.accountprovider.item.anAccountProvider import io.element.android.libraries.architecture.Async open class AccountProviderStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - aAccountProviderState(), + anAccountProviderState(), // Add other state here ) } -fun aAccountProviderState() = AccountProviderState( - homeserver = "matrix.org", - isMatrix = true, +fun anAccountProviderState() = AccountProviderState( + accountProvider = anAccountProvider(), isAccountCreation = false, loginFlow = Async.Uninitialized, eventSink = {} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt index 3548504802..6c8b1df60e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt @@ -30,14 +30,13 @@ 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.changeserver.ChangeServerError +import io.element.android.features.login.impl.error.ChangeServerError import io.element.android.features.login.impl.changeserver.SlidingSyncNotSupportedDialog 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.async.AsyncFailure -import io.element.android.libraries.designsystem.components.async.AsyncLoading 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 @@ -78,7 +77,7 @@ fun AccountProviderView( } else { R.string.screen_account_provider_signin_title }, - state.homeserver + state.accountProvider.title ), subTitle = stringResource( id = if (state.isAccountCreation) { @@ -123,7 +122,7 @@ fun AccountProviderView( } ) } - is Async.Loading -> AsyncLoading() + 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) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/AccountProviderItem.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProvider.kt similarity index 86% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/AccountProviderItem.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProvider.kt index 22c8079c48..6f8adff403 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/AccountProviderItem.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProvider.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package io.element.android.features.login.impl.changeaccountprovider.item +package io.element.android.features.login.impl.accountprovider.item -data class AccountProviderItem constructor( +data class AccountProvider constructor( val title: String, val subtitle: String? = null, val isPublic: Boolean = false, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProviderProvider.kt similarity index 64% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemProvider.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProviderProvider.kt index 523b4143d3..c931c3e801 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProviderProvider.kt @@ -14,21 +14,21 @@ * limitations under the License. */ -package io.element.android.features.login.impl.changeaccountprovider.item +package io.element.android.features.login.impl.accountprovider.item import androidx.compose.ui.tooling.preview.PreviewParameterProvider -open class ChangeAccountProviderItemProvider : PreviewParameterProvider { - override val values: Sequence +open class AccountProviderProvider : PreviewParameterProvider { + override val values: Sequence get() = sequenceOf( - aChangeAccountProviderItem(), - aChangeAccountProviderItem().copy(subtitle = null), - aChangeAccountProviderItem().copy(title = "Other", subtitle = null, isPublic = false, isMatrixOrg = false), + anAccountProvider(), + anAccountProvider().copy(subtitle = null), + anAccountProvider().copy(title = "Other", subtitle = null, isPublic = false, isMatrixOrg = false), // Add other state here ) } -fun aChangeAccountProviderItem() = AccountProviderItem( +fun anAccountProvider() = AccountProvider( title = "matrix.org", subtitle = "Matrix.org is an open network for secure, decentralized communication.", isPublic = true, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProviderView.kt similarity index 90% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemView.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProviderView.kt index 867666e59c..eb2f67947e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProviderView.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.login.impl.changeaccountprovider.item +package io.element.android.features.login.impl.accountprovider.item import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column @@ -48,8 +48,8 @@ import io.element.android.libraries.designsystem.theme.components.Text * https://www.figma.com/file/o9p34zmiuEpZRyvZXJZAYL/FTUE?type=design&node-id=604-60817 */ @Composable -fun ChangeAccountProviderItemView( - item: AccountProviderItem, +fun AccountProviderView( + item: AccountProvider, modifier: Modifier = Modifier, onClick: () -> Unit, ) { @@ -115,17 +115,17 @@ fun ChangeAccountProviderItemView( @Preview @Composable -fun ChangeAccountProviderViewLightPreview(@PreviewParameter(ChangeAccountProviderItemProvider::class) item: AccountProviderItem) = +fun AccountProviderViewLightPreview(@PreviewParameter(AccountProviderProvider::class) item: AccountProvider) = ElementPreviewLight { ContentToPreview(item) } @Preview @Composable -fun ChangeAccountProviderViewDarkPreview(@PreviewParameter(ChangeAccountProviderItemProvider::class) item: AccountProviderItem) = +fun AccountProviderViewDarkPreview(@PreviewParameter(AccountProviderProvider::class) item: AccountProvider) = ElementPreviewDark { ContentToPreview(item) } @Composable -private fun ContentToPreview(item: AccountProviderItem) { - ChangeAccountProviderItemView( +private fun ContentToPreview(item: AccountProvider) { + AccountProviderView( item = item, onClick = { } ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt index e54d4347d2..aafaee818d 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt @@ -25,7 +25,7 @@ import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode -import io.element.android.features.login.impl.changeaccountprovider.item.AccountProviderItem +import io.element.android.features.login.impl.accountprovider.item.AccountProvider import io.element.android.libraries.di.AppScope @ContributesNode(AppScope::class) @@ -36,12 +36,12 @@ class ChangeAccountProviderNode @AssistedInject constructor( ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { - fun onAccountProviderItemClicked(data: AccountProviderItem) + fun onAccountProviderClicked(data: AccountProvider) fun onOtherClicked() } - private fun onAccountProviderItemClicked(data: AccountProviderItem) { - plugins().forEach { it.onAccountProviderItemClicked(data) } + private fun onAccountProviderClicked(data: AccountProvider) { + plugins().forEach { it.onAccountProviderClicked(data) } } private fun onOtherClicked() { @@ -55,7 +55,7 @@ class ChangeAccountProviderNode @AssistedInject constructor( state = state, modifier = modifier, onBackPressed = ::navigateUp, - onAccountProviderItemClicked = ::onAccountProviderItemClicked, + onAccountProviderClicked = ::onAccountProviderClicked, onOtherProviderClicked = ::onOtherClicked, ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt index 8749fbec06..055541ca09 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt @@ -17,7 +17,7 @@ package io.element.android.features.login.impl.changeaccountprovider import androidx.compose.runtime.Composable -import io.element.android.features.login.impl.changeaccountprovider.item.AccountProviderItem +import io.element.android.features.login.impl.accountprovider.item.AccountProvider import io.element.android.libraries.architecture.Presenter import javax.inject.Inject @@ -28,8 +28,8 @@ class ChangeAccountProviderPresenter @Inject constructor( override fun present(): ChangeAccountProviderState { return ChangeAccountProviderState( // Just matrix.org by default for now - accountProviderItems = listOf( - AccountProviderItem( + accountProviders = listOf( + AccountProvider( title = "matrix.org", subtitle = null, isPublic = true, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt index 62c756ad07..89a286518e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt @@ -16,9 +16,9 @@ package io.element.android.features.login.impl.changeaccountprovider -import io.element.android.features.login.impl.changeaccountprovider.item.AccountProviderItem +import io.element.android.features.login.impl.accountprovider.item.AccountProvider // Do not use default value, so no member get forgotten in the presenters. data class ChangeAccountProviderState constructor( - val accountProviderItems: List, + val accountProviders: List, ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt index 7707c66945..627f11c742 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt @@ -17,7 +17,7 @@ package io.element.android.features.login.impl.changeaccountprovider import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.features.login.impl.changeaccountprovider.item.AccountProviderItem +import io.element.android.features.login.impl.accountprovider.item.anAccountProvider open class ChangeAccountProviderStateProvider : PreviewParameterProvider { override val values: Sequence @@ -28,12 +28,7 @@ open class ChangeAccountProviderStateProvider : PreviewParameterProvider Unit, - onAccountProviderItemClicked: (AccountProviderItem) -> Unit = {}, + onAccountProviderClicked: (AccountProvider) -> Unit = {}, onOtherProviderClicked: () -> Unit = {}, ) { val scrollState = rememberScrollState() @@ -92,7 +92,7 @@ fun ChangeAccountProviderView( subTitle = stringResource(id = R.string.screen_change_account_provider_subtitle), ) - state.accountProviderItems.forEach { item -> + state.accountProviders.forEach { item -> val alteredItem = if (item.isMatrixOrg) { // Set the subtitle from the resource item.copy( @@ -101,16 +101,16 @@ fun ChangeAccountProviderView( } else { item } - ChangeAccountProviderItemView( + AccountProviderView( item = alteredItem, onClick = { - onAccountProviderItemClicked(alteredItem) + onAccountProviderClicked(alteredItem) } ) } // Other - ChangeAccountProviderItemView( - item = AccountProviderItem( + AccountProviderView( + item = AccountProvider( title = stringResource(id = R.string.screen_change_account_provider_other), ), onClick = onOtherProviderClicked diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormNode.kt index 28e48f7263..8786497e60 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormNode.kt @@ -25,7 +25,7 @@ import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode -import io.element.android.features.login.impl.changeaccountprovider.item.AccountProviderItem +import io.element.android.features.login.impl.accountprovider.item.AccountProvider import io.element.android.libraries.di.AppScope @ContributesNode(AppScope::class) @@ -36,11 +36,11 @@ class ChangeAccountProviderFormNode @AssistedInject constructor( ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { - fun onAccountProviderItemClicked(data: AccountProviderItem) + fun onAccountProviderClicked(data: AccountProvider) } - private fun onAccountProviderItemClicked(data: AccountProviderItem) { - plugins().forEach { it.onAccountProviderItemClicked(data) } + private fun onAccountProviderClicked(data: AccountProvider) { + plugins().forEach { it.onAccountProviderClicked(data) } } @Composable @@ -50,7 +50,7 @@ class ChangeAccountProviderFormNode @AssistedInject constructor( state = state, modifier = modifier, onBackPressed = ::navigateUp, - onProviderClicked = ::onAccountProviderItemClicked + onAccountProviderClicked = ::onAccountProviderClicked ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt index 54ddd5f10e..9e00d9c47d 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt @@ -50,8 +50,8 @@ 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.changeaccountprovider.item.AccountProviderItem -import io.element.android.features.login.impl.changeaccountprovider.item.ChangeAccountProviderItemView +import io.element.android.features.login.impl.accountprovider.item.AccountProvider +import io.element.android.features.login.impl.accountprovider.item.AccountProviderView import io.element.android.libraries.architecture.Async import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.components.button.BackButton @@ -72,7 +72,7 @@ fun ChangeAccountProviderFormView( state: ChangeAccountProviderFormState, modifier: Modifier = Modifier, onBackPressed: () -> Unit, - onProviderClicked: (AccountProviderItem) -> Unit = {}, + onAccountProviderClicked: (AccountProvider) -> Unit = {}, ) { val eventSink = state.eventSink val scrollState = rememberScrollState() @@ -161,16 +161,16 @@ fun ChangeAccountProviderFormView( is Async.Success -> { state.userInputResult.state.forEach { homeserverData -> val isMatrixOrg = homeserverData.homeserverUrl == "https://matrix.org" - val item = AccountProviderItem( + val item = AccountProvider( title = homeserverData.homeserverUrl.removePrefix("http://").removePrefix("https://"), subtitle = if (isMatrixOrg) stringResource(id = R.string.screen_change_account_provider_matrix_org_subtitle) else null, isPublic = isMatrixOrg, // There is no need to know for other servers right now isMatrixOrg = isMatrixOrg, ) - ChangeAccountProviderItemView( + AccountProviderView( item = item, onClick = { - onProviderClicked(item) + onAccountProviderClicked(item) } ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerNode.kt index 11952ce5a8..5ac13ed409 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerNode.kt @@ -32,6 +32,9 @@ import io.element.android.features.login.impl.util.LoginConstants import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.di.AppScope +/** + * Not used anymore. + */ @ContributesNode(AppScope::class) class ChangeServerNode @AssistedInject constructor( @Assisted buildContext: BuildContext, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt index f2e688812c..81a19e5021 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt @@ -22,6 +22,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable +import io.element.android.features.login.impl.error.ChangeServerError import io.element.android.features.login.impl.util.LoginConstants import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt index 3e6eeef05a..9e938ce15b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt @@ -18,6 +18,7 @@ package io.element.android.features.login.impl.changeserver import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.login.impl.R +import io.element.android.features.login.impl.error.ChangeServerError import io.element.android.libraries.architecture.Async open class ChangeServerStateProvider : PreviewParameterProvider { @@ -30,7 +31,8 @@ open class ChangeServerStateProvider : PreviewParameterProvider = MutableStateFlow( - defaultAccountProviderItem + private val accountProvider: MutableStateFlow = MutableStateFlow( + defaultAccountProvider ) - fun flow(): StateFlow { + fun flow(): StateFlow { return accountProvider.asStateFlow() } - fun userSelection(data: AccountProviderItem) { + fun userSelection(data: AccountProvider) { accountProvider.tryEmit(data) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerError.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt similarity index 95% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerError.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt index 954f4f10dc..9976089f52 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerError.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.login.impl.changeserver +package io.element.android.features.login.impl.error import androidx.annotation.StringRes import androidx.compose.runtime.Composable diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ErrorFormatter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ErrorFormatter.kt index 68b0a70db4..86820645ae 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ErrorFormatter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ErrorFormatter.kt @@ -16,12 +16,14 @@ package io.element.android.features.login.impl.error +import androidx.annotation.StringRes import io.element.android.features.login.impl.R import io.element.android.libraries.matrix.api.auth.AuthErrorCode import io.element.android.libraries.matrix.api.auth.AuthenticationException import io.element.android.libraries.matrix.api.auth.errorCode import io.element.android.libraries.ui.strings.R.string as StringR +@StringRes fun loginError( throwable: Throwable ): Int { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordPresenter.kt index af495eb106..96f749a89c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordPresenter.kt @@ -18,10 +18,13 @@ package io.element.android.features.login.impl.loginpassword import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable +import io.element.android.features.login.impl.datasource.AccountProviderDataSource import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService @@ -32,6 +35,7 @@ import javax.inject.Inject class LoginPasswordPresenter @Inject constructor( private val authenticationService: MatrixAuthenticationService, + private val accountProviderDataSource: AccountProviderDataSource, ) : Presenter { @Composable @@ -44,6 +48,7 @@ class LoginPasswordPresenter @Inject constructor( val formState = rememberSaveable { mutableStateOf(LoginFormState.Default) } + val accountProvider by accountProviderDataSource.flow().collectAsState() fun handleEvents(event: LoginPasswordEvents) { when (event) { @@ -61,6 +66,7 @@ class LoginPasswordPresenter @Inject constructor( } return LoginPasswordState( + accountProvider = accountProvider, formState = formState.value, loginAction = loginAction.value, eventSink = ::handleEvents diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordState.kt index eac406a1c4..07bb7d1c51 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordState.kt @@ -17,11 +17,13 @@ package io.element.android.features.login.impl.loginpassword import android.os.Parcelable +import io.element.android.features.login.impl.accountprovider.item.AccountProvider import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.api.core.SessionId import kotlinx.parcelize.Parcelize data class LoginPasswordState( + val accountProvider: AccountProvider, val formState: LoginFormState, val loginAction: Async, val eventSink: (LoginPasswordEvents) -> Unit diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordStateProvider.kt index deb7e86acc..f53cfb168a 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordStateProvider.kt @@ -17,6 +17,7 @@ package io.element.android.features.login.impl.loginpassword import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.login.impl.accountprovider.item.anAccountProvider import io.element.android.libraries.architecture.Async open class LoginPasswordStateProvider : PreviewParameterProvider { @@ -31,6 +32,7 @@ open class LoginPasswordStateProvider : PreviewParameterProvider Box( - modifier = modifier + modifier = Modifier .fillMaxSize() .imePadding() .padding(padding) @@ -121,14 +122,13 @@ fun LoginPasswordView( .verticalScroll(state = scrollState) .padding(horizontal = 16.dp), ) { - Spacer(Modifier.height(16.dp)) // Title IconTitleSubtitleMolecule( - modifier = Modifier.padding(top = 60.dp), + modifier = Modifier.padding(top = 20.dp), iconImageVector = Icons.Filled.AccountCircle, title = stringResource( id = R.string.screen_account_provider_signin_title, - "state.homeserver" // TODO + state.accountProvider.title ), subTitle = stringResource(id = R.string.screen_login_form_header) ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootNode.kt index 787f5d0b48..1de7477555 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootNode.kt @@ -28,6 +28,9 @@ import io.element.android.anvilannotations.ContributesNode import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.auth.OidcDetails +/** + * Not used anymore. + */ @ContributesNode(AppScope::class) class LoginRootNode @AssistedInject constructor( @Assisted buildContext: BuildContext, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/LoginConstants.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/LoginConstants.kt index 06ffcb94f4..10f69c3d1a 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/LoginConstants.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/LoginConstants.kt @@ -16,7 +16,7 @@ package io.element.android.features.login.impl.util -import io.element.android.features.login.impl.changeaccountprovider.item.AccountProviderItem +import io.element.android.features.login.impl.accountprovider.item.AccountProvider object LoginConstants { const val MATRIX_ORG_URL = "matrix.org" @@ -25,7 +25,7 @@ object LoginConstants { const val SLIDING_SYNC_READ_MORE_URL = "https://github.com/matrix-org/sliding-sync/blob/main/docs/Landing.md" } -val defaultAccountProviderItem = AccountProviderItem( +val defaultAccountProvider = AccountProvider( title = LoginConstants.DEFAULT_HOMESERVER_URL, subtitle = null, isPublic = LoginConstants.DEFAULT_HOMESERVER_URL == LoginConstants.MATRIX_ORG_URL, From 9836b3fc8a917a63ec93a228a5aa3f894dc3b3e2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Jun 2023 10:26:13 +0200 Subject: [PATCH 10/61] More fixes --- .../features/login/impl/LoginFlowNode.kt | 1 + .../AccountProviderPresenter.kt | 15 ++++----------- .../accountprovider/AccountProviderView.kt | 9 +-------- .../form/ChangeAccountProviderFormPresenter.kt | 18 +++++++++++++----- .../datasource/AccountProviderDataSource.kt | 4 ++++ 5 files changed, 23 insertions(+), 24 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt index 67a28d1569..3220d8b897 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt @@ -192,6 +192,7 @@ class LoginFlowNode @AssistedInject constructor( DisposableEffect(Unit) { onDispose { activity = null + accountProviderDataSource.reset() } } Children( diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt index 9302ed7a48..5246f9c528 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt @@ -23,13 +23,11 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import io.element.android.features.login.impl.error.ChangeServerError import io.element.android.features.login.impl.datasource.AccountProviderDataSource -import io.element.android.features.login.impl.util.LoginConstants +import io.element.android.features.login.impl.error.ChangeServerError import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.execute @@ -57,12 +55,8 @@ class AccountProviderPresenter @AssistedInject constructor( @Composable override fun present(): AccountProviderState { val accountProvider by accountProviderDataSource.flow().collectAsState() - val currentHomeServerDetails = authenticationService.getHomeserverDetails().collectAsState().value val localCoroutineScope = rememberCoroutineScope() - val homeserver = rememberSaveable { - mutableStateOf(currentHomeServerDetails?.url ?: LoginConstants.DEFAULT_HOMESERVER_URL) - } val loginFlowAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } @@ -70,7 +64,7 @@ class AccountProviderPresenter @AssistedInject constructor( fun handleEvents(event: AccountProviderEvents) { when (event) { AccountProviderEvents.Continue -> { - localCoroutineScope.submit(homeserver, loginFlowAction) + localCoroutineScope.submit(accountProvider.title, loginFlowAction) } AccountProviderEvents.ClearError -> loginFlowAction.value = Async.Uninitialized } @@ -85,12 +79,11 @@ class AccountProviderPresenter @AssistedInject constructor( } private fun CoroutineScope.submit( - homeserverUrl: MutableState, + homeserverUrl: String, loginFlowAction: MutableState>, ) = launch { suspend { - val domain = tryOrNull { URL(homeserverUrl.value) }?.host ?: homeserverUrl.value - homeserverUrl.value = domain + val domain = tryOrNull { URL(homeserverUrl) }?.host ?: homeserverUrl authenticationService.setHomeserver(domain).map { authenticationService.getHomeserverDetails().value!! }.map { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt index 6c8b1df60e..36352416f9 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt @@ -114,14 +114,7 @@ fun AccountProviderView( } ) { when (state.loginFlow) { - is Async.Failure -> { - AsyncFailure( - throwable = state.loginFlow.error, - onRetry = { - state.eventSink.invoke(AccountProviderEvents.Continue) - } - ) - } + is Async.Failure -> Unit // Error dialog will be displayed is Async.Loading -> Unit // The Continue button shows the loading state is Async.Success -> { when (val loginFlowState = state.loginFlow.state) { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt index 24da7892ee..157dc8a120 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt @@ -24,9 +24,9 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.architecture.execute import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import javax.inject.Inject @@ -63,9 +63,17 @@ class ChangeAccountProviderFormPresenter @Inject constructor( ) } - private fun CoroutineScope.userInput(userInput: String, userInputResult: MutableState>>) = launch { - suspend { - homeserverResolver.resolve(userInput) - }.execute(userInputResult) + // Could be reworked using LaunchedEffect + private fun CoroutineScope.userInput(userInput: String, state: MutableState>>) = launch { + state.value = Async.Uninitialized + // Debounce + delay(300) + state.value = Async.Loading() + try { + val result = homeserverResolver.resolve(userInput) + state.value = Async.Success(result) + } catch (error: Throwable) { + state.value = Async.Failure(error) + } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/datasource/AccountProviderDataSource.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/datasource/AccountProviderDataSource.kt index 1240124c5a..ebd1c15ae1 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/datasource/AccountProviderDataSource.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/datasource/AccountProviderDataSource.kt @@ -36,6 +36,10 @@ class AccountProviderDataSource @Inject constructor( return accountProvider.asStateFlow() } + fun reset() { + accountProvider.tryEmit(defaultAccountProvider) + } + fun userSelection(data: AccountProvider) { accountProvider.tryEmit(data) } From 27313ada3747038ec85a10155bedf70282646ede Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Jun 2023 12:20:11 +0200 Subject: [PATCH 11/61] Get faster results --- .../ChangeAccountProviderFormPresenter.kt | 31 ++----- .../form/HomeserverResolver.kt | 86 ++++++++++++------- 2 files changed, 61 insertions(+), 56 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt index 157dc8a120..8262c35a27 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt @@ -17,16 +17,13 @@ package io.element.android.features.login.impl.changeaccountprovider.form import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable -import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import javax.inject.Inject @@ -38,42 +35,28 @@ class ChangeAccountProviderFormPresenter @Inject constructor( override fun present(): ChangeAccountProviderFormState { val localCoroutineScope = rememberCoroutineScope() - var currentJob: Job? = remember { null } - val userInput = rememberSaveable { mutableStateOf("") } - val userInputResult: MutableState>> = remember { - mutableStateOf(Async.Uninitialized) - } + val data by homeserverResolver.flow().collectAsState() fun handleEvents(event: ChangeAccountProviderFormEvents) { when (event) { is ChangeAccountProviderFormEvents.UserInput -> { - currentJob?.cancel() - currentJob = localCoroutineScope.userInput(event.input, userInputResult) + localCoroutineScope.userInput(event.input) } } } return ChangeAccountProviderFormState( userInput = userInput.value, - userInputResult = userInputResult.value, + userInputResult = data, eventSink = ::handleEvents ) } // Could be reworked using LaunchedEffect - private fun CoroutineScope.userInput(userInput: String, state: MutableState>>) = launch { - state.value = Async.Uninitialized - // Debounce - delay(300) - state.value = Async.Loading() - try { - val result = homeserverResolver.resolve(userInput) - state.value = Async.Success(result) - } catch (error: Throwable) { - state.value = Async.Failure(error) - } + private fun CoroutineScope.userInput(userInput: String) = launch { + homeserverResolver.accept(userInput) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverResolver.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverResolver.kt index fceddaf42f..d80b20f1fd 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverResolver.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverResolver.kt @@ -17,12 +17,20 @@ package io.element.android.features.login.impl.changeaccountprovider.form import io.element.android.features.login.impl.changeaccountprovider.form.network.WellknownRequest +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.uri.ensureProtocol import io.element.android.libraries.core.uri.isValidUrl +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import kotlinx.coroutines.async -import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import javax.inject.Inject @@ -33,45 +41,59 @@ class HomeserverResolver @Inject constructor( private val dispatchers: CoroutineDispatchers, private val wellknownRequest: WellknownRequest, ) { + private val mutableFlow: MutableStateFlow>> = MutableStateFlow(Async.Uninitialized) - suspend fun resolve(userInput: String): List { - return withContext(dispatchers.io) { - val cleanedUpUserInput = userInput.trim() - if (cleanedUpUserInput.length < 4) { - // Wait for more chars - emptyList() - } else { + fun flow(): StateFlow>> = mutableFlow + + private var currentJob: Job? = null + + suspend fun accept(userInput: String) { + currentJob?.cancel() + val cleanedUpUserInput = userInput.trim() + mutableFlow.tryEmit(Async.Uninitialized) + if (cleanedUpUserInput.length > 3) { + delay(300) + mutableFlow.tryEmit(Async.Loading()) + withContext(dispatchers.io) { val list = getUrlCandidate(cleanedUpUserInput) - val resolvedList = resolveList(userInput, list) - - // If list is empty, and the user as entered an URL, do not block the user. - if (resolvedList.isEmpty() && userInput.isValidUrl()) { - listOf( - HomeserverData( - userInput = userInput, - homeserverUrl = userInput, - isWellknownValid = false - ) - ) - } else { - resolvedList - } + currentJob = resolveList(userInput, list) } } } - private suspend fun resolveList(userInput: String, list: List): List { - return coroutineScope { - buildList { - list.map { - async { - val isValid = wellknownRequest.execute(it) - if (isValid) { - add(HomeserverData(userInput, it, true)) + private fun CoroutineScope.resolveList(userInput: String, list: List): Job { + val currentList = mutableListOf() + return launch { + list.map { + async { + val isValid = tryOrNull { wellknownRequest.execute(it) }.orFalse() + if (isValid) { + // Emit the list as soon as possible + currentList.add(HomeserverData(userInput, it, true)) + mutableFlow.tryEmit(Async.Success(currentList)) + } + } + }.joinAll() + .also { + // If list is empty, and the user as entered an URL, do not block the user. + if (currentList.isEmpty()) { + if (userInput.isValidUrl()) { + mutableFlow.tryEmit( + Async.Success( + listOf( + HomeserverData( + userInput = userInput, + homeserverUrl = userInput, + isWellknownValid = false + ) + ) + ) + ) + } else { + mutableFlow.tryEmit(Async.Uninitialized) } } - }.joinAll() - } + } } } From b453ae246d43fa8765ca0fef8cb585958402306f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Jun 2023 12:21:54 +0200 Subject: [PATCH 12/61] Quality --- .../designsystem/atomic/atoms/RoundedIconAtom.kt | 10 +++++----- .../atomic/molecules/IconTitleSubtitleMolecule.kt | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt index 01fa2355d3..b5c98cf483 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt @@ -38,11 +38,6 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.theme.LocalColors import io.element.android.libraries.designsystem.theme.components.Icon -enum class RoundedIconAtomSize { - Medium, - Large -} - @Composable fun RoundedIconAtom( modifier: Modifier = Modifier, @@ -115,3 +110,8 @@ private fun ContentToPreview() { ) } } + +enum class RoundedIconAtomSize { + Medium, + Large +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt index c1365d4954..06c111d069 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt @@ -38,7 +38,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.theme.components.Text /** - * Provide either an `iconResourceId` or an `iconImageVector` + * Provide either an `iconResourceId` or an `iconImageVector`. */ @Composable fun IconTitleSubtitleMolecule( From c5c5d90c2404776ab3c5b736ff49a47a66e96096 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Jun 2023 14:18:46 +0200 Subject: [PATCH 13/61] Remove old screens. --- .../features/login/impl/LoginFlowNode.kt | 30 -- .../accountprovider/AccountProviderView.kt | 2 - .../SlidingSyncNotSupportedDialog.kt | 36 ++ .../impl/changeserver/ChangeServerEvents.kt | 23 - .../impl/changeserver/ChangeServerNode.kt | 65 --- .../changeserver/ChangeServerPresenter.kt | 77 ---- .../impl/changeserver/ChangeServerState.kt | 27 -- .../changeserver/ChangeServerStateProvider.kt | 44 -- .../impl/changeserver/ChangeServerView.kt | 292 ------------- .../login/impl/root/LoginRootEvents.kt | 25 -- .../features/login/impl/root/LoginRootNode.kt | 65 --- .../login/impl/root/LoginRootPresenter.kt | 171 -------- .../login/impl/root/LoginRootState.kt | 58 --- .../login/impl/root/LoginRootStateProvider.kt | 76 ---- .../features/login/impl/root/LoginRootView.kt | 403 ------------------ 15 files changed, 36 insertions(+), 1358 deletions(-) create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/SlidingSyncNotSupportedDialog.kt delete mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvents.kt delete mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerNode.kt delete mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt delete mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerState.kt delete mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt delete mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt delete mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootEvents.kt delete mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootNode.kt delete mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootPresenter.kt delete mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootState.kt delete mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootStateProvider.kt delete mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootView.kt diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt index 3220d8b897..1c0ab78ee4 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt @@ -36,13 +36,11 @@ import io.element.android.features.login.impl.accountprovider.AccountProviderNod import io.element.android.features.login.impl.accountprovider.item.AccountProvider 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.changeserver.ChangeServerNode 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.customtab.CustomTabHandler import io.element.android.features.login.impl.oidc.webview.OidcNode -import io.element.android.features.login.impl.root.LoginRootNode import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler @@ -78,10 +76,6 @@ class LoginFlowNode @AssistedInject constructor( private val inputs: Inputs = inputs() sealed interface NavTarget : Parcelable { - // Not used anymore - @Parcelize - object Root : NavTarget - @Parcelize object AccountProvider : NavTarget @@ -94,36 +88,12 @@ class LoginFlowNode @AssistedInject constructor( @Parcelize object LoginPasswordForm : NavTarget - // Not used anymore - @Parcelize - object ChangeServer : NavTarget - @Parcelize data class OidcView(val oidcDetails: OidcDetails) : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { - NavTarget.Root -> { - val callback = object : LoginRootNode.Callback { - override fun onChangeHomeServer() { - backstack.push(NavTarget.ChangeServer) - } - - override fun onOidcDetails(oidcDetails: OidcDetails) { - if (customTabAvailabilityChecker.supportCustomTab()) { - // In this case open a Chrome Custom tab - activity?.let { customTabHandler.open(it, darkTheme, oidcDetails.url) } - } else { - // Fallback to WebView mode - backstack.push(NavTarget.OidcView(oidcDetails)) - } - } - } - createNode(buildContext, plugins = listOf(callback)) - } - - NavTarget.ChangeServer -> createNode(buildContext) is NavTarget.OidcView -> { val input = OidcNode.Inputs(navTarget.oidcDetails) createNode(buildContext, plugins = listOf(input)) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt index 36352416f9..dcc219f93e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt @@ -31,12 +31,10 @@ 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.error.ChangeServerError -import io.element.android.features.login.impl.changeserver.SlidingSyncNotSupportedDialog 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.async.AsyncFailure 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 diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/SlidingSyncNotSupportedDialog.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/SlidingSyncNotSupportedDialog.kt new file mode 100644 index 0000000000..03cf3a754e --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/SlidingSyncNotSupportedDialog.kt @@ -0,0 +1,36 @@ +/* + * 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 + +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import io.element.android.features.login.impl.R +import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog +import io.element.android.libraries.ui.strings.R as StringR + +@Composable +internal fun SlidingSyncNotSupportedDialog(onLearnMoreClicked: () -> Unit, onDismiss: () -> Unit) { + ConfirmationDialog( + onDismiss = onDismiss, + submitText = stringResource(StringR.string.action_learn_more), + onSubmitClicked = onLearnMoreClicked, + onCancelClicked = onDismiss, + emphasizeSubmitButton = true, + title = stringResource(StringR.string.dialog_title_error), + content = stringResource(R.string.screen_change_server_error_no_sliding_sync_message), + ) +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvents.kt deleted file mode 100644 index dd1e83c3f8..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvents.kt +++ /dev/null @@ -1,23 +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.changeserver - -sealed interface ChangeServerEvents { - data class SetServer(val server: String) : ChangeServerEvents - object Submit : ChangeServerEvents - object ClearError : ChangeServerEvents -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerNode.kt deleted file mode 100644 index 5ac13ed409..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerNode.kt +++ /dev/null @@ -1,65 +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.changeserver - -import android.content.Context -import android.content.Intent -import android.net.Uri -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin -import dagger.assisted.Assisted -import dagger.assisted.AssistedInject -import io.element.android.anvilannotations.ContributesNode -import io.element.android.features.login.impl.util.LoginConstants -import io.element.android.libraries.core.data.tryOrNull -import io.element.android.libraries.di.AppScope - -/** - * Not used anymore. - */ -@ContributesNode(AppScope::class) -class ChangeServerNode @AssistedInject constructor( - @Assisted buildContext: BuildContext, - @Assisted plugins: List, - private val presenter: ChangeServerPresenter, -) : Node(buildContext, plugins = plugins) { - private fun onSuccess() { - navigateUp() - } - - private fun openLearnMorePage(context: Context) { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(LoginConstants.SLIDING_SYNC_READ_MORE_URL)) - tryOrNull { context.startActivity(intent) } - } - - @Composable - override fun View(modifier: Modifier) { - val state = presenter.present() - val context = LocalContext.current - ChangeServerView( - state = state, - modifier = modifier, - onChangeServerSuccess = this::onSuccess, - onBackPressed = { navigateUp() }, - onLearnMoreClicked = { openLearnMorePage(context) }, - ) - } -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt deleted file mode 100644 index 81a19e5021..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt +++ /dev/null @@ -1,77 +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.changeserver - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable -import io.element.android.features.login.impl.error.ChangeServerError -import io.element.android.features.login.impl.util.LoginConstants -import io.element.android.libraries.architecture.Async -import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.architecture.execute -import io.element.android.libraries.core.data.tryOrNull -import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import java.net.URL -import javax.inject.Inject - -class ChangeServerPresenter @Inject constructor(private val authenticationService: MatrixAuthenticationService) : Presenter { - - @Composable - override fun present(): ChangeServerState { - val localCoroutineScope = rememberCoroutineScope() - - val homeserver = rememberSaveable { - mutableStateOf(authenticationService.getHomeserverDetails().value?.url ?: LoginConstants.DEFAULT_HOMESERVER_URL) - } - val changeServerAction: MutableState> = remember { - mutableStateOf(Async.Uninitialized) - } - - fun handleEvents(event: ChangeServerEvents) { - when (event) { - is ChangeServerEvents.SetServer -> { - homeserver.value = event.server - handleEvents(ChangeServerEvents.ClearError) - } - ChangeServerEvents.Submit -> { - localCoroutineScope.submit(homeserver, changeServerAction) - } - ChangeServerEvents.ClearError -> changeServerAction.value = Async.Uninitialized - } - } - - return ChangeServerState( - homeserver = homeserver.value, - changeServerAction = changeServerAction.value, - eventSink = ::handleEvents - ) - } - - private fun CoroutineScope.submit(homeserverUrl: MutableState, changeServerAction: MutableState>) = launch { - suspend { - val domain = tryOrNull { URL(homeserverUrl.value) }?.host ?: homeserverUrl.value - authenticationService.setHomeserver(domain).getOrThrow() - homeserverUrl.value = domain - }.execute(changeServerAction, errorMapping = ChangeServerError::from) - } -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerState.kt deleted file mode 100644 index 5a3ea3b856..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerState.kt +++ /dev/null @@ -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.changeserver - -import io.element.android.libraries.architecture.Async - -data class ChangeServerState( - val homeserver: String, - val changeServerAction: Async, - val eventSink: (ChangeServerEvents) -> Unit, -) { - val submitEnabled: Boolean get() = homeserver.isNotEmpty() && (changeServerAction is Async.Uninitialized || changeServerAction is Async.Loading) -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt deleted file mode 100644 index 9e938ce15b..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt +++ /dev/null @@ -1,44 +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.changeserver - -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.features.login.impl.R -import io.element.android.features.login.impl.error.ChangeServerError -import io.element.android.libraries.architecture.Async - -open class ChangeServerStateProvider : PreviewParameterProvider { - override val values: Sequence - get() = sequenceOf( - aChangeServerState(), - aChangeServerState().copy(homeserver = "matrix.org"), - aChangeServerState().copy(homeserver = "matrix.org", changeServerAction = Async.Loading()), - aChangeServerState().copy( - homeserver = "invalid.org", - changeServerAction = Async.Failure(ChangeServerError.InlineErrorMessage(R.string.screen_change_server_error_invalid_homeserver)) - ), - aChangeServerState().copy(homeserver = "invalid.org", changeServerAction = Async.Failure( - ChangeServerError.SlidingSyncAlert)), - aChangeServerState().copy(homeserver = "matrix.org", changeServerAction = Async.Success(Unit)), - ) -} - -fun aChangeServerState() = ChangeServerState( - homeserver = "", - changeServerAction = Async.Uninitialized, - eventSink = {} -) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt deleted file mode 100644 index 3d9a8d6545..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright (c) 2022 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.changeserver - -import androidx.compose.foundation.background -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.consumeWindowInsets -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Close -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.ExperimentalTextApi -import androidx.compose.ui.text.ParagraphStyle -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.UrlAnnotation -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.withStyle -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.error.ChangeServerError -import io.element.android.features.login.impl.util.LoginConstants -import io.element.android.libraries.architecture.Async -import io.element.android.libraries.designsystem.ElementTextStyles -import io.element.android.libraries.designsystem.LinkColor -import io.element.android.libraries.designsystem.components.ClickableLinkText -import io.element.android.libraries.designsystem.components.button.BackButton -import io.element.android.libraries.designsystem.components.button.ButtonWithProgress -import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog -import io.element.android.libraries.designsystem.components.form.textFieldState -import io.element.android.libraries.designsystem.preview.ElementPreviewDark -import io.element.android.libraries.designsystem.preview.ElementPreviewLight -import io.element.android.libraries.designsystem.theme.LocalColors -import io.element.android.libraries.designsystem.theme.components.Icon -import io.element.android.libraries.designsystem.theme.components.IconButton -import io.element.android.libraries.designsystem.theme.components.Scaffold -import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.designsystem.theme.components.TextField -import io.element.android.libraries.designsystem.theme.components.TopAppBar -import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext -import io.element.android.libraries.testtags.TestTags -import io.element.android.libraries.testtags.testTag -import io.element.android.libraries.ui.strings.R as StringR - -@OptIn(ExperimentalMaterial3Api::class, ExperimentalTextApi::class, ExperimentalLayoutApi::class) -@Composable -fun ChangeServerView( - state: ChangeServerState, - onLearnMoreClicked: () -> Unit, - onBackPressed: () -> Unit, - modifier: Modifier = Modifier, - onChangeServerSuccess: () -> Unit = {}, -) { - val eventSink = state.eventSink - val scrollState = rememberScrollState() - val isLoading by remember(state.changeServerAction) { - derivedStateOf { - state.changeServerAction is Async.Loading - } - } - val invalidHomeserverError = (state.changeServerAction as? Async.Failure)?.error as? ChangeServerError.InlineErrorMessage - val slidingSyncNotSupportedError = (state.changeServerAction as? Async.Failure)?.error as? ChangeServerError.SlidingSyncAlert - val focusManager = LocalFocusManager.current - - fun submit() { - // Clear focus to prevent keyboard issues with textfields - focusManager.clearFocus(force = true) - - eventSink(ChangeServerEvents.Submit) - } - - Scaffold( - topBar = { - TopAppBar( - title = {}, - navigationIcon = { BackButton(onClick = onBackPressed) } - ) - } - ) { padding -> - Box( - modifier = modifier - .fillMaxSize() - .imePadding() - .padding(padding) - .consumeWindowInsets(padding) - ) { - Column( - modifier = Modifier - .verticalScroll( - state = scrollState, - ) - .padding(horizontal = 16.dp) - ) { - Spacer(Modifier.height(42.dp)) - Box( - modifier = Modifier - .size(width = 70.dp, height = 70.dp) - .align(Alignment.CenterHorizontally) - .background( - color = LocalColors.current.quinary, - shape = RoundedCornerShape(14.dp) - ) - ) { - Icon( - modifier = Modifier - .align(Alignment.Center) - .size(width = 32.dp, height = 32.dp), - tint = MaterialTheme.colorScheme.secondary, - resourceId = R.drawable.ic_homeserver, - contentDescription = "", - ) - } - Spacer(modifier = Modifier.height(16.dp)) - Text( - text = stringResource(id = R.string.screen_change_server_title), - modifier = Modifier - .fillMaxWidth() - .align(Alignment.CenterHorizontally), - textAlign = TextAlign.Center, - style = ElementTextStyles.Bold.title2, - color = MaterialTheme.colorScheme.primary, - ) - Spacer(Modifier.height(8.dp)) - Text( - text = stringResource(id = R.string.screen_change_server_subtitle), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - textAlign = TextAlign.Center, - style = ElementTextStyles.Regular.subheadline, - color = MaterialTheme.colorScheme.secondary, - ) - Spacer(Modifier.height(24.dp)) - Text( - stringResource(R.string.screen_change_server_form_header), - style = ElementTextStyles.Regular.formHeader, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) - ) - var homeserverFieldState by textFieldState(stateValue = state.homeserver) - TextField( - value = homeserverFieldState, - readOnly = isLoading, - modifier = Modifier - .fillMaxWidth() - .testTag(TestTags.changeServerServer) - .onTabOrEnterKeyFocusNext(focusManager), - onValueChange = { - homeserverFieldState = it - eventSink(ChangeServerEvents.SetServer(it)) - }, - keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Password, - imeAction = ImeAction.Done, - ), - keyboardActions = KeyboardActions( - onDone = { submit() } - ), - singleLine = true, - maxLines = 1, - trailingIcon = if (homeserverFieldState.isNotEmpty()) { - { - IconButton(onClick = { - eventSink(ChangeServerEvents.SetServer("")) - }, enabled = !isLoading) { - Icon(imageVector = Icons.Filled.Close, contentDescription = stringResource(StringR.string.action_clear)) - } - } - } else null, - isError = invalidHomeserverError != null, - supportingText = { - if (invalidHomeserverError != null) { - Text(invalidHomeserverError.message(), color = MaterialTheme.colorScheme.error) - } else { - val footerMessage = stringResource(R.string.screen_change_server_form_notice, "") - val footerAction = stringResource(StringR.string.action_learn_more) - val footerText = buildAnnotatedString { - val defaultColor = MaterialTheme.colorScheme.tertiary - withStyle(ParagraphStyle(textAlign = TextAlign.Start)) { - withStyle(SpanStyle(color = defaultColor)) { - append(footerMessage) - } - val start = length - withStyle(SpanStyle(color = LinkColor)) { - append(footerAction) - } - addUrlAnnotation(UrlAnnotation(LoginConstants.SLIDING_SYNC_READ_MORE_URL), start, length) - } - } - ClickableLinkText( - text = footerText, - interactionSource = MutableInteractionSource(), - style = ElementTextStyles.Regular.caption1, - ) - } - } - - ) - if (slidingSyncNotSupportedError != null) { - SlidingSyncNotSupportedDialog(onLearnMoreClicked = { - onLearnMoreClicked() - eventSink(ChangeServerEvents.ClearError) - }, onDismiss = { - eventSink(ChangeServerEvents.ClearError) - }) - } - Spacer(Modifier.height(32.dp)) - ButtonWithProgress( - text = stringResource(id = R.string.screen_change_server_submit), - showProgress = isLoading, - onClick = ::submit, - enabled = state.submitEnabled, - modifier = Modifier - .fillMaxWidth() - .testTag(TestTags.changeServerContinue) - ) - if (state.changeServerAction is Async.Success) { - onChangeServerSuccess() - } - } - } - } -} - -@Composable -internal fun SlidingSyncNotSupportedDialog(onLearnMoreClicked: () -> Unit, onDismiss: () -> Unit) { - ConfirmationDialog( - onDismiss = onDismiss, - submitText = stringResource(StringR.string.action_learn_more), - onSubmitClicked = onLearnMoreClicked, - onCancelClicked = onDismiss, - emphasizeSubmitButton = true, - title = stringResource(StringR.string.dialog_title_error), - content = stringResource(R.string.screen_change_server_error_no_sliding_sync_message), - ) -} - -@Preview -@Composable -internal fun ChangeServerViewLightPreview(@PreviewParameter(ChangeServerStateProvider::class) state: ChangeServerState) = - ElementPreviewLight { ContentToPreview(state) } - -@Preview -@Composable -internal fun ChangeServerViewDarkPreview(@PreviewParameter(ChangeServerStateProvider::class) state: ChangeServerState) = - ElementPreviewDark { ContentToPreview(state) } - -@Composable -private fun ContentToPreview(state: ChangeServerState) { - ChangeServerView(state = state, onBackPressed = {}, onLearnMoreClicked = {}) -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootEvents.kt deleted file mode 100644 index 5aa5071876..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootEvents.kt +++ /dev/null @@ -1,25 +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.root - -sealed interface LoginRootEvents { - object RetryFetchServerInfo : LoginRootEvents - data class SetLogin(val login: String) : LoginRootEvents - data class SetPassword(val password: String) : LoginRootEvents - object Submit : LoginRootEvents - object ClearError : LoginRootEvents -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootNode.kt deleted file mode 100644 index 1de7477555..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootNode.kt +++ /dev/null @@ -1,65 +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.root - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins -import dagger.assisted.Assisted -import dagger.assisted.AssistedInject -import io.element.android.anvilannotations.ContributesNode -import io.element.android.libraries.di.AppScope -import io.element.android.libraries.matrix.api.auth.OidcDetails - -/** - * Not used anymore. - */ -@ContributesNode(AppScope::class) -class LoginRootNode @AssistedInject constructor( - @Assisted buildContext: BuildContext, - @Assisted plugins: List, - private val presenter: LoginRootPresenter, -) : Node(buildContext, plugins = plugins) { - - interface Callback : Plugin { - fun onChangeHomeServer() - fun onOidcDetails(oidcDetails: OidcDetails) - } - - private fun onChangeHomeServer() { - plugins().forEach { it.onChangeHomeServer() } - } - - private fun onOidcDetails(oidcDetails: OidcDetails) { - plugins().forEach { it.onOidcDetails(oidcDetails) } - } - - @Composable - override fun View(modifier: Modifier) { - val state = presenter.present() - LoginRootView( - state = state, - modifier = modifier, - onChangeServer = ::onChangeHomeServer, - onOidcDetails = ::onOidcDetails, - onBackPressed = ::navigateUp - ) - } -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootPresenter.kt deleted file mode 100644 index f55c2030e7..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootPresenter.kt +++ /dev/null @@ -1,171 +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.root - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable -import io.element.android.features.login.api.oidc.OidcAction -import io.element.android.features.login.impl.oidc.customtab.DefaultOidcActionFlow -import io.element.android.features.login.impl.util.LoginConstants -import io.element.android.libraries.architecture.Async -import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.architecture.execute -import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService -import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import javax.inject.Inject - -class LoginRootPresenter @Inject constructor( - private val authenticationService: MatrixAuthenticationService, - private val defaultOidcActionFlow: DefaultOidcActionFlow, -) : Presenter { - - @Composable - override fun present(): LoginRootState { - val localCoroutineScope = rememberCoroutineScope() - val currentHomeServerDetails = authenticationService.getHomeserverDetails().collectAsState().value - val homeserver = currentHomeServerDetails?.url ?: LoginConstants.DEFAULT_HOMESERVER_URL - val getHomeServerDetailsAction: MutableState> = remember { - if (currentHomeServerDetails != null) { - mutableStateOf(Async.Success(currentHomeServerDetails)) - } else { - mutableStateOf(Async.Uninitialized) - } - } - - LaunchedEffect(Unit) { - if (currentHomeServerDetails == null) { - getHomeServerDetails(homeserver, getHomeServerDetailsAction) - } - } - - val loggedInState: MutableState = remember { - mutableStateOf(LoggedInState.NotLoggedIn) - } - val formState = rememberSaveable { - mutableStateOf(LoginFormState.Default) - } - - LaunchedEffect(Unit) { - launch { - defaultOidcActionFlow.collect { - onOidcAction(it, loggedInState) - } - } - } - - fun handleEvents(event: LoginRootEvents) { - when (event) { - LoginRootEvents.RetryFetchServerInfo -> localCoroutineScope.getHomeServerDetails(homeserver, getHomeServerDetailsAction) - is LoginRootEvents.SetLogin -> updateFormState(formState) { - copy(login = event.login) - } - is LoginRootEvents.SetPassword -> updateFormState(formState) { - copy(password = event.password) - } - LoginRootEvents.Submit -> { - val homeServerDetails = getHomeServerDetailsAction.value.dataOrNull() ?: return - when { - homeServerDetails.supportsOidcLogin -> localCoroutineScope.submitOidc(loggedInState) - homeServerDetails.supportsPasswordLogin -> localCoroutineScope.submit(formState.value, loggedInState) - } - } - LoginRootEvents.ClearError -> loggedInState.value = LoggedInState.NotLoggedIn - } - } - - return LoginRootState( - homeserverUrl = homeserver, - homeserverDetails = getHomeServerDetailsAction.value, - loggedInState = loggedInState.value, - formState = formState.value, - eventSink = ::handleEvents - ) - } - - private fun CoroutineScope.getHomeServerDetails( - homeserver: String, - state: MutableState>, - ) = launch { - suspend { - authenticationService.setHomeserver(homeserver) - .map { - authenticationService.getHomeserverDetails().value!! - } - .getOrThrow() - }.execute(state) - } - - private fun CoroutineScope.submitOidc(loggedInState: MutableState) = launch { - loggedInState.value = LoggedInState.LoggingIn - authenticationService.getOidcUrl() - .onSuccess { - loggedInState.value = LoggedInState.OidcStarted(it) - } - .onFailure { failure -> - loggedInState.value = LoggedInState.ErrorLoggingIn(failure) - } - } - - private fun CoroutineScope.submit(formState: LoginFormState, loggedInState: MutableState) = launch { - loggedInState.value = LoggedInState.LoggingIn - authenticationService.login(formState.login.trim(), formState.password) - .onSuccess { sessionId -> - loggedInState.value = LoggedInState.LoggedIn(sessionId) - } - .onFailure { failure -> - loggedInState.value = LoggedInState.ErrorLoggingIn(failure) - } - } - - private fun updateFormState(formState: MutableState, updateLambda: LoginFormState.() -> LoginFormState) { - formState.value = updateLambda(formState.value) - } - - private suspend fun onOidcAction(oidcAction: OidcAction?, loggedInState: MutableState) { - oidcAction ?: return - loggedInState.value = LoggedInState.LoggingIn - when (oidcAction) { - OidcAction.GoBack -> { - authenticationService.cancelOidcLogin() - .onSuccess { - loggedInState.value = LoggedInState.NotLoggedIn - } - .onFailure { failure -> - loggedInState.value = LoggedInState.ErrorLoggingIn(failure) - } - } - is OidcAction.Success -> { - authenticationService.loginWithOidc(oidcAction.url) - .onSuccess { sessionId -> - loggedInState.value = LoggedInState.LoggedIn(sessionId) - } - .onFailure { failure -> - loggedInState.value = LoggedInState.ErrorLoggingIn(failure) - } - } - } - defaultOidcActionFlow.reset() - } -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootState.kt deleted file mode 100644 index 45eafa744c..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootState.kt +++ /dev/null @@ -1,58 +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.root - -import android.os.Parcelable -import io.element.android.libraries.architecture.Async -import io.element.android.libraries.core.bool.orFalse -import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails -import io.element.android.libraries.matrix.api.auth.OidcDetails -import io.element.android.libraries.matrix.api.core.SessionId -import kotlinx.parcelize.Parcelize - -data class LoginRootState( - val homeserverUrl: String, - val homeserverDetails: Async, - val loggedInState: LoggedInState, - val formState: LoginFormState, - val eventSink: (LoginRootEvents) -> Unit -) { - val supportPasswordLogin = (homeserverDetails as? Async.Success)?.state?.supportsPasswordLogin.orFalse() - val supportOidcLogin = (homeserverDetails as? Async.Success)?.state?.supportsOidcLogin.orFalse() - val submitEnabled: Boolean - get() = loggedInState !is LoggedInState.ErrorLoggingIn && - ((formState.login.isNotEmpty() && formState.password.isNotEmpty()) || supportOidcLogin) -} - -sealed interface LoggedInState { - object NotLoggedIn : LoggedInState - object LoggingIn : LoggedInState - data class OidcStarted(val oidcDetail: OidcDetails) : LoggedInState - data class ErrorLoggingIn(val failure: Throwable) : LoggedInState - data class LoggedIn(val sessionId: SessionId) : LoggedInState -} - -@Parcelize -data class LoginFormState( - val login: String, - val password: String -) : Parcelable { - - companion object { - val Default = LoginFormState("", "") - } -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootStateProvider.kt deleted file mode 100644 index 5f6d7c1f3a..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootStateProvider.kt +++ /dev/null @@ -1,76 +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.root - -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.architecture.Async -import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails -import io.element.android.libraries.matrix.api.core.SessionId - -open class LoginRootStateProvider : PreviewParameterProvider { - override val values: Sequence - get() = sequenceOf( - aLoginRootState(), - aLoginRootState().copy( - homeserverDetails = Async.Success( - MatrixHomeServerDetails( - "some-custom-server.com", - supportsPasswordLogin = true, - supportsOidcLogin = false - ) - ) - ), - aLoginRootState().copy(formState = LoginFormState("user", "pass")), - aLoginRootState().copy(formState = LoginFormState("user", "pass"), loggedInState = LoggedInState.LoggingIn), - aLoginRootState().copy(formState = LoginFormState("user", "pass"), loggedInState = LoggedInState.ErrorLoggingIn(Throwable())), - aLoginRootState().copy(formState = LoginFormState("user", "pass"), loggedInState = LoggedInState.LoggedIn(SessionId("@user:domain"))), - // Oidc - aLoginRootState().copy( - homeserverUrl = "server-with-oidc.org", - homeserverDetails = Async.Success( - MatrixHomeServerDetails( - "server-with-oidc.org", - supportsPasswordLogin = false, - supportsOidcLogin = true - ) - ) - ), - // No password, no oidc support - aLoginRootState().copy( - homeserverUrl = "wrong.org", - homeserverDetails = Async.Success( - MatrixHomeServerDetails( - "wrong.org", - supportsPasswordLogin = false, - supportsOidcLogin = false - ) - ) - ), - // Loading - aLoginRootState().copy(homeserverDetails = Async.Loading()), - //Error - aLoginRootState().copy(homeserverDetails = Async.Failure(Exception("An error occurred"))), - ) -} - -fun aLoginRootState() = LoginRootState( - homeserverUrl = "matrix.org", - homeserverDetails = Async.Success(MatrixHomeServerDetails("matrix.org", supportsPasswordLogin = true, supportsOidcLogin = false)), - loggedInState = LoggedInState.NotLoggedIn, - formState = LoginFormState.Default, - eventSink = {} -) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootView.kt deleted file mode 100644 index 2444418725..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/root/LoginRootView.kt +++ /dev/null @@ -1,403 +0,0 @@ -/* - * Copyright (c) 2022 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.root - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.consumeWindowInsets -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ChevronRight -import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.filled.Visibility -import androidx.compose.material.icons.filled.VisibilityOff -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier -import androidx.compose.ui.autofill.AutofillType -import androidx.compose.ui.draw.clip -import androidx.compose.ui.focus.FocusDirection -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.PasswordVisualTransformation -import androidx.compose.ui.text.input.VisualTransformation -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.features.login.impl.error.loginError -import io.element.android.libraries.architecture.Async -import io.element.android.libraries.designsystem.ElementTextStyles -import io.element.android.libraries.designsystem.components.async.AsyncFailure -import io.element.android.libraries.designsystem.components.async.AsyncLoading -import io.element.android.libraries.designsystem.components.button.BackButton -import io.element.android.libraries.designsystem.components.button.ButtonWithProgress -import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog -import io.element.android.libraries.designsystem.components.form.textFieldState -import io.element.android.libraries.designsystem.preview.ElementPreviewDark -import io.element.android.libraries.designsystem.preview.ElementPreviewLight -import io.element.android.libraries.designsystem.theme.components.Icon -import io.element.android.libraries.designsystem.theme.components.IconButton -import io.element.android.libraries.designsystem.theme.components.Scaffold -import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.designsystem.theme.components.TextField -import io.element.android.libraries.designsystem.theme.components.TopAppBar -import io.element.android.libraries.designsystem.theme.components.autofill -import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext -import io.element.android.libraries.matrix.api.auth.OidcDetails -import io.element.android.libraries.testtags.TestTags -import io.element.android.libraries.testtags.testTag -import io.element.android.libraries.ui.strings.R as StringR - -@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) -@Composable -fun LoginRootView( - state: LoginRootState, - modifier: Modifier = Modifier, - onChangeServer: () -> Unit = {}, - onOidcDetails: (OidcDetails) -> Unit = {}, - onBackPressed: () -> Unit, -) { - val isLoading by remember(state.loggedInState) { - derivedStateOf { - state.loggedInState == LoggedInState.LoggingIn - } - } - val focusManager = LocalFocusManager.current - - fun submit() { - // Clear focus to prevent keyboard issues with textfields - focusManager.clearFocus(force = true) - - state.eventSink(LoginRootEvents.Submit) - } - - Scaffold( - topBar = { - TopAppBar( - title = {}, - navigationIcon = { BackButton(onClick = onBackPressed) }, - ) - } - ) { padding -> - Box( - modifier = modifier - .fillMaxSize() - .imePadding() - .padding(padding) - .consumeWindowInsets(padding) - ) { - val scrollState = rememberScrollState() - - Column( - modifier = Modifier - .verticalScroll(state = scrollState) - .padding(horizontal = 16.dp), - ) { - Spacer(Modifier.height(16.dp)) - // Title - Text( - text = stringResource(id = R.string.screen_login_title), - modifier = Modifier - .fillMaxWidth(), - style = ElementTextStyles.Bold.title1, - color = MaterialTheme.colorScheme.primary, - ) - Spacer(Modifier.height(32.dp)) - - ChangeServerSection( - interactionEnabled = !isLoading, - homeserver = state.homeserverUrl, - onChangeServer = onChangeServer - ) - - Spacer(Modifier.height(32.dp)) - - when (state.homeserverDetails) { - Async.Uninitialized, - is Async.Loading -> AsyncLoading() - is Async.Failure -> AsyncFailure( - throwable = state.homeserverDetails.error, - onRetry = { - state.eventSink.invoke(LoginRootEvents.RetryFetchServerInfo) - } - ) - is Async.Success -> ServerDetailForm(state, isLoading, ::submit) - } - } - when (val loggedInState = state.loggedInState) { - is LoggedInState.OidcStarted -> onOidcDetails(loggedInState.oidcDetail) - else -> Unit - } - } - } - - if (state.loggedInState is LoggedInState.ErrorLoggingIn) { - LoginErrorDialog(error = state.loggedInState.failure, onDismiss = { - state.eventSink(LoginRootEvents.ClearError) - }) - } -} - -@Composable -fun ServerDetailForm( - state: LoginRootState, - isLoading: Boolean, - submit: () -> Unit, - modifier: Modifier = Modifier, -) { - when { - state.supportOidcLogin -> { - // Oidc, in this case, just display a Spacer and the submit button - Spacer(modifier.height(28.dp)) - } - state.supportPasswordLogin -> { - LoginForm(state = state, isLoading = isLoading, onSubmit = submit, modifier = modifier) - } - else -> { - Text(modifier = modifier, text = "No supported login flow") - } - } - - Spacer(Modifier.height(28.dp)) - - if (state.supportOidcLogin || state.supportPasswordLogin) { - // Submit - ButtonWithProgress( - text = stringResource(R.string.screen_login_submit), - showProgress = isLoading, - onClick = submit, - enabled = state.submitEnabled, - modifier = Modifier - .fillMaxWidth() - .testTag(TestTags.loginContinue) - ) - Spacer(modifier = Modifier.height(32.dp)) - } -} - -@Composable -internal fun ChangeServerSection( - interactionEnabled: Boolean, - homeserver: String, - onChangeServer: () -> Unit, - modifier: Modifier = Modifier -) { - Column(modifier) { - Text( - modifier = Modifier.padding(start = 16.dp, bottom = 8.dp), - text = stringResource(id = R.string.screen_login_server_header), - style = ElementTextStyles.Regular.formHeader, - ) - Row( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(14.dp)) - .background(MaterialTheme.colorScheme.surfaceVariant) - .testTag(TestTags.loginChangeServer) - .clickable { - if (interactionEnabled) { - onChangeServer() - } - }, - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = homeserver, - style = ElementTextStyles.Bold.body, - textAlign = TextAlign.Start, - modifier = Modifier - .weight(1f) - .padding(horizontal = 16.dp, vertical = 16.dp) - ) - IconButton( - modifier = Modifier.size(24.dp), - onClick = { - if (interactionEnabled) { - onChangeServer() - } - } - ) { - Icon(imageVector = Icons.Default.ChevronRight, contentDescription = null, tint = MaterialTheme.colorScheme.tertiary) - } - Spacer(Modifier.width(8.dp)) - } - } -} - -@OptIn(ExperimentalComposeUiApi::class) -@Composable -internal fun LoginForm( - state: LoginRootState, - isLoading: Boolean, - onSubmit: () -> Unit, - modifier: Modifier = Modifier -) { - var loginFieldState by textFieldState(stateValue = state.formState.login) - var passwordFieldState by textFieldState(stateValue = state.formState.password) - - val focusManager = LocalFocusManager.current - val eventSink = state.eventSink - - Column(modifier) { - Text( - text = stringResource(R.string.screen_login_form_header), - modifier = Modifier.padding(start = 16.dp), - style = ElementTextStyles.Regular.formHeader - ) - - Spacer(modifier = Modifier.height(8.dp)) - TextField( - value = loginFieldState, - readOnly = isLoading, - modifier = Modifier - .fillMaxWidth() - .onTabOrEnterKeyFocusNext(focusManager) - .testTag(TestTags.loginEmailUsername) - .autofill(autofillTypes = listOf(AutofillType.Username), onFill = { - loginFieldState = it - eventSink(LoginRootEvents.SetLogin(it)) - }), - label = { - Text(text = stringResource(R.string.screen_login_username_hint)) - }, - onValueChange = { - loginFieldState = it - eventSink(LoginRootEvents.SetLogin(it)) - }, - keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Email, - imeAction = ImeAction.Next - ), - keyboardActions = KeyboardActions(onNext = { - focusManager.moveFocus(FocusDirection.Down) - }), - singleLine = true, - maxLines = 1, - trailingIcon = if (loginFieldState.isNotEmpty()) { - { - IconButton(onClick = { - loginFieldState = "" - }) { - Icon(imageVector = Icons.Filled.Close, contentDescription = stringResource(StringR.string.action_clear)) - } - } - } else null, - ) - - var passwordVisible by remember { mutableStateOf(false) } - if (state.loggedInState is LoggedInState.LoggingIn) { - // Ensure password is hidden when user submits the form - passwordVisible = false - } - Spacer(Modifier.height(20.dp)) - TextField( - value = passwordFieldState, - readOnly = isLoading, - modifier = Modifier - .fillMaxWidth() - .onTabOrEnterKeyFocusNext(focusManager) - .testTag(TestTags.loginPassword) - .autofill(autofillTypes = listOf(AutofillType.Password), onFill = { - passwordFieldState = it - eventSink(LoginRootEvents.SetPassword(it)) - }), - onValueChange = { - passwordFieldState = it - eventSink(LoginRootEvents.SetPassword(it)) - }, - label = { - Text(text = stringResource(R.string.screen_login_password_hint)) - }, - visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), - trailingIcon = { - val image = - if (passwordVisible) Icons.Filled.Visibility else Icons.Filled.VisibilityOff - val description = - if (passwordVisible) stringResource(StringR.string.a11y_hide_password) else stringResource(StringR.string.a11y_show_password) - - IconButton(onClick = { passwordVisible = !passwordVisible }) { - Icon(imageVector = image, description) - } - }, - keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Password, - imeAction = ImeAction.Done, - ), - keyboardActions = KeyboardActions( - onDone = { onSubmit() } - ), - singleLine = true, - maxLines = 1, - ) - } -} - -@Composable -internal fun LoginErrorDialog(error: Throwable, onDismiss: () -> Unit) { - ErrorDialog( - content = stringResource(loginError(error)), - onDismiss = onDismiss - ) -} - -@Preview -@Composable -internal fun LoginRootScreenLightPreview(@PreviewParameter(LoginRootStateProvider::class) state: LoginRootState) = - ElementPreviewLight { ContentToPreview(state) } - -@Preview -@Composable -internal fun LoginRootScreenDarkPreview(@PreviewParameter(LoginRootStateProvider::class) state: LoginRootState) = - ElementPreviewDark { ContentToPreview(state) } - -@Composable -private fun ContentToPreview(state: LoginRootState) { - LoginRootView( - state = state, - onBackPressed = {} - ) -} From 3d0156e5e38743681c4c8d645731d62c98f2d603 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Jun 2023 14:40:02 +0200 Subject: [PATCH 14/61] Fix compilation of minimal app. --- .../android/samples/minimal/LoginScreen.kt | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/LoginScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/LoginScreen.kt index 9d9971a842..976a68f81c 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/LoginScreen.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/LoginScreen.kt @@ -17,11 +17,13 @@ package io.element.android.samples.minimal import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import io.element.android.features.login.impl.oidc.customtab.DefaultOidcActionFlow -import io.element.android.features.login.impl.root.LoginRootPresenter -import io.element.android.features.login.impl.root.LoginRootView +import io.element.android.features.login.impl.datasource.AccountProviderDataSource +import io.element.android.features.login.impl.loginpassword.LoginPasswordPresenter +import io.element.android.features.login.impl.loginpassword.LoginPasswordView +import io.element.android.features.login.impl.util.defaultAccountProvider import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService class LoginScreen(private val authenticationService: MatrixAuthenticationService) { @@ -29,13 +31,18 @@ class LoginScreen(private val authenticationService: MatrixAuthenticationService @Composable fun Content(modifier: Modifier = Modifier) { val presenter = remember { - LoginRootPresenter( + LoginPasswordPresenter( authenticationService = authenticationService, - DefaultOidcActionFlow() + AccountProviderDataSource() ) } + + LaunchedEffect(Unit) { + authenticationService.setHomeserver(defaultAccountProvider.title) + } + val state = presenter.present() - LoginRootView( + LoginPasswordView( state = state, modifier = modifier, onBackPressed = {}, From c94b29bf83790a34ceb822c6cb015ae679d7a4fd Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Jun 2023 14:47:42 +0200 Subject: [PATCH 15/61] Add test for AccountProviderPresenter --- .../AccountProviderPresenterTest.kt} | 112 ++++++++---------- 1 file changed, 48 insertions(+), 64 deletions(-) rename features/login/impl/src/test/kotlin/io/element/android/features/login/impl/{changeserver/ChangeServerPresenterTest.kt => accountprovider/AccountProviderPresenterTest.kt} (50%) diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenterTest.kt similarity index 50% rename from features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt rename to features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenterTest.kt index a30bd9449c..0e34c623ae 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenterTest.kt @@ -14,132 +14,116 @@ * limitations under the License. */ -package io.element.android.features.login.impl.changeserver +package io.element.android.features.login.impl.accountprovider import app.cash.molecule.RecompositionClock import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.features.login.impl.util.LoginConstants +import io.element.android.features.login.impl.datasource.AccountProviderDataSource +import io.element.android.features.login.impl.util.defaultAccountProvider 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_URL_2 +import io.element.android.libraries.matrix.test.A_HOMESERVER_OIDC import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService import kotlinx.coroutines.test.runTest import org.junit.Test -class ChangeServerPresenterTest { +class AccountProviderPresenterTest { @Test - fun `present - should start with default homeserver`() = runTest { - val presenter = ChangeServerPresenter( + fun `present - initial test`() = runTest { + val presenter = AccountProviderPresenter( + AccountProviderPresenterParams(isAccountCreation = false), + AccountProviderDataSource(), FakeAuthenticationService(), ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { val initialState = awaitItem() - assertThat(initialState.homeserver).isEqualTo(LoginConstants.DEFAULT_HOMESERVER_URL) + assertThat(initialState.isAccountCreation).isFalse() assertThat(initialState.submitEnabled).isTrue() + assertThat(initialState.accountProvider).isEqualTo(defaultAccountProvider) + assertThat(initialState.loginFlow).isEqualTo(Async.Uninitialized) } } @Test - fun `present - authentication service can provide a homeserver`() = runTest { - val presenter = ChangeServerPresenter( - FakeAuthenticationService().apply { - givenHomeserver(A_HOMESERVER.copy(url = A_HOMESERVER_URL_2)) - }, + fun `present - continue password login`() = runTest { + val authServer = FakeAuthenticationService() + val presenter = AccountProviderPresenter( + AccountProviderPresenterParams(isAccountCreation = false), + AccountProviderDataSource(), + authServer, ) + authServer.givenHomeserver(A_HOMESERVER) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { val initialState = awaitItem() - assertThat(initialState.homeserver).isEqualTo(A_HOMESERVER_URL_2) - assertThat(initialState.submitEnabled).isTrue() - } - } - - @Test - fun `present - disable if empty or not correct`() = runTest { - val presenter = ChangeServerPresenter( - FakeAuthenticationService(), - ) - moleculeFlow(RecompositionClock.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - initialState.eventSink.invoke(ChangeServerEvents.SetServer("")) - val emptyState = awaitItem() - assertThat(emptyState.homeserver).isEqualTo("") - assertThat(emptyState.submitEnabled).isFalse() - } - } - - @Test - fun `present - submit`() = runTest { - val presenter = ChangeServerPresenter( - FakeAuthenticationService(), - ) - moleculeFlow(RecompositionClock.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - initialState.eventSink.invoke(ChangeServerEvents.Submit) + initialState.eventSink.invoke(AccountProviderEvents.Continue) val loadingState = awaitItem() assertThat(loadingState.submitEnabled).isTrue() - assertThat(loadingState.changeServerAction).isInstanceOf(Async.Loading::class.java) + assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java) val successState = awaitItem() assertThat(successState.submitEnabled).isFalse() - assertThat(successState.changeServerAction).isInstanceOf(Async.Success::class.java) + assertThat(successState.loginFlow).isInstanceOf(Async.Success::class.java) + assertThat(successState.loginFlow.dataOrNull()).isEqualTo(LoginFlow.PasswordLogin) } } @Test - fun `present - submit parses URL`() = runTest { - val presenter = ChangeServerPresenter( - FakeAuthenticationService(), + fun `present - continue oidc`() = runTest { + val authServer = FakeAuthenticationService() + val presenter = AccountProviderPresenter( + AccountProviderPresenterParams(isAccountCreation = false), + AccountProviderDataSource(), + authServer, ) + authServer.givenHomeserver(A_HOMESERVER_OIDC) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { - val longUrl = "https://matrix.org/.well-known/" val initialState = awaitItem() - initialState.eventSink.invoke(ChangeServerEvents.SetServer(longUrl)) - awaitItem() - initialState.eventSink.invoke(ChangeServerEvents.Submit) + initialState.eventSink.invoke(AccountProviderEvents.Continue) val loadingState = awaitItem() assertThat(loadingState.submitEnabled).isTrue() - assertThat(loadingState.changeServerAction).isInstanceOf(Async.Loading::class.java) - awaitItem() // Skip changing the url to the parsed domain + assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java) val successState = awaitItem() assertThat(successState.submitEnabled).isFalse() - assertThat(successState.changeServerAction).isInstanceOf(Async.Success::class.java) - assertThat(successState.homeserver).isEqualTo("matrix.org") + assertThat(successState.loginFlow).isInstanceOf(Async.Success::class.java) + assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java) } } @Test fun `present - submit fails`() = runTest { val authServer = FakeAuthenticationService() - val presenter = ChangeServerPresenter(authServer) + val presenter = AccountProviderPresenter( + AccountProviderPresenterParams(isAccountCreation = false), + AccountProviderDataSource(), + authServer, + ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { val initialState = awaitItem() authServer.givenChangeServerError(Throwable()) - initialState.eventSink.invoke(ChangeServerEvents.Submit) + initialState.eventSink.invoke(AccountProviderEvents.Continue) skipItems(1) // Loading val failureState = awaitItem() assertThat(failureState.submitEnabled).isFalse() - assertThat(failureState.changeServerAction).isInstanceOf(Async.Failure::class.java) + assertThat(failureState.loginFlow).isInstanceOf(Async.Failure::class.java) } } @Test fun `present - clear error`() = runTest { val authenticationService = FakeAuthenticationService() - val presenter = ChangeServerPresenter( + val presenter = AccountProviderPresenter( + AccountProviderPresenterParams(isAccountCreation = false), + AccountProviderDataSource(), authenticationService, ) moleculeFlow(RecompositionClock.Immediate) { @@ -149,18 +133,18 @@ class ChangeServerPresenterTest { // Submit will return an error authenticationService.givenChangeServerError(A_THROWABLE) - initialState.eventSink(ChangeServerEvents.Submit) + initialState.eventSink(AccountProviderEvents.Continue) skipItems(1) // Loading // Check an error was returned val submittedState = awaitItem() - assertThat(submittedState.changeServerAction).isInstanceOf(Async.Failure::class.java) + assertThat(submittedState.loginFlow).isInstanceOf(Async.Failure::class.java) // Assert the error is then cleared - submittedState.eventSink(ChangeServerEvents.ClearError) + submittedState.eventSink(AccountProviderEvents.ClearError) val clearedState = awaitItem() - assertThat(clearedState.changeServerAction).isEqualTo(Async.Uninitialized) + assertThat(clearedState.loginFlow).isEqualTo(Async.Uninitialized) } } } From 97090b9007ee20335399ad564598b1143734916c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Jun 2023 14:50:31 +0200 Subject: [PATCH 16/61] Add test for ChangeAccountProviderPresenter --- .../ChangeAccountProviderPresenterTest.kt | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenterTest.kt diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenterTest.kt new file mode 100644 index 0000000000..add636a4c4 --- /dev/null +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenterTest.kt @@ -0,0 +1,48 @@ +/* + * 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 + +import app.cash.molecule.RecompositionClock +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.features.login.impl.accountprovider.item.AccountProvider +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class ChangeAccountProviderPresenterTest { + @Test + fun `present - initial state`() = runTest { + val presenter = ChangeAccountProviderPresenter( + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.accountProviders).isEqualTo( + listOf( + AccountProvider( + title = "matrix.org", + subtitle = null, + isPublic = true, + isMatrixOrg = true, + ) + ) + ) + } + } +} From ae38ffa91433c142d61e75c6410531a1071a23c4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Jun 2023 15:18:23 +0200 Subject: [PATCH 17/61] Add test for ChangeAccountProviderPresenter and other presenters. --- .../ChangeAccountProviderFormPresenter.kt | 1 + .../ChangeAccountProviderStateProvider.kt | 8 + .../form/DefaultHomeserverResolver.kt | 120 +++++++ .../form/HomeserverData.kt | 2 +- .../form/HomeserverResolver.kt | 97 +----- .../ChangeAccountProviderFormPresenterTest.kt | 87 +++++ .../form/FakeHomeServerResolver.kt | 49 +++ .../LoginPasswordPresenterTest.kt | 161 +++++++++ .../login/impl/root/LoginRootPresenterTest.kt | 308 ------------------ .../components/form/TextFieldLocalState.kt | 2 +- 10 files changed, 432 insertions(+), 403 deletions(-) create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt create mode 100644 features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenterTest.kt create mode 100644 features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/FakeHomeServerResolver.kt create mode 100644 features/login/impl/src/test/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordPresenterTest.kt delete mode 100644 features/login/impl/src/test/kotlin/io/element/android/features/login/impl/root/LoginRootPresenterTest.kt diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt index 8262c35a27..ed8c98bb33 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt @@ -43,6 +43,7 @@ class ChangeAccountProviderFormPresenter @Inject constructor( fun handleEvents(event: ChangeAccountProviderFormEvents) { when (event) { is ChangeAccountProviderFormEvents.UserInput -> { + userInput.value = event.input localCoroutineScope.userInput(event.input) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderStateProvider.kt index c61e03cca3..6cc00dd0ee 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderStateProvider.kt @@ -51,3 +51,11 @@ fun aHomeserverDataList(): List { ) ) } + +fun aHomeserverData(): HomeserverData { + return HomeserverData( + userInput = "matrix", + homeserverUrl = "https://matrix.org", + isWellknownValid = true, + ) +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt new file mode 100644 index 0000000000..812f98f323 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt @@ -0,0 +1,120 @@ +/* + * 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 com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.login.impl.changeaccountprovider.form.network.WellknownRequest +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.core.bool.orFalse +import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.data.tryOrNull +import io.element.android.libraries.core.uri.ensureProtocol +import io.element.android.libraries.core.uri.isValidUrl +import io.element.android.libraries.di.AppScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject + +/** + * Resolve homeserver base on search terms + */ +@ContributesBinding(AppScope::class) +class DefaultHomeserverResolver @Inject constructor( + private val dispatchers: CoroutineDispatchers, + private val wellknownRequest: WellknownRequest, +): HomeserverResolver { + private val mutableFlow: MutableStateFlow>> = MutableStateFlow(Async.Uninitialized) + + override fun flow(): StateFlow>> = mutableFlow + + private var currentJob: Job? = null + + override suspend fun accept(userInput: String) { + currentJob?.cancel() + val cleanedUpUserInput = userInput.trim() + mutableFlow.tryEmit(Async.Uninitialized) + if (cleanedUpUserInput.length > 3) { + delay(300) + mutableFlow.tryEmit(Async.Loading()) + withContext(dispatchers.io) { + val list = getUrlCandidate(cleanedUpUserInput) + currentJob = resolveList(userInput, list) + } + } + } + + private fun CoroutineScope.resolveList(userInput: String, list: List): Job { + val currentList = mutableListOf() + return launch { + list.map { + async { + val isValid = tryOrNull { wellknownRequest.execute(it) }.orFalse() + if (isValid) { + // Emit the list as soon as possible + currentList.add(HomeserverData(userInput, it, true)) + mutableFlow.tryEmit(Async.Success(currentList)) + } + } + }.joinAll() + .also { + // If list is empty, and the user as entered an URL, do not block the user. + if (currentList.isEmpty()) { + if (userInput.isValidUrl()) { + mutableFlow.tryEmit( + Async.Success( + listOf( + HomeserverData( + userInput = userInput, + homeserverUrl = userInput, + isWellknownValid = false + ) + ) + ) + ) + } else { + mutableFlow.tryEmit(Async.Uninitialized) + } + } + } + } + } + + private fun getUrlCandidate(data: String): List { + return buildList { + val s = data.ensureProtocol() + .removeSuffix("/") + + // Always try what the user has entered + add(s) + + if (s.contains(".")) { + // TLD detected? + } else { + add("$s.org") + add("$s.com") + add("$s.io") + } + } + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverData.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverData.kt index 6d254e3738..44dd583f00 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverData.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverData.kt @@ -16,7 +16,7 @@ package io.element.android.features.login.impl.changeaccountprovider.form -data class HomeserverData( +data class HomeserverData constructor( // What the user has entered val userInput: String, // The computed homeserver url, for which a wellknown file has been retrieved, or just a valid Url diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverResolver.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverResolver.kt index d80b20f1fd..a7abb444f6 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverResolver.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverResolver.kt @@ -16,102 +16,13 @@ package io.element.android.features.login.impl.changeaccountprovider.form -import io.element.android.features.login.impl.changeaccountprovider.form.network.WellknownRequest import io.element.android.libraries.architecture.Async -import io.element.android.libraries.core.bool.orFalse -import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.core.data.tryOrNull -import io.element.android.libraries.core.uri.ensureProtocol -import io.element.android.libraries.core.uri.isValidUrl -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.async -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.joinAll -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import javax.inject.Inject /** - * Resolve homeserver base on search terms + * Resolve homeserver base on search terms. */ -class HomeserverResolver @Inject constructor( - private val dispatchers: CoroutineDispatchers, - private val wellknownRequest: WellknownRequest, -) { - private val mutableFlow: MutableStateFlow>> = MutableStateFlow(Async.Uninitialized) - - fun flow(): StateFlow>> = mutableFlow - - private var currentJob: Job? = null - - suspend fun accept(userInput: String) { - currentJob?.cancel() - val cleanedUpUserInput = userInput.trim() - mutableFlow.tryEmit(Async.Uninitialized) - if (cleanedUpUserInput.length > 3) { - delay(300) - mutableFlow.tryEmit(Async.Loading()) - withContext(dispatchers.io) { - val list = getUrlCandidate(cleanedUpUserInput) - currentJob = resolveList(userInput, list) - } - } - } - - private fun CoroutineScope.resolveList(userInput: String, list: List): Job { - val currentList = mutableListOf() - return launch { - list.map { - async { - val isValid = tryOrNull { wellknownRequest.execute(it) }.orFalse() - if (isValid) { - // Emit the list as soon as possible - currentList.add(HomeserverData(userInput, it, true)) - mutableFlow.tryEmit(Async.Success(currentList)) - } - } - }.joinAll() - .also { - // If list is empty, and the user as entered an URL, do not block the user. - if (currentList.isEmpty()) { - if (userInput.isValidUrl()) { - mutableFlow.tryEmit( - Async.Success( - listOf( - HomeserverData( - userInput = userInput, - homeserverUrl = userInput, - isWellknownValid = false - ) - ) - ) - ) - } else { - mutableFlow.tryEmit(Async.Uninitialized) - } - } - } - } - } - - private fun getUrlCandidate(data: String): List { - return buildList { - val s = data.ensureProtocol() - .removeSuffix("/") - - // Always try what the user has entered - add(s) - - if (s.contains(".")) { - // TLD detected? - } else { - add("$s.org") - add("$s.com") - add("$s.io") - } - } - } +interface HomeserverResolver { + fun flow(): StateFlow>> + suspend fun accept(userInput: String) } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenterTest.kt new file mode 100644 index 0000000000..e2b2a8f546 --- /dev/null +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenterTest.kt @@ -0,0 +1,87 @@ +/* + * 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 app.cash.molecule.RecompositionClock +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.architecture.Async +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class ChangeAccountProviderFormPresenterTest { + @Test + fun `present - initial state`() = runTest { + val homeServerResolver = FakeHomeServerResolver() + val presenter = ChangeAccountProviderFormPresenter( + homeServerResolver + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.userInput).isEmpty() + assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized) + } + } + + @Test + fun `present - enter text no result`() = runTest { + val homeServerResolver = FakeHomeServerResolver() + val presenter = ChangeAccountProviderFormPresenter( + homeServerResolver + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(ChangeAccountProviderFormEvents.UserInput("test")) + val withInputState = awaitItem() + assertThat(withInputState.userInput).isEqualTo("test") + assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized) + assertThat(awaitItem().userInputResult).isInstanceOf(Async.Loading::class.java) + assertThat(awaitItem().userInputResult).isEqualTo(Async.Uninitialized) + } + } + + @Test + fun `present - enter text one then two results`() = runTest { + val homeServerResolver = FakeHomeServerResolver() + homeServerResolver.givenResult( + listOf( + listOf(aHomeserverData()), + listOf(aHomeserverData(), aHomeserverData()), + ) + ) + val presenter = ChangeAccountProviderFormPresenter( + homeServerResolver + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(ChangeAccountProviderFormEvents.UserInput("test")) + val withInputState = awaitItem() + assertThat(withInputState.userInput).isEqualTo("test") + assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized) + assertThat(awaitItem().userInputResult).isInstanceOf(Async.Loading::class.java) + assertThat(awaitItem().userInputResult).isEqualTo(Async.Success(listOf(aHomeserverData()))) + assertThat(awaitItem().userInputResult).isEqualTo(Async.Success(listOf(aHomeserverData(), aHomeserverData()))) + } + } +} diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/FakeHomeServerResolver.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/FakeHomeServerResolver.kt new file mode 100644 index 0000000000..db86e09903 --- /dev/null +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/FakeHomeServerResolver.kt @@ -0,0 +1,49 @@ +/* + * 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 io.element.android.libraries.matrix.test.FAKE_DELAY_IN_MS +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class FakeHomeServerResolver : HomeserverResolver { + private var pendingResult: List> = emptyList() + fun givenResult(result: List>) { + pendingResult = result + } + + private val mutableFlow: MutableStateFlow>> = MutableStateFlow(Async.Uninitialized) + + override fun flow(): StateFlow>> = mutableFlow + + override suspend fun accept(userInput: String) { + mutableFlow.tryEmit(Async.Uninitialized) + delay(FAKE_DELAY_IN_MS) + mutableFlow.tryEmit(Async.Loading()) + // Sending the pending result + if (pendingResult.isEmpty()) { + mutableFlow.tryEmit(Async.Uninitialized) + } else { + pendingResult.forEach { + delay(FAKE_DELAY_IN_MS) + mutableFlow.tryEmit(Async.Success(it)) + } + } + } +} diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordPresenterTest.kt new file mode 100644 index 0000000000..996f0b88bf --- /dev/null +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordPresenterTest.kt @@ -0,0 +1,161 @@ +/* + * 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.loginpassword + +import app.cash.molecule.RecompositionClock +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.features.login.impl.datasource.AccountProviderDataSource +import io.element.android.features.login.impl.util.defaultAccountProvider +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.test.A_HOMESERVER +import io.element.android.libraries.matrix.test.A_PASSWORD +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.A_THROWABLE +import io.element.android.libraries.matrix.test.A_USER_NAME +import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class LoginPasswordPresenterTest { + @Test + fun `present - initial state`() = runTest { + val authenticationService = FakeAuthenticationService() + val accountProviderDataSource = AccountProviderDataSource() + val presenter = LoginPasswordPresenter( + authenticationService, + accountProviderDataSource, + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.accountProvider).isEqualTo(defaultAccountProvider) + assertThat(initialState.formState).isEqualTo(LoginFormState.Default) + assertThat(initialState.loginAction).isEqualTo(Async.Uninitialized) + assertThat(initialState.submitEnabled).isFalse() + } + } + + @Test + fun `present - enter login and password`() = runTest { + val authenticationService = FakeAuthenticationService() + val accountProviderDataSource = AccountProviderDataSource() + val presenter = LoginPasswordPresenter( + authenticationService, + accountProviderDataSource, + ) + authenticationService.givenHomeserver(A_HOMESERVER) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(LoginPasswordEvents.SetLogin(A_USER_NAME)) + val loginState = awaitItem() + assertThat(loginState.formState).isEqualTo(LoginFormState(login = A_USER_NAME, password = "")) + assertThat(loginState.submitEnabled).isFalse() + initialState.eventSink.invoke(LoginPasswordEvents.SetPassword(A_PASSWORD)) + val loginAndPasswordState = awaitItem() + assertThat(loginAndPasswordState.formState).isEqualTo(LoginFormState(login = A_USER_NAME, password = A_PASSWORD)) + assertThat(loginAndPasswordState.submitEnabled).isTrue() + } + } + + @Test + fun `present - submit`() = runTest { + val authenticationService = FakeAuthenticationService() + val accountProviderDataSource = AccountProviderDataSource() + val presenter = LoginPasswordPresenter( + authenticationService, + accountProviderDataSource, + ) + authenticationService.givenHomeserver(A_HOMESERVER) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(LoginPasswordEvents.SetLogin(A_USER_NAME)) + initialState.eventSink.invoke(LoginPasswordEvents.SetPassword(A_PASSWORD)) + skipItems(1) + val loginAndPasswordState = awaitItem() + loginAndPasswordState.eventSink.invoke(LoginPasswordEvents.Submit) + val submitState = awaitItem() + assertThat(submitState.loginAction).isInstanceOf(Async.Loading::class.java) + val loggedInState = awaitItem() + assertThat(loggedInState.loginAction).isEqualTo(Async.Success(A_SESSION_ID)) + } + } + + @Test + fun `present - submit with error`() = runTest { + val authenticationService = FakeAuthenticationService() + val accountProviderDataSource = AccountProviderDataSource() + val presenter = LoginPasswordPresenter( + authenticationService, + accountProviderDataSource, + ) + authenticationService.givenHomeserver(A_HOMESERVER) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(LoginPasswordEvents.SetLogin(A_USER_NAME)) + initialState.eventSink.invoke(LoginPasswordEvents.SetPassword(A_PASSWORD)) + skipItems(1) + val loginAndPasswordState = awaitItem() + authenticationService.givenLoginError(A_THROWABLE) + loginAndPasswordState.eventSink.invoke(LoginPasswordEvents.Submit) + val submitState = awaitItem() + assertThat(submitState.loginAction).isInstanceOf(Async.Loading::class.java) + val loggedInState = awaitItem() + assertThat(loggedInState.loginAction).isEqualTo(Async.Failure(A_THROWABLE)) + } + } + + @Test + fun `present - clear error`() = runTest { + val authenticationService = FakeAuthenticationService() + val accountProviderDataSource = AccountProviderDataSource() + val presenter = LoginPasswordPresenter( + authenticationService, + accountProviderDataSource, + ) + authenticationService.givenHomeserver(A_HOMESERVER) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(LoginPasswordEvents.SetLogin(A_USER_NAME)) + initialState.eventSink.invoke(LoginPasswordEvents.SetPassword(A_PASSWORD)) + skipItems(1) + val loginAndPasswordState = awaitItem() + authenticationService.givenLoginError(A_THROWABLE) + loginAndPasswordState.eventSink.invoke(LoginPasswordEvents.Submit) + val submitState = awaitItem() + assertThat(submitState.loginAction).isInstanceOf(Async.Loading::class.java) + val loggedInState = awaitItem() + // Check an error was returned + assertThat(loggedInState.loginAction).isEqualTo(Async.Failure(A_THROWABLE)) + // Assert the error is then cleared + loggedInState.eventSink(LoginPasswordEvents.ClearError) + val clearedState = awaitItem() + assertThat(clearedState.loginAction).isEqualTo(Async.Uninitialized) + } + } +} diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/root/LoginRootPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/root/LoginRootPresenterTest.kt deleted file mode 100644 index 0dee8d47c0..0000000000 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/root/LoginRootPresenterTest.kt +++ /dev/null @@ -1,308 +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.root - -import app.cash.molecule.RecompositionClock -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test -import com.google.common.truth.Truth.assertThat -import io.element.android.features.login.api.oidc.OidcAction -import io.element.android.features.login.impl.oidc.customtab.DefaultOidcActionFlow -import io.element.android.features.login.impl.util.LoginConstants -import io.element.android.libraries.architecture.Async -import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails -import io.element.android.libraries.matrix.test.A_HOMESERVER -import io.element.android.libraries.matrix.test.A_HOMESERVER_OIDC -import io.element.android.libraries.matrix.test.A_PASSWORD -import io.element.android.libraries.matrix.test.A_SESSION_ID -import io.element.android.libraries.matrix.test.A_THROWABLE -import io.element.android.libraries.matrix.test.A_USER_NAME -import io.element.android.libraries.matrix.test.auth.A_OIDC_DATA -import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService -import kotlinx.coroutines.test.runTest -import org.junit.Test - -class LoginRootPresenterTest { - @Test - fun `present - initial state`() = runTest { - val presenter = LoginRootPresenter( - FakeAuthenticationService(), - DefaultOidcActionFlow(), - ) - moleculeFlow(RecompositionClock.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - assertThat(initialState.homeserverUrl).isEqualTo(LoginConstants.DEFAULT_HOMESERVER_URL) - assertThat(initialState.homeserverDetails).isEqualTo(Async.Uninitialized) - assertThat(initialState.loggedInState).isEqualTo(LoggedInState.NotLoggedIn) - assertThat(initialState.formState).isEqualTo(LoginFormState.Default) - assertThat(initialState.submitEnabled).isFalse() - cancelAndIgnoreRemainingEvents() - } - } - - @Test - fun `present - initial state server load`() = runTest { - val authenticationService = FakeAuthenticationService() - val oidcActionFlow = DefaultOidcActionFlow() - val presenter = LoginRootPresenter( - authenticationService, - oidcActionFlow, - ) - moleculeFlow(RecompositionClock.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - assertThat(initialState.homeserverUrl).isEqualTo(LoginConstants.DEFAULT_HOMESERVER_URL) - assertThat(initialState.homeserverDetails).isEqualTo(Async.Uninitialized) - assertThat(initialState.loggedInState).isEqualTo(LoggedInState.NotLoggedIn) - assertThat(initialState.formState).isEqualTo(LoginFormState.Default) - assertThat(initialState.submitEnabled).isFalse() - val loadingState = awaitItem() - assertThat(loadingState.homeserverDetails).isEqualTo(Async.Loading()) - authenticationService.givenHomeserver(A_HOMESERVER) - skipItems(1) - val loadedState = awaitItem() - assertThat(loadedState.homeserverDetails).isEqualTo(Async.Success(A_HOMESERVER)) - } - } - - @Test - fun `present - initial state server load error and retry`() = runTest { - val authenticationService = FakeAuthenticationService() - val oidcActionFlow = DefaultOidcActionFlow() - val presenter = LoginRootPresenter( - authenticationService, - oidcActionFlow, - ) - moleculeFlow(RecompositionClock.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - assertThat(initialState.homeserverUrl).isEqualTo(LoginConstants.DEFAULT_HOMESERVER_URL) - assertThat(initialState.homeserverDetails).isEqualTo(Async.Uninitialized) - assertThat(initialState.loggedInState).isEqualTo(LoggedInState.NotLoggedIn) - assertThat(initialState.formState).isEqualTo(LoginFormState.Default) - assertThat(initialState.submitEnabled).isFalse() - val loadingState = awaitItem() - assertThat(loadingState.homeserverDetails).isEqualTo(Async.Loading()) - val aThrowable = Throwable("Error") - authenticationService.givenChangeServerError(aThrowable) - val errorState = awaitItem() - assertThat(errorState.homeserverDetails).isEqualTo(Async.Failure(aThrowable)) - // Retry - errorState.eventSink.invoke(LoginRootEvents.RetryFetchServerInfo) - val loadingState2 = awaitItem() - assertThat(loadingState2.homeserverDetails).isEqualTo(Async.Loading()) - authenticationService.givenChangeServerError(null) - authenticationService.givenHomeserver(A_HOMESERVER) - skipItems(1) - val loadedState = awaitItem() - assertThat(loadedState.homeserverDetails).isEqualTo(Async.Success(A_HOMESERVER)) - } - } - - @Test - fun `present - enter login and password`() = runTest { - val authenticationService = FakeAuthenticationService() - val oidcActionFlow = DefaultOidcActionFlow() - val presenter = LoginRootPresenter( - authenticationService, - oidcActionFlow, - ) - authenticationService.givenHomeserver(A_HOMESERVER) - moleculeFlow(RecompositionClock.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - initialState.eventSink.invoke(LoginRootEvents.SetLogin(A_USER_NAME)) - val loginState = awaitItem() - assertThat(loginState.formState).isEqualTo(LoginFormState(login = A_USER_NAME, password = "")) - assertThat(loginState.submitEnabled).isFalse() - initialState.eventSink.invoke(LoginRootEvents.SetPassword(A_PASSWORD)) - val loginAndPasswordState = awaitItem() - assertThat(loginAndPasswordState.formState).isEqualTo(LoginFormState(login = A_USER_NAME, password = A_PASSWORD)) - assertThat(loginAndPasswordState.submitEnabled).isTrue() - } - } - - @Test - fun `present - oidc login`() = runTest { - val authenticationService = FakeAuthenticationService() - val oidcActionFlow = DefaultOidcActionFlow() - val presenter = LoginRootPresenter( - authenticationService, - oidcActionFlow, - ) - authenticationService.givenHomeserver(A_HOMESERVER_OIDC) - moleculeFlow(RecompositionClock.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - assertThat(initialState.submitEnabled).isTrue() - initialState.eventSink.invoke(LoginRootEvents.Submit) - val oidcState = awaitItem() - assertThat(oidcState.loggedInState).isEqualTo(LoggedInState.OidcStarted(A_OIDC_DATA)) - } - } - - @Test - fun `present - oidc login error`() = runTest { - val authenticationService = FakeAuthenticationService() - val oidcActionFlow = DefaultOidcActionFlow() - val presenter = LoginRootPresenter( - authenticationService, - oidcActionFlow, - ) - authenticationService.givenHomeserver(A_HOMESERVER_OIDC) - authenticationService.givenOidcError(A_THROWABLE) - moleculeFlow(RecompositionClock.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - assertThat(initialState.submitEnabled).isTrue() - initialState.eventSink.invoke(LoginRootEvents.Submit) - val oidcState = awaitItem() - assertThat(oidcState.loggedInState).isEqualTo(LoggedInState.ErrorLoggingIn(A_THROWABLE)) - } - } - - @Test - fun `present - oidc custom tab login`() = runTest { - val authenticationService = FakeAuthenticationService() - val oidcActionFlow = DefaultOidcActionFlow() - val presenter = LoginRootPresenter( - authenticationService, - oidcActionFlow, - ) - authenticationService.givenHomeserver(A_HOMESERVER_OIDC) - moleculeFlow(RecompositionClock.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - assertThat(initialState.submitEnabled).isTrue() - initialState.eventSink.invoke(LoginRootEvents.Submit) - val oidcState = awaitItem() - assertThat(oidcState.loggedInState).isEqualTo(LoggedInState.OidcStarted(A_OIDC_DATA)) - // Oidc cancel, sdk error - authenticationService.givenOidcCancelError(A_THROWABLE) - oidcActionFlow.post(OidcAction.GoBack) - val stateCancelSdkError = awaitItem() - assertThat(stateCancelSdkError.loggedInState).isEqualTo(LoggedInState.ErrorLoggingIn(A_THROWABLE)) - // Oidc cancel, sdk OK - authenticationService.givenOidcCancelError(null) - oidcActionFlow.post(OidcAction.GoBack) - val stateCancel = awaitItem() - assertThat(stateCancel.loggedInState).isEqualTo(LoggedInState.NotLoggedIn) - // Oidc success, sdk error - authenticationService.givenLoginError(A_THROWABLE) - oidcActionFlow.post(OidcAction.Success(A_OIDC_DATA.url)) - val stateSuccessSdkErrorLoading = awaitItem() - assertThat(stateSuccessSdkErrorLoading.loggedInState).isEqualTo(LoggedInState.LoggingIn) - val stateSuccessSdkError = awaitItem() - assertThat(stateSuccessSdkError.loggedInState).isEqualTo(LoggedInState.ErrorLoggingIn(A_THROWABLE)) - // Oidc success - authenticationService.givenLoginError(null) - oidcActionFlow.post(OidcAction.Success(A_OIDC_DATA.url)) - val stateSuccess = awaitItem() - assertThat(stateSuccess.loggedInState).isEqualTo(LoggedInState.LoggingIn) - val stateSuccessLoggedIn = awaitItem() - assertThat(stateSuccessLoggedIn.loggedInState).isEqualTo(LoggedInState.LoggedIn(A_SESSION_ID)) - } - } - - @Test - fun `present - submit`() = runTest { - val authenticationService = FakeAuthenticationService() - val oidcActionFlow = DefaultOidcActionFlow() - val presenter = LoginRootPresenter( - authenticationService, - oidcActionFlow, - ) - authenticationService.givenHomeserver(A_HOMESERVER) - moleculeFlow(RecompositionClock.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - initialState.eventSink.invoke(LoginRootEvents.SetLogin(A_USER_NAME)) - initialState.eventSink.invoke(LoginRootEvents.SetPassword(A_PASSWORD)) - skipItems(1) - val loginAndPasswordState = awaitItem() - loginAndPasswordState.eventSink.invoke(LoginRootEvents.Submit) - val submitState = awaitItem() - assertThat(submitState.loggedInState).isEqualTo(LoggedInState.LoggingIn) - val loggedInState = awaitItem() - assertThat(loggedInState.loggedInState).isEqualTo(LoggedInState.LoggedIn(A_SESSION_ID)) - } - } - - @Test - fun `present - submit with error`() = runTest { - val authenticationService = FakeAuthenticationService() - val oidcActionFlow = DefaultOidcActionFlow() - val presenter = LoginRootPresenter( - authenticationService, - oidcActionFlow, - ) - authenticationService.givenHomeserver(A_HOMESERVER) - moleculeFlow(RecompositionClock.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - initialState.eventSink.invoke(LoginRootEvents.SetLogin(A_USER_NAME)) - initialState.eventSink.invoke(LoginRootEvents.SetPassword(A_PASSWORD)) - skipItems(1) - val loginAndPasswordState = awaitItem() - authenticationService.givenLoginError(A_THROWABLE) - loginAndPasswordState.eventSink.invoke(LoginRootEvents.Submit) - val submitState = awaitItem() - assertThat(submitState.loggedInState).isEqualTo(LoggedInState.LoggingIn) - val loggedInState = awaitItem() - assertThat(loggedInState.loggedInState).isEqualTo(LoggedInState.ErrorLoggingIn(A_THROWABLE)) - } - } - - @Test - fun `present - clear error`() = runTest { - val authenticationService = FakeAuthenticationService() - val oidcActionFlow = DefaultOidcActionFlow() - val presenter = LoginRootPresenter( - authenticationService, - oidcActionFlow, - ) - authenticationService.givenHomeserver(A_HOMESERVER) - moleculeFlow(RecompositionClock.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - // Submit will return an error - authenticationService.givenLoginError(A_THROWABLE) - initialState.eventSink(LoginRootEvents.Submit) - awaitItem() // Skip LoggingIn state - - // Check an error was returned - val submittedState = awaitItem() - assertThat(submittedState.loggedInState).isEqualTo(LoggedInState.ErrorLoggingIn(A_THROWABLE)) - - // Assert the error is then cleared - submittedState.eventSink(LoginRootEvents.ClearError) - val clearedState = awaitItem() - assertThat(clearedState.loggedInState).isEqualTo(LoggedInState.NotLoggedIn) - } - } -} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/form/TextFieldLocalState.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/form/TextFieldLocalState.kt index c7366dcfb7..0de4dbba78 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/form/TextFieldLocalState.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/form/TextFieldLocalState.kt @@ -22,5 +22,5 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @Composable -public fun textFieldState(stateValue: String): MutableState = +fun textFieldState(stateValue: String): MutableState = remember(stateValue) { mutableStateOf(stateValue) } From c97751cce538e43f082e2977ecd59bb31a597274 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Jun 2023 16:33:30 +0200 Subject: [PATCH 18/61] Cleanup --- .../kotlin/io/element/android/appnav/LoggedInEventProcessor.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt index f1d87330e1..c07a2d72e9 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt @@ -23,7 +23,6 @@ import io.element.android.libraries.matrix.api.verification.SessionVerificationS import io.element.android.libraries.matrix.api.verification.VerificationFlowState import io.element.android.libraries.ui.strings.R import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.launchIn @@ -31,7 +30,6 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import javax.inject.Inject -import kotlin.coroutines.coroutineContext class LoggedInEventProcessor @Inject constructor( private val snackbarDispatcher: SnackbarDispatcher, From 3eeeee2c9833feebcb017ba2f65ac51c4d21c40b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Jun 2023 16:43:56 +0200 Subject: [PATCH 19/61] Code quality. --- .../changeaccountprovider/form/DefaultHomeserverResolver.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt index 812f98f323..356dbf4f42 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt @@ -111,9 +111,9 @@ class DefaultHomeserverResolver @Inject constructor( if (s.contains(".")) { // TLD detected? } else { - add("$s.org") - add("$s.com") - add("$s.io") + add("${s}.org") + add("${s}.com") + add("${s}.io") } } } From b4723bb1827eb2c58b6fbc2b0dc2195f0f8b158c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Jun 2023 16:55:37 +0200 Subject: [PATCH 20/61] Fix some errors --- .../item/AccountProviderView.kt | 8 +-- .../ChangeAccountProviderEvents.kt | 20 ------ .../form/ChangeAccountProviderFormView.kt | 5 +- .../form/network/WellKnown.kt | 2 +- .../form/network/WellKnownBaseConfig.kt | 2 +- .../form/network/WellknownAPI.kt | 1 + .../form/network/WellknownRequest.kt | 2 +- .../impl/loginpassword/LoginPasswordView.kt | 12 ++-- .../atomic/atoms/SeparatorAtom.kt | 62 ------------------- 9 files changed, 16 insertions(+), 98 deletions(-) delete mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderEvents.kt delete mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/SeparatorAtom.kt diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProviderView.kt index eb2f67947e..ae3aea8878 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProviderView.kt @@ -38,9 +38,9 @@ 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.atomic.atoms.SeparatorAtom 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 @@ -56,7 +56,7 @@ fun AccountProviderView( Column(modifier = modifier .fillMaxWidth() .clickable { onClick() }) { - SeparatorAtom() + Divider() Column( modifier = Modifier .fillMaxWidth() @@ -82,7 +82,7 @@ fun AccountProviderView( ) } Text( - modifier = modifier + modifier = Modifier .padding(start = 16.dp) .weight(1f), text = item.title, @@ -102,7 +102,7 @@ fun AccountProviderView( } if (item.subtitle != null) { Text( - modifier = modifier + modifier = Modifier .padding(start = 46.dp, bottom = 12.dp, end = 26.dp), text = item.subtitle, style = ElementTextStyles.Regular.subheadline.copy(textAlign = TextAlign.Start), diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderEvents.kt deleted file mode 100644 index 0579006dfc..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderEvents.kt +++ /dev/null @@ -1,20 +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 - -sealed interface ChangeAccountProviderEvents { -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt index 9e00d9c47d..311eb21684 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class) +@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) package io.element.android.features.login.impl.changeaccountprovider.form @@ -71,7 +71,7 @@ import io.element.android.libraries.designsystem.theme.components.Text fun ChangeAccountProviderFormView( state: ChangeAccountProviderFormState, modifier: Modifier = Modifier, - onBackPressed: () -> Unit, + onBackPressed: () -> Unit = {}, onAccountProviderClicked: (AccountProvider) -> Unit = {}, ) { val eventSink = state.eventSink @@ -197,6 +197,5 @@ fun ChangeAccountProviderFormViewDarkPreview(@PreviewParameter(ChangeAccountProv private fun ContentToPreview(state: ChangeAccountProviderFormState) { ChangeAccountProviderFormView( state = state, - onBackPressed = { } ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnown.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnown.kt index a78f41a266..60006e7530 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnown.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnown.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * 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. diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownBaseConfig.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownBaseConfig.kt index b0d33e1854..619b656d22 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownBaseConfig.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownBaseConfig.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * 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. diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownAPI.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownAPI.kt index 13153cfabb..0362a67c60 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownAPI.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownAPI.kt @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package io.element.android.features.login.impl.changeaccountprovider.form.network import retrofit2.http.GET diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownRequest.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownRequest.kt index a8d911ee86..709eea0255 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownRequest.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownRequest.kt @@ -24,7 +24,7 @@ class WellknownRequest @Inject constructor( private val retrofitFactory: RetrofitFactory, ) { /** - * Return true if the wellknown can be retrieved and is valid + * Return true if the wellknown can be retrieved and is valid. * @param baseUrl for instance https://matrix.org */ suspend fun execute(baseUrl: String): Boolean { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt index c7decb8cb7..958ab94ae3 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt @@ -139,13 +139,13 @@ fun LoginPasswordView( submit = ::submit ) } - } - } - if (state.loginAction is Async.Failure) { - LoginErrorDialog(error = state.loginAction.error, onDismiss = { - state.eventSink(LoginPasswordEvents.ClearError) - }) + if (state.loginAction is Async.Failure) { + LoginErrorDialog(error = state.loginAction.error, onDismiss = { + state.eventSink(LoginPasswordEvents.ClearError) + }) + } + } } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/SeparatorAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/SeparatorAtom.kt deleted file mode 100644 index fe15ae5480..0000000000 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/SeparatorAtom.kt +++ /dev/null @@ -1,62 +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.libraries.designsystem.atomic.atoms - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.preview.ElementPreviewDark -import io.element.android.libraries.designsystem.preview.ElementPreviewLight -import io.element.android.libraries.designsystem.theme.LocalColors - -@Composable -fun SeparatorAtom( - modifier: Modifier = Modifier, - horizontalPadding: Dp = 0.dp, -) { - Spacer( - modifier = modifier - .height(0.5f.dp) - .fillMaxWidth() - .padding(horizontal = horizontalPadding) - .background( - color = LocalColors.current.quinary, - ) - ) -} - -@Preview -@Composable -internal fun SeparatorAtomLightPreview() = - ElementPreviewLight { ContentToPreview() } - -@Preview -@Composable -internal fun SeparatorAtomDarkPreview() = - ElementPreviewDark { ContentToPreview() } - -@Composable -private fun ContentToPreview() { - SeparatorAtom(horizontalPadding = 20.dp) -} From de31591eba160f4d2218c5f72614d37d80a0f5c0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Jun 2023 16:58:31 +0200 Subject: [PATCH 21/61] Moar fixes. --- .../form/ChangeAccountProviderFormEvents.kt | 2 +- ...ountProviderState.kt => ChangeAccountProviderFormState.kt} | 0 ...eProvider.kt => ChangeAccountProviderFormStateProvider.kt} | 2 +- .../form/ChangeAccountProviderFormView.kt | 4 ++-- .../changeaccountprovider/form/DefaultHomeserverResolver.kt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/{ChangeAccountProviderState.kt => ChangeAccountProviderFormState.kt} (100%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/{ChangeAccountProviderStateProvider.kt => ChangeAccountProviderFormStateProvider.kt} (96%) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormEvents.kt index 2e1180a211..79bbc5cc95 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormEvents.kt @@ -18,7 +18,7 @@ package io.element.android.features.login.impl.changeaccountprovider.form sealed interface ChangeAccountProviderFormEvents { /** - * 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 } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormState.kt similarity index 100% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderState.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormState.kt diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormStateProvider.kt similarity index 96% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderStateProvider.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormStateProvider.kt index 6cc00dd0ee..f5232f8239 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormStateProvider.kt @@ -19,7 +19,7 @@ package io.element.android.features.login.impl.changeaccountprovider.form import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.Async -open class ChangeAccountProviderStateFormProvider : PreviewParameterProvider { +open class ChangeAccountProviderFormStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aChangeAccountProviderFormState(), diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt index 311eb21684..e301cfd546 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt @@ -185,12 +185,12 @@ fun ChangeAccountProviderFormView( @Preview @Composable -fun ChangeAccountProviderFormViewLightPreview(@PreviewParameter(ChangeAccountProviderStateFormProvider::class) state: ChangeAccountProviderFormState) = +fun ChangeAccountProviderFormViewLightPreview(@PreviewParameter(ChangeAccountProviderFormStateProvider::class) state: ChangeAccountProviderFormState) = ElementPreviewLight { ContentToPreview(state) } @Preview @Composable -fun ChangeAccountProviderFormViewDarkPreview(@PreviewParameter(ChangeAccountProviderStateFormProvider::class) state: ChangeAccountProviderFormState) = +fun ChangeAccountProviderFormViewDarkPreview(@PreviewParameter(ChangeAccountProviderFormStateProvider::class) state: ChangeAccountProviderFormState) = ElementPreviewDark { ContentToPreview(state) } @Composable diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt index 356dbf4f42..73db831ac5 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt @@ -37,7 +37,7 @@ import kotlinx.coroutines.withContext import javax.inject.Inject /** - * Resolve homeserver base on search terms + * Resolve homeserver base on search terms. */ @ContributesBinding(AppScope::class) class DefaultHomeserverResolver @Inject constructor( From 248d0bad83a444de4d9aaa2315b91c9db89bb0aa Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 8 Jun 2023 10:32:24 +0200 Subject: [PATCH 22/61] Moar fixes --- .../ChangeAccountProviderView.kt | 2 +- .../impl/loginpassword/LoginPasswordView.kt | 41 +++++++------------ 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt index f0adfd101b..4c7dab1961 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt @@ -56,7 +56,7 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar fun ChangeAccountProviderView( state: ChangeAccountProviderState, modifier: Modifier = Modifier, - onBackPressed: () -> Unit, + onBackPressed: () -> Unit = {}, onAccountProviderClicked: (AccountProvider) -> Unit = {}, onOtherProviderClicked: () -> Unit = {}, ) { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt index 958ab94ae3..d52843ea5e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt @@ -133,11 +133,22 @@ fun LoginPasswordView( subTitle = stringResource(id = R.string.screen_login_form_header) ) Spacer(Modifier.height(32.dp)) - ServerDetailForm( - state = state, + LoginForm(state = state, isLoading = isLoading, - submit = ::submit + onSubmit = ::submit ) + Spacer(Modifier.height(28.dp)) + // Submit + ButtonWithProgress( + text = stringResource(R.string.screen_login_submit), + showProgress = isLoading, + onClick = ::submit, + enabled = state.submitEnabled, + modifier = Modifier + .fillMaxWidth() + .testTag(TestTags.loginContinue) + ) + Spacer(modifier = Modifier.height(32.dp)) } if (state.loginAction is Async.Failure) { @@ -149,30 +160,6 @@ fun LoginPasswordView( } } -@Composable -fun ServerDetailForm( - state: LoginPasswordState, - isLoading: Boolean, - submit: () -> Unit, - modifier: Modifier = Modifier, -) { - LoginForm(state = state, isLoading = isLoading, onSubmit = submit, modifier = modifier) - - Spacer(Modifier.height(28.dp)) - - // Submit - ButtonWithProgress( - text = stringResource(R.string.screen_login_submit), - showProgress = isLoading, - onClick = submit, - enabled = state.submitEnabled, - modifier = Modifier - .fillMaxWidth() - .testTag(TestTags.loginContinue) - ) - Spacer(modifier = Modifier.height(32.dp)) -} - @OptIn(ExperimentalComposeUiApi::class) @Composable internal fun LoginForm( From 48a95c6e0110294715c80b857b508e8bec17b361 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 8 Jun 2023 14:32:58 +0200 Subject: [PATCH 23/61] Get more info from WellKnown request. --- .../accountprovider/item/AccountProvider.kt | 2 ++ .../item/AccountProviderProvider.kt | 6 +++- .../ChangeAccountProviderPresenter.kt | 2 ++ .../ChangeAccountProviderFormStateProvider.kt | 25 ++++++------- .../form/ChangeAccountProviderFormView.kt | 21 +++++++---- .../form/DefaultHomeserverResolver.kt | 35 +++++++++++-------- .../form/HomeserverData.kt | 4 +-- .../form/network/WellKnown.kt | 17 ++++++++- .../network/WellKnownSlidyincSyncConfig.kt | 26 ++++++++++++++ .../form/network/WellknownRequest.kt | 19 ++-------- .../ChangeAccountProviderPresenterTest.kt | 2 ++ 11 files changed, 103 insertions(+), 56 deletions(-) create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownSlidyincSyncConfig.kt diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProvider.kt index 6f8adff403..c09d6fab9b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProvider.kt @@ -21,4 +21,6 @@ data class AccountProvider constructor( val subtitle: String? = null, val isPublic: Boolean = false, val isMatrixOrg: Boolean = false, + val isValid: Boolean = false, + val supportSlidingSync: Boolean = false, ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProviderProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProviderProvider.kt index c931c3e801..211aa4f5bc 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProviderProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProviderProvider.kt @@ -23,7 +23,9 @@ open class AccountProviderProvider : PreviewParameterProvider { get() = sequenceOf( anAccountProvider(), anAccountProvider().copy(subtitle = null), - anAccountProvider().copy(title = "Other", subtitle = null, isPublic = false, isMatrixOrg = false), + anAccountProvider().copy(subtitle = null, title = "no.sliding.sync", supportSlidingSync = false), + anAccountProvider().copy(subtitle = null, title = "invalid", isValid = false, supportSlidingSync = false), + anAccountProvider().copy(subtitle = null, title = "Other", isPublic = false, isMatrixOrg = false), // Add other state here ) } @@ -33,4 +35,6 @@ fun anAccountProvider() = AccountProvider( subtitle = "Matrix.org is an open network for secure, decentralized communication.", isPublic = true, isMatrixOrg = true, + isValid = true, + supportSlidingSync = true, ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt index 055541ca09..f80610d935 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt @@ -34,6 +34,8 @@ class ChangeAccountProviderPresenter @Inject constructor( subtitle = null, isPublic = true, isMatrixOrg = true, + isValid = true, + supportSlidingSync = true, ) ), ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormStateProvider.kt index f5232f8239..c73cbc5acb 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormStateProvider.kt @@ -39,23 +39,20 @@ fun aChangeAccountProviderFormState( fun aHomeserverDataList(): List { return listOf( - HomeserverData( - userInput = "matrix", - homeserverUrl = "https://matrix.org", - isWellknownValid = true, - ), - HomeserverData( - userInput = "matrix", - homeserverUrl = "https://matrix.io", - isWellknownValid = false, - ) + aHomeserverData(isWellknownValid = true, supportSlidingSync = true), + aHomeserverData(homeserverUrl = "https://no.sliding.sync", isWellknownValid = true, supportSlidingSync = false), + aHomeserverData(homeserverUrl = "https://invalid", isWellknownValid = false, supportSlidingSync = false), ) } -fun aHomeserverData(): HomeserverData { +fun aHomeserverData( + homeserverUrl: String = "https://matrix.org", + isWellknownValid: Boolean = true, + supportSlidingSync: Boolean = true, +): HomeserverData { return HomeserverData( - userInput = "matrix", - homeserverUrl = "https://matrix.org", - isWellknownValid = true, + homeserverUrl = homeserverUrl, + isWellknownValid = isWellknownValid, + supportSlidingSync = supportSlidingSync, ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt index e301cfd546..16fdd10c5c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt @@ -160,13 +160,7 @@ fun ChangeAccountProviderFormView( } is Async.Success -> { state.userInputResult.state.forEach { homeserverData -> - val isMatrixOrg = homeserverData.homeserverUrl == "https://matrix.org" - val item = AccountProvider( - title = homeserverData.homeserverUrl.removePrefix("http://").removePrefix("https://"), - subtitle = if (isMatrixOrg) stringResource(id = R.string.screen_change_account_provider_matrix_org_subtitle) else null, - isPublic = isMatrixOrg, // There is no need to know for other servers right now - isMatrixOrg = isMatrixOrg, - ) + val item = homeserverData.toAccountProvider() AccountProviderView( item = item, onClick = { @@ -183,6 +177,19 @@ fun ChangeAccountProviderFormView( } } +@Composable +private fun HomeserverData.toAccountProvider(): AccountProvider { + val isMatrixOrg = homeserverUrl == "https://matrix.org" + return AccountProvider( + title = homeserverUrl.removePrefix("http://").removePrefix("https://"), + subtitle = if (isMatrixOrg) stringResource(id = R.string.screen_change_account_provider_matrix_org_subtitle) else null, + isPublic = isMatrixOrg, // There is no need to know for other servers right now + isMatrixOrg = isMatrixOrg, + isValid = isWellknownValid, + supportSlidingSync = supportSlidingSync, + ) +} + @Preview @Composable fun ChangeAccountProviderFormViewLightPreview(@PreviewParameter(ChangeAccountProviderFormStateProvider::class) state: ChangeAccountProviderFormState) = diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt index 73db831ac5..6f7274f35f 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt @@ -43,7 +43,7 @@ import javax.inject.Inject class DefaultHomeserverResolver @Inject constructor( private val dispatchers: CoroutineDispatchers, private val wellknownRequest: WellknownRequest, -): HomeserverResolver { +) : HomeserverResolver { private val mutableFlow: MutableStateFlow>> = MutableStateFlow(Async.Uninitialized) override fun flow(): StateFlow>> = mutableFlow @@ -52,14 +52,14 @@ class DefaultHomeserverResolver @Inject constructor( override suspend fun accept(userInput: String) { currentJob?.cancel() - val cleanedUpUserInput = userInput.trim() + val cleanedUpUserInput = userInput.trim().ensureProtocol().removeSuffix("/") mutableFlow.tryEmit(Async.Uninitialized) if (cleanedUpUserInput.length > 3) { delay(300) mutableFlow.tryEmit(Async.Loading()) withContext(dispatchers.io) { val list = getUrlCandidate(cleanedUpUserInput) - currentJob = resolveList(userInput, list) + currentJob = resolveList(cleanedUpUserInput, list) } } } @@ -69,10 +69,18 @@ class DefaultHomeserverResolver @Inject constructor( return launch { list.map { async { - val isValid = tryOrNull { wellknownRequest.execute(it) }.orFalse() + val wellKnown = tryOrNull { wellknownRequest.execute(it) } + val isValid = wellKnown?.isValid().orFalse() + val supportSlidingSync = wellKnown?.supportSlidingSync().orFalse() if (isValid) { // Emit the list as soon as possible - currentList.add(HomeserverData(userInput, it, true)) + currentList.add( + HomeserverData( + homeserverUrl = it, + isWellknownValid = true, + supportSlidingSync = supportSlidingSync + ) + ) mutableFlow.tryEmit(Async.Success(currentList)) } } @@ -85,9 +93,9 @@ class DefaultHomeserverResolver @Inject constructor( Async.Success( listOf( HomeserverData( - userInput = userInput, homeserverUrl = userInput, - isWellknownValid = false + isWellknownValid = false, + supportSlidingSync = false, ) ) ) @@ -102,18 +110,15 @@ class DefaultHomeserverResolver @Inject constructor( private fun getUrlCandidate(data: String): List { return buildList { - val s = data.ensureProtocol() - .removeSuffix("/") - // Always try what the user has entered - add(s) + add(data) - if (s.contains(".")) { + if (data.contains(".")) { // TLD detected? } else { - add("${s}.org") - add("${s}.com") - add("${s}.io") + add("${data}.org") + add("${data}.com") + add("${data}.io") } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverData.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverData.kt index 44dd583f00..e0d7dbc0d5 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverData.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverData.kt @@ -17,10 +17,10 @@ package io.element.android.features.login.impl.changeaccountprovider.form data class HomeserverData constructor( - // What the user has entered - val userInput: String, // The computed homeserver url, for which a wellknown file has been retrieved, or just a valid Url val homeserverUrl: String, // True if a wellknown file has been found and is valid. If false, it means that the [homeserverUrl] is valid val isWellknownValid: Boolean, + // True if a wellknown file has been found and is valid and is claiming a sliding sync Url + val supportSlidingSync: Boolean, ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnown.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnown.kt index 60006e7530..283e8eb496 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnown.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnown.kt @@ -16,6 +16,7 @@ package io.element.android.features.login.impl.changeaccountprovider.form.network +import io.element.android.libraries.core.bool.orFalse import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -28,6 +29,9 @@ import kotlinx.serialization.Serializable * }, * "m.identity_server": { * "base_url": "https://vector.im" + * }, + * "org.matrix.msc3575.proxy": { + * "url": "https://slidingsync.lab.matrix.org" * } * } * @@ -40,4 +44,15 @@ data class WellKnown( @SerialName("m.identity_server") val identityServer: WellKnownBaseConfig? = null, -) + + @SerialName("org.matrix.msc3575.proxy") + val slidingSyncProxy: WellKnownSlidingSyncConfig? = null, +) { + fun isValid(): Boolean { + return homeServer?.baseURL?.isNotBlank().orFalse() + } + + fun supportSlidingSync(): Boolean { + return slidingSyncProxy?.url?.isNotBlank().orFalse() + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownSlidyincSyncConfig.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownSlidyincSyncConfig.kt new file mode 100644 index 0000000000..4ff8633d0d --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownSlidyincSyncConfig.kt @@ -0,0 +1,26 @@ +/* + * 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.network + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class WellKnownSlidingSyncConfig( + @SerialName("url") + val url: String? = null, +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownRequest.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownRequest.kt index 709eea0255..1f18edc695 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownRequest.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownRequest.kt @@ -15,32 +15,19 @@ */ package io.element.android.features.login.impl.changeaccountprovider.form.network -import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.network.RetrofitFactory -import timber.log.Timber import javax.inject.Inject class WellknownRequest @Inject constructor( private val retrofitFactory: RetrofitFactory, ) { /** - * Return true if the wellknown can be retrieved and is valid. + * Return the WellKnown data, if found. * @param baseUrl for instance https://matrix.org */ - suspend fun execute(baseUrl: String): Boolean { + suspend fun execute(baseUrl: String): WellKnown { val wellknownApi = retrofitFactory.create(baseUrl) .create(WellknownAPI::class.java) - - return try { - val response = wellknownApi.getWellKnown() - response.isValid() - } catch (throwable: Throwable) { - Timber.e(throwable) - false - } + return wellknownApi.getWellKnown() } } - -private fun WellKnown.isValid(): Boolean { - return homeServer?.baseURL?.isNotBlank().orFalse() -} diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenterTest.kt index add636a4c4..911ecaab78 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenterTest.kt @@ -40,6 +40,8 @@ class ChangeAccountProviderPresenterTest { subtitle = null, isPublic = true, isMatrixOrg = true, + isValid = true, + supportSlidingSync = true, ) ) ) From 9a07b72a0ee0c128dc61bbff56546847737072b4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 8 Jun 2023 15:52:45 +0200 Subject: [PATCH 24/61] Validate server on user click. --- .../features/login/impl/LoginFlowNode.kt | 7 +- .../accountprovider/AccountProviderNode.kt | 11 +-- .../accountprovider/AccountProviderView.kt | 39 ++++----- .../SlidingSyncNotSupportedDialog.kt | 8 +- .../ChangeAccountProviderNode.kt | 13 +-- .../ChangeAccountProviderPresenter.kt | 4 + .../ChangeAccountProviderState.kt | 2 + .../ChangeAccountProviderStateProvider.kt | 2 + .../ChangeAccountProviderView.kt | 12 ++- .../common/ChangeServerEvents.kt | 24 ++++++ .../common/ChangeServerPresenter.kt | 76 +++++++++++++++++ .../common/ChangeServerState.kt | 24 ++++++ .../common/ChangeServerStateProvider.kt | 32 +++++++ .../common/ChangeServerView.kt | 84 +++++++++++++++++++ .../form/ChangeAccountProviderFormNode.kt | 13 +-- .../ChangeAccountProviderFormPresenter.kt | 4 + .../form/ChangeAccountProviderFormState.kt | 2 + .../ChangeAccountProviderFormStateProvider.kt | 2 + .../form/ChangeAccountProviderFormView.kt | 12 ++- .../android/features/login/impl/util/Util.kt | 27 ++++++ .../components/async/AsyncFailure.kt | 6 +- 21 files changed, 353 insertions(+), 51 deletions(-) create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerEvents.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerPresenter.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerState.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerStateProvider.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerView.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/Util.kt diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt index 1c0ab78ee4..36b6d904e7 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt @@ -33,7 +33,6 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.features.login.impl.accountprovider.AccountProviderNode -import io.element.android.features.login.impl.accountprovider.item.AccountProvider 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 @@ -125,8 +124,7 @@ class LoginFlowNode @AssistedInject constructor( } NavTarget.ChangeAccountProvider -> { val callback = object : ChangeAccountProviderNode.Callback { - override fun onAccountProviderClicked(data: AccountProvider) { - accountProviderDataSource.userSelection(data) + override fun onDone() { // Go back to the Account Provider screen backstack.singleTop(NavTarget.AccountProvider) } @@ -140,8 +138,7 @@ class LoginFlowNode @AssistedInject constructor( } NavTarget.ChangeAccountProviderForm -> { val callback = object : ChangeAccountProviderFormNode.Callback { - override fun onAccountProviderClicked(data: AccountProvider) { - accountProviderDataSource.userSelection(data) + override fun onDone() { // Go back to the Account Provider screen backstack.singleTop(NavTarget.AccountProvider) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderNode.kt index aac5068294..dfcf6fd034 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderNode.kt @@ -16,9 +16,6 @@ package io.element.android.features.login.impl.accountprovider -import android.content.Context -import android.content.Intent -import android.net.Uri import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -29,10 +26,9 @@ import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode -import io.element.android.features.login.impl.util.LoginConstants +import io.element.android.features.login.impl.util.openLearnMorePage import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.inputs -import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.auth.OidcDetails @@ -72,11 +68,6 @@ class AccountProviderNode @AssistedInject constructor( plugins().forEach { it.onChangeAccountProvider() } } - private fun openLearnMorePage(context: Context) { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(LoginConstants.SLIDING_SYNC_READ_MORE_URL)) - tryOrNull { context.startActivity(intent) } - } - @Composable override fun View(modifier: Modifier) { val state = presenter.present() diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt index dcc219f93e..56ea41a3d9 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt @@ -60,8 +60,6 @@ fun AccountProviderView( } } val eventSink = state.eventSink - val invalidHomeserverError = (state.loginFlow as? Async.Failure)?.error as? ChangeServerError.InlineErrorMessage - val slidingSyncNotSupportedError = (state.loginFlow as? Async.Failure)?.error as? ChangeServerError.SlidingSyncAlert HeaderFooterPage( modifier = modifier, @@ -112,7 +110,26 @@ fun AccountProviderView( } ) { when (state.loginFlow) { - is Async.Failure -> Unit // Error dialog will be displayed + 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) { @@ -122,22 +139,6 @@ fun AccountProviderView( } Async.Uninitialized -> Unit } - if (slidingSyncNotSupportedError != null) { - SlidingSyncNotSupportedDialog(onLearnMoreClicked = { - onLearnMoreClicked() - eventSink(AccountProviderEvents.ClearError) - }, onDismiss = { - eventSink(AccountProviderEvents.ClearError) - }) - } - if (invalidHomeserverError != null) { - ErrorDialog( - content = invalidHomeserverError.message(), - onDismiss = { - eventSink.invoke(AccountProviderEvents.ClearError) - } - ) - } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/SlidingSyncNotSupportedDialog.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/SlidingSyncNotSupportedDialog.kt index 03cf3a754e..364b4d955a 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/SlidingSyncNotSupportedDialog.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/SlidingSyncNotSupportedDialog.kt @@ -17,14 +17,20 @@ package io.element.android.features.login.impl.accountprovider import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import io.element.android.features.login.impl.R import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog import io.element.android.libraries.ui.strings.R as StringR @Composable -internal fun SlidingSyncNotSupportedDialog(onLearnMoreClicked: () -> Unit, onDismiss: () -> Unit) { +internal fun SlidingSyncNotSupportedDialog( + onLearnMoreClicked: () -> Unit, + onDismiss: () -> Unit, + modifier: Modifier = Modifier, +) { ConfirmationDialog( + modifier = modifier, onDismiss = onDismiss, submitText = stringResource(StringR.string.action_learn_more), onSubmitClicked = onLearnMoreClicked, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt index aafaee818d..9787efc9cc 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt @@ -18,6 +18,7 @@ package io.element.android.features.login.impl.changeaccountprovider import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin @@ -25,7 +26,7 @@ import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode -import io.element.android.features.login.impl.accountprovider.item.AccountProvider +import io.element.android.features.login.impl.util.openLearnMorePage import io.element.android.libraries.di.AppScope @ContributesNode(AppScope::class) @@ -36,12 +37,12 @@ class ChangeAccountProviderNode @AssistedInject constructor( ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { - fun onAccountProviderClicked(data: AccountProvider) + fun onDone() fun onOtherClicked() } - private fun onAccountProviderClicked(data: AccountProvider) { - plugins().forEach { it.onAccountProviderClicked(data) } + private fun onDone() { + plugins().forEach { it.onDone() } } private fun onOtherClicked() { @@ -51,11 +52,13 @@ class ChangeAccountProviderNode @AssistedInject constructor( @Composable override fun View(modifier: Modifier) { val state = presenter.present() + val context = LocalContext.current ChangeAccountProviderView( state = state, modifier = modifier, onBackPressed = ::navigateUp, - onAccountProviderClicked = ::onAccountProviderClicked, + onLearnMoreClicked = { openLearnMorePage(context) }, + onDone = ::onDone, onOtherProviderClicked = ::onOtherClicked, ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt index f80610d935..be1da695f8 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt @@ -18,14 +18,17 @@ package io.element.android.features.login.impl.changeaccountprovider import androidx.compose.runtime.Composable import io.element.android.features.login.impl.accountprovider.item.AccountProvider +import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerPresenter import io.element.android.libraries.architecture.Presenter import javax.inject.Inject class ChangeAccountProviderPresenter @Inject constructor( + private val changeServerPresenter: ChangeServerPresenter, ) : Presenter { @Composable override fun present(): ChangeAccountProviderState { + val changeServerState = changeServerPresenter.present() return ChangeAccountProviderState( // Just matrix.org by default for now accountProviders = listOf( @@ -38,6 +41,7 @@ class ChangeAccountProviderPresenter @Inject constructor( supportSlidingSync = true, ) ), + changeServerState = changeServerState, ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt index 89a286518e..2fd472e5c5 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt @@ -17,8 +17,10 @@ package io.element.android.features.login.impl.changeaccountprovider import io.element.android.features.login.impl.accountprovider.item.AccountProvider +import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerState // Do not use default value, so no member get forgotten in the presenters. data class ChangeAccountProviderState constructor( val accountProviders: List, + val changeServerState: ChangeServerState, ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt index 627f11c742..01ffb65500 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt @@ -18,6 +18,7 @@ package io.element.android.features.login.impl.changeaccountprovider import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.login.impl.accountprovider.item.anAccountProvider +import io.element.android.features.login.impl.changeaccountprovider.common.aChangeServerState open class ChangeAccountProviderStateProvider : PreviewParameterProvider { override val values: Sequence @@ -31,4 +32,5 @@ fun aChangeAccountProviderState() = ChangeAccountProviderState( accountProviders = listOf( anAccountProvider() ), + changeServerState = aChangeServerState(), ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt index 4c7dab1961..d413f807e0 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt @@ -42,6 +42,8 @@ import androidx.compose.ui.unit.dp 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.item.AccountProviderView +import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerEvents +import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerView import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreviewDark @@ -57,7 +59,8 @@ fun ChangeAccountProviderView( state: ChangeAccountProviderState, modifier: Modifier = Modifier, onBackPressed: () -> Unit = {}, - onAccountProviderClicked: (AccountProvider) -> Unit = {}, + onLearnMoreClicked: () -> Unit = {}, + onDone: () -> Unit = {}, onOtherProviderClicked: () -> Unit = {}, ) { val scrollState = rememberScrollState() @@ -104,7 +107,7 @@ fun ChangeAccountProviderView( AccountProviderView( item = alteredItem, onClick = { - onAccountProviderClicked(alteredItem) + state.changeServerState.eventSink.invoke(ChangeServerEvents.ChangeServer(alteredItem)) } ) } @@ -117,6 +120,11 @@ fun ChangeAccountProviderView( ) Spacer(Modifier.height(32.dp)) } + ChangeServerView( + state = state.changeServerState, + onLearnMoreClicked = onLearnMoreClicked, + onDone = onDone, + ) } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerEvents.kt new file mode 100644 index 0000000000..8cded25388 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerEvents.kt @@ -0,0 +1,24 @@ +/* + * 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.common + +import io.element.android.features.login.impl.accountprovider.item.AccountProvider + +sealed interface ChangeServerEvents { + data class ChangeServer(val accountProvider: AccountProvider) : ChangeServerEvents + object ClearError : ChangeServerEvents +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerPresenter.kt new file mode 100644 index 0000000000..fcc4dc7885 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerPresenter.kt @@ -0,0 +1,76 @@ +/* + * 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.common + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import io.element.android.features.login.impl.accountprovider.item.AccountProvider +import io.element.android.features.login.impl.datasource.AccountProviderDataSource +import io.element.android.features.login.impl.error.ChangeServerError +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.architecture.execute +import io.element.android.libraries.core.data.tryOrNull +import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import java.net.URL +import javax.inject.Inject + +class ChangeServerPresenter @Inject constructor( + private val authenticationService: MatrixAuthenticationService, + private val accountProviderDataSource: AccountProviderDataSource, +) : Presenter { + + @Composable + override fun present(): ChangeServerState { + val localCoroutineScope = rememberCoroutineScope() + + val changeServerAction: MutableState> = remember { + mutableStateOf(Async.Uninitialized) + } + + fun handleEvents(event: ChangeServerEvents) { + when (event) { + is ChangeServerEvents.ChangeServer -> localCoroutineScope.changeServer(event.accountProvider, changeServerAction) + ChangeServerEvents.ClearError -> changeServerAction.value = Async.Uninitialized + } + } + + return ChangeServerState( + changeServerAction = changeServerAction.value, + eventSink = ::handleEvents + ) + } + + private fun CoroutineScope.changeServer( + data: AccountProvider, + changeServerAction: MutableState>, + ) = launch { + suspend { + val domain = tryOrNull { URL(data.title) }?.host ?: data.title + authenticationService.setHomeserver(domain).map { + authenticationService.getHomeserverDetails().value!! + // Valid, remember user choice + accountProviderDataSource.userSelection(data) + }.getOrThrow() + }.execute(changeServerAction, errorMapping = ChangeServerError::from) + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerState.kt new file mode 100644 index 0000000000..fbe4c37117 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerState.kt @@ -0,0 +1,24 @@ +/* + * 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.common + +import io.element.android.libraries.architecture.Async + +data class ChangeServerState( + val changeServerAction: Async, + val eventSink: (ChangeServerEvents) -> Unit +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerStateProvider.kt new file mode 100644 index 0000000000..b089ef9ff9 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerStateProvider.kt @@ -0,0 +1,32 @@ +/* + * 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.common + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.Async + +open class ChangeServerStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aChangeServerState(), + ) +} + +fun aChangeServerState() = ChangeServerState( + changeServerAction = Async.Uninitialized, + eventSink = {} +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerView.kt new file mode 100644 index 0000000000..dd46fb5b2e --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerView.kt @@ -0,0 +1,84 @@ +/* + * 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.common + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.features.login.impl.accountprovider.SlidingSyncNotSupportedDialog +import io.element.android.features.login.impl.error.ChangeServerError +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.designsystem.components.ProgressDialog +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 + +@Composable +fun ChangeServerView( + state: ChangeServerState, + modifier: Modifier = Modifier, + onLearnMoreClicked: () -> Unit = {}, + onDone: () -> Unit = {}, +) { + val eventSink = state.eventSink + when (state.changeServerAction) { + is Async.Failure -> { + when (val error = state.changeServerAction.error) { + is ChangeServerError.InlineErrorMessage -> { + ErrorDialog( + modifier = modifier, + content = error.message(), + onDismiss = { + eventSink.invoke(ChangeServerEvents.ClearError) + } + ) + } + is ChangeServerError.SlidingSyncAlert -> { + SlidingSyncNotSupportedDialog( + modifier = modifier, + onLearnMoreClicked = { + onLearnMoreClicked() + eventSink.invoke(ChangeServerEvents.ClearError) + }, onDismiss = { + eventSink.invoke(ChangeServerEvents.ClearError) + }) + } + } + } + is Async.Loading -> ProgressDialog() + is Async.Success -> onDone() + Async.Uninitialized -> Unit + } +} + +@Preview +@Composable +fun ChangeServerViewLightPreview(@PreviewParameter(ChangeServerStateProvider::class) state: ChangeServerState) = + ElementPreviewLight { ContentToPreview(state) } + +@Preview +@Composable +fun ChangeServerViewDarkPreview(@PreviewParameter(ChangeServerStateProvider::class) state: ChangeServerState) = + ElementPreviewDark { ContentToPreview(state) } + +@Composable +private fun ContentToPreview(state: ChangeServerState) { + ChangeServerView( + state = state, + ) +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormNode.kt index 8786497e60..dac0b01f01 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormNode.kt @@ -18,6 +18,7 @@ package io.element.android.features.login.impl.changeaccountprovider.form import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin @@ -25,7 +26,7 @@ import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode -import io.element.android.features.login.impl.accountprovider.item.AccountProvider +import io.element.android.features.login.impl.util.openLearnMorePage import io.element.android.libraries.di.AppScope @ContributesNode(AppScope::class) @@ -36,21 +37,23 @@ class ChangeAccountProviderFormNode @AssistedInject constructor( ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { - fun onAccountProviderClicked(data: AccountProvider) + fun onDone() } - private fun onAccountProviderClicked(data: AccountProvider) { - plugins().forEach { it.onAccountProviderClicked(data) } + private fun onDone() { + plugins().forEach { it.onDone() } } @Composable override fun View(modifier: Modifier) { val state = presenter.present() + val context = LocalContext.current ChangeAccountProviderFormView( state = state, modifier = modifier, onBackPressed = ::navigateUp, - onAccountProviderClicked = ::onAccountProviderClicked + onLearnMoreClicked = { openLearnMorePage(context) }, + onDone = ::onDone, ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt index ed8c98bb33..5ba3146895 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt @@ -22,6 +22,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable +import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerPresenter import io.element.android.libraries.architecture.Presenter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -29,6 +30,7 @@ import javax.inject.Inject class ChangeAccountProviderFormPresenter @Inject constructor( private val homeserverResolver: HomeserverResolver, + private val changeServerPresenter: ChangeServerPresenter, ) : Presenter { @Composable @@ -38,6 +40,7 @@ class ChangeAccountProviderFormPresenter @Inject constructor( val userInput = rememberSaveable { mutableStateOf("") } + val changeServerState = changeServerPresenter.present() val data by homeserverResolver.flow().collectAsState() fun handleEvents(event: ChangeAccountProviderFormEvents) { @@ -52,6 +55,7 @@ class ChangeAccountProviderFormPresenter @Inject constructor( return ChangeAccountProviderFormState( userInput = userInput.value, userInputResult = data, + changeServerState = changeServerState, eventSink = ::handleEvents ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormState.kt index e87180b954..f7b841bed3 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormState.kt @@ -16,11 +16,13 @@ package io.element.android.features.login.impl.changeaccountprovider.form +import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerState import io.element.android.libraries.architecture.Async // Do not use default value, so no member get forgotten in the presenters. data class ChangeAccountProviderFormState( val userInput: String, val userInputResult: Async>, + val changeServerState: ChangeServerState, val eventSink: (ChangeAccountProviderFormEvents) -> Unit ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormStateProvider.kt index c73cbc5acb..5c1b4a092b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormStateProvider.kt @@ -17,6 +17,7 @@ package io.element.android.features.login.impl.changeaccountprovider.form import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.login.impl.changeaccountprovider.common.aChangeServerState import io.element.android.libraries.architecture.Async open class ChangeAccountProviderFormStateProvider : PreviewParameterProvider { @@ -34,6 +35,7 @@ fun aChangeAccountProviderFormState( ) = ChangeAccountProviderFormState( userInput = userInput, userInputResult = userInputResult, + changeServerState = aChangeServerState(), eventSink = {} ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt index 16fdd10c5c..72868873eb 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt @@ -52,6 +52,8 @@ import androidx.compose.ui.unit.dp 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.item.AccountProviderView +import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerEvents +import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerView import io.element.android.libraries.architecture.Async import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.components.button.BackButton @@ -72,7 +74,8 @@ fun ChangeAccountProviderFormView( state: ChangeAccountProviderFormState, modifier: Modifier = Modifier, onBackPressed: () -> Unit = {}, - onAccountProviderClicked: (AccountProvider) -> Unit = {}, + onLearnMoreClicked: () -> Unit = {}, + onDone: () -> Unit = {}, ) { val eventSink = state.eventSink val scrollState = rememberScrollState() @@ -164,7 +167,7 @@ fun ChangeAccountProviderFormView( AccountProviderView( item = item, onClick = { - onAccountProviderClicked(item) + state.changeServerState.eventSink.invoke(ChangeServerEvents.ChangeServer(item)) } ) } @@ -173,6 +176,11 @@ fun ChangeAccountProviderFormView( } Spacer(Modifier.height(32.dp)) } + ChangeServerView( + state = state.changeServerState, + onLearnMoreClicked = onLearnMoreClicked, + onDone = onDone, + ) } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/Util.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/Util.kt new file mode 100644 index 0000000000..261b02c1b8 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/Util.kt @@ -0,0 +1,27 @@ +/* + * 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.util + +import android.content.Context +import android.content.Intent +import android.net.Uri +import io.element.android.libraries.core.data.tryOrNull + +fun openLearnMorePage(context: Context) { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(LoginConstants.SLIDING_SYNC_READ_MORE_URL)) + tryOrNull { context.startActivity(intent) } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncFailure.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncFailure.kt index 5863f4c80c..e81bc140bf 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncFailure.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncFailure.kt @@ -24,12 +24,14 @@ import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.ui.strings.R as StringR @Composable fun AsyncFailure( @@ -43,11 +45,11 @@ fun AsyncFailure( .padding(vertical = 32.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { - Text(text = throwable.message ?: "An error occurred") + Text(text = throwable.message ?: stringResource(id = StringR.string.error_unknown)) if (onRetry != null) { Spacer(modifier = Modifier.height(24.dp)) Button(onClick = onRetry) { - Text(text = "Retry") + Text(text = stringResource(id = StringR.string.action_retry)) } } } From aa61c95e8c47e0b0c127036cbbd35e0077c22a10 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 8 Jun 2023 16:30:08 +0200 Subject: [PATCH 25/61] Test ChangeServerPresenter --- .../ChangeAccountProviderPresenterTest.kt | 8 ++ .../common/ChangeServerPresenterTest.kt | 91 +++++++++++++++++++ .../ChangeAccountProviderFormPresenterTest.kt | 24 ++++- 3 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerPresenterTest.kt diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenterTest.kt index 911ecaab78..e6f4a84471 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenterTest.kt @@ -21,13 +21,21 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.login.impl.accountprovider.item.AccountProvider +import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerPresenter +import io.element.android.features.login.impl.datasource.AccountProviderDataSource +import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService import kotlinx.coroutines.test.runTest import org.junit.Test class ChangeAccountProviderPresenterTest { @Test fun `present - initial state`() = runTest { + val changeServerPresenter = ChangeServerPresenter( + FakeAuthenticationService(), + AccountProviderDataSource() + ) val presenter = ChangeAccountProviderPresenter( + changeServerPresenter ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerPresenterTest.kt new file mode 100644 index 0000000000..cc668f8217 --- /dev/null +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerPresenterTest.kt @@ -0,0 +1,91 @@ +/* + * 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.common + +import app.cash.molecule.RecompositionClock +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.features.login.impl.accountprovider.item.AccountProvider +import io.element.android.features.login.impl.datasource.AccountProviderDataSource +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_URL +import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class ChangeServerPresenterTest { + @Test + fun `present - initial state`() = runTest { + val presenter = ChangeServerPresenter( + FakeAuthenticationService(), + AccountProviderDataSource() + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.changeServerAction).isEqualTo(Async.Uninitialized) + } + } + + @Test + fun `present - change server ok`() = runTest { + val authenticationService = FakeAuthenticationService() + val presenter = ChangeServerPresenter( + authenticationService, + AccountProviderDataSource() + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.changeServerAction).isEqualTo(Async.Uninitialized) + authenticationService.givenHomeserver(A_HOMESERVER) + initialState.eventSink.invoke(ChangeServerEvents.ChangeServer(AccountProvider(A_HOMESERVER_URL))) + val loadingState = awaitItem() + assertThat(loadingState.changeServerAction).isInstanceOf(Async.Loading::class.java) + val successState = awaitItem() + assertThat(successState.changeServerAction).isEqualTo(Async.Success(Unit)) + } + } + + @Test + fun `present - change server error`() = runTest { + val authenticationService = FakeAuthenticationService() + val presenter = ChangeServerPresenter( + authenticationService, + AccountProviderDataSource() + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.changeServerAction).isEqualTo(Async.Uninitialized) + initialState.eventSink.invoke(ChangeServerEvents.ChangeServer(AccountProvider(A_HOMESERVER_URL))) + val loadingState = awaitItem() + assertThat(loadingState.changeServerAction).isInstanceOf(Async.Loading::class.java) + val failureState = awaitItem() + assertThat(failureState.changeServerAction).isInstanceOf(Async.Failure::class.java) + // Clear error + failureState.eventSink.invoke(ChangeServerEvents.ClearError) + val finalState = awaitItem() + assertThat(finalState.changeServerAction).isEqualTo(Async.Uninitialized) + } + } +} diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenterTest.kt index e2b2a8f546..1a425993a8 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenterTest.kt @@ -20,7 +20,10 @@ import app.cash.molecule.RecompositionClock import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerPresenter +import io.element.android.features.login.impl.datasource.AccountProviderDataSource import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService import kotlinx.coroutines.test.runTest import org.junit.Test @@ -28,8 +31,13 @@ class ChangeAccountProviderFormPresenterTest { @Test fun `present - initial state`() = runTest { val homeServerResolver = FakeHomeServerResolver() + val changeServerPresenter = ChangeServerPresenter( + FakeAuthenticationService(), + AccountProviderDataSource() + ) val presenter = ChangeAccountProviderFormPresenter( - homeServerResolver + homeServerResolver, + changeServerPresenter ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -43,8 +51,13 @@ class ChangeAccountProviderFormPresenterTest { @Test fun `present - enter text no result`() = runTest { val homeServerResolver = FakeHomeServerResolver() + val changeServerPresenter = ChangeServerPresenter( + FakeAuthenticationService(), + AccountProviderDataSource() + ) val presenter = ChangeAccountProviderFormPresenter( - homeServerResolver + homeServerResolver, + changeServerPresenter ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -68,8 +81,13 @@ class ChangeAccountProviderFormPresenterTest { listOf(aHomeserverData(), aHomeserverData()), ) ) + val changeServerPresenter = ChangeServerPresenter( + FakeAuthenticationService(), + AccountProviderDataSource() + ) val presenter = ChangeAccountProviderFormPresenter( - homeServerResolver + homeServerResolver, + changeServerPresenter ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() From 5a82834459b3b2e1480769796f4933860793779f Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 8 Jun 2023 14:43:37 +0000 Subject: [PATCH 26/61] Update screenshots --- ...ccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 3 +++ ...countProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 3 +++ ...p_ChangeServerViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 3 +++ ..._ChangeServerViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 3 +++ ...ntProviderFormViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 3 +++ ...ntProviderFormViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 3 +++ ...tProviderFormViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 3 +++ ...tProviderFormViewLightPreview_0_null_1,NEXUS_5,1.0,en].png | 3 +++ ...ccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 3 +++ ...countProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 3 +++ ...p_ChangeServerViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 3 --- ...p_ChangeServerViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 3 --- ...p_ChangeServerViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png | 3 --- ...p_ChangeServerViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png | 3 --- ...p_ChangeServerViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png | 3 --- ...p_ChangeServerViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png | 3 --- ..._ChangeServerViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 3 --- ..._ChangeServerViewLightPreview_0_null_1,NEXUS_5,1.0,en].png | 3 --- ..._ChangeServerViewLightPreview_0_null_2,NEXUS_5,1.0,en].png | 3 --- ..._ChangeServerViewLightPreview_0_null_3,NEXUS_5,1.0,en].png | 3 --- ..._ChangeServerViewLightPreview_0_null_4,NEXUS_5,1.0,en].png | 3 --- ..._ChangeServerViewLightPreview_0_null_5,NEXUS_5,1.0,en].png | 3 --- ...up_LoginRootScreenDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 3 +++ ...up_LoginRootScreenDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 3 +++ ...up_LoginRootScreenDarkPreview_0_null_2,NEXUS_5,1.0,en].png | 3 +++ ...p_LoginRootScreenLightPreview_0_null_0,NEXUS_5,1.0,en].png | 3 +++ ...p_LoginRootScreenLightPreview_0_null_1,NEXUS_5,1.0,en].png | 3 +++ ...p_LoginRootScreenLightPreview_0_null_2,NEXUS_5,1.0,en].png | 3 +++ ...up_LoginRootScreenDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 3 --- ...up_LoginRootScreenDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 3 --- ...up_LoginRootScreenDarkPreview_0_null_2,NEXUS_5,1.0,en].png | 3 --- ...up_LoginRootScreenDarkPreview_0_null_3,NEXUS_5,1.0,en].png | 3 --- ...up_LoginRootScreenDarkPreview_0_null_4,NEXUS_5,1.0,en].png | 3 --- ...up_LoginRootScreenDarkPreview_0_null_5,NEXUS_5,1.0,en].png | 3 --- ...up_LoginRootScreenDarkPreview_0_null_6,NEXUS_5,1.0,en].png | 3 --- ...up_LoginRootScreenDarkPreview_0_null_7,NEXUS_5,1.0,en].png | 3 --- ...up_LoginRootScreenDarkPreview_0_null_8,NEXUS_5,1.0,en].png | 3 --- ...up_LoginRootScreenDarkPreview_0_null_9,NEXUS_5,1.0,en].png | 3 --- ...p_LoginRootScreenLightPreview_0_null_0,NEXUS_5,1.0,en].png | 3 --- ...p_LoginRootScreenLightPreview_0_null_1,NEXUS_5,1.0,en].png | 3 --- ...p_LoginRootScreenLightPreview_0_null_2,NEXUS_5,1.0,en].png | 3 --- ...p_LoginRootScreenLightPreview_0_null_3,NEXUS_5,1.0,en].png | 3 --- ...p_LoginRootScreenLightPreview_0_null_4,NEXUS_5,1.0,en].png | 3 --- ...p_LoginRootScreenLightPreview_0_null_5,NEXUS_5,1.0,en].png | 3 --- ...p_LoginRootScreenLightPreview_0_null_6,NEXUS_5,1.0,en].png | 3 --- ...p_LoginRootScreenLightPreview_0_null_7,NEXUS_5,1.0,en].png | 3 --- ...p_LoginRootScreenLightPreview_0_null_8,NEXUS_5,1.0,en].png | 3 --- ...p_LoginRootScreenLightPreview_0_null_9,NEXUS_5,1.0,en].png | 3 --- ...ifySelfSessionViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...ifySelfSessionViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...ifySelfSessionViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...ifySelfSessionViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...ifySelfSessionViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...ifySelfSessionViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png | 4 ++-- ...fySelfSessionViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...fySelfSessionViewLightPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...fySelfSessionViewLightPreview_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...fySelfSessionViewLightPreview_0_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...fySelfSessionViewLightPreview_0_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...fySelfSessionViewLightPreview_0_null_5,NEXUS_5,1.0,en].png | 4 ++-- ...roup_RoundedIconAtomDarkPreview_0_null,NEXUS_5,1.0,en].png | 3 +++ ...oup_RoundedIconAtomLightPreview_0_null,NEXUS_5,1.0,en].png | 3 +++ ...itleSubtitleMoleculeDarkPreview_0_null,NEXUS_5,1.0,en].png | 4 ++-- ...tleSubtitleMoleculeLightPreview_0_null,NEXUS_5,1.0,en].png | 4 ++-- 64 files changed, 82 insertions(+), 124 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.common_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.common_null_DefaultGroup_ChangeServerViewLightPreview_0_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider_null_DefaultGroup_ChangeAccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider_null_DefaultGroup_ChangeAccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_0,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_1,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_2,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_3,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_4,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_5,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_2,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_0,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_1,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_2,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_3,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_4,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_5,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_6,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_7,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_8,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_9,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_0,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_1,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_2,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_3,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_4,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_5,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_6,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_7,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_8,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_9,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.atomic.atoms_null_DefaultGroup_RoundedIconAtomDarkPreview_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.atomic.atoms_null_DefaultGroup_RoundedIconAtomLightPreview_0_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..e96d33b1db --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5b66e68165054b6c4e09c00b7ebcff13229373bdc0eb10520f111b9d15576cb +size 39804 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..3f672cd84a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0292719b35c9e9b75a783628300fab10f75bb857c040e6829ee9ec51e5abc8dd +size 39605 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.common_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.common_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..665c8811ac --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.common_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb0d3bfcfd75cbd75fd9270ff1dc27090e5dbac79ca8db8a46d91a4c12bc966b +size 4457 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.common_null_DefaultGroup_ChangeServerViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.common_null_DefaultGroup_ChangeServerViewLightPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..665c8811ac --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.common_null_DefaultGroup_ChangeServerViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb0d3bfcfd75cbd75fd9270ff1dc27090e5dbac79ca8db8a46d91a4c12bc966b +size 4457 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..20c687f808 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32ae0372d47d6e68e853fbcdeb00e7829d78944b2cb3c284a845a0d463247c2b +size 25670 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..1c8e3c110e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:905cbb22344e797506dcabcbad15cdb2ab5c2e0ec2deaf544fdc89fdeca2615d +size 47638 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..e1a1286e15 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aaf00b3d9944a98db7d15890f93bf82834fd7376d323347f03bba3830aa2caa6 +size 25431 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c08530f117 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99ed6c29df9a96d7d6e0dcd34770d9837def14f73fb6509a8ccaf85ef964a934 +size 47046 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider_null_DefaultGroup_ChangeAccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider_null_DefaultGroup_ChangeAccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..d7f8ebf657 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider_null_DefaultGroup_ChangeAccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0cf16abe33c78fbe4db6c447fb54a4c6f4495e02ab13774ddd25bc74c24e785b +size 43758 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider_null_DefaultGroup_ChangeAccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider_null_DefaultGroup_ChangeAccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..cd94f38e06 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider_null_DefaultGroup_ChangeAccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3712ed6d166f726d4c6f56cdf9208787e7b79aec49900e3e59702e23da07c076 +size 43460 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png deleted file mode 100644 index 55945f00cb..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ac39dda453fbdf0f5cc3c6df2d9436a171c141c8be2ca2e2d2a2c7ee5d16b36f -size 39650 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png deleted file mode 100644 index 9f65a20aaa..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:49ef94a7bd404fe5982f7319aa1c4f6b5d29d11675f53e192af85b4efb8c3517 -size 43267 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png deleted file mode 100644 index 75c156af00..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:47942f38dd2afb38d0e1174bd155bb708f645b231b250525920f5abaa7074308 -size 43217 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png deleted file mode 100644 index bfba913061..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b006f7768bbc48cf68e5d897920a14894496d0b2b84a1839a7158b45edea9a75 -size 48757 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png deleted file mode 100644 index a63cf4d7e8..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:12702b2d71193e376f15df6f1a029531868b9651b70c40d2d738646f15980704 -size 42362 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png deleted file mode 100644 index 7b72ffcde5..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a942ed49efc175776994cf58d511af3be8f878b30ed5b34ab3ae0d600f2e0db1 -size 42316 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_0,NEXUS_5,1.0,en].png deleted file mode 100644 index 8c716007cc..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d692cd9030e6193691e676de62f68a9241cebf220a1f6c7986e095370e25c547 -size 37839 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_1,NEXUS_5,1.0,en].png deleted file mode 100644 index c979df7432..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:716e20f80dd634feb58ee26247defdd8e390d268e9ec5d9767ad7b574d7ca2bc -size 41581 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_2,NEXUS_5,1.0,en].png deleted file mode 100644 index bc159e7bcb..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_2,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9658c925b80565109772111972a495d437bda9ae97a9d9327d92a44ff0a1b9d1 -size 41521 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_3,NEXUS_5,1.0,en].png deleted file mode 100644 index d0764f07b3..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_3,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f0511bf277f5194a6d38cf13416c2b2a1c834bf216c511243351d2f9bfe1b7e3 -size 47857 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_4,NEXUS_5,1.0,en].png deleted file mode 100644 index be3729d669..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_4,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e248fa1785749f3a590576ed6f3c6899ad0042f71a80a4afdc7336981c7419a3 -size 40589 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_5,NEXUS_5,1.0,en].png deleted file mode 100644 index 7a621153ce..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_5,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:903df8ff5d3816a85e16a3ec46b2a98e742ba0fc0a317df829b0fca57f33d9aa -size 40553 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..44a334df69 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:871df52acf33bad33443a9e8140b58d17e5a068e008c5414be06d45f3b3e89ce +size 31619 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..a5aa6e658b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:871baa23b7ce95b95d55956f47cd1659408901c2a4221cb32231d4187a6861f8 +size 31653 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..44a334df69 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:871df52acf33bad33443a9e8140b58d17e5a068e008c5414be06d45f3b3e89ce +size 31619 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..71d1ec0eda --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82ee4c067a7396dc3f1247169f72792f027029a14f04ab512d4db1c0f1920996 +size 31509 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c4fa17263b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b05b56e0b8b54e2d13df513d180f7fed3d20df558793dab4358edeed4bac751 +size 31493 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..71d1ec0eda --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82ee4c067a7396dc3f1247169f72792f027029a14f04ab512d4db1c0f1920996 +size 31509 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_0,NEXUS_5,1.0,en].png deleted file mode 100644 index 22b1ce6d04..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:558f8f3b34e3a94a62460cca37396f94b0caa8197b7fbd49ce894382392f1899 -size 31512 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_1,NEXUS_5,1.0,en].png deleted file mode 100644 index 22b1ce6d04..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:558f8f3b34e3a94a62460cca37396f94b0caa8197b7fbd49ce894382392f1899 -size 31512 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_2,NEXUS_5,1.0,en].png deleted file mode 100644 index f48af1a990..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_2,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c4469faa61e68d951ed13b9cf324d11a99a1b041fb096bade2cd5d11c8185acc -size 33232 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_3,NEXUS_5,1.0,en].png deleted file mode 100644 index d65333610e..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_3,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:81125c72229ddb56f3c3e8b826de42f947f8739c3f372cad99917ee7d4db3b5c -size 33318 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_4,NEXUS_5,1.0,en].png deleted file mode 100644 index 4efbb4fca6..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_4,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cff519b372c22b623f72407e7c4113cfa24dae31fc517aea5de75bea9b4d61bf -size 29616 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_5,NEXUS_5,1.0,en].png deleted file mode 100644 index f48af1a990..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_5,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c4469faa61e68d951ed13b9cf324d11a99a1b041fb096bade2cd5d11c8185acc -size 33232 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_6,NEXUS_5,1.0,en].png deleted file mode 100644 index 1a305e0c79..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_6,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:78b278d94f46995b7d11719677b72c0804219b1c36072fd8e2ad6dfdb9de8aba -size 25313 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_7,NEXUS_5,1.0,en].png deleted file mode 100644 index 2adab63ade..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_7,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:25b6b15bd1646316a6b1dde8d68396e6b8f1e677b0528f07cab8f8d17a828902 -size 24564 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_8,NEXUS_5,1.0,en].png deleted file mode 100644 index b31cc0a315..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_8,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a59f9b56497f976a0664855e3da6245c423e8608c24b3f08b0c0cef0809ba0ee -size 20273 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_9,NEXUS_5,1.0,en].png deleted file mode 100644 index 873614913b..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_9,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1f8841cada0e8826a43cbca6ed8af3aa703633eccdcb664be3377ab6b38ed960 -size 26073 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_0,NEXUS_5,1.0,en].png deleted file mode 100644 index a9d3b597b1..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ef34f0de8599a2c950690d1ef1d4c8f68419b137594177ed09cfe3715deb3767 -size 30490 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_1,NEXUS_5,1.0,en].png deleted file mode 100644 index a9d3b597b1..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ef34f0de8599a2c950690d1ef1d4c8f68419b137594177ed09cfe3715deb3767 -size 30490 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_2,NEXUS_5,1.0,en].png deleted file mode 100644 index 718f68a9f8..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_2,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:99de01c644442e4d309b1d6bc75d0920eb205df31ef0c0f2411cbb756e0a838e -size 32106 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_3,NEXUS_5,1.0,en].png deleted file mode 100644 index a11d8fe9d2..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_3,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:acdde4f931b9e8aa18280f8f6f6e003ba406801ce1603e87ab087afe44465167 -size 32124 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_4,NEXUS_5,1.0,en].png deleted file mode 100644 index 5b2872a1e2..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_4,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6f3cbb18c3c2b766b7b10b204edc300cce73193c458a2c9b7dd88bf6fa7bcd6b -size 29007 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_5,NEXUS_5,1.0,en].png deleted file mode 100644 index 718f68a9f8..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_5,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:99de01c644442e4d309b1d6bc75d0920eb205df31ef0c0f2411cbb756e0a838e -size 32106 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_6,NEXUS_5,1.0,en].png deleted file mode 100644 index b7201b0739..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_6,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3a1473e95cf2436f5e42fb1ca2d7bbd40915be830f012b96ad47801ee1fd70a2 -size 24580 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_7,NEXUS_5,1.0,en].png deleted file mode 100644 index b20d358a12..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_7,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e338c8c2d3f89f3b7fdeafc5afffb36399855155e2b85231349e13e381e4cf1f -size 23617 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_8,NEXUS_5,1.0,en].png deleted file mode 100644 index 4313439757..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_8,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dea12dfe1c1799b1d2d765214b1bd911154a19a7794bf37c6663d179cec76c81 -size 19556 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_9,NEXUS_5,1.0,en].png deleted file mode 100644 index ed81ad1335..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_9,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1dc7d39a9b4e0f3eee8157b0290146b6bae5e6d0ea280296ddfc7051f31b95df -size 24786 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index f67e775a44..9e0556cea5 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eed20ed1221b53520bcc92c76a7b45f19134d5896220c109f89914d0d2de8c68 -size 29536 +oid sha256:64e2e959463e616c85dbf84f7e89bf4921ad8bda8b56c3ad5923c97d41ebb17a +size 29109 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png index 2980892672..8e27acb63e 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bff3aaf96ea36cd876e0bb262155f7da0a5e110ab5308872bf43d80fa50da414 -size 26909 +oid sha256:b29de0962ec154558d45759678ae01b4cd79b5b0ca77e44023fa8d4c461375a7 +size 26436 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png index de5e56864b..484bf3737d 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a1d4d57ed7cd0ab3c274d0cb46d70f6a2821fcf7fb2b4350258e675ecad8c964 -size 61153 +oid sha256:c1f43f5652bc1312b0a10b074ef7cf1b630774c9633551da36cf563218cd3812 +size 60953 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png index 35dbc4a40e..a26fee1b27 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e5db9cb1ebafb13c52b0e5f68c7dee3d6d27ce8b9bad0a1f5dd5ce76f4bda4cb -size 61738 +oid sha256:cc770343fe1da082c3f65d6f51f4b69e62751807a5b9217a0c785d720789dad2 +size 61539 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png index abbfcf77d5..d034c99ff3 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:102bc04b2e12eb68919c45346f4aab946470e8c9e9848ed59a03ad07b178e1bb -size 32639 +oid sha256:e2c28114a14e7251b70254516bb454d3cf05a183f3b5dcec9a8a8c2913d4d80e +size 32441 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png index c5f846756d..04a4cd408d 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:052f915a61028b2cd9605c07fd13000b09f003744397740cfee7054dbcabdcbd -size 27218 +oid sha256:bbf6e55866620ad7422340380a4a02620840217f6b18cc184cfa4b3c2d15a63d +size 27065 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index b6a5f9ef16..ccf74db6e2 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a9d85fcaba465c6747e933974cf9712953222fe934eb2ef3acd513050c9ab19e -size 29087 +oid sha256:31b41c8f96870545a06ef473206b6c1d3b24f0fa8626c18adbdc31bc592e363f +size 28957 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_1,NEXUS_5,1.0,en].png index 7053e141af..5391ae55ba 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff5f22bda0c7deaaba6e8006abdd7e0c9ba1562016ae62ad41b6a70e67737fdc -size 26179 +oid sha256:296a54854a782fd3c358960b42cdfba0c6822656742daa7034d2c36b53d040fa +size 25959 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_2,NEXUS_5,1.0,en].png index d1e830a2ce..486a20f3dc 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6faa634a10bf61fa3b84d396c8572586334a5554187fb9935c472d1a56e02884 -size 59497 +oid sha256:4a399eaa0f0e8a33bf7eb2e524dd162265918ddb90fd3200d9bb48a9db75a2c3 +size 58921 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_3,NEXUS_5,1.0,en].png index 1da6df2b42..89246cbcaa 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:649cd1ab79165a14c67000ce378fdba8abf3e30b10db306641f6f8d158194f0c -size 59923 +oid sha256:17bec083f13ac0d24787cd171f232b673d9c3f9cbffba9161bf70acf48b30c85 +size 59343 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_4,NEXUS_5,1.0,en].png index 526c311ff2..9ac1730d01 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a6a7c9e187e4825524e292243dcb2e3c57bab49c750dac25016e279d639c56c -size 32266 +oid sha256:2e5afbd696458ba2ec094da4bbee3b74faa32f0abbc215a1082eb45b628dfcaf +size 31835 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_5,NEXUS_5,1.0,en].png index 78effe0524..8c57f95a28 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f9d94a1d66c85094c425f4054cc3115cab28e3109272657a96be939bbc169cf3 -size 26759 +oid sha256:a857ce94e305acd45c97af4ae2532bfa9310a1e1bf0e549c780f654a89065e5e +size 26160 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.atomic.atoms_null_DefaultGroup_RoundedIconAtomDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.atomic.atoms_null_DefaultGroup_RoundedIconAtomDarkPreview_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..bb4c8a386d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.atomic.atoms_null_DefaultGroup_RoundedIconAtomDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fddc4b662ae2284c9179ccea787dbe4fe7cf4923bad81cdbaf891e8149b7131e +size 8083 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.atomic.atoms_null_DefaultGroup_RoundedIconAtomLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.atomic.atoms_null_DefaultGroup_RoundedIconAtomLightPreview_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..e4993027cc --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.atomic.atoms_null_DefaultGroup_RoundedIconAtomLightPreview_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e46889dfdfcd8a9999992afcc75d73ea5dd8107a4f5fc19335f9022335c6fc7d +size 6834 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.atomic.molecules_null_DefaultGroup_IconTitleSubtitleMoleculeDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.atomic.molecules_null_DefaultGroup_IconTitleSubtitleMoleculeDarkPreview_0_null,NEXUS_5,1.0,en].png index 9f2b08ab8d..97f45a7e34 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.atomic.molecules_null_DefaultGroup_IconTitleSubtitleMoleculeDarkPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.atomic.molecules_null_DefaultGroup_IconTitleSubtitleMoleculeDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73d4db2c6cdd12c05672affc584c75161aa481fd0065c1b084f32ed4c3481c66 -size 10605 +oid sha256:d71452355496c1fe8214f061f72baf3c7690953138777952cdb65befdc133e57 +size 10407 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.atomic.molecules_null_DefaultGroup_IconTitleSubtitleMoleculeLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.atomic.molecules_null_DefaultGroup_IconTitleSubtitleMoleculeLightPreview_0_null,NEXUS_5,1.0,en].png index 010dd2b941..a874edaa42 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.atomic.molecules_null_DefaultGroup_IconTitleSubtitleMoleculeLightPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.atomic.molecules_null_DefaultGroup_IconTitleSubtitleMoleculeLightPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7aadaccb2874346a08bbdcf2cd8d51fd1490319e10c22065611c70543ce8d28d -size 9695 +oid sha256:be000cc43901f8b61a12c93201de8026fd57654e6a66cf2d5507f0b29af76e6a +size 9227 From 52545bc6205bd6416063931e8d3729265180dd79 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 8 Jun 2023 16:56:20 +0200 Subject: [PATCH 27/61] Fix icon tint --- .../form/ChangeAccountProviderFormView.kt | 1 - .../features/login/impl/loginpassword/LoginPasswordView.kt | 4 ++-- .../atomic/molecules/IconTitleSubtitleMolecule.kt | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt index 72868873eb..cb35774147 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt @@ -105,7 +105,6 @@ fun ChangeAccountProviderFormView( IconTitleSubtitleMolecule( modifier = Modifier.padding(top = 16.dp, bottom = 40.dp, start = 16.dp, end = 16.dp), iconImageVector = Icons.Filled.Home, - iconTint = MaterialTheme.colorScheme.primary, title = stringResource(id = R.string.screen_account_provider_form_title), subTitle = stringResource(id = R.string.screen_account_provider_form_subtitle), ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt index d52843ea5e..04db2ea73b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt @@ -278,12 +278,12 @@ internal fun LoginErrorDialog(error: Throwable, onDismiss: () -> Unit) { @Preview @Composable -internal fun LoginRootScreenLightPreview(@PreviewParameter(LoginPasswordStateProvider::class) state: LoginPasswordState) = +internal fun LoginPasswordViewLightPreview(@PreviewParameter(LoginPasswordStateProvider::class) state: LoginPasswordState) = ElementPreviewLight { ContentToPreview(state) } @Preview @Composable -internal fun LoginRootScreenDarkPreview(@PreviewParameter(LoginPasswordStateProvider::class) state: LoginPasswordState) = +internal fun LoginPasswordViewDarkPreview(@PreviewParameter(LoginPasswordStateProvider::class) state: LoginPasswordState) = ElementPreviewDark { ContentToPreview(state) } @Composable diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt index 06c111d069..9d348b706f 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt @@ -47,7 +47,7 @@ fun IconTitleSubtitleMolecule( modifier: Modifier = Modifier, iconResourceId: Int? = null, iconImageVector: ImageVector? = null, - iconTint: Color = Color.Unspecified, + iconTint: Color = MaterialTheme.colorScheme.primary, ) { Column(modifier) { RoundedIconAtom( From 763159651a648ab10372ff5317bd428b8cb70f3d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 8 Jun 2023 18:20:26 +0200 Subject: [PATCH 28/61] Rename file --- ...llKnownSlidyincSyncConfig.kt => WellKnownSlidingSyncConfig.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/{WellKnownSlidyincSyncConfig.kt => WellKnownSlidingSyncConfig.kt} (100%) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownSlidyincSyncConfig.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownSlidingSyncConfig.kt similarity index 100% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownSlidyincSyncConfig.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownSlidingSyncConfig.kt From e1e984cfb0523549e5f2ce795bee9bd6f1ad5374 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 8 Jun 2023 22:04:12 +0200 Subject: [PATCH 29/61] Rework HomeserverResolver --- .../ChangeAccountProviderFormPresenter.kt | 33 +++---- .../form/DefaultHomeserverResolver.kt | 94 ++++++++----------- .../form/HomeserverResolver.kt | 5 +- .../form/FakeHomeServerResolver.kt | 18 ++-- 4 files changed, 67 insertions(+), 83 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt index 5ba3146895..6c4b07b3ed 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt @@ -17,15 +17,15 @@ package io.element.android.features.login.impl.changeaccountprovider.form import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerPresenter +import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch import javax.inject.Inject class ChangeAccountProviderFormPresenter @Inject constructor( @@ -35,33 +35,34 @@ class ChangeAccountProviderFormPresenter @Inject constructor( @Composable override fun present(): ChangeAccountProviderFormState { - val localCoroutineScope = rememberCoroutineScope() - - val userInput = rememberSaveable { + var userInput by rememberSaveable { mutableStateOf("") } val changeServerState = changeServerPresenter.present() - val data by homeserverResolver.flow().collectAsState() + + var data: Async> by remember { + mutableStateOf(Async.Uninitialized) + } + + LaunchedEffect(userInput) { + homeserverResolver.resolve(userInput).collect { + data = it + } + } fun handleEvents(event: ChangeAccountProviderFormEvents) { when (event) { is ChangeAccountProviderFormEvents.UserInput -> { - userInput.value = event.input - localCoroutineScope.userInput(event.input) + userInput = event.input } } } return ChangeAccountProviderFormState( - userInput = userInput.value, + userInput = userInput, userInputResult = data, changeServerState = changeServerState, eventSink = ::handleEvents ) } - - // Could be reworked using LaunchedEffect - private fun CoroutineScope.userInput(userInput: String) = launch { - homeserverResolver.accept(userInput) - } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt index 6f7274f35f..706b954b86 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt @@ -25,15 +25,14 @@ import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.uri.ensureProtocol import io.element.android.libraries.core.uri.isValidUrl import io.element.android.libraries.di.AppScope -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.joinAll -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.withContext +import java.util.Collections import javax.inject.Inject /** @@ -44,35 +43,25 @@ class DefaultHomeserverResolver @Inject constructor( private val dispatchers: CoroutineDispatchers, private val wellknownRequest: WellknownRequest, ) : HomeserverResolver { - private val mutableFlow: MutableStateFlow>> = MutableStateFlow(Async.Uninitialized) - override fun flow(): StateFlow>> = mutableFlow - - private var currentJob: Job? = null - - override suspend fun accept(userInput: String) { - currentJob?.cancel() - val cleanedUpUserInput = userInput.trim().ensureProtocol().removeSuffix("/") - mutableFlow.tryEmit(Async.Uninitialized) - if (cleanedUpUserInput.length > 3) { - delay(300) - mutableFlow.tryEmit(Async.Loading()) - withContext(dispatchers.io) { - val list = getUrlCandidate(cleanedUpUserInput) - currentJob = resolveList(cleanedUpUserInput, list) - } - } - } - - private fun CoroutineScope.resolveList(userInput: String, list: List): Job { - val currentList = mutableListOf() - return launch { + override suspend fun resolve(userInput: String): Flow>> = flow { + val flowContext = currentCoroutineContext() + emit(Async.Uninitialized) + // Debounce + delay(300) + val clean = userInput.trim() + if (clean.length < 4) return@flow + emit(Async.Loading()) + val list = getUrlCandidate(clean.ensureProtocol().removeSuffix("/")) + val currentList = Collections.synchronizedList(mutableListOf()) + // Run all the requests in parallel + withContext(dispatchers.io) { list.map { async { val wellKnown = tryOrNull { wellknownRequest.execute(it) } val isValid = wellKnown?.isValid().orFalse() - val supportSlidingSync = wellKnown?.supportSlidingSync().orFalse() if (isValid) { + val supportSlidingSync = wellKnown?.supportSlidingSync().orFalse() // Emit the list as soon as possible currentList.add( HomeserverData( @@ -81,38 +70,35 @@ class DefaultHomeserverResolver @Inject constructor( supportSlidingSync = supportSlidingSync ) ) - mutableFlow.tryEmit(Async.Success(currentList)) - } - } - }.joinAll() - .also { - // If list is empty, and the user as entered an URL, do not block the user. - if (currentList.isEmpty()) { - if (userInput.isValidUrl()) { - mutableFlow.tryEmit( - Async.Success( - listOf( - HomeserverData( - homeserverUrl = userInput, - isWellknownValid = false, - supportSlidingSync = false, - ) - ) - ) - ) - } else { - mutableFlow.tryEmit(Async.Uninitialized) + withContext(flowContext) { + emit(Async.Success(currentList)) } } } + }.awaitAll() + } + // If list is empty, and the user as entered an URL, do not block the user. + if (currentList.isEmpty()) { + if (userInput.isValidUrl()) { + emit( + Async.Success( + listOf( + HomeserverData( + homeserverUrl = userInput, + isWellknownValid = false, + supportSlidingSync = false, + ) + ) + ) + ) + } else { + emit(Async.Uninitialized) + } } } private fun getUrlCandidate(data: String): List { return buildList { - // Always try what the user has entered - add(data) - if (data.contains(".")) { // TLD detected? } else { @@ -120,6 +106,8 @@ class DefaultHomeserverResolver @Inject constructor( add("${data}.com") add("${data}.io") } + // Always try what the user has entered + add(data) } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverResolver.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverResolver.kt index a7abb444f6..0c8bcfc712 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverResolver.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverResolver.kt @@ -17,12 +17,11 @@ package io.element.android.features.login.impl.changeaccountprovider.form import io.element.android.libraries.architecture.Async -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.Flow /** * Resolve homeserver base on search terms. */ interface HomeserverResolver { - fun flow(): StateFlow>> - suspend fun accept(userInput: String) + suspend fun resolve(userInput: String): Flow>> } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/FakeHomeServerResolver.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/FakeHomeServerResolver.kt index db86e09903..8a712aa4a0 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/FakeHomeServerResolver.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/FakeHomeServerResolver.kt @@ -19,8 +19,8 @@ package io.element.android.features.login.impl.changeaccountprovider.form import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.test.FAKE_DELAY_IN_MS import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow class FakeHomeServerResolver : HomeserverResolver { private var pendingResult: List> = emptyList() @@ -28,21 +28,17 @@ class FakeHomeServerResolver : HomeserverResolver { pendingResult = result } - private val mutableFlow: MutableStateFlow>> = MutableStateFlow(Async.Uninitialized) - - override fun flow(): StateFlow>> = mutableFlow - - override suspend fun accept(userInput: String) { - mutableFlow.tryEmit(Async.Uninitialized) + override suspend fun resolve(userInput: String): Flow>> = flow { + emit(Async.Uninitialized) delay(FAKE_DELAY_IN_MS) - mutableFlow.tryEmit(Async.Loading()) + emit(Async.Loading()) // Sending the pending result if (pendingResult.isEmpty()) { - mutableFlow.tryEmit(Async.Uninitialized) + emit(Async.Uninitialized) } else { pendingResult.forEach { delay(FAKE_DELAY_IN_MS) - mutableFlow.tryEmit(Async.Success(it)) + emit(Async.Success(it)) } } } From dbe77967049c551ab0cbc23a8eb2ba1c3f1c34a5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 8 Jun 2023 22:56:36 +0200 Subject: [PATCH 30/61] Add tests. --- features/login/impl/build.gradle.kts | 1 + .../form/DefaultHomeserverResolver.kt | 15 +-- .../form/network/DefaultWellknownRequest.kt | 36 ++++++ .../form/network/WellknownRequest.kt | 15 +-- .../ChangeAccountProviderFormPresenterTest.kt | 114 ++++++++++++++++-- .../form/FakeHomeServerResolver.kt | 45 ------- .../form/FakeWellknownRequest.kt | 31 +++++ 7 files changed, 180 insertions(+), 77 deletions(-) create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/DefaultWellknownRequest.kt delete mode 100644 features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/FakeHomeServerResolver.kt create mode 100644 features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/FakeWellknownRequest.kt diff --git a/features/login/impl/build.gradle.kts b/features/login/impl/build.gradle.kts index 5f4563c3a9..279ac27767 100644 --- a/features/login/impl/build.gradle.kts +++ b/features/login/impl/build.gradle.kts @@ -59,6 +59,7 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.tests.testutils) androidTestImplementation(libs.test.junitext) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt index 706b954b86..1079e6d64b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt @@ -49,10 +49,11 @@ class DefaultHomeserverResolver @Inject constructor( emit(Async.Uninitialized) // Debounce delay(300) - val clean = userInput.trim() - if (clean.length < 4) return@flow + val trimmedUserInput = userInput.trim() + if (trimmedUserInput.length < 4) return@flow emit(Async.Loading()) - val list = getUrlCandidate(clean.ensureProtocol().removeSuffix("/")) + val candidateBase = trimmedUserInput.ensureProtocol().removeSuffix("/") + val list = getUrlCandidates(candidateBase) val currentList = Collections.synchronizedList(mutableListOf()) // Run all the requests in parallel withContext(dispatchers.io) { @@ -77,14 +78,14 @@ class DefaultHomeserverResolver @Inject constructor( } }.awaitAll() } - // If list is empty, and the user as entered an URL, do not block the user. + // If list is empty, and the user has entered an URL, do not block the user. if (currentList.isEmpty()) { - if (userInput.isValidUrl()) { + if (trimmedUserInput.isValidUrl()) { emit( Async.Success( listOf( HomeserverData( - homeserverUrl = userInput, + homeserverUrl = trimmedUserInput, isWellknownValid = false, supportSlidingSync = false, ) @@ -97,7 +98,7 @@ class DefaultHomeserverResolver @Inject constructor( } } - private fun getUrlCandidate(data: String): List { + private fun getUrlCandidates(data: String): List { return buildList { if (data.contains(".")) { // TLD detected? diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/DefaultWellknownRequest.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/DefaultWellknownRequest.kt new file mode 100644 index 0000000000..36aa47bbd0 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/DefaultWellknownRequest.kt @@ -0,0 +1,36 @@ +/* + * 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.network + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.network.RetrofitFactory +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultWellknownRequest @Inject constructor( + private val retrofitFactory: RetrofitFactory, +) : WellknownRequest { + /** + * Return the WellKnown data, if found. + * @param baseUrl for instance https://matrix.org + */ + override suspend fun execute(baseUrl: String): WellKnown { + val wellknownApi = retrofitFactory.create(baseUrl) + .create(WellknownAPI::class.java) + return wellknownApi.getWellKnown() + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownRequest.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownRequest.kt index 1f18edc695..85d471edbd 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownRequest.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownRequest.kt @@ -15,19 +15,10 @@ */ package io.element.android.features.login.impl.changeaccountprovider.form.network -import io.element.android.libraries.network.RetrofitFactory -import javax.inject.Inject - -class WellknownRequest @Inject constructor( - private val retrofitFactory: RetrofitFactory, -) { +interface WellknownRequest { /** - * Return the WellKnown data, if found. + * Return the WellKnown data, or throw an error if not found. * @param baseUrl for instance https://matrix.org */ - suspend fun execute(baseUrl: String): WellKnown { - val wellknownApi = retrofitFactory.create(baseUrl) - .create(WellknownAPI::class.java) - return wellknownApi.getWellKnown() - } + suspend fun execute(baseUrl: String): WellKnown } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenterTest.kt index 1a425993a8..dff15a63c6 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenterTest.kt @@ -21,22 +21,27 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerPresenter +import io.element.android.features.login.impl.changeaccountprovider.form.network.WellKnown +import io.element.android.features.login.impl.changeaccountprovider.form.network.WellKnownBaseConfig +import io.element.android.features.login.impl.changeaccountprovider.form.network.WellKnownSlidingSyncConfig import io.element.android.features.login.impl.datasource.AccountProviderDataSource import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService +import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.test.runTest import org.junit.Test class ChangeAccountProviderFormPresenterTest { @Test fun `present - initial state`() = runTest { - val homeServerResolver = FakeHomeServerResolver() + val fakeWellknownRequest = FakeWellknownRequest() val changeServerPresenter = ChangeServerPresenter( FakeAuthenticationService(), AccountProviderDataSource() ) val presenter = ChangeAccountProviderFormPresenter( - homeServerResolver, + DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), changeServerPresenter ) moleculeFlow(RecompositionClock.Immediate) { @@ -50,13 +55,13 @@ class ChangeAccountProviderFormPresenterTest { @Test fun `present - enter text no result`() = runTest { - val homeServerResolver = FakeHomeServerResolver() + val fakeWellknownRequest = FakeWellknownRequest() val changeServerPresenter = ChangeServerPresenter( FakeAuthenticationService(), AccountProviderDataSource() ) val presenter = ChangeAccountProviderFormPresenter( - homeServerResolver, + DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), changeServerPresenter ) moleculeFlow(RecompositionClock.Immediate) { @@ -73,12 +78,41 @@ class ChangeAccountProviderFormPresenterTest { } @Test - fun `present - enter text one then two results`() = runTest { - val homeServerResolver = FakeHomeServerResolver() - homeServerResolver.givenResult( - listOf( - listOf(aHomeserverData()), - listOf(aHomeserverData(), aHomeserverData()), + fun `present - enter valid url no wellknown`() = runTest { + val fakeWellknownRequest = FakeWellknownRequest() + val changeServerPresenter = ChangeServerPresenter( + FakeAuthenticationService(), + AccountProviderDataSource() + ) + val presenter = ChangeAccountProviderFormPresenter( + DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), + changeServerPresenter + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(ChangeAccountProviderFormEvents.UserInput("https://test.org")) + val withInputState = awaitItem() + assertThat(withInputState.userInput).isEqualTo("https://test.org") + assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized) + assertThat(awaitItem().userInputResult).isInstanceOf(Async.Loading::class.java) + assertThat(awaitItem().userInputResult).isEqualTo( + Async.Success( + listOf( + aHomeserverData(homeserverUrl = "https://test.org", isWellknownValid = false, supportSlidingSync = false) + ) + ) + ) + } + } + + @Test + fun `present - enter text one result no sliding sync`() = runTest { + val fakeWellknownRequest = FakeWellknownRequest() + fakeWellknownRequest.givenResultMap( + mapOf( + "https://test.org" to aWellKnown().copy(slidingSyncProxy = null), ) ) val changeServerPresenter = ChangeServerPresenter( @@ -86,7 +120,7 @@ class ChangeAccountProviderFormPresenterTest { AccountProviderDataSource() ) val presenter = ChangeAccountProviderFormPresenter( - homeServerResolver, + DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), changeServerPresenter ) moleculeFlow(RecompositionClock.Immediate) { @@ -98,8 +132,62 @@ class ChangeAccountProviderFormPresenterTest { assertThat(withInputState.userInput).isEqualTo("test") assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized) assertThat(awaitItem().userInputResult).isInstanceOf(Async.Loading::class.java) - assertThat(awaitItem().userInputResult).isEqualTo(Async.Success(listOf(aHomeserverData()))) - assertThat(awaitItem().userInputResult).isEqualTo(Async.Success(listOf(aHomeserverData(), aHomeserverData()))) + assertThat(awaitItem().userInputResult).isEqualTo( + Async.Success( + listOf( + aHomeserverData(homeserverUrl = "https://test.org", isWellknownValid = true, supportSlidingSync = false) + ) + ) + ) } } + + @Test + fun `present - enter text one result with sliding sync`() = runTest { + val fakeWellknownRequest = FakeWellknownRequest() + fakeWellknownRequest.givenResultMap( + mapOf( + "https://test.io" to aWellKnown(), + ) + ) + val changeServerPresenter = ChangeServerPresenter( + FakeAuthenticationService(), + AccountProviderDataSource() + ) + val presenter = ChangeAccountProviderFormPresenter( + DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), + changeServerPresenter + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(ChangeAccountProviderFormEvents.UserInput("test")) + val withInputState = awaitItem() + assertThat(withInputState.userInput).isEqualTo("test") + assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized) + assertThat(awaitItem().userInputResult).isInstanceOf(Async.Loading::class.java) + assertThat(awaitItem().userInputResult).isEqualTo( + Async.Success( + listOf( + aHomeserverData(homeserverUrl = "https://test.io") + ) + ) + ) + } + } + + private fun aWellKnown(): WellKnown { + return WellKnown( + homeServer = WellKnownBaseConfig( + baseURL = A_HOMESERVER_URL + ), + identityServer = WellKnownBaseConfig( + baseURL = A_HOMESERVER_URL + ), + slidingSyncProxy = WellKnownSlidingSyncConfig( + url = A_HOMESERVER_URL + ) + ) + } } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/FakeHomeServerResolver.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/FakeHomeServerResolver.kt deleted file mode 100644 index 8a712aa4a0..0000000000 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/FakeHomeServerResolver.kt +++ /dev/null @@ -1,45 +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 io.element.android.libraries.matrix.test.FAKE_DELAY_IN_MS -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow - -class FakeHomeServerResolver : HomeserverResolver { - private var pendingResult: List> = emptyList() - fun givenResult(result: List>) { - pendingResult = result - } - - override suspend fun resolve(userInput: String): Flow>> = flow { - emit(Async.Uninitialized) - delay(FAKE_DELAY_IN_MS) - emit(Async.Loading()) - // Sending the pending result - if (pendingResult.isEmpty()) { - emit(Async.Uninitialized) - } else { - pendingResult.forEach { - delay(FAKE_DELAY_IN_MS) - emit(Async.Success(it)) - } - } - } -} diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/FakeWellknownRequest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/FakeWellknownRequest.kt new file mode 100644 index 0000000000..0cc4a8435d --- /dev/null +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/FakeWellknownRequest.kt @@ -0,0 +1,31 @@ +/* + * 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.features.login.impl.changeaccountprovider.form.network.WellKnown +import io.element.android.features.login.impl.changeaccountprovider.form.network.WellknownRequest + +class FakeWellknownRequest : WellknownRequest { + private var resultMap: Map = emptyMap() + fun givenResultMap(map: Map) { + resultMap = map + } + + override suspend fun execute(baseUrl: String): WellKnown { + return resultMap[baseUrl] ?: error("No result provided for $baseUrl") + } +} From e53c415075c811d08f43d6d5ce69b952d3aa3d53 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 8 Jun 2023 21:05:14 +0000 Subject: [PATCH 31/61] Update screenshots --- ...ccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...countProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ..._LoginPasswordViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 3 +++ ..._LoginPasswordViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 3 +++ ..._LoginPasswordViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png | 3 +++ ...LoginPasswordViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 3 +++ ...LoginPasswordViewLightPreview_0_null_1,NEXUS_5,1.0,en].png | 3 +++ ...LoginPasswordViewLightPreview_0_null_2,NEXUS_5,1.0,en].png | 3 +++ ...up_LoginRootScreenDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 3 --- ...up_LoginRootScreenDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 3 --- ...up_LoginRootScreenDarkPreview_0_null_2,NEXUS_5,1.0,en].png | 3 --- ...p_LoginRootScreenLightPreview_0_null_0,NEXUS_5,1.0,en].png | 3 --- ...p_LoginRootScreenLightPreview_0_null_1,NEXUS_5,1.0,en].png | 3 --- ...p_LoginRootScreenLightPreview_0_null_2,NEXUS_5,1.0,en].png | 3 --- ...fySelfSessionViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...fySelfSessionViewLightPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...fySelfSessionViewLightPreview_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...fySelfSessionViewLightPreview_0_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...fySelfSessionViewLightPreview_0_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...fySelfSessionViewLightPreview_0_null_5,NEXUS_5,1.0,en].png | 4 ++-- ...tleSubtitleMoleculeLightPreview_0_null,NEXUS_5,1.0,en].png | 4 ++-- 21 files changed, 36 insertions(+), 36 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_2,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_0,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_1,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_2,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_0,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_1,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index e96d33b1db..406ee49b58 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c5b66e68165054b6c4e09c00b7ebcff13229373bdc0eb10520f111b9d15576cb -size 39804 +oid sha256:51897c460fad147f358268ad6b15d6865b6378edd8533d744fe108bc96f5d718 +size 40107 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index 3f672cd84a..3346d30e90 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0292719b35c9e9b75a783628300fab10f75bb857c040e6829ee9ec51e5abc8dd -size 39605 +oid sha256:57ace1181dd3d95bdbc0ec11270951b6b2fce6301a68af88c6cddc428acba022 +size 39533 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4fc7c6de68 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:84a19a75922a661d173d1213797112bba2e8faf5093561f336925c2f06ecd17a +size 31799 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..9dcfbbebaa --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:20a03449955f95720f3ef1fcf36d9fd82fb34c5e2efea7515e1dd25b11a5257b +size 31834 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4fc7c6de68 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:84a19a75922a661d173d1213797112bba2e8faf5093561f336925c2f06ecd17a +size 31799 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c9c94638f6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:367400d1ff6e9e39a5a27bcd45227c75a13fc3d2a81ca532eda75a07481c025a +size 31472 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..0ea591d06f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d33b86a10b237ee11b7fcf757462c9685f2cc10fb30ee1c76f2808306b7bc14c +size 31456 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c9c94638f6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:367400d1ff6e9e39a5a27bcd45227c75a13fc3d2a81ca532eda75a07481c025a +size 31472 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_0,NEXUS_5,1.0,en].png deleted file mode 100644 index 44a334df69..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:871df52acf33bad33443a9e8140b58d17e5a068e008c5414be06d45f3b3e89ce -size 31619 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_1,NEXUS_5,1.0,en].png deleted file mode 100644 index a5aa6e658b..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:871baa23b7ce95b95d55956f47cd1659408901c2a4221cb32231d4187a6861f8 -size 31653 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_2,NEXUS_5,1.0,en].png deleted file mode 100644 index 44a334df69..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_2,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:871df52acf33bad33443a9e8140b58d17e5a068e008c5414be06d45f3b3e89ce -size 31619 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_0,NEXUS_5,1.0,en].png deleted file mode 100644 index 71d1ec0eda..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:82ee4c067a7396dc3f1247169f72792f027029a14f04ab512d4db1c0f1920996 -size 31509 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_1,NEXUS_5,1.0,en].png deleted file mode 100644 index c4fa17263b..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5b05b56e0b8b54e2d13df513d180f7fed3d20df558793dab4358edeed4bac751 -size 31493 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_2,NEXUS_5,1.0,en].png deleted file mode 100644 index 71d1ec0eda..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginRootScreenLightPreview_0_null_2,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:82ee4c067a7396dc3f1247169f72792f027029a14f04ab512d4db1c0f1920996 -size 31509 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index ccf74db6e2..f204672d04 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:31b41c8f96870545a06ef473206b6c1d3b24f0fa8626c18adbdc31bc592e363f -size 28957 +oid sha256:4c3b9167b8b97eed74727902987a91fc1808d91709cef3a935b0fc58dbb4650a +size 29049 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_1,NEXUS_5,1.0,en].png index 5391ae55ba..0c9a371a45 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:296a54854a782fd3c358960b42cdfba0c6822656742daa7034d2c36b53d040fa -size 25959 +oid sha256:1f1c11cdf296cef8514547cee32fffc53eeb2ba16ab85475e98c66eaaa315f0f +size 26294 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_2,NEXUS_5,1.0,en].png index 486a20f3dc..262a0ccbf9 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4a399eaa0f0e8a33bf7eb2e524dd162265918ddb90fd3200d9bb48a9db75a2c3 -size 58921 +oid sha256:bb425ad24d840d6f85fcbc7f8629f58e2ba9b4f8173e1e728056d3185c2bdd49 +size 59691 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_3,NEXUS_5,1.0,en].png index 89246cbcaa..9ef4777a07 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17bec083f13ac0d24787cd171f232b673d9c3f9cbffba9161bf70acf48b30c85 -size 59343 +oid sha256:0f5f76169e604b2f2a5ab5dc302b91bdc142fc2a0a17001f78d6d8f63774c3f0 +size 60130 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_4,NEXUS_5,1.0,en].png index 9ac1730d01..a5f8157309 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2e5afbd696458ba2ec094da4bbee3b74faa32f0abbc215a1082eb45b628dfcaf -size 31835 +oid sha256:8325684caab3d97fdc240748b85592a7fab64233055fbdb65e18e56786968e40 +size 32388 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_5,NEXUS_5,1.0,en].png index 8c57f95a28..e8535fd92c 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.verifysession.impl_null_DefaultGroup_VerifySelfSessionViewLightPreview_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a857ce94e305acd45c97af4ae2532bfa9310a1e1bf0e549c780f654a89065e5e -size 26160 +oid sha256:78e9a0f4d9799f1a526e217398dd885d91bee6f69d4544d2f5799a170fefd02a +size 26971 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.atomic.molecules_null_DefaultGroup_IconTitleSubtitleMoleculeLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.atomic.molecules_null_DefaultGroup_IconTitleSubtitleMoleculeLightPreview_0_null,NEXUS_5,1.0,en].png index a874edaa42..70d1d4e355 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.atomic.molecules_null_DefaultGroup_IconTitleSubtitleMoleculeLightPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.atomic.molecules_null_DefaultGroup_IconTitleSubtitleMoleculeLightPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be000cc43901f8b61a12c93201de8026fd57654e6a66cf2d5507f0b29af76e6a -size 9227 +oid sha256:b508532be8dd29f60e30c00923834ac21ff0664cf3521fb86cdcdd493e9bfff7 +size 9930 From b72172e548bf3f865267cc376e29a8d9d085f5f0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 09:28:50 +0200 Subject: [PATCH 32/61] Format --- features/login/impl/build.gradle.kts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/features/login/impl/build.gradle.kts b/features/login/impl/build.gradle.kts index 279ac27767..aacf4c0aeb 100644 --- a/features/login/impl/build.gradle.kts +++ b/features/login/impl/build.gradle.kts @@ -19,7 +19,8 @@ plugins { alias(libs.plugins.anvil) alias(libs.plugins.ksp) id("kotlin-parcelize") - kotlin("plugin.serialization") version "1.8.21"} + kotlin("plugin.serialization") version "1.8.21" +} android { namespace = "io.element.android.features.login.impl" From 4c214db5c49675c810b3e59fc90bedcd07b8184d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 10:17:30 +0200 Subject: [PATCH 33/61] Update maestro test regarding the new login flow. --- .maestro/README.md | 2 +- .maestro/tests/account/changeServer.yaml | 13 ++++++++++++- .maestro/tests/account/login.yaml | 2 ++ .maestro/tests/assertions/assertLoginDisplayed.yaml | 2 +- .../impl/accountprovider/AccountProviderView.kt | 3 ++- .../form/ChangeAccountProviderFormView.kt | 5 ++++- .../element/android/libraries/testtags/TestTags.kt | 1 - 7 files changed, 22 insertions(+), 6 deletions(-) diff --git a/.maestro/README.md b/.maestro/README.md index 76268e144e..3926dcdf56 100644 --- a/.maestro/README.md +++ b/.maestro/README.md @@ -25,7 +25,7 @@ maestro test \ -e APP_ID=io.element.android.x.debug \ -e USERNAME=user \ -e PASSWORD=123 \ - -e ROOM_NAME="my room" \ + -e ROOM_NAME="MyRoom" \ .maestro/allTests.yaml ``` diff --git a/.maestro/tests/account/changeServer.yaml b/.maestro/tests/account/changeServer.yaml index 505a12d2e1..df4b12f253 100644 --- a/.maestro/tests/account/changeServer.yaml +++ b/.maestro/tests/account/changeServer.yaml @@ -3,4 +3,15 @@ appId: ${APP_ID} - tapOn: id: "login-change_server" - takeScreenshot: build/maestro/200-ChangeServer -- tapOn: "Continue" +- tapOn: "matrix.org" +- tapOn: + id: "login-change_server" +- tapOn: "Other" +- tapOn: + id: "change_server-server" +- inputText: "element" +- hideKeyboard +- tapOn: "element.io" +- tapOn: "Cancel" +- back +- back diff --git a/.maestro/tests/account/login.yaml b/.maestro/tests/account/login.yaml index 845746a76b..728ff98b31 100644 --- a/.maestro/tests/account/login.yaml +++ b/.maestro/tests/account/login.yaml @@ -5,6 +5,8 @@ appId: ${APP_ID} - takeScreenshot: build/maestro/100-SignIn - runFlow: changeServer.yaml - runFlow: ../assertions/assertLoginDisplayed.yaml +- tapOn: + id: "login-continue" - tapOn: id: "login-email_username" - inputText: ${USERNAME} diff --git a/.maestro/tests/assertions/assertLoginDisplayed.yaml b/.maestro/tests/assertions/assertLoginDisplayed.yaml index 41f1ff3306..3abd86ceef 100644 --- a/.maestro/tests/assertions/assertLoginDisplayed.yaml +++ b/.maestro/tests/assertions/assertLoginDisplayed.yaml @@ -1,5 +1,5 @@ appId: ${APP_ID} --- - extendedWaitUntil: - visible: "Welcome back!" + visible: "Change account provider" timeout: 10_000 diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt index 56ea41a3d9..5c0ba23ab5 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt @@ -94,7 +94,7 @@ fun AccountProviderView( enabled = state.submitEnabled, modifier = Modifier .fillMaxWidth() - .testTag(TestTags.changeServerContinue) + .testTag(TestTags.loginContinue) ) TextButton( onClick = { @@ -103,6 +103,7 @@ fun AccountProviderView( enabled = true, modifier = Modifier .fillMaxWidth() + .testTag(TestTags.loginChangeServer) ) { Text(text = stringResource(id = R.string.screen_account_provider_change)) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt index cb35774147..8ab0659d7b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt @@ -65,6 +65,8 @@ import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.OutlinedTextField import io.element.android.libraries.designsystem.theme.components.Text +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=611-61435 @@ -117,7 +119,8 @@ fun ChangeAccountProviderFormView( // readOnly = isLoading, modifier = Modifier .fillMaxWidth() - .padding(start = 16.dp, end = 16.dp, bottom = 30.dp), + .padding(start = 16.dp, end = 16.dp, bottom = 30.dp) + .testTag(TestTags.changeServerServer), onValueChange = { userInputState = it eventSink(ChangeAccountProviderFormEvents.UserInput(it)) diff --git a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt index df12c755e3..d702c797a8 100644 --- a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt +++ b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt @@ -37,7 +37,6 @@ object TestTags { * Change server screen. */ val changeServerServer = TestTag("change_server-server") - val changeServerContinue = TestTag("change_server-continue") /** * Room list / Home screen. From 0bb3d12e9d94bed6fa229f849c83b4c32c4ec07e Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 9 Jun 2023 09:01:02 +0000 Subject: [PATCH 34/61] Update screenshots --- ...ntProviderFormViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...ntProviderFormViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...tProviderFormViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...tProviderFormViewLightPreview_0_null_1,NEXUS_5,1.0,en].png | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index 20c687f808..f90601bd76 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:32ae0372d47d6e68e853fbcdeb00e7829d78944b2cb3c284a845a0d463247c2b -size 25670 +oid sha256:abc7f0b614bb7532341dd016fab0f5ba72a08f9718bb496b420c94edfcedad2f +size 25927 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png index 1c8e3c110e..ac844925a8 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:905cbb22344e797506dcabcbad15cdb2ab5c2e0ec2deaf544fdc89fdeca2615d -size 47638 +oid sha256:33dfbd2203f5d78760730e65c689182b54169988f10906ba987af9fe43d0d03d +size 47910 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index e1a1286e15..fe94473bb3 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aaf00b3d9944a98db7d15890f93bf82834fd7376d323347f03bba3830aa2caa6 -size 25431 +oid sha256:9d11e04a2440ecd57a4178a0e2c1aa2755c62d147e28c065c7b10a957fef35da +size 25669 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_1,NEXUS_5,1.0,en].png index c08530f117..a11e2c6787 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:99ed6c29df9a96d7d6e0dcd34770d9837def14f73fb6509a8ccaf85ef964a934 -size 47046 +oid sha256:3ebf802513c7372eaba77b4ebaabbc8e313c1b8f8b00ef8e496194586c9431dc +size 47340 From 67cca41e0dec36f9fd4172e0eeeda40e0e0eded2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 12:48:44 +0200 Subject: [PATCH 35/61] trigger CI From 06297bb792416f6bf568f8e2c1614e2a637d000c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 16:34:22 +0200 Subject: [PATCH 36/61] Update strings. --- .../login/impl/src/main/res/values/localazy.xml | 6 ++++++ .../impl/src/main/res/values/localazy.xml | 2 +- .../push/impl/src/main/res/values/localazy.xml | 1 + .../ui-strings/src/main/res/values/localazy.xml | 13 +++++++------ tools/localazy/config.json | 1 + 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/features/login/impl/src/main/res/values/localazy.xml b/features/login/impl/src/main/res/values/localazy.xml index 96d05df794..bb27ca51e1 100644 --- a/features/login/impl/src/main/res/values/localazy.xml +++ b/features/login/impl/src/main/res/values/localazy.xml @@ -26,6 +26,12 @@ "Where your conversations live" "Welcome back!" "Sign in to %1$s" + "Change account provider" + "A private server for Element employees." + "Matrix is an open network for secure, decentralised communication." + "This is where your conversations will live — just like you would use an email provider to keep your emails." + "You’re about to sign in to %1$s" + "You’re about to create an account on %1$s" "Continue" "Select your server" "Password" diff --git a/features/onboarding/impl/src/main/res/values/localazy.xml b/features/onboarding/impl/src/main/res/values/localazy.xml index 54d86ba247..ef91de7541 100644 --- a/features/onboarding/impl/src/main/res/values/localazy.xml +++ b/features/onboarding/impl/src/main/res/values/localazy.xml @@ -4,6 +4,6 @@ "Sign in with QR code" "Create account" "Communicate and collaborate securely" - "Welcome to the %1$s Beta. Supercharged, for speed and simplicity." + "Welcome to %1$s. Supercharged, for speed and simplicity." "Be in your Element"
\ No newline at end of file diff --git a/libraries/push/impl/src/main/res/values/localazy.xml b/libraries/push/impl/src/main/res/values/localazy.xml index d38bf7d8dd..922d35b3e1 100644 --- a/libraries/push/impl/src/main/res/values/localazy.xml +++ b/libraries/push/impl/src/main/res/values/localazy.xml @@ -7,6 +7,7 @@ "** Failed to send - please open room" "Join" "Reject" + "invited you" "New Messages" "Mark as read" "Me" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index f3cada2464..4f3ca67296 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -34,6 +34,7 @@ "No" "Not now" "OK" + "Open with" "Quick reply" "Quote" "Remove" @@ -56,9 +57,11 @@ "View Source" "Yes" "About" + "Acceptable use policy" "Analytics" "Audio" "Bubbles" + "Copyright" "Creating room…" "Left room" "Decryption error" @@ -80,11 +83,13 @@ "Message layout" "Message removed" "Modern" + "Mute" "No results" "Offline" "Password" "People" "Permalink" + "Privacy policy" "Reactions" "Replying to %1$s" "Report a bug" @@ -103,11 +108,13 @@ "Sticker" "Success" "Suggestions" + "Third-party notices" "Topic" "What is this room about?" "Unable to decrypt" "We were unable to successfully send invites to one or more users." "Unable to send invite(s)" + "Unmute" "Unsupported event" "Username" "Verification cancelled" @@ -150,12 +157,6 @@ "Failed processing media to upload, please try again." "Failed uploading media, please try again." "Check if you want to hide all current and future messages from this user" - "Change account provider" - "A private server for Element employees." - "Matrix is an open network for secure, decentralised communication." - "This is where your conversations will live — just like you would use an email provider to keep your emails." - "You’re about to sign in to %1$s" - "You’re about to create an account on %1$s" "Rageshake" "Detection threshold" "General" diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 8fa70179e3..a7317af0db 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -73,6 +73,7 @@ "name": ":features:login:impl", "includeRegex": [ "screen_login_.*", + "screen_server_confirmation_.*", "screen_change_server_.*", "screen_change_account_provider_.*", "screen_account_provider_.*" From dc064069babfe5eeccbf62deb9149ca2df29099b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 17:34:14 +0200 Subject: [PATCH 37/61] Rename nodes and organize by package into `screens` subpackage for clarity --- .../features/login/impl/LoginFlowNode.kt | 50 ++--- .../{item => }/AccountProvider.kt | 2 +- .../AccountProviderDataSource.kt | 3 +- .../{item => }/AccountProviderProvider.kt | 2 +- .../accountprovider/AccountProviderView.kt | 187 ++++++++---------- .../item/AccountProviderView.kt | 132 ------------- .../form/HomeserverResolver.kt | 27 --- .../ChangeServerEvents.kt | 4 +- .../ChangeServerPresenter.kt | 6 +- .../ChangeServerState.kt | 2 +- .../ChangeServerStateProvider.kt | 2 +- .../ChangeServerView.kt | 4 +- .../resolver}/HomeserverData.kt | 2 +- .../resolver/HomeserverResolver.kt} | 13 +- .../network/DefaultWellknownRequest.kt | 2 +- .../resolver}/network/WellKnown.kt | 2 +- .../resolver}/network/WellKnownBaseConfig.kt | 2 +- .../network/WellKnownSlidingSyncConfig.kt | 2 +- .../resolver}/network/WellknownAPI.kt | 2 +- .../resolver}/network/WellknownRequest.kt | 2 +- .../SlidingSyncNotSupportedDialog.kt | 2 +- .../ChangeAccountProviderNode.kt | 2 +- .../ChangeAccountProviderPresenter.kt | 6 +- .../ChangeAccountProviderState.kt | 6 +- .../ChangeAccountProviderStateProvider.kt | 6 +- .../ChangeAccountProviderView.kt | 10 +- .../ConfirmAccountProviderEvents.kt} | 8 +- .../ConfirmAccountProviderNode.kt} | 10 +- .../ConfirmAccountProviderPresenter.kt} | 30 +-- .../ConfirmAccountProviderState.kt} | 8 +- .../ConfirmAccountProviderStateProvider.kt} | 12 +- .../ConfirmAccountProviderView.kt | 162 +++++++++++++++ .../loginpassword/LoginPasswordEvents.kt | 2 +- .../loginpassword/LoginPasswordNode.kt | 2 +- .../loginpassword/LoginPasswordPresenter.kt | 4 +- .../loginpassword/LoginPasswordState.kt | 4 +- .../LoginPasswordStateProvider.kt | 4 +- .../loginpassword/LoginPasswordView.kt | 4 +- .../SearchAccountProviderEvents.kt} | 6 +- .../SearchAccountProviderNode.kt} | 8 +- .../SearchAccountProviderPresenter.kt} | 18 +- .../SearchAccountProviderState.kt} | 9 +- .../SearchAccountProviderStateProvider.kt} | 17 +- .../SearchAccountProviderView.kt} | 27 +-- .../login/impl/util/LoginConstants.kt | 2 +- .../ChangeServerPresenterTest.kt | 6 +- .../resolver/network}/FakeWellknownRequest.kt | 5 +- .../ChangeAccountProviderPresenterTest.kt | 8 +- .../ConfirmAccountProviderPresenterTest.kt} | 36 ++-- .../LoginPasswordPresenterTest.kt | 4 +- .../SearchAccountProviderPresenterTest.kt} | 44 +++-- .../android/samples/minimal/LoginScreen.kt | 6 +- 52 files changed, 450 insertions(+), 476 deletions(-) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/{item => }/AccountProvider.kt (92%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{datasource => accountprovider}/AccountProviderDataSource.kt (90%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/{item => }/AccountProviderProvider.kt (95%) delete mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProviderView.kt delete mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverResolver.kt rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/common => changeserver}/ChangeServerEvents.kt (82%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/common => changeserver}/ChangeServerPresenter.kt (92%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/common => changeserver}/ChangeServerState.kt (91%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/common => changeserver}/ChangeServerStateProvider.kt (93%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/common => changeserver}/ChangeServerView.kt (94%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/form => changeserver/resolver}/HomeserverData.kt (93%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/form/DefaultHomeserverResolver.kt => changeserver/resolver/HomeserverResolver.kt} (89%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/form => changeserver/resolver}/network/DefaultWellknownRequest.kt (94%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/form => changeserver/resolver}/network/WellKnown.kt (95%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/form => changeserver/resolver}/network/WellKnownBaseConfig.kt (92%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/form => changeserver/resolver}/network/WellKnownSlidingSyncConfig.kt (90%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/form => changeserver/resolver}/network/WellknownAPI.kt (90%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/form => changeserver/resolver}/network/WellknownRequest.kt (90%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{accountprovider => dialogs}/SlidingSyncNotSupportedDialog.kt (96%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{ => screens}/changeaccountprovider/ChangeAccountProviderNode.kt (96%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{ => screens}/changeaccountprovider/ChangeAccountProviderPresenter.kt (86%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{ => screens}/changeaccountprovider/ChangeAccountProviderState.kt (77%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{ => screens}/changeaccountprovider/ChangeAccountProviderStateProvider.kt (82%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{ => screens}/changeaccountprovider/ChangeAccountProviderView.kt (93%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{accountprovider/AccountProviderEvents.kt => screens/confirmaccountprovider/ConfirmAccountProviderEvents.kt} (72%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{accountprovider/AccountProviderNode.kt => screens/confirmaccountprovider/ConfirmAccountProviderNode.kt} (90%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{accountprovider/AccountProviderPresenter.kt => screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt} (80%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{accountprovider/AccountProviderState.kt => screens/confirmaccountprovider/ConfirmAccountProviderState.kt} (82%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{accountprovider/AccountProviderStateProvider.kt => screens/confirmaccountprovider/ConfirmAccountProviderStateProvider.kt} (68%) create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{ => screens}/loginpassword/LoginPasswordEvents.kt (92%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{ => screens}/loginpassword/LoginPasswordNode.kt (95%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{ => screens}/loginpassword/LoginPasswordPresenter.kt (95%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{ => screens}/loginpassword/LoginPasswordState.kt (90%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{ => screens}/loginpassword/LoginPasswordStateProvider.kt (90%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{ => screens}/loginpassword/LoginPasswordView.kt (99%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/form/ChangeAccountProviderFormEvents.kt => screens/searchaccountprovider/SearchAccountProviderEvents.kt} (77%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/form/ChangeAccountProviderFormNode.kt => screens/searchaccountprovider/SearchAccountProviderNode.kt} (88%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt => screens/searchaccountprovider/SearchAccountProviderPresenter.kt} (74%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/form/ChangeAccountProviderFormState.kt => screens/searchaccountprovider/SearchAccountProviderState.kt} (72%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/form/ChangeAccountProviderFormStateProvider.kt => screens/searchaccountprovider/SearchAccountProviderStateProvider.kt} (74%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeaccountprovider/form/ChangeAccountProviderFormView.kt => screens/searchaccountprovider/SearchAccountProviderView.kt} (88%) rename features/login/impl/src/test/kotlin/io/element/android/features/login/impl/{changeaccountprovider/common => changeserver}/ChangeServerPresenterTest.kt (93%) rename features/login/impl/src/test/kotlin/io/element/android/features/login/impl/{changeaccountprovider/form => changeserver/resolver/network}/FakeWellknownRequest.kt (78%) rename features/login/impl/src/test/kotlin/io/element/android/features/login/impl/{ => screens}/changeaccountprovider/ChangeAccountProviderPresenterTest.kt (84%) rename features/login/impl/src/test/kotlin/io/element/android/features/login/impl/{accountprovider/AccountProviderPresenterTest.kt => screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt} (80%) rename features/login/impl/src/test/kotlin/io/element/android/features/login/impl/{ => screens}/loginpassword/LoginPasswordPresenterTest.kt (97%) rename features/login/impl/src/test/kotlin/io/element/android/features/login/impl/{changeaccountprovider/form/ChangeAccountProviderFormPresenterTest.kt => screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt} (78%) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt index 36b6d904e7..33e3a66abe 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt @@ -32,14 +32,14 @@ import com.bumble.appyx.navmodel.backstack.operation.singleTop import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode -import io.element.android.features.login.impl.accountprovider.AccountProviderNode -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.accountprovider.AccountProviderDataSource 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.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.NodeInputs import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler @@ -59,7 +59,7 @@ class LoginFlowNode @AssistedInject constructor( private val accountProviderDataSource: AccountProviderDataSource, ) : BackstackNode( backstack = BackStack( - initialElement = NavTarget.AccountProvider, // NavTarget.Root, + initialElement = NavTarget.ConfirmAccountProvider, savedStateMap = buildContext.savedStateMap, ), buildContext = buildContext, @@ -76,16 +76,16 @@ class LoginFlowNode @AssistedInject constructor( sealed interface NavTarget : Parcelable { @Parcelize - object AccountProvider : NavTarget + object ConfirmAccountProvider : NavTarget @Parcelize object ChangeAccountProvider : NavTarget @Parcelize - object ChangeAccountProviderForm : NavTarget + object SearchAccountProvider : NavTarget @Parcelize - object LoginPasswordForm : NavTarget + object LoginPassword : NavTarget @Parcelize data class OidcView(val oidcDetails: OidcDetails) : NavTarget @@ -93,15 +93,11 @@ class LoginFlowNode @AssistedInject constructor( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { - is NavTarget.OidcView -> { - val input = OidcNode.Inputs(navTarget.oidcDetails) - createNode(buildContext, plugins = listOf(input)) - } - NavTarget.AccountProvider -> { - val inputs = AccountProviderNode.Inputs( + NavTarget.ConfirmAccountProvider -> { + val inputs = ConfirmAccountProviderNode.Inputs( isAccountCreation = inputs.isAccountCreation ) - val callback = object : AccountProviderNode.Callback { + val callback = object : ConfirmAccountProviderNode.Callback { override fun onOidcDetails(oidcDetails: OidcDetails) { if (customTabAvailabilityChecker.supportCustomTab()) { // In this case open a Chrome Custom tab @@ -113,42 +109,46 @@ class LoginFlowNode @AssistedInject constructor( } override fun onLoginPasswordNeeded() { - backstack.push(NavTarget.LoginPasswordForm) + backstack.push(NavTarget.LoginPassword) } override fun onChangeAccountProvider() { backstack.push(NavTarget.ChangeAccountProvider) } } - createNode(buildContext, plugins = listOf(inputs, callback)) + createNode(buildContext, plugins = listOf(inputs, callback)) } NavTarget.ChangeAccountProvider -> { val callback = object : ChangeAccountProviderNode.Callback { override fun onDone() { // Go back to the Account Provider screen - backstack.singleTop(NavTarget.AccountProvider) + backstack.singleTop(NavTarget.ConfirmAccountProvider) } override fun onOtherClicked() { - backstack.push(NavTarget.ChangeAccountProviderForm) + backstack.push(NavTarget.SearchAccountProvider) } } createNode(buildContext, plugins = listOf(callback)) } - NavTarget.ChangeAccountProviderForm -> { - val callback = object : ChangeAccountProviderFormNode.Callback { + NavTarget.SearchAccountProvider -> { + val callback = object : SearchAccountProviderNode.Callback { override fun onDone() { // Go back to the Account Provider screen - backstack.singleTop(NavTarget.AccountProvider) + backstack.singleTop(NavTarget.ConfirmAccountProvider) } } - createNode(buildContext, plugins = listOf(callback)) + createNode(buildContext, plugins = listOf(callback)) } - NavTarget.LoginPasswordForm -> { + NavTarget.LoginPassword -> { createNode(buildContext, plugins = listOf()) } + is NavTarget.OidcView -> { + val input = OidcNode.Inputs(navTarget.oidcDetails) + createNode(buildContext, plugins = listOf(input)) + } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProvider.kt similarity index 92% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProvider.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProvider.kt index c09d6fab9b..b6aea81951 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProvider.kt @@ -14,7 +14,7 @@ * 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( val title: String, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/datasource/AccountProviderDataSource.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt similarity index 90% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/datasource/AccountProviderDataSource.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt index ebd1c15ae1..ea541285df 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/datasource/AccountProviderDataSource.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt @@ -14,9 +14,8 @@ * 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.libraries.di.AppScope import io.element.android.libraries.di.SingleIn diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProviderProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderProvider.kt similarity index 95% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProviderProvider.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderProvider.kt index 211aa4f5bc..71e1abd591 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProviderProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderProvider.kt @@ -14,7 +14,7 @@ * 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 diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt index 5c0ba23ab5..0ceb65dea9 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt @@ -16,146 +16,117 @@ 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.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.AccountCircle +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment 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.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.features.login.impl.R -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.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 -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 fun AccountProviderView( - state: AccountProviderState, + item: AccountProvider, modifier: Modifier = Modifier, - onOidcDetails: (OidcDetails) -> Unit = {}, - onLoginPasswordNeeded: () -> Unit = {}, - onLearnMoreClicked: () -> Unit = {}, - onChange: () -> Unit = {}, + onClick: () -> 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(AccountProviderEvents.Continue) }, - enabled = state.submitEnabled, + 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 - .fillMaxWidth() - .testTag(TestTags.loginContinue) + .padding(start = 16.dp) + .weight(1f), + text = item.title, + style = ElementTextStyles.Regular.headline.copy(textAlign = TextAlign.Start), + color = MaterialTheme.colorScheme.primary, ) - TextButton( - onClick = { - onChange() - }, - enabled = true, + 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 - .fillMaxWidth() - .testTag(TestTags.loginChangeServer) - ) { - Text(text = stringResource(id = R.string.screen_account_provider_change)) - } + .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, + ) } } - ) { - 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 @Composable -fun AccountProviderViewLightPreview(@PreviewParameter(AccountProviderStateProvider::class) state: AccountProviderState) = - ElementPreviewLight { ContentToPreview(state) } +fun AccountProviderViewLightPreview(@PreviewParameter(AccountProviderProvider::class) item: AccountProvider) = + ElementPreviewLight { ContentToPreview(item) } @Preview @Composable -fun AccountProviderViewDarkPreview(@PreviewParameter(AccountProviderStateProvider::class) state: AccountProviderState) = - ElementPreviewDark { ContentToPreview(state) } +fun AccountProviderViewDarkPreview(@PreviewParameter(AccountProviderProvider::class) item: AccountProvider) = + ElementPreviewDark { ContentToPreview(item) } @Composable -private fun ContentToPreview(state: AccountProviderState) { +private fun ContentToPreview(item: AccountProvider) { AccountProviderView( - state = state, + item = item, + onClick = { } ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProviderView.kt deleted file mode 100644 index ae3aea8878..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/item/AccountProviderView.kt +++ /dev/null @@ -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 = { } - ) -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverResolver.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverResolver.kt deleted file mode 100644 index 0c8bcfc712..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverResolver.kt +++ /dev/null @@ -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>> -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvents.kt similarity index 82% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerEvents.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvents.kt index 8cded25388..cd1cb7b4ce 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvents.kt @@ -14,9 +14,9 @@ * 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 { data class ChangeServer(val accountProvider: AccountProvider) : ChangeServerEvents diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt similarity index 92% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerPresenter.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt index fcc4dc7885..2e3f9548d6 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt @@ -14,15 +14,15 @@ * 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.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import io.element.android.features.login.impl.accountprovider.item.AccountProvider -import io.element.android.features.login.impl.datasource.AccountProviderDataSource +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.error.ChangeServerError import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerState.kt similarity index 91% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerState.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerState.kt index fbe4c37117..e49fa1f2fe 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerState.kt @@ -14,7 +14,7 @@ * 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 diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt similarity index 93% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerStateProvider.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt index b089ef9ff9..90c2bff455 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt @@ -14,7 +14,7 @@ * 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 io.element.android.libraries.architecture.Async diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt similarity index 94% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerView.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt index dd46fb5b2e..37d90f581c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt @@ -14,13 +14,13 @@ * 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.ui.Modifier import androidx.compose.ui.tooling.preview.Preview 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.libraries.architecture.Async import io.element.android.libraries.designsystem.components.ProgressDialog diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverData.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/HomeserverData.kt similarity index 93% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverData.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/HomeserverData.kt index e0d7dbc0d5..04bcd0099d 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/HomeserverData.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/HomeserverData.kt @@ -14,7 +14,7 @@ * 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( // The computed homeserver url, for which a wellknown file has been retrieved, or just a valid Url diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/HomeserverResolver.kt similarity index 89% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/HomeserverResolver.kt index 1079e6d64b..9409f285e5 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/DefaultHomeserverResolver.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/HomeserverResolver.kt @@ -14,17 +14,15 @@ * 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.changeaccountprovider.form.network.WellknownRequest +import io.element.android.features.login.impl.changeserver.resolver.network.WellknownRequest import io.element.android.libraries.architecture.Async import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.uri.ensureProtocol import io.element.android.libraries.core.uri.isValidUrl -import io.element.android.libraries.di.AppScope import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.currentCoroutineContext @@ -38,13 +36,12 @@ import javax.inject.Inject /** * Resolve homeserver base on search terms. */ -@ContributesBinding(AppScope::class) -class DefaultHomeserverResolver @Inject constructor( +class HomeserverResolver @Inject constructor( private val dispatchers: CoroutineDispatchers, private val wellknownRequest: WellknownRequest, -) : HomeserverResolver { +) { - override suspend fun resolve(userInput: String): Flow>> = flow { + suspend fun resolve(userInput: String): Flow>> = flow { val flowContext = currentCoroutineContext() emit(Async.Uninitialized) // Debounce diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/DefaultWellknownRequest.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/DefaultWellknownRequest.kt similarity index 94% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/DefaultWellknownRequest.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/DefaultWellknownRequest.kt index 36aa47bbd0..95960971c3 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/DefaultWellknownRequest.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/DefaultWellknownRequest.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * 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 io.element.android.libraries.di.AppScope diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnown.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellKnown.kt similarity index 95% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnown.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellKnown.kt index 283e8eb496..af5a4f17f3 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnown.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellKnown.kt @@ -14,7 +14,7 @@ * 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 kotlinx.serialization.SerialName diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownBaseConfig.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellKnownBaseConfig.kt similarity index 92% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownBaseConfig.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellKnownBaseConfig.kt index 619b656d22..3bac743d64 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownBaseConfig.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellKnownBaseConfig.kt @@ -14,7 +14,7 @@ * 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.Serializable diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownSlidingSyncConfig.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellKnownSlidingSyncConfig.kt similarity index 90% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownSlidingSyncConfig.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellKnownSlidingSyncConfig.kt index 4ff8633d0d..863b524f84 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellKnownSlidingSyncConfig.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellKnownSlidingSyncConfig.kt @@ -14,7 +14,7 @@ * 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.Serializable diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownAPI.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellknownAPI.kt similarity index 90% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownAPI.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellknownAPI.kt index 0362a67c60..f4d4ba7c67 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownAPI.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellknownAPI.kt @@ -14,7 +14,7 @@ * 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 diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownRequest.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellknownRequest.kt similarity index 90% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownRequest.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellknownRequest.kt index 85d471edbd..54c393e522 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/network/WellknownRequest.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellknownRequest.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * 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 { /** diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/SlidingSyncNotSupportedDialog.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/dialogs/SlidingSyncNotSupportedDialog.kt similarity index 96% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/SlidingSyncNotSupportedDialog.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/dialogs/SlidingSyncNotSupportedDialog.kt index 364b4d955a..32eb1b0824 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/SlidingSyncNotSupportedDialog.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/dialogs/SlidingSyncNotSupportedDialog.kt @@ -14,7 +14,7 @@ * 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.ui.Modifier diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderNode.kt similarity index 96% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderNode.kt index 9787efc9cc..45bf4489b5 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderNode.kt @@ -14,7 +14,7 @@ * 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.ui.Modifier diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenter.kt similarity index 86% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenter.kt index be1da695f8..dfdb7dcf99 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenter.kt @@ -14,11 +14,11 @@ * 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 io.element.android.features.login.impl.accountprovider.item.AccountProvider -import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerPresenter +import io.element.android.features.login.impl.accountprovider.AccountProvider +import io.element.android.features.login.impl.changeserver.ChangeServerPresenter import io.element.android.libraries.architecture.Presenter import javax.inject.Inject diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderState.kt similarity index 77% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderState.kt index 2fd472e5c5..806ce5bc64 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderState.kt @@ -14,10 +14,10 @@ * 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.changeaccountprovider.common.ChangeServerState +import io.element.android.features.login.impl.accountprovider.AccountProvider +import io.element.android.features.login.impl.changeserver.ChangeServerState // Do not use default value, so no member get forgotten in the presenters. data class ChangeAccountProviderState constructor( diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderStateProvider.kt similarity index 82% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderStateProvider.kt index 01ffb65500..403746f227 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderStateProvider.kt @@ -14,11 +14,11 @@ * 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 io.element.android.features.login.impl.accountprovider.item.anAccountProvider -import io.element.android.features.login.impl.changeaccountprovider.common.aChangeServerState +import io.element.android.features.login.impl.accountprovider.anAccountProvider +import io.element.android.features.login.impl.changeserver.aChangeServerState open class ChangeAccountProviderStateProvider : PreviewParameterProvider { override val values: Sequence diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderView.kt similarity index 93% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderView.kt index d413f807e0..d7ffbe3d8d 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderView.kt @@ -16,7 +16,7 @@ @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.Column @@ -40,10 +40,10 @@ 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.accountprovider.item.AccountProvider -import io.element.android.features.login.impl.accountprovider.item.AccountProviderView -import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerEvents -import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerView +import io.element.android.features.login.impl.accountprovider.AccountProvider +import io.element.android.features.login.impl.accountprovider.AccountProviderView +import io.element.android.features.login.impl.changeserver.ChangeServerEvents +import io.element.android.features.login.impl.changeserver.ChangeServerView import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreviewDark diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderEvents.kt similarity index 72% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderEvents.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderEvents.kt index ec3a879c89..1ba3cc3028 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderEvents.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package io.element.android.features.login.impl.accountprovider +package io.element.android.features.login.impl.screens.confirmaccountprovider -sealed interface AccountProviderEvents { - object Continue : AccountProviderEvents - object ClearError : AccountProviderEvents +sealed interface ConfirmAccountProviderEvents { + object Continue : ConfirmAccountProviderEvents + object ClearError : ConfirmAccountProviderEvents } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderNode.kt similarity index 90% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderNode.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderNode.kt index dfcf6fd034..7cef986013 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderNode.kt @@ -14,7 +14,7 @@ * 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.ui.Modifier @@ -33,10 +33,10 @@ import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.auth.OidcDetails @ContributesNode(AppScope::class) -class AccountProviderNode @AssistedInject constructor( +class ConfirmAccountProviderNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, - presenterFactory: AccountProviderPresenter.Factory, + presenterFactory: ConfirmAccountProviderPresenter.Factory, ) : Node(buildContext, plugins = plugins) { data class Inputs( @@ -45,7 +45,7 @@ class AccountProviderNode @AssistedInject constructor( private val inputs: Inputs = inputs() private val presenter = presenterFactory.create( - AccountProviderPresenterParams( + ConfirmAccountProviderPresenter.Params( isAccountCreation = inputs.isAccountCreation, ) ) @@ -72,7 +72,7 @@ class AccountProviderNode @AssistedInject constructor( override fun View(modifier: Modifier) { val state = presenter.present() val context = LocalContext.current - AccountProviderView( + ConfirmAccountProviderView( state = state, modifier = modifier, onOidcDetails = ::onOidcDetails, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt similarity index 80% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt index 5246f9c528..446d6611d0 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt @@ -14,7 +14,7 @@ * 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.MutableState @@ -26,7 +26,7 @@ import androidx.compose.runtime.rememberCoroutineScope import dagger.assisted.Assisted import dagger.assisted.AssistedFactory 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.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter @@ -37,23 +37,23 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import java.net.URL -data class AccountProviderPresenterParams( - val isAccountCreation: Boolean, -) - -class AccountProviderPresenter @AssistedInject constructor( - @Assisted private val params: AccountProviderPresenterParams, +class ConfirmAccountProviderPresenter @AssistedInject constructor( + @Assisted private val params: Params, private val accountProviderDataSource: AccountProviderDataSource, private val authenticationService: MatrixAuthenticationService -) : Presenter { +) : Presenter { + + data class Params( + val isAccountCreation: Boolean, + ) @AssistedFactory interface Factory { - fun create(params: AccountProviderPresenterParams): AccountProviderPresenter + fun create(params: Params): ConfirmAccountProviderPresenter } @Composable - override fun present(): AccountProviderState { + override fun present(): ConfirmAccountProviderState { val accountProvider by accountProviderDataSource.flow().collectAsState() val localCoroutineScope = rememberCoroutineScope() @@ -61,16 +61,16 @@ class AccountProviderPresenter @AssistedInject constructor( mutableStateOf(Async.Uninitialized) } - fun handleEvents(event: AccountProviderEvents) { + fun handleEvents(event: ConfirmAccountProviderEvents) { when (event) { - AccountProviderEvents.Continue -> { + ConfirmAccountProviderEvents.Continue -> { localCoroutineScope.submit(accountProvider.title, loginFlowAction) } - AccountProviderEvents.ClearError -> loginFlowAction.value = Async.Uninitialized + ConfirmAccountProviderEvents.ClearError -> loginFlowAction.value = Async.Uninitialized } } - return AccountProviderState( + return ConfirmAccountProviderState( accountProvider = accountProvider, isAccountCreation = params.isAccountCreation, loginFlow = loginFlowAction.value, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt similarity index 82% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderState.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt index 625ef0070d..a870b88c58 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt @@ -14,18 +14,18 @@ * 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.matrix.api.auth.OidcDetails // Do not use default value, so no member get forgotten in the presenters. -data class AccountProviderState( +data class ConfirmAccountProviderState( val accountProvider: AccountProvider, val isAccountCreation: Boolean, val loginFlow: Async, - val eventSink: (AccountProviderEvents) -> Unit + val eventSink: (ConfirmAccountProviderEvents) -> Unit ) { val submitEnabled: Boolean get() = accountProvider.title.isNotEmpty() && (loginFlow is Async.Uninitialized || loginFlow is Async.Loading) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderStateProvider.kt similarity index 68% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderStateProvider.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderStateProvider.kt index 56584c1935..d5f98f5716 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderStateProvider.kt @@ -14,21 +14,21 @@ * 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 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 -open class AccountProviderStateProvider : PreviewParameterProvider { - override val values: Sequence +open class ConfirmAccountProviderStateProvider : PreviewParameterProvider { + override val values: Sequence get() = sequenceOf( - anAccountProviderState(), + aConfirmAccountProviderState(), // Add other state here ) } -fun anAccountProviderState() = AccountProviderState( +fun aConfirmAccountProviderState() = ConfirmAccountProviderState( accountProvider = anAccountProvider(), isAccountCreation = false, loginFlow = Async.Uninitialized, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt new file mode 100644 index 0000000000..b16214b34a --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt @@ -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, + ) +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordEvents.kt similarity index 92% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordEvents.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordEvents.kt index 44b442dd08..e6f23ca418 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordEvents.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.login.impl.loginpassword +package io.element.android.features.login.impl.screens.loginpassword sealed interface LoginPasswordEvents { data class SetLogin(val login: String) : LoginPasswordEvents diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordNode.kt similarity index 95% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordNode.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordNode.kt index ea731f379b..630b08570c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordNode.kt @@ -14,7 +14,7 @@ * 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.ui.Modifier diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt similarity index 95% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordPresenter.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt index 96f749a89c..1fc4a10bbb 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt @@ -14,7 +14,7 @@ * 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.MutableState @@ -24,7 +24,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope 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.Presenter import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordState.kt similarity index 90% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordState.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordState.kt index 07bb7d1c51..c8fa2f4ad3 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordState.kt @@ -14,10 +14,10 @@ * 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 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.matrix.api.core.SessionId import kotlinx.parcelize.Parcelize diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordStateProvider.kt similarity index 90% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordStateProvider.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordStateProvider.kt index f53cfb168a..b4f5a84691 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordStateProvider.kt @@ -14,10 +14,10 @@ * 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 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 open class LoginPasswordStateProvider : PreviewParameterProvider { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt similarity index 99% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt index 04db2ea73b..5016a61054 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt @@ -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"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * 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.Column diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderEvents.kt similarity index 77% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormEvents.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderEvents.kt index 79bbc5cc95..9a6a175cab 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderEvents.kt @@ -14,11 +14,11 @@ * 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. */ - data class UserInput(val input: String) : ChangeAccountProviderFormEvents + data class UserInput(val input: String) : SearchAccountProviderEvents } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderNode.kt similarity index 88% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormNode.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderNode.kt index dac0b01f01..7178a105f6 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderNode.kt @@ -14,7 +14,7 @@ * 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.ui.Modifier @@ -30,10 +30,10 @@ import io.element.android.features.login.impl.util.openLearnMorePage import io.element.android.libraries.di.AppScope @ContributesNode(AppScope::class) -class ChangeAccountProviderFormNode @AssistedInject constructor( +class SearchAccountProviderNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, - private val presenter: ChangeAccountProviderFormPresenter, + private val presenter: SearchAccountProviderPresenter, ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { @@ -48,7 +48,7 @@ class ChangeAccountProviderFormNode @AssistedInject constructor( override fun View(modifier: Modifier) { val state = presenter.present() val context = LocalContext.current - ChangeAccountProviderFormView( + SearchAccountProviderView( state = state, modifier = modifier, onBackPressed = ::navigateUp, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenter.kt similarity index 74% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenter.kt index 6c4b07b3ed..1ff510ea94 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenter.kt @@ -14,7 +14,7 @@ * 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.LaunchedEffect @@ -23,18 +23,20 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable 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.Presenter import javax.inject.Inject -class ChangeAccountProviderFormPresenter @Inject constructor( +class SearchAccountProviderPresenter @Inject constructor( private val homeserverResolver: HomeserverResolver, private val changeServerPresenter: ChangeServerPresenter, -) : Presenter { +) : Presenter { @Composable - override fun present(): ChangeAccountProviderFormState { + override fun present(): SearchAccountProviderState { var userInput by rememberSaveable { mutableStateOf("") } @@ -50,15 +52,15 @@ class ChangeAccountProviderFormPresenter @Inject constructor( } } - fun handleEvents(event: ChangeAccountProviderFormEvents) { + fun handleEvents(event: SearchAccountProviderEvents) { when (event) { - is ChangeAccountProviderFormEvents.UserInput -> { + is SearchAccountProviderEvents.UserInput -> { userInput = event.input } } } - return ChangeAccountProviderFormState( + return SearchAccountProviderState( userInput = userInput, userInputResult = data, changeServerState = changeServerState, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderState.kt similarity index 72% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormState.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderState.kt index f7b841bed3..c89dc53aff 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderState.kt @@ -14,15 +14,16 @@ * 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 // Do not use default value, so no member get forgotten in the presenters. -data class ChangeAccountProviderFormState( +data class SearchAccountProviderState( val userInput: String, val userInputResult: Async>, val changeServerState: ChangeServerState, - val eventSink: (ChangeAccountProviderFormEvents) -> Unit + val eventSink: (SearchAccountProviderEvents) -> Unit ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderStateProvider.kt similarity index 74% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormStateProvider.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderStateProvider.kt index 5c1b4a092b..9c15a63802 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderStateProvider.kt @@ -14,25 +14,26 @@ * 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 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 -open class ChangeAccountProviderFormStateProvider : PreviewParameterProvider { - override val values: Sequence +open class SearchAccountProviderStateProvider : PreviewParameterProvider { + override val values: Sequence get() = sequenceOf( - aChangeAccountProviderFormState(), - aChangeAccountProviderFormState(userInputResult = Async.Success(aHomeserverDataList())), + aSearchAccountProviderState(), + aSearchAccountProviderState(userInputResult = Async.Success(aHomeserverDataList())), // Add other state here ) } -fun aChangeAccountProviderFormState( +fun aSearchAccountProviderState( userInput: String = "", userInputResult: Async> = Async.Uninitialized, -) = ChangeAccountProviderFormState( +) = SearchAccountProviderState( userInput = userInput, userInputResult = userInputResult, changeServerState = aChangeServerState(), diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt similarity index 88% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt index 8ab0659d7b..cc4d326d64 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt @@ -16,7 +16,7 @@ @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.Column @@ -50,10 +50,11 @@ 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.accountprovider.item.AccountProvider -import io.element.android.features.login.impl.accountprovider.item.AccountProviderView -import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerEvents -import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerView +import io.element.android.features.login.impl.accountprovider.AccountProvider +import io.element.android.features.login.impl.accountprovider.AccountProviderView +import io.element.android.features.login.impl.changeserver.ChangeServerEvents +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.designsystem.atomic.molecules.IconTitleSubtitleMolecule 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 */ @Composable -fun ChangeAccountProviderFormView( - state: ChangeAccountProviderFormState, +fun SearchAccountProviderView( + state: SearchAccountProviderState, modifier: Modifier = Modifier, onBackPressed: () -> Unit = {}, onLearnMoreClicked: () -> Unit = {}, @@ -123,7 +124,7 @@ fun ChangeAccountProviderFormView( .testTag(TestTags.changeServerServer), onValueChange = { userInputState = it - eventSink(ChangeAccountProviderFormEvents.UserInput(it)) + eventSink(SearchAccountProviderEvents.UserInput(it)) }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Uri, @@ -135,7 +136,7 @@ fun ChangeAccountProviderFormView( { IconButton(onClick = { userInputState = "" - eventSink(ChangeAccountProviderFormEvents.UserInput("")) + eventSink(SearchAccountProviderEvents.UserInput("")) }) { Icon( imageVector = Icons.Filled.Close, @@ -202,17 +203,17 @@ private fun HomeserverData.toAccountProvider(): AccountProvider { @Preview @Composable -fun ChangeAccountProviderFormViewLightPreview(@PreviewParameter(ChangeAccountProviderFormStateProvider::class) state: ChangeAccountProviderFormState) = +fun SearchAccountProviderViewLightPreview(@PreviewParameter(SearchAccountProviderStateProvider::class) state: SearchAccountProviderState) = ElementPreviewLight { ContentToPreview(state) } @Preview @Composable -fun ChangeAccountProviderFormViewDarkPreview(@PreviewParameter(ChangeAccountProviderFormStateProvider::class) state: ChangeAccountProviderFormState) = +fun SearchAccountProviderViewDarkPreview(@PreviewParameter(SearchAccountProviderStateProvider::class) state: SearchAccountProviderState) = ElementPreviewDark { ContentToPreview(state) } @Composable -private fun ContentToPreview(state: ChangeAccountProviderFormState) { - ChangeAccountProviderFormView( +private fun ContentToPreview(state: SearchAccountProviderState) { + SearchAccountProviderView( state = state, ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/LoginConstants.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/LoginConstants.kt index 10f69c3d1a..e8bcea990e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/LoginConstants.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/LoginConstants.kt @@ -16,7 +16,7 @@ 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 { const val MATRIX_ORG_URL = "matrix.org" diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt similarity index 93% rename from features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerPresenterTest.kt rename to features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt index cc668f8217..9aefafb382 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/common/ChangeServerPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt @@ -14,14 +14,14 @@ * 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.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.features.login.impl.accountprovider.item.AccountProvider -import io.element.android.features.login.impl.datasource.AccountProviderDataSource +import io.element.android.features.login.impl.accountprovider.AccountProvider +import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource 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_URL diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/FakeWellknownRequest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/FakeWellknownRequest.kt similarity index 78% rename from features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/FakeWellknownRequest.kt rename to features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/FakeWellknownRequest.kt index 0cc4a8435d..4dea24b1d7 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/FakeWellknownRequest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/FakeWellknownRequest.kt @@ -14,10 +14,7 @@ * limitations under the License. */ -package io.element.android.features.login.impl.changeaccountprovider.form - -import io.element.android.features.login.impl.changeaccountprovider.form.network.WellKnown -import io.element.android.features.login.impl.changeaccountprovider.form.network.WellknownRequest +package io.element.android.features.login.impl.changeserver.resolver.network class FakeWellknownRequest : WellknownRequest { private var resultMap: Map = emptyMap() diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenterTest.kt similarity index 84% rename from features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenterTest.kt rename to features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenterTest.kt index e6f4a84471..086428257a 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenterTest.kt @@ -14,15 +14,15 @@ * 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.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.features.login.impl.accountprovider.item.AccountProvider -import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerPresenter -import io.element.android.features.login.impl.datasource.AccountProviderDataSource +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.changeserver.ChangeServerPresenter import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt similarity index 80% rename from features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenterTest.kt rename to features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt index 0e34c623ae..131d0d9298 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt @@ -14,13 +14,13 @@ * 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.moleculeFlow import app.cash.turbine.test 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.libraries.architecture.Async 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 org.junit.Test -class AccountProviderPresenterTest { +class ConfirmAccountProviderPresenterTest { @Test fun `present - initial test`() = runTest { - val presenter = AccountProviderPresenter( - AccountProviderPresenterParams(isAccountCreation = false), + val presenter = ConfirmAccountProviderPresenter( + ConfirmAccountProviderPresenter.Params(isAccountCreation = false), AccountProviderDataSource(), FakeAuthenticationService(), ) @@ -52,8 +52,8 @@ class AccountProviderPresenterTest { @Test fun `present - continue password login`() = runTest { val authServer = FakeAuthenticationService() - val presenter = AccountProviderPresenter( - AccountProviderPresenterParams(isAccountCreation = false), + val presenter = ConfirmAccountProviderPresenter( + ConfirmAccountProviderPresenter.Params(isAccountCreation = false), AccountProviderDataSource(), authServer, ) @@ -62,7 +62,7 @@ class AccountProviderPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink.invoke(AccountProviderEvents.Continue) + initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue) val loadingState = awaitItem() assertThat(loadingState.submitEnabled).isTrue() assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java) @@ -76,8 +76,8 @@ class AccountProviderPresenterTest { @Test fun `present - continue oidc`() = runTest { val authServer = FakeAuthenticationService() - val presenter = AccountProviderPresenter( - AccountProviderPresenterParams(isAccountCreation = false), + val presenter = ConfirmAccountProviderPresenter( + ConfirmAccountProviderPresenter.Params(isAccountCreation = false), AccountProviderDataSource(), authServer, ) @@ -86,7 +86,7 @@ class AccountProviderPresenterTest { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink.invoke(AccountProviderEvents.Continue) + initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue) val loadingState = awaitItem() assertThat(loadingState.submitEnabled).isTrue() assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java) @@ -100,8 +100,8 @@ class AccountProviderPresenterTest { @Test fun `present - submit fails`() = runTest { val authServer = FakeAuthenticationService() - val presenter = AccountProviderPresenter( - AccountProviderPresenterParams(isAccountCreation = false), + val presenter = ConfirmAccountProviderPresenter( + ConfirmAccountProviderPresenter.Params(isAccountCreation = false), AccountProviderDataSource(), authServer, ) @@ -110,7 +110,7 @@ class AccountProviderPresenterTest { }.test { val initialState = awaitItem() authServer.givenChangeServerError(Throwable()) - initialState.eventSink.invoke(AccountProviderEvents.Continue) + initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue) skipItems(1) // Loading val failureState = awaitItem() assertThat(failureState.submitEnabled).isFalse() @@ -121,8 +121,8 @@ class AccountProviderPresenterTest { @Test fun `present - clear error`() = runTest { val authenticationService = FakeAuthenticationService() - val presenter = AccountProviderPresenter( - AccountProviderPresenterParams(isAccountCreation = false), + val presenter = ConfirmAccountProviderPresenter( + ConfirmAccountProviderPresenter.Params(isAccountCreation = false), AccountProviderDataSource(), authenticationService, ) @@ -133,7 +133,7 @@ class AccountProviderPresenterTest { // Submit will return an error authenticationService.givenChangeServerError(A_THROWABLE) - initialState.eventSink(AccountProviderEvents.Continue) + initialState.eventSink(ConfirmAccountProviderEvents.Continue) skipItems(1) // Loading @@ -142,7 +142,7 @@ class AccountProviderPresenterTest { assertThat(submittedState.loginFlow).isInstanceOf(Async.Failure::class.java) // Assert the error is then cleared - submittedState.eventSink(AccountProviderEvents.ClearError) + submittedState.eventSink(ConfirmAccountProviderEvents.ClearError) val clearedState = awaitItem() assertThat(clearedState.loginFlow).isEqualTo(Async.Uninitialized) } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt similarity index 97% rename from features/login/impl/src/test/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordPresenterTest.kt rename to features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt index 996f0b88bf..c4c8a97155 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/loginpassword/LoginPasswordPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt @@ -14,13 +14,13 @@ * 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.moleculeFlow import app.cash.turbine.test 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.libraries.architecture.Async import io.element.android.libraries.matrix.api.core.SessionId diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt similarity index 78% rename from features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenterTest.kt rename to features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt index dff15a63c6..1337e0f228 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeaccountprovider/form/ChangeAccountProviderFormPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt @@ -14,17 +14,19 @@ * 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.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerPresenter -import io.element.android.features.login.impl.changeaccountprovider.form.network.WellKnown -import io.element.android.features.login.impl.changeaccountprovider.form.network.WellKnownBaseConfig -import io.element.android.features.login.impl.changeaccountprovider.form.network.WellKnownSlidingSyncConfig -import io.element.android.features.login.impl.datasource.AccountProviderDataSource +import io.element.android.features.login.impl.changeserver.ChangeServerPresenter +import io.element.android.features.login.impl.changeserver.resolver.network.WellKnown +import io.element.android.features.login.impl.changeserver.resolver.network.WellKnownBaseConfig +import io.element.android.features.login.impl.changeserver.resolver.network.WellKnownSlidingSyncConfig +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.matrix.test.A_HOMESERVER_URL 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 org.junit.Test -class ChangeAccountProviderFormPresenterTest { +class SearchAccountProviderPresenterTest { @Test fun `present - initial state`() = runTest { val fakeWellknownRequest = FakeWellknownRequest() @@ -40,8 +42,8 @@ class ChangeAccountProviderFormPresenterTest { FakeAuthenticationService(), AccountProviderDataSource() ) - val presenter = ChangeAccountProviderFormPresenter( - DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), + val presenter = SearchAccountProviderPresenter( + HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), changeServerPresenter ) moleculeFlow(RecompositionClock.Immediate) { @@ -60,15 +62,15 @@ class ChangeAccountProviderFormPresenterTest { FakeAuthenticationService(), AccountProviderDataSource() ) - val presenter = ChangeAccountProviderFormPresenter( - DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), + val presenter = SearchAccountProviderPresenter( + HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), changeServerPresenter ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink.invoke(ChangeAccountProviderFormEvents.UserInput("test")) + initialState.eventSink.invoke(SearchAccountProviderEvents.UserInput("test")) val withInputState = awaitItem() assertThat(withInputState.userInput).isEqualTo("test") assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized) @@ -84,15 +86,15 @@ class ChangeAccountProviderFormPresenterTest { FakeAuthenticationService(), AccountProviderDataSource() ) - val presenter = ChangeAccountProviderFormPresenter( - DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), + val presenter = SearchAccountProviderPresenter( + HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), changeServerPresenter ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink.invoke(ChangeAccountProviderFormEvents.UserInput("https://test.org")) + initialState.eventSink.invoke(SearchAccountProviderEvents.UserInput("https://test.org")) val withInputState = awaitItem() assertThat(withInputState.userInput).isEqualTo("https://test.org") assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized) @@ -119,15 +121,15 @@ class ChangeAccountProviderFormPresenterTest { FakeAuthenticationService(), AccountProviderDataSource() ) - val presenter = ChangeAccountProviderFormPresenter( - DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), + val presenter = SearchAccountProviderPresenter( + HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), changeServerPresenter ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink.invoke(ChangeAccountProviderFormEvents.UserInput("test")) + initialState.eventSink.invoke(SearchAccountProviderEvents.UserInput("test")) val withInputState = awaitItem() assertThat(withInputState.userInput).isEqualTo("test") assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized) @@ -154,15 +156,15 @@ class ChangeAccountProviderFormPresenterTest { FakeAuthenticationService(), AccountProviderDataSource() ) - val presenter = ChangeAccountProviderFormPresenter( - DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), + val presenter = SearchAccountProviderPresenter( + HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), changeServerPresenter ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { val initialState = awaitItem() - initialState.eventSink.invoke(ChangeAccountProviderFormEvents.UserInput("test")) + initialState.eventSink.invoke(SearchAccountProviderEvents.UserInput("test")) val withInputState = awaitItem() assertThat(withInputState.userInput).isEqualTo("test") assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized) diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/LoginScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/LoginScreen.kt index 976a68f81c..54bef4652b 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/LoginScreen.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/LoginScreen.kt @@ -20,9 +20,9 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import io.element.android.features.login.impl.datasource.AccountProviderDataSource -import io.element.android.features.login.impl.loginpassword.LoginPasswordPresenter -import io.element.android.features.login.impl.loginpassword.LoginPasswordView +import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource +import io.element.android.features.login.impl.screens.loginpassword.LoginPasswordPresenter +import io.element.android.features.login.impl.screens.loginpassword.LoginPasswordView import io.element.android.features.login.impl.util.defaultAccountProvider import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService From 9fbc918f10ee1ae503ba46d2c2bf24fd1ab471f3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 17:43:34 +0200 Subject: [PATCH 38/61] Enforce lambda parameters to be passed in. --- .../login/impl/changeserver/ChangeServerView.kt | 6 ++++-- .../ChangeAccountProviderView.kt | 13 ++++++++----- .../ConfirmAccountProviderView.kt | 12 ++++++++---- .../SearchAccountProviderView.kt | 9 ++++++--- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt index 37d90f581c..136a4a7d5b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt @@ -31,9 +31,9 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight @Composable fun ChangeServerView( state: ChangeServerState, + onLearnMoreClicked: () -> Unit, + onDone: () -> Unit, modifier: Modifier = Modifier, - onLearnMoreClicked: () -> Unit = {}, - onDone: () -> Unit = {}, ) { val eventSink = state.eventSink when (state.changeServerAction) { @@ -80,5 +80,7 @@ fun ChangeServerViewDarkPreview(@PreviewParameter(ChangeServerStateProvider::cla private fun ContentToPreview(state: ChangeServerState) { ChangeServerView( state = state, + onLearnMoreClicked = {}, + onDone = {}, ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderView.kt index d7ffbe3d8d..eb8ee11a1c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderView.kt @@ -57,11 +57,11 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar @Composable fun ChangeAccountProviderView( state: ChangeAccountProviderState, + onBackPressed: () -> Unit, + onLearnMoreClicked: () -> Unit, + onDone: () -> Unit, + onOtherProviderClicked: () -> Unit, modifier: Modifier = Modifier, - onBackPressed: () -> Unit = {}, - onLearnMoreClicked: () -> Unit = {}, - onDone: () -> Unit = {}, - onOtherProviderClicked: () -> Unit = {}, ) { val scrollState = rememberScrollState() @@ -143,6 +143,9 @@ fun ChangeAccountProviderViewDarkPreview(@PreviewParameter(ChangeAccountProvider private fun ContentToPreview(state: ChangeAccountProviderState) { ChangeAccountProviderView( state = state, - onBackPressed = { } + onBackPressed = { }, + onLearnMoreClicked = { }, + onDone = { }, + onOtherProviderClicked = { }, ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt index b16214b34a..91b79bca77 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt @@ -49,11 +49,11 @@ import io.element.android.libraries.testtags.testTag @Composable fun ConfirmAccountProviderView( state: ConfirmAccountProviderState, + onOidcDetails: (OidcDetails) -> Unit, + onLoginPasswordNeeded: () -> Unit, + onLearnMoreClicked: () -> Unit, + onChange: () -> Unit, modifier: Modifier = Modifier, - onOidcDetails: (OidcDetails) -> Unit = {}, - onLoginPasswordNeeded: () -> Unit = {}, - onLearnMoreClicked: () -> Unit = {}, - onChange: () -> Unit = {}, ) { val isLoading by remember(state.loginFlow) { derivedStateOf { @@ -158,5 +158,9 @@ fun ConfirmAccountProviderViewDarkPreview(@PreviewParameter(ConfirmAccountProvid private fun ContentToPreview(state: ConfirmAccountProviderState) { ConfirmAccountProviderView( state = state, + onOidcDetails = {}, + onLoginPasswordNeeded = {}, + onLearnMoreClicked = {}, + onChange = {}, ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt index cc4d326d64..f5b178ce27 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt @@ -75,10 +75,10 @@ import io.element.android.libraries.testtags.testTag @Composable fun SearchAccountProviderView( state: SearchAccountProviderState, + onBackPressed: () -> Unit, + onLearnMoreClicked: () -> Unit, + onDone: () -> Unit, modifier: Modifier = Modifier, - onBackPressed: () -> Unit = {}, - onLearnMoreClicked: () -> Unit = {}, - onDone: () -> Unit = {}, ) { val eventSink = state.eventSink val scrollState = rememberScrollState() @@ -215,5 +215,8 @@ fun SearchAccountProviderViewDarkPreview(@PreviewParameter(SearchAccountProvider private fun ContentToPreview(state: SearchAccountProviderState) { SearchAccountProviderView( state = state, + onBackPressed = {}, + onLearnMoreClicked = {}, + onDone = {}, ) } From 0dd4109d21b5042c2062cdcd06fe4b23d6ed1725 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 17:45:51 +0200 Subject: [PATCH 39/61] Use function ref. --- .../ConfirmAccountProviderView.kt | 4 +--- .../features/onboarding/impl/OnBoardingView.kt | 12 +++--------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt index 91b79bca77..9305bccd12 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt @@ -98,9 +98,7 @@ fun ConfirmAccountProviderView( .testTag(TestTags.loginContinue) ) TextButton( - onClick = { - onChange() - }, + onClick = onChange, enabled = true, modifier = Modifier .fillMaxWidth() diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingView.kt b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingView.kt index 6f071fd090..643ab2bfd8 100644 --- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingView.kt +++ b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingView.kt @@ -120,9 +120,7 @@ private fun OnBoardingButtons( ButtonColumnMolecule(modifier = modifier) { if (state.canLoginWithQrCode) { Button( - onClick = { - onSignInWithQrCode() - }, + onClick = onSignInWithQrCode, enabled = true, modifier = Modifier .fillMaxWidth() @@ -136,9 +134,7 @@ private fun OnBoardingButtons( } } Button( - onClick = { - onSignIn() - }, + onClick = onSignIn, enabled = true, modifier = Modifier .fillMaxWidth() @@ -148,9 +144,7 @@ private fun OnBoardingButtons( } if (state.canCreateAccount) { OutlinedButton( - onClick = { - onCreateAccount() - }, + onClick = onCreateAccount, enabled = true, modifier = Modifier .fillMaxWidth() From 8b177645a90a17be33894edbecab0bd2b5946f5e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 17:51:42 +0200 Subject: [PATCH 40/61] Rename `InlineErrorMessage` to simple `Error` to not consider how the error will be used (actually used in a dialog now) --- .../features/login/impl/changeserver/ChangeServerView.kt | 2 +- .../android/features/login/impl/error/ChangeServerError.kt | 4 ++-- .../confirmaccountprovider/ConfirmAccountProviderView.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt index 136a4a7d5b..d2e1a03cf5 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt @@ -39,7 +39,7 @@ fun ChangeServerView( when (state.changeServerAction) { is Async.Failure -> { when (val error = state.changeServerAction.error) { - is ChangeServerError.InlineErrorMessage -> { + is ChangeServerError.Error -> { ErrorDialog( modifier = modifier, content = error.message(), diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt index 9976089f52..444ea3d3f2 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt @@ -23,7 +23,7 @@ import io.element.android.features.login.impl.R import io.element.android.libraries.matrix.api.auth.AuthenticationException sealed class ChangeServerError : Throwable() { - data class InlineErrorMessage(@StringRes val messageId: Int) : ChangeServerError() { + data class Error(@StringRes val messageId: Int) : ChangeServerError() { @Composable fun message(): String = stringResource(messageId) } @@ -32,7 +32,7 @@ sealed class ChangeServerError : Throwable() { companion object { fun from(error: Throwable): ChangeServerError = when (error) { is AuthenticationException.SlidingSyncNotAvailable -> SlidingSyncAlert - else -> InlineErrorMessage(R.string.screen_change_server_error_invalid_homeserver) + else -> Error(R.string.screen_change_server_error_invalid_homeserver) } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt index 9305bccd12..c9053d67c9 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt @@ -112,7 +112,7 @@ fun ConfirmAccountProviderView( when (state.loginFlow) { is Async.Failure -> { when (val error = state.loginFlow.error) { - is ChangeServerError.InlineErrorMessage -> { + is ChangeServerError.Error -> { ErrorDialog( content = error.message(), onDismiss = { From b98010d74de0470dc08e5ceb9fc13b858b07f220 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 17:55:09 +0200 Subject: [PATCH 41/61] Merge the 2 map. --- .../ConfirmAccountProviderPresenter.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt index 446d6611d0..2626e56365 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt @@ -85,12 +85,11 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor( suspend { val domain = tryOrNull { URL(homeserverUrl) }?.host ?: homeserverUrl authenticationService.setHomeserver(domain).map { - authenticationService.getHomeserverDetails().value!! - }.map { - if (it.supportsOidcLogin) { + val matrixHomeServerDetails = authenticationService.getHomeserverDetails().value!! + if (matrixHomeServerDetails.supportsOidcLogin) { // Retrieve the details right now LoginFlow.OidcFlow(authenticationService.getOidcUrl().getOrThrow()) - } else if (it.supportsPasswordLogin) { + } else if (matrixHomeServerDetails.supportsPasswordLogin) { LoginFlow.PasswordLogin } else { throw IllegalStateException("Unsupported login flow") From 1e3727bb3983546286adf06f8579d1289a1e22b7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 17:58:36 +0200 Subject: [PATCH 42/61] Use LaunchedEffect to avoid multiple calls. --- .../features/login/impl/changeserver/ChangeServerView.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt index d2e1a03cf5..f9e9624503 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt @@ -17,6 +17,7 @@ package io.element.android.features.login.impl.changeserver import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -61,7 +62,9 @@ fun ChangeServerView( } } is Async.Loading -> ProgressDialog() - is Async.Success -> onDone() + is Async.Success -> LaunchedEffect(state.changeServerAction) { + onDone() + } Async.Uninitialized -> Unit } } From 0c199a012b5526c47f348750226301b7e09bc984 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 18:05:08 +0200 Subject: [PATCH 43/61] Move the resolver to the parent package. --- .../{changeserver => }/resolver/HomeserverData.kt | 2 +- .../resolver/HomeserverResolver.kt | 4 ++-- .../resolver/network/DefaultWellknownRequest.kt | 2 +- .../{changeserver => }/resolver/network/WellKnown.kt | 2 +- .../resolver/network/WellKnownBaseConfig.kt | 2 +- .../resolver/network/WellKnownSlidingSyncConfig.kt | 2 +- .../resolver/network/WellknownAPI.kt | 2 +- .../resolver/network/WellknownRequest.kt | 2 +- .../SearchAccountProviderPresenter.kt | 4 ++-- .../SearchAccountProviderState.kt | 2 +- .../SearchAccountProviderStateProvider.kt | 2 +- .../SearchAccountProviderView.kt | 2 +- .../resolver/network/FakeWellknownRequest.kt | 2 +- .../SearchAccountProviderPresenterTest.kt | 12 ++++++------ 14 files changed, 21 insertions(+), 21 deletions(-) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeserver => }/resolver/HomeserverData.kt (93%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeserver => }/resolver/HomeserverResolver.kt (96%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeserver => }/resolver/network/DefaultWellknownRequest.kt (94%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeserver => }/resolver/network/WellKnown.kt (95%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeserver => }/resolver/network/WellKnownBaseConfig.kt (92%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeserver => }/resolver/network/WellKnownSlidingSyncConfig.kt (91%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeserver => }/resolver/network/WellknownAPI.kt (90%) rename features/login/impl/src/main/kotlin/io/element/android/features/login/impl/{changeserver => }/resolver/network/WellknownRequest.kt (91%) rename features/login/impl/src/test/kotlin/io/element/android/features/login/impl/{changeserver => }/resolver/network/FakeWellknownRequest.kt (92%) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/HomeserverData.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverData.kt similarity index 93% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/HomeserverData.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverData.kt index 04bcd0099d..c1f0158605 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/HomeserverData.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverData.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.login.impl.changeserver.resolver +package io.element.android.features.login.impl.resolver data class HomeserverData constructor( // The computed homeserver url, for which a wellknown file has been retrieved, or just a valid Url diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/HomeserverResolver.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt similarity index 96% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/HomeserverResolver.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt index 9409f285e5..8a1a72670c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/HomeserverResolver.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package io.element.android.features.login.impl.changeserver.resolver +package io.element.android.features.login.impl.resolver -import io.element.android.features.login.impl.changeserver.resolver.network.WellknownRequest +import io.element.android.features.login.impl.resolver.network.WellknownRequest import io.element.android.libraries.architecture.Async import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.coroutine.CoroutineDispatchers diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/DefaultWellknownRequest.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/DefaultWellknownRequest.kt similarity index 94% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/DefaultWellknownRequest.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/DefaultWellknownRequest.kt index 95960971c3..7de6d26f10 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/DefaultWellknownRequest.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/DefaultWellknownRequest.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.element.android.features.login.impl.changeserver.resolver.network +package io.element.android.features.login.impl.resolver.network import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.AppScope diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellKnown.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnown.kt similarity index 95% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellKnown.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnown.kt index af5a4f17f3..63b6e7d189 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellKnown.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnown.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.login.impl.changeserver.resolver.network +package io.element.android.features.login.impl.resolver.network import io.element.android.libraries.core.bool.orFalse import kotlinx.serialization.SerialName diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellKnownBaseConfig.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnownBaseConfig.kt similarity index 92% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellKnownBaseConfig.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnownBaseConfig.kt index 3bac743d64..87b86736fa 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellKnownBaseConfig.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnownBaseConfig.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.login.impl.changeserver.resolver.network +package io.element.android.features.login.impl.resolver.network import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellKnownSlidingSyncConfig.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnownSlidingSyncConfig.kt similarity index 91% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellKnownSlidingSyncConfig.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnownSlidingSyncConfig.kt index 863b524f84..98c712d9ac 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellKnownSlidingSyncConfig.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellKnownSlidingSyncConfig.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.login.impl.changeserver.resolver.network +package io.element.android.features.login.impl.resolver.network import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellknownAPI.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellknownAPI.kt similarity index 90% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellknownAPI.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellknownAPI.kt index f4d4ba7c67..04a0dfb803 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellknownAPI.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellknownAPI.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.login.impl.changeserver.resolver.network +package io.element.android.features.login.impl.resolver.network import retrofit2.http.GET diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellknownRequest.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellknownRequest.kt similarity index 91% rename from features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellknownRequest.kt rename to features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellknownRequest.kt index 54c393e522..570b621b83 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/WellknownRequest.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellknownRequest.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.element.android.features.login.impl.changeserver.resolver.network +package io.element.android.features.login.impl.resolver.network interface WellknownRequest { /** diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenter.kt index 1ff510ea94..f95c027620 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenter.kt @@ -24,8 +24,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue 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.features.login.impl.resolver.HomeserverData +import io.element.android.features.login.impl.resolver.HomeserverResolver import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import javax.inject.Inject diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderState.kt index c89dc53aff..15859afde1 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderState.kt @@ -17,7 +17,7 @@ package io.element.android.features.login.impl.screens.searchaccountprovider import io.element.android.features.login.impl.changeserver.ChangeServerState -import io.element.android.features.login.impl.changeserver.resolver.HomeserverData +import io.element.android.features.login.impl.resolver.HomeserverData import io.element.android.libraries.architecture.Async // Do not use default value, so no member get forgotten in the presenters. diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderStateProvider.kt index 9c15a63802..b6ffac8bd1 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderStateProvider.kt @@ -18,7 +18,7 @@ package io.element.android.features.login.impl.screens.searchaccountprovider import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.login.impl.changeserver.aChangeServerState -import io.element.android.features.login.impl.changeserver.resolver.HomeserverData +import io.element.android.features.login.impl.resolver.HomeserverData import io.element.android.libraries.architecture.Async open class SearchAccountProviderStateProvider : PreviewParameterProvider { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt index f5b178ce27..605992d930 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt @@ -54,7 +54,7 @@ import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProviderView import io.element.android.features.login.impl.changeserver.ChangeServerEvents import io.element.android.features.login.impl.changeserver.ChangeServerView -import io.element.android.features.login.impl.changeserver.resolver.HomeserverData +import io.element.android.features.login.impl.resolver.HomeserverData import io.element.android.libraries.architecture.Async import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.components.button.BackButton diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/FakeWellknownRequest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/resolver/network/FakeWellknownRequest.kt similarity index 92% rename from features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/FakeWellknownRequest.kt rename to features/login/impl/src/test/kotlin/io/element/android/features/login/impl/resolver/network/FakeWellknownRequest.kt index 4dea24b1d7..58c2bf82a3 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/resolver/network/FakeWellknownRequest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/resolver/network/FakeWellknownRequest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.login.impl.changeserver.resolver.network +package io.element.android.features.login.impl.resolver.network class FakeWellknownRequest : WellknownRequest { private var resultMap: Map = emptyMap() diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt index 1337e0f228..9163f247f5 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt @@ -20,13 +20,13 @@ import app.cash.molecule.RecompositionClock import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.features.login.impl.changeserver.ChangeServerPresenter -import io.element.android.features.login.impl.changeserver.resolver.network.WellKnown -import io.element.android.features.login.impl.changeserver.resolver.network.WellKnownBaseConfig -import io.element.android.features.login.impl.changeserver.resolver.network.WellKnownSlidingSyncConfig -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.features.login.impl.changeserver.ChangeServerPresenter +import io.element.android.features.login.impl.resolver.HomeserverResolver +import io.element.android.features.login.impl.resolver.network.FakeWellknownRequest +import io.element.android.features.login.impl.resolver.network.WellKnown +import io.element.android.features.login.impl.resolver.network.WellKnownBaseConfig +import io.element.android.features.login.impl.resolver.network.WellKnownSlidingSyncConfig import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService From 4fa30f384d7b045a0edd29ec3a1485ca2eaf612a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 18:06:29 +0200 Subject: [PATCH 44/61] Better comment --- .../searchaccountprovider/SearchAccountProviderEvents.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderEvents.kt index 9a6a175cab..53ee45f644 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderEvents.kt @@ -18,7 +18,8 @@ package io.element.android.features.login.impl.screens.searchaccountprovider 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 matching account provider results + * in the state. */ data class UserInput(val input: String) : SearchAccountProviderEvents } From f8a16f2ea3f7a2cf791ec52cb9964e906f99c85a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 18:09:09 +0200 Subject: [PATCH 45/61] Inline the scroll state. --- .../changeaccountprovider/ChangeAccountProviderView.kt | 6 +----- .../searchaccountprovider/SearchAccountProviderView.kt | 6 +----- .../features/rageshake/impl/bugreport/BugReportView.kt | 5 +---- .../designsystem/components/preferences/PreferenceScreen.kt | 5 +---- 4 files changed, 4 insertions(+), 18 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderView.kt index eb8ee11a1c..0f444350c9 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderView.kt @@ -63,8 +63,6 @@ fun ChangeAccountProviderView( onOtherProviderClicked: () -> Unit, modifier: Modifier = Modifier, ) { - val scrollState = rememberScrollState() - Scaffold( modifier = modifier, topBar = { @@ -83,9 +81,7 @@ fun ChangeAccountProviderView( ) { Column( modifier = Modifier - .verticalScroll( - state = scrollState, - ) + .verticalScroll(state = rememberScrollState()) ) { IconTitleSubtitleMolecule( modifier = Modifier.padding(top = 16.dp, bottom = 32.dp, start = 16.dp, end = 16.dp), diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt index 605992d930..3ee9f115d7 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt @@ -81,8 +81,6 @@ fun SearchAccountProviderView( modifier: Modifier = Modifier, ) { val eventSink = state.eventSink - val scrollState = rememberScrollState() - Scaffold( modifier = modifier, topBar = { @@ -101,9 +99,7 @@ fun SearchAccountProviderView( ) { Column( modifier = Modifier - .verticalScroll( - state = scrollState, - ) + .verticalScroll(state = rememberScrollState()) ) { IconTitleSubtitleMolecule( modifier = Modifier.padding(top = 16.dp, bottom = 40.dp, start = 16.dp, end = 16.dp), diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt index 05f3232aac..0e0ebaaab2 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt @@ -81,12 +81,9 @@ fun BugReportView( .systemBarsPadding() .imePadding() ) { - val scrollState = rememberScrollState() Column( modifier = Modifier - .verticalScroll( - state = scrollState, - ) + .verticalScroll(state = rememberScrollState()) .padding(horizontal = 16.dp), ) { val isError = state.sending is Async.Failure diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceScreen.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceScreen.kt index eacdf82fb7..65cd428729 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceScreen.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceScreen.kt @@ -68,14 +68,11 @@ fun PreferenceView( ) }, content = { - val scrollState = rememberScrollState() Column( modifier = Modifier .padding(it) .consumeWindowInsets(it) - .verticalScroll( - state = scrollState, - ) + .verticalScroll(state = rememberScrollState()) ) { content() } From 03273985b2984f42d9e94de2ed96a5ef3e67e08e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 18:10:59 +0200 Subject: [PATCH 46/61] Use the search icon on this screen. --- .../searchaccountprovider/SearchAccountProviderView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt index 3ee9f115d7..c9d769c1e5 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt @@ -33,7 +33,7 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close -import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Search import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold @@ -103,7 +103,7 @@ fun SearchAccountProviderView( ) { IconTitleSubtitleMolecule( modifier = Modifier.padding(top = 16.dp, bottom = 40.dp, start = 16.dp, end = 16.dp), - iconImageVector = Icons.Filled.Home, + iconImageVector = Icons.Filled.Search, title = stringResource(id = R.string.screen_account_provider_form_title), subTitle = stringResource(id = R.string.screen_account_provider_form_subtitle), ) From 8d50acaff49460f3448a8c4fd6b0c8fd0fa1e3f6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 18:16:23 +0200 Subject: [PATCH 47/61] No need to pass `maxLines = 1` if `singleline = true` anymore. --- .../login/impl/screens/loginpassword/LoginPasswordView.kt | 2 -- .../screens/searchaccountprovider/SearchAccountProviderView.kt | 1 - .../libraries/designsystem/components/LabelledTextField.kt | 2 +- .../designsystem/theme/components/OutlinedTextField.kt | 2 +- .../libraries/designsystem/theme/components/TextField.kt | 2 +- 5 files changed, 3 insertions(+), 6 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt index 5016a61054..9154a0792f 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt @@ -208,7 +208,6 @@ internal fun LoginForm( focusManager.moveFocus(FocusDirection.Down) }), singleLine = true, - maxLines = 1, trailingIcon = if (loginFieldState.isNotEmpty()) { { IconButton(onClick = { @@ -263,7 +262,6 @@ internal fun LoginForm( onDone = { onSubmit() } ), singleLine = true, - maxLines = 1, ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt index c9d769c1e5..f61bc1caf8 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt @@ -127,7 +127,6 @@ fun SearchAccountProviderView( imeAction = ImeAction.Done, ), singleLine = true, - maxLines = 1, trailingIcon = if (userInputState.isNotEmpty()) { { IconButton(onClick = { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledTextField.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledTextField.kt index 0f0ada5497..f7b4d9ab5c 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledTextField.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledTextField.kt @@ -36,8 +36,8 @@ fun LabelledTextField( value: String, modifier: Modifier = Modifier, placeholder: String? = null, - maxLines: Int = Int.MAX_VALUE, singleLine: Boolean = false, + maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, onValueChange: (String) -> Unit = {}, ) { Column( diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/OutlinedTextField.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/OutlinedTextField.kt index e0edeed050..8b8aec7030 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/OutlinedTextField.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/OutlinedTextField.kt @@ -66,7 +66,7 @@ fun OutlinedTextField( keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardActions: KeyboardActions = KeyboardActions.Default, singleLine: Boolean = false, - maxLines: Int = Int.MAX_VALUE, + maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, shape: Shape = OutlinedTextFieldDefaults.shape, colors: TextFieldColors = OutlinedTextFieldDefaults.colors() diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt index 54fe50b8bf..e244639e7a 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt @@ -67,7 +67,7 @@ fun TextField( keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardActions: KeyboardActions = KeyboardActions.Default, singleLine: Boolean = false, - maxLines: Int = Int.MAX_VALUE, + maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, shape: Shape = TextFieldDefaults.shape, colors: TextFieldColors = TextFieldDefaults.colors() From 19e090f6dc04c3bce95a559e18403ad2085b128a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 18:19:00 +0200 Subject: [PATCH 48/61] Always use `import io.element.android.libraries.ui.strings.R as StringR` --- .../android/features/login/impl/error/ErrorFormatter.kt | 6 +++--- .../searchaccountprovider/SearchAccountProviderView.kt | 3 ++- .../roomdetails/impl/invite/RoomInviteMembersView.kt | 2 +- .../libraries/eventformatter/impl/StateContentFormatter.kt | 3 ++- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ErrorFormatter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ErrorFormatter.kt index 86820645ae..ec2dc95386 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ErrorFormatter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ErrorFormatter.kt @@ -21,16 +21,16 @@ import io.element.android.features.login.impl.R import io.element.android.libraries.matrix.api.auth.AuthErrorCode import io.element.android.libraries.matrix.api.auth.AuthenticationException import io.element.android.libraries.matrix.api.auth.errorCode -import io.element.android.libraries.ui.strings.R.string as StringR +import io.element.android.libraries.ui.strings.R as StringR @StringRes fun loginError( throwable: Throwable ): Int { - val authException = throwable as? AuthenticationException ?: return StringR.error_unknown + val authException = throwable as? AuthenticationException ?: return StringR.string.error_unknown return when (authException.errorCode) { AuthErrorCode.FORBIDDEN -> R.string.screen_login_error_invalid_credentials AuthErrorCode.USER_DEACTIVATED -> R.string.screen_login_error_deactivated_account - AuthErrorCode.UNKNOWN -> StringR.error_unknown + AuthErrorCode.UNKNOWN -> StringR.string.error_unknown } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt index f61bc1caf8..26928446d3 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt @@ -68,6 +68,7 @@ import io.element.android.libraries.designsystem.theme.components.OutlinedTextFi import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.testTag +import io.element.android.libraries.ui.strings.R as StringR /** * https://www.figma.com/file/o9p34zmiuEpZRyvZXJZAYL/FTUE?type=design&node-id=611-61435 @@ -135,7 +136,7 @@ fun SearchAccountProviderView( }) { Icon( imageVector = Icons.Filled.Close, - contentDescription = stringResource(io.element.android.libraries.ui.strings.R.string.action_clear) + contentDescription = stringResource(StringR.string.action_clear) ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt index ced5e79bbe..867d2f433d 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt @@ -147,7 +147,7 @@ private fun RoomInviteMembersSearchBar( selectedUsers: ImmutableList, active: Boolean, modifier: Modifier = Modifier, - placeHolderTitle: String = stringResource(io.element.android.libraries.ui.strings.R.string.common_search_for_someone), + placeHolderTitle: String = stringResource(StringR.string.common_search_for_someone), onActiveChanged: (Boolean) -> Unit = {}, onTextChanged: (String) -> Unit = {}, onUserToggled: (MatrixUser) -> Unit = {}, diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/StateContentFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/StateContentFormatter.kt index 169ec55a9d..ccf9c2976a 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/StateContentFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/StateContentFormatter.kt @@ -22,6 +22,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.StateContent import io.element.android.services.toolbox.api.strings.StringProvider import timber.log.Timber import javax.inject.Inject +import io.element.android.libraries.ui.strings.R as StringR class StateContentFormatter @Inject constructor( private val sp: StringProvider, @@ -49,7 +50,7 @@ class StateContentFormatter @Inject constructor( sp.getString(R.string.state_event_room_created, senderDisplayName) } } - is OtherState.RoomEncryption -> sp.getString(io.element.android.libraries.ui.strings.R.string.common_encryption_enabled) + is OtherState.RoomEncryption -> sp.getString(StringR.string.common_encryption_enabled) is OtherState.RoomName -> { val hasRoomName = content.name != null when { From 18f32e51b463f050216f9a4d5a0a7e2ea27f4127 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 18:24:51 +0200 Subject: [PATCH 49/61] Prevent direct usage of `io.element.android.libraries.ui.strings.R` package. --- tools/check/forbidden_strings_in_code.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index ae346c170d..41a6c16466 100755 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -129,3 +129,6 @@ System\.currentTimeMillis\(\)===1 ### Suspicious String template. Please check that the string template will behave as expected, i.e. the class field and not the whole object will be used. For instance `Timber.d("$event.type")` is not correct, you should write `Timber.d("${event.type}")`. In the former the whole event content will be logged, since it's a data class. If this is expected (i.e. to fix false positive), please add explicit curly braces (`{` and `}`) around the variable, for instance `"elementLogs.${i}.txt"` \$[a-zA-Z_]\w*\??\.[a-zA-Z_] + +### Use `import io.element.android.libraries.ui.strings.R as StringsR` then `StringR.string.` instead +io\.element\.android\.libraries\.ui\.strings\.R\. From 6b1809ed3b16035c4421970346eb6b311af1269a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 18:30:44 +0200 Subject: [PATCH 50/61] Use a LazyColumn, in case we get more results in the future. --- .../SearchAccountProviderView.kt | 116 +++++++++--------- 1 file changed, 60 insertions(+), 56 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt index 26928446d3..53061c4b92 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt @@ -19,7 +19,6 @@ package io.element.android.features.login.impl.screens.searchaccountprovider import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets @@ -28,9 +27,10 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Search @@ -98,70 +98,72 @@ fun SearchAccountProviderView( .padding(padding) .consumeWindowInsets(padding) ) { - Column( - modifier = Modifier - .verticalScroll(state = rememberScrollState()) - ) { - IconTitleSubtitleMolecule( - modifier = Modifier.padding(top = 16.dp, bottom = 40.dp, start = 16.dp, end = 16.dp), - iconImageVector = Icons.Filled.Search, - title = stringResource(id = R.string.screen_account_provider_form_title), - subTitle = stringResource(id = R.string.screen_account_provider_form_subtitle), - ) + LazyColumn(modifier = Modifier.fillMaxWidth(), state = rememberLazyListState()) { + item { + IconTitleSubtitleMolecule( + modifier = Modifier.padding(top = 16.dp, bottom = 40.dp, start = 16.dp, end = 16.dp), + iconImageVector = Icons.Filled.Search, + title = stringResource(id = R.string.screen_account_provider_form_title), + subTitle = stringResource(id = R.string.screen_account_provider_form_subtitle), + ) + } + item { + // TextInput + var userInputState by textFieldState(stateValue = state.userInput) - // TextInput - var userInputState by textFieldState(stateValue = state.userInput) - - OutlinedTextField( - value = userInputState, - // readOnly = isLoading, - modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp, end = 16.dp, bottom = 30.dp) - .testTag(TestTags.changeServerServer), - onValueChange = { - userInputState = it - eventSink(SearchAccountProviderEvents.UserInput(it)) - }, - keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Uri, - imeAction = ImeAction.Done, - ), - singleLine = true, - trailingIcon = if (userInputState.isNotEmpty()) { - { - IconButton(onClick = { - userInputState = "" - eventSink(SearchAccountProviderEvents.UserInput("")) - }) { - Icon( - imageVector = Icons.Filled.Close, - contentDescription = stringResource(StringR.string.action_clear) - ) + OutlinedTextField( + value = userInputState, + // readOnly = isLoading, + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, bottom = 30.dp) + .testTag(TestTags.changeServerServer), + onValueChange = { + userInputState = it + eventSink(SearchAccountProviderEvents.UserInput(it)) + }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Uri, + imeAction = ImeAction.Done, + ), + singleLine = true, + trailingIcon = if (userInputState.isNotEmpty()) { + { + IconButton(onClick = { + userInputState = "" + eventSink(SearchAccountProviderEvents.UserInput("")) + }) { + Icon( + imageVector = Icons.Filled.Close, + contentDescription = stringResource(StringR.string.action_clear) + ) + } } + } else null, + supportingText = { + Text(text = stringResource(id = R.string.screen_account_provider_form_notice), color = MaterialTheme.colorScheme.secondary) } - } else null, - supportingText = { - Text(text = stringResource(id = R.string.screen_account_provider_form_notice), color = MaterialTheme.colorScheme.secondary) - } - ) + ) + } when (state.userInputResult) { is Async.Failure -> { // Ignore errors (let the user type more chars) } is Async.Loading -> { - Box( - modifier = Modifier - .fillMaxSize() - ) { - CircularProgressIndicator( - modifier = Modifier.align(Alignment.Center) - ) + item { + Box( + modifier = Modifier + .fillMaxSize() + ) { + CircularProgressIndicator( + modifier = Modifier.align(Alignment.Center) + ) + } } } is Async.Success -> { - state.userInputResult.state.forEach { homeserverData -> + items(state.userInputResult.state) { homeserverData -> val item = homeserverData.toAccountProvider() AccountProviderView( item = item, @@ -173,7 +175,9 @@ fun SearchAccountProviderView( } Async.Uninitialized -> Unit } - Spacer(Modifier.height(32.dp)) + item { + Spacer(Modifier.height(32.dp)) + } } ChangeServerView( state = state.changeServerState, From cdf89e86d695a55c85d8ff5a96fb5b51f1dc03c6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 18:34:00 +0200 Subject: [PATCH 51/61] Add a 5s timeout to avoid infinite loading (actually waiting for network timeout which can be long and is harder to configure). --- .../features/login/impl/resolver/HomeserverResolver.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt index 8a1a72670c..a11b680f63 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt @@ -30,6 +30,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeout import java.util.Collections import javax.inject.Inject @@ -56,7 +57,11 @@ class HomeserverResolver @Inject constructor( withContext(dispatchers.io) { list.map { async { - val wellKnown = tryOrNull { wellknownRequest.execute(it) } + val wellKnown = tryOrNull { + withTimeout(5000) { + wellknownRequest.execute(it) + } + } val isValid = wellKnown?.isValid().orFalse() if (isValid) { val supportSlidingSync = wellKnown?.supportSlidingSync().orFalse() From ddafe50eb51f3be93122f1f76ee69b995850d830 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 18:36:10 +0200 Subject: [PATCH 52/61] Reduce space above the result to give more visibility especially when the keyboard is opened. --- .../screens/searchaccountprovider/SearchAccountProviderView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt index 53061c4b92..9c6d91dfaa 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt @@ -116,7 +116,7 @@ fun SearchAccountProviderView( // readOnly = isLoading, modifier = Modifier .fillMaxWidth() - .padding(start = 16.dp, end = 16.dp, bottom = 30.dp) + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) .testTag(TestTags.changeServerServer), onValueChange = { userInputState = it From 937f4b81eeb0ddb2cf11c0ac897b55e28582fcff Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 18:40:31 +0200 Subject: [PATCH 53/61] Add some generated KDoc. --- .../designsystem/atomic/atoms/RoundedIconAtom.kt | 9 +++++++++ .../atomic/molecules/IconTitleSubtitleMolecule.kt | 9 ++++++++- .../libraries/designsystem/theme/components/Icon.kt | 11 +++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt index b5c98cf483..752c80175d 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt @@ -38,6 +38,15 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.theme.LocalColors import io.element.android.libraries.designsystem.theme.components.Icon +/** + * RoundedIconAtom is an atom which displays an icon inside a rounded container. + * + * @param modifier the modifier to apply to this layout + * @param size the size of the icon + * @param resourceId the resource id of the icon to display, exclusive with [imageVector] + * @param imageVector the image vector of the icon to display, exclusive with [resourceId] + * @param tint the tint to apply to the icon + */ @Composable fun RoundedIconAtom( modifier: Modifier = Modifier, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt index 9d348b706f..24adf90156 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt @@ -38,7 +38,14 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.theme.components.Text /** - * Provide either an `iconResourceId` or an `iconImageVector`. + * IconTitleSubtitleMolecule is a molecule which displays an icon, a title and a subtitle. + * + * @param title the title to display + * @param subTitle the subtitle to display + * @param modifier the modifier to apply to this layout + * @param iconResourceId the resource id of the icon to display, exclusive with [iconImageVector] + * @param iconImageVector the image vector of the icon to display, exclusive with [iconResourceId] + * @param iconTint the tint to apply to the icon */ @Composable fun IconTitleSubtitleMolecule( diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt index 1a9bfe3a0a..24de433058 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt @@ -30,6 +30,17 @@ import androidx.compose.ui.tooling.preview.Preview import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup +/** + * Icon is a wrapper around [androidx.compose.material3.Icon] which allows to use + * [ImageVector], [ImageBitmap] or [DrawableRes] as icon source. + * + * @param contentDescription the content description to be used for accessibility + * @param modifier the modifier to apply to this layout + * @param tint the tint to apply to the icon + * @param imageVector the image vector of the icon to display, exclusive with [bitmap] and [resourceId] + * @param bitmap the bitmap of the icon to display, exclusive with [imageVector] and [resourceId] + * @param resourceId the resource id of the icon to display, exclusive with [imageVector] and [bitmap] + */ @Composable fun Icon( contentDescription: String?, From de3eb23a24903832cde2c47bbcf02245325c3b9a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Jun 2023 18:45:32 +0200 Subject: [PATCH 54/61] Improve accessibility with keyboard. --- .../searchaccountprovider/SearchAccountProviderView.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt index 9c6d91dfaa..5c280cba0e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt @@ -30,6 +30,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close @@ -43,6 +44,8 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusDirection +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType @@ -66,6 +69,7 @@ import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.OutlinedTextField import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.testTag import io.element.android.libraries.ui.strings.R as StringR @@ -110,13 +114,14 @@ fun SearchAccountProviderView( item { // TextInput var userInputState by textFieldState(stateValue = state.userInput) - + val focusManager = LocalFocusManager.current OutlinedTextField( value = userInputState, // readOnly = isLoading, modifier = Modifier .fillMaxWidth() .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .onTabOrEnterKeyFocusNext(focusManager) .testTag(TestTags.changeServerServer), onValueChange = { userInputState = it @@ -126,6 +131,9 @@ fun SearchAccountProviderView( keyboardType = KeyboardType.Uri, imeAction = ImeAction.Done, ), + keyboardActions = KeyboardActions(onDone = { + focusManager.moveFocus(FocusDirection.Down) + }), singleLine = true, trailingIcon = if (userInputState.isNotEmpty()) { { From a75d6c9231f9fef7308d045998af3d33b5e3620a Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 9 Jun 2023 16:55:57 +0000 Subject: [PATCH 55/61] Update screenshots --- ...ccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...ccountProviderViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 3 +++ ...ccountProviderViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png | 3 +++ ...ccountProviderViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png | 3 +++ ...ccountProviderViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png | 3 +++ ...countProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...countProviderViewLightPreview_0_null_1,NEXUS_5,1.0,en].png | 3 +++ ...countProviderViewLightPreview_0_null_2,NEXUS_5,1.0,en].png | 3 +++ ...countProviderViewLightPreview_0_null_3,NEXUS_5,1.0,en].png | 3 +++ ...countProviderViewLightPreview_0_null_4,NEXUS_5,1.0,en].png | 3 +++ ...ntProviderFormViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 3 --- ...ntProviderFormViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 3 --- ...tProviderFormViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 3 --- ...tProviderFormViewLightPreview_0_null_1,NEXUS_5,1.0,en].png | 3 --- ..._ChangeServerViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png} | 0 ...ChangeServerViewLightPreview_0_null_0,NEXUS_5,1.0,en].png} | 0 ...countProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png} | 0 ...ountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png} | 0 ...ccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 3 +++ ...countProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 3 +++ ...LoginPasswordViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png} | 0 ...LoginPasswordViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png} | 0 ...LoginPasswordViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png} | 0 ...oginPasswordViewLightPreview_0_null_0,NEXUS_5,1.0,en].png} | 0 ...oginPasswordViewLightPreview_0_null_1,NEXUS_5,1.0,en].png} | 0 ...oginPasswordViewLightPreview_0_null_2,NEXUS_5,1.0,en].png} | 0 ...ccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 3 +++ ...ccountProviderViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 3 +++ ...countProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png | 3 +++ ...countProviderViewLightPreview_0_null_1,NEXUS_5,1.0,en].png | 3 +++ 30 files changed, 46 insertions(+), 16 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_4,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_0,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_1,NEXUS_5,1.0,en].png rename tests/uitests/src/test/snapshots/images/{io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.common_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png => io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.common_null_DefaultGroup_ChangeServerViewLightPreview_0_null_0,NEXUS_5,1.0,en].png => io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider_null_DefaultGroup_ChangeAccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png => io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.changeaccountprovider_null_DefaultGroup_ChangeAccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider_null_DefaultGroup_ChangeAccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png => io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.changeaccountprovider_null_DefaultGroup_ChangeAccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png} (100%) create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.confirmaccountprovider_null_DefaultGroup_ConfirmAccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.confirmaccountprovider_null_DefaultGroup_ConfirmAccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png rename tests/uitests/src/test/snapshots/images/{io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png => io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png => io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png => io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_0,NEXUS_5,1.0,en].png => io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_1,NEXUS_5,1.0,en].png => io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_2,NEXUS_5,1.0,en].png => io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_2,NEXUS_5,1.0,en].png} (100%) create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.searchaccountprovider_null_DefaultGroup_SearchAccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.searchaccountprovider_null_DefaultGroup_SearchAccountProviderViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.searchaccountprovider_null_DefaultGroup_SearchAccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.searchaccountprovider_null_DefaultGroup_SearchAccountProviderViewLightPreview_0_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index 406ee49b58..a0ea87cbc0 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51897c460fad147f358268ad6b15d6865b6378edd8533d744fe108bc96f5d718 -size 40107 +oid sha256:c5ecc3788746bc425efe843018b13a8fad0a10665fcc0b9b528df6df54791cef +size 20811 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..2b5edb0440 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2036b2b9b3815c69dca77fbde216be6d333607d6dc3ed254923ba35244d20061 +size 9441 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..22503a7c95 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59d342f4e163fd52199a2e05a2d403698a87bb51ce2494ee52c01968edbd71d0 +size 10482 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..1f570432a2 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e38d29272030a66ce7d581f82f82553a3a19f4694117e1b5dd5d507ecce20c4 +size 8532 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..218db727b5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:07414bddb30f4535ba56c38cd295cc272a8531641485a5339f898b4186d3a466 +size 7464 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index 3346d30e90..d44b573a9a 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:57ace1181dd3d95bdbc0ec11270951b6b2fce6301a68af88c6cddc428acba022 -size 39533 +oid sha256:6ce4df1f9faf5704b2407357f06a8879ad57fcc593a0fa59d642f580f1bd7bab +size 20098 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..37e177fe42 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64ed033fbcccc2a5d40664ebbe2a32782579f77eaa07200a329d49165f2606a2 +size 9122 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..bc67803f7f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09ce6b3272ea2450b30c7b3b5fe6ee0aef44a03e62f433f42ab17b255e2fa680 +size 10172 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..534b7f86b3 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9ac9e05b076079caaaf9cd097b75d6cb928982a7c24a6f2eb9c73573521d959 +size 8276 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..e0f3550333 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.accountprovider_null_DefaultGroup_AccountProviderViewLightPreview_0_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37c6d5fd1dbbb10fe4d18bb70c27409da86d832e3394d05841f194729c5b14cd +size 7148 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png deleted file mode 100644 index f90601bd76..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:abc7f0b614bb7532341dd016fab0f5ba72a08f9718bb496b420c94edfcedad2f -size 25927 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png deleted file mode 100644 index ac844925a8..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:33dfbd2203f5d78760730e65c689182b54169988f10906ba987af9fe43d0d03d -size 47910 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_0,NEXUS_5,1.0,en].png deleted file mode 100644 index fe94473bb3..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9d11e04a2440ecd57a4178a0e2c1aa2755c62d147e28c065c7b10a957fef35da -size 25669 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_1,NEXUS_5,1.0,en].png deleted file mode 100644 index a11e2c6787..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.form_null_DefaultGroup_ChangeAccountProviderFormViewLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3ebf802513c7372eaba77b4ebaabbc8e313c1b8f8b00ef8e496194586c9431dc -size 47340 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.common_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.common_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.common_null_DefaultGroup_ChangeServerViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider.common_null_DefaultGroup_ChangeServerViewLightPreview_0_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider_null_DefaultGroup_ChangeAccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.changeaccountprovider_null_DefaultGroup_ChangeAccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider_null_DefaultGroup_ChangeAccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.changeaccountprovider_null_DefaultGroup_ChangeAccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider_null_DefaultGroup_ChangeAccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.changeaccountprovider_null_DefaultGroup_ChangeAccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeaccountprovider_null_DefaultGroup_ChangeAccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.changeaccountprovider_null_DefaultGroup_ChangeAccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.confirmaccountprovider_null_DefaultGroup_ConfirmAccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.confirmaccountprovider_null_DefaultGroup_ConfirmAccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..406ee49b58 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.confirmaccountprovider_null_DefaultGroup_ConfirmAccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:51897c460fad147f358268ad6b15d6865b6378edd8533d744fe108bc96f5d718 +size 40107 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.confirmaccountprovider_null_DefaultGroup_ConfirmAccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.confirmaccountprovider_null_DefaultGroup_ConfirmAccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..3346d30e90 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.confirmaccountprovider_null_DefaultGroup_ConfirmAccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57ace1181dd3d95bdbc0ec11270951b6b2fce6301a68af88c6cddc428acba022 +size 39533 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.searchaccountprovider_null_DefaultGroup_SearchAccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.searchaccountprovider_null_DefaultGroup_SearchAccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..74b6c3556e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.searchaccountprovider_null_DefaultGroup_SearchAccountProviderViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0456d54912f117e8d07c3023c4a0c10fc4933234937f20a87c79cdc8364c3d1 +size 26891 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.searchaccountprovider_null_DefaultGroup_SearchAccountProviderViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.searchaccountprovider_null_DefaultGroup_SearchAccountProviderViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..02296060db --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.searchaccountprovider_null_DefaultGroup_SearchAccountProviderViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac1a1bec7cc71e7d1ab8513c98cb315f720fe0a7341e3485fabbaff4134352aa +size 48795 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.searchaccountprovider_null_DefaultGroup_SearchAccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.searchaccountprovider_null_DefaultGroup_SearchAccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..0facef8667 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.searchaccountprovider_null_DefaultGroup_SearchAccountProviderViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62d743d3bfa2744891c6cd2bb4052cccb623b9ff6e99c06ce07948c71212cc54 +size 26598 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.searchaccountprovider_null_DefaultGroup_SearchAccountProviderViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.searchaccountprovider_null_DefaultGroup_SearchAccountProviderViewLightPreview_0_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..95741de0ba --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.searchaccountprovider_null_DefaultGroup_SearchAccountProviderViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4935ca32e03ea0687d766f6675af3fb78ee7cfff2c7e32e3557d1065fc91ad40 +size 48404 From 78461639c0f49c1d3e61e1b19f44468dcfca3573 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 12 Jun 2023 10:40:34 +0200 Subject: [PATCH 56/61] trigger CI From 01f76816db21a570a96f21cafcbba93810d52d83 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 12 Jun 2023 11:04:53 +0200 Subject: [PATCH 57/61] Fix lint errors manually (a string sync will be needed on develop). --- .../ui-strings/src/main/res/values-cs/translations.xml | 6 ------ .../ui-strings/src/main/res/values-de/translations.xml | 5 +---- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/libraries/ui-strings/src/main/res/values-cs/translations.xml b/libraries/ui-strings/src/main/res/values-cs/translations.xml index daffb59602..0f2affe054 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -150,12 +150,6 @@ "Nahrání média se nezdařilo, zkuste to prosím znovu." "Nahrání média se nezdařilo, zkuste to prosím znovu." "Zaškrtněte, pokud chcete skrýt všechny aktuální a budoucí zprávy od tohoto uživatele" - "Změnit poskytovatele účtu" - "Soukromý server pro zaměstnance Elementu." - "Matrix je otevřená síť pro bezpečnou a decentralizovanou komunikaci." - "Zde budou uloženy vaše konverzace - podobně jako u poskytovatele e-mailových služeb uchováváte své e-maily." - "Chystáte se přihlásit do služby %1$s" - "Chystáte se vytvořit účet na %1$s" "Rageshake" "Práh detekce" "Obecné" diff --git a/libraries/ui-strings/src/main/res/values-de/translations.xml b/libraries/ui-strings/src/main/res/values-de/translations.xml index 90f27bd8dd..ad4c3571ad 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -142,9 +142,6 @@ "Medienauswahl fehlgeschlagen, bitte versuche es erneut." "Medien hochladen fehlgeschlagen. Bitte versuchen Sie es erneut." "Prüfe, ob du alle aktuellen und zukünftigen Nachrichten dieses Benutzers ausblenden möchtest" - "Kontoanbieter wechseln" - "Ein privater Server für Element-Mitarbeiter." - "Matrix ist ein offenes Netzwerk für sichere, dezentrale Kommunikation" "Rageshake" "Erkennungsschwelle" "Allgemein" @@ -156,4 +153,4 @@ "Sie können alle unsere Nutzerbedingungen %1$s lesen." "hier" "Nutzer blockieren" - \ No newline at end of file + From 7cc9b6312491c9cae8ce50b557e1865e5913fe4a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 12 Jun 2023 17:42:01 +0200 Subject: [PATCH 58/61] Fix warning --- .../android/features/roomdetails/RoomDetailsPresenterTests.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt index cf1d9a49ac..43f1667730 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt @@ -252,7 +252,7 @@ class RoomDetailsPresenterTests { fun aMatrixClient( sessionId: SessionId = A_SESSION_ID, -) = FakeMatrixClient() +) = FakeMatrixClient(sessionId = sessionId) fun aMatrixRoom( roomId: RoomId = A_ROOM_ID, From 7705c93eade1f23e8831273f4c6e235693a1c1fc Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 12 Jun 2023 18:28:57 +0200 Subject: [PATCH 59/61] Let HomeserverResolver emit the List and not the Async. --- .../login/impl/resolver/HomeserverResolver.kt | 38 +++++++------------ .../SearchAccountProviderPresenter.kt | 25 +++++++++--- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt index a11b680f63..7c692f5208 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt @@ -17,7 +17,6 @@ package io.element.android.features.login.impl.resolver import io.element.android.features.login.impl.resolver.network.WellknownRequest -import io.element.android.libraries.architecture.Async import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.data.tryOrNull @@ -26,7 +25,6 @@ import io.element.android.libraries.core.uri.isValidUrl import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.currentCoroutineContext -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.withContext @@ -42,24 +40,20 @@ class HomeserverResolver @Inject constructor( private val wellknownRequest: WellknownRequest, ) { - suspend fun resolve(userInput: String): Flow>> = flow { + suspend fun resolve(userInput: String): Flow> = flow { val flowContext = currentCoroutineContext() - emit(Async.Uninitialized) - // Debounce - delay(300) val trimmedUserInput = userInput.trim() if (trimmedUserInput.length < 4) return@flow - emit(Async.Loading()) val candidateBase = trimmedUserInput.ensureProtocol().removeSuffix("/") val list = getUrlCandidates(candidateBase) val currentList = Collections.synchronizedList(mutableListOf()) // Run all the requests in parallel withContext(dispatchers.io) { - list.map { + list.map { url -> async { val wellKnown = tryOrNull { withTimeout(5000) { - wellknownRequest.execute(it) + wellknownRequest.execute(url) } } val isValid = wellKnown?.isValid().orFalse() @@ -68,35 +62,29 @@ class HomeserverResolver @Inject constructor( // Emit the list as soon as possible currentList.add( HomeserverData( - homeserverUrl = it, + homeserverUrl = url, isWellknownValid = true, supportSlidingSync = supportSlidingSync ) ) withContext(flowContext) { - emit(Async.Success(currentList)) + emit(currentList.toList()) } } } }.awaitAll() } // If list is empty, and the user has entered an URL, do not block the user. - if (currentList.isEmpty()) { - if (trimmedUserInput.isValidUrl()) { - emit( - Async.Success( - listOf( - HomeserverData( - homeserverUrl = trimmedUserInput, - isWellknownValid = false, - supportSlidingSync = false, - ) - ) + if (currentList.isEmpty() && trimmedUserInput.isValidUrl()) { + emit( + listOf( + HomeserverData( + homeserverUrl = trimmedUserInput, + isWellknownValid = false, + supportSlidingSync = false, ) ) - } else { - emit(Async.Uninitialized) - } + ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenter.kt index f95c027620..1d8271e394 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenter.kt @@ -18,6 +18,7 @@ package io.element.android.features.login.impl.screens.searchaccountprovider import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -28,6 +29,9 @@ import io.element.android.features.login.impl.resolver.HomeserverData import io.element.android.features.login.impl.resolver.HomeserverResolver import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import javax.inject.Inject class SearchAccountProviderPresenter @Inject constructor( @@ -42,14 +46,12 @@ class SearchAccountProviderPresenter @Inject constructor( } val changeServerState = changeServerPresenter.present() - var data: Async> by remember { + val data: MutableState>> = remember { mutableStateOf(Async.Uninitialized) } LaunchedEffect(userInput) { - homeserverResolver.resolve(userInput).collect { - data = it - } + onUserInput(userInput, data) } fun handleEvents(event: SearchAccountProviderEvents) { @@ -62,9 +64,22 @@ class SearchAccountProviderPresenter @Inject constructor( return SearchAccountProviderState( userInput = userInput, - userInputResult = data, + userInputResult = data.value, changeServerState = changeServerState, eventSink = ::handleEvents ) } + + private fun CoroutineScope.onUserInput(userInput: String, data: MutableState>>) = launch { + data.value = Async.Uninitialized + // Debounce + delay(300) + data.value = Async.Loading() + homeserverResolver.resolve(userInput).collect { + data.value = Async.Success(it) + } + if (data.value !is Async.Success) { + data.value = Async.Uninitialized + } + } } From 0d55c47d4ffdb270a89099f0cd9b72707eb4051b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 12 Jun 2023 18:51:17 +0200 Subject: [PATCH 60/61] Fix infinite tests. --- .../SearchAccountProviderPresenterTest.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt index 9163f247f5..c1d36c2e2f 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt @@ -63,7 +63,7 @@ class SearchAccountProviderPresenterTest { AccountProviderDataSource() ) val presenter = SearchAccountProviderPresenter( - HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), + HomeserverResolver(testCoroutineDispatchers(testScheduler), fakeWellknownRequest), changeServerPresenter ) moleculeFlow(RecompositionClock.Immediate) { @@ -87,7 +87,7 @@ class SearchAccountProviderPresenterTest { AccountProviderDataSource() ) val presenter = SearchAccountProviderPresenter( - HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), + HomeserverResolver(testCoroutineDispatchers(testScheduler), fakeWellknownRequest), changeServerPresenter ) moleculeFlow(RecompositionClock.Immediate) { @@ -122,7 +122,7 @@ class SearchAccountProviderPresenterTest { AccountProviderDataSource() ) val presenter = SearchAccountProviderPresenter( - HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), + HomeserverResolver(testCoroutineDispatchers(testScheduler), fakeWellknownRequest), changeServerPresenter ) moleculeFlow(RecompositionClock.Immediate) { @@ -157,7 +157,7 @@ class SearchAccountProviderPresenterTest { AccountProviderDataSource() ) val presenter = SearchAccountProviderPresenter( - HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), + HomeserverResolver(testCoroutineDispatchers(testScheduler), fakeWellknownRequest), changeServerPresenter ) moleculeFlow(RecompositionClock.Immediate) { From 91d20e1b7867553a82523a5abff4ffb49ea5fac7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 12 Jun 2023 19:49:56 +0200 Subject: [PATCH 61/61] Enforce usage of `TestScope.testScheduler` --- .../features/leaveroom/impl/LeaveRoomPresenterImplTest.kt | 2 +- .../SearchAccountProviderPresenterTest.kt | 8 ++++---- .../features/messages/fixtures/timelineItemsFactory.kt | 3 ++- .../impl/invite/RoomInviteMembersPresenterTest.kt | 3 ++- .../roomdetails/members/RoomMemberListPresenterTests.kt | 5 +++-- .../android/tests/testutils/TestCoroutineDispatchers.kt | 7 ++----- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImplTest.kt b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImplTest.kt index 54ab3d1dc5..03eeb3adc6 100644 --- a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImplTest.kt +++ b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImplTest.kt @@ -213,5 +213,5 @@ private fun TestScope.createPresenter( ): LeaveRoomPresenter = LeaveRoomPresenterImpl( client = client, roomMembershipObserver = roomMembershipObserver, - dispatchers = testCoroutineDispatchers(testScheduler, false), + dispatchers = testCoroutineDispatchers(false), ) diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt index c1d36c2e2f..9163f247f5 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenterTest.kt @@ -63,7 +63,7 @@ class SearchAccountProviderPresenterTest { AccountProviderDataSource() ) val presenter = SearchAccountProviderPresenter( - HomeserverResolver(testCoroutineDispatchers(testScheduler), fakeWellknownRequest), + HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), changeServerPresenter ) moleculeFlow(RecompositionClock.Immediate) { @@ -87,7 +87,7 @@ class SearchAccountProviderPresenterTest { AccountProviderDataSource() ) val presenter = SearchAccountProviderPresenter( - HomeserverResolver(testCoroutineDispatchers(testScheduler), fakeWellknownRequest), + HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), changeServerPresenter ) moleculeFlow(RecompositionClock.Immediate) { @@ -122,7 +122,7 @@ class SearchAccountProviderPresenterTest { AccountProviderDataSource() ) val presenter = SearchAccountProviderPresenter( - HomeserverResolver(testCoroutineDispatchers(testScheduler), fakeWellknownRequest), + HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), changeServerPresenter ) moleculeFlow(RecompositionClock.Immediate) { @@ -157,7 +157,7 @@ class SearchAccountProviderPresenterTest { AccountProviderDataSource() ) val presenter = SearchAccountProviderPresenter( - HomeserverResolver(testCoroutineDispatchers(testScheduler), fakeWellknownRequest), + HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), changeServerPresenter ) moleculeFlow(RecompositionClock.Immediate) { diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt index c6ea04f407..ba1e7a8869 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt @@ -36,8 +36,9 @@ import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter import io.element.android.libraries.eventformatter.api.TimelineEventFormatter import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.test.TestScope -internal fun aTimelineItemsFactory(): TimelineItemsFactory { +internal fun TestScope.aTimelineItemsFactory(): TimelineItemsFactory { val timelineEventFormatter = aTimelineEventFormatter() return TimelineItemsFactory( dispatchers = testCoroutineDispatchers(), diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenterTest.kt index 3baea96990..495e639baf 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenterTest.kt @@ -38,6 +38,7 @@ import io.element.android.libraries.usersearch.api.UserSearchResult import io.element.android.libraries.usersearch.test.FakeUserRepository import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.collections.immutable.ImmutableList +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test @@ -368,7 +369,7 @@ internal class RoomInviteMembersPresenterTest { } } - private fun createDataSource( + private fun TestScope.createDataSource( matrixRoom: MatrixRoom = aMatrixRoom().apply { givenRoomMembersState(MatrixRoomMembersState.Ready(aRoomMemberList())) }, diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt index 9625a167f7..b9b52b29f8 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt @@ -35,6 +35,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test @@ -165,7 +166,7 @@ class RoomMemberListPresenterTests { } @ExperimentalCoroutinesApi -private fun createDataSource( +private fun TestScope.createDataSource( matrixRoom: MatrixRoom = aMatrixRoom().apply { givenRoomMembersState(MatrixRoomMembersState.Ready(aRoomMemberList())) }, @@ -173,7 +174,7 @@ private fun createDataSource( ) = RoomMemberListDataSource(matrixRoom, coroutineDispatchers) @ExperimentalCoroutinesApi -private fun createPresenter( +private fun TestScope.createPresenter( matrixRoom: MatrixRoom = FakeMatrixRoom(), roomMemberListDataSource: RoomMemberListDataSource = createDataSource(), coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers() diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/TestCoroutineDispatchers.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/TestCoroutineDispatchers.kt index 4628f4910c..786d8f8cd2 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/TestCoroutineDispatchers.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/TestCoroutineDispatchers.kt @@ -21,19 +21,16 @@ package io.element.android.tests.testutils import io.element.android.libraries.core.coroutine.CoroutineDispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestCoroutineScheduler +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher /** * Create a [CoroutineDispatchers] instance for testing. * - * @param testScheduler The [TestCoroutineScheduler] to use. If using [runTest] use the one provided by its [TestScope]. - * If null the [TestDispatcher] logic will select one or create a new one. * @param useUnconfinedTestDispatcher If true, use [UnconfinedTestDispatcher] for all dispatchers. * If false, use [StandardTestDispatcher] for all dispatchers. */ -fun testCoroutineDispatchers( - testScheduler: TestCoroutineScheduler? = null, +fun TestScope.testCoroutineDispatchers( useUnconfinedTestDispatcher: Boolean = true, ): CoroutineDispatchers = when (useUnconfinedTestDispatcher) { false -> CoroutineDispatchers(