From e6c87ff00b8b991cde8b5aaf2a3dc63a840a2299 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 18 Apr 2023 00:10:42 +0300 Subject: [PATCH] android: profiles UI (#2194) * android: profiles UI * reverted color change * changes * update color, padding * colors changes and error check * focused field color * refactor * focus * layout * profile editor * centered layout * bottom paddings --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- .../java/chat/simplex/app/ui/theme/Theme.kt | 1 + .../chat/simplex/app/views/WelcomeView.kt | 130 +++++++++++------- .../app/views/call/IncomingCallActivity.kt | 14 +- .../app/views/chat/group/GroupProfileView.kt | 39 +++--- .../app/views/helpers/CloseSheetBar.kt | 19 ++- .../app/views/helpers/DefaultTopAppBar.kt | 6 +- .../simplex/app/views/helpers/SimpleButton.kt | 21 ++- .../app/views/newchat/AddContactView.kt | 12 +- .../simplex/app/views/newchat/AddGroupView.kt | 49 ++++--- .../app/views/onboarding/HowItWorks.kt | 5 +- .../views/onboarding/SetNotificationsMode.kt | 44 +++--- .../app/views/onboarding/SimpleXInfo.kt | 38 +++-- .../app/views/usersettings/UserProfileView.kt | 39 ++++-- .../app/src/main/res/values/strings.xml | 1 + 14 files changed, 278 insertions(+), 140 deletions(-) diff --git a/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Theme.kt b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Theme.kt index 8b05e28925..dfe6f68989 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Theme.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Theme.kt @@ -18,6 +18,7 @@ val DEFAULT_PADDING = 16.dp val DEFAULT_SPACE_AFTER_ICON = 4.dp val DEFAULT_PADDING_HALF = DEFAULT_PADDING / 2 val DEFAULT_BOTTOM_PADDING = 48.dp +val DEFAULT_BOTTOM_BUTTON_PADDING = 20.dp val DarkColorPalette = darkColors( primary = SimplexBlue, // If this value changes also need to update #0088ff in string resource files diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt index fbd765213e..1d933ac762 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt @@ -3,22 +3,23 @@ package chat.simplex.app.views import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.* +import androidx.compose.material.MaterialTheme.colors import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ArrowBackIosNew import androidx.compose.material.icons.outlined.ArrowForwardIos import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.* import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.text.style.* import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.app.* @@ -26,61 +27,71 @@ import chat.simplex.app.R import chat.simplex.app.model.ChatModel import chat.simplex.app.model.Profile import chat.simplex.app.ui.theme.* -import chat.simplex.app.views.helpers.AppBarTitle -import chat.simplex.app.views.helpers.withApi +import chat.simplex.app.views.helpers.* import chat.simplex.app.views.onboarding.OnboardingStage import chat.simplex.app.views.onboarding.ReadableText import com.google.accompanist.insets.navigationBarsWithImePadding import kotlinx.coroutines.delay fun isValidDisplayName(name: String) : Boolean { - return (name.firstOrNull { it.isWhitespace() }) == null + return (name.firstOrNull { it.isWhitespace() }) == null && !name.startsWith("@") && !name.startsWith("#") } @Composable fun CreateProfilePanel(chatModel: ChatModel, close: () -> Unit) { - val displayName = remember { mutableStateOf("") } - val fullName = remember { mutableStateOf("") } + val displayName = rememberSaveable { mutableStateOf("") } + val fullName = rememberSaveable { mutableStateOf("") } val focusRequester = remember { FocusRequester() } Surface(Modifier.background(MaterialTheme.colors.onBackground)) { Column( modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()) ) { - AppBarTitle(stringResource(R.string.create_profile), false) - ReadableText(R.string.your_profile_is_stored_on_your_device) - ReadableText(R.string.profile_is_only_shared_with_your_contacts) - Spacer(Modifier.height(10.dp)) - Text( - stringResource(R.string.display_name), - style = MaterialTheme.typography.h6, - modifier = Modifier.padding(bottom = 3.dp) - ) - ProfileNameField(displayName, focusRequester) - val errorText = if (!isValidDisplayName(displayName.value)) stringResource(R.string.display_name_cannot_contain_whitespace) else "" - Text( - errorText, - fontSize = 15.sp, - color = MaterialTheme.colors.error - ) - Spacer(Modifier.height(3.dp)) - Text( - stringResource(R.string.full_name_optional__prompt), - style = MaterialTheme.typography.h6, - modifier = Modifier.padding(bottom = 5.dp) - ) - ProfileNameField(fullName) + /*CloseSheetBar(close = { + if (chatModel.users.isEmpty()) { + chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo + } else { + close() + } + })*/ + Column(Modifier.padding(horizontal = DEFAULT_PADDING * 1f)) { + AppBarTitleCentered(stringResource(R.string.create_profile)) + ReadableText(R.string.your_profile_is_stored_on_your_device, TextAlign.Center, padding = PaddingValues()) + ReadableText(R.string.profile_is_only_shared_with_your_contacts, TextAlign.Center) + Spacer(Modifier.height(DEFAULT_PADDING * 1.5f)) + Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Text( + stringResource(R.string.display_name), + fontSize = 16.sp + ) + if (!isValidDisplayName(displayName.value)) { + Text( + stringResource(R.string.no_spaces), + fontSize = 16.sp, + color = Color.Red + ) + } + } + ProfileNameField(displayName, "", ::isValidDisplayName, focusRequester) + Spacer(Modifier.height(DEFAULT_PADDING)) + Text( + stringResource(R.string.full_name_optional__prompt), + fontSize = 16.sp, + modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF) + ) + ProfileNameField(fullName, "", ::isValidDisplayName) + } Spacer(Modifier.fillMaxHeight().weight(1f)) Row { if (chatModel.users.isEmpty()) { - SimpleButton( + SimpleButtonDecorated( text = stringResource(R.string.about_simplex), - icon = Icons.Outlined.ArrowBackIosNew + icon = Icons.Outlined.ArrowBackIosNew, + textDecoration = TextDecoration.None, + fontWeight = FontWeight.Bold ) { chatModel.onboardingStage.value = OnboardingStage.Step1_SimpleXInfo } } - Spacer(Modifier.fillMaxWidth().weight(1f)) - val enabled = displayName.value.isNotEmpty() && isValidDisplayName(displayName.value) val createModifier: Modifier val createColor: Color @@ -93,7 +104,7 @@ fun CreateProfilePanel(chatModel: ChatModel, close: () -> Unit) { } Surface(shape = RoundedCornerShape(20.dp)) { Row(verticalAlignment = Alignment.CenterVertically, modifier = createModifier) { - Text(stringResource(R.string.create_profile_button), style = MaterialTheme.typography.caption, color = createColor) + Text(stringResource(R.string.create_profile_button), style = MaterialTheme.typography.caption, color = createColor, fontWeight = FontWeight.Medium) Icon(Icons.Outlined.ArrowForwardIos, stringResource(R.string.create_profile_button), tint = createColor) } } @@ -128,24 +139,47 @@ fun createProfile(chatModel: ChatModel, displayName: String, fullName: String, c } @Composable -fun ProfileNameField(name: MutableState, focusRequester: FocusRequester? = null) { +fun ProfileNameField(name: MutableState, placeholder: String = "", isValid: (String) -> Boolean = { true }, focusRequester: FocusRequester? = null) { + var valid by rememberSaveable { mutableStateOf(true) } + var focused by rememberSaveable { mutableStateOf(false) } + val strokeColor by remember { + derivedStateOf { + if (valid) { + if (focused) { + HighOrLowlight.copy(alpha = 0.6f) + } else { + HighOrLowlight.copy(alpha = 0.3f) + } + } else Color.Red + } + } val modifier = Modifier .fillMaxWidth() - .background(MaterialTheme.colors.secondary) - .height(40.dp) - .clip(RoundedCornerShape(5.dp)) - .padding(8.dp) + .height(55.dp) + .border(border = BorderStroke(1.dp, strokeColor), shape = RoundedCornerShape(50)) + .padding(horizontal = 8.dp) .navigationBarsWithImePadding() - BasicTextField( + .onFocusChanged { focused = it.isFocused } + TextField( value = name.value, - onValueChange = { name.value = it }, + onValueChange = { name.value = it; valid = isValid(it) }, modifier = if (focusRequester == null) modifier else modifier.focusRequester(focusRequester), - textStyle = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground), + textStyle = TextStyle(fontSize = 18.sp, color = colors.onBackground), keyboardOptions = KeyboardOptions( capitalization = KeyboardCapitalization.None, autoCorrect = false ), singleLine = true, - cursorBrush = SolidColor(HighOrLowlight) - ) + isError = !valid, + placeholder = { Text(placeholder, fontSize = 18.sp, color = HighOrLowlight.copy(alpha = 0.3f)) }, + shape = RoundedCornerShape(50), + colors = TextFieldDefaults.textFieldColors( + backgroundColor = Color.Unspecified, + textColor = MaterialTheme.colors.onBackground, + focusedIndicatorColor = Color.Unspecified, + unfocusedIndicatorColor = Color.Unspecified, + cursorColor = HighOrLowlight, + errorIndicatorColor = Color.Unspecified + ) + ) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt b/apps/android/app/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt index 5877bb6d4d..ba8830c533 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt @@ -12,6 +12,7 @@ import android.view.WindowManager import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -25,6 +26,7 @@ import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -36,7 +38,6 @@ import chat.simplex.app.model.* import chat.simplex.app.model.NtfManager.Companion.OpenChatAction import chat.simplex.app.ui.theme.* import chat.simplex.app.views.helpers.ProfileImage -import chat.simplex.app.views.onboarding.SimpleXLogo import kotlinx.datetime.Clock class IncomingCallActivity: ComponentActivity() { @@ -186,6 +187,17 @@ fun IncomingCallLockScreenAlertLayout( } } +@Composable +private fun SimpleXLogo() { + Image( + painter = painterResource(if (isInDarkTheme()) R.drawable.logo_light else R.drawable.logo), + contentDescription = stringResource(R.string.image_descr_simplex_logo), + modifier = Modifier + .padding(vertical = DEFAULT_PADDING) + .fillMaxWidth(0.80f) + ) +} + @Composable private fun LockScreenCallButton(text: String, icon: ImageVector, color: Color, action: () -> Unit) { Surface( diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupProfileView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupProfileView.kt index ce20848636..89271baf2e 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupProfileView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupProfileView.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -53,8 +54,8 @@ fun GroupProfileLayout( saveProfile: (GroupProfile) -> Unit, ) { val bottomSheetModalState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden) - val displayName = remember { mutableStateOf(groupProfile.displayName) } - val fullName = remember { mutableStateOf(groupProfile.fullName) } + val displayName = rememberSaveable { mutableStateOf(groupProfile.displayName) } + val fullName = rememberSaveable { mutableStateOf(groupProfile.fullName) } val chosenImage = rememberSaveable { mutableStateOf(null) } val profileImage = rememberSaveable { mutableStateOf(groupProfile.image) } val scope = rememberCoroutineScope() @@ -109,24 +110,29 @@ fun GroupProfileLayout( } } } - Text( - stringResource(R.string.group_display_name_field), - Modifier.padding(bottom = 3.dp) - ) - ProfileNameField(displayName, focusRequester) - val errorText = if (!isValidDisplayName(displayName.value)) stringResource(R.string.display_name_cannot_contain_whitespace) else "" - Text( - errorText, - fontSize = 15.sp, - color = MaterialTheme.colors.error - ) - Spacer(Modifier.height(3.dp)) + Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Text( + stringResource(R.string.group_display_name_field), + fontSize = 16.sp + ) + if (!isValidDisplayName(displayName.value)) { + Spacer(Modifier.size(DEFAULT_PADDING_HALF)) + Text( + stringResource(R.string.no_spaces), + fontSize = 16.sp, + color = Color.Red + ) + } + } + ProfileNameField(displayName, "", ::isValidDisplayName, focusRequester) + Spacer(Modifier.height(DEFAULT_PADDING)) Text( stringResource(R.string.group_full_name_field), - Modifier.padding(bottom = 5.dp) + fontSize = 16.sp, + modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF) ) ProfileNameField(fullName) - Spacer(Modifier.height(16.dp)) + Spacer(Modifier.height(DEFAULT_PADDING)) Row { TextButton(stringResource(R.string.cancel_verb)) { close.invoke() @@ -153,6 +159,7 @@ fun GroupProfileLayout( } } } + Spacer(Modifier.height(DEFAULT_BOTTOM_BUTTON_PADDING)) LaunchedEffect(Unit) { delay(300) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt index 6a7ab82771..5a910c563a 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt @@ -6,13 +6,15 @@ import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import chat.simplex.app.R import chat.simplex.app.ui.theme.* @Composable -fun CloseSheetBar(close: () -> Unit, endButtons: @Composable RowScope.() -> Unit = {}) { +fun CloseSheetBar(close: (() -> Unit)?, endButtons: @Composable RowScope.() -> Unit = {}) { Column( Modifier .fillMaxWidth() @@ -28,7 +30,7 @@ fun CloseSheetBar(close: () -> Unit, endButtons: @Composable RowScope.() -> Unit horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - NavigationButtonBack(close) + NavigationButtonBack(onButtonClicked = close) Row { endButtons() } @@ -54,6 +56,19 @@ fun AppBarTitle(title: String, withPadding: Boolean = true) { ) } +@Composable +fun ColumnScope.AppBarTitleCentered(title: String, withPadding: Boolean = true) { + Text( + title, + Modifier + .padding(bottom = if (withPadding) DEFAULT_PADDING * 1.5f else 0.dp) + .align(Alignment.CenterHorizontally), + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.h1, + color = MaterialTheme.colors.primary + ) +} + @Preview(showBackground = true) @Preview( uiMode = Configuration.UI_MODE_NIGHT_YES, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultTopAppBar.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultTopAppBar.kt index 8544fdc435..6dfe6f3408 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultTopAppBar.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultTopAppBar.kt @@ -45,10 +45,10 @@ fun DefaultTopAppBar( } @Composable -fun NavigationButtonBack(onButtonClicked: () -> Unit) { - IconButton(onButtonClicked) { +fun NavigationButtonBack(onButtonClicked: (() -> Unit)?) { + IconButton(onButtonClicked ?: {}, enabled = onButtonClicked != null) { Icon( - Icons.Outlined.ArrowBackIos, stringResource(R.string.back), tint = MaterialTheme.colors.primary + Icons.Outlined.ArrowBackIos, stringResource(R.string.back), tint = if (onButtonClicked != null) MaterialTheme.colors.primary else HighOrLowlight ) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt index 5068dd837f..ba8176edda 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt @@ -12,6 +12,8 @@ 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.text.font.FontWeight +import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -28,6 +30,21 @@ fun SimpleButton(text: String, icon: ImageVector, } } +@Composable +fun SimpleButtonDecorated(text: String, icon: ImageVector, + color: Color = MaterialTheme.colors.primary, + textDecoration: TextDecoration = TextDecoration.Underline, + fontWeight: FontWeight = FontWeight.Normal, + click: () -> Unit) { + SimpleButtonFrame(click) { + Icon( + icon, text, tint = color, + modifier = Modifier.padding(end = 8.dp) + ) + Text(text, style = MaterialTheme.typography.caption, fontWeight = fontWeight, color = color, textDecoration = textDecoration) + } +} + @Composable fun SimpleButton( text: String, icon: ImageVector, @@ -61,9 +78,9 @@ fun SimpleButtonIconEnded( } @Composable -fun SimpleButtonFrame(click: () -> Unit, disabled: Boolean = false, content: @Composable () -> Unit) { +fun SimpleButtonFrame(click: () -> Unit, modifier: Modifier = Modifier, disabled: Boolean = false, content: @Composable () -> Unit) { Surface(shape = RoundedCornerShape(20.dp)) { - val modifier = if (disabled) Modifier else Modifier.clickable { click() } + val modifier = if (disabled) modifier else modifier.clickable { click() } Row( verticalAlignment = Alignment.CenterVertically, modifier = modifier.padding(8.dp) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt index 62b2752e18..6fd7c26b54 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddContactView.kt @@ -88,13 +88,14 @@ fun AddContactLayout(connReq: String, connIncognito: Boolean, share: () -> Unit) } @Composable -fun InfoAboutIncognito(chatModelIncognito: Boolean, supportedIncognito: Boolean = true, onText: String, offText: String) { +fun InfoAboutIncognito(chatModelIncognito: Boolean, supportedIncognito: Boolean = true, onText: String, offText: String, centered: Boolean = false) { if (chatModelIncognito) { Row( Modifier .fillMaxWidth() .padding(vertical = 4.dp), - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = if (centered) Arrangement.Center else Arrangement.Start ) { Icon( if (supportedIncognito) Icons.Filled.TheaterComedy else Icons.Outlined.Info, @@ -102,14 +103,15 @@ fun InfoAboutIncognito(chatModelIncognito: Boolean, supportedIncognito: Boolean tint = if (supportedIncognito) Indigo else WarningOrange, modifier = Modifier.padding(end = 10.dp).size(20.dp) ) - Text(onText, textAlign = TextAlign.Left, style = MaterialTheme.typography.body2) + Text(onText, textAlign = if (centered) TextAlign.Center else TextAlign.Left, style = MaterialTheme.typography.body2) } } else { Row( Modifier .fillMaxWidth() .padding(vertical = 4.dp), - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = if (centered) Arrangement.Center else Arrangement.Start ) { Icon( Icons.Outlined.Info, @@ -117,7 +119,7 @@ fun InfoAboutIncognito(chatModelIncognito: Boolean, supportedIncognito: Boolean tint = HighOrLowlight, modifier = Modifier.padding(end = 10.dp).size(20.dp) ) - Text(offText, textAlign = TextAlign.Left, style = MaterialTheme.typography.body2) + Text(offText, textAlign = if (centered) TextAlign.Center else TextAlign.Left, style = MaterialTheme.typography.body2) } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddGroupView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddGroupView.kt index e9b9ee23fa..a4b64107bb 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddGroupView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/AddGroupView.kt @@ -15,6 +15,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -60,8 +62,8 @@ fun AddGroupView(chatModel: ChatModel, close: () -> Unit) { fun AddGroupLayout(chatModelIncognito: Boolean, createGroup: (GroupProfile) -> Unit, close: () -> Unit) { val bottomSheetModalState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden) val scope = rememberCoroutineScope() - val displayName = remember { mutableStateOf("") } - val fullName = remember { mutableStateOf("") } + val displayName = rememberSaveable { mutableStateOf("") } + val fullName = rememberSaveable { mutableStateOf("") } val chosenImage = rememberSaveable { mutableStateOf(null) } val profileImage = rememberSaveable { mutableStateOf(null) } val focusRequester = remember { FocusRequester() } @@ -88,13 +90,14 @@ fun AddGroupLayout(chatModelIncognito: Boolean, createGroup: (GroupProfile) -> U .verticalScroll(rememberScrollState()) .padding(horizontal = DEFAULT_PADDING) ) { - AppBarTitle(stringResource(R.string.create_secret_group_title), false) - Text(stringResource(R.string.group_is_decentralized)) + AppBarTitleCentered(stringResource(R.string.create_secret_group_title)) + Text(stringResource(R.string.group_is_decentralized), Modifier.fillMaxWidth(), textAlign = TextAlign.Center) InfoAboutIncognito( chatModelIncognito, false, generalGetString(R.string.group_unsupported_incognito_main_profile_sent), - generalGetString(R.string.group_main_profile_sent) + generalGetString(R.string.group_main_profile_sent), + true ) Box( Modifier @@ -112,24 +115,28 @@ fun AddGroupLayout(chatModelIncognito: Boolean, createGroup: (GroupProfile) -> U } } } - Text( - stringResource(R.string.group_display_name_field), - Modifier.padding(bottom = 3.dp) - ) - ProfileNameField(displayName, focusRequester) - val errorText = if (!isValidDisplayName(displayName.value)) stringResource(R.string.display_name_cannot_contain_whitespace) else "" - Text( - errorText, - fontSize = 15.sp, - color = MaterialTheme.colors.error - ) - Spacer(Modifier.height(3.dp)) + Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Text( + stringResource(R.string.group_display_name_field), + fontSize = 16.sp + ) + if (!isValidDisplayName(displayName.value)) { + Spacer(Modifier.size(DEFAULT_PADDING_HALF)) + Text( + stringResource(R.string.no_spaces), + fontSize = 16.sp, + color = Color.Red + ) + } + } + ProfileNameField(displayName, "", ::isValidDisplayName, focusRequester) + Spacer(Modifier.height(DEFAULT_PADDING)) Text( stringResource(R.string.group_full_name_field), - Modifier.padding(bottom = 5.dp) + fontSize = 16.sp, + modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF) ) - ProfileNameField(fullName) - + ProfileNameField(fullName, "") Spacer(Modifier.height(8.dp)) val enabled = displayName.value.isNotEmpty() && isValidDisplayName(displayName.value) if (enabled) { @@ -163,7 +170,7 @@ fun CreateGroupButton(color: Color, modifier: Modifier) { ) { Surface(shape = RoundedCornerShape(20.dp)) { Row(modifier, verticalAlignment = Alignment.CenterVertically) { - Text(stringResource(R.string.create_profile_button), style = MaterialTheme.typography.caption, color = color) + Text(stringResource(R.string.create_profile_button), style = MaterialTheme.typography.caption, color = color, fontWeight = FontWeight.Bold) Icon(Icons.Outlined.ArrowForwardIos, stringResource(R.string.create_profile_button), tint = color) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/HowItWorks.kt b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/HowItWorks.kt index 33c4e7ea1a..27f3200fe2 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/HowItWorks.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/HowItWorks.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -55,8 +56,8 @@ fun HowItWorks(user: User?, onboardingStage: MutableState? = n } @Composable -fun ReadableText(@StringRes stringResId: Int) { - Text(annotatedStringResource(stringResId), modifier = Modifier.padding(bottom = 12.dp), lineHeight = 22.sp) +fun ReadableText(@StringRes stringResId: Int, textAlign: TextAlign = TextAlign.Start, padding: PaddingValues = PaddingValues(bottom = 12.dp)) { + Text(annotatedStringResource(stringResId), modifier = Modifier.padding(padding), textAlign = textAlign, lineHeight = 22.sp) } @Preview(showBackground = true) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.kt b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.kt index a96f517208..c577fce4de 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/SetNotificationsMode.kt @@ -12,6 +12,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.app.R @@ -23,22 +25,22 @@ import chat.simplex.app.views.usersettings.changeNotificationsMode @Composable fun SetNotificationsMode(m: ChatModel) { - Column( - Modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()) - .padding(20.dp) - ) { - AppBarTitle(stringResource(R.string.onboarding_notifications_mode_title), false) - val currentMode = rememberSaveable { mutableStateOf(NotificationsMode.default) } - Text(stringResource(R.string.onboarding_notifications_mode_subtitle)) - Spacer(Modifier.padding(DEFAULT_PADDING_HALF)) + Column( + modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()) + ) { + //CloseSheetBar(null) + AppBarTitleCentered(stringResource(R.string.onboarding_notifications_mode_title)) + val currentMode = rememberSaveable { mutableStateOf(NotificationsMode.default) } + Column(Modifier.padding(horizontal = DEFAULT_PADDING * 1f)) { + Text(stringResource(R.string.onboarding_notifications_mode_subtitle), Modifier.fillMaxWidth(), textAlign = TextAlign.Center) + Spacer(Modifier.height(DEFAULT_PADDING * 2f)) NotificationButton(currentMode, NotificationsMode.OFF, R.string.onboarding_notifications_mode_off, R.string.onboarding_notifications_mode_off_desc) NotificationButton(currentMode, NotificationsMode.PERIODIC, R.string.onboarding_notifications_mode_periodic, R.string.onboarding_notifications_mode_periodic_desc) NotificationButton(currentMode, NotificationsMode.SERVICE, R.string.onboarding_notifications_mode_service, R.string.onboarding_notifications_mode_service_desc) - Spacer(Modifier.fillMaxHeight().weight(1f)) - Box(Modifier.fillMaxWidth().padding(bottom = 16.dp), contentAlignment = Alignment.Center) { - OnboardingActionButton(R.string.use_chat, OnboardingStage.OnboardingComplete, m.onboardingStage) { + } + Spacer(Modifier.fillMaxHeight().weight(1f)) + Box(Modifier.fillMaxWidth().padding(bottom = 16.dp), contentAlignment = Alignment.Center) { + OnboardingActionButton(R.string.use_chat, OnboardingStage.OnboardingComplete, m.onboardingStage, false) { changeNotificationsMode(currentMode.value, m) } } @@ -51,18 +53,24 @@ private fun NotificationButton(currentMode: MutableState, mod TextButton( onClick = { currentMode.value = mode }, border = BorderStroke(1.dp, color = if (currentMode.value == mode) MaterialTheme.colors.primary else HighOrLowlight.copy(alpha = 0.5f)), - shape = RoundedCornerShape(15.dp), + shape = RoundedCornerShape(35.dp), ) { - Column(Modifier.padding(bottom = 6.dp).padding(horizontal = 8.dp)) { + Column(Modifier.padding(14.dp)) { Text( stringResource(title), style = MaterialTheme.typography.h2, fontWeight = FontWeight.Medium, color = if (currentMode.value == mode) MaterialTheme.colors.primary else HighOrLowlight, - modifier = Modifier.padding(bottom = 4.dp) + modifier = Modifier.padding(bottom = 14.dp).align(Alignment.CenterHorizontally), + textAlign = TextAlign.Center + ) + Text(annotatedStringResource(description), + Modifier.align(Alignment.CenterHorizontally), + color = MaterialTheme.colors.onBackground, + lineHeight = 24.sp, + textAlign = TextAlign.Center ) - Text(annotatedStringResource(description), color = MaterialTheme.colors.onBackground, lineHeight = 24.sp) } } - Spacer(Modifier.height(DEFAULT_PADDING)) + Spacer(Modifier.height(14.dp)) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/SimpleXInfo.kt b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/SimpleXInfo.kt index 9993136fd8..a315ac2376 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/SimpleXInfo.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/onboarding/SimpleXInfo.kt @@ -4,6 +4,7 @@ import android.content.res.Configuration import androidx.annotation.StringRes import androidx.compose.foundation.* import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ArrowForwardIos @@ -44,13 +45,13 @@ fun SimpleXInfoLayout( Modifier .fillMaxSize() .verticalScroll(rememberScrollState()) - .padding(horizontal = DEFAULT_PADDING), + .padding(start = DEFAULT_PADDING * 1.5f, end = DEFAULT_PADDING * 1.5f, top = DEFAULT_PADDING * 4,/* bottom = DEFAULT_PADDING * 4*/), ) { - Box(Modifier.fillMaxWidth().padding(top = 8.dp), contentAlignment = Alignment.Center) { + Box(Modifier.fillMaxWidth().padding(top = 8.dp, bottom = 10.dp), contentAlignment = Alignment.Center) { SimpleXLogo() } - Text(stringResource(R.string.next_generation_of_private_messaging), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = 36.dp).padding(horizontal = 48.dp), textAlign = TextAlign.Center) + Text(stringResource(R.string.next_generation_of_private_messaging), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = 60.dp).padding(horizontal = 48.dp), textAlign = TextAlign.Center) InfoRow(painterResource(R.drawable.privacy), R.string.privacy_redefined, R.string.first_platform_without_user_ids, width = 80.dp) InfoRow(painterResource(R.drawable.shield), R.string.immune_to_spam_and_abuse, R.string.people_can_connect_only_via_links_you_share) @@ -68,11 +69,12 @@ fun SimpleXInfoLayout( Box( Modifier .fillMaxWidth() - .padding(bottom = 16.dp), contentAlignment = Alignment.Center + .padding(bottom = DEFAULT_PADDING, top = DEFAULT_PADDING), contentAlignment = Alignment.Center ) { - SimpleButton(text = stringResource(R.string.how_it_works), icon = Icons.Outlined.Info, + SimpleButtonDecorated(text = stringResource(R.string.how_it_works), icon = Icons.Outlined.Info, click = showModal { HowItWorks(user, onboardingStage) }) } + Spacer(Modifier.weight(1f)) } } @@ -83,7 +85,7 @@ fun SimpleXLogo() { contentDescription = stringResource(R.string.image_descr_simplex_logo), modifier = Modifier .padding(vertical = DEFAULT_PADDING) - .fillMaxWidth(0.80f) + .fillMaxWidth(0.60f) ) } @@ -103,9 +105,9 @@ private fun InfoRow(icon: Painter, @StringRes titleId: Int, @StringRes textId: I @Composable fun OnboardingActionButton(user: User?, onboardingStage: MutableState, onclick: (() -> Unit)? = null) { if (user == null) { - OnboardingActionButton(R.string.create_your_profile, onboarding = OnboardingStage.Step2_CreateProfile, onboardingStage, onclick) + OnboardingActionButton(R.string.create_your_profile, onboarding = OnboardingStage.Step2_CreateProfile, onboardingStage, true, onclick) } else { - OnboardingActionButton(R.string.make_private_connection, onboarding = OnboardingStage.OnboardingComplete, onboardingStage, onclick) + OnboardingActionButton(R.string.make_private_connection, onboarding = OnboardingStage.OnboardingComplete, onboardingStage, true, onclick) } } @@ -114,16 +116,30 @@ fun OnboardingActionButton( @StringRes labelId: Int, onboarding: OnboardingStage?, onboardingStage: MutableState, + border: Boolean, onclick: (() -> Unit)? ) { + val modifier = if (border) { + Modifier + .border(border = BorderStroke(1.dp, MaterialTheme.colors.primary), shape = RoundedCornerShape(50)) + .padding( + horizontal = DEFAULT_PADDING * 3, + vertical = 4.dp + ) + } else { + Modifier + } + SimpleButtonFrame(click = { onclick?.invoke() onboardingStage.value = onboarding - }) { - Text(stringResource(labelId), style = MaterialTheme.typography.h2, color = MaterialTheme.colors.primary) + }, modifier) { + Text(stringResource(labelId), style = MaterialTheme.typography.h2, color = MaterialTheme.colors.primary, fontSize = 20.sp) Icon( Icons.Outlined.ArrowForwardIos, "next stage", tint = MaterialTheme.colors.primary, - modifier = Modifier.padding(end = 8.dp) + modifier = Modifier + .padding(start = 16.dp, top = 5.dp) + .size(15.dp) ) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt index 905c01f3a5..a00eb73420 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt @@ -16,18 +16,22 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.app.R import chat.simplex.app.model.* import chat.simplex.app.ui.theme.* +import chat.simplex.app.views.ProfileNameField import chat.simplex.app.views.helpers.* import chat.simplex.app.views.isValidDisplayName +import chat.simplex.app.views.onboarding.ReadableText import com.google.accompanist.insets.ProvideWindowInsets import com.google.accompanist.insets.navigationBarsWithImePadding import kotlinx.coroutines.launch @@ -72,6 +76,7 @@ fun UserProfileLayout( val scrollState = rememberScrollState() val keyboardState by getKeyboardState() var savedKeyboardState by remember { mutableStateOf(keyboardState) } + val focusRequester = remember { FocusRequester() } ProvideWindowInsets(windowInsetsAnimationsEnabled = true) { ModalBottomSheetLayout( scrimColor = Color.Black.copy(alpha = 0.12F), @@ -94,13 +99,8 @@ fun UserProfileLayout( .padding(horizontal = DEFAULT_PADDING), horizontalAlignment = Alignment.Start ) { - AppBarTitle(stringResource(R.string.your_current_profile), false) - Text( - stringResource(R.string.your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it), - Modifier.padding(bottom = 24.dp), - color = MaterialTheme.colors.onBackground, - lineHeight = 22.sp - ) + AppBarTitleCentered(stringResource(R.string.your_current_profile)) + ReadableText(R.string.your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it, TextAlign.Center) if (editProfile.value) { Column( Modifier.fillMaxWidth(), @@ -122,13 +122,29 @@ fun UserProfileLayout( } } } - Box { + Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Text( + stringResource(R.string.display_name__field), + fontSize = 16.sp + ) if (!isValidDisplayName(displayName.value)) { - Icon(Icons.Outlined.Info, tint = Color.Red, contentDescription = stringResource(R.string.display_name_cannot_contain_whitespace)) + Spacer(Modifier.size(DEFAULT_PADDING_HALF)) + Text( + stringResource(R.string.no_spaces), + fontSize = 16.sp, + color = Color.Red + ) } - ProfileNameTextField(displayName) } - ProfileNameTextField(fullName) + ProfileNameField(displayName, "", ::isValidDisplayName, focusRequester) + Spacer(Modifier.height(DEFAULT_PADDING)) + Text( + stringResource(R.string.full_name__field), + fontSize = 16.sp, + modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF) + ) + ProfileNameField(fullName) + Spacer(Modifier.height(DEFAULT_PADDING)) Row { TextButton(stringResource(R.string.cancel_verb)) { displayName.value = profile.displayName @@ -178,6 +194,7 @@ fun UserProfileLayout( TextButton(stringResource(R.string.edit_verb)) { editProfile.value = true } } } + Spacer(Modifier.height(DEFAULT_BOTTOM_BUTTON_PADDING)) if (savedKeyboardState != keyboardState) { LaunchedEffect(keyboardState) { scope.launch { diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml index 609cb8a836..e340191e78 100644 --- a/apps/android/app/src/main/res/values/strings.xml +++ b/apps/android/app/src/main/res/values/strings.xml @@ -607,6 +607,7 @@ Create profile Your profile, contacts and delivered messages are stored on your device. The profile is only shared with your contacts. + No spaces! Display name cannot contain whitespace. Display Name Full Name (optional)