From e472a494a1bd0d5fc0eadf9e10904c8e6c92803b Mon Sep 17 00:00:00 2001 From: maxmoney21m Date: Sun, 12 Mar 2023 11:59:16 +0800 Subject: [PATCH] Re-use existing LoginPage component in account switcher --- .../ui/navigation/AccountSwitchBottomSheet.kt | 181 +++--------------- .../amethyst/ui/screen/AccountScreen.kt | 3 +- .../amethyst/ui/screen/loggedIn/MainScreen.kt | 2 +- .../ui/screen/loggedOff/LoginScreen.kt | 86 +++++---- 4 files changed, 75 insertions(+), 197 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt index affca2daf..d090c46b5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt @@ -1,70 +1,42 @@ package com.vitorpamplona.amethyst.ui.navigation -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight 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.width import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.ClickableText -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults -import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme -import androidx.compose.material.ModalBottomSheetState -import androidx.compose.material.OutlinedTextField import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.material.TextButton +import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Logout -import androidx.compose.material.icons.outlined.Visibility -import androidx.compose.material.icons.outlined.VisibilityOff import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier -import androidx.compose.ui.autofill.AutofillNode -import androidx.compose.ui.autofill.AutofillType import androidx.compose.ui.draw.clip -import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.BitmapPainter -import androidx.compose.ui.layout.boundsInWindow -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.LocalAutofill -import androidx.compose.ui.platform.LocalAutofillTree import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.PasswordVisualTransformation -import androidx.compose.ui.text.input.TextFieldValue -import androidx.compose.ui.text.input.VisualTransformation -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import com.vitorpamplona.amethyst.LocalPreferences @@ -75,15 +47,13 @@ import com.vitorpamplona.amethyst.ui.components.ResizeImage import com.vitorpamplona.amethyst.ui.note.toShortenHex import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginPage -@OptIn(ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class) @Composable fun AccountSwitchBottomSheet( accountViewModel: AccountViewModel, - accountStateViewModel: AccountStateViewModel, - sheetState: ModalBottomSheetState + accountStateViewModel: AccountStateViewModel ) { - val coroutineScope = rememberCoroutineScope() val context = LocalContext.current val accounts = LocalPreferences.findAllLocalAccounts() @@ -117,7 +87,8 @@ fun AccountSwitchBottomSheet( Row( modifier = Modifier.clickable { accountStateViewModel.login(acc.npub) - } + }, + verticalAlignment = Alignment.CenterVertically ) { AsyncImageProxy( model = ResizeImage(acc.profilePicture, 64.dp), @@ -141,13 +112,18 @@ fun AccountSwitchBottomSheet( if (current) { Text("✓") } - Spacer(modifier = Modifier.weight(1f)) } + Spacer(modifier = Modifier.weight(1f)) + IconButton( onClick = { accountStateViewModel.logOff(acc.npub) } ) { - Icon(imageVector = Icons.Default.Logout, "Logout") + Icon( + imageVector = Icons.Default.Logout, + contentDescription = "Logout", + tint = MaterialTheme.colors.onSurface + ) } } } @@ -170,126 +146,21 @@ fun AccountSwitchBottomSheet( properties = DialogProperties(usePlatformDefaultWidth = false) ) { Surface(modifier = Modifier.fillMaxSize()) { - Column( - modifier = Modifier - .fillMaxHeight() - .background(MaterialTheme.colors.surface), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - val key = remember { mutableStateOf(TextFieldValue("")) } - var errorMessage by remember { mutableStateOf("") } - var showPassword by remember { - mutableStateOf(false) - } - val autofillNode = AutofillNode( - autofillTypes = listOf(AutofillType.Password), - onFill = { key.value = TextFieldValue(it) } - ) - val autofill = LocalAutofill.current - LocalAutofillTree.current += autofillNode - - OutlinedTextField( - modifier = Modifier - .onGloballyPositioned { coordinates -> - autofillNode.boundingBox = coordinates.boundsInWindow() - } - .onFocusChanged { focusState -> - autofill?.run { - if (focusState.isFocused) { - requestAutofillForNode(autofillNode) - } else { - cancelAutofillForNode(autofillNode) - } - } - }, - value = key.value, - onValueChange = { key.value = it }, - keyboardOptions = KeyboardOptions( - autoCorrect = false, - keyboardType = KeyboardType.Password, - imeAction = ImeAction.Go - ), - placeholder = { - Text( - text = stringResource(R.string.nsec_npub_hex_private_key), - color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) - ) - }, - trailingIcon = { - IconButton(onClick = { showPassword = !showPassword }) { + Box { + LoginPage(accountStateViewModel, isFirstLogin = false) + TopAppBar( + title = { Text(text = "Add New Account") }, + navigationIcon = { + IconButton(onClick = { popupExpanded = false }) { Icon( - imageVector = if (showPassword) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility, - contentDescription = if (showPassword) { - stringResource(R.string.show_password) - } else { - stringResource( - R.string.hide_password - ) - } + imageVector = Icons.Default.ArrowBack, + contentDescription = "Back", + tint = MaterialTheme.colors.onSurface ) } }, - visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(), - keyboardActions = KeyboardActions( - onGo = { - try { - accountStateViewModel.login(key.value.text) - } catch (e: Exception) { - errorMessage = context.getString(R.string.invalid_key) - } - } - ) - ) - if (errorMessage.isNotBlank()) { - Text( - text = errorMessage, - color = MaterialTheme.colors.error, - style = MaterialTheme.typography.caption - ) - } - - Spacer(modifier = Modifier.height(16.dp)) - - Button( - onClick = { - if (key.value.text.isBlank()) { - errorMessage = context.getString(R.string.key_is_required) - } - try { - accountStateViewModel.login(key.value.text) - } catch (e: Exception) { - errorMessage = context.getString(R.string.invalid_key) - } - }, - shape = RoundedCornerShape(35.dp), - modifier = Modifier - .fillMaxWidth() - .height(50.dp), - colors = ButtonDefaults - .buttonColors( - backgroundColor = MaterialTheme.colors.primary - ) - ) { - Text(text = stringResource(R.string.login)) - } - - Spacer(modifier = Modifier.height(16.dp)) - - ClickableText( - text = AnnotatedString(stringResource(R.string.generate_a_new_key)), - modifier = Modifier - .padding(20.dp) - .fillMaxWidth(), - onClick = { - accountStateViewModel.newKey() - }, - style = TextStyle( - fontSize = 14.sp, - textDecoration = TextDecoration.Underline, - color = MaterialTheme.colors.primary, - textAlign = TextAlign.Center - ) + backgroundColor = Color.Transparent, + elevation = 0.dp ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt index d82d6a353..f96914f78 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.MainScreen +import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginPage @Composable fun AccountScreen(accountStateViewModel: AccountStateViewModel, startingPage: String?) { @@ -17,7 +18,7 @@ fun AccountScreen(accountStateViewModel: AccountStateViewModel, startingPage: St Crossfade(targetState = accountState, animationSpec = tween(durationMillis = 100)) { state -> when (state) { is AccountState.LoggedOff -> { - LoginPage(accountStateViewModel) + LoginPage(accountStateViewModel, isFirstLogin = true) } is AccountState.LoggedIn -> { MainScreen(AccountViewModel(state.account), accountStateViewModel, startingPage) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt index a1cb6f101..90fac6a13 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt @@ -47,7 +47,7 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun ModalBottomSheetLayout( sheetState = sheetState, sheetContent = { - AccountSwitchBottomSheet(accountViewModel = accountViewModel, accountStateViewModel = accountStateViewModel, sheetState = sheetState) + AccountSwitchBottomSheet(accountViewModel = accountViewModel, accountStateViewModel = accountStateViewModel) } ) { Scaffold( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt index ffa3c8ce4..d78673f7f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt @@ -1,4 +1,4 @@ -package com.vitorpamplona.amethyst.ui.screen +package com.vitorpamplona.amethyst.ui.screen.loggedOff import androidx.compose.foundation.Image import androidx.compose.foundation.layout.* @@ -36,14 +36,18 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import java.util.* @OptIn(ExperimentalComposeUiApi::class) @Composable -fun LoginPage(accountViewModel: AccountStateViewModel) { +fun LoginPage( + accountViewModel: AccountStateViewModel, + isFirstLogin: Boolean +) { val key = remember { mutableStateOf(TextFieldValue("")) } var errorMessage by remember { mutableStateOf("") } - val acceptedTerms = remember { mutableStateOf(false) } + val acceptedTerms = remember { mutableStateOf(!isFirstLogin) } var termsAcceptanceIsRequired by remember { mutableStateOf("") } val uri = LocalUriHandler.current val context = LocalContext.current @@ -147,48 +151,50 @@ fun LoginPage(accountViewModel: AccountStateViewModel) { Spacer(modifier = Modifier.height(20.dp)) - Row(verticalAlignment = Alignment.CenterVertically) { - Checkbox( - checked = acceptedTerms.value, - onCheckedChange = { acceptedTerms.value = it } - ) + if (isFirstLogin) { + Row(verticalAlignment = Alignment.CenterVertically) { + Checkbox( + checked = acceptedTerms.value, + onCheckedChange = { acceptedTerms.value = it } + ) - val regularText = - SpanStyle(color = MaterialTheme.colors.onBackground) + val regularText = + SpanStyle(color = MaterialTheme.colors.onBackground) - val clickableTextStyle = - SpanStyle(color = MaterialTheme.colors.primary) + val clickableTextStyle = + SpanStyle(color = MaterialTheme.colors.primary) - val annotatedTermsString = buildAnnotatedString { - withStyle(regularText) { - append(stringResource(R.string.i_accept_the)) - } - - withStyle(clickableTextStyle) { - pushStringAnnotation("openTerms", "") - append(stringResource(R.string.terms_of_use)) - } - } - - ClickableText( - text = annotatedTermsString - ) { spanOffset -> - annotatedTermsString.getStringAnnotations(spanOffset, spanOffset) - .firstOrNull() - ?.also { span -> - if (span.tag == "openTerms") { - runCatching { uri.openUri("https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md") } - } + val annotatedTermsString = buildAnnotatedString { + withStyle(regularText) { + append(stringResource(R.string.i_accept_the)) } - } - } - if (termsAcceptanceIsRequired.isNotBlank()) { - Text( - text = termsAcceptanceIsRequired, - color = MaterialTheme.colors.error, - style = MaterialTheme.typography.caption - ) + withStyle(clickableTextStyle) { + pushStringAnnotation("openTerms", "") + append(stringResource(R.string.terms_of_use)) + } + } + + ClickableText( + text = annotatedTermsString + ) { spanOffset -> + annotatedTermsString.getStringAnnotations(spanOffset, spanOffset) + .firstOrNull() + ?.also { span -> + if (span.tag == "openTerms") { + runCatching { uri.openUri("https://github.com/vitorpamplona/amethyst/blob/main/PRIVACY.md") } + } + } + } + } + + if (termsAcceptanceIsRequired.isNotBlank()) { + Text( + text = termsAcceptanceIsRequired, + color = MaterialTheme.colors.error, + style = MaterialTheme.typography.caption + ) + } } Spacer(modifier = Modifier.height(20.dp))