diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt index 33722468d8..b4ad1a4e62 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt @@ -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(), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt index c5ea8056fb..80db28a2dc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt @@ -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 = "" 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 fad9ebf31b..c501ac4328 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 @@ -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, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletScreen.kt index 8ef7878156..10aecc4116 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletScreen.kt @@ -30,22 +30,28 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons 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.AccountBalanceWallet import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.CircularProgressIndicator 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.OutlinedButton +import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -64,6 +70,7 @@ import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.theme.placeholderText import java.text.NumberFormat @OptIn(ExperimentalMaterial3Api::class) @@ -73,10 +80,14 @@ fun WalletScreen( nav: INav, ) { val walletViewModel: WalletViewModel = viewModel() - walletViewModel.init(accountViewModel.account) + walletViewModel.init(accountViewModel) val hasWallet by walletViewModel.hasWalletSetup.collectAsState() + LaunchedEffect(accountViewModel) { + walletViewModel.loadLnAddress() + } + Scaffold( topBar = { TopAppBar( @@ -92,30 +103,36 @@ fun WalletScreen( ) }, ) { padding -> - if (!hasWallet) { - NoWalletSetup( - modifier = Modifier.padding(padding), - nav = nav, - ) - } else { - WalletHomeContent( - walletViewModel = walletViewModel, - modifier = Modifier.padding(padding), - nav = nav, - ) + Column( + modifier = + Modifier + .padding(padding) + .fillMaxSize() + .verticalScroll(rememberScrollState()), + ) { + if (!hasWallet) { + NoWalletSetup(nav = nav) + } else { + WalletHomeContent( + walletViewModel = walletViewModel, + nav = nav, + ) + } + + HorizontalDivider() + LightningAddressSection(walletViewModel = walletViewModel) + HorizontalDivider() + PaymentTargetsRow(nav = nav) } } } @Composable -private fun NoWalletSetup( - modifier: Modifier, - nav: INav, -) { +private fun NoWalletSetup(nav: INav) { Column( modifier = - modifier - .fillMaxSize() + Modifier + .fillMaxWidth() .padding(32.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, @@ -142,7 +159,6 @@ private fun NoWalletSetup( @Composable private fun WalletHomeContent( walletViewModel: WalletViewModel, - modifier: Modifier, nav: INav, ) { val balance by walletViewModel.balanceSats.collectAsState() @@ -157,8 +173,8 @@ private fun WalletHomeContent( Column( modifier = - modifier - .fillMaxSize() + Modifier + .fillMaxWidth() .padding(24.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { @@ -206,7 +222,7 @@ private fun WalletHomeContent( ) } - Spacer(modifier = Modifier.weight(1f)) + Spacer(modifier = Modifier.height(32.dp)) // Action buttons Row( @@ -276,3 +292,68 @@ private fun WalletHomeContent( Spacer(modifier = Modifier.height(24.dp)) } } + +@Composable +private fun LightningAddressSection(walletViewModel: WalletViewModel) { + val lnAddress by walletViewModel.lnAddress.collectAsState() + + Column( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + Text( + text = stringRes(R.string.lightning_address), + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold, + ) + 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 +private fun PaymentTargetsRow(nav: INav) { + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + imageVector = Icons.Outlined.AccountBalanceWallet, + contentDescription = null, + modifier = Modifier.size(24.dp), + tint = MaterialTheme.colorScheme.onBackground, + ) + Spacer(modifier = Modifier.width(16.dp)) + Text( + text = stringRes(R.string.payment_targets), + style = MaterialTheme.typography.bodyLarge, + ) + } + TextButton(onClick = { nav.nav(Route.EditPaymentTargets) }) { + Text(text = stringRes(R.string.manage)) + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletViewModel.kt index 7737923a01..2c22a44a68 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletViewModel.kt @@ -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(null) val balanceSats = _balanceSats.asStateFlow() @@ -137,15 +142,42 @@ 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 + val acc = account ?: return + accountVm.launchSigner { + val event = acc.userMetadata.sendNewUserMetadata(lnAddress = _lnAddress.value) + acc.sendLiterallyEverywhere(event) + } + } + fun fetchBalance() { val acc = account ?: return viewModelScope.launch(Dispatchers.IO) {