@momo-kits/native-kits 0.163.1-beta.1-debug → 0.163.1-beta.2-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 (113) hide show
  1. package/compose/build.gradle.kts +11 -8
  2. package/compose/build.gradle.kts.backup +10 -7
  3. package/compose/compose.podspec +1 -1
  4. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +2 -7
  5. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +11 -12
  6. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +6 -24
  7. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +4 -19
  8. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +1 -10
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +2 -2
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +1 -1
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +1 -1
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +6 -17
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +1 -1
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +6 -101
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +3 -16
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +8 -33
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +1 -1
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +22 -25
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +27 -29
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +3 -26
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +27 -30
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +3 -13
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -5
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +3 -11
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +3 -18
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +11 -11
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +3 -11
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +1 -5
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +84 -59
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +13 -71
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +10 -51
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +6 -1
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +4 -21
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +1 -1
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +15 -76
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +5 -26
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +1 -1
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +3 -11
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +16 -40
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +3 -9
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +0 -28
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +2 -13
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +4 -10
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +3 -28
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +8 -47
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +4 -25
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +2 -11
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +1 -17
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +1 -6
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +50 -111
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +1 -5
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +7 -8
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +1 -12
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +4 -96
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +5 -7
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +2 -12
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +9 -21
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +2 -2
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +9 -14
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +8 -11
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +1 -1
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +24 -40
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +1 -1
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +0 -5
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +0 -2
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +94 -4
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +0 -2
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +2 -2
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +3 -7
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +5 -15
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +50 -10
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +48 -33
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +58 -57
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +3 -9
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +12 -32
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +1 -1
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +6 -24
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +11 -7
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +2 -1
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +9 -6
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +1 -1
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +3 -13
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +3 -2
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +1 -40
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +0 -2
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +0 -8
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +2 -17
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +1 -1
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +3 -3
  90. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +2 -3
  91. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +0 -2
  92. package/gradle.properties +1 -1
  93. package/ios/Application/ApplicationEnvironment.swift +1 -0
  94. package/ios/Application/Components.swift +1 -0
  95. package/ios/Application/FloatingButton.swift +1 -0
  96. package/ios/Application/FontScaleStore.swift +59 -0
  97. package/ios/Badge/Badge.swift +1 -0
  98. package/ios/Badge/BadgeRibbon.swift +2 -0
  99. package/ios/Button/Button.swift +1 -1
  100. package/ios/Checkbox/Checkbox.swift +1 -0
  101. package/ios/Information/Information.swift +151 -0
  102. package/ios/Input/Input.swift +1 -0
  103. package/ios/Input/InputPhoneNumber.swift +1 -0
  104. package/ios/Input/InputSearch.swift +3 -0
  105. package/ios/Input/InputTextArea.swift +1 -0
  106. package/ios/OTPKeyboard/KeyboardButton.swift +1 -1
  107. package/ios/Popup/PopupDisplay.swift +6 -0
  108. package/ios/Popup/PopupInput.swift +2 -0
  109. package/ios/Template/TrustBanner/TrustBanner.swift +2 -0
  110. package/ios/Typography/Text.swift +13 -7
  111. package/ios/Typography/Typography.swift +22 -8
  112. package/package.json +1 -1
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +0 -17
@@ -29,33 +29,45 @@ fun NavigationContainer(
29
29
  localize: Localize? = null,
30
30
  ){
31
31
  val navController = rememberNavController()
32
- val navigator = remember { Navigator(navController = navController, maxApi = maxApi) }
32
+ val registry = remember { DynamicScreenRegistry() }
33
+ val navigator = remember { Navigator(navController = navController, maxApi = maxApi, registry = registry) }
33
34
  val statusBarHeight = statusBarHeight ?: getAppStatusBarHeight()
34
35
  val navigationBarHeight = getNavigationBarHeight()
35
36
 
36
- val parentContext = ApplicationContext.current
37
+ val parentContext = LocalContext.current
37
38
  val mergedContext = MiniAppContext.merge(parentContext, miniappContext)
38
39
 
40
+ var observerFontScaleConfig by remember { mutableStateOf(mergedContext?.fontScaleConfig) }
41
+ LaunchedEffect(maxApi) {
42
+ val api = maxApi ?: return@LaunchedEffect
43
+ runCatching {
44
+ api.observer(FONT_SCALE_OBSERVER_KEY).collect { data ->
45
+ observerFontScaleConfig = parseFontScaleConfig(data)
46
+ }
47
+ }
48
+ }
49
+
39
50
  val theme = remember { mutableStateOf(initialTheme) }
40
51
 
41
52
  val fallbackLocalize = remember { Localize() }
42
53
  val resolvedLocalize = localize ?: fallbackLocalize
43
54
 
44
- val screenId = rememberSaveable { DynamicScreenRegistry.nextId() }
45
- DynamicScreenRegistry.bind(screenId, initialScreenName, initialScreen, options)
55
+ val screenId = rememberSaveable { registry.nextId() }
56
+ registry.bind(screenId, initialScreenName, initialScreen, options)
46
57
  val startDestination = remember(screenId) { DynamicScreenRoute(screenId) }
47
58
 
48
59
  ProvideNavigationEventDispatcherOwner {
49
60
  CompositionLocalProvider(
50
61
  LocalNavigator provides navigator,
62
+ LocalDynamicScreenRegistry provides registry,
51
63
  LocalMaxApi provides maxApi,
52
64
  AppTheme provides theme.value,
53
65
  AppStatusBar provides statusBarHeight,
54
66
  AppNavigationBar provides navigationBarHeight,
55
- ApplicationContext provides mergedContext,
56
- AppLanguage provides resolvedLocalize.currentLanguage,
67
+ LocalContext provides mergedContext,
57
68
  LocalLocalize provides resolvedLocalize,
58
- ) {
69
+ LocalFontScaleConfig provides (observerFontScaleConfig ?: LocalFontScaleConfig.current),
70
+ ) {
59
71
  LaunchedEffect(Unit) {
60
72
  setNavigator?.invoke(navigator)
61
73
  }
@@ -78,7 +90,7 @@ fun NavigationContainer(
78
90
  }
79
91
  ) { backStackEntry ->
80
92
  val route = backStackEntry.toRoute<DynamicScreenRoute>()
81
- val screen = DynamicScreenRegistry.getScreen(route.id)
93
+ val screen = registry.getScreen(route.id)
82
94
 
83
95
  if (screen != null) {
84
96
  StackScreen(
@@ -107,7 +119,7 @@ fun NavigationContainer(
107
119
  }
108
120
  ) { backStackEntry ->
109
121
  val route = backStackEntry.toRoute<DynamicDialogRoute>()
110
- val screen = DynamicScreenRegistry.getScreen(route.id)
122
+ val screen = registry.getScreen(route.id)
111
123
 
112
124
  if (screen != null) {
113
125
  StackScreen(
@@ -125,7 +137,7 @@ fun NavigationContainer(
125
137
  DisposableEffect(Unit) {
126
138
  onDispose {
127
139
  navigator.dispose()
128
- DynamicScreenRegistry.unregisterScreen(screenId)
140
+ registry.unregisterScreen(screenId)
129
141
  }
130
142
  }
131
143
  }
@@ -133,3 +145,31 @@ fun NavigationContainer(
133
145
  val LocalMaxApi = staticCompositionLocalOf<IMaxApi?> {
134
146
  error("No MaxApi provided")
135
147
  }
148
+
149
+ private const val FONT_SCALE_OBSERVER_KEY = "font_scale_config"
150
+
151
+ private fun parseFontScaleConfig(raw: Map<String, Any?>?): FontScaleConfig? {
152
+ if (raw.isNullOrEmpty()) return null
153
+ @Suppress("UNCHECKED_CAST")
154
+ val data = (raw["response"] as? Map<String, Any?>)
155
+ ?: (raw["data"] as? Map<String, Any?>)
156
+ ?: raw
157
+ if (!data.containsKey("useOSFontScale") && !data.containsKey("userScaleRate")) return null
158
+ val useOS = when (val v = data["useOSFontScale"]) {
159
+ is Boolean -> v
160
+ is String -> v.equals("true", ignoreCase = true)
161
+ is Number -> v.toInt() != 0
162
+ else -> true
163
+ }
164
+ val rate = when (val v = data["userScaleRate"]) {
165
+ is Number -> v.toFloat()
166
+ is String -> v.toFloatOrNull()
167
+ else -> null
168
+ }
169
+ val osScale = when (val v = data["osFontScale"]) {
170
+ is Number -> v.toFloat()
171
+ is String -> v.toFloatOrNull()
172
+ else -> null
173
+ }
174
+ return FontScaleConfig(useOSFontScale = useOS, userScaleRate = rate, osFontScale = osScale)
175
+ }
@@ -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,18 @@ 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
97
99
 
98
100
  // Auto tracking state
99
101
  val trackingState = remember {
100
102
  ScreenTrackingState().apply {
101
- startTime = Clock.System.now().toEpochMilliseconds()
103
+ time = Clock.System.now().toEpochMilliseconds()
102
104
  }
103
105
  }
104
106
 
@@ -119,8 +121,8 @@ internal fun StackScreen(
119
121
  )
120
122
 
121
123
  // Track screen displayed after 2 seconds (debounce)
122
- delay(2000)
123
- val loadTime = Clock.System.now().toEpochMilliseconds() - trackingState.startTime
124
+ delay(2000.milliseconds)
125
+ val loadTime = Clock.System.now().toEpochMilliseconds() - trackingState.time
124
126
  ScreenTracker.trackScreenDisplayed(
125
127
  maxApi = maxApi,
126
128
  context = context,
@@ -130,7 +132,7 @@ internal fun StackScreen(
130
132
  )
131
133
 
132
134
  // Track screen interacted after displayed
133
- val interactionTime = Clock.System.now().toEpochMilliseconds() - trackingState.startTime
135
+ val interactionTime = Clock.System.now().toEpochMilliseconds() - trackingState.time
134
136
  ScreenTracker.trackScreenInteracted(
135
137
  maxApi = maxApi,
136
138
  context = context,
@@ -204,8 +206,9 @@ internal fun StackScreen(
204
206
  }
205
207
  }
206
208
 
209
+ @Suppress("FrequentlyChangingValue")
207
210
  @Composable
208
- fun FloatingContent() {
211
+ internal fun FloatingContent() {
209
212
  val options = LocalOptions.current
210
213
  val density = LocalDensity.current
211
214
  val footerHeightPx = LocalFooterHeightPx.current
@@ -229,7 +232,7 @@ fun FloatingContent() {
229
232
  }
230
233
 
231
234
  @Composable
232
- fun ColumnScope.MainContent(content: @Composable () -> Unit) {
235
+ internal fun ColumnScope.MainContent(content: @Composable () -> Unit) {
233
236
  val options = LocalOptions.current
234
237
  val inputSearchType = getInputSearchType(options)
235
238
  val density = LocalDensity.current
@@ -264,7 +267,7 @@ fun ColumnScope.MainContent(content: @Composable () -> Unit) {
264
267
  }
265
268
 
266
269
  @Composable
267
- fun ScreenContent(content: @Composable () -> Unit) {
270
+ internal fun ScreenContent(content: @Composable () -> Unit) {
268
271
  val scrollState = LocalScrollState.current
269
272
  val options = LocalOptions.current
270
273
 
@@ -284,7 +287,7 @@ fun ScreenContent(content: @Composable () -> Unit) {
284
287
  }
285
288
 
286
289
  @Composable
287
- fun FooterContent() {
290
+ internal fun FooterContent() {
288
291
  val options = LocalOptions.current
289
292
  if (options.footerComponent != null) {
290
293
  val keyboardSize = keyboardSizeState()
@@ -294,13 +297,13 @@ fun FooterContent() {
294
297
  }
295
298
 
296
299
  @Composable
297
- fun keyboardSizeState(): State<Dp> {
300
+ internal fun keyboardSizeState(): State<Dp> {
298
301
  val bottom = WindowInsets.ime.asPaddingValues()
299
302
  return rememberUpdatedState(bottom.calculateBottomPadding())
300
303
  }
301
304
 
302
305
  @Composable
303
- fun OverplayView(bottomTabIndex: Int, id: Int) {
306
+ internal fun OverplayView(bottomTabIndex: Int, id: Int) {
304
307
  val overplayType = OverplayComponentRegistry.getOverplayType()
305
308
 
306
309
  if (overplayType != null) {
@@ -355,8 +358,9 @@ data class InputSearchLayoutParams(
355
358
  val endPadding: Dp
356
359
  )
357
360
 
361
+ @Suppress("FrequentlyChangingValue")
358
362
  @Composable
359
- fun SearchAnimated(
363
+ internal fun SearchAnimated(
360
364
  isScrollInProgress: Boolean
361
365
  ) {
362
366
  val scrollState = LocalScrollState.current
@@ -469,64 +473,61 @@ private fun quantize(value: Int, stepPx: Int): Int {
469
473
  }
470
474
 
471
475
  @Composable
472
- fun LazyListState.proxyScrollState(
476
+ internal fun LazyListState.proxyScrollState(
473
477
  thresholdPx: Int = 200,
474
478
  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
- }
479
+ ): Pair<ScrollState, Boolean> = proxyScroll(
480
+ source = this,
481
+ thresholdPx = thresholdPx,
482
+ stepPx = stepPx,
483
+ isScrollInProgress = { isScrollInProgress },
484
+ rawTargetFlow = {
485
+ snapshotFlow {
486
+ if (firstVisibleItemIndex == 0) firstVisibleItemScrollOffset
487
+ else SCROLLED_PAST_FIRST_ITEM_THRESHOLD
488
+ }
489
+ },
490
+ )
504
491
 
505
492
  @Composable
506
- fun ScrollState.proxyLimitedScrollState(
493
+ internal fun ScrollState.proxyLimitedScrollState(
507
494
  thresholdPx: Int = 200,
508
495
  stepPx: Int = 4
496
+ ): Pair<ScrollState, Boolean> = proxyScroll(
497
+ source = this,
498
+ thresholdPx = thresholdPx,
499
+ stepPx = stepPx,
500
+ isScrollInProgress = { isScrollInProgress },
501
+ rawTargetFlow = { snapshotFlow { value } },
502
+ )
503
+
504
+ @Composable
505
+ private fun proxyScroll(
506
+ source: Any,
507
+ thresholdPx: Int,
508
+ stepPx: Int,
509
+ isScrollInProgress: () -> Boolean,
510
+ rawTargetFlow: () -> Flow<Int>,
509
511
  ): Pair<ScrollState, Boolean> {
510
512
  val proxy = rememberScrollState()
511
513
  val locked = remember { mutableStateOf(false) }
512
514
 
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
515
+ LaunchedEffect(source, proxy, thresholdPx, stepPx) {
516
+ rawTargetFlow().collect { rawTarget ->
517
+ val shouldLock = rawTarget > thresholdPx
518
+ if (locked.value != shouldLock) locked.value = shouldLock
518
519
 
519
- val desired = if (shouldLock) thresholdPx else quantize(rawTarget, stepPx)
520
+ val desired = if (shouldLock) thresholdPx else quantize(rawTarget, stepPx)
520
521
 
521
- if (proxy.value != desired) {
522
- proxy.scrollTo(desired.coerceAtLeast(0))
523
- }
522
+ if (proxy.value != desired) {
523
+ proxy.scrollTo(desired.coerceAtLeast(0))
524
524
  }
525
+ }
525
526
  }
526
527
 
527
528
  val inProgress = remember { mutableStateOf(false) }
528
- LaunchedEffect(this, thresholdPx) {
529
- snapshotFlow { isScrollInProgress }
529
+ LaunchedEffect(source, thresholdPx) {
530
+ snapshotFlow { isScrollInProgress() }
530
531
  .collect { progressing ->
531
532
  inProgress.value = if (locked.value) false else progressing
532
533
  }
@@ -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
@@ -32,12 +26,12 @@ import vn.momo.kits.navigation.component.HeaderType
32
26
  import vn.momo.kits.platform.getScreenHeight
33
27
 
34
28
  private var bottomTabOptionItems : MutableList<NavigationOptions?> = mutableListOf()
35
- fun setBottomTabOption(index: Int, options: NavigationOptions){
29
+ internal fun setBottomTabOption(index: Int, options: NavigationOptions){
36
30
  if (index in bottomTabOptionItems.indices) {
37
31
  bottomTabOptionItems[index] = options
38
32
  }
39
33
  }
40
- fun getBottomTabOption(index: Int): NavigationOptions? {
34
+ internal fun getBottomTabOption(index: Int): NavigationOptions? {
41
35
  return if (index in bottomTabOptionItems.indices) {
42
36
  bottomTabOptionItems[index]
43
37
  } else null
@@ -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,18 @@ 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.*
46
26
  import vn.momo.kits.modifier.conditional
47
27
  import vn.momo.kits.modifier.noFeedbackClickable
48
28
  import vn.momo.kits.platform.getScreenDimensions
49
29
 
50
- val floatingButtonWidth = 75.dp
51
- const val BOTTOM_TAB_BAR_HEIGHT = 64
30
+ internal val floatingButtonWidth = 75.dp
31
+ internal const val BOTTOM_TAB_BAR_HEIGHT = 64
52
32
 
53
33
  @Composable
54
- fun BottomTabBar(
34
+ internal fun BottomTabBar(
55
35
  items: List<BottomTabItem>,
56
36
  floatingButton: BottomTabFloatingButton? = null,
57
37
  navController: NavController,
@@ -91,11 +71,11 @@ fun BottomTabBar(
91
71
  horizontalArrangement = Arrangement.SpaceAround,
92
72
  ) {
93
73
  if (floatingButton == null) {
94
- renderTabBarItem(items, 0, items.size, selectedIndex, onTabSelected)
74
+ RenderTabBarItem(items, 0, items.size, selectedIndex, onTabSelected)
95
75
  } else {
96
- renderTabBarItem(items, 0, mid, selectedIndex, onTabSelected)
76
+ RenderTabBarItem(items, 0, mid, selectedIndex, onTabSelected)
97
77
  Spacer(modifier = Modifier.width(floatingButtonWidth).padding(horizontal = Spacing.XXS))
98
- renderTabBarItem(items, mid, items.size, selectedIndex, onTabSelected)
78
+ RenderTabBarItem(items, mid, items.size, selectedIndex, onTabSelected)
99
79
  }
100
80
  }
101
81
 
@@ -124,7 +104,7 @@ fun BottomTabBar(
124
104
  }
125
105
 
126
106
  @Composable
127
- fun RowScope.renderTabBarItem(
107
+ internal fun RowScope.RenderTabBarItem(
128
108
  items: List<BottomTabItem>,
129
109
  fromIndex: Int,
130
110
  toIndex: Int,
@@ -143,7 +123,7 @@ fun RowScope.renderTabBarItem(
143
123
  }
144
124
 
145
125
  @Composable
146
- fun TabBarItem(item: BottomTabItem, selected: Boolean, onClick: () -> Unit) {
126
+ internal fun TabBarItem(item: BottomTabItem, selected: Boolean, onClick: () -> Unit) {
147
127
  fun isNumber(label: String): Boolean {
148
128
  val numberRegex = "^\\d+$".toRegex()
149
129
  return numberRegex.matches(label)
@@ -203,7 +183,7 @@ fun TabBarItem(item: BottomTabItem, selected: Boolean, onClick: () -> Unit) {
203
183
  }
204
184
 
205
185
  @Composable
206
- fun FloatingButton(data: BottomTabFloatingButton) {
186
+ internal fun FloatingButton(data: BottomTabFloatingButton) {
207
187
  Column(
208
188
  modifier = Modifier
209
189
  .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,