diff --git a/CHANGELOG.md b/CHANGELOG.md index f7fb99797..249b55be7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Show loading state on Spending tab when node is not running #875 ### Added +- Transfer from Savings button on empty Spending screen when savings balance exists #882 - Connection issues overlay with connectivity fixes across Send, Receive, and Transfer flows #878 - Lightning Connections empty state with onboarding screen #857 - Unified PIN management screen (enable/disable/change in one place) #857 diff --git a/app/src/main/java/to/bitkit/ui/ContentView.kt b/app/src/main/java/to/bitkit/ui/ContentView.kt index 073fb1767..11d9ab25c 100644 --- a/app/src/main/java/to/bitkit/ui/ContentView.kt +++ b/app/src/main/java/to/bitkit/ui/ContentView.kt @@ -815,6 +815,7 @@ private fun NavGraphBuilder.home( } composableWithDefaultTransitions { val hasSeenSavingsIntro by settingsViewModel.hasSeenSavingsIntro.collectAsStateWithLifecycle() + val hasSeenSpendingIntro by settingsViewModel.hasSeenSpendingIntro.collectAsStateWithLifecycle() val lightningState by walletViewModel.lightningState.collectAsStateWithLifecycle() val lightningActivities by activityListViewModel.lightningActivities.collectAsStateWithLifecycle() @@ -831,6 +832,13 @@ private fun NavGraphBuilder.home( navController.navigateToTransferSavingsAvailability() } }, + onTransferFromSavingsClick = { + if (!hasSeenSpendingIntro) { + navController.navigateToTransferSpendingIntro() + } else { + navController.navigateToTransferSpendingAmount() + } + }, onBackClick = { navController.popBackStack() }, ) } diff --git a/app/src/main/java/to/bitkit/ui/components/Button.kt b/app/src/main/java/to/bitkit/ui/components/Button.kt index dc91fb4f8..6887ed3a3 100644 --- a/app/src/main/java/to/bitkit/ui/components/Button.kt +++ b/app/src/main/java/to/bitkit/ui/components/Button.kt @@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredHeight @@ -83,6 +82,7 @@ enum class ButtonSize { Small -> 8.dp Large -> 6.dp } + fun secondaryBorder(enabled: Boolean): BorderStroke = when (this) { Large -> BorderStroke(2.dp, if (enabled) Colors.Gray4 else Color.Transparent) Small -> BorderStroke(1.dp, if (enabled) Colors.White16 else Color.Transparent) @@ -183,6 +183,7 @@ fun SecondaryButton( // and AFTER size modifiers (Haze needs to know dimensions) val buttonShape = MaterialTheme.shapes.extraLarge Box( + propagateMinConstraints = true, modifier = modifier .then(if (fullWidth) Modifier.fillMaxWidth() else Modifier) .requiredHeight(size.height) @@ -208,7 +209,6 @@ fun SecondaryButton( colors = AppButtonDefaults.secondaryColors.copy(contentColor = contentColor), contentPadding = contentPadding, border = border, - modifier = if (fullWidth) Modifier.fillMaxSize() else Modifier, ) { if (isLoading) { GradientCircularProgressIndicator( @@ -488,6 +488,25 @@ private fun SecondaryButtonPreview() { ) }, ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + SecondaryButton( + text = "Secondary", + fullWidth = false, + hazeState = hazeState, + onClick = {}, + modifier = Modifier.weight(1f) + ) + SecondaryButton( + text = "Secondary", + fullWidth = false, + hazeState = hazeState, + onClick = {}, + modifier = Modifier.weight(1f) + ) + } SecondaryButton( text = "Secondary Small", size = ButtonSize.Small, @@ -588,6 +607,23 @@ private fun TertiaryButtonPreview() { }, onClick = {} ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + TertiaryButton( + text = "Tertiary", + fullWidth = false, + onClick = {}, + modifier = Modifier.weight(1f) + ) + TertiaryButton( + text = "Tertiary", + fullWidth = false, + onClick = {}, + modifier = Modifier.weight(1f) + ) + } TertiaryButton( text = "Tertiary Small", size = ButtonSize.Small, diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt index 364abb8cf..39838aed6 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt @@ -4,12 +4,10 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding @@ -45,6 +43,7 @@ import to.bitkit.ui.components.EmptyStateView import to.bitkit.ui.components.IncomingTransfer import to.bitkit.ui.components.SecondaryButton import to.bitkit.ui.components.TabBar +import to.bitkit.ui.components.VerticalSpacer import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.DrawerNavIcon import to.bitkit.ui.scaffold.ScreenColumn @@ -63,6 +62,7 @@ fun SpendingWalletScreen( onActivityItemClick: (String) -> Unit, onEmptyActivityRowClick: () -> Unit, onTransferToSavingsClick: () -> Unit, + onTransferFromSavingsClick: () -> Unit, onBackClick: () -> Unit, balances: BalanceState = LocalBalances.current, ) { @@ -76,7 +76,9 @@ fun SpendingWalletScreen( val hasChannels = channels.isNotEmpty() mutableStateOf(hasLnBalance && hasChannels) } - + val canTransferFromSavings by remember(showEmptyState, balances.totalOnchainSats) { + mutableStateOf(showEmptyState && balances.totalOnchainSats > 0uL) + } val hazeState = rememberHazeState() Box( modifier = Modifier @@ -129,8 +131,26 @@ fun SpendingWalletScreen( ) } + if (canTransferFromSavings) { + VerticalSpacer(32.dp) + + SecondaryButton( + onClick = onTransferFromSavingsClick, + text = stringResource(R.string.lightning__funding__button1), + icon = { + Icon( + painter = painterResource(R.drawable.ic_transfer), + contentDescription = null, + modifier = Modifier.size(16.dp) + ) + }, + hazeState = hazeState, + modifier = Modifier.testTag("TransferFromSavings") + ) + } + if (!showEmptyState) { - Spacer(modifier = Modifier.height(32.dp)) + VerticalSpacer(32.dp) if (canTransfer) { SecondaryButton( @@ -160,8 +180,7 @@ fun SpendingWalletScreen( } if (showEmptyState) { EmptyStateView( - text = stringResource(R.string.wallet__spending__onboarding) - .withAccent(accentColor = Colors.Purple), + text = stringResource(R.string.wallet__spending__onboarding).withAccent(accentColor = Colors.Purple), modifier = Modifier .systemBarsPadding() .align(Alignment.BottomCenter) @@ -182,6 +201,7 @@ private fun Preview() { onActivityItemClick = {}, onEmptyActivityRowClick = {}, onTransferToSavingsClick = {}, + onTransferFromSavingsClick = {}, onBackClick = {}, balances = BalanceState(totalLightningSats = 50_000u), ) @@ -202,6 +222,7 @@ private fun PreviewTransfer() { onActivityItemClick = {}, onEmptyActivityRowClick = {}, onTransferToSavingsClick = {}, + onTransferFromSavingsClick = {}, onBackClick = {}, balances = BalanceState( totalLightningSats = 50_000u, @@ -225,6 +246,7 @@ private fun PreviewNoActivity() { onActivityItemClick = {}, onEmptyActivityRowClick = {}, onTransferToSavingsClick = {}, + onTransferFromSavingsClick = {}, onBackClick = {}, balances = BalanceState(totalLightningSats = 50_000u), ) @@ -245,7 +267,29 @@ private fun PreviewEmpty() { onActivityItemClick = {}, onEmptyActivityRowClick = {}, onTransferToSavingsClick = {}, + onTransferFromSavingsClick = {}, + onBackClick = {}, + ) + TabBar() + } + } +} + +@Preview(showSystemUi = true) +@Composable +private fun PreviewEmptyWithSavings() { + AppThemeSurface { + Box { + SpendingWalletScreen( + channels = persistentListOf(), + lightningActivities = persistentListOf(), + onAllActivityButtonClick = {}, + onActivityItemClick = {}, + onEmptyActivityRowClick = {}, + onTransferToSavingsClick = {}, + onTransferFromSavingsClick = {}, onBackClick = {}, + balances = BalanceState(totalOnchainSats = 100_000u), ) TabBar() }