-
Notifications
You must be signed in to change notification settings - Fork 3
fix: pinned tabs with viewpager and drop shadow #968
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
f061c77
2ca84a7
bfe3429
6fb99d4
8358f89
f37d9e6
3c24dbe
43b371b
e93c06b
2b6f782
f2462cf
c89dd88
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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() } } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: should use subcompose layout to avoid re-renders during measurements composition pass. |
||
| ) { | ||
| header() | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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()) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
| 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( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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,14 +15,14 @@ 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 | ||
| 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<Activity>?, | ||
| 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), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
After moving All Activity into Useful? React with 👍 / 👎. |
||
| modifier = Modifier | ||
| .swipeToChangeTab( | ||
| currentTabIndex = currentTabIndex, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: there's also a
.scaffoldpackage for this kind of things.