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:
+72
-41
@@ -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,23 +137,17 @@ 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,
|
||||||
)
|
)
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
) {
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = lnAddress,
|
value = lnAddress,
|
||||||
onValueChange = { walletViewModel.updateLnAddress(it) },
|
onValueChange = { walletViewModel.updateLnAddress(it) },
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
placeholder = {
|
placeholder = {
|
||||||
Text(
|
Text(
|
||||||
text = "me@mylightningnode.com",
|
text = "me@mylightningnode.com",
|
||||||
@@ -151,41 +156,67 @@ private fun LightningAddressSetupSection(walletViewModel: WalletViewModel) {
|
|||||||
},
|
},
|
||||||
singleLine = true,
|
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)) {
|
||||||
|
Text(
|
||||||
|
text = target.type.replaceFirstChar(Char::titlecase),
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = target.authority,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.grayText,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconButton(onClick = onDelete) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.AccountBalanceWallet,
|
imageVector = Icons.Rounded.Delete,
|
||||||
contentDescription = null,
|
contentDescription = stringRes(id = R.string.delete_payment_target),
|
||||||
modifier = Modifier.size(20.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text(
|
|
||||||
text = stringRes(R.string.payment_targets),
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
color = MaterialTheme.colorScheme.primary,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
TextButton(onClick = { nav.nav(Route.EditPaymentTargets) }) {
|
|
||||||
Text(
|
|
||||||
text = stringRes(R.string.manage),
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user