From 96f9eb839774a10a1dea5e03ad29fd4bf5e36f0f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 5 Jun 2023 17:52:16 +0200 Subject: [PATCH] Change account provider screen. --- .../ChangeAccountProviderEvents.kt | 23 +++ .../ChangeAccountProviderNode.kt | 46 +++++ .../ChangeAccountProviderPresenter.kt | 79 ++++++++ .../ChangeAccountProviderState.kt | 29 +++ .../ChangeAccountProviderStateProvider.kt | 34 ++++ .../ChangeAccountProviderView.kt | 178 ++++++++++++++++++ .../item/ChangeAccountProviderItem.kt | 24 +++ .../item/ChangeAccountProviderItemProvider.kt | 36 ++++ .../item/ChangeAccountProviderItemView.kt | 131 +++++++++++++ .../impl/src/main/res/drawable/ic_matrix.xml | 12 ++ .../impl/src/main/res/drawable/ic_public.xml | 12 ++ .../impl/src/main/res/values/localazy.xml | 4 + .../atomic/atoms/RoundedIconAtom.kt | 4 +- .../atomic/atoms/SeparatorAtom.kt | 62 ++++++ tools/localazy/config.json | 3 +- 15 files changed, 675 insertions(+), 2 deletions(-) create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderEvents.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItem.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemProvider.kt create mode 100644 features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemView.kt create mode 100644 features/login/impl/src/main/res/drawable/ic_matrix.xml create mode 100644 features/login/impl/src/main/res/drawable/ic_public.xml create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/SeparatorAtom.kt diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderEvents.kt new file mode 100644 index 0000000000..73b9929dca --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderEvents.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.changeaccountprovider + +sealed interface ChangeAccountProviderEvents { + data class SetServer(val server: String) : ChangeAccountProviderEvents + object Submit : ChangeAccountProviderEvents + object ClearError : ChangeAccountProviderEvents +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt new file mode 100644 index 0000000000..00b9d312c0 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderNode.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.changeaccountprovider + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.AppScope + +@ContributesNode(AppScope::class) +class ChangeAccountProviderNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: ChangeAccountProviderPresenter, +) : Node(buildContext, plugins = plugins) { + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + ChangeAccountProviderView( + state = state, + modifier = modifier, + // TODO + onBackPressed = {} + ) + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt new file mode 100644 index 0000000000..b4fbdac771 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderPresenter.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.changeaccountprovider + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import io.element.android.features.login.impl.changeserver.ChangeServerError +import io.element.android.features.login.impl.util.LoginConstants +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.architecture.execute +import io.element.android.libraries.core.data.tryOrNull +import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import java.net.URL +import javax.inject.Inject + +class ChangeAccountProviderPresenter @Inject constructor( + private val authenticationService: MatrixAuthenticationService +) : Presenter { + + @Composable + override fun present(): ChangeAccountProviderState { + val localCoroutineScope = rememberCoroutineScope() + + val homeserver = rememberSaveable { + mutableStateOf(authenticationService.getHomeserverDetails().value?.url ?: LoginConstants.DEFAULT_HOMESERVER_URL) + } + val changeServerAction: MutableState> = remember { + mutableStateOf(Async.Uninitialized) + } + + fun handleEvents(event: ChangeAccountProviderEvents) { + when (event) { + is ChangeAccountProviderEvents.SetServer -> { + homeserver.value = event.server + handleEvents(ChangeAccountProviderEvents.ClearError) + } + ChangeAccountProviderEvents.Submit -> { + localCoroutineScope.submit(homeserver, changeServerAction) + } + ChangeAccountProviderEvents.ClearError -> changeServerAction.value = Async.Uninitialized + } + } + + return ChangeAccountProviderState( + homeserver = homeserver.value, + changeServerAction = changeServerAction.value, + eventSink = ::handleEvents + ) + } + + private fun CoroutineScope.submit(homeserverUrl: MutableState, changeServerAction: MutableState>) = launch { + suspend { + val domain = tryOrNull { URL(homeserverUrl.value) }?.host ?: homeserverUrl.value + authenticationService.setHomeserver(domain).getOrThrow() + homeserverUrl.value = domain + }.execute(changeServerAction, errorMapping = ChangeServerError::from) + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt new file mode 100644 index 0000000000..98f95ca357 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderState.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.changeaccountprovider + +import io.element.android.libraries.architecture.Async + +// Do not use default value, so no member get forgotten in the presenters. +data class ChangeAccountProviderState( + val homeserver: String, + val changeServerAction: Async, + val eventSink: (ChangeAccountProviderEvents) -> Unit +) { + // TODO Remove + val submitEnabled: Boolean get() = changeServerAction is Async.Uninitialized || changeServerAction is Async.Loading +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt new file mode 100644 index 0000000000..a176de572e --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderStateProvider.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.changeaccountprovider + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.Async + +open class ChangeAccountProviderStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aChangeAccountProviderState(), + // Add other state here + ) +} + +fun aChangeAccountProviderState() = ChangeAccountProviderState( + homeserver = "", + changeServerAction = Async.Uninitialized, + eventSink = {} +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt new file mode 100644 index 0000000000..d778bb3c35 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/ChangeAccountProviderView.kt @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) + +package io.element.android.features.login.impl.changeaccountprovider + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Home +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.features.login.impl.R +import io.element.android.features.login.impl.changeaccountprovider.item.ChangeAccountProviderItem +import io.element.android.features.login.impl.changeaccountprovider.item.ChangeAccountProviderItemView +import io.element.android.features.login.impl.changeserver.ChangeServerError +import io.element.android.features.login.impl.changeserver.SlidingSyncNotSupportedDialog +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule +import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.components.button.ButtonWithProgress +import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.components.Scaffold +import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.testtags.TestTags +import io.element.android.libraries.testtags.testTag + +/** + * https://www.figma.com/file/o9p34zmiuEpZRyvZXJZAYL/FTUE?type=design&node-id=604-60817 + */ +@Composable +fun ChangeAccountProviderView( + state: ChangeAccountProviderState, + modifier: Modifier = Modifier, + onBackPressed: () -> Unit, + onOtherProviderClicked: () -> Unit = {}, + onChangeServerSuccess: () -> Unit = {}, +) { + val eventSink = state.eventSink + val scrollState = rememberScrollState() + val isLoading by remember(state.changeServerAction) { + derivedStateOf { + state.changeServerAction is Async.Loading + } + } + val invalidHomeserverError = (state.changeServerAction as? Async.Failure)?.error as? ChangeServerError.InlineErrorMessage + val slidingSyncNotSupportedError = (state.changeServerAction as? Async.Failure)?.error as? ChangeServerError.SlidingSyncAlert + val focusManager = LocalFocusManager.current + + fun submit() { + // Clear focus to prevent keyboard issues with textfields + focusManager.clearFocus(force = true) + + eventSink(ChangeAccountProviderEvents.Submit) + } + + Scaffold( + topBar = { + TopAppBar( + title = {}, + navigationIcon = { BackButton(onClick = onBackPressed) } + ) + } + ) { padding -> + Box( + modifier = modifier + .fillMaxSize() + .imePadding() + .padding(padding) + .consumeWindowInsets(padding) + ) { + Column( + modifier = Modifier + .verticalScroll( + state = scrollState, + ) + ) { + IconTitleSubtitleMolecule( + modifier = Modifier.padding(top = 16.dp, bottom = 32.dp, start = 16.dp, end = 16.dp), + iconImageVector = Icons.Filled.Home, + title = stringResource(id = R.string.screen_change_account_provider_title), + subTitle = stringResource(id = R.string.screen_change_account_provider_subtitle) + ) + + if (slidingSyncNotSupportedError != null) { + SlidingSyncNotSupportedDialog(onLearnMoreClicked = { + eventSink(ChangeAccountProviderEvents.ClearError) + }, onDismiss = { + eventSink(ChangeAccountProviderEvents.ClearError) + }) + } + ChangeAccountProviderItemView( + item = ChangeAccountProviderItem( + title = "matrix.org", + subtitle = stringResource(id = R.string.screen_change_account_provider_matrix_org_subtitle), + isPublic = true, + isMatrix = true, + ), + onClick = { + TODO() + } + ) + ChangeAccountProviderItemView( + item = ChangeAccountProviderItem( + title = stringResource(id = R.string.screen_change_account_provider_other), + ), + onClick = onOtherProviderClicked + ) + + Spacer(Modifier.height(32.dp)) + ButtonWithProgress( + text = stringResource(id = R.string.screen_change_server_submit), + showProgress = isLoading, + onClick = ::submit, + enabled = state.submitEnabled, + modifier = Modifier + .fillMaxWidth() + .testTag(TestTags.changeServerContinue) + ) + if (state.changeServerAction is Async.Success) { + onChangeServerSuccess() + } + } + } + } +} + +@Preview +@Composable +fun ChangeAccountProviderViewLightPreview(@PreviewParameter(ChangeAccountProviderStateProvider::class) state: ChangeAccountProviderState) = + ElementPreviewLight { ContentToPreview(state) } + +@Preview +@Composable +fun ChangeAccountProviderViewDarkPreview(@PreviewParameter(ChangeAccountProviderStateProvider::class) state: ChangeAccountProviderState) = + ElementPreviewDark { ContentToPreview(state) } + +@Composable +private fun ContentToPreview(state: ChangeAccountProviderState) { + ChangeAccountProviderView( + state = state, + onBackPressed = { } + ) +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItem.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItem.kt new file mode 100644 index 0000000000..c913923205 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItem.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.changeaccountprovider.item + +data class ChangeAccountProviderItem( + val title: String, + val subtitle: String? = null, + val isPublic: Boolean = false, + val isMatrix: Boolean = false, +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemProvider.kt new file mode 100644 index 0000000000..bf7a92cf08 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemProvider.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.changeaccountprovider.item + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider + +open class ChangeAccountProviderItemProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aChangeAccountProviderItem(), + aChangeAccountProviderItem().copy(subtitle = null), + aChangeAccountProviderItem().copy(title = "Other", subtitle = null, isPublic = false, isMatrix = false), + // Add other state here + ) +} + +fun aChangeAccountProviderItem() = ChangeAccountProviderItem( + title = "matrix.org", + subtitle = "Matrix.org is an open network for secure, decentralized communication.", + isPublic = true, + isMatrix = true, +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemView.kt new file mode 100644 index 0000000000..96d7a26e7c --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeaccountprovider/item/ChangeAccountProviderItemView.kt @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.login.impl.changeaccountprovider.item + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.features.login.impl.R +import io.element.android.libraries.designsystem.ElementTextStyles +import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtom +import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtomSize +import io.element.android.libraries.designsystem.atomic.atoms.SeparatorAtom +import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.Text + +/** + * https://www.figma.com/file/o9p34zmiuEpZRyvZXJZAYL/FTUE?type=design&node-id=604-60817 + */ +@Composable +fun ChangeAccountProviderItemView( + item: ChangeAccountProviderItem, + modifier: Modifier = Modifier, + onClick: () -> Unit, +) { + Column(modifier = modifier + .fillMaxWidth() + .clickable { onClick() }) { + SeparatorAtom() + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp, horizontal = 16.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .heightIn(min = 44.dp), + verticalAlignment = Alignment.CenterVertically + ) { + if (item.isMatrix) { + RoundedIconAtom( + size = RoundedIconAtomSize.Medium, + resourceId = R.drawable.ic_matrix, + tint = Color.Unspecified, + ) + } else { + RoundedIconAtom( + size = RoundedIconAtomSize.Medium, + imageVector = Icons.Filled.Search, + ) + } + Text( + modifier = modifier + .padding(start = 16.dp) + .weight(1f), + text = item.title, + style = ElementTextStyles.Regular.headline.copy(textAlign = TextAlign.Start), + color = MaterialTheme.colorScheme.primary, + ) + if (item.isPublic) { + Icon( + modifier = Modifier + .padding(start = 10.dp) + .size(16.dp), + resourceId = R.drawable.ic_public, + contentDescription = null, + tint = Color.Unspecified, + ) + } + } + if (item.subtitle != null) { + Text( + modifier = modifier + .padding(start = 46.dp, bottom = 12.dp, end = 26.dp), + text = item.subtitle, + style = ElementTextStyles.Regular.subheadline.copy(textAlign = TextAlign.Start), + color = MaterialTheme.colorScheme.secondary, + ) + } + } + } +} + +@Preview +@Composable +fun ChangeAccountProviderViewLightPreview(@PreviewParameter(ChangeAccountProviderItemProvider::class) item: ChangeAccountProviderItem) = + ElementPreviewLight { ContentToPreview(item) } + +@Preview +@Composable +fun ChangeAccountProviderViewDarkPreview(@PreviewParameter(ChangeAccountProviderItemProvider::class) item: ChangeAccountProviderItem) = + ElementPreviewDark { ContentToPreview(item) } + +@Composable +private fun ContentToPreview(item: ChangeAccountProviderItem) { + ChangeAccountProviderItemView( + item = item, + onClick = { } + ) +} diff --git a/features/login/impl/src/main/res/drawable/ic_matrix.xml b/features/login/impl/src/main/res/drawable/ic_matrix.xml new file mode 100644 index 0000000000..dbc788a031 --- /dev/null +++ b/features/login/impl/src/main/res/drawable/ic_matrix.xml @@ -0,0 +1,12 @@ + + + + diff --git a/features/login/impl/src/main/res/drawable/ic_public.xml b/features/login/impl/src/main/res/drawable/ic_public.xml new file mode 100644 index 0000000000..fc1eacbc9f --- /dev/null +++ b/features/login/impl/src/main/res/drawable/ic_public.xml @@ -0,0 +1,12 @@ + + + + diff --git a/features/login/impl/src/main/res/values/localazy.xml b/features/login/impl/src/main/res/values/localazy.xml index cf59844e89..39c35ac92f 100644 --- a/features/login/impl/src/main/res/values/localazy.xml +++ b/features/login/impl/src/main/res/values/localazy.xml @@ -1,5 +1,9 @@ + "Matrix.org is an open network for secure, decentralized communication." + "Other" + "Use a different account provider, such as your own private server or a work account." + "Change account provider" "We couldn\'t reach this homeserver. Please check that you have entered the homeserver URL correctly. If the URL is correct, contact your homeserver administrator for further help." "This server currently doesn’t support sliding sync." "Homeserver URL" diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt index e73af11254..01fa2355d3 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt @@ -28,6 +28,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp @@ -48,6 +49,7 @@ fun RoundedIconAtom( size: RoundedIconAtomSize = RoundedIconAtomSize.Large, resourceId: Int? = null, imageVector: ImageVector? = null, + tint: Color = MaterialTheme.colorScheme.secondary ) { Box( modifier = modifier @@ -61,7 +63,7 @@ fun RoundedIconAtom( modifier = Modifier .align(Alignment.Center) .size(size.toIconSize()), - tint = MaterialTheme.colorScheme.secondary, + tint = tint, resourceId = resourceId, imageVector = imageVector, contentDescription = "", diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/SeparatorAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/SeparatorAtom.kt new file mode 100644 index 0000000000..fe15ae5480 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/SeparatorAtom.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.atomic.atoms + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.LocalColors + +@Composable +fun SeparatorAtom( + modifier: Modifier = Modifier, + horizontalPadding: Dp = 0.dp, +) { + Spacer( + modifier = modifier + .height(0.5f.dp) + .fillMaxWidth() + .padding(horizontal = horizontalPadding) + .background( + color = LocalColors.current.quinary, + ) + ) +} + +@Preview +@Composable +internal fun SeparatorAtomLightPreview() = + ElementPreviewLight { ContentToPreview() } + +@Preview +@Composable +internal fun SeparatorAtomDarkPreview() = + ElementPreviewDark { ContentToPreview() } + +@Composable +private fun ContentToPreview() { + SeparatorAtom(horizontalPadding = 20.dp) +} diff --git a/tools/localazy/config.json b/tools/localazy/config.json index a23d652cd8..23908ecad8 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -73,7 +73,8 @@ "name": ":features:login:impl", "includeRegex": [ "screen_login_.*", - "screen_change_server_.*" + "screen_change_server_.*", + "screen_change_account_provider_.*" ] }, {