diff --git a/app/src/main/java/to/bitkit/ui/components/PinnedTabsScaffold.kt b/app/src/main/java/to/bitkit/ui/components/PinnedTabsScaffold.kt new file mode 100644 index 000000000..0398a829a --- /dev/null +++ b/app/src/main/java/to/bitkit/ui/components/PinnedTabsScaffold.kt @@ -0,0 +1,84 @@ +package to.bitkit.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +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.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +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.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import dev.chrisbanes.haze.HazeStyle +import dev.chrisbanes.haze.HazeTint +import dev.chrisbanes.haze.hazeEffect +import dev.chrisbanes.haze.hazeSource +import dev.chrisbanes.haze.rememberHazeState +import to.bitkit.ui.theme.Colors + +private val PinnedTabsShadowHeight = 32.dp +private val PinnedTabsBlurRadius = 24.dp + +@Composable +fun PinnedTabsScaffold( + header: @Composable ColumnScope.() -> Unit, + modifier: Modifier = Modifier, + content: @Composable (topPadding: Dp) -> Unit, +) { + val hazeState = rememberHazeState() + val density = LocalDensity.current + var headerHeight by remember { mutableStateOf(0.dp) } + val shadowBrush = remember { + Brush.verticalGradient(colors = listOf(Colors.Black, Color.Transparent)) + } + val hazeStyle = remember { + HazeStyle( + backgroundColor = Colors.Black, + tint = HazeTint(Colors.Black70), + blurRadius = PinnedTabsBlurRadius, + ) + } + + Box(modifier = modifier.fillMaxSize()) { + Box( + modifier = Modifier + .fillMaxSize() + .hazeSource(hazeState) + ) { + content(headerHeight) + } + + Box( + modifier = Modifier + .fillMaxWidth() + .offset(y = headerHeight) + .height(PinnedTabsShadowHeight) + .background(shadowBrush) + .zIndex(1f) + ) + + Column( + modifier = Modifier + .align(Alignment.TopStart) + .fillMaxWidth() + .zIndex(2f) + .hazeEffect(state = hazeState, style = hazeStyle) + .onSizeChanged { headerHeight = with(density) { it.height.toDp() } } + ) { + header() + } + } +} diff --git a/app/src/main/java/to/bitkit/ui/screens/shop/shopDiscover/ShopDiscoverScreen.kt b/app/src/main/java/to/bitkit/ui/screens/shop/shopDiscover/ShopDiscoverScreen.kt index 616ed7e7b..623b4c3be 100644 --- a/app/src/main/java/to/bitkit/ui/screens/shop/shopDiscover/ShopDiscoverScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/shop/shopDiscover/ShopDiscoverScreen.kt @@ -8,12 +8,15 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement 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.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.HorizontalDivider @@ -22,6 +25,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -32,11 +36,13 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.launch import to.bitkit.R import to.bitkit.env.Env import to.bitkit.ext.configureForBasicWebContent import to.bitkit.models.BitrefillCategory import to.bitkit.ui.components.BodyM +import to.bitkit.ui.components.PinnedTabsScaffold import to.bitkit.ui.components.SuggestionCard import to.bitkit.ui.components.Text13Up import to.bitkit.ui.components.VerticalSpacer @@ -64,26 +70,42 @@ fun ShopDiscoverScreen( modifier: Modifier = Modifier, ) { val tabs = remember { ShopDiscoverTab.entries.toImmutableList() } - var selectedTab by remember { mutableStateOf(ShopDiscoverTab.Shop) } + val pagerState = rememberPagerState(pageCount = { tabs.size }) + val scope = rememberCoroutineScope() ScreenColumn(modifier = modifier) { - AppTopBar( - titleText = stringResource(R.string.other__shop__discover__nav_title), - onBackClick = onBack, - actions = { DrawerNavIcon() }, - ) + PinnedTabsScaffold( + header = { + Column(modifier = modifier.fillMaxWidth()) { + AppTopBar( + titleText = stringResource(R.string.other__shop__discover__nav_title), + onBackClick = onBack, + actions = { DrawerNavIcon() }, + ) - CustomTabRowWithSpacing( - tabs = tabs, - currentTabIndex = tabs.indexOf(selectedTab), - selectedColor = Colors.White, - onTabChange = { selectedTab = it }, - modifier = Modifier.padding(horizontal = 16.dp) - ) + CustomTabRowWithSpacing( + tabs = tabs, + currentTabIndex = pagerState.currentPage, + selectedColor = Colors.White, + onTabChange = { scope.launch { pagerState.animateScrollToPage(tabs.indexOf(it)) } }, + modifier = Modifier.padding(horizontal = 16.dp) + ) + } + } + ) { topPadding -> + HorizontalPager( + state = pagerState, + userScrollEnabled = tabs[pagerState.settledPage] != ShopDiscoverTab.Map, + ) { page -> + when (tabs[page]) { + ShopDiscoverTab.Shop -> ShopTabContent( + navigateWebView = navigateWebView, + contentPadding = PaddingValues(top = topPadding, bottom = 42.dp), + ) - when (selectedTab) { - ShopDiscoverTab.Shop -> ShopTabContent(navigateWebView = navigateWebView) - ShopDiscoverTab.Map -> MapTabContent() + ShopDiscoverTab.Map -> MapTabContent(modifier = Modifier.padding(top = topPadding)) + } + } } } } @@ -92,8 +114,10 @@ fun ShopDiscoverScreen( private fun ShopTabContent( navigateWebView: (String, String) -> Unit, modifier: Modifier = Modifier, + contentPadding: PaddingValues = PaddingValues(0.dp), ) { LazyColumn( + contentPadding = contentPadding, modifier = modifier.padding(horizontal = 16.dp) ) { item { @@ -229,7 +253,7 @@ private fun MapTabContent( Box( contentAlignment = Alignment.Center, modifier = modifier - .padding(top = 16.dp, start = 16.dp, end = 16.dp) + .padding(start = 16.dp, end = 16.dp, top = 16.dp) .clip(Shapes.medium) ) { AndroidView( 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 ef7625bf2..4c3f2e0f2 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 @@ -793,6 +793,13 @@ private fun WidgetsPage( text = stringResource(R.string.widgets__add), onClick = onClickAddWidget, enabled = !isCalculatorInputActive, + icon = { + Icon( + painter = painterResource(R.drawable.ic_plus), + contentDescription = null, + modifier = Modifier.size(16.dp) + ) + }, modifier = Modifier .alpha(footerAlpha) .testTag("WidgetsAdd") diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt index 079264f31..d071d72eb 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt @@ -1,14 +1,12 @@ package to.bitkit.ui.screens.wallets.activity -import androidx.compose.foundation.layout.Box 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.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag @@ -17,7 +15,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.synonym.bitkitcore.Activity -import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.persistentListOf @@ -25,6 +22,7 @@ import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.toImmutableList import to.bitkit.R import to.bitkit.ui.appViewModel +import to.bitkit.ui.components.PinnedTabsScaffold import to.bitkit.ui.components.Sheet import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.DrawerNavIcon @@ -75,7 +73,6 @@ fun AllActivityScreen( } @Composable -@OptIn(ExperimentalHazeMaterialsApi::class) private fun AllActivityScreenContent( filteredActivities: ImmutableList?, searchText: String, @@ -93,6 +90,12 @@ private fun AllActivityScreenContent( onActivityItemClick: (String) -> Unit, onEmptyActivityRowClick: () -> Unit, ) { + val listState = rememberLazyListState() + + LaunchedEffect(currentTabIndex) { + listState.scrollToItem(0) + } + Column( modifier = Modifier.screen() ) { @@ -104,33 +107,30 @@ private fun AllActivityScreenContent( }, ) - ActivityListFilter( - searchText = searchText, - onSearchTextChange = onSearchTextChange, - hasTagFilter = hasTagFilter, - hasDateRangeFilter = hasDateRangeFilter, - onTagClick = onTagClick, - selectedTags = selectedTags, - onRemoveTag = onRemoveTag, - onDateRangeClick = onDateRangeClick, - tabs = tabs, - currentTabIndex = currentTabIndex, - onTabChange = { onTabChange(tabs.indexOf(it)) }, - modifier = Modifier.padding(horizontal = 16.dp) - - ) - Spacer(modifier = Modifier.height(16.dp)) - - // List - Box( - modifier = Modifier - .fillMaxSize() - ) { + PinnedTabsScaffold( + header = { + ActivityListFilter( + searchText = searchText, + onSearchTextChange = onSearchTextChange, + hasTagFilter = hasTagFilter, + hasDateRangeFilter = hasDateRangeFilter, + onTagClick = onTagClick, + selectedTags = selectedTags, + onRemoveTag = onRemoveTag, + onDateRangeClick = onDateRangeClick, + tabs = tabs, + currentTabIndex = currentTabIndex, + onTabChange = { onTabChange(tabs.indexOf(it)) }, + modifier = Modifier.padding(horizontal = 16.dp) + ) + } + ) { topPadding -> ActivityListGrouped( items = filteredActivities, onActivityItemClick = onActivityItemClick, onEmptyActivityRowClick = onEmptyActivityRowClick, - contentPadding = PaddingValues(top = 0.dp), + listState = listState, + contentPadding = PaddingValues(top = topPadding + 16.dp), modifier = Modifier .swipeToChangeTab( currentTabIndex = currentTabIndex, 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 f74e38850..c7247aecb 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 @@ -9,7 +9,9 @@ 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.LazyListState import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -46,6 +48,7 @@ fun ActivityListGrouped( onActivityItemClick: (String) -> Unit, onEmptyActivityRowClick: () -> Unit, modifier: Modifier = Modifier, + listState: LazyListState = rememberLazyListState(), showFooter: Boolean = false, onAllActivityButtonClick: () -> Unit = {}, contentPadding: PaddingValues = PaddingValues(top = 20.dp), @@ -65,6 +68,7 @@ fun ActivityListGrouped( val groupedItems = groupActivityItems(items) LazyColumn( + state = listState, horizontalAlignment = Alignment.CenterHorizontally, contentPadding = contentPadding, modifier = Modifier.fillMaxWidth() diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index 329674c2a..03f56dbdf 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -33,6 +34,7 @@ import to.bitkit.ui.LocalCurrencies import to.bitkit.ui.Routes import to.bitkit.ui.appViewModel import to.bitkit.ui.components.AuthCheckAction +import to.bitkit.ui.components.PinnedTabsScaffold import to.bitkit.ui.components.Sheet import to.bitkit.ui.components.VerticalSpacer import to.bitkit.ui.components.settings.SectionHeader @@ -220,30 +222,37 @@ private fun SettingsContent( actions = { DrawerNavIcon() }, ) - CustomTabRowWithSpacing( - tabs = tabs, - currentTabIndex = pagerState.currentPage, - selectedColor = Colors.White, - onTabChange = { scope.launch { pagerState.animateScrollToPage(tabs.indexOf(it)) } }, - modifier = Modifier.padding(horizontal = 16.dp) - ) - - HorizontalPager(state = pagerState) { page -> - when (tabs[page]) { - SettingsTab.General -> GeneralTabContent( - state = generalState, - onEvent = onEvent, + PinnedTabsScaffold( + header = { + CustomTabRowWithSpacing( + tabs = tabs, + currentTabIndex = pagerState.currentPage, + selectedColor = Colors.White, + onTabChange = { scope.launch { pagerState.animateScrollToPage(tabs.indexOf(it)) } }, + modifier = Modifier.padding(horizontal = 16.dp) ) + } + ) { topPadding -> + HorizontalPager(state = pagerState) { page -> + when (tabs[page]) { + SettingsTab.General -> GeneralTabContent( + state = generalState, + onEvent = onEvent, + topPadding = topPadding, + ) - SettingsTab.Security -> SecurityTabContent( - state = securityState, - onEvent = onEvent, - ) + SettingsTab.Security -> SecurityTabContent( + state = securityState, + onEvent = onEvent, + topPadding = topPadding, + ) - SettingsTab.Advanced -> AdvancedTabContent( - state = advancedState, - onEvent = onEvent, - ) + SettingsTab.Advanced -> AdvancedTabContent( + state = advancedState, + onEvent = onEvent, + topPadding = topPadding, + ) + } } } } @@ -253,12 +262,13 @@ private fun SettingsContent( private fun GeneralTabContent( state: GeneralTabState, onEvent: OnSettingsEvent, + topPadding: Dp = 0.dp, ) { Column( modifier = Modifier .fillMaxSize() - .padding(horizontal = 16.dp) .verticalScroll(rememberScrollState()) + .padding(top = topPadding, start = 16.dp, end = 16.dp) ) { SectionHeader(title = stringResource(R.string.settings__general__section_interface)) @@ -364,12 +374,13 @@ private fun GeneralTabContent( private fun SecurityTabContent( state: SecurityTabState, onEvent: OnSettingsEvent, + topPadding: Dp = 0.dp, ) { Column( modifier = Modifier .fillMaxSize() - .padding(horizontal = 16.dp) .verticalScroll(rememberScrollState()) + .padding(top = topPadding, start = 16.dp, end = 16.dp) ) { SectionHeader(title = stringResource(R.string.settings__security__section_backup)) @@ -480,12 +491,13 @@ private fun SecurityTabContent( private fun AdvancedTabContent( state: AdvancedTabState, onEvent: OnSettingsEvent, + topPadding: Dp = 0.dp, ) { Column( modifier = Modifier .fillMaxSize() - .padding(horizontal = 16.dp) .verticalScroll(rememberScrollState()) + .padding(top = topPadding, start = 16.dp, end = 16.dp) .testTag("advanced_settings_screen") ) { if (state.isDevModeEnabled) { diff --git a/changelog.d/next/916.changed.md b/changelog.d/next/916.changed.md new file mode 100644 index 000000000..b6e012af1 --- /dev/null +++ b/changelog.d/next/916.changed.md @@ -0,0 +1 @@ +Activity, Shop and Settings now keep their tabs pinned with a drop shadow as content scrolls behind them, the Add Widget button shows its icon, and the Shop list has more bottom spacing.