@momo-kits/native-kits 0.162.2-beta.2-debug → 0.162.2-beta.20-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.
Files changed (100) hide show
  1. package/.claude/settings.local.json +10 -0
  2. package/CLAUDE.md +78 -0
  3. package/compose/build.gradle.kts +12 -8
  4. package/compose/build.gradle.kts.backup +11 -7
  5. package/compose/compose.podspec +1 -1
  6. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +2 -7
  7. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +3 -11
  8. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +6 -24
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +4 -19
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +1 -10
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +2 -2
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +1 -1
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +1 -1
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +6 -17
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +1 -1
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +6 -101
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +269 -0
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +5 -19
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +8 -33
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +1 -1
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +22 -25
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +27 -29
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +3 -26
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +30 -31
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +3 -13
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -5
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +3 -11
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +3 -18
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +11 -11
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +3 -11
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +69 -26
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +1 -5
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +84 -59
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +13 -71
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +10 -51
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +6 -1
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +4 -21
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +1 -1
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +16 -75
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +5 -26
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +1 -1
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +3 -11
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +16 -40
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +3 -9
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +0 -28
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +2 -13
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +4 -10
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +3 -28
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +8 -47
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +4 -25
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +2 -11
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +1 -17
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +1 -6
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +50 -111
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +1 -5
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +1 -5
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +1 -12
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +4 -96
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +5 -7
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +2 -12
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +9 -21
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +2 -2
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +9 -14
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +8 -11
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +1 -1
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +3 -18
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +1 -1
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +0 -5
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +0 -2
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +94 -4
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +0 -2
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +2 -2
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +3 -7
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +9 -15
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +33 -24
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +48 -33
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +67 -61
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +6 -13
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +13 -31
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +1 -1
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +6 -24
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +11 -6
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +2 -1
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +9 -6
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +1 -1
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +3 -13
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +3 -2
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +1 -40
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +0 -2
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +0 -8
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +2 -17
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +1 -1
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +3 -3
  94. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +2 -3
  95. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +0 -2
  96. package/gradle/libs.versions.toml +2 -0
  97. package/gradle.properties +1 -1
  98. package/ios/Typography/Typography.swift +5 -5
  99. package/local.properties +8 -0
  100. package/package.json +1 -1
@@ -9,6 +9,7 @@ import androidx.compose.runtime.staticCompositionLocalOf
9
9
  import androidx.compose.ui.Alignment
10
10
  import androidx.compose.ui.Modifier
11
11
  import androidx.navigation.NavController
12
+ import androidx.navigation.toRoute
12
13
  import kotlinx.coroutines.CoroutineScope
13
14
  import kotlinx.coroutines.Dispatchers
14
15
  import kotlinx.coroutines.SupervisorJob
@@ -21,30 +22,38 @@ import vn.momo.kits.components.PopupNotify
21
22
  import vn.momo.kits.navigation.BottomHeader.*
22
23
  import vn.momo.kits.navigation.component.SnackBar
23
24
  import vn.momo.maxapi.IMaxApi
25
+ import kotlin.time.Duration.Companion.milliseconds
24
26
 
25
27
  class Navigator(
26
28
  private val navController: NavController,
27
- private val maxApi: IMaxApi?
29
+ private val maxApi: IMaxApi?,
30
+ private val registry: DynamicScreenRegistry
28
31
  ) {
29
32
  private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
30
33
 
34
+ private fun currentScreen(): DynamicScreen? {
35
+ val entry = navController.currentBackStackEntry ?: return null
36
+ val id = runCatching { entry.toRoute<DynamicScreenRoute>().id }.getOrNull()
37
+ ?: runCatching { entry.toRoute<DynamicDialogRoute>().id }.getOrNull()
38
+ ?: return null
39
+ return registry.getScreen(id)
40
+ }
41
+
31
42
  fun push(screenName: String, content: @Composable () -> Unit, options: NavigationOptions? = null) {
32
- val route = DynamicScreenRegistry.register(screenName, content, options)
43
+ val route = registry.register(screenName, content, options)
33
44
  navController.navigate(DynamicScreenRoute(route.id))
34
45
  }
35
46
 
36
47
  fun replace(screenName: String, content: @Composable () -> Unit, options: NavigationOptions? = null) {
37
- if (navController.previousBackStackEntry != null){
38
- val latestScreen = DynamicScreenRegistry.getLatestScreen()
39
- latestScreen?.let { DynamicScreenRegistry.unregisterScreen(it.id) }
40
- navController.popBackStack()
41
- push(screenName, content, options)
42
- }
48
+ val replacedScreen = currentScreen()
49
+ replacedScreen?.let { registry.unregisterScreen(it.id) }
50
+ navController.popBackStack()
51
+ push(screenName, content, options)
43
52
  }
44
53
 
45
54
  fun onBackSafe(callBack: (() -> Unit)? = null) {
46
- val latestScreen = DynamicScreenRegistry.getLatestScreen()
47
- val options = latestScreen?.options
55
+ val currentScreen = currentScreen()
56
+ val options = currentScreen?.options
48
57
  if (options?.onBackHandler != null) {
49
58
  options.onBackHandler.invoke()
50
59
  return
@@ -87,31 +96,30 @@ class Navigator(
87
96
  }
88
97
  private suspend fun dismissOverplay(isDelay: Boolean = true) {
89
98
  OverplayComponentRegistry.clear()
90
- if (isDelay) delay(300L)
99
+ if (isDelay) delay(300L.milliseconds)
91
100
  OverplayComponentRegistry.hardClearAfterDismiss()
92
101
  }
93
102
 
94
103
  private suspend fun dismissScreen() {
95
104
  if (navController.previousBackStackEntry != null){
105
+ val dismissing = currentScreen()
96
106
  navController.popBackStack()
97
- delay(300L)
98
- DynamicScreenRegistry.getLatestScreen()?.let { it1 ->
99
- DynamicScreenRegistry.unregisterScreen(it1.id)
100
- }
107
+ delay(300L.milliseconds)
108
+ dismissing?.let { registry.unregisterScreen(it.id) }
101
109
  } else {
102
110
  maxApi?.dismiss { }
103
111
  }
104
112
  }
105
113
 
106
114
  fun present(screenName: String, content: @Composable () -> Unit, options: NavigationOptions? = null) {
107
- val route = DynamicScreenRegistry.register(screenName, content, options)
115
+ val route = registry.register(screenName, content, options)
108
116
  navController.navigate(DynamicDialogRoute(route.id))
109
117
  }
110
118
 
111
119
  fun reset(screenName: String, content: @Composable () -> Unit, options: NavigationOptions? = null) {
112
- DynamicScreenRegistry.unregisterAll()
120
+ registry.unregisterAll()
113
121
 
114
- val route = DynamicScreenRegistry.register(screenName, content, options)
122
+ val route = registry.register(screenName, content, options)
115
123
  navController.navigate(DynamicScreenRoute(route.id)) {
116
124
  popUpTo(0) { inclusive = true }
117
125
  }
@@ -122,7 +130,7 @@ class Navigator(
122
130
  barrierDismissible: Boolean = true,
123
131
  onDismiss: (() -> Unit)? = null
124
132
  ){
125
- val id = DynamicScreenRegistry.getLatestScreen()?.id ?: -1
133
+ val id = currentScreen()?.id ?: -1
126
134
  OverplayComponentRegistry.registerOverplay(id, content, OverplayComponentType.MODAL, false, barrierDismissible, onDismiss)
127
135
  }
128
136
 
@@ -133,12 +141,12 @@ class Navigator(
133
141
  onDismiss: (() -> Unit)? = null,
134
142
  bottomSheetHeader: BottomHeader? = null
135
143
  ){
136
- val id = DynamicScreenRegistry.getLatestScreen()?.id ?: -1
144
+ val id = currentScreen()?.id ?: -1
137
145
  OverplayComponentRegistry.registerOverplay(id, content, OverplayComponentType.BOTTOM_SHEET, isSurface, barrierDismissible, onDismiss, bottomSheetHeader)
138
146
  }
139
147
 
140
148
  fun showSnackBar(snackBar: SnackBar, onDismiss: (() -> Unit)? = null) {
141
- val id = DynamicScreenRegistry.getLatestScreen()?.id ?: -1
149
+ val id = currentScreen()?.id ?: -1
142
150
  scope.launch {
143
151
  OverplayComponentRegistry.registerOverplay(
144
152
  id = id,
@@ -154,7 +162,7 @@ class Navigator(
154
162
  fun hideSnackBar() {
155
163
  scope.launch {
156
164
  OverplayComponentRegistry.clear()
157
- delay(250L)
165
+ delay(250L.milliseconds)
158
166
  OverplayComponentRegistry.hardClearAfterDismiss()
159
167
  }
160
168
  }
@@ -181,12 +189,11 @@ data class DynamicScreen(
181
189
  var options: NavigationOptions? = null
182
190
  )
183
191
 
184
- object DynamicScreenRegistry {
192
+ class DynamicScreenRegistry {
185
193
  private val screens = mutableMapOf<Int, DynamicScreen>()
186
- private var idCounter = 1
187
194
 
188
195
  fun register(screenName: String, content: @Composable () -> Unit, options: NavigationOptions?): DynamicScreenRoute {
189
- val id = idCounter++
196
+ val id = nextGlobalId()
190
197
  screens[id] = DynamicScreen(
191
198
  id = id,
192
199
  name = screenName,
@@ -196,7 +203,7 @@ object DynamicScreenRegistry {
196
203
  return DynamicScreenRoute(id)
197
204
  }
198
205
 
199
- fun nextId(): Int = idCounter++
206
+ fun nextId(): Int = nextGlobalId()
200
207
 
201
208
  fun bind(id: Int, screenName: String, content: @Composable () -> Unit, options: NavigationOptions?) {
202
209
  screens[id] = DynamicScreen(
@@ -205,17 +212,13 @@ object DynamicScreenRegistry {
205
212
  content = content,
206
213
  options = options
207
214
  )
208
- if (id >= idCounter) idCounter = id + 1 // keep counter ahead after process-death restore
215
+ advanceGlobalIdPast(id) // keep counter ahead after process-death restore
209
216
  }
210
217
 
211
218
  fun unregisterScreen(id: Int) {
212
219
  screens.remove(id)
213
220
  }
214
221
 
215
- fun getLatestScreen(): DynamicScreen?{
216
- return screens.entries.lastOrNull()?.value
217
- }
218
-
219
222
  fun unregisterAll(){
220
223
  val ids = screens.keys.toList()
221
224
  for (id in ids) {
@@ -228,8 +231,20 @@ object DynamicScreenRegistry {
228
231
  fun setOptions(id: Int, options: NavigationOptions){
229
232
  screens[id]?.options = options
230
233
  }
234
+
235
+ companion object {
236
+ private var globalIdCounter = 1
237
+
238
+ private fun nextGlobalId(): Int = globalIdCounter++
239
+
240
+ private fun advanceGlobalIdPast(id: Int) {
241
+ if (id >= globalIdCounter) globalIdCounter = id + 1
242
+ }
243
+ }
231
244
  }
232
245
 
246
+ val LocalDynamicScreenRegistry = staticCompositionLocalOf<DynamicScreenRegistry?> { null }
247
+
233
248
 
234
249
  sealed class OverplayComponentParams {
235
250
  class Modal(
@@ -244,7 +259,7 @@ sealed class OverplayComponentParams {
244
259
  val bottomSheetHeader: BottomHeader? = null,
245
260
  ) : OverplayComponentParams()
246
261
 
247
- class SnackBar() : OverplayComponentParams()
262
+ class SnackBar : OverplayComponentParams()
248
263
  }
249
264
 
250
265
  data class OverplayComponent(
@@ -308,7 +323,7 @@ object OverplayComponentRegistry {
308
323
  }
309
324
 
310
325
  @Composable
311
- fun OverlayComponent(){
326
+ internal fun OverlayComponent(){
312
327
  val overplay = currentOverlayComponent.value ?: return
313
328
 
314
329
  when (val params = overplay.params) {
@@ -45,8 +45,8 @@ import androidx.compose.ui.layout.onGloballyPositioned
45
45
  import androidx.compose.ui.platform.LocalDensity
46
46
  import androidx.compose.ui.unit.Dp
47
47
  import androidx.compose.ui.unit.dp
48
- import androidx.compose.ui.unit.min
49
48
  import androidx.compose.ui.zIndex
49
+ import kotlinx.coroutines.flow.Flow
50
50
  import vn.momo.kits.components.InputSearch
51
51
  import vn.momo.kits.const.AppNavigationBar
52
52
  import vn.momo.kits.const.AppStatusBar
@@ -69,8 +69,9 @@ import vn.momo.kits.platform.supportsImePadding
69
69
  import vn.momo.kits.navigation.tracking.ScreenTracker
70
70
  import vn.momo.kits.navigation.tracking.ScreenTrackingState
71
71
  import kotlinx.coroutines.delay
72
- import vn.momo.kits.application.ApplicationContext
72
+ import vn.momo.kits.application.LocalContext
73
73
  import kotlin.time.Clock
74
+ import kotlin.time.Duration.Companion.milliseconds
74
75
  import kotlin.time.ExperimentalTime
75
76
 
76
77
  internal val LocalFooterHeightPx = staticCompositionLocalOf { mutableIntStateOf(0) }
@@ -88,17 +89,20 @@ internal fun StackScreen(
88
89
  ) {
89
90
  val navigator = LocalNavigator.current
90
91
  val maxApi = LocalMaxApi.current
91
- val context = ApplicationContext.current
92
+ val context = LocalContext.current
92
93
  val statusBar = AppStatusBar.current
93
94
  val density = LocalDensity.current
94
- val navigation = remember { Navigation(id = id, bottomTabIndex = bottomTabIndex, initOptions = navigationOptions) }
95
+ val screenRegistry = LocalDynamicScreenRegistry.current
96
+ val navigation = remember { Navigation(id = id, bottomTabIndex = bottomTabIndex, initOptions = navigationOptions, registry = screenRegistry) }
95
97
 
96
98
  val options by navigation.currentOptions
99
+ val isBottomTabChild = bottomTabIndex != -1
100
+ val isBottomTabRoot = navigation.isBottomTabRoot
97
101
 
98
102
  // Auto tracking state
99
103
  val trackingState = remember {
100
104
  ScreenTrackingState().apply {
101
- startTime = Clock.System.now().toEpochMilliseconds()
105
+ time = Clock.System.now().toEpochMilliseconds()
102
106
  }
103
107
  }
104
108
 
@@ -119,8 +123,8 @@ internal fun StackScreen(
119
123
  )
120
124
 
121
125
  // Track screen displayed after 2 seconds (debounce)
122
- delay(2000)
123
- val loadTime = Clock.System.now().toEpochMilliseconds() - trackingState.startTime
126
+ delay(2000.milliseconds)
127
+ val loadTime = Clock.System.now().toEpochMilliseconds() - trackingState.time
124
128
  ScreenTracker.trackScreenDisplayed(
125
129
  maxApi = maxApi,
126
130
  context = context,
@@ -130,7 +134,7 @@ internal fun StackScreen(
130
134
  )
131
135
 
132
136
  // Track screen interacted after displayed
133
- val interactionTime = Clock.System.now().toEpochMilliseconds() - trackingState.startTime
137
+ val interactionTime = Clock.System.now().toEpochMilliseconds() - trackingState.time
134
138
  ScreenTracker.trackScreenInteracted(
135
139
  maxApi = maxApi,
136
140
  context = context,
@@ -191,7 +195,7 @@ internal fun StackScreen(
191
195
  }
192
196
 
193
197
  Column(Modifier.zIndex(2f).fillMaxSize()) {
194
- MainContent(content = content)
198
+ MainContent(content = content, isBottomTab = isBottomTabRoot)
195
199
  FooterContent()
196
200
  }
197
201
 
@@ -199,21 +203,23 @@ internal fun StackScreen(
199
203
  FloatingContent()
200
204
  }
201
205
 
202
- OverplayView(bottomTabIndex = bottomTabIndex, id = id)
206
+ OverplayView(isBottomTabChild = isBottomTabChild, id = id)
203
207
  }
204
208
  }
205
209
  }
206
210
 
211
+ @Suppress("FrequentlyChangingValue")
207
212
  @Composable
208
- fun FloatingContent() {
213
+ internal fun FloatingContent() {
209
214
  val options = LocalOptions.current
210
215
  val density = LocalDensity.current
211
216
  val footerHeightPx = LocalFooterHeightPx.current
212
217
  val scrollState = LocalScrollState.current
218
+ val navigationBar = AppNavigationBar.current
213
219
 
214
220
  val fabProps = options.floatingButtonProps ?: return
215
221
  val bottomPadding = fabProps.bottom
216
- ?: (Spacing.M + if (options.footerComponent != null) with(density) { footerHeightPx.intValue.toDp() } else 0.dp)
222
+ ?: (Spacing.M + if (options.footerComponent != null) with(density) { footerHeightPx.intValue.toDp() } else navigationBar)
217
223
 
218
224
  FloatingButton(
219
225
  scrollPosition = fabProps.scrollState?.value ?: scrollState.value,
@@ -229,11 +235,12 @@ fun FloatingContent() {
229
235
  }
230
236
 
231
237
  @Composable
232
- fun ColumnScope.MainContent(content: @Composable () -> Unit) {
238
+ internal fun ColumnScope.MainContent(content: @Composable () -> Unit, isBottomTab: Boolean) {
233
239
  val options = LocalOptions.current
234
240
  val inputSearchType = getInputSearchType(options)
235
241
  val density = LocalDensity.current
236
242
  val scrollState = LocalScrollState.current
243
+ val navigationBar = AppNavigationBar.current
237
244
 
238
245
  Spacer(
239
246
  Modifier.height(
@@ -257,6 +264,7 @@ fun ColumnScope.MainContent(content: @Composable () -> Unit) {
257
264
  .conditional(options.scrollData.scrollable && options.scrollData.scrollState is ScrollState) {
258
265
  verticalScroll(scrollState)
259
266
  }
267
+ .padding(bottom = if (options.footerComponent != null || isBottomTab) 0.dp else navigationBar)
260
268
  ) {
261
269
  ScreenContent(content = content)
262
270
  }
@@ -264,7 +272,7 @@ fun ColumnScope.MainContent(content: @Composable () -> Unit) {
264
272
  }
265
273
 
266
274
  @Composable
267
- fun ScreenContent(content: @Composable () -> Unit) {
275
+ internal fun ScreenContent(content: @Composable () -> Unit) {
268
276
  val scrollState = LocalScrollState.current
269
277
  val options = LocalOptions.current
270
278
 
@@ -284,7 +292,7 @@ fun ScreenContent(content: @Composable () -> Unit) {
284
292
  }
285
293
 
286
294
  @Composable
287
- fun FooterContent() {
295
+ internal fun FooterContent() {
288
296
  val options = LocalOptions.current
289
297
  if (options.footerComponent != null) {
290
298
  val keyboardSize = keyboardSizeState()
@@ -294,18 +302,18 @@ fun FooterContent() {
294
302
  }
295
303
 
296
304
  @Composable
297
- fun keyboardSizeState(): State<Dp> {
305
+ internal fun keyboardSizeState(): State<Dp> {
298
306
  val bottom = WindowInsets.ime.asPaddingValues()
299
307
  return rememberUpdatedState(bottom.calculateBottomPadding())
300
308
  }
301
309
 
302
310
  @Composable
303
- fun OverplayView(bottomTabIndex: Int, id: Int) {
311
+ internal fun OverplayView(isBottomTabChild: Boolean, id: Int) {
304
312
  val overplayType = OverplayComponentRegistry.getOverplayType()
305
313
 
306
314
  if (overplayType != null) {
307
315
  Box(Modifier.zIndex(if (overplayType == OverplayComponentType.SNACK_BAR) 3f else 7f).fillMaxSize()) {
308
- if (bottomTabIndex != -1) return@Box
316
+ if (isBottomTabChild) return@Box
309
317
  if (OverplayComponentRegistry.currentRootId() != id) return@Box
310
318
  OverplayComponentRegistry.OverlayComponent()
311
319
  }
@@ -355,8 +363,9 @@ data class InputSearchLayoutParams(
355
363
  val endPadding: Dp
356
364
  )
357
365
 
366
+ @Suppress("FrequentlyChangingValue")
358
367
  @Composable
359
- fun SearchAnimated(
368
+ internal fun SearchAnimated(
360
369
  isScrollInProgress: Boolean
361
370
  ) {
362
371
  val scrollState = LocalScrollState.current
@@ -469,64 +478,61 @@ private fun quantize(value: Int, stepPx: Int): Int {
469
478
  }
470
479
 
471
480
  @Composable
472
- fun LazyListState.proxyScrollState(
481
+ internal fun LazyListState.proxyScrollState(
473
482
  thresholdPx: Int = 200,
474
483
  stepPx: Int = 4
475
- ): Pair<ScrollState, Boolean> {
476
- val scrollState = rememberScrollState()
477
- val locked = remember { mutableStateOf(false) }
478
-
479
- LaunchedEffect(this, scrollState, thresholdPx, stepPx) {
480
- snapshotFlow { firstVisibleItemIndex to firstVisibleItemScrollOffset }
481
- .collect { (index, offset) ->
482
- val rawTarget = if (index == 0) offset else SCROLLED_PAST_FIRST_ITEM_THRESHOLD
483
- val shouldLock = rawTarget > thresholdPx
484
- if (locked.value != shouldLock) locked.value = shouldLock
485
-
486
- val desired = if (shouldLock) thresholdPx else quantize(rawTarget, stepPx)
487
-
488
- if (scrollState.value != desired) {
489
- scrollState.scrollTo(desired.coerceAtLeast(0))
490
- }
491
- }
492
- }
493
-
494
- val inProgress = remember { mutableStateOf(false) }
495
- LaunchedEffect(this, thresholdPx) {
496
- snapshotFlow { isScrollInProgress }
497
- .collect { progressing ->
498
- inProgress.value = if (locked.value) false else progressing
499
- }
500
- }
501
-
502
- return scrollState to inProgress.value
503
- }
484
+ ): Pair<ScrollState, Boolean> = proxyScroll(
485
+ source = this,
486
+ thresholdPx = thresholdPx,
487
+ stepPx = stepPx,
488
+ isScrollInProgress = { isScrollInProgress },
489
+ rawTargetFlow = {
490
+ snapshotFlow {
491
+ if (firstVisibleItemIndex == 0) firstVisibleItemScrollOffset
492
+ else SCROLLED_PAST_FIRST_ITEM_THRESHOLD
493
+ }
494
+ },
495
+ )
504
496
 
505
497
  @Composable
506
- fun ScrollState.proxyLimitedScrollState(
498
+ internal fun ScrollState.proxyLimitedScrollState(
507
499
  thresholdPx: Int = 200,
508
500
  stepPx: Int = 4
501
+ ): Pair<ScrollState, Boolean> = proxyScroll(
502
+ source = this,
503
+ thresholdPx = thresholdPx,
504
+ stepPx = stepPx,
505
+ isScrollInProgress = { isScrollInProgress },
506
+ rawTargetFlow = { snapshotFlow { value } },
507
+ )
508
+
509
+ @Composable
510
+ private fun proxyScroll(
511
+ source: Any,
512
+ thresholdPx: Int,
513
+ stepPx: Int,
514
+ isScrollInProgress: () -> Boolean,
515
+ rawTargetFlow: () -> Flow<Int>,
509
516
  ): Pair<ScrollState, Boolean> {
510
517
  val proxy = rememberScrollState()
511
518
  val locked = remember { mutableStateOf(false) }
512
519
 
513
- LaunchedEffect(this, proxy, thresholdPx, stepPx) {
514
- snapshotFlow { value }
515
- .collect { rawTarget ->
516
- val shouldLock = rawTarget > thresholdPx
517
- if (locked.value != shouldLock) locked.value = shouldLock
520
+ LaunchedEffect(source, proxy, thresholdPx, stepPx) {
521
+ rawTargetFlow().collect { rawTarget ->
522
+ val shouldLock = rawTarget > thresholdPx
523
+ if (locked.value != shouldLock) locked.value = shouldLock
518
524
 
519
- val desired = if (shouldLock) thresholdPx else quantize(rawTarget, stepPx)
525
+ val desired = if (shouldLock) thresholdPx else quantize(rawTarget, stepPx)
520
526
 
521
- if (proxy.value != desired) {
522
- proxy.scrollTo(desired.coerceAtLeast(0))
523
- }
527
+ if (proxy.value != desired) {
528
+ proxy.scrollTo(desired.coerceAtLeast(0))
524
529
  }
530
+ }
525
531
  }
526
532
 
527
533
  val inProgress = remember { mutableStateOf(false) }
528
- LaunchedEffect(this, thresholdPx) {
529
- snapshotFlow { isScrollInProgress }
534
+ LaunchedEffect(source, thresholdPx) {
535
+ snapshotFlow { isScrollInProgress() }
530
536
  .collect { progressing ->
531
537
  inProgress.value = if (locked.value) false else progressing
532
538
  }
@@ -6,13 +6,7 @@ import androidx.compose.animation.fadeOut
6
6
  import androidx.compose.animation.scaleIn
7
7
  import androidx.compose.animation.scaleOut
8
8
  import androidx.compose.foundation.background
9
- import androidx.compose.foundation.layout.Box
10
- import androidx.compose.foundation.layout.Column
11
- import androidx.compose.foundation.layout.Spacer
12
- import androidx.compose.foundation.layout.fillMaxSize
13
- import androidx.compose.foundation.layout.fillMaxWidth
14
- import androidx.compose.foundation.layout.height
15
- import androidx.compose.foundation.layout.padding
9
+ import androidx.compose.foundation.layout.*
16
10
  import androidx.compose.runtime.Composable
17
11
  import androidx.compose.runtime.LaunchedEffect
18
12
  import androidx.compose.ui.Alignment
@@ -29,15 +23,14 @@ import vn.momo.kits.navigation.LocalNavigator
29
23
  import vn.momo.kits.navigation.NavigationOptions
30
24
  import vn.momo.kits.navigation.StackScreen
31
25
  import vn.momo.kits.navigation.component.HeaderType
32
- import vn.momo.kits.platform.getScreenHeight
33
26
 
34
27
  private var bottomTabOptionItems : MutableList<NavigationOptions?> = mutableListOf()
35
- fun setBottomTabOption(index: Int, options: NavigationOptions){
28
+ internal fun setBottomTabOption(index: Int, options: NavigationOptions){
36
29
  if (index in bottomTabOptionItems.indices) {
37
30
  bottomTabOptionItems[index] = options
38
31
  }
39
32
  }
40
- fun getBottomTabOption(index: Int): NavigationOptions? {
33
+ internal fun getBottomTabOption(index: Int): NavigationOptions? {
41
34
  return if (index in bottomTabOptionItems.indices) {
42
35
  bottomTabOptionItems[index]
43
36
  } else null
@@ -58,15 +51,15 @@ fun BottomTab(
58
51
 
59
52
 
60
53
  LaunchedEffect(Unit){
54
+ navigation.markAsBottomTabRoot()
61
55
  navigation.setOptions(
62
56
  headerType = HeaderType.None
63
57
  )
64
58
  }
65
59
 
66
- Box(modifier = Modifier.fillMaxWidth().height(getScreenHeight()), contentAlignment = Alignment.BottomCenter) {
60
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.BottomCenter) {
67
61
  Box(modifier = Modifier
68
62
  .fillMaxSize()
69
- .padding(bottom = BOTTOM_TAB_BAR_HEIGHT.dp + AppNavigationBar.current)
70
63
  ) {
71
64
  NavHost(
72
65
  navController = navController,
@@ -133,7 +126,7 @@ fun BottomTab(
133
126
  onTabSelected = {
134
127
  val currentRoute = navController.currentBackStackEntry?.destination?.route
135
128
  val targetRoute = "option$it"
136
- if (currentRoute != targetRoute){
129
+ if (currentRoute != targetRoute) {
137
130
  navController.navigate(targetRoute)
138
131
  }
139
132
  }
@@ -5,19 +5,7 @@ import androidx.compose.animation.core.animateDpAsState
5
5
  import androidx.compose.animation.core.tween
6
6
  import androidx.compose.foundation.background
7
7
  import androidx.compose.foundation.border
8
- import androidx.compose.foundation.layout.Arrangement
9
- import androidx.compose.foundation.layout.Box
10
- import androidx.compose.foundation.layout.Column
11
- import androidx.compose.foundation.layout.Row
12
- import androidx.compose.foundation.layout.RowScope
13
- import androidx.compose.foundation.layout.Spacer
14
- import androidx.compose.foundation.layout.fillMaxSize
15
- import androidx.compose.foundation.layout.fillMaxWidth
16
- import androidx.compose.foundation.layout.height
17
- import androidx.compose.foundation.layout.offset
18
- import androidx.compose.foundation.layout.padding
19
- import androidx.compose.foundation.layout.size
20
- import androidx.compose.foundation.layout.width
8
+ import androidx.compose.foundation.layout.*
21
9
  import androidx.compose.foundation.shape.CircleShape
22
10
  import androidx.compose.foundation.shape.RoundedCornerShape
23
11
  import androidx.compose.runtime.Composable
@@ -32,26 +20,20 @@ import androidx.compose.ui.text.style.TextOverflow
32
20
  import androidx.compose.ui.unit.dp
33
21
  import androidx.navigation.NavController
34
22
  import androidx.navigation.compose.currentBackStackEntryAsState
35
- import vn.momo.kits.components.Badge
36
- import vn.momo.kits.components.Icon
37
- import vn.momo.kits.components.Text
38
- import vn.momo.kits.const.AppTheme
39
- import vn.momo.kits.const.Colors
40
- import vn.momo.kits.const.Radius
41
- import vn.momo.kits.const.Spacing
42
23
  import vn.momo.kits.application.IsShowBaseLineDebug
43
- import vn.momo.kits.components.BadgeDot
44
- import vn.momo.kits.components.DotSize
45
- import vn.momo.kits.const.Typography
24
+ import vn.momo.kits.components.*
25
+ import vn.momo.kits.const.*
26
+ import vn.momo.kits.modifier.InternalApi
46
27
  import vn.momo.kits.modifier.conditional
47
28
  import vn.momo.kits.modifier.noFeedbackClickable
48
29
  import vn.momo.kits.platform.getScreenDimensions
49
30
 
50
- val floatingButtonWidth = 75.dp
31
+ internal val floatingButtonWidth = 75.dp
32
+ @InternalApi
51
33
  const val BOTTOM_TAB_BAR_HEIGHT = 64
52
34
 
53
35
  @Composable
54
- fun BottomTabBar(
36
+ internal fun BottomTabBar(
55
37
  items: List<BottomTabItem>,
56
38
  floatingButton: BottomTabFloatingButton? = null,
57
39
  navController: NavController,
@@ -91,11 +73,11 @@ fun BottomTabBar(
91
73
  horizontalArrangement = Arrangement.SpaceAround,
92
74
  ) {
93
75
  if (floatingButton == null) {
94
- renderTabBarItem(items, 0, items.size, selectedIndex, onTabSelected)
76
+ RenderTabBarItem(items, 0, items.size, selectedIndex, onTabSelected)
95
77
  } else {
96
- renderTabBarItem(items, 0, mid, selectedIndex, onTabSelected)
78
+ RenderTabBarItem(items, 0, mid, selectedIndex, onTabSelected)
97
79
  Spacer(modifier = Modifier.width(floatingButtonWidth).padding(horizontal = Spacing.XXS))
98
- renderTabBarItem(items, mid, items.size, selectedIndex, onTabSelected)
80
+ RenderTabBarItem(items, mid, items.size, selectedIndex, onTabSelected)
99
81
  }
100
82
  }
101
83
 
@@ -124,7 +106,7 @@ fun BottomTabBar(
124
106
  }
125
107
 
126
108
  @Composable
127
- fun RowScope.renderTabBarItem(
109
+ internal fun RowScope.RenderTabBarItem(
128
110
  items: List<BottomTabItem>,
129
111
  fromIndex: Int,
130
112
  toIndex: Int,
@@ -143,7 +125,7 @@ fun RowScope.renderTabBarItem(
143
125
  }
144
126
 
145
127
  @Composable
146
- fun TabBarItem(item: BottomTabItem, selected: Boolean, onClick: () -> Unit) {
128
+ internal fun TabBarItem(item: BottomTabItem, selected: Boolean, onClick: () -> Unit) {
147
129
  fun isNumber(label: String): Boolean {
148
130
  val numberRegex = "^\\d+$".toRegex()
149
131
  return numberRegex.matches(label)
@@ -203,7 +185,7 @@ fun TabBarItem(item: BottomTabItem, selected: Boolean, onClick: () -> Unit) {
203
185
  }
204
186
 
205
187
  @Composable
206
- fun FloatingButton(data: BottomTabFloatingButton) {
188
+ internal fun FloatingButton(data: BottomTabFloatingButton) {
207
189
  Column(
208
190
  modifier = Modifier
209
191
  .width(floatingButtonWidth)
@@ -17,7 +17,7 @@ import androidx.compose.ui.zIndex
17
17
  import vn.momo.kits.const.AppTheme
18
18
 
19
19
  @Composable
20
- fun CurvedContainer(
20
+ internal fun CurvedContainer(
21
21
  circleSize: Dp = 48.dp,
22
22
  tabBarItemIconSize: Dp = 28.dp,
23
23
  circleOverSize: Dp = 2.dp,