From d8db9edafc18c571a3ea28d39cd4a76f3da30cbf Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 6 Jun 2023 14:40:16 +0200 Subject: [PATCH] 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 +