diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e457ca0a..e6d4ccf5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +- Fix Spending and Savings screens scrolling behind top bar and add gradient fade effect #892 - Connection issues overlay with connectivity fixes across Send, Receive, and Transfer flows #878 ## [2.2.0] - 2026-04-07 diff --git a/app/src/main/java/to/bitkit/ui/components/SheetHost.kt b/app/src/main/java/to/bitkit/ui/components/SheetHost.kt index d181c611b..784888ba8 100644 --- a/app/src/main/java/to/bitkit/ui/components/SheetHost.kt +++ b/app/src/main/java/to/bitkit/ui/components/SheetHost.kt @@ -23,8 +23,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import kotlinx.coroutines.launch -import to.bitkit.ui.shared.modifiers.clickableAlpha import to.bitkit.ui.screens.wallets.receive.ReceiveRoute +import to.bitkit.ui.shared.modifiers.clickableAlpha import to.bitkit.ui.sheets.BackupRoute import to.bitkit.ui.sheets.PinRoute import to.bitkit.ui.sheets.SendRoute diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt index 45af13c37..0a7e231f0 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt @@ -50,7 +50,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity @@ -138,6 +137,7 @@ import to.bitkit.ui.sheets.PinRoute import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors import to.bitkit.ui.theme.Insets +import to.bitkit.ui.theme.TopBarGradient import to.bitkit.ui.utils.withAccent import to.bitkit.viewmodels.ActivityListViewModel import to.bitkit.viewmodels.AppViewModel @@ -805,20 +805,13 @@ private fun TopBar( onNavigateToAppStatus: () -> Unit = {}, onOpenDrawer: () -> Unit = {}, ) { - val topbarGradient = Brush.verticalGradient( - colorStops = arrayOf( - 0.5f to Colors.Black, - 1.0f to Color.Transparent, - ) - ) - Box( modifier = Modifier .fillMaxWidth() .hazeEffect(state = hazeState) { - mask = topbarGradient + mask = TopBarGradient } - .background(topbarGradient) + .background(TopBarGradient) .zIndex(1f) ) { TopAppBar( diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt index 980581b0d..45efea812 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt @@ -3,13 +3,10 @@ package to.bitkit.ui.screens.wallets 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 @@ -17,6 +14,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -30,7 +28,9 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex import com.synonym.bitkitcore.Activity +import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.rememberHazeState import kotlinx.collections.immutable.ImmutableList @@ -42,15 +42,18 @@ import to.bitkit.ui.components.BalanceHeaderView import to.bitkit.ui.components.EmptyStateView import to.bitkit.ui.components.IncomingTransfer import to.bitkit.ui.components.SecondaryButton +import to.bitkit.ui.components.StatusBarSpacer import to.bitkit.ui.components.TabBar +import to.bitkit.ui.components.TopBarSpacer +import to.bitkit.ui.components.VerticalSpacer import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.DrawerNavIcon -import to.bitkit.ui.scaffold.ScreenColumn -import to.bitkit.ui.screens.wallets.activity.components.ActivityListGrouped +import to.bitkit.ui.screens.wallets.activity.components.activityListGroupedItems import to.bitkit.ui.screens.wallets.activity.utils.previewOnchainActivityItems import to.bitkit.ui.shared.util.blockPointerInputPassthrough import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors +import to.bitkit.ui.theme.TopBarGradient import to.bitkit.ui.utils.withAccent @Composable @@ -76,6 +79,7 @@ fun SavingsWalletScreen( } val hazeState = rememberHazeState() + Box( modifier = Modifier .fillMaxSize() @@ -101,18 +105,31 @@ fun SavingsWalletScreen( .windowInsetsPadding(WindowInsets.statusBars.only(WindowInsetsSides.Vertical)) ) } - ScreenColumn(noBackground = true) { - AppTopBar( - titleText = stringResource(R.string.wallet__savings__title), - icon = R.drawable.ic_btc_circle, - onBackClick = onBackClick, - actions = { - DrawerNavIcon() + + AppTopBar( + titleText = stringResource(R.string.wallet__savings__title), + icon = R.drawable.ic_btc_circle, + onBackClick = onBackClick, + actions = { + DrawerNavIcon() + }, + modifier = Modifier + .hazeEffect(state = hazeState) { + mask = TopBarGradient } - ) - Column( - modifier = Modifier.padding(horizontal = 16.dp) - ) { + .background(TopBarGradient) + .zIndex(1f) + .windowInsetsPadding(WindowInsets.statusBars.only(WindowInsetsSides.Vertical)) + ) + + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp) + ) { + item { StatusBarSpacer() } + item { TopBarSpacer() } + item { BalanceHeaderView( sats = balances.totalOnchainSats.toLong(), testTag = "TotalBalance", @@ -120,19 +137,23 @@ fun SavingsWalletScreen( .fillMaxWidth() .testTag("TotalBalance") ) + } - if (balances.balanceInTransferToSavings > 0u) { + if (balances.balanceInTransferToSavings > 0u) { + item { IncomingTransfer( amount = balances.balanceInTransferToSavings, remainingDuration = forceCloseRemainingDuration, modifier = Modifier.padding(vertical = 8.dp) ) } + } - if (!showEmptyState) { - Spacer(modifier = Modifier.height(32.dp)) + if (!showEmptyState) { + item { VerticalSpacer(32.dp) } - if (canTransfer) { + if (canTransfer) { + item { SecondaryButton( onClick = onTransferToSpendingClick, text = stringResource(R.string.wallet__transfer_to_spending), @@ -147,17 +168,18 @@ fun SavingsWalletScreen( modifier = Modifier.testTag("TransferToSpending") ) } - - ActivityListGrouped( - items = onchainActivities, - onActivityItemClick = onActivityItemClick, - onEmptyActivityRowClick = onEmptyActivityRowClick, - showFooter = true, - onAllActivityButtonClick = onAllActivityButtonClick, - ) } + + activityListGroupedItems( + items = onchainActivities, + onActivityItemClick = onActivityItemClick, + onEmptyActivityRowClick = onEmptyActivityRowClick, + showFooter = true, + onAllActivityButtonClick = onAllActivityButtonClick, + ) } } + if (showEmptyState) { EmptyStateView( text = stringResource(R.string.wallet__savings__onboarding).withAccent(), 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 39838aed6..2cedaeafb 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 @@ -3,7 +3,6 @@ package to.bitkit.ui.screens.wallets 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.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxSize @@ -15,6 +14,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -28,7 +28,9 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex import com.synonym.bitkitcore.Activity +import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.rememberHazeState import kotlinx.collections.immutable.ImmutableList @@ -42,16 +44,18 @@ import to.bitkit.ui.components.BalanceHeaderView import to.bitkit.ui.components.EmptyStateView import to.bitkit.ui.components.IncomingTransfer import to.bitkit.ui.components.SecondaryButton +import to.bitkit.ui.components.StatusBarSpacer import to.bitkit.ui.components.TabBar +import to.bitkit.ui.components.TopBarSpacer import to.bitkit.ui.components.VerticalSpacer import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.DrawerNavIcon -import to.bitkit.ui.scaffold.ScreenColumn -import to.bitkit.ui.screens.wallets.activity.components.ActivityListGrouped +import to.bitkit.ui.screens.wallets.activity.components.activityListGroupedItems import to.bitkit.ui.screens.wallets.activity.utils.previewLightningActivityItems import to.bitkit.ui.shared.util.blockPointerInputPassthrough import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors +import to.bitkit.ui.theme.TopBarGradient import to.bitkit.ui.utils.withAccent @Composable @@ -80,6 +84,7 @@ fun SpendingWalletScreen( mutableStateOf(showEmptyState && balances.totalOnchainSats > 0uL) } val hazeState = rememberHazeState() + Box( modifier = Modifier .fillMaxSize() @@ -104,18 +109,31 @@ fun SpendingWalletScreen( .windowInsetsPadding(WindowInsets.statusBars.only(WindowInsetsSides.Vertical)) ) } - ScreenColumn(noBackground = true) { - AppTopBar( - titleText = stringResource(R.string.wallet__spending__title), - icon = R.drawable.ic_ln_circle, - onBackClick = onBackClick, - actions = { - DrawerNavIcon() + + AppTopBar( + titleText = stringResource(R.string.wallet__spending__title), + icon = R.drawable.ic_ln_circle, + onBackClick = onBackClick, + actions = { + DrawerNavIcon() + }, + modifier = Modifier + .hazeEffect(state = hazeState) { + mask = TopBarGradient } - ) - Column( - modifier = Modifier.padding(horizontal = 16.dp) - ) { + .background(TopBarGradient) + .zIndex(1f) + .windowInsetsPadding(WindowInsets.statusBars.only(WindowInsetsSides.Vertical)) + ) + + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp) + ) { + item { StatusBarSpacer() } + item { TopBarSpacer() } + item { BalanceHeaderView( sats = balances.totalLightningSats.toLong(), testTag = "TotalBalance", @@ -123,17 +141,21 @@ fun SpendingWalletScreen( .fillMaxWidth() .testTag("TotalBalance") ) + } - if (balances.balanceInTransferToSpending > 0u) { + if (balances.balanceInTransferToSpending > 0u) { + item { IncomingTransfer( amount = balances.balanceInTransferToSpending, modifier = Modifier.padding(vertical = 8.dp) ) } + } - if (canTransferFromSavings) { - VerticalSpacer(32.dp) + if (canTransferFromSavings) { + item { VerticalSpacer(32.dp) } + item { SecondaryButton( onClick = onTransferFromSavingsClick, text = stringResource(R.string.lightning__funding__button1), @@ -148,11 +170,13 @@ fun SpendingWalletScreen( modifier = Modifier.testTag("TransferFromSavings") ) } + } - if (!showEmptyState) { - VerticalSpacer(32.dp) + if (!showEmptyState) { + item { VerticalSpacer(32.dp) } - if (canTransfer) { + if (canTransfer) { + item { SecondaryButton( onClick = onTransferToSavingsClick, text = stringResource(R.string.wallet__transfer_to_savings), @@ -167,17 +191,18 @@ fun SpendingWalletScreen( modifier = Modifier.testTag("TransferToSavings") ) } - - ActivityListGrouped( - items = lightningActivities, - onActivityItemClick = onActivityItemClick, - onEmptyActivityRowClick = onEmptyActivityRowClick, - showFooter = true, - onAllActivityButtonClick = onAllActivityButtonClick, - ) } + + activityListGroupedItems( + items = lightningActivities, + onActivityItemClick = onActivityItemClick, + onEmptyActivityRowClick = onEmptyActivityRowClick, + showFooter = true, + onAllActivityButtonClick = onAllActivityButtonClick, + ) } } + if (showEmptyState) { EmptyStateView( text = stringResource(R.string.wallet__spending__onboarding).withAccent(accentColor = Colors.Purple), diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListGrouped.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListGrouped.kt index 7ff7c1c4d..b84ad7108 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListGrouped.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityListGrouped.kt @@ -3,13 +3,12 @@ package to.bitkit.ui.screens.wallets.activity.components import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -50,7 +49,7 @@ fun ActivityListGrouped( horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier.fillMaxSize() ) { - if (items != null && items.isNotEmpty()) { + if (!items.isNullOrEmpty()) { val groupedItems = groupActivityItems(items) LazyColumn( @@ -115,7 +114,7 @@ fun ActivityListGrouped( } } item { - Spacer(modifier = Modifier.height(120.dp)) + VerticalSpacer(120.dp) } } } else { @@ -136,6 +135,92 @@ fun ActivityListGrouped( } } +@Suppress("LongMethod") +fun LazyListScope.activityListGroupedItems( + items: ImmutableList?, + onActivityItemClick: (String) -> Unit, + onEmptyActivityRowClick: () -> Unit, + showFooter: Boolean = false, + onAllActivityButtonClick: () -> Unit = {}, +) { + if (!items.isNullOrEmpty()) { + val groupedItems = groupActivityItems(items) + itemsIndexed( + items = groupedItems, + key = { index, item -> + when (item) { + is String -> "header_$item" + is Activity -> when (item) { + is Activity.Lightning -> "lightning_${item.rawId()}" + is Activity.Onchain -> "onchain_${item.rawId()}" + } + + else -> "item_$index" + } + }, + ) { index, item -> + when (item) { + is String -> { + Caption13Up( + text = item, + color = Colors.White64, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp) + .animateItem( + fadeInSpec = tween(durationMillis = 300), + fadeOutSpec = tween(durationMillis = 300), + placementSpec = tween(durationMillis = 300), + ) + ) + } + + is Activity -> { + Column( + modifier = Modifier + .animateItem( + fadeInSpec = tween(durationMillis = 300), + fadeOutSpec = tween(durationMillis = 300), + placementSpec = tween(durationMillis = 300), + ) + ) { + ActivityRow(item, onActivityItemClick, testTag = "Activity-$index") + VerticalSpacer(16.dp) + } + } + } + } + if (showFooter) { + item { + TertiaryButton( + text = stringResource(R.string.wallet__activity_show_all), + onClick = onAllActivityButtonClick, + modifier = Modifier + .wrapContentWidth() + .padding(top = 8.dp) + ) + } + } + item { + VerticalSpacer(120.dp) + } + } else { + if (showFooter) { + item { EmptyActivityRow(onClick = onEmptyActivityRowClick) } + } else { + item { + BodyM( + text = stringResource(R.string.wallet__activity_no), + color = Colors.White64, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) + } + } + } +} + // region utils @Suppress("CyclomaticComplexMethod") private fun groupActivityItems(activityItems: List): List { diff --git a/app/src/main/java/to/bitkit/ui/theme/Defaults.kt b/app/src/main/java/to/bitkit/ui/theme/Defaults.kt index 1c7d71b12..3913120f5 100644 --- a/app/src/main/java/to/bitkit/ui/theme/Defaults.kt +++ b/app/src/main/java/to/bitkit/ui/theme/Defaults.kt @@ -15,6 +15,7 @@ import androidx.compose.material3.TextFieldColors import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.unit.Dp @@ -136,3 +137,10 @@ object Insets { @OptIn(ExperimentalMaterial3Api::class) val TopBarHeight: Dp = TopAppBarDefaults.TopAppBarExpandedHeight + +val TopBarGradient: Brush = Brush.verticalGradient( + colorStops = arrayOf( + 0.5f to Colors.Black, + 1.0f to Color.Transparent, + ), +)