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)) } } }