Validate server on user click.
This commit is contained in:
parent
4df03762a8
commit
b039f0d01d
21 changed files with 353 additions and 51 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Callback>().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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<Callback>().forEach { it.onAccountProviderClicked(data) }
|
||||
private fun onDone() {
|
||||
plugins<Callback>().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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ChangeAccountProviderState> {
|
||||
|
||||
@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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<AccountProvider>,
|
||||
val changeServerState: ChangeServerState,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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<ChangeAccountProviderState> {
|
||||
override val values: Sequence<ChangeAccountProviderState>
|
||||
|
|
@ -31,4 +32,5 @@ fun aChangeAccountProviderState() = ChangeAccountProviderState(
|
|||
accountProviders = listOf(
|
||||
anAccountProvider()
|
||||
),
|
||||
changeServerState = aChangeServerState(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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<ChangeServerState> {
|
||||
|
||||
@Composable
|
||||
override fun present(): ChangeServerState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
|
||||
val changeServerAction: MutableState<Async<Unit>> = 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<Async<Unit>>,
|
||||
) = 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Unit>,
|
||||
val eventSink: (ChangeServerEvents) -> Unit
|
||||
)
|
||||
|
|
@ -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<ChangeServerState> {
|
||||
override val values: Sequence<ChangeServerState>
|
||||
get() = sequenceOf(
|
||||
aChangeServerState(),
|
||||
)
|
||||
}
|
||||
|
||||
fun aChangeServerState() = ChangeServerState(
|
||||
changeServerAction = Async.Uninitialized,
|
||||
eventSink = {}
|
||||
)
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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<Callback>().forEach { it.onAccountProviderClicked(data) }
|
||||
private fun onDone() {
|
||||
plugins<Callback>().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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ChangeAccountProviderFormState> {
|
||||
|
||||
@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
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<List<HomeserverData>>,
|
||||
val changeServerState: ChangeServerState,
|
||||
val eventSink: (ChangeAccountProviderFormEvents) -> Unit
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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<ChangeAccountProviderFormState> {
|
||||
|
|
@ -34,6 +35,7 @@ fun aChangeAccountProviderFormState(
|
|||
) = ChangeAccountProviderFormState(
|
||||
userInput = userInput,
|
||||
userInputResult = userInputResult,
|
||||
changeServerState = aChangeServerState(),
|
||||
eventSink = {}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue