Account provider form screen.
This commit is contained in:
committed by
Benoit Marty
parent
7ac8843bf8
commit
d8db9edafc
@@ -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)
|
||||
|
||||
|
||||
@@ -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<AccountProviderNode>(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<ChangeAccountProviderNode>(buildContext, plugins = listOf(callback))
|
||||
}
|
||||
NavTarget.ChangeAccountProviderForm -> {
|
||||
val callback = object : ChangeAccountProviderFormNode.Callback {
|
||||
override fun onAccountProviderItemClicked(data: AccountProviderItem) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
createNode<ChangeAccountProviderFormNode>(buildContext, plugins = listOf(callback))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<Callback>().forEach { it.onAccountProviderItemClicked(data) }
|
||||
}
|
||||
|
||||
private fun onOtherClicked() {
|
||||
plugins<Callback>().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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ChangeAccountProviderState> {
|
||||
|
||||
@Composable
|
||||
override fun present(): ChangeAccountProviderState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
|
||||
val homeserver = rememberSaveable {
|
||||
mutableStateOf(authenticationService.getHomeserverDetails().value?.url ?: LoginConstants.DEFAULT_HOMESERVER_URL)
|
||||
}
|
||||
val changeServerAction: MutableState<Async<Unit>> = 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<String>, changeServerAction: MutableState<Async<Unit>>) = launch {
|
||||
suspend {
|
||||
val domain = tryOrNull { URL(homeserverUrl.value) }?.host ?: homeserverUrl.value
|
||||
authenticationService.setHomeserver(domain).getOrThrow()
|
||||
homeserverUrl.value = domain
|
||||
}.execute(changeServerAction, errorMapping = ChangeServerError::from)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Unit>,
|
||||
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<AccountProviderItem>,
|
||||
)
|
||||
|
||||
@@ -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<ChangeAccountProviderState> {
|
||||
override val values: Sequence<ChangeAccountProviderState>
|
||||
@@ -28,7 +28,12 @@ open class ChangeAccountProviderStateProvider : PreviewParameterProvider<ChangeA
|
||||
}
|
||||
|
||||
fun aChangeAccountProviderState() = ChangeAccountProviderState(
|
||||
homeserver = "",
|
||||
changeServerAction = Async.Uninitialized,
|
||||
eventSink = {}
|
||||
accountProviderItems = listOf(
|
||||
AccountProviderItem(
|
||||
title = "matrix.org",
|
||||
subtitle = null,
|
||||
isPublic = true,
|
||||
isMatrixOrg = true,
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
@@ -34,21 +34,14 @@ import androidx.compose.material.icons.filled.Home
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.features.login.impl.R
|
||||
import io.element.android.features.login.impl.changeaccountprovider.item.ChangeAccountProviderItem
|
||||
import io.element.android.features.login.impl.changeaccountprovider.item.AccountProviderItem
|
||||
import io.element.android.features.login.impl.changeaccountprovider.item.ChangeAccountProviderItemView
|
||||
import io.element.android.features.login.impl.changeserver.ChangeServerError
|
||||
import io.element.android.features.login.impl.changeserver.SlidingSyncNotSupportedDialog
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
@@ -64,26 +57,10 @@ fun ChangeAccountProviderView(
|
||||
state: ChangeAccountProviderState,
|
||||
modifier: Modifier = Modifier,
|
||||
onBackPressed: () -> 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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<Plugin>,
|
||||
private val presenter: ChangeAccountProviderFormPresenter,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
|
||||
interface Callback : Plugin {
|
||||
fun onAccountProviderItemClicked(data: AccountProviderItem)
|
||||
}
|
||||
|
||||
private fun onAccountProviderItemClicked(data: AccountProviderItem) {
|
||||
plugins<Callback>().forEach { it.onAccountProviderItemClicked(data) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
ChangeAccountProviderFormView(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
onBackPressed = ::navigateUp,
|
||||
onProviderClicked = ::onAccountProviderItemClicked
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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<ChangeAccountProviderFormState> {
|
||||
|
||||
@Composable
|
||||
override fun present(): ChangeAccountProviderFormState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
|
||||
var currentJob: Job? = remember { null }
|
||||
|
||||
val userInput = rememberSaveable {
|
||||
mutableStateOf("")
|
||||
}
|
||||
val userInputResult: MutableState<Async<List<HomeserverData>>> = 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<Async<List<HomeserverData>>>) = launch {
|
||||
suspend {
|
||||
homeserverResolver.resolve(userInput)
|
||||
}.execute(userInputResult)
|
||||
}
|
||||
}
|
||||
@@ -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 = { }
|
||||
)
|
||||
}
|
||||
@@ -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<List<HomeserverData>>,
|
||||
val eventSink: (ChangeAccountProviderFormEvents) -> Unit
|
||||
)
|
||||
@@ -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<ChangeAccountProviderFormState> {
|
||||
override val values: Sequence<ChangeAccountProviderFormState>
|
||||
get() = sequenceOf(
|
||||
aChangeAccountProviderFormState(),
|
||||
aChangeAccountProviderFormState(userInputResult = Async.Success(aHomeserverDataList())),
|
||||
// Add other state here
|
||||
)
|
||||
}
|
||||
|
||||
fun aChangeAccountProviderFormState(
|
||||
userInput: String = "",
|
||||
userInputResult: Async<List<HomeserverData>> = Async.Uninitialized,
|
||||
) = ChangeAccountProviderFormState(
|
||||
userInput = userInput,
|
||||
userInputResult = userInputResult,
|
||||
eventSink = {}
|
||||
)
|
||||
|
||||
fun aHomeserverDataList(): List<HomeserverData> {
|
||||
return listOf(
|
||||
HomeserverData(
|
||||
userInput = "matrix",
|
||||
homeserverUrl = "https://matrix.org",
|
||||
isWellknownValid = true,
|
||||
),
|
||||
HomeserverData(
|
||||
userInput = "matrix",
|
||||
homeserverUrl = "https://matrix.io",
|
||||
isWellknownValid = false,
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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<HomeserverData> {
|
||||
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<String>): List<HomeserverData> {
|
||||
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<String> {
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
* <pre>
|
||||
* {
|
||||
* "m.homeserver": {
|
||||
* "base_url": "https://matrix.org"
|
||||
* },
|
||||
* "m.identity_server": {
|
||||
* "base_url": "https://vector.im"
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* .
|
||||
*/
|
||||
@Serializable
|
||||
data class WellKnown(
|
||||
@SerialName("m.homeserver")
|
||||
val homeServer: WellKnownBaseConfig? = null,
|
||||
|
||||
@SerialName("m.identity_server")
|
||||
val identityServer: WellKnownBaseConfig? = null,
|
||||
)
|
||||
@@ -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
|
||||
* <pre>
|
||||
* {
|
||||
* "base_url": "https://element.io"
|
||||
* }
|
||||
* </pre>
|
||||
* .
|
||||
*/
|
||||
@Serializable
|
||||
data class WellKnownBaseConfig(
|
||||
@SerialName("base_url")
|
||||
val baseURL: String? = null
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
@@ -18,19 +18,19 @@ package io.element.android.features.login.impl.changeaccountprovider.item
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
|
||||
open class ChangeAccountProviderItemProvider : PreviewParameterProvider<ChangeAccountProviderItem> {
|
||||
override val values: Sequence<ChangeAccountProviderItem>
|
||||
open class ChangeAccountProviderItemProvider : PreviewParameterProvider<AccountProviderItem> {
|
||||
override val values: Sequence<AccountProviderItem>
|
||||
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,
|
||||
)
|
||||
|
||||
@@ -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 = { }
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_account_provider_change">"Change account provider"</string>
|
||||
<string name="screen_account_provider_continue">"Continue"</string>
|
||||
<string name="screen_account_provider_form_hint">"Homeserver address"</string>
|
||||
<string name="screen_account_provider_form_notice">"Enter a search term or a domain address."</string>
|
||||
<string name="screen_account_provider_form_subtitle">"Search for a company, community, or private server."</string>
|
||||
<string name="screen_account_provider_form_title">"Find an account provider"</string>
|
||||
<string name="screen_account_provider_signin_title">"You’re about to sign in to %s"</string>
|
||||
<string name="screen_account_provider_signup_subtitle">"This is where you conversations will live — just like you would use an email provider to keep your emails."</string>
|
||||
<string name="screen_account_provider_signup_title">"You’re about to create an account on %s"</string>
|
||||
@@ -27,4 +31,4 @@
|
||||
<string name="screen_login_password_hint">"Password"</string>
|
||||
<string name="screen_login_submit">"Continue"</string>
|
||||
<string name="screen_login_username_hint">"Username"</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user