@momo-kits/native-kits 0.162.35-debug → 0.162.36-debug
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/compose/build.gradle.kts +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +33 -47
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +28 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +3 -1
- package/gradle.properties +1 -1
- package/package.json +1 -1
package/compose/build.gradle.kts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
package vn.momo.kits.navigation
|
|
2
2
|
|
|
3
3
|
import androidx.compose.animation.animateColorAsState
|
|
4
|
-
import androidx.compose.animation.core.animateDpAsState
|
|
5
4
|
import androidx.compose.foundation.ScrollState
|
|
6
5
|
import androidx.compose.foundation.background
|
|
7
6
|
import androidx.compose.foundation.border
|
|
8
7
|
import androidx.compose.foundation.gestures.ScrollableState
|
|
8
|
+
import androidx.compose.foundation.gestures.animateScrollBy
|
|
9
9
|
import androidx.compose.foundation.layout.Box
|
|
10
10
|
import androidx.compose.foundation.layout.Column
|
|
11
11
|
import androidx.compose.foundation.layout.ColumnScope
|
|
@@ -21,7 +21,6 @@ import androidx.compose.foundation.layout.imePadding
|
|
|
21
21
|
import androidx.compose.foundation.layout.offset
|
|
22
22
|
import androidx.compose.foundation.layout.padding
|
|
23
23
|
import androidx.compose.foundation.lazy.LazyListState
|
|
24
|
-
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
25
24
|
import androidx.compose.foundation.rememberScrollState
|
|
26
25
|
import androidx.compose.foundation.verticalScroll
|
|
27
26
|
import androidx.compose.material.ExperimentalMaterialApi
|
|
@@ -45,8 +44,11 @@ import androidx.compose.ui.layout.onGloballyPositioned
|
|
|
45
44
|
import androidx.compose.ui.platform.LocalDensity
|
|
46
45
|
import androidx.compose.ui.unit.Dp
|
|
47
46
|
import androidx.compose.ui.unit.dp
|
|
47
|
+
import androidx.compose.ui.unit.lerp
|
|
48
48
|
import androidx.compose.ui.zIndex
|
|
49
49
|
import kotlinx.coroutines.flow.Flow
|
|
50
|
+
import kotlinx.coroutines.flow.filter
|
|
51
|
+
import kotlinx.coroutines.launch
|
|
50
52
|
import vn.momo.kits.components.InputSearch
|
|
51
53
|
import vn.momo.kits.const.AppNavigationBar
|
|
52
54
|
import vn.momo.kits.const.AppStatusBar
|
|
@@ -154,13 +156,15 @@ internal fun StackScreen(
|
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
val limit = with(density) {
|
|
157
|
-
(statusBar).toPx()
|
|
159
|
+
(statusBar + HEADER_HEIGHT.dp).toPx()
|
|
158
160
|
}.toInt()
|
|
159
161
|
|
|
160
|
-
val
|
|
161
|
-
|
|
162
|
+
val lazyListState = options.scrollData.scrollState as? LazyListState
|
|
163
|
+
val (scrollState, _) = if (lazyListState != null)
|
|
164
|
+
lazyListState.proxyScrollState(limit, 1)
|
|
162
165
|
else
|
|
163
|
-
(options.scrollData.scrollState as? ScrollState ?: rememberScrollState()).proxyLimitedScrollState(limit,
|
|
166
|
+
(options.scrollData.scrollState as? ScrollState ?: rememberScrollState()).proxyLimitedScrollState(limit, 1)
|
|
167
|
+
val snapState: ScrollableState = lazyListState ?: scrollState
|
|
164
168
|
|
|
165
169
|
val footerHeightPx = remember { mutableIntStateOf(0) }
|
|
166
170
|
val headerRightWidthPx = remember { mutableIntStateOf(0) }
|
|
@@ -205,7 +209,7 @@ internal fun StackScreen(
|
|
|
205
209
|
}
|
|
206
210
|
|
|
207
211
|
Column(Modifier.zIndex(5f)) {
|
|
208
|
-
SearchAnimated(
|
|
212
|
+
SearchAnimated(snapState = snapState)
|
|
209
213
|
}
|
|
210
214
|
|
|
211
215
|
Column(Modifier.zIndex(2f).fillMaxSize()) {
|
|
@@ -269,11 +273,7 @@ internal fun ColumnScope.MainContent(content: @Composable () -> Unit, isBottomTa
|
|
|
269
273
|
if (inputSearchType == InputSearchType.Animated) {
|
|
270
274
|
val scrollDp = with(density) { scrollState.value.toDp() }
|
|
271
275
|
|
|
272
|
-
|
|
273
|
-
targetValue = (HEADER_HEIGHT.dp - scrollDp).coerceIn(0.dp, HEADER_HEIGHT.dp),
|
|
274
|
-
label = "AnimatedTopPadding"
|
|
275
|
-
)
|
|
276
|
-
Spacer(Modifier.height(animatedTopPadding))
|
|
276
|
+
Spacer(Modifier.height((HEADER_HEIGHT.dp - scrollDp).coerceIn(0.dp, HEADER_HEIGHT.dp)))
|
|
277
277
|
}
|
|
278
278
|
Column(
|
|
279
279
|
Modifier
|
|
@@ -413,7 +413,7 @@ data class InputSearchLayoutParams(
|
|
|
413
413
|
@Suppress("FrequentlyChangingValue")
|
|
414
414
|
@Composable
|
|
415
415
|
internal fun SearchAnimated(
|
|
416
|
-
|
|
416
|
+
snapState: ScrollableState
|
|
417
417
|
) {
|
|
418
418
|
val scrollState = LocalScrollState.current
|
|
419
419
|
val options = LocalOptions.current
|
|
@@ -453,44 +453,24 @@ internal fun SearchAnimated(
|
|
|
453
453
|
}
|
|
454
454
|
|
|
455
455
|
InputSearchType.Animated -> {
|
|
456
|
-
val
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
)
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
targetValue = (minEndPadding + scrollDp).coerceIn(minEndPadding, maxEndPadding),
|
|
468
|
-
label = "AnimatedEndPadding"
|
|
469
|
-
)
|
|
470
|
-
|
|
471
|
-
val maxPadding = remember(maxTopPadding, maxStartPadding, maxEndPadding) {
|
|
472
|
-
maxOf(maxEndPadding, maxStartPadding, maxTopPadding)
|
|
473
|
-
}
|
|
474
|
-
val snapScrollValue = with(density) { maxPadding.toPx().toInt() }
|
|
475
|
-
|
|
476
|
-
LaunchedEffect(isScrollInProgress && scrollState.value != 0 && scrollState.value != snapScrollValue) {
|
|
477
|
-
if (scrollDp < maxPadding) {
|
|
478
|
-
if (!isScrollInProgress) {
|
|
479
|
-
val midpoint = (maxTopPadding - minTopPadding) / 2
|
|
480
|
-
|
|
481
|
-
if (scrollDp < midpoint) {
|
|
482
|
-
scrollState.animateScrollTo(0)
|
|
483
|
-
} else {
|
|
484
|
-
scrollState.animateScrollTo(snapScrollValue)
|
|
485
|
-
}
|
|
456
|
+
val collapsePx = with(density) { HEADER_HEIGHT.dp.roundToPx() }
|
|
457
|
+
val progress = (scrollState.value / collapsePx.toFloat()).coerceIn(0f, 1f)
|
|
458
|
+
|
|
459
|
+
LaunchedEffect(snapState, collapsePx) {
|
|
460
|
+
snapshotFlow { snapState.isScrollInProgress }
|
|
461
|
+
.filter { !it }
|
|
462
|
+
.collect {
|
|
463
|
+
val offset = snapState.headerOffsetPx() ?: return@collect
|
|
464
|
+
if (offset <= 0 || offset >= collapsePx) return@collect
|
|
465
|
+
val target = if (offset < collapsePx / 2) 0 else collapsePx
|
|
466
|
+
launch { snapState.animateScrollBy((target - offset).toFloat()) }
|
|
486
467
|
}
|
|
487
|
-
}
|
|
488
468
|
}
|
|
489
469
|
|
|
490
470
|
InputSearchLayoutParams(
|
|
491
|
-
topPadding =
|
|
492
|
-
startPadding =
|
|
493
|
-
endPadding =
|
|
471
|
+
topPadding = lerp(maxTopPadding, minTopPadding, progress),
|
|
472
|
+
startPadding = lerp(minStartPadding, maxStartPadding, progress),
|
|
473
|
+
endPadding = lerp(minEndPadding, maxEndPadding, progress)
|
|
494
474
|
)
|
|
495
475
|
}
|
|
496
476
|
|
|
@@ -519,6 +499,12 @@ internal fun SearchAnimated(
|
|
|
519
499
|
}
|
|
520
500
|
}
|
|
521
501
|
|
|
502
|
+
private fun ScrollableState.headerOffsetPx(): Int? = when (this) {
|
|
503
|
+
is LazyListState -> if (firstVisibleItemIndex == 0) firstVisibleItemScrollOffset else null
|
|
504
|
+
is ScrollState -> value
|
|
505
|
+
else -> null
|
|
506
|
+
}
|
|
507
|
+
|
|
522
508
|
private fun quantize(value: Int, stepPx: Int): Int {
|
|
523
509
|
if (stepPx <= 1) return value
|
|
524
510
|
return (value / stepPx) * stepPx
|
|
@@ -15,11 +15,14 @@ import androidx.compose.foundation.layout.offset
|
|
|
15
15
|
import androidx.compose.foundation.layout.padding
|
|
16
16
|
import androidx.compose.foundation.layout.size
|
|
17
17
|
import androidx.compose.foundation.layout.width
|
|
18
|
+
import androidx.compose.foundation.layout.wrapContentHeight
|
|
18
19
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
19
20
|
import androidx.compose.runtime.Composable
|
|
20
21
|
import androidx.compose.runtime.getValue
|
|
21
22
|
import androidx.compose.ui.Alignment
|
|
22
23
|
import androidx.compose.ui.Modifier
|
|
24
|
+
import androidx.compose.ui.draw.clipToBounds
|
|
25
|
+
import androidx.compose.ui.graphics.graphicsLayer
|
|
23
26
|
import androidx.compose.ui.graphics.Brush
|
|
24
27
|
import androidx.compose.ui.graphics.Color
|
|
25
28
|
import androidx.compose.ui.layout.onGloballyPositioned
|
|
@@ -28,6 +31,7 @@ import androidx.compose.ui.text.style.TextAlign
|
|
|
28
31
|
import androidx.compose.ui.unit.Dp
|
|
29
32
|
import androidx.compose.ui.unit.dp
|
|
30
33
|
import vn.momo.kits.components.Icon
|
|
34
|
+
import vn.momo.kits.components.Image
|
|
31
35
|
import vn.momo.kits.components.InputSearchProps
|
|
32
36
|
import vn.momo.kits.const.AppStatusBar
|
|
33
37
|
import vn.momo.kits.const.AppTheme
|
|
@@ -88,15 +92,38 @@ internal fun Header(onBackHandler: (() -> Unit)? = null) {
|
|
|
88
92
|
// Centered title needs a full-width overlay to stay screen-centered, not boxed between back button and header right.
|
|
89
93
|
val centerTitle = titlePosition == TitlePosition.CENTER && options.headerTitle is HeaderTitle.Default
|
|
90
94
|
|
|
95
|
+
val headerImage = AppTheme.current.assets.headerBackground
|
|
96
|
+
.takeUnless { options.headerType is HeaderType.Transparent }
|
|
97
|
+
|
|
91
98
|
Box(
|
|
92
99
|
Modifier.height(AppStatusBar.current + HEADER_HEIGHT.dp)
|
|
93
100
|
.fillMaxWidth()
|
|
94
|
-
.
|
|
101
|
+
.conditional(headerImage.isNullOrEmpty()) {
|
|
102
|
+
background(background.copy(alpha = animatedAlpha))
|
|
103
|
+
}
|
|
95
104
|
.conditional(IsShowBaseLineDebug) {
|
|
96
105
|
border(1.dp, Colors.blue_03)
|
|
97
106
|
},
|
|
98
107
|
contentAlignment = Alignment.BottomCenter
|
|
99
108
|
) {
|
|
109
|
+
if (!headerImage.isNullOrEmpty()) {
|
|
110
|
+
Box(
|
|
111
|
+
Modifier
|
|
112
|
+
.matchParentSize()
|
|
113
|
+
.clipToBounds()
|
|
114
|
+
.graphicsLayer { alpha = animatedAlpha }
|
|
115
|
+
.background(background)
|
|
116
|
+
) {
|
|
117
|
+
Image(
|
|
118
|
+
source = headerImage,
|
|
119
|
+
loading = false,
|
|
120
|
+
modifier = Modifier
|
|
121
|
+
.fillMaxWidth()
|
|
122
|
+
.wrapContentHeight(Alignment.Top, unbounded = true)
|
|
123
|
+
.height(HEADER_BACKGROUND_HEIGHT),
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
100
127
|
Row(
|
|
101
128
|
modifier = Modifier.height(HEADER_HEIGHT.dp)
|
|
102
129
|
.fillMaxWidth()
|
|
@@ -27,6 +27,8 @@ enum class AnimatedHeaderRatio(val value: Float){
|
|
|
27
27
|
RATIO_3_2(3f / 2f)
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
internal val HEADER_BACKGROUND_HEIGHT = 154.dp
|
|
31
|
+
|
|
30
32
|
@Suppress("FrequentlyChangingValue")
|
|
31
33
|
@Composable
|
|
32
34
|
internal fun HeaderBackground() {
|
|
@@ -37,7 +39,7 @@ internal fun HeaderBackground() {
|
|
|
37
39
|
val backgroundColor = options.backgroundColor ?: AppTheme.current.colors.background.default
|
|
38
40
|
|
|
39
41
|
val minHeight = AppStatusBar.current + HEADER_HEIGHT.dp
|
|
40
|
-
val maxHeight =
|
|
42
|
+
val maxHeight = HEADER_BACKGROUND_HEIGHT
|
|
41
43
|
val opacity by animateFloatAsState(
|
|
42
44
|
targetValue = (1 - (scrollState.value * 1f / HEADER_HEIGHT * 1f)).coerceIn(0f, 1f),
|
|
43
45
|
)
|
package/gradle.properties
CHANGED