From 9ee28e28b85f02af9b54f9e87c11714d64a95c6d Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 12 Sep 2024 15:57:35 -0400 Subject: [PATCH] If already logged in and an NFC tag shows up with ncryptsec, logs in as a transient account --- .../ui/navigation/AccountSwitchBottomSheet.kt | 32 +-------- .../amethyst/ui/navigation/AppNavigation.kt | 40 +++++++---- .../amethyst/ui/screen/AccountScreen.kt | 2 +- .../ui/screen/loggedOff/AddAccountDialog.kt | 70 +++++++++++++++++++ .../screen/loggedOff/LoginOrSignupScreen.kt | 7 +- .../ui/screen/loggedOff/LoginScreen.kt | 3 +- 6 files changed, 106 insertions(+), 48 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/AddAccountDialog.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt index c5c7462cd0..2c5d851810 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AccountSwitchBottomSheet.kt @@ -26,7 +26,6 @@ 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.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width @@ -40,11 +39,8 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf @@ -59,8 +55,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties import com.vitorpamplona.amethyst.AccountInfo import com.vitorpamplona.amethyst.LocalPreferences import com.vitorpamplona.amethyst.R @@ -69,11 +63,10 @@ import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage -import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon 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.LoginOrSignupScreen +import com.vitorpamplona.amethyst.ui.screen.loggedOff.AddAccountDialog import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.AccountPictureModifier import com.vitorpamplona.amethyst.ui.theme.Size10dp @@ -125,28 +118,7 @@ fun AccountSwitchBottomSheet( } if (popupExpanded) { - Dialog( - onDismissRequest = { popupExpanded = false }, - properties = DialogProperties(usePlatformDefaultWidth = false), - ) { - Surface(modifier = Modifier.fillMaxSize()) { - Box { - LoginOrSignupScreen(accountStateViewModel, isFirstLogin = false) - TopAppBar( - title = { - Text(text = stringRes(R.string.account_switch_add_account_dialog_title)) - }, - navigationIcon = { - IconButton(onClick = { popupExpanded = false }) { ArrowBackIcon() } - }, - colors = - TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.surface, - ), - ) - } - } - } + AddAccountDialog(null, accountStateViewModel) { popupExpanded = false } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index 04e3c22725..0ea38edd45 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -41,7 +41,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalContext import androidx.core.util.Consumer import androidx.navigation.NavBackStackEntry -import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import com.vitorpamplona.amethyst.R @@ -72,7 +71,9 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SecurityFiltersScr import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.ThreadScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.VideoScreen +import com.vitorpamplona.amethyst.ui.screen.loggedOff.AddAccountDialog import com.vitorpamplona.amethyst.ui.uriToRoute +import com.vitorpamplona.quartz.encoders.Nip19Bech32 import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.net.URLDecoder @@ -284,7 +285,7 @@ fun AppNavigation( } } - NavigateIfIntentRequested(nav.controller, accountViewModel) + NavigateIfIntentRequested(nav, accountViewModel, accountStateViewModel) DisplayErrorMessages(accountViewModel) DisplayNotifyMessages(accountViewModel, nav) @@ -292,10 +293,12 @@ fun AppNavigation( @Composable private fun NavigateIfIntentRequested( - navController: NavHostController, + nav: Nav, accountViewModel: AccountViewModel, + accountStateViewModel: AccountStateViewModel, ) { val activity = LocalContext.current.getActivity() + var newAccount by remember { mutableStateOf(null) } var currentIntentNextPage by remember { mutableStateOf( @@ -314,15 +317,19 @@ private fun NavigateIfIntentRequested( LaunchedEffect(intentNextPage) { if (actionableNextPage != null) { actionableNextPage?.let { - val currentRoute = getRouteWithArguments(navController) + val currentRoute = getRouteWithArguments(nav.controller) if (!isSameRoute(currentRoute, it)) { - navController.navigate(it) { - popUpTo(Route.Home.route) - launchSingleTop = true - } + nav.newStack(it) } actionableNextPage = null } + } else if (intentNextPage.contains("ncryptsec1")) { + // login functions + Nip19Bech32.tryParseAndClean(intentNextPage)?.let { + newAccount = it + } + + actionableNextPage = null } else { accountViewModel.toast( R.string.invalid_nip19_uri, @@ -342,15 +349,18 @@ private fun NavigateIfIntentRequested( Consumer { intent -> val uri = intent?.data?.toString() if (!uri.isNullOrBlank()) { + // navigation functions val newPage = uriToRoute(uri) if (newPage != null) { - val currentRoute = getRouteWithArguments(navController) + val currentRoute = getRouteWithArguments(nav.controller) if (!isSameRoute(currentRoute, newPage)) { - navController.navigate(newPage) { - popUpTo(Route.Home.route) - launchSingleTop = true - } + nav.newStack(newPage) + } + } else if (uri.contains("ncryptsec")) { + // login functions + Nip19Bech32.tryParseAndClean(uri)?.let { + newAccount = it } } else { scope.launch { @@ -367,6 +377,10 @@ private fun NavigateIfIntentRequested( activity.addOnNewIntentListener(consumer) onDispose { activity.removeOnNewIntentListener(consumer) } } + + if (newAccount != null) { + AddAccountDialog(newAccount, accountStateViewModel) { newAccount = null } + } } fun Context.getActivity(): MainActivity { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt index a4689c8716..7693e2d7c1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt @@ -88,7 +88,7 @@ fun AccountScreen( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background, ) { - LoginOrSignupScreen(accountStateViewModel, isFirstLogin = true) + LoginOrSignupScreen(null, accountStateViewModel, isFirstLogin = true) } } is AccountState.LoggedIn -> { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/AddAccountDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/AddAccountDialog.kt new file mode 100644 index 0000000000..75711ded47 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/AddAccountDialog.kt @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedOff + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon +import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel +import com.vitorpamplona.amethyst.ui.stringRes + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AddAccountDialog( + newAccountKey: String?, + accountStateViewModel: AccountStateViewModel, + onClose: () -> Unit, +) { + Dialog( + onDismissRequest = onClose, + properties = DialogProperties(usePlatformDefaultWidth = false), + ) { + Surface(modifier = Modifier.fillMaxSize()) { + Box { + LoginOrSignupScreen(newAccountKey, accountStateViewModel, isFirstLogin = false) + TopAppBar( + title = { + Text(text = stringRes(R.string.account_switch_add_account_dialog_title)) + }, + navigationIcon = { + IconButton(onClick = onClose) { ArrowBackIcon() } + }, + colors = + TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.surface, + ), + ) + } + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginOrSignupScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginOrSignupScreen.kt index 9957886c65..1494016eab 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginOrSignupScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginOrSignupScreen.kt @@ -30,7 +30,8 @@ import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel @Composable fun LoginOrSignupScreen( - accountViewModel: AccountStateViewModel, + newAccountKey: String?, + accountStateViewModel: AccountStateViewModel, isFirstLogin: Boolean, ) { var wantsNewUser by remember { @@ -39,11 +40,11 @@ fun LoginOrSignupScreen( Crossfade(wantsNewUser, label = "LoginOrSignupScreen") { if (it) { - SignUpPage(accountStateViewModel = accountViewModel) { + SignUpPage(accountStateViewModel) { wantsNewUser = false } } else { - LoginPage(accountStateViewModel = accountViewModel, isFirstLogin = isFirstLogin) { + LoginPage(accountStateViewModel, isFirstLogin, newAccountKey) { wantsNewUser = true } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt index 638567fc7b..963fa6969a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt @@ -129,9 +129,10 @@ fun LoginPage() { fun LoginPage( accountStateViewModel: AccountStateViewModel, isFirstLogin: Boolean, + newAccountKey: String? = null, onWantsToLogin: () -> Unit, ) { - val key = remember { mutableStateOf(TextFieldValue("")) } + val key = remember { mutableStateOf(TextFieldValue(newAccountKey ?: "")) } var errorMessage by remember { mutableStateOf("") } val acceptedTerms = remember { mutableStateOf(!isFirstLogin) } var termsAcceptanceIsRequired by remember { mutableStateOf("") }