Merge pull request #2183 from greenart7c3/claude/move-payment-targets-wallet-KQlnM
Integrate Lightning Address and Payment Targets into NIP47 Setup
This commit is contained in:
@@ -227,22 +227,6 @@ fun NewUserMetadataScreen(
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringRes(R.string.lightning_address)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.lnAddress.value,
|
||||
onValueChange = { postViewModel.lnAddress.value = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "me@mylightningnode.com",
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringRes(R.string.nip_05) + " (NIP-05)") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
||||
-8
@@ -57,8 +57,6 @@ class NewUserMetadataViewModel : ViewModel() {
|
||||
val website = mutableStateOf("")
|
||||
val pronouns = mutableStateOf("")
|
||||
val nip05 = mutableStateOf("")
|
||||
val lnAddress = mutableStateOf("")
|
||||
val lnURL = mutableStateOf("")
|
||||
|
||||
val twitter = mutableStateOf("")
|
||||
val github = mutableStateOf("")
|
||||
@@ -82,8 +80,6 @@ class NewUserMetadataViewModel : ViewModel() {
|
||||
website.value = it.info.website ?: ""
|
||||
pronouns.value = it.info.pronouns ?: ""
|
||||
nip05.value = it.info.nip05 ?: ""
|
||||
lnAddress.value = it.info.lud16 ?: ""
|
||||
lnURL.value = it.info.lud06 ?: ""
|
||||
}
|
||||
|
||||
twitter.value = ""
|
||||
@@ -121,8 +117,6 @@ class NewUserMetadataViewModel : ViewModel() {
|
||||
pronouns = pronouns.value,
|
||||
about = about.value,
|
||||
nip05 = nip05.value,
|
||||
lnAddress = lnAddress.value,
|
||||
lnURL = lnURL.value,
|
||||
)
|
||||
|
||||
val identities =
|
||||
@@ -146,8 +140,6 @@ class NewUserMetadataViewModel : ViewModel() {
|
||||
banner.value = ""
|
||||
website.value = ""
|
||||
nip05.value = ""
|
||||
lnAddress.value = ""
|
||||
lnURL.value = ""
|
||||
twitter.value = ""
|
||||
github.value = ""
|
||||
mastodon.value = ""
|
||||
|
||||
+8
-2
@@ -79,9 +79,15 @@ class PaymentTargetsViewModel : ViewModel() {
|
||||
fun savePaymentTargets() {
|
||||
if (isModified) {
|
||||
accountViewModel.launchSigner {
|
||||
account.savePaymentTargets(_paymentTargets.value)
|
||||
refresh()
|
||||
savePaymentTargetsSuspend()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun savePaymentTargetsSuspend() {
|
||||
if (isModified) {
|
||||
account.savePaymentTargets(_paymentTargets.value)
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -148,6 +149,7 @@ fun UpdateZapAmountContent(
|
||||
onClose: () -> Unit,
|
||||
nip47uri: String? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
trailingContent: @Composable ColumnScope.() -> Unit = {},
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val clipboardManager = LocalClipboard.current
|
||||
@@ -649,6 +651,8 @@ fun UpdateZapAmountContent(
|
||||
}
|
||||
}
|
||||
|
||||
trailingContent()
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -84,6 +84,12 @@ class UpdateZapAmountViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
fun sendPost() {
|
||||
accountViewModel.launchSigner {
|
||||
sendPostSuspend()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun sendPostSuspend() {
|
||||
val nip47Update =
|
||||
if (walletConnectRelay.text.isNotBlank() && walletConnectPubkey.text.isNotBlank()) {
|
||||
val pubkeyHex =
|
||||
@@ -110,7 +116,7 @@ class UpdateZapAmountViewModel : ViewModel() {
|
||||
null
|
||||
}
|
||||
|
||||
accountViewModel.updateZapAmounts(amountSet, selectedZapType, nip47Update)
|
||||
accountViewModel.account.updateZapAmounts(amountSet, selectedZapType, nip47Update)
|
||||
|
||||
nextAmount = TextFieldValue("")
|
||||
}
|
||||
|
||||
-8
@@ -29,7 +29,6 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.AccountBalanceWallet
|
||||
import androidx.compose.material.icons.outlined.Bolt
|
||||
import androidx.compose.material.icons.outlined.CloudUpload
|
||||
import androidx.compose.material.icons.outlined.DeleteForever
|
||||
@@ -122,13 +121,6 @@ fun AllSettingsScreen(
|
||||
onClick = { nav.nav(Route.EditMediaServers) },
|
||||
)
|
||||
HorizontalDivider()
|
||||
SettingsNavigationRow(
|
||||
title = R.string.payment_targets,
|
||||
icon = Icons.Outlined.AccountBalanceWallet,
|
||||
tint = tint,
|
||||
onClick = { nav.nav(Route.EditPaymentTargets) },
|
||||
)
|
||||
HorizontalDivider()
|
||||
SettingsNavigationRow(
|
||||
title = R.string.reactions,
|
||||
icon = Icons.Outlined.FavoriteBorder,
|
||||
|
||||
+146
-6
@@ -20,20 +20,45 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Delete
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
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.topbars.SavingTopBar
|
||||
import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountContent
|
||||
import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountViewModel
|
||||
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.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.quartz.experimental.nipA3.PaymentTarget
|
||||
|
||||
@Composable
|
||||
fun NIP47SetupScreen(
|
||||
@@ -44,17 +69,27 @@ fun NIP47SetupScreen(
|
||||
val postViewModel: UpdateZapAmountViewModel = viewModel()
|
||||
postViewModel.init(accountViewModel)
|
||||
|
||||
val walletViewModel: WalletViewModel = viewModel()
|
||||
walletViewModel.init(accountViewModel)
|
||||
|
||||
val paymentTargetsViewModel: PaymentTargetsViewModel = viewModel()
|
||||
paymentTargetsViewModel.init(accountViewModel)
|
||||
|
||||
LaunchedEffect(accountViewModel, postViewModel) {
|
||||
postViewModel.load()
|
||||
walletViewModel.loadLnAddress()
|
||||
paymentTargetsViewModel.load()
|
||||
}
|
||||
|
||||
NIP47SetupScreen(postViewModel, accountViewModel, nav, nip47)
|
||||
NIP47SetupScreen(postViewModel, walletViewModel, paymentTargetsViewModel, accountViewModel, nav, nip47)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun NIP47SetupScreen(
|
||||
postViewModel: UpdateZapAmountViewModel,
|
||||
walletViewModel: WalletViewModel,
|
||||
paymentTargetsViewModel: PaymentTargetsViewModel,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
nip47: String?,
|
||||
@@ -66,20 +101,125 @@ fun NIP47SetupScreen(
|
||||
isActive = postViewModel::hasChanged,
|
||||
onCancel = {
|
||||
postViewModel.cancel()
|
||||
paymentTargetsViewModel.refresh()
|
||||
nav.popBack()
|
||||
},
|
||||
onPost = {
|
||||
postViewModel.sendPost()
|
||||
accountViewModel.launchSigner {
|
||||
postViewModel.sendPostSuspend()
|
||||
walletViewModel.saveLnAddressSuspend()
|
||||
paymentTargetsViewModel.savePaymentTargetsSuspend()
|
||||
}
|
||||
nav.popBack()
|
||||
},
|
||||
)
|
||||
},
|
||||
) {
|
||||
Column(Modifier.padding(it)) {
|
||||
UpdateZapAmountContent(postViewModel, onClose = {
|
||||
postViewModel.cancel()
|
||||
nav.popBack()
|
||||
}, nip47, accountViewModel)
|
||||
UpdateZapAmountContent(
|
||||
postViewModel,
|
||||
onClose = {
|
||||
postViewModel.cancel()
|
||||
paymentTargetsViewModel.refresh()
|
||||
nav.popBack()
|
||||
},
|
||||
nip47,
|
||||
accountViewModel,
|
||||
) {
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
|
||||
LightningAddressSetupSection(walletViewModel)
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
|
||||
PaymentTargetsInlineSection(paymentTargetsViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LightningAddressSetupSection(walletViewModel: WalletViewModel) {
|
||||
val lnAddress by walletViewModel.lnAddress.collectAsState()
|
||||
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text(
|
||||
text = stringRes(R.string.lightning_address),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
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(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
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(
|
||||
imageVector = Icons.Rounded.Delete,
|
||||
contentDescription = stringRes(id = R.string.delete_payment_target),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@ fun WalletReceiveScreen(
|
||||
val walletViewModel: WalletViewModel = viewModel()
|
||||
|
||||
LaunchedEffect(accountViewModel) {
|
||||
walletViewModel.init(accountViewModel.account)
|
||||
walletViewModel.init(accountViewModel)
|
||||
}
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
|
||||
+12
-1
@@ -36,6 +36,7 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.List
|
||||
import androidx.compose.material.icons.filled.ArrowDownward
|
||||
import androidx.compose.material.icons.filled.ArrowUpward
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
@@ -73,7 +74,7 @@ fun WalletScreen(
|
||||
nav: INav,
|
||||
) {
|
||||
val walletViewModel: WalletViewModel = viewModel()
|
||||
walletViewModel.init(accountViewModel.account)
|
||||
walletViewModel.init(accountViewModel)
|
||||
|
||||
val hasWallet by walletViewModel.hasWalletSetup.collectAsState()
|
||||
|
||||
@@ -89,6 +90,16 @@ fun WalletScreen(
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
if (hasWallet) {
|
||||
IconButton(onClick = { nav.nav(Route.Nip47NWCSetup()) }) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Settings,
|
||||
contentDescription = stringRes(R.string.settings),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
) { padding ->
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ fun WalletSendScreen(
|
||||
val walletViewModel: WalletViewModel = viewModel()
|
||||
|
||||
LaunchedEffect(accountViewModel) {
|
||||
walletViewModel.init(accountViewModel.account)
|
||||
walletViewModel.init(accountViewModel)
|
||||
}
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ fun WalletTransactionsScreen(
|
||||
val walletViewModel: WalletViewModel = viewModel()
|
||||
|
||||
LaunchedEffect(accountViewModel) {
|
||||
walletViewModel.init(accountViewModel.account)
|
||||
walletViewModel.init(accountViewModel)
|
||||
walletViewModel.fetchTransactions()
|
||||
}
|
||||
|
||||
|
||||
+39
-3
@@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.rpc.GetBalanceMethod
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.rpc.GetBalanceSuccessResponse
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.rpc.GetInfoMethod
|
||||
@@ -86,10 +87,14 @@ private const val PAYMENT_FAILED = "Payment failed"
|
||||
|
||||
class WalletViewModel : ViewModel() {
|
||||
private var account: Account? = null
|
||||
private var accountViewModel: AccountViewModel? = null
|
||||
|
||||
private val _hasWalletSetup = MutableStateFlow(false)
|
||||
val hasWalletSetup = _hasWalletSetup.asStateFlow()
|
||||
|
||||
private val _lnAddress = MutableStateFlow("")
|
||||
val lnAddress = _lnAddress.asStateFlow()
|
||||
|
||||
private val _balanceSats = MutableStateFlow<Long?>(null)
|
||||
val balanceSats = _balanceSats.asStateFlow()
|
||||
|
||||
@@ -137,15 +142,46 @@ class WalletViewModel : ViewModel() {
|
||||
onTimeout()
|
||||
}
|
||||
|
||||
fun init(account: Account) {
|
||||
this.account = account
|
||||
_hasWalletSetup.value = account.nip47SignerState.hasWalletConnectSetup()
|
||||
fun init(accountViewModel: AccountViewModel) {
|
||||
this.accountViewModel = accountViewModel
|
||||
this.account = accountViewModel.account
|
||||
_hasWalletSetup.value = accountViewModel.account.nip47SignerState.hasWalletConnectSetup()
|
||||
}
|
||||
|
||||
fun refreshWalletSetup() {
|
||||
_hasWalletSetup.value = account?.nip47SignerState?.hasWalletConnectSetup() == true
|
||||
}
|
||||
|
||||
fun loadLnAddress() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val info =
|
||||
account
|
||||
?.userProfile()
|
||||
?.metadataOrNull()
|
||||
?.flow
|
||||
?.value
|
||||
?.info
|
||||
_lnAddress.value = info?.lud16 ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
fun updateLnAddress(address: String) {
|
||||
_lnAddress.value = address
|
||||
}
|
||||
|
||||
fun saveLnAddress() {
|
||||
val accountVm = accountViewModel ?: return
|
||||
accountVm.launchSigner {
|
||||
saveLnAddressSuspend()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun saveLnAddressSuspend() {
|
||||
val acc = account ?: return
|
||||
val event = acc.userMetadata.sendNewUserMetadata(lnAddress = _lnAddress.value)
|
||||
acc.sendLiterallyEverywhere(event)
|
||||
}
|
||||
|
||||
fun fetchBalance() {
|
||||
val acc = account ?: return
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
|
||||
Reference in New Issue
Block a user