Skip to content

Commit afb165e

Browse files
authored
Merge pull request #968 from synonymdev/fix/design-review-181
2 parents 25fd65a + f3781d9 commit afb165e

7 files changed

Lines changed: 222 additions & 71 deletions

File tree

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package to.bitkit.ui.scaffold
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.layout.Box
5+
import androidx.compose.foundation.layout.Column
6+
import androidx.compose.foundation.layout.ColumnScope
7+
import androidx.compose.foundation.layout.fillMaxSize
8+
import androidx.compose.foundation.layout.fillMaxWidth
9+
import androidx.compose.foundation.layout.height
10+
import androidx.compose.runtime.Composable
11+
import androidx.compose.runtime.remember
12+
import androidx.compose.ui.Modifier
13+
import androidx.compose.ui.graphics.Brush
14+
import androidx.compose.ui.graphics.Color
15+
import androidx.compose.ui.layout.SubcomposeLayout
16+
import androidx.compose.ui.unit.Dp
17+
import androidx.compose.ui.unit.dp
18+
import dev.chrisbanes.haze.HazeStyle
19+
import dev.chrisbanes.haze.HazeTint
20+
import dev.chrisbanes.haze.hazeEffect
21+
import dev.chrisbanes.haze.hazeSource
22+
import dev.chrisbanes.haze.rememberHazeState
23+
import to.bitkit.ui.theme.Colors
24+
25+
private val PinnedTabsShadowHeight = 32.dp
26+
private val PinnedTabsBlurRadius = 24.dp
27+
28+
private enum class PinnedTabsSlot { Header, Content, Shadow }
29+
30+
@Composable
31+
fun PinnedTabsScaffold(
32+
header: @Composable ColumnScope.() -> Unit,
33+
modifier: Modifier = Modifier,
34+
content: @Composable (topPadding: Dp) -> Unit,
35+
) {
36+
val hazeState = rememberHazeState()
37+
val shadowBrush = remember {
38+
Brush.verticalGradient(colors = listOf(Colors.Black, Color.Transparent))
39+
}
40+
val hazeStyle = remember {
41+
HazeStyle(
42+
backgroundColor = Colors.Black,
43+
tint = HazeTint(Colors.Black70),
44+
blurRadius = PinnedTabsBlurRadius,
45+
)
46+
}
47+
48+
SubcomposeLayout(modifier = modifier.fillMaxSize()) { constraints ->
49+
val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
50+
51+
val headerPlaceables = subcompose(PinnedTabsSlot.Header) {
52+
Column(
53+
modifier = Modifier
54+
.fillMaxWidth()
55+
.hazeEffect(state = hazeState, style = hazeStyle),
56+
content = { header() },
57+
)
58+
}.map { it.measure(looseConstraints) }
59+
60+
val headerHeightPx = headerPlaceables.maxOfOrNull { it.height } ?: 0
61+
val headerHeightDp = headerHeightPx.toDp()
62+
63+
val contentPlaceables = subcompose(PinnedTabsSlot.Content) {
64+
Box(
65+
modifier = Modifier
66+
.fillMaxSize()
67+
.hazeSource(hazeState)
68+
) {
69+
content(headerHeightDp)
70+
}
71+
}.map { it.measure(constraints) }
72+
73+
val shadowPlaceables = subcompose(PinnedTabsSlot.Shadow) {
74+
Box(
75+
modifier = Modifier
76+
.fillMaxWidth()
77+
.height(PinnedTabsShadowHeight)
78+
.background(shadowBrush)
79+
)
80+
}.map { it.measure(looseConstraints) }
81+
82+
layout(constraints.maxWidth, constraints.maxHeight) {
83+
contentPlaceables.forEach { it.placeRelative(0, 0) }
84+
shadowPlaceables.forEach { it.placeRelative(0, headerHeightPx) }
85+
headerPlaceables.forEach { it.placeRelative(0, 0) }
86+
}
87+
}
88+
}

app/src/main/java/to/bitkit/ui/screens/shop/shopDiscover/ShopDiscoverScreen.kt

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
package to.bitkit.ui.screens.shop.shopDiscover
22

33
import android.annotation.SuppressLint
4+
import android.view.MotionEvent
45
import android.view.ViewGroup
56
import android.webkit.WebView
67
import androidx.annotation.StringRes
78
import androidx.compose.foundation.background
89
import androidx.compose.foundation.layout.Arrangement
910
import androidx.compose.foundation.layout.Box
1011
import androidx.compose.foundation.layout.Column
12+
import androidx.compose.foundation.layout.PaddingValues
1113
import androidx.compose.foundation.layout.Row
1214
import androidx.compose.foundation.layout.fillMaxWidth
1315
import androidx.compose.foundation.layout.padding
1416
import androidx.compose.foundation.layout.size
1517
import androidx.compose.foundation.lazy.LazyColumn
1618
import androidx.compose.foundation.lazy.items
19+
import androidx.compose.foundation.pager.HorizontalPager
20+
import androidx.compose.foundation.pager.rememberPagerState
1721
import androidx.compose.foundation.shape.CircleShape
1822
import androidx.compose.material3.CircularProgressIndicator
1923
import androidx.compose.material3.HorizontalDivider
@@ -22,16 +26,19 @@ import androidx.compose.runtime.Composable
2226
import androidx.compose.runtime.getValue
2327
import androidx.compose.runtime.mutableStateOf
2428
import androidx.compose.runtime.remember
29+
import androidx.compose.runtime.rememberCoroutineScope
2530
import androidx.compose.runtime.setValue
2631
import androidx.compose.ui.Alignment
2732
import androidx.compose.ui.Modifier
2833
import androidx.compose.ui.draw.clip
2934
import androidx.compose.ui.res.painterResource
3035
import androidx.compose.ui.res.stringResource
3136
import androidx.compose.ui.tooling.preview.Preview
37+
import androidx.compose.ui.unit.Dp
3238
import androidx.compose.ui.unit.dp
3339
import androidx.compose.ui.viewinterop.AndroidView
3440
import kotlinx.collections.immutable.toImmutableList
41+
import kotlinx.coroutines.launch
3542
import to.bitkit.R
3643
import to.bitkit.env.Env
3744
import to.bitkit.ext.configureForBasicWebContent
@@ -42,6 +49,7 @@ import to.bitkit.ui.components.Text13Up
4249
import to.bitkit.ui.components.VerticalSpacer
4350
import to.bitkit.ui.scaffold.AppTopBar
4451
import to.bitkit.ui.scaffold.DrawerNavIcon
52+
import to.bitkit.ui.scaffold.PinnedTabsScaffold
4553
import to.bitkit.ui.scaffold.ScreenColumn
4654
import to.bitkit.ui.screens.wallets.activity.components.CustomTabRowWithSpacing
4755
import to.bitkit.ui.screens.wallets.activity.components.TabItem
@@ -64,26 +72,39 @@ fun ShopDiscoverScreen(
6472
modifier: Modifier = Modifier,
6573
) {
6674
val tabs = remember { ShopDiscoverTab.entries.toImmutableList() }
67-
var selectedTab by remember { mutableStateOf(ShopDiscoverTab.Shop) }
75+
val pagerState = rememberPagerState(pageCount = { tabs.size })
76+
val scope = rememberCoroutineScope()
6877

6978
ScreenColumn(modifier = modifier) {
70-
AppTopBar(
71-
titleText = stringResource(R.string.other__shop__discover__nav_title),
72-
onBackClick = onBack,
73-
actions = { DrawerNavIcon() },
74-
)
79+
PinnedTabsScaffold(
80+
header = {
81+
Column(modifier = Modifier.fillMaxWidth()) {
82+
AppTopBar(
83+
titleText = stringResource(R.string.other__shop__discover__nav_title),
84+
onBackClick = onBack,
85+
actions = { DrawerNavIcon() },
86+
)
7587

76-
CustomTabRowWithSpacing(
77-
tabs = tabs,
78-
currentTabIndex = tabs.indexOf(selectedTab),
79-
selectedColor = Colors.White,
80-
onTabChange = { selectedTab = it },
81-
modifier = Modifier.padding(horizontal = 16.dp)
82-
)
88+
CustomTabRowWithSpacing(
89+
tabs = tabs,
90+
currentTabIndex = pagerState.currentPage,
91+
selectedColor = Colors.White,
92+
onTabChange = { scope.launch { pagerState.animateScrollToPage(tabs.indexOf(it)) } },
93+
modifier = Modifier.padding(horizontal = 16.dp)
94+
)
95+
}
96+
}
97+
) { topPadding ->
98+
HorizontalPager(state = pagerState) { page ->
99+
when (tabs[page]) {
100+
ShopDiscoverTab.Shop -> ShopTabContent(
101+
navigateWebView = navigateWebView,
102+
contentPadding = PaddingValues(top = topPadding, bottom = 42.dp),
103+
)
83104

84-
when (selectedTab) {
85-
ShopDiscoverTab.Shop -> ShopTabContent(navigateWebView = navigateWebView)
86-
ShopDiscoverTab.Map -> MapTabContent()
105+
ShopDiscoverTab.Map -> MapTabContent(topPadding = topPadding)
106+
}
107+
}
87108
}
88109
}
89110
}
@@ -92,8 +113,10 @@ fun ShopDiscoverScreen(
92113
private fun ShopTabContent(
93114
navigateWebView: (String, String) -> Unit,
94115
modifier: Modifier = Modifier,
116+
contentPadding: PaddingValues = PaddingValues(0.dp),
95117
) {
96118
LazyColumn(
119+
contentPadding = contentPadding,
97120
modifier = modifier.padding(horizontal = 16.dp)
98121
) {
99122
item {
@@ -213,10 +236,11 @@ private fun ShopTabContent(
213236
}
214237
}
215238

216-
@SuppressLint("SetJavaScriptEnabled")
239+
@SuppressLint("SetJavaScriptEnabled", "ClickableViewAccessibility")
217240
@Composable
218241
private fun MapTabContent(
219242
modifier: Modifier = Modifier,
243+
topPadding: Dp = 0.dp,
220244
) {
221245
var isLoading by remember { mutableStateOf(true) }
222246

@@ -229,7 +253,7 @@ private fun MapTabContent(
229253
Box(
230254
contentAlignment = Alignment.Center,
231255
modifier = modifier
232-
.padding(top = 16.dp, start = 16.dp, end = 16.dp)
256+
.padding(start = 16.dp, end = 16.dp, top = topPadding + 16.dp)
233257
.clip(Shapes.medium)
234258
) {
235259
AndroidView(
@@ -242,6 +266,20 @@ private fun MapTabContent(
242266

243267
this.webViewClient = webViewClient
244268
configureForBasicWebContent()
269+
// Keep the parent HorizontalPager from intercepting horizontal pans while the user is
270+
// interacting with the map. Outside the WebView bounds the pager still swipes normally.
271+
setOnTouchListener { view, event ->
272+
when (event.actionMasked) {
273+
MotionEvent.ACTION_DOWN,
274+
MotionEvent.ACTION_MOVE,
275+
-> view.parent?.requestDisallowInterceptTouchEvent(true)
276+
277+
MotionEvent.ACTION_UP,
278+
MotionEvent.ACTION_CANCEL,
279+
-> view.parent?.requestDisallowInterceptTouchEvent(false)
280+
}
281+
false
282+
}
245283
loadUrl(Env.BTC_MAP_URL)
246284
}
247285
},

app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,13 @@ private fun WidgetsPage(
793793
text = stringResource(R.string.widgets__add),
794794
onClick = onClickAddWidget,
795795
enabled = !isCalculatorInputActive,
796+
icon = {
797+
Icon(
798+
painter = painterResource(R.drawable.ic_plus),
799+
contentDescription = null,
800+
modifier = Modifier.size(16.dp)
801+
)
802+
},
796803
modifier = Modifier
797804
.alpha(footerAlpha)
798805
.testTag("WidgetsAdd")

app/src/main/java/to/bitkit/ui/screens/wallets/activity/AllActivityScreen.kt

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
package to.bitkit.ui.screens.wallets.activity
22

3-
import androidx.compose.foundation.layout.Box
43
import androidx.compose.foundation.layout.Column
54
import androidx.compose.foundation.layout.PaddingValues
6-
import androidx.compose.foundation.layout.Spacer
7-
import androidx.compose.foundation.layout.fillMaxSize
8-
import androidx.compose.foundation.layout.height
95
import androidx.compose.foundation.layout.padding
6+
import androidx.compose.foundation.lazy.rememberLazyListState
107
import androidx.compose.material3.ExperimentalMaterial3Api
118
import androidx.compose.runtime.Composable
9+
import androidx.compose.runtime.LaunchedEffect
1210
import androidx.compose.runtime.getValue
1311
import androidx.compose.ui.Modifier
1412
import androidx.compose.ui.platform.testTag
@@ -17,7 +15,6 @@ import androidx.compose.ui.tooling.preview.Preview
1715
import androidx.compose.ui.unit.dp
1816
import androidx.lifecycle.compose.collectAsStateWithLifecycle
1917
import com.synonym.bitkitcore.Activity
20-
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
2118
import kotlinx.collections.immutable.ImmutableList
2219
import kotlinx.collections.immutable.ImmutableSet
2320
import kotlinx.collections.immutable.persistentListOf
@@ -28,6 +25,7 @@ import to.bitkit.ui.appViewModel
2825
import to.bitkit.ui.components.Sheet
2926
import to.bitkit.ui.scaffold.AppTopBar
3027
import to.bitkit.ui.scaffold.DrawerNavIcon
28+
import to.bitkit.ui.scaffold.PinnedTabsScaffold
3129
import to.bitkit.ui.screens.wallets.activity.components.ActivityListFilter
3230
import to.bitkit.ui.screens.wallets.activity.components.ActivityListGrouped
3331
import to.bitkit.ui.screens.wallets.activity.components.ActivityTab
@@ -75,7 +73,6 @@ fun AllActivityScreen(
7573
}
7674

7775
@Composable
78-
@OptIn(ExperimentalHazeMaterialsApi::class)
7976
private fun AllActivityScreenContent(
8077
filteredActivities: ImmutableList<Activity>?,
8178
searchText: String,
@@ -93,6 +90,12 @@ private fun AllActivityScreenContent(
9390
onActivityItemClick: (String) -> Unit,
9491
onEmptyActivityRowClick: () -> Unit,
9592
) {
93+
val listState = rememberLazyListState()
94+
95+
LaunchedEffect(currentTabIndex) {
96+
listState.scrollToItem(0)
97+
}
98+
9699
Column(
97100
modifier = Modifier.screen()
98101
) {
@@ -104,33 +107,30 @@ private fun AllActivityScreenContent(
104107
},
105108
)
106109

107-
ActivityListFilter(
108-
searchText = searchText,
109-
onSearchTextChange = onSearchTextChange,
110-
hasTagFilter = hasTagFilter,
111-
hasDateRangeFilter = hasDateRangeFilter,
112-
onTagClick = onTagClick,
113-
selectedTags = selectedTags,
114-
onRemoveTag = onRemoveTag,
115-
onDateRangeClick = onDateRangeClick,
116-
tabs = tabs,
117-
currentTabIndex = currentTabIndex,
118-
onTabChange = { onTabChange(tabs.indexOf(it)) },
119-
modifier = Modifier.padding(horizontal = 16.dp)
120-
121-
)
122-
Spacer(modifier = Modifier.height(16.dp))
123-
124-
// List
125-
Box(
126-
modifier = Modifier
127-
.fillMaxSize()
128-
) {
110+
PinnedTabsScaffold(
111+
header = {
112+
ActivityListFilter(
113+
searchText = searchText,
114+
onSearchTextChange = onSearchTextChange,
115+
hasTagFilter = hasTagFilter,
116+
hasDateRangeFilter = hasDateRangeFilter,
117+
onTagClick = onTagClick,
118+
selectedTags = selectedTags,
119+
onRemoveTag = onRemoveTag,
120+
onDateRangeClick = onDateRangeClick,
121+
tabs = tabs,
122+
currentTabIndex = currentTabIndex,
123+
onTabChange = { onTabChange(tabs.indexOf(it)) },
124+
modifier = Modifier.padding(horizontal = 16.dp)
125+
)
126+
}
127+
) { topPadding ->
129128
ActivityListGrouped(
130129
items = filteredActivities,
131130
onActivityItemClick = onActivityItemClick,
132131
onEmptyActivityRowClick = onEmptyActivityRowClick,
133-
contentPadding = PaddingValues(top = 0.dp),
132+
listState = listState,
133+
contentPadding = PaddingValues(top = topPadding + 16.dp),
134134
modifier = Modifier
135135
.swipeToChangeTab(
136136
currentTabIndex = currentTabIndex,

0 commit comments

Comments
 (0)