diff --git a/app/src/main/java/to/bitkit/models/NodeLifecycleState.kt b/app/src/main/java/to/bitkit/models/NodeLifecycleState.kt index 9602948e7..bd7b13073 100644 --- a/app/src/main/java/to/bitkit/models/NodeLifecycleState.kt +++ b/app/src/main/java/to/bitkit/models/NodeLifecycleState.kt @@ -10,6 +10,8 @@ sealed class NodeLifecycleState { fun isStoppedOrStopping() = this is Stopped || this is Stopping fun isRunningOrStarting() = this is Running || this is Starting + fun isStarting() = this is Starting + fun isRunning() = this is Running val displayState: String get() = when (this) { diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt index 679ad84ac..5ba8ddd3b 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt @@ -1,5 +1,6 @@ package to.bitkit.ui.screens.wallets.receive +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -42,6 +43,7 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import to.bitkit.R import to.bitkit.ext.truncate +import to.bitkit.models.NodeLifecycleState import to.bitkit.models.NodeLifecycleState.Running import to.bitkit.ui.appViewModel import to.bitkit.ui.blocktankViewModel @@ -124,7 +126,8 @@ fun ReceiveQrSheet( navController.navigate(ReceiveRoutes.AMOUNT) } }, - onClickEditInvoice = { navController.navigate(ReceiveRoutes.EDIT_INVOICE) } + onClickEditInvoice = { navController.navigate(ReceiveRoutes.EDIT_INVOICE) }, + onClickReceiveOnSpending = { wallet.updateReceiveOnSpending() } ) } composable(ReceiveRoutes.AMOUNT) { @@ -177,7 +180,8 @@ private fun ReceiveQrScreen( cjitActive: MutableState, walletState: MainUiState, onCjitToggle: (Boolean) -> Unit, - onClickEditInvoice: () -> Unit + onClickEditInvoice: () -> Unit, + onClickReceiveOnSpending: () -> Unit, ) { val qrLogoImageRes by remember(walletState, cjitInvoice.value) { val resId = when { @@ -222,18 +226,50 @@ private fun ReceiveQrScreen( onchainAddress = onchainAddress, bolt11 = walletState.bolt11, cjitInvoice = cjitInvoice.value, + receiveOnSpendingBalance = walletState.receiveOnSpendingBalance ) } } } Spacer(modifier = Modifier.height(24.dp)) - if (walletState.nodeLifecycleState.isRunningOrStarting() && walletState.channels.isEmpty()) { + AnimatedVisibility(walletState.nodeLifecycleState.isRunning() && walletState.channels.isEmpty()) { ReceiveLightningFunds( cjitInvoice = cjitInvoice, cjitActive = cjitActive, onCjitToggle = onCjitToggle, ) } + AnimatedVisibility(walletState.nodeLifecycleState.isRunning() && walletState.channels.isNotEmpty()) { + Column { + AnimatedVisibility (!walletState.receiveOnSpendingBalance) { + Headline( + text = stringResource(R.string.wallet__receive_text_lnfunds).withAccent(accentColor = Colors.Purple) + ) + } + Row(verticalAlignment = Alignment.CenterVertically) { + BodyM(text = stringResource(R.string.wallet__receive_spending)) + Spacer(modifier = Modifier.weight(1f)) + AnimatedVisibility(!walletState.receiveOnSpendingBalance) { + Icon( + painter = painterResource(R.drawable.empty_state_arrow_horizontal), + contentDescription = null, + tint = Colors.White64, + modifier = Modifier + .rotate(17.33f) + .padding(start = 7.65.dp, end = 13.19.dp) + ) + } + Switch( + checked = walletState.receiveOnSpendingBalance, + onCheckedChange = { onClickReceiveOnSpending() }, + colors = AppSwitchDefaults.colorsPurple, + ) + } + } + } + AnimatedVisibility(walletState.nodeLifecycleState.isStarting()) { + BodyM(text = stringResource(R.string.wallet__receive_ldk_init)) + } Spacer(modifier = Modifier.height(24.dp)) } } @@ -246,7 +282,7 @@ private fun ReceiveLightningFunds( onCjitToggle: (Boolean) -> Unit, ) { Column { - if (cjitInvoice.value == null) { + AnimatedVisibility (!cjitActive.value && cjitInvoice.value == null) { Headline( text = stringResource(R.string.wallet__receive_text_lnfunds).withAccent(accentColor = Colors.Purple) ) @@ -254,14 +290,16 @@ private fun ReceiveLightningFunds( Row(verticalAlignment = Alignment.CenterVertically) { BodyM(text = stringResource(R.string.wallet__receive_spending)) Spacer(modifier = Modifier.weight(1f)) - Icon( - painter = painterResource(R.drawable.empty_state_arrow_horizontal), - contentDescription = null, - tint = Colors.White64, - modifier = Modifier - .rotate(17.33f) - .padding(start = 7.65.dp, end = 13.19.dp) - ) + AnimatedVisibility(!cjitActive.value && cjitInvoice.value == null) { + Icon( + painter = painterResource(R.drawable.empty_state_arrow_horizontal), + contentDescription = null, + tint = Colors.White64, + modifier = Modifier + .rotate(17.33f) + .padding(start = 7.65.dp, end = 13.19.dp) + ) + } Switch( checked = cjitActive.value, onCheckedChange = onCjitToggle, @@ -351,20 +389,21 @@ private fun CopyValuesSlide( onchainAddress: String, bolt11: String, cjitInvoice: String?, + receiveOnSpendingBalance: Boolean ) { Card( colors = CardDefaults.cardColors(containerColor = Colors.White10), shape = AppShapes.small, ) { Column { - if (onchainAddress.isNotEmpty()) { + if (onchainAddress.isNotEmpty() && cjitInvoice == null) { CopyAddressCard( title = stringResource(R.string.wallet__receive_bitcoin_invoice), address = onchainAddress, type = CopyAddressType.ONCHAIN, ) } - if (bolt11.isNotEmpty()) { + if (bolt11.isNotEmpty() && receiveOnSpendingBalance) { CopyAddressCard( title = stringResource(R.string.wallet__receive_lightning_invoice), address = bolt11, @@ -401,7 +440,7 @@ private fun CopyAddressCard( Spacer(modifier = Modifier.width(3.dp)) - val iconRes = if (type == CopyAddressType.ONCHAIN) R.drawable.ic_bitcoin else R.drawable.ic_lightning_alt + val iconRes = if (type == CopyAddressType.ONCHAIN) R.drawable.ic_bitcoin else R.drawable.ic_lightning_alt Icon(painter = painterResource(iconRes), contentDescription = null, tint = Colors.White64) } Spacer(modifier = Modifier.height(16.dp)) @@ -455,7 +494,8 @@ private fun ReceiveQrScreenPreview() { nodeLifecycleState = Running, ), onCjitToggle = { }, - onClickEditInvoice = {} + onClickEditInvoice = {}, + onClickReceiveOnSpending = {}, ) } } @@ -471,7 +511,8 @@ private fun ReceiveQrScreenPreviewSmallScreen() { nodeLifecycleState = Running, ), onCjitToggle = { }, - onClickEditInvoice = {} + onClickEditInvoice = {}, + onClickReceiveOnSpending = {}, ) } } @@ -484,10 +525,11 @@ private fun ReceiveQrScreenPreviewTablet() { cjitInvoice = remember { mutableStateOf(null) }, cjitActive = remember { mutableStateOf(false) }, walletState = MainUiState( - nodeLifecycleState = Running, + nodeLifecycleState = NodeLifecycleState.Starting, ), onCjitToggle = { }, - onClickEditInvoice = {} + onClickEditInvoice = {}, + onClickReceiveOnSpending = {}, ) } } @@ -506,6 +548,7 @@ private fun CopyValuesSlidePreview() { onchainAddress = "bcrt1qfserxgtuesul4m9zva56wzk849yf9l8rk4qy0l", bolt11 = "lnbcrt500u1pn7umn7pp5x0s9lt9fwrff6rp70pz3guwnjgw97sjuv79...", cjitInvoice = null, + true ) } } diff --git a/app/src/main/java/to/bitkit/viewmodels/WalletViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/WalletViewModel.kt index e1a23971b..7a2dafa01 100644 --- a/app/src/main/java/to/bitkit/viewmodels/WalletViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/WalletViewModel.kt @@ -307,20 +307,23 @@ class WalletViewModel @Inject constructor( fun updateBip21Invoice( amountSats: ULong? = null, - description: String = "" + description: String = "", + generateBolt11IfAvailable: Boolean = true ) { viewModelScope.launch(Dispatchers.IO) { + _uiState.update { it.copy(bip21AmountSats = amountSats, bip21Description = description) } + val hasChannels = lightningService.channels.hasChannels() - _bolt11 = if (hasChannels) { - createInvoice(amountSats = amountSats, description = description) + _bolt11 = if (hasChannels && generateBolt11IfAvailable) { + createInvoice(amountSats = _uiState.value.bip21AmountSats, description = _uiState.value.bip21Description) } else { "" } val newBip21 = Bip21Utils.buildBip21Url( bitcoinAddress = _onchainAddress, - amountSats = amountSats, + amountSats = _uiState.value.bip21AmountSats, message = description.ifBlank { DEFAULT_INVOICE_MESSAGE }, lightningInvoice = _bolt11 ) @@ -330,6 +333,14 @@ class WalletViewModel @Inject constructor( } } + fun updateReceiveOnSpending() { + _uiState.update { it.copy(receiveOnSpendingBalance = !it.receiveOnSpendingBalance) } + updateBip21Invoice( + amountSats = _uiState.value.bip21AmountSats, + description = _uiState.value.bip21Description, + generateBolt11IfAvailable = _uiState.value.receiveOnSpendingBalance + ) + } suspend fun createInvoice( amountSats: ULong? = null, @@ -535,6 +546,9 @@ data class MainUiState( val peers: List = emptyList(), val channels: List = emptyList(), val isRefreshing: Boolean = false, + val receiveOnSpendingBalance: Boolean = true, + val bip21AmountSats: ULong? = null, + val bip21Description: String = "" ) // endregion