@momo-kits/native-kits 0.151.2-test.1 → 0.151.2-test.11
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 +2 -0
- package/compose/src/commonMain/composeResources/files/lottie_circle_loader.json +1 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +1 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +2 -14
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +1 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +1 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +1 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +2 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +1 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +7 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +2 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +1 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +16 -15
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/LottieView.kt +39 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +4 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +4 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +23 -5
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +4 -5
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +132 -119
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +21 -8
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +21 -20
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +5 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +4 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +14 -0
- package/ios/Button/Button.swift +25 -4
- package/ios/Lottie/LottieView.swift +86 -0
- package/ios/native-kits.podspec +1 -0
- package/package.json +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +0 -385
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Localize.kt +0 -35
|
@@ -7,6 +7,7 @@ import androidx.compose.foundation.background
|
|
|
7
7
|
import androidx.compose.foundation.gestures.ScrollableState
|
|
8
8
|
import androidx.compose.foundation.layout.Box
|
|
9
9
|
import androidx.compose.foundation.layout.Column
|
|
10
|
+
import androidx.compose.foundation.layout.ColumnScope
|
|
10
11
|
import androidx.compose.foundation.layout.Spacer
|
|
11
12
|
import androidx.compose.foundation.layout.WindowInsets
|
|
12
13
|
import androidx.compose.foundation.layout.aspectRatio
|
|
@@ -26,9 +27,9 @@ import androidx.compose.runtime.Composable
|
|
|
26
27
|
import androidx.compose.runtime.CompositionLocalProvider
|
|
27
28
|
import androidx.compose.runtime.LaunchedEffect
|
|
28
29
|
import androidx.compose.runtime.getValue
|
|
30
|
+
import androidx.compose.runtime.mutableIntStateOf
|
|
29
31
|
import androidx.compose.runtime.mutableStateOf
|
|
30
32
|
import androidx.compose.runtime.remember
|
|
31
|
-
import androidx.compose.runtime.setValue
|
|
32
33
|
import androidx.compose.runtime.snapshotFlow
|
|
33
34
|
import androidx.compose.runtime.staticCompositionLocalOf
|
|
34
35
|
import androidx.compose.ui.Alignment
|
|
@@ -42,7 +43,6 @@ import androidx.compose.ui.unit.Dp
|
|
|
42
43
|
import androidx.compose.ui.unit.dp
|
|
43
44
|
import androidx.compose.ui.unit.min
|
|
44
45
|
import androidx.compose.ui.zIndex
|
|
45
|
-
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
46
46
|
import vn.momo.kits.components.InputSearch
|
|
47
47
|
import vn.momo.kits.const.AppNavigationBar
|
|
48
48
|
import vn.momo.kits.const.AppStatusBar
|
|
@@ -52,16 +52,17 @@ import vn.momo.kits.modifier.conditional
|
|
|
52
52
|
import vn.momo.kits.modifier.hideKeyboardOnTap
|
|
53
53
|
import vn.momo.kits.navigation.component.FABPosition
|
|
54
54
|
import vn.momo.kits.navigation.component.FloatingButton
|
|
55
|
+
import vn.momo.kits.navigation.component.HEADER_HEIGHT
|
|
55
56
|
import vn.momo.kits.navigation.component.Header
|
|
56
57
|
import vn.momo.kits.navigation.component.HeaderBackground
|
|
57
58
|
import vn.momo.kits.navigation.component.HeaderRight
|
|
58
59
|
import vn.momo.kits.navigation.component.HeaderType
|
|
60
|
+
import vn.momo.kits.navigation.component.InputSearchType
|
|
59
61
|
import vn.momo.kits.platform.BackHandler
|
|
60
62
|
import vn.momo.kits.platform.getAndroidBuildVersion
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
enum class InputSearchType { None, Header, Animated }
|
|
64
|
+
internal val LocalFooterHeightPx = staticCompositionLocalOf { mutableIntStateOf(0) }
|
|
65
|
+
internal val LocalHeaderRightWidthPx = staticCompositionLocalOf { mutableIntStateOf(0) }
|
|
65
66
|
|
|
66
67
|
@OptIn(ExperimentalMaterialApi::class)
|
|
67
68
|
@Composable
|
|
@@ -73,43 +74,36 @@ internal fun StackScreen(
|
|
|
73
74
|
) {
|
|
74
75
|
val navigator = LocalNavigator.current
|
|
75
76
|
val statusBar = AppStatusBar.current
|
|
77
|
+
val density = LocalDensity.current
|
|
76
78
|
val navigation = remember { Navigation(id = id, bottomTabIndex = bottomTabIndex, initOptions = navigationOptions) }
|
|
77
79
|
|
|
78
80
|
val options by navigation.currentOptions
|
|
79
81
|
|
|
80
|
-
val
|
|
81
|
-
val inputSearchType = getInputSearchType(options)
|
|
82
|
-
|
|
83
|
-
var footerHeight by remember { mutableStateOf(0) }
|
|
84
|
-
var headerRightWidth by remember { mutableStateOf(0) }
|
|
85
|
-
|
|
86
|
-
val limit = with(LocalDensity.current) {
|
|
82
|
+
val limit = with(density) {
|
|
87
83
|
(statusBar).toPx() + HEADER_HEIGHT
|
|
88
84
|
}.toInt()
|
|
89
85
|
|
|
90
86
|
val (scrollState, scrollInProcess) = if (options.scrollData.scrollState is LazyListState)
|
|
91
|
-
(options.scrollData.scrollState as? LazyListState ?: rememberLazyListState()).proxyScrollState(limit)
|
|
87
|
+
(options.scrollData.scrollState as? LazyListState ?: rememberLazyListState()).proxyScrollState(limit, 15)
|
|
92
88
|
else
|
|
93
|
-
(options.scrollData.scrollState as? ScrollState ?: rememberScrollState()).proxyLimitedScrollState(limit)
|
|
94
|
-
|
|
95
|
-
val scrollDp = with(LocalDensity.current) { scrollState.value.toDp() }
|
|
89
|
+
(options.scrollData.scrollState as? ScrollState ?: rememberScrollState()).proxyLimitedScrollState(limit, 15)
|
|
96
90
|
|
|
97
|
-
val
|
|
98
|
-
val
|
|
91
|
+
val footerHeightPx = remember { mutableIntStateOf(0) }
|
|
92
|
+
val headerRightWidthPx = remember { mutableIntStateOf(0) }
|
|
99
93
|
|
|
100
|
-
BackHandler(true){
|
|
101
|
-
navigator.pop()
|
|
102
|
-
}
|
|
94
|
+
BackHandler(true) { navigator.pop() }
|
|
103
95
|
|
|
104
96
|
CompositionLocalProvider(
|
|
105
97
|
StackScreenScrollableState provides options.scrollData.scrollState,
|
|
106
98
|
LocalNavigation provides navigation,
|
|
107
99
|
LocalScrollState provides scrollState,
|
|
108
|
-
LocalOptions provides options
|
|
100
|
+
LocalOptions provides options,
|
|
101
|
+
LocalFooterHeightPx provides footerHeightPx,
|
|
102
|
+
LocalHeaderRightWidthPx provides headerRightWidthPx
|
|
109
103
|
) {
|
|
110
104
|
Box(Modifier
|
|
111
105
|
.fillMaxSize()
|
|
112
|
-
.background(backgroundColor)
|
|
106
|
+
.background(options.backgroundColor ?: AppTheme.current.colors.background.default)
|
|
113
107
|
.conditional(options.keyboardOptions.keyboardShouldPersistTaps) {
|
|
114
108
|
hideKeyboardOnTap()
|
|
115
109
|
}
|
|
@@ -122,68 +116,87 @@ internal fun StackScreen(
|
|
|
122
116
|
}
|
|
123
117
|
|
|
124
118
|
Box(Modifier.zIndex(4f)) {
|
|
125
|
-
Header(
|
|
126
|
-
onHeaderRightWidthMeasured = { headerRightWidth = it}
|
|
127
|
-
)
|
|
119
|
+
Header()
|
|
128
120
|
}
|
|
129
121
|
|
|
130
122
|
Column (Modifier.zIndex(5f)) {
|
|
131
|
-
SearchAnimated(
|
|
132
|
-
inputSearchType = inputSearchType,
|
|
133
|
-
isScrollInProgress = scrollInProcess,
|
|
134
|
-
headerRightWidth = headerRightWidth
|
|
135
|
-
)
|
|
123
|
+
SearchAnimated(isScrollInProgress = scrollInProcess)
|
|
136
124
|
}
|
|
137
125
|
|
|
138
126
|
Column(Modifier.zIndex(2f).fillMaxSize()) {
|
|
139
|
-
|
|
140
|
-
if (inputSearchType == InputSearchType.Animated){
|
|
141
|
-
val animatedTopPadding by animateDpAsState(
|
|
142
|
-
targetValue = (HEADER_HEIGHT.dp - scrollDp).coerceIn(0.dp, HEADER_HEIGHT.dp),
|
|
143
|
-
label = "AnimatedTopPadding"
|
|
144
|
-
)
|
|
145
|
-
Spacer(Modifier.height(animatedTopPadding))
|
|
146
|
-
}
|
|
147
|
-
Column (Modifier
|
|
148
|
-
.fillMaxWidth()
|
|
149
|
-
.weight(1f)
|
|
150
|
-
.conditional(options.scrollData.scrollable && options.scrollData.scrollState is ScrollState) {
|
|
151
|
-
verticalScroll(scrollState)
|
|
152
|
-
}
|
|
153
|
-
) {
|
|
154
|
-
ScreenContent(content = content)
|
|
155
|
-
}
|
|
156
|
-
if (options.footerComponent != null){
|
|
157
|
-
Footer(footerComponent = options.footerComponent, bottomPadding = bottomPadding, onHeightMeasured = { footerHeight = it })
|
|
158
|
-
}
|
|
127
|
+
MainContent(content = content)
|
|
159
128
|
}
|
|
160
129
|
|
|
161
130
|
Box(Modifier.zIndex(5f)){
|
|
162
|
-
|
|
163
|
-
val bottomPadding = fabProps.bottom ?:
|
|
164
|
-
(Spacing.M + if (options.footerComponent != null) with(LocalDensity.current){ footerHeight.toDp() } else 0.dp)
|
|
165
|
-
|
|
166
|
-
FloatingButton(
|
|
167
|
-
scrollPosition = fabProps.scrollState?.value ?: scrollState.value,
|
|
168
|
-
onClick = fabProps.onClick,
|
|
169
|
-
containerColor = AppTheme.current.colors.primary,
|
|
170
|
-
bottom = bottomPadding,
|
|
171
|
-
icon = fabProps.icon,
|
|
172
|
-
iconColor = fabProps.iconColor,
|
|
173
|
-
text = fabProps.label,
|
|
174
|
-
size = fabProps.size,
|
|
175
|
-
position = fabProps.position ?: FABPosition.END,
|
|
176
|
-
)
|
|
131
|
+
FloatingContent()
|
|
177
132
|
}
|
|
178
133
|
|
|
179
134
|
Box(Modifier.zIndex(6f).fillMaxSize()){
|
|
180
135
|
if (bottomTabIndex != -1) return@Box
|
|
181
|
-
OverplayComponentRegistry.
|
|
136
|
+
OverplayComponentRegistry.OverlayComponent()
|
|
182
137
|
}
|
|
183
138
|
}
|
|
184
139
|
}
|
|
185
140
|
}
|
|
186
141
|
|
|
142
|
+
@Composable
|
|
143
|
+
fun FloatingContent(){
|
|
144
|
+
val options = LocalOptions.current
|
|
145
|
+
val density = LocalDensity.current
|
|
146
|
+
val footerHeightPx = LocalFooterHeightPx.current
|
|
147
|
+
val scrollState = LocalScrollState.current
|
|
148
|
+
|
|
149
|
+
val fabProps = options.floatingButtonProps ?: return
|
|
150
|
+
val bottomPadding = fabProps.bottom ?:
|
|
151
|
+
(Spacing.M + if (options.footerComponent != null) with(density){ footerHeightPx.intValue.toDp() } else 0.dp)
|
|
152
|
+
|
|
153
|
+
FloatingButton(
|
|
154
|
+
scrollPosition = fabProps.scrollState?.value ?: scrollState.value,
|
|
155
|
+
onClick = fabProps.onClick,
|
|
156
|
+
containerColor = AppTheme.current.colors.primary,
|
|
157
|
+
bottom = bottomPadding,
|
|
158
|
+
icon = fabProps.icon,
|
|
159
|
+
iconColor = fabProps.iconColor,
|
|
160
|
+
text = fabProps.label,
|
|
161
|
+
size = fabProps.size,
|
|
162
|
+
position = fabProps.position ?: FABPosition.END,
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@Composable
|
|
167
|
+
fun ColumnScope.MainContent(content: @Composable ()-> Unit){
|
|
168
|
+
val options = LocalOptions.current
|
|
169
|
+
val inputSearchType = getInputSearchType(options)
|
|
170
|
+
val density = LocalDensity.current
|
|
171
|
+
val scrollState = LocalScrollState.current
|
|
172
|
+
|
|
173
|
+
Spacer(Modifier.height(if (options.headerType is HeaderType.DefaultOrExtended) AppStatusBar.current + HEADER_HEIGHT.dp else 0.dp))
|
|
174
|
+
if (inputSearchType == InputSearchType.Animated){
|
|
175
|
+
val scrollDp = with(density) { scrollState.value.toDp() }
|
|
176
|
+
|
|
177
|
+
val animatedTopPadding by animateDpAsState(
|
|
178
|
+
targetValue = (HEADER_HEIGHT.dp - scrollDp).coerceIn(0.dp, HEADER_HEIGHT.dp),
|
|
179
|
+
label = "AnimatedTopPadding"
|
|
180
|
+
)
|
|
181
|
+
Spacer(Modifier.height(animatedTopPadding))
|
|
182
|
+
}
|
|
183
|
+
Column (Modifier
|
|
184
|
+
.fillMaxWidth()
|
|
185
|
+
.weight(1f)
|
|
186
|
+
.conditional(options.scrollData.scrollable && options.scrollData.scrollState is ScrollState) {
|
|
187
|
+
verticalScroll(scrollState)
|
|
188
|
+
}
|
|
189
|
+
) {
|
|
190
|
+
ScreenContent(content = content)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (options.footerComponent != null){
|
|
194
|
+
val isKeyboardVisible = isKeyboardVisible()
|
|
195
|
+
val bottomPadding = min(AppNavigationBar.current, if (isKeyboardVisible) 0.dp else 21.dp)
|
|
196
|
+
Footer(footerComponent = options.footerComponent, bottomPadding = bottomPadding)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
187
200
|
@Composable
|
|
188
201
|
fun ScreenContent(content: @Composable () -> Unit){
|
|
189
202
|
val scrollState = LocalScrollState.current
|
|
@@ -205,11 +218,19 @@ fun ScreenContent(content: @Composable () -> Unit){
|
|
|
205
218
|
}
|
|
206
219
|
|
|
207
220
|
@Composable
|
|
208
|
-
fun Footer(footerComponent: @Composable (() -> Unit)?,
|
|
221
|
+
fun Footer(footerComponent: @Composable (() -> Unit)?, bottomPadding: Dp) {
|
|
209
222
|
if (footerComponent == null) return
|
|
210
223
|
|
|
224
|
+
val footerHeightPx = LocalFooterHeightPx.current
|
|
225
|
+
|
|
226
|
+
val shadowBrush = remember {
|
|
227
|
+
Brush.verticalGradient(
|
|
228
|
+
colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.05f))
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
211
232
|
Box(Modifier.onGloballyPositioned {
|
|
212
|
-
|
|
233
|
+
if (footerHeightPx.intValue != it.size.height) footerHeightPx.intValue = it.size.height
|
|
213
234
|
}) {
|
|
214
235
|
Box(Modifier
|
|
215
236
|
.fillMaxWidth()
|
|
@@ -223,11 +244,7 @@ fun Footer(footerComponent: @Composable (() -> Unit)?, onHeightMeasured: (Int) -
|
|
|
223
244
|
.fillMaxWidth()
|
|
224
245
|
.height(6.dp)
|
|
225
246
|
.offset(x = 0.dp, y = (-6).dp)
|
|
226
|
-
.background(
|
|
227
|
-
brush = Brush.verticalGradient(
|
|
228
|
-
colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.05f))
|
|
229
|
-
)
|
|
230
|
-
)
|
|
247
|
+
.background(shadowBrush)
|
|
231
248
|
)
|
|
232
249
|
}
|
|
233
250
|
}
|
|
@@ -239,14 +256,18 @@ data class InputSearchLayoutParams(
|
|
|
239
256
|
)
|
|
240
257
|
@Composable
|
|
241
258
|
fun SearchAnimated(
|
|
242
|
-
|
|
243
|
-
isScrollInProgress: Boolean,
|
|
244
|
-
headerRightWidth: Int
|
|
259
|
+
isScrollInProgress: Boolean
|
|
245
260
|
) {
|
|
246
261
|
val scrollState = LocalScrollState.current
|
|
247
262
|
val options = LocalOptions.current
|
|
248
263
|
val navigator = LocalNavigator.current
|
|
249
|
-
val
|
|
264
|
+
val density = LocalDensity.current
|
|
265
|
+
|
|
266
|
+
val scrollDp = with(density) { scrollState.value.toDp() }
|
|
267
|
+
|
|
268
|
+
val inputSearchType = getInputSearchType(options)
|
|
269
|
+
val headerRightWidthPx = LocalHeaderRightWidthPx.current
|
|
270
|
+
val headerRightWidthDp = with(density) { headerRightWidthPx.intValue.toDp() }
|
|
250
271
|
|
|
251
272
|
if (inputSearchType == InputSearchType.None) return
|
|
252
273
|
val inputSearchProps = (options.headerType as? HeaderType.DefaultOrExtended)?.inputSearchProps ?: return
|
|
@@ -256,8 +277,7 @@ fun SearchAnimated(
|
|
|
256
277
|
val minStartPadding = Spacing.M
|
|
257
278
|
val maxStartPadding = if (options.hiddenBack) Spacing.M else 52.dp
|
|
258
279
|
val minEndPadding = Spacing.M
|
|
259
|
-
val maxEndPadding =
|
|
260
|
-
if (options.headerRight is HeaderRight.None) Spacing.M else Spacing.M * 2
|
|
280
|
+
val maxEndPadding = headerRightWidthDp + if (options.headerRight is HeaderRight.None) Spacing.M else Spacing.M * 2
|
|
261
281
|
|
|
262
282
|
val targetColor = lerp(
|
|
263
283
|
AppTheme.current.colors.background.surface,
|
|
@@ -268,7 +288,6 @@ fun SearchAnimated(
|
|
|
268
288
|
|
|
269
289
|
val layoutParams = when (inputSearchType) {
|
|
270
290
|
InputSearchType.Header -> {
|
|
271
|
-
|
|
272
291
|
InputSearchLayoutParams(
|
|
273
292
|
topPadding = minTopPadding,
|
|
274
293
|
startPadding = maxStartPadding,
|
|
@@ -292,8 +311,11 @@ fun SearchAnimated(
|
|
|
292
311
|
label = "AnimatedEndPadding"
|
|
293
312
|
)
|
|
294
313
|
|
|
295
|
-
val maxPadding =
|
|
296
|
-
|
|
314
|
+
val maxPadding = remember(maxTopPadding, maxStartPadding, maxEndPadding) {
|
|
315
|
+
maxOf(maxEndPadding, maxStartPadding, maxTopPadding)
|
|
316
|
+
}
|
|
317
|
+
val snapScrollValue = with(density) { maxPadding.toPx().toInt() }
|
|
318
|
+
|
|
297
319
|
LaunchedEffect(isScrollInProgress && scrollState.value != 0 && scrollState.value != snapScrollValue) {
|
|
298
320
|
if (scrollDp < maxPadding) {
|
|
299
321
|
if (!isScrollInProgress) {
|
|
@@ -344,34 +366,32 @@ fun SearchAnimated(
|
|
|
344
366
|
internal fun isKeyboardVisible(): Boolean {
|
|
345
367
|
val ime = WindowInsets.ime
|
|
346
368
|
val density = LocalDensity.current
|
|
347
|
-
|
|
348
|
-
|
|
369
|
+
return ime.getBottom(density) > 0
|
|
370
|
+
}
|
|
371
|
+
private fun quantize(value: Int, stepPx: Int): Int {
|
|
372
|
+
if (stepPx <= 1) return value
|
|
373
|
+
return (value / stepPx) * stepPx
|
|
349
374
|
}
|
|
350
375
|
|
|
351
376
|
@Composable
|
|
352
|
-
fun LazyListState.proxyScrollState(
|
|
377
|
+
fun LazyListState.proxyScrollState(
|
|
378
|
+
thresholdPx: Int = 200,
|
|
379
|
+
stepPx: Int = 10
|
|
380
|
+
): Pair<ScrollState, Boolean> {
|
|
353
381
|
val scrollState = rememberScrollState()
|
|
354
382
|
val locked = remember { mutableStateOf(false) }
|
|
355
383
|
|
|
356
|
-
LaunchedEffect(this, scrollState, thresholdPx) {
|
|
384
|
+
LaunchedEffect(this, scrollState, thresholdPx, stepPx) {
|
|
357
385
|
snapshotFlow { firstVisibleItemIndex to firstVisibleItemScrollOffset }
|
|
358
|
-
.distinctUntilChanged()
|
|
359
386
|
.collect { (index, offset) ->
|
|
360
|
-
val
|
|
361
|
-
val shouldLock =
|
|
387
|
+
val rawTarget = if (index == 0) offset else SCROLLED_PAST_FIRST_ITEM_THRESHOLD
|
|
388
|
+
val shouldLock = rawTarget > thresholdPx
|
|
362
389
|
if (locked.value != shouldLock) locked.value = shouldLock
|
|
363
390
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
else -> {
|
|
371
|
-
if (scrollState.value != target) {
|
|
372
|
-
scrollState.scrollTo(target)
|
|
373
|
-
}
|
|
374
|
-
}
|
|
391
|
+
val desired = if (shouldLock) thresholdPx else quantize(rawTarget, stepPx)
|
|
392
|
+
|
|
393
|
+
if (scrollState.value != desired) {
|
|
394
|
+
scrollState.scrollTo(desired.coerceAtLeast(0))
|
|
375
395
|
}
|
|
376
396
|
}
|
|
377
397
|
}
|
|
@@ -388,28 +408,23 @@ fun LazyListState.proxyScrollState(thresholdPx: Int = 200): Pair<ScrollState, Bo
|
|
|
388
408
|
}
|
|
389
409
|
|
|
390
410
|
@Composable
|
|
391
|
-
fun ScrollState.proxyLimitedScrollState(
|
|
411
|
+
fun ScrollState.proxyLimitedScrollState(
|
|
412
|
+
thresholdPx: Int = 200,
|
|
413
|
+
stepPx: Int = 10
|
|
414
|
+
): Pair<ScrollState, Boolean> {
|
|
392
415
|
val proxy = rememberScrollState()
|
|
393
416
|
val locked = remember { mutableStateOf(false) }
|
|
394
417
|
|
|
395
|
-
LaunchedEffect(this, proxy, thresholdPx) {
|
|
418
|
+
LaunchedEffect(this, proxy, thresholdPx, stepPx) {
|
|
396
419
|
snapshotFlow { value }
|
|
397
|
-
.
|
|
398
|
-
|
|
399
|
-
val shouldLock = target > thresholdPx
|
|
420
|
+
.collect { rawTarget ->
|
|
421
|
+
val shouldLock = rawTarget > thresholdPx
|
|
400
422
|
if (locked.value != shouldLock) locked.value = shouldLock
|
|
401
423
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
else -> {
|
|
409
|
-
if (proxy.value != target) {
|
|
410
|
-
proxy.scrollTo(target)
|
|
411
|
-
}
|
|
412
|
-
}
|
|
424
|
+
val desired = if (shouldLock) thresholdPx else quantize(rawTarget, stepPx)
|
|
425
|
+
|
|
426
|
+
if (proxy.value != desired) {
|
|
427
|
+
proxy.scrollTo(desired.coerceAtLeast(0))
|
|
413
428
|
}
|
|
414
429
|
}
|
|
415
430
|
}
|
|
@@ -425,8 +440,6 @@ fun ScrollState.proxyLimitedScrollState(thresholdPx: Int = 200): Pair<ScrollStat
|
|
|
425
440
|
return proxy to inProgress.value
|
|
426
441
|
}
|
|
427
442
|
|
|
428
|
-
|
|
429
|
-
|
|
430
443
|
private const val SCROLLED_PAST_FIRST_ITEM_THRESHOLD = 10_000
|
|
431
444
|
|
|
432
445
|
internal val LocalScrollState = staticCompositionLocalOf<ScrollState> { error("No Scroll State provided") }
|
|
@@ -23,15 +23,13 @@ import androidx.navigation.compose.NavHost
|
|
|
23
23
|
import androidx.navigation.compose.composable
|
|
24
24
|
import androidx.navigation.compose.rememberNavController
|
|
25
25
|
import vn.momo.kits.const.AppNavigationBar
|
|
26
|
-
import vn.momo.kits.const.AppStatusBar
|
|
27
26
|
import vn.momo.kits.const.AppTheme
|
|
28
27
|
import vn.momo.kits.const.Spacing
|
|
29
28
|
import vn.momo.kits.navigation.LocalNavigation
|
|
29
|
+
import vn.momo.kits.navigation.LocalNavigator
|
|
30
30
|
import vn.momo.kits.navigation.NavigationOptions
|
|
31
31
|
import vn.momo.kits.navigation.StackScreen
|
|
32
32
|
import vn.momo.kits.navigation.component.HeaderType
|
|
33
|
-
import vn.momo.kits.platform.getPlatformName
|
|
34
|
-
import vn.momo.kits.platform.getScreenDimensions
|
|
35
33
|
import vn.momo.kits.platform.getScreenHeight
|
|
36
34
|
|
|
37
35
|
private var bottomTabOptionItems : MutableList<NavigationOptions?> = mutableListOf()
|
|
@@ -52,9 +50,22 @@ fun BottomTab(
|
|
|
52
50
|
floatingButton: BottomTabFloatingButton? = null
|
|
53
51
|
) {
|
|
54
52
|
val navigation = LocalNavigation.current
|
|
53
|
+
val navigator = LocalNavigator.current
|
|
55
54
|
val navController = rememberNavController()
|
|
56
55
|
|
|
57
|
-
bottomTabOptionItems = items.
|
|
56
|
+
bottomTabOptionItems = items.mapIndexed { index, item ->
|
|
57
|
+
val options = item.options ?: NavigationOptions()
|
|
58
|
+
options.copy(
|
|
59
|
+
onBackHandler = {
|
|
60
|
+
if (index != 0) {
|
|
61
|
+
navController.popBackStack()
|
|
62
|
+
} else {
|
|
63
|
+
navigator.pop()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
}.toMutableList()
|
|
68
|
+
|
|
58
69
|
|
|
59
70
|
LaunchedEffect(Unit){
|
|
60
71
|
navigation.setOptions(
|
|
@@ -103,13 +114,15 @@ fun BottomTab(
|
|
|
103
114
|
)
|
|
104
115
|
}
|
|
105
116
|
) {
|
|
106
|
-
val option = getBottomTabOption(index)?.
|
|
107
|
-
|
|
108
|
-
|
|
117
|
+
val option = getBottomTabOption(index)?.copy(
|
|
118
|
+
onBackHandler = {
|
|
119
|
+
if (index != 0) {
|
|
109
120
|
navController.popBackStack()
|
|
121
|
+
} else {
|
|
122
|
+
navigator.pop()
|
|
110
123
|
}
|
|
111
124
|
}
|
|
112
|
-
|
|
125
|
+
)
|
|
113
126
|
|
|
114
127
|
StackScreen(
|
|
115
128
|
content = item.screen,
|
|
@@ -24,8 +24,6 @@ import androidx.compose.ui.platform.LocalDensity
|
|
|
24
24
|
import androidx.compose.ui.text.style.TextAlign
|
|
25
25
|
import androidx.compose.ui.unit.Dp
|
|
26
26
|
import androidx.compose.ui.unit.dp
|
|
27
|
-
import vn.momo.kits.application.AnimatedHeaderRatio
|
|
28
|
-
import vn.momo.kits.application.HEADER_HEIGHT
|
|
29
27
|
import vn.momo.kits.components.Icon
|
|
30
28
|
import vn.momo.kits.components.InputSearchProps
|
|
31
29
|
import vn.momo.kits.const.AppStatusBar
|
|
@@ -33,24 +31,26 @@ import vn.momo.kits.const.AppTheme
|
|
|
33
31
|
import vn.momo.kits.const.Colors
|
|
34
32
|
import vn.momo.kits.const.Spacing
|
|
35
33
|
import vn.momo.kits.modifier.activeOpacityClickable
|
|
36
|
-
import vn.momo.kits.navigation.
|
|
34
|
+
import vn.momo.kits.navigation.LocalHeaderRightWidthPx
|
|
37
35
|
import vn.momo.kits.navigation.LocalNavigator
|
|
38
36
|
import vn.momo.kits.navigation.LocalOptions
|
|
39
37
|
import vn.momo.kits.navigation.LocalScrollState
|
|
40
38
|
import vn.momo.kits.navigation.getInputSearchType
|
|
41
39
|
|
|
40
|
+
const val HEADER_HEIGHT = 52
|
|
41
|
+
enum class InputSearchType { None, Header, Animated }
|
|
42
|
+
|
|
42
43
|
@Composable
|
|
43
|
-
fun Header(
|
|
44
|
-
onHeaderRightWidthMeasured: (Int) -> Unit
|
|
45
|
-
) {
|
|
44
|
+
fun Header() {
|
|
46
45
|
val options = LocalOptions.current
|
|
47
46
|
val navigator = LocalNavigator.current
|
|
48
47
|
val scrollState = LocalScrollState.current
|
|
48
|
+
val headerRightWidthPx = LocalHeaderRightWidthPx.current
|
|
49
49
|
val inputSearchType = getInputSearchType(options)
|
|
50
50
|
|
|
51
51
|
val opacityHeight = when (val header = options.headerType) {
|
|
52
52
|
is HeaderType.Animated -> with(LocalDensity.current) { header.layoutOffSet.roundToPx() }
|
|
53
|
-
else ->
|
|
53
|
+
else -> HEADER_HEIGHT
|
|
54
54
|
}
|
|
55
55
|
val opacity by animateFloatAsState(
|
|
56
56
|
targetValue = ((scrollState.value * 1f / opacityHeight * 1f)).coerceIn(0f, 1f),
|
|
@@ -75,7 +75,7 @@ fun Header(
|
|
|
75
75
|
horizontalArrangement = Arrangement.SpaceBetween
|
|
76
76
|
) {
|
|
77
77
|
if(!options.hiddenBack) {
|
|
78
|
-
|
|
78
|
+
BackButton(
|
|
79
79
|
borderColor = headerColor.borderColor,
|
|
80
80
|
backgroundButton = headerColor.backgroundButton,
|
|
81
81
|
tintIconColor = headerColor.tintIconColor,
|
|
@@ -85,12 +85,14 @@ fun Header(
|
|
|
85
85
|
)
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
HeaderContent(
|
|
89
89
|
options.headerTitle,
|
|
90
90
|
headerColor.tintIconColor
|
|
91
91
|
.copy(alpha = if (inputSearchType == InputSearchType.Animated) 1f - animatedAlpha else 1f)
|
|
92
92
|
)
|
|
93
|
-
Box(Modifier.onGloballyPositioned {
|
|
93
|
+
Box(Modifier.onGloballyPositioned {
|
|
94
|
+
if(headerRightWidthPx.intValue != it.size.width) headerRightWidthPx.intValue = it.size.width
|
|
95
|
+
}){
|
|
94
96
|
HeaderRight(options.headerRight, options.tintColor, headerColor)
|
|
95
97
|
}
|
|
96
98
|
}
|
|
@@ -99,7 +101,7 @@ fun Header(
|
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
@Composable
|
|
102
|
-
fun
|
|
104
|
+
fun BackButton(borderColor: Color, backgroundButton: Color, tintIconColor: Color, onBackHandler: () -> Unit){
|
|
103
105
|
Box(
|
|
104
106
|
modifier = Modifier
|
|
105
107
|
.size(28.dp)
|
|
@@ -118,7 +120,7 @@ fun renderBackButton(borderColor: Color, backgroundButton: Color, tintIconColor:
|
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
@Composable
|
|
121
|
-
fun RowScope.
|
|
123
|
+
fun RowScope.HeaderContent(headerTitle: HeaderTitle, tintIconColor: Color){
|
|
122
124
|
Box(
|
|
123
125
|
Modifier.weight(1f).padding(horizontal = Spacing.M)
|
|
124
126
|
) {
|
|
@@ -126,16 +128,14 @@ fun RowScope.renderHeaderContent(headerTitle: HeaderTitle, tintIconColor: Color)
|
|
|
126
128
|
is HeaderTitle.Default -> {
|
|
127
129
|
HeaderTitle(
|
|
128
130
|
title = headerTitle.title,
|
|
129
|
-
color = tintIconColor
|
|
131
|
+
color = tintIconColor,
|
|
132
|
+
modifier = Modifier.fillMaxWidth(),
|
|
133
|
+
textAlign = TextAlign.Start
|
|
130
134
|
)
|
|
131
135
|
}
|
|
132
136
|
is HeaderTitle.Journey -> {}
|
|
133
137
|
is HeaderTitle.Location -> {}
|
|
134
|
-
is HeaderTitle.User -> {
|
|
135
|
-
HeaderUser(
|
|
136
|
-
data = headerTitle
|
|
137
|
-
)
|
|
138
|
-
}
|
|
138
|
+
is HeaderTitle.User -> {}
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
}
|
|
@@ -163,8 +163,9 @@ sealed class HeaderTitle {
|
|
|
163
163
|
val title: String,
|
|
164
164
|
val subTitle: String? = null,
|
|
165
165
|
val image: List<String>? = null,
|
|
166
|
-
val dotColor:
|
|
167
|
-
val
|
|
166
|
+
val dotColor: String? = null,
|
|
167
|
+
val verify: Boolean = false,
|
|
168
|
+
val tintColor: String? = null,
|
|
168
169
|
val onPress: (() -> Unit)? = null,
|
|
169
170
|
val icons: List<String> = emptyList(),
|
|
170
171
|
val isLoading: Boolean = false
|
|
@@ -14,7 +14,6 @@ import androidx.compose.ui.graphics.Brush
|
|
|
14
14
|
import androidx.compose.ui.graphics.Color
|
|
15
15
|
import androidx.compose.ui.platform.LocalDensity
|
|
16
16
|
import androidx.compose.ui.unit.dp
|
|
17
|
-
import vn.momo.kits.application.HEADER_HEIGHT
|
|
18
17
|
import vn.momo.kits.components.Image
|
|
19
18
|
import vn.momo.kits.const.AppStatusBar
|
|
20
19
|
import vn.momo.kits.const.AppTheme
|
|
@@ -22,6 +21,11 @@ import vn.momo.kits.modifier.conditional
|
|
|
22
21
|
import vn.momo.kits.navigation.LocalOptions
|
|
23
22
|
import vn.momo.kits.navigation.LocalScrollState
|
|
24
23
|
|
|
24
|
+
enum class AnimatedHeaderRatio(val value: Float){
|
|
25
|
+
RATIO_16_9(16f / 9f),
|
|
26
|
+
RATIO_1_1(1f),
|
|
27
|
+
RATIO_3_2(3f / 2f)
|
|
28
|
+
}
|
|
25
29
|
|
|
26
30
|
@Composable
|
|
27
31
|
fun HeaderBackground() {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
package vn.momo.kits.navigation.component
|
|
2
2
|
|
|
3
|
-
import androidx.compose.foundation.layout.fillMaxWidth
|
|
4
3
|
import androidx.compose.runtime.Composable
|
|
5
4
|
import androidx.compose.ui.Modifier
|
|
6
5
|
import androidx.compose.ui.graphics.Color
|
|
@@ -14,12 +13,14 @@ import vn.momo.kits.const.Typography
|
|
|
14
13
|
@Composable
|
|
15
14
|
fun HeaderTitle(
|
|
16
15
|
title: String = "",
|
|
16
|
+
modifier: Modifier = Modifier,
|
|
17
|
+
textAlign: TextAlign = TextAlign.Start,
|
|
17
18
|
color: Color? = null,
|
|
18
19
|
) {
|
|
19
20
|
Text(
|
|
20
|
-
modifier = Modifier.
|
|
21
|
+
modifier = Modifier.then(modifier).zIndex(1f),
|
|
21
22
|
text = title,
|
|
22
|
-
textAlign =
|
|
23
|
+
textAlign = textAlign,
|
|
23
24
|
style = Typography.actionSBold.copy(
|
|
24
25
|
fontSize = 15.sp,
|
|
25
26
|
lineHeight = 22.sp,
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
package vn.momo.kits.utils
|
|
2
2
|
|
|
3
|
+
import androidx.compose.foundation.layout.WindowInsets
|
|
4
|
+
import androidx.compose.foundation.layout.asPaddingValues
|
|
5
|
+
import androidx.compose.foundation.layout.systemBars
|
|
6
|
+
import androidx.compose.runtime.Composable
|
|
3
7
|
import androidx.compose.ui.Modifier
|
|
4
8
|
import androidx.compose.ui.composed
|
|
5
9
|
import androidx.compose.ui.draw.drawBehind
|
|
@@ -8,6 +12,7 @@ import androidx.compose.ui.graphics.Color
|
|
|
8
12
|
import androidx.compose.ui.platform.LocalDensity
|
|
9
13
|
import androidx.compose.ui.unit.Dp
|
|
10
14
|
import androidx.compose.ui.unit.dp
|
|
15
|
+
import vn.momo.kits.const.AppStatusBar
|
|
11
16
|
import kotlin.math.abs
|
|
12
17
|
|
|
13
18
|
|
|
@@ -72,3 +77,12 @@ fun formatNumberToMoney(number: Long, currency: String = "đ"): String {
|
|
|
72
77
|
|
|
73
78
|
return result + currency
|
|
74
79
|
}
|
|
80
|
+
|
|
81
|
+
@Composable
|
|
82
|
+
fun getAppStatusBarHeight(): Dp {
|
|
83
|
+
return if (AppStatusBar.current == 0.dp) {
|
|
84
|
+
WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
|
|
85
|
+
} else {
|
|
86
|
+
AppStatusBar.current
|
|
87
|
+
}
|
|
88
|
+
}
|