From fb066b3b6ff2684bc06399f021e1d74c85b298e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Mar 2026 15:42:51 +0000 Subject: [PATCH 1/2] feat: convert UpdateReactionTypeDialog to a route Replace the dialog-based UpdateReactionTypeDialog with a navigation route (Route.UpdateReactionType) that slides up from the bottom. UpdateReactionTypeScreen replaces the Dialog wrapper with a plain Scaffold. Call sites in AllSettingsScreen and ReactionsRow now navigate to the route instead of managing local dialog state. https://claude.ai/code/session_01TSjuYdBADTRXwT4w8Yx1vU --- .../amethyst/ui/navigation/AppNavigation.kt | 2 + .../amethyst/ui/navigation/routes/Routes.kt | 3 + .../amethyst/ui/note/ReactionsRow.kt | 13 +- .../ui/note/UpdateReactionTypeDialog.kt | 175 ++++++++---------- .../loggedIn/settings/AllSettingsScreen.kt | 12 +- 5 files changed, 87 insertions(+), 118 deletions(-) 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 c6ab5bdb6..f9816ddd2 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 @@ -55,6 +55,7 @@ import com.vitorpamplona.amethyst.ui.navigation.routes.getRouteWithArguments import com.vitorpamplona.amethyst.ui.navigation.routes.isBaseRoute import com.vitorpamplona.amethyst.ui.navigation.routes.isSameRoute import com.vitorpamplona.amethyst.ui.note.PayViaIntentScreen +import com.vitorpamplona.amethyst.ui.note.UpdateReactionTypeScreen import com.vitorpamplona.amethyst.ui.note.nip22Comments.ReplyCommentPostScreen import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountSwitcherAndLeftDrawerLayout @@ -174,6 +175,7 @@ fun AppNavigation( composableFromBottomArgs { NIP47SetupScreen(accountViewModel, nav, it.nip47) } composableFromEndArgs { AllRelayListScreen(accountViewModel, nav) } composableFromEndArgs { AllMediaServersScreen(accountViewModel, nav) } + composableFromBottom { UpdateReactionTypeScreen(accountViewModel, nav) } composableFromEndArgs { DvmContentDiscoveryScreen(it.id, accountViewModel, nav) } composableFromEndArgs { ProfileScreen(it.id, accountViewModel, nav) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt index 106dee44f..cdbdf19bc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt @@ -113,6 +113,8 @@ sealed class Route { @Serializable object EditMediaServers : Route() + @Serializable object UpdateReactionType : Route() + @Serializable data class Nip47NWCSetup( val nip47: String? = null, ) : Route() @@ -351,6 +353,7 @@ fun getRouteWithArguments(navController: NavHostController): Route? { dest.hasRoute() -> entry.toRoute() dest.hasRoute() -> entry.toRoute() dest.hasRoute() -> entry.toRoute() + dest.hasRoute() -> entry.toRoute() dest.hasRoute() -> entry.toRoute() dest.hasRoute() -> entry.toRoute() dest.hasRoute() -> entry.toRoute() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index 89874e20c..50dd07bb7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -871,7 +871,6 @@ fun LikeReaction( heartSizeModifier: Modifier = Size18Modifier, iconFontSize: TextUnit = Font14SP, ) { - var wantsToChangeReactionSymbol by remember { mutableStateOf(false) } var wantsToReact by remember { mutableStateOf(false) } ClickableBox( @@ -883,7 +882,7 @@ fun LikeReaction( onWantsToSignReaction = { accountViewModel.reactToOrDelete(baseNote) }, ) }, - onLongClick = { wantsToChangeReactionSymbol = true }, + onLongClick = { nav.nav(Route.UpdateReactionType) }, ) { ObserveLikeIcon(baseNote, accountViewModel) { reactionType -> CrossfadeIfEnabled(targetState = reactionType, contentAlignment = Center, label = "LikeIcon", accountViewModel = accountViewModel) { @@ -895,14 +894,6 @@ fun LikeReaction( } } - if (wantsToChangeReactionSymbol) { - UpdateReactionTypeDialog( - { wantsToChangeReactionSymbol = false }, - accountViewModel = accountViewModel, - nav, - ) - } - if (wantsToReact) { ReactionChoicePopup( baseNote, @@ -911,7 +902,7 @@ fun LikeReaction( onDismiss = { wantsToReact = false }, onChangeAmount = { wantsToReact = false - wantsToChangeReactionSymbol = true + nav.nav(Route.UpdateReactionType) }, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt index c4c7e8ae1..d51dd4a7a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt @@ -69,8 +69,6 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.ViewModel import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R @@ -81,7 +79,6 @@ import com.vitorpamplona.amethyst.service.firstFullChar import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEventAndMapNotNull import com.vitorpamplona.amethyst.ui.components.AnimatedBorderTextCornerRadius import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer -import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor import com.vitorpamplona.amethyst.ui.navigation.topbars.SavingTopBar @@ -153,8 +150,7 @@ class UpdateReactionTypeViewModel : ViewModel() { } @Composable -fun UpdateReactionTypeDialog( - onClose: () -> Unit, +fun UpdateReactionTypeScreen( accountViewModel: AccountViewModel, nav: INav, ) { @@ -165,119 +161,106 @@ fun UpdateReactionTypeDialog( postViewModel.load() } - UpdateReactionTypeDialog(postViewModel, onClose, accountViewModel, nav) + UpdateReactionTypeScreen(postViewModel, accountViewModel, nav) } @OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class) @Composable -fun UpdateReactionTypeDialog( +fun UpdateReactionTypeScreen( postViewModel: UpdateReactionTypeViewModel, - onClose: () -> Unit, accountViewModel: AccountViewModel, nav: INav, ) { - Dialog( - onDismissRequest = { onClose() }, - properties = - DialogProperties( - usePlatformDefaultWidth = false, - dismissOnClickOutside = false, - decorFitsSystemWindows = false, - ), - ) { - SetDialogToEdgeToEdge() - - Scaffold( - topBar = { - SavingTopBar( - isActive = postViewModel::hasChanged, - onCancel = { - postViewModel.cancel() - onClose() - }, - onPost = { - postViewModel.sendPost() - onClose() - }, - ) - }, - ) { pad -> - Surface( - modifier = - Modifier - .padding(pad) - .consumeWindowInsets(pad) - .imePadding(), + Scaffold( + topBar = { + SavingTopBar( + isActive = postViewModel::hasChanged, + onCancel = { + postViewModel.cancel() + nav.popBack() + }, + onPost = { + postViewModel.sendPost() + nav.popBack() + }, + ) + }, + ) { pad -> + Surface( + modifier = + Modifier + .padding(pad) + .consumeWindowInsets(pad) + .imePadding(), + ) { + Column( + modifier = Modifier.padding(10.dp), ) { - Column( - modifier = Modifier.padding(10.dp), + Row( + modifier = Modifier.fillMaxWidth(), ) { - Row( - modifier = Modifier.fillMaxWidth(), + Column( + modifier = Modifier.verticalScroll(rememberScrollState()), ) { - Column( - modifier = Modifier.verticalScroll(rememberScrollState()), - ) { - Row(modifier = Modifier.fillMaxWidth()) { - Column(modifier = Modifier.animateContentSize()) { - FlowRow( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center, - ) { - postViewModel.reactionSet.forEach { reactionType -> - RenderReactionOption(reactionType, postViewModel) - } + Row(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.animateContentSize()) { + FlowRow( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + ) { + postViewModel.reactionSet.forEach { reactionType -> + RenderReactionOption(reactionType, postViewModel) } } } + } - Spacer(modifier = Modifier.height(10.dp)) + Spacer(modifier = Modifier.height(10.dp)) - Row( - modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp), - verticalAlignment = Alignment.CenterVertically, + Row( + modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + OutlinedTextField( + label = { Text(text = stringRes(R.string.new_reaction_symbol)) }, + value = postViewModel.nextChoice, + onValueChange = { postViewModel.nextChoice = it }, + keyboardOptions = + KeyboardOptions.Default.copy( + capitalization = KeyboardCapitalization.None, + keyboardType = KeyboardType.Text, + ), + placeholder = { + Text( + text = "\uD83D\uDCAF, \uD83C\uDF89, \uD83D\uDC4E", + color = MaterialTheme.colorScheme.placeholderText, + ) + }, + singleLine = true, + modifier = Modifier.padding(end = 10.dp).weight(1f), + ) + + Button( + onClick = { postViewModel.addChoice() }, + shape = ButtonBorder, + colors = + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary, + ), ) { - OutlinedTextField( - label = { Text(text = stringRes(R.string.new_reaction_symbol)) }, - value = postViewModel.nextChoice, - onValueChange = { postViewModel.nextChoice = it }, - keyboardOptions = - KeyboardOptions.Default.copy( - capitalization = KeyboardCapitalization.None, - keyboardType = KeyboardType.Text, - ), - placeholder = { - Text( - text = "\uD83D\uDCAF, \uD83C\uDF89, \uD83D\uDC4E", - color = MaterialTheme.colorScheme.placeholderText, - ) - }, - singleLine = true, - modifier = Modifier.padding(end = 10.dp).weight(1f), - ) - - Button( - onClick = { postViewModel.addChoice() }, - shape = ButtonBorder, - colors = - ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.primary, - ), - ) { - Text(text = stringRes(R.string.add), color = Color.White) - } + Text(text = stringRes(R.string.add), color = Color.White) } } } + } - Spacer(StdVertSpacer) + Spacer(StdVertSpacer) - EmojiSelector( - accountViewModel = accountViewModel, - nav = nav, - ) { - postViewModel.addChoice(it) - } + EmojiSelector( + accountViewModel = accountViewModel, + nav = nav, + ) { + postViewModel.addChoice(it) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt index 1b9ee3abd..ff5fcf37a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt @@ -57,7 +57,6 @@ import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton -import com.vitorpamplona.amethyst.ui.note.UpdateReactionTypeDialog import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountDialog import com.vitorpamplona.amethyst.ui.painterRes import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -84,17 +83,8 @@ fun AllSettingsScreen( ) { val tint = MaterialTheme.colorScheme.onBackground - var showReactionDialog by remember { mutableStateOf(false) } var wantsToChangeZapAmount by remember { mutableStateOf(false) } - if (showReactionDialog) { - UpdateReactionTypeDialog( - onClose = { showReactionDialog = false }, - accountViewModel = accountViewModel, - nav = nav, - ) - } - if (wantsToChangeZapAmount) { UpdateZapAmountDialog( onClose = { wantsToChangeZapAmount = false }, @@ -128,7 +118,7 @@ fun AllSettingsScreen( title = R.string.reactions, icon = Icons.Outlined.FavoriteBorder, tint = tint, - onClick = { showReactionDialog = true }, + onClick = { nav.nav(Route.UpdateReactionType) }, ) HorizontalDivider() SettingsNavigationRow( From 9910bd0dfa76f80c5b0ecd078fa9921665187b62 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Mar 2026 15:43:18 +0000 Subject: [PATCH 2/2] feat: convert AccountBackupDialog to a navigation Route MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the local dialog state in AllSettingsScreen with a proper Route.AccountBackup, presented as a bottom-slide composable. - Add `Route.AccountBackup` to Routes.kt (sealed class + getRouteWithArguments) - Rename `AccountBackupDialog` → `AccountBackupScreen` accepting `INav` instead of `onClose`; remove Dialog wrapper - Register `composableFromBottom` in AppNavigation - Update AllSettingsScreen to navigate to Route.AccountBackup instead of managing local dialog state https://claude.ai/code/session_01KRRANB1b7wV7mpxEvpfjc7 --- .../amethyst/ui/navigation/AppNavigation.kt | 2 ++ .../amethyst/ui/navigation/routes/Routes.kt | 3 +++ .../loggedIn/keyBackup/AccountBackupDialog.kt | 20 +++++++------------ .../loggedIn/settings/AllSettingsScreen.kt | 12 +---------- 4 files changed, 13 insertions(+), 24 deletions(-) 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 c6ab5bdb6..98783df1a 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 @@ -105,6 +105,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.relay.RelayFeedScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.AllRelayListScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.RelayInformationScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.SearchScreen +import com.vitorpamplona.amethyst.ui.screen.loggedIn.keyBackup.AccountBackupScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.AllSettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NIP47SetupScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SecurityFiltersScreen @@ -165,6 +166,7 @@ fun AppNavigation( composable { SearchScreen(accountViewModel, nav) } composableFromEnd { AllSettingsScreen(accountViewModel, nav) } + composableFromBottom { AccountBackupScreen(accountViewModel, nav) } composableFromEnd { SecurityFiltersScreen(accountViewModel, nav) } composableFromEnd { PrivacyOptionsScreen(Amethyst.instance.torPrefs.value, nav) } composableFromEnd { BookmarkListScreen(accountViewModel, nav) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt index 106dee44f..57fc0d0b1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt @@ -81,6 +81,8 @@ sealed class Route { @Serializable object AllSettings : Route() + @Serializable object AccountBackup : Route() + @Serializable object Settings : Route() @Serializable object UserSettings : Route() @@ -332,6 +334,7 @@ fun getRouteWithArguments(navController: NavHostController): Route? { dest.hasRoute() -> entry.toRoute() dest.hasRoute() -> entry.toRoute() dest.hasRoute() -> entry.toRoute() + dest.hasRoute() -> entry.toRoute() dest.hasRoute() -> entry.toRoute() dest.hasRoute() -> entry.toRoute() dest.hasRoute() -> entry.toRoute() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/keyBackup/AccountBackupDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/keyBackup/AccountBackupDialog.kt index d0c669bad..a51b6e5c2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/keyBackup/AccountBackupDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/keyBackup/AccountBackupDialog.kt @@ -80,8 +80,6 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties import androidx.fragment.app.FragmentActivity import com.halilibo.richtext.commonmark.CommonMarkdownParseOptions import com.halilibo.richtext.commonmark.CommonmarkAstNodeParser @@ -91,6 +89,7 @@ import com.halilibo.richtext.ui.material3.RichText import com.halilibo.richtext.ui.resolveDefaults import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon import com.vitorpamplona.amethyst.ui.note.authenticate import com.vitorpamplona.amethyst.ui.painterRes @@ -112,23 +111,18 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalComposeUiApi::class) @Composable -fun AccountBackupDialog( +fun AccountBackupScreen( accountViewModel: AccountViewModel, - onClose: () -> Unit, + nav: INav, ) { - Dialog( - onDismissRequest = onClose, - properties = DialogProperties(usePlatformDefaultWidth = false), - ) { - DialogContents(accountViewModel, onClose) - } + AccountBackupScreenContent(accountViewModel, nav::popBack) } @Preview(device = "spec:width=2160px,height=2340px,dpi=440") @Composable -fun DialogContentsPreview() { +fun AccountBackupScreenPreview() { ThemeComparisonRow { - DialogContents( + AccountBackupScreenContent( mockAccountViewModel(), ) {} } @@ -136,7 +130,7 @@ fun DialogContentsPreview() { @OptIn(ExperimentalComposeUiApi::class) @Composable -private fun DialogContents( +private fun AccountBackupScreenContent( accountViewModel: AccountViewModel, onClose: () -> Unit, ) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt index 1b9ee3abd..9f9667412 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt @@ -61,7 +61,6 @@ import com.vitorpamplona.amethyst.ui.note.UpdateReactionTypeDialog import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountDialog import com.vitorpamplona.amethyst.ui.painterRes import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.keyBackup.AccountBackupDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn @@ -152,22 +151,13 @@ fun AllSettingsScreen( onClick = { nav.nav(Route.UserSettings) }, ) accountViewModel.account.settings.keyPair.privKey?.let { - var backupDialogOpen by remember { mutableStateOf(false) } - HorizontalDivider() SettingsNavigationRow( title = R.string.backup_keys, icon = Icons.Outlined.Key, tint = tint, - onClick = { - nav.closeDrawer() - backupDialogOpen = true - }, + onClick = { nav.nav(Route.AccountBackup) }, ) - - if (backupDialogOpen) { - AccountBackupDialog(accountViewModel, onClose = { backupDialogOpen = false }) - } } HorizontalDivider(thickness = 4.dp) SettingsSectionHeader(R.string.app_settings)