refactor: inline lightning address and payment targets in wallet setup screen

- Remove separate Save/Manage buttons; both are now saved via the
  screen's existing SavingTopBar save action
- Lightning address: plain OutlinedTextField, saved on confirm
- Payment targets: inline editable list (Column, not LazyColumn) with
  add/delete directly in the scrollable content area
- Wire walletViewModel.saveLnAddress() and
  paymentTargetsViewModel.savePaymentTargets() into onPost
- Wire paymentTargetsViewModel.refresh() into onCancel

https://claude.ai/code/session_017NPhwCnSzCuMn9sTX3ca37
This commit is contained in:
Claude
2026-04-08 18:32:08 +00:00
parent bbc4155c40
commit 144b82dbe3
@@ -23,40 +23,42 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AccountBalanceWallet import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.actions.paymentTargets.PaymentTargetAddField
import com.vitorpamplona.amethyst.ui.actions.paymentTargets.PaymentTargetsViewModel
import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.navigation.topbars.SavingTopBar import com.vitorpamplona.amethyst.ui.navigation.topbars.SavingTopBar
import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountContent import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountContent
import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountViewModel import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategory
import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.WalletViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.WalletViewModel
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.SettingsCategorySpacingModifier
import com.vitorpamplona.amethyst.ui.theme.grayText
import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.experimental.nipA3.PaymentTarget
@Composable @Composable
fun NIP47SetupScreen( fun NIP47SetupScreen(
@@ -70,12 +72,16 @@ fun NIP47SetupScreen(
val walletViewModel: WalletViewModel = viewModel() val walletViewModel: WalletViewModel = viewModel()
walletViewModel.init(accountViewModel) walletViewModel.init(accountViewModel)
val paymentTargetsViewModel: PaymentTargetsViewModel = viewModel()
paymentTargetsViewModel.init(accountViewModel)
LaunchedEffect(accountViewModel, postViewModel) { LaunchedEffect(accountViewModel, postViewModel) {
postViewModel.load() postViewModel.load()
walletViewModel.loadLnAddress() walletViewModel.loadLnAddress()
paymentTargetsViewModel.load()
} }
NIP47SetupScreen(postViewModel, walletViewModel, accountViewModel, nav, nip47) NIP47SetupScreen(postViewModel, walletViewModel, paymentTargetsViewModel, accountViewModel, nav, nip47)
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -83,6 +89,7 @@ fun NIP47SetupScreen(
fun NIP47SetupScreen( fun NIP47SetupScreen(
postViewModel: UpdateZapAmountViewModel, postViewModel: UpdateZapAmountViewModel,
walletViewModel: WalletViewModel, walletViewModel: WalletViewModel,
paymentTargetsViewModel: PaymentTargetsViewModel,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: INav, nav: INav,
nip47: String?, nip47: String?,
@@ -94,10 +101,13 @@ fun NIP47SetupScreen(
isActive = postViewModel::hasChanged, isActive = postViewModel::hasChanged,
onCancel = { onCancel = {
postViewModel.cancel() postViewModel.cancel()
paymentTargetsViewModel.refresh()
nav.popBack() nav.popBack()
}, },
onPost = { onPost = {
postViewModel.sendPost() postViewModel.sendPost()
walletViewModel.saveLnAddress()
paymentTargetsViewModel.savePaymentTargets()
nav.popBack() nav.popBack()
}, },
) )
@@ -108,6 +118,7 @@ fun NIP47SetupScreen(
postViewModel, postViewModel,
onClose = { onClose = {
postViewModel.cancel() postViewModel.cancel()
paymentTargetsViewModel.refresh()
nav.popBack() nav.popBack()
}, },
nip47, nip47,
@@ -116,7 +127,7 @@ fun NIP47SetupScreen(
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
LightningAddressSetupSection(walletViewModel) LightningAddressSetupSection(walletViewModel)
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
PaymentTargetsSetupRow(nav) PaymentTargetsInlineSection(paymentTargetsViewModel)
} }
} }
} }
@@ -126,66 +137,86 @@ fun NIP47SetupScreen(
private fun LightningAddressSetupSection(walletViewModel: WalletViewModel) { private fun LightningAddressSetupSection(walletViewModel: WalletViewModel) {
val lnAddress by walletViewModel.lnAddress.collectAsState() val lnAddress by walletViewModel.lnAddress.collectAsState()
Column( Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
modifier = Modifier.padding(horizontal = 0.dp, vertical = 4.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text( Text(
text = stringRes(R.string.lightning_address), text = stringRes(R.string.lightning_address),
color = MaterialTheme.colorScheme.primary, color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.titleSmall, style = MaterialTheme.typography.titleSmall,
modifier = SettingsCategorySpacingModifier,
)
OutlinedTextField(
value = lnAddress,
onValueChange = { walletViewModel.updateLnAddress(it) },
modifier = Modifier.fillMaxWidth(),
placeholder = {
Text(
text = "me@mylightningnode.com",
color = MaterialTheme.colorScheme.placeholderText,
)
},
singleLine = true,
) )
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
OutlinedTextField(
value = lnAddress,
onValueChange = { walletViewModel.updateLnAddress(it) },
modifier = Modifier.weight(1f),
placeholder = {
Text(
text = "me@mylightningnode.com",
color = MaterialTheme.colorScheme.placeholderText,
)
},
singleLine = true,
)
TextButton(onClick = { walletViewModel.saveLnAddress() }) {
Text(text = stringRes(R.string.save))
}
}
} }
} }
@Composable @Composable
private fun PaymentTargetsSetupRow(nav: INav) { private fun PaymentTargetsInlineSection(viewModel: PaymentTargetsViewModel) {
val targets by viewModel.paymentTargets.collectAsStateWithLifecycle()
SettingsCategory(
R.string.payment_targets,
R.string.payment_targets_section_explainer,
SettingsCategorySpacingModifier,
)
if (targets.isEmpty()) {
Text(
text = stringRes(id = R.string.no_payment_targets_message),
modifier = Modifier.padding(vertical = 8.dp),
color = MaterialTheme.colorScheme.grayText,
style = MaterialTheme.typography.bodyMedium,
)
} else {
Column {
targets.forEach { target ->
PaymentTargetInlineEntry(target = target, onDelete = { viewModel.removeTarget(target) })
}
}
}
PaymentTargetAddField { type, authority ->
viewModel.addTarget(type, authority)
}
}
@Composable
private fun PaymentTargetInlineEntry(
target: PaymentTarget,
onDelete: () -> Unit,
) {
Row( Row(
modifier = modifier =
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 8.dp), .padding(vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
) { ) {
Row(verticalAlignment = Alignment.CenterVertically) { Column(modifier = Modifier.weight(1f)) {
Icon(
imageVector = Icons.Outlined.AccountBalanceWallet,
contentDescription = null,
modifier = Modifier.size(20.dp),
tint = MaterialTheme.colorScheme.primary,
)
Spacer(modifier = Modifier.width(8.dp))
Text( Text(
text = stringRes(R.string.payment_targets), text = target.type.replaceFirstChar(Char::titlecase),
style = MaterialTheme.typography.titleSmall, style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.primary, )
Text(
text = target.authority,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.grayText,
) )
} }
TextButton(onClick = { nav.nav(Route.EditPaymentTargets) }) { IconButton(onClick = onDelete) {
Text( Icon(
text = stringRes(R.string.manage), imageVector = Icons.Rounded.Delete,
fontWeight = FontWeight.SemiBold, contentDescription = stringRes(id = R.string.delete_payment_target),
) )
} }
} }