From 4ecd4c636f27a19b9107e3ac003f642ad9fcc9db Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 24 Aug 2023 16:21:33 +0200 Subject: [PATCH] Extract Composable to new SunsetPage. --- .../screens/waitlistscreen/WaitListView.kt | 194 +++++------------- .../designsystem/atomic/pages/SunsetPage.kt | 134 ++++++++++++ .../designsystem/text/AnnotatedStrings.kt | 19 ++ .../src/main/res/drawable/light_dark.png | Bin 4 files changed, 206 insertions(+), 141 deletions(-) create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt rename {features/login/impl => libraries/designsystem}/src/main/res/drawable/light_dark.png (100%) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt index 1bce4b39a9..6eb1b37d73 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt @@ -16,32 +16,17 @@ package io.element.android.features.login.impl.screens.waitlistscreen -import androidx.annotation.StringRes -import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.systemBarsPadding -import androidx.compose.foundation.layout.widthIn import androidx.compose.material3.LocalContentColor import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment -import androidx.compose.ui.BiasAbsoluteAlignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.buildAnnotatedString -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 @@ -50,15 +35,13 @@ import io.element.android.features.login.impl.R import io.element.android.features.login.impl.error.isWaitListError import io.element.android.features.login.impl.error.loginError import io.element.android.libraries.architecture.Async +import io.element.android.libraries.designsystem.atomic.pages.SunsetPage import io.element.android.libraries.designsystem.components.dialogs.RetryDialog 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.CircularProgressIndicator -import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.utils.OnLifecycleEvent -import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings // Ref: https://www.figma.com/file/0MMNu7cTOzLOlWb7ctTkv3/Element-X?type=design&node-id=6761-148425 @@ -75,12 +58,7 @@ fun WaitListView( else -> Unit } } - - Box(modifier = modifier) { - WaitListBackground() - WaitListContent(state, onCancelClicked) - WaitListError(state) - } + WaitListContent(state, onCancelClicked, modifier) } @Composable @@ -101,136 +79,70 @@ private fun WaitListError(state: WaitListState) { } } -@Composable -private fun WaitListBackground( - modifier: Modifier = Modifier, -) { - Column(modifier = modifier.fillMaxSize()) { - Box( - modifier = Modifier - .fillMaxWidth() - .weight(0.3f) - .background(Color.White) - ) - Image( - modifier = Modifier - .fillMaxWidth(), - painter = painterResource(id = R.drawable.light_dark), - contentScale = ContentScale.Crop, - contentDescription = null, - ) - Box( - modifier = Modifier - .fillMaxWidth() - .weight(0.7f) - .background(Color(0xFF121418)) - ) - } -} - @Composable private fun WaitListContent( state: WaitListState, onCancelClicked: () -> Unit, modifier: Modifier = Modifier, ) { - ElementTheme( - darkTheme = true + Box( + modifier = modifier.fillMaxSize(), ) { - Box( - modifier = modifier - .fillMaxSize() - .systemBarsPadding() - .padding(horizontal = 16.dp, vertical = 16.dp) - ) { - if (state.loginAction !is Async.Success) { - CompositionLocalProvider(LocalContentColor provides Color.Black) { - TextButton( - text = stringResource(CommonStrings.action_cancel), - onClick = onCancelClicked, - ) - } - } - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = BiasAbsoluteAlignment( - horizontalBias = 0f, - verticalBias = -0.05f - ) - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally - ) { - if (state.loginAction.isLoading()) { - CircularProgressIndicator( - modifier = Modifier.size(24.dp), - strokeWidth = 2.dp, - color = ElementTheme.colors.iconPrimary - ) - } else { - Spacer(modifier = Modifier.height(24.dp)) - } - Spacer(modifier = Modifier.height(18.dp)) - val titleRes = when (state.loginAction) { - is Async.Success -> R.string.screen_waitlist_title_success - else -> R.string.screen_waitlist_title - } - Text( - text = withColoredPeriod(titleRes), - style = ElementTheme.typography.fontHeadingXlBold, - textAlign = TextAlign.Center, - color = ElementTheme.colors.textPrimary, - ) - Spacer(modifier = Modifier.height(8.dp)) - val subtitle = when (state.loginAction) { - is Async.Success -> stringResource( - id = R.string.screen_waitlist_message_success, - state.appName, - ) - else -> stringResource( - id = R.string.screen_waitlist_message, - state.appName, - state.serverName, - ) - } - Text( - modifier = Modifier.widthIn(max = 360.dp), - text = subtitle, - style = ElementTheme.typography.fontBodyLgRegular, - textAlign = TextAlign.Center, - color = ElementTheme.colors.textPrimary, - ) - } - } - if (state.loginAction is Async.Success) { - Button( - text = stringResource(id = CommonStrings.action_continue), - onClick = { state.eventSink.invoke(WaitListEvents.Continue) }, - modifier = Modifier - .fillMaxWidth() - .align(Alignment.BottomCenter) - .padding(bottom = 8.dp), - ) + val title = stringResource( + when (state.loginAction) { + is Async.Success -> R.string.screen_waitlist_title_success + else -> R.string.screen_waitlist_title } + ) + val subtitle = when (state.loginAction) { + is Async.Success -> stringResource( + id = R.string.screen_waitlist_message_success, + state.appName, + ) + else -> stringResource( + id = R.string.screen_waitlist_message, + state.appName, + state.serverName, + ) } + SunsetPage( + modifier = modifier, + isLoading = state.loginAction !is Async.Success, + title = title, + subtitle = subtitle, + ) { + OverallContent(state, onCancelClicked) + } + WaitListError(state) } } @Composable -private fun withColoredPeriod( - @StringRes textRes: Int, -) = buildAnnotatedString { - val text = stringResource(textRes) - append(text) - if (text.endsWith(".")) { - addStyle( - style = SpanStyle( - // Light.colorGreen700 - color = Color(0xff0bc491), - ), - start = text.length - 1, - end = text.length, - ) +private fun OverallContent( + state: WaitListState, + onCancelClicked: () -> Unit, + modifier: Modifier = Modifier, +) { + Box(modifier = modifier.fillMaxSize()) { + if (state.loginAction !is Async.Success) { + CompositionLocalProvider(LocalContentColor provides Color.Black) { + TextButton( + text = stringResource(CommonStrings.action_cancel), + onClick = onCancelClicked, + ) + } + } + if (state.loginAction is Async.Success) { + Button( + text = stringResource(id = CommonStrings.action_continue), + onClick = { state.eventSink.invoke(WaitListEvents.Continue) }, + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter) + .padding(bottom = 8.dp), + ) + } + } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt new file mode 100644 index 0000000000..17cca583f6 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt @@ -0,0 +1,134 @@ +/* + * 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.pages + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.layout.widthIn +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.BiasAbsoluteAlignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.R +import io.element.android.libraries.designsystem.text.withColoredPeriod +import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.theme.ElementTheme + +@Composable +fun SunsetPage( + isLoading: Boolean, + title: String, + subtitle: String, + modifier: Modifier = Modifier, + overallContent: @Composable () -> Unit, +) { + ElementTheme( + darkTheme = true + ) { + Box( + modifier = modifier.fillMaxSize() + ) { + SunsetBackground() + Box( + modifier = modifier + .fillMaxSize() + .systemBarsPadding() + .padding(horizontal = 16.dp, vertical = 16.dp) + ) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = BiasAbsoluteAlignment( + horizontalBias = 0f, + verticalBias = -0.05f + ) + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + if (isLoading) { + CircularProgressIndicator( + modifier = Modifier.size(24.dp), + strokeWidth = 2.dp, + color = ElementTheme.colors.iconPrimary + ) + } else { + Spacer(modifier = Modifier.height(24.dp)) + } + Spacer(modifier = Modifier.height(18.dp)) + Text( + text = withColoredPeriod(title), + style = ElementTheme.typography.fontHeadingXlBold, + textAlign = TextAlign.Center, + color = ElementTheme.colors.textPrimary, + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + modifier = Modifier.widthIn(max = 360.dp), + text = subtitle, + style = ElementTheme.typography.fontBodyLgRegular, + textAlign = TextAlign.Center, + color = ElementTheme.colors.textPrimary, + ) + } + } + overallContent() + } + } + } +} + +@Composable +private fun SunsetBackground( + modifier: Modifier = Modifier, +) { + Column(modifier = modifier.fillMaxSize()) { + Box( + modifier = Modifier + .fillMaxWidth() + .weight(0.3f) + .background(Color.White) + ) + Image( + modifier = Modifier + .fillMaxWidth(), + painter = painterResource(id = R.drawable.light_dark), + contentScale = ContentScale.Crop, + contentDescription = null, + ) + Box( + modifier = Modifier + .fillMaxWidth() + .weight(0.7f) + .background(Color(0xFF121418)) + ) + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt index 6b16ff96e7..779b7e7053 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt @@ -82,3 +82,22 @@ fun buildAnnotatedStringWithStyledPart( end = startIndex + coloredPart.length, ) } + +/** + * Convert a string to an [AnnotatedString] with colored end period if present. + */ +fun withColoredPeriod( + text: String, +) = buildAnnotatedString { + append(text) + if (text.endsWith(".")) { + addStyle( + style = SpanStyle( + // Light.colorGreen700 + color = Color(0xff0bc491), + ), + start = text.length - 1, + end = text.length, + ) + } +} diff --git a/features/login/impl/src/main/res/drawable/light_dark.png b/libraries/designsystem/src/main/res/drawable/light_dark.png similarity index 100% rename from features/login/impl/src/main/res/drawable/light_dark.png rename to libraries/designsystem/src/main/res/drawable/light_dark.png