@momo-kits/native-kits 0.151.2-test.1 → 0.151.2-test.10

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.
Files changed (33) hide show
  1. package/compose/build.gradle.kts +2 -0
  2. package/compose/src/commonMain/composeResources/files/lottie_circle_loader.json +1 -0
  3. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +1 -0
  4. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +2 -14
  5. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +1 -0
  6. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +2 -2
  7. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +1 -0
  8. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +1 -0
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +2 -0
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +1 -0
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +2 -1
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +7 -1
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +2 -0
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +1 -0
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +36 -15
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +1 -1
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +4 -2
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +1 -1
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +4 -2
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +23 -5
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +4 -5
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +132 -119
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +21 -8
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +21 -20
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +5 -1
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +4 -3
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +14 -0
  28. package/ios/Button/Button.swift +25 -4
  29. package/ios/Lottie/LottieView.swift +86 -0
  30. package/ios/native-kits.podspec +1 -0
  31. package/package.json +1 -1
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +0 -385
  33. 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
- const val HEADER_HEIGHT = 52
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 backgroundColor = options.backgroundColor ?: AppTheme.current.colors.background.default
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 isKeyboardVisible = isKeyboardVisible()
98
- val bottomPadding = min(AppNavigationBar.current, if (isKeyboardVisible) 0.dp else 21.dp)
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
- Spacer(Modifier.height(if (options.headerType is HeaderType.DefaultOrExtended) AppStatusBar.current + HEADER_HEIGHT.dp else 0.dp))
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
- val fabProps = options.floatingButtonProps ?: return@Box
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.showOverlayComponent()
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)?, onHeightMeasured: (Int) -> Unit, bottomPadding: Dp) {
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
- onHeightMeasured(it.size.height)
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
- inputSearchType: InputSearchType,
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 scrollDp = with(LocalDensity.current) { scrollState.value.toDp() }
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 = with(LocalDensity.current){ headerRightWidth.toDp() } +
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 = maxOf(maxEndPadding, maxStartPadding, maxTopPadding)
296
- val snapScrollValue = with(LocalDensity.current) { maxPadding.toPx().toInt() }
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
- val bottom = ime.getBottom(density)
348
- return bottom > 0
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(thresholdPx: Int = 200): Pair<ScrollState, Boolean> {
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 target = if (index == 0) offset else SCROLLED_PAST_FIRST_ITEM_THRESHOLD
361
- val shouldLock = target > thresholdPx
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
- when {
365
- shouldLock -> {
366
- if (scrollState.value != thresholdPx) {
367
- scrollState.scrollTo(thresholdPx)
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(thresholdPx: Int = 200): Pair<ScrollState, Boolean> {
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
- .distinctUntilChanged()
398
- .collect { target ->
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
- when {
403
- shouldLock -> {
404
- if (proxy.value != thresholdPx) {
405
- proxy.scrollTo(thresholdPx)
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.map { it.options }.toMutableList()
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)?.apply {
107
- if (index != 0){
108
- onBackHandler = {
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.InputSearchType
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 -> vn.momo.kits.navigation.HEADER_HEIGHT
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
- renderBackButton(
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
- renderHeaderContent(
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 { onHeaderRightWidthMeasured(it.size.width) }){
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 renderBackButton(borderColor: Color, backgroundButton: Color, tintIconColor: Color, onBackHandler: () -> Unit){
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.renderHeaderContent(headerTitle: HeaderTitle, tintIconColor: Color){
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: Color? = null,
167
- val tintColor: Color? = null,
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.fillMaxWidth().zIndex(1f),
21
+ modifier = Modifier.then(modifier).zIndex(1f),
21
22
  text = title,
22
- textAlign = TextAlign.Start,
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
+ }