@momo-kits/native-kits 0.0.2-alpha.1 → 0.0.2-alpha.1-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 (143) hide show
  1. package/build.gradle.kts +11 -0
  2. package/compose/build.gradle.kts +189 -0
  3. package/compose/build.gradle.kts.backup +189 -0
  4. package/compose/compose.podspec +47 -0
  5. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +19 -0
  6. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +6 -0
  7. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +12 -0
  8. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +7 -0
  9. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +151 -0
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +52 -0
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +124 -0
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +183 -0
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +207 -0
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +39 -0
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +86 -0
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +76 -0
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +76 -0
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +293 -0
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +33 -0
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +831 -0
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +269 -0
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +110 -0
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +378 -0
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +65 -0
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +69 -0
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +157 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +82 -0
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +32 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +338 -0
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +175 -0
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +356 -0
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +113 -0
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +89 -0
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +128 -0
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +209 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +542 -0
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +49 -0
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +86 -0
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +125 -0
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +231 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +189 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +475 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +114 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +266 -0
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +240 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +223 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +264 -0
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +185 -0
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +343 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +108 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +56 -0
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +41 -0
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +84 -0
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +40 -0
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +328 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +97 -0
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +310 -0
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +59 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +87 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +21 -0
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +90 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +323 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +220 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +473 -0
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +122 -0
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +199 -0
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +91 -0
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +552 -0
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +88 -0
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +125 -0
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +207 -0
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +498 -0
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +176 -0
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +182 -0
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +193 -0
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +234 -0
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +188 -0
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +325 -0
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +16 -0
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +18 -0
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +22 -0
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +16 -0
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +194 -0
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +282 -0
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +54 -0
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +51 -0
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +41 -0
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +52 -0
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +66 -0
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +104 -0
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +48 -0
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +299 -0
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +58 -0
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +129 -0
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +106 -0
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +211 -0
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +398 -0
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +8 -0
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +595 -0
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +148 -0
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +40 -0
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +184 -0
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +225 -0
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +169 -0
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +357 -0
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +83 -0
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +106 -0
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +75 -0
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +326 -0
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +39 -0
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +364 -0
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +132 -0
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/ToolkitRequests.kt +33 -0
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +135 -0
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +12 -0
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +91 -0
  122. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +9 -0
  123. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +71 -0
  124. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
  125. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +55 -0
  126. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +15 -0
  127. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +105 -0
  128. package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/ToolkitRequestsTest.kt +47 -0
  129. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +40 -0
  130. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +45 -0
  131. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +36 -0
  132. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +38 -0
  133. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +66 -0
  134. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +213 -0
  135. package/gradle/libs.versions.toml +71 -0
  136. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  137. package/gradle/wrapper/gradle-wrapper.properties +8 -0
  138. package/gradle.properties +26 -0
  139. package/gradlew +252 -0
  140. package/gradlew.bat +94 -0
  141. package/ios-demo/README.md +120 -0
  142. package/package.json +1 -1
  143. package/settings.gradle.kts +64 -0
@@ -0,0 +1,398 @@
1
+ package vn.momo.kits.navigation
2
+
3
+ import androidx.compose.foundation.layout.Box
4
+ import androidx.compose.foundation.layout.fillMaxSize
5
+ import androidx.compose.runtime.Composable
6
+ import androidx.compose.runtime.MutableState
7
+ import androidx.compose.runtime.mutableStateOf
8
+ import androidx.compose.runtime.staticCompositionLocalOf
9
+ import androidx.compose.ui.Alignment
10
+ import androidx.compose.ui.Modifier
11
+ import androidx.navigation.NavController
12
+ import androidx.navigation.toRoute
13
+ import kotlinx.coroutines.CoroutineScope
14
+ import kotlinx.coroutines.Dispatchers
15
+ import kotlinx.coroutines.SupervisorJob
16
+ import kotlinx.coroutines.cancel
17
+ import kotlinx.coroutines.delay
18
+ import kotlinx.coroutines.launch
19
+ import kotlinx.serialization.Serializable
20
+ import vn.momo.kits.components.PopupAction
21
+ import vn.momo.kits.components.PopupNotify
22
+ import vn.momo.kits.navigation.BottomHeader.*
23
+ import vn.momo.kits.navigation.component.SnackBar
24
+ import vn.momo.maxapi.IMaxApi
25
+ // AI-GENERATED START: dismiss through the typed navigator target
26
+ import vn.momo.maxapi.contract.navigation.model.DismissTarget
27
+ // AI-GENERATED END: dismiss through the typed navigator target
28
+ import kotlin.time.Duration.Companion.milliseconds
29
+
30
+ class Navigator(
31
+ private val navController: NavController,
32
+ private val maxApi: IMaxApi?,
33
+ private val registry: DynamicScreenRegistry
34
+ ) {
35
+ private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
36
+
37
+ // currentScreen() reads the root back stack, so it never sees the nested tab NavHost.
38
+ internal var tabBackHandler: (() -> Unit)? = null
39
+
40
+ private fun currentScreen(): DynamicScreen? {
41
+ val entry = navController.currentBackStackEntry ?: return null
42
+ val id = runCatching { entry.toRoute<DynamicScreenRoute>().id }.getOrNull()
43
+ ?: runCatching { entry.toRoute<DynamicDialogRoute>().id }.getOrNull()
44
+ ?: return null
45
+ return registry.getScreen(id)
46
+ }
47
+
48
+ fun push(screenName: String, content: @Composable () -> Unit, options: NavigationOptions? = null) {
49
+ val route = registry.register(screenName, content, options)
50
+ navController.navigate(DynamicScreenRoute(route.id))
51
+ }
52
+
53
+ fun replace(screenName: String, content: @Composable () -> Unit, options: NavigationOptions? = null) {
54
+ val replacedScreen = currentScreen()
55
+ replacedScreen?.let { registry.unregisterScreen(it.id) }
56
+ navController.popBackStack()
57
+ push(screenName, content, options)
58
+ }
59
+
60
+ fun onBackSafe(callBack: (() -> Unit)? = null) {
61
+ tabBackHandler?.let {
62
+ it()
63
+ return
64
+ }
65
+ val currentScreen = currentScreen()
66
+ val options = currentScreen?.options
67
+ if (options?.onBackHandler != null) {
68
+ options.onBackHandler.invoke()
69
+ return
70
+ }
71
+ val preventBack = options?.headerBackProps?.preventBack
72
+ if (preventBack != null) {
73
+ showModal(
74
+ content = {
75
+ PopupNotify(preventBack.copy(
76
+ primary = PopupAction(
77
+ title = preventBack.primary?.title ?: "",
78
+ onPress = {
79
+ preventBack.primary?.onPress?.invoke()
80
+ pop(2, callBack) // close popup notify, then close the screen
81
+ }
82
+ )
83
+ ))
84
+ }
85
+ )
86
+ } else {
87
+ pop(1, callBack)
88
+ }
89
+ }
90
+
91
+ // The swipe-back opt-out predicate in StackScreen needs this: the root screen must keep custom
92
+ // back handling so it still reaches maxApi.dismiss.
93
+ // Public: the iOS host asks this to decide whether the kit owns the swipe on this screen.
94
+ fun canPop(): Boolean = navController.previousBackStackEntry != null
95
+
96
+ fun pop(count: Int = 1, callBack: (() -> Unit)? = null) {
97
+ scope.launch {
98
+ repeat(count) {
99
+ if (OverplayComponentRegistry.getOverplayType() == OverplayComponentType.SNACK_BAR){
100
+ dismissScreen()
101
+ OverplayComponentRegistry.hardClearAfterDismiss()
102
+ }
103
+ else if (OverplayComponentRegistry.isOverplayShowing()){
104
+ dismissOverplay()
105
+ } else {
106
+ dismissScreen()
107
+ }
108
+ }
109
+ callBack?.invoke()
110
+ }
111
+ }
112
+ private suspend fun dismissOverplay(isDelay: Boolean = true) {
113
+ OverplayComponentRegistry.clear()
114
+ if (isDelay) delay(300L.milliseconds)
115
+ OverplayComponentRegistry.hardClearAfterDismiss()
116
+ }
117
+
118
+ private suspend fun dismissScreen() {
119
+ if (navController.previousBackStackEntry != null){
120
+ val dismissing = currentScreen()
121
+ navController.popBackStack()
122
+ delay(STACK_TRANSITION_DURATION.milliseconds)
123
+ dismissing?.let { registry.unregisterScreen(it.id) }
124
+ } else {
125
+ // AI-GENERATED START: preserve root-screen dismissal
126
+ maxApi?.dismiss(DismissTarget.Current()) {}
127
+ // AI-GENERATED END: preserve root-screen dismissal
128
+ }
129
+ }
130
+
131
+ fun present(screenName: String, content: @Composable () -> Unit, options: NavigationOptions? = null) {
132
+ val route = registry.register(screenName, content, options)
133
+ navController.navigate(DynamicDialogRoute(route.id))
134
+ }
135
+
136
+ fun reset(screenName: String, content: @Composable () -> Unit, options: NavigationOptions? = null) {
137
+ registry.unregisterAll()
138
+
139
+ val route = registry.register(screenName, content, options)
140
+ navController.navigate(DynamicScreenRoute(route.id)) {
141
+ popUpTo(0) { inclusive = true }
142
+ }
143
+ }
144
+
145
+ fun showModal(
146
+ content: @Composable () -> Unit,
147
+ barrierDismissible: Boolean = true,
148
+ onDismiss: (() -> Unit)? = null
149
+ ){
150
+ val id = currentScreen()?.id ?: -1
151
+ OverplayComponentRegistry.registerOverplay(id, content, OverplayComponentType.MODAL, false, barrierDismissible, onDismiss)
152
+ }
153
+
154
+ fun showBottomSheet(
155
+ content: @Composable () -> Unit,
156
+ isSurface: Boolean = false,
157
+ barrierDismissible: Boolean = true,
158
+ onDismiss: (() -> Unit)? = null,
159
+ bottomSheetHeader: BottomHeader? = null,
160
+ draggable: Boolean = true,
161
+ useBottomInset: Boolean = true,
162
+ footerComponent: (@Composable () -> Unit)? = null
163
+ ){
164
+ val id = currentScreen()?.id ?: -1
165
+ OverplayComponentRegistry.registerOverplay(id, content, OverplayComponentType.BOTTOM_SHEET, isSurface, barrierDismissible, onDismiss, bottomSheetHeader, draggable, useBottomInset, footerComponent)
166
+ }
167
+
168
+ fun showSnackBar(snackBar: SnackBar, onDismiss: (() -> Unit)? = null) {
169
+ val id = currentScreen()?.id ?: -1
170
+ scope.launch {
171
+ OverplayComponentRegistry.registerOverplay(
172
+ id = id,
173
+ content = {
174
+ SnackBar(snackBar, onDismiss)
175
+ },
176
+ type = OverplayComponentType.SNACK_BAR,
177
+ onDismiss = onDismiss
178
+ )
179
+ }
180
+ }
181
+
182
+ fun hideSnackBar() {
183
+ scope.launch {
184
+ OverplayComponentRegistry.clear()
185
+ delay(250L.milliseconds)
186
+ OverplayComponentRegistry.hardClearAfterDismiss()
187
+ }
188
+ }
189
+
190
+ fun dispose(){
191
+ scope.cancel()
192
+ }
193
+ }
194
+
195
+ val LocalNavigator = staticCompositionLocalOf<Navigator> {
196
+ error("No NavigationStack provided")
197
+ }
198
+
199
+ @Serializable
200
+ data class DynamicScreenRoute(val id: Int)
201
+
202
+ @Serializable
203
+ data class DynamicDialogRoute(val id: Int)
204
+
205
+ data class DynamicScreen(
206
+ val id: Int,
207
+ val name: String,
208
+ val content: @Composable () -> Unit,
209
+ var options: NavigationOptions? = null
210
+ )
211
+
212
+ class DynamicScreenRegistry {
213
+ private val screens = mutableMapOf<Int, DynamicScreen>()
214
+
215
+ fun register(screenName: String, content: @Composable () -> Unit, options: NavigationOptions?): DynamicScreenRoute {
216
+ val id = nextGlobalId()
217
+ screens[id] = DynamicScreen(
218
+ id = id,
219
+ name = screenName,
220
+ content = content,
221
+ options = options
222
+ )
223
+ return DynamicScreenRoute(id)
224
+ }
225
+
226
+ fun nextId(): Int = nextGlobalId()
227
+
228
+ fun bind(id: Int, screenName: String, content: @Composable () -> Unit, options: NavigationOptions?) {
229
+ screens[id] = DynamicScreen(
230
+ id = id,
231
+ name = screenName,
232
+ content = content,
233
+ options = options
234
+ )
235
+ advanceGlobalIdPast(id) // keep counter ahead after process-death restore
236
+ }
237
+
238
+ fun unregisterScreen(id: Int) {
239
+ screens.remove(id)
240
+ }
241
+
242
+ // Gesture pops go through composeNavigator.popBackStack() inside NavHost and never reach
243
+ // Navigator.dismissScreen(), so screens would leak without a stack-driven prune.
244
+ fun retainOnly(ids: Set<Int>) {
245
+ val stale = screens.keys.filter { it !in ids }
246
+ for (id in stale) {
247
+ screens.remove(id)
248
+ }
249
+ }
250
+
251
+ fun unregisterAll(){
252
+ val ids = screens.keys.toList()
253
+ for (id in ids) {
254
+ unregisterScreen(id)
255
+ }
256
+ }
257
+
258
+ fun getScreen(id: Int): DynamicScreen? = screens[id]
259
+
260
+ fun setOptions(id: Int, options: NavigationOptions){
261
+ screens[id]?.options = options
262
+ }
263
+
264
+ companion object {
265
+ private var globalIdCounter = 1
266
+
267
+ private fun nextGlobalId(): Int = globalIdCounter++
268
+
269
+ private fun advanceGlobalIdPast(id: Int) {
270
+ if (id >= globalIdCounter) globalIdCounter = id + 1
271
+ }
272
+ }
273
+ }
274
+
275
+ val LocalDynamicScreenRegistry = staticCompositionLocalOf<DynamicScreenRegistry?> { null }
276
+
277
+
278
+ sealed class OverplayComponentParams {
279
+ class Modal(
280
+ val onDismiss: (() -> Unit)? = null,
281
+ val barrierDismissible: Boolean = true
282
+ ) : OverplayComponentParams()
283
+
284
+ class BottomSheet(
285
+ val isSurface: Boolean = false,
286
+ val onDismiss: (() -> Unit)? = null,
287
+ val barrierDismissible: Boolean = true,
288
+ val bottomSheetHeader: BottomHeader? = null,
289
+ val draggable: Boolean = true,
290
+ val useBottomInset: Boolean = true,
291
+ val footerComponent: (@Composable () -> Unit)? = null,
292
+ ) : OverplayComponentParams()
293
+
294
+ class SnackBar : OverplayComponentParams()
295
+ }
296
+
297
+ data class OverplayComponent(
298
+ val id: Int,
299
+ val type: OverplayComponentType? = null,
300
+ val content: @Composable () -> Unit,
301
+ val params: OverplayComponentParams? = null
302
+ )
303
+
304
+ enum class OverplayComponentType {
305
+ MODAL, BOTTOM_SHEET, SNACK_BAR
306
+ }
307
+
308
+ object OverplayComponentRegistry {
309
+ private var currentOverlayComponent : MutableState<OverplayComponent?> = mutableStateOf(null)
310
+ private var requestClose: (() -> Unit)? = null
311
+ internal fun bindClose(handler: (() -> Unit)?) {
312
+ requestClose = handler
313
+ }
314
+
315
+ fun registerOverplay(
316
+ id: Int,
317
+ content: @Composable () -> Unit,
318
+ type: OverplayComponentType,
319
+ isSurface: Boolean = false,
320
+ barrierDismissible: Boolean = true,
321
+ onDismiss: (() -> Unit)?,
322
+ bottomSheetHeader: BottomHeader? = null,
323
+ draggable: Boolean = true,
324
+ useBottomInset: Boolean = true,
325
+ footerComponent: (@Composable () -> Unit)? = null,
326
+ ){
327
+ val params = when(type){
328
+ OverplayComponentType.MODAL -> OverplayComponentParams.Modal(onDismiss, barrierDismissible)
329
+ OverplayComponentType.BOTTOM_SHEET -> OverplayComponentParams.BottomSheet(isSurface, onDismiss, barrierDismissible, bottomSheetHeader, draggable, useBottomInset, footerComponent)
330
+ OverplayComponentType.SNACK_BAR -> OverplayComponentParams.SnackBar()
331
+ }
332
+
333
+ currentOverlayComponent.value = OverplayComponent(
334
+ id = id,
335
+ type = type,
336
+ content = content,
337
+ params = params
338
+ )
339
+ }
340
+
341
+ fun isOverplayShowing(): Boolean = currentOverlayComponent.value != null
342
+
343
+ fun getOverplayType(): OverplayComponentType? = currentOverlayComponent.value?.type
344
+
345
+ fun currentRootId(): Int? = currentOverlayComponent.value?.id
346
+
347
+ fun clear(){
348
+ if (requestClose != null) {
349
+ requestClose?.invoke()
350
+ } else {
351
+ currentOverlayComponent.value = null
352
+ }
353
+ }
354
+
355
+ internal fun hardClearAfterDismiss() {
356
+ currentOverlayComponent.value = null
357
+ requestClose = null
358
+ }
359
+
360
+ @Composable
361
+ internal fun OverlayComponent(){
362
+ val overplay = currentOverlayComponent.value ?: return
363
+
364
+ when (val params = overplay.params) {
365
+ is OverplayComponentParams.BottomSheet -> {
366
+ BottomSheet(
367
+ content = overplay.content,
368
+ header = params.bottomSheetHeader ?: Title(),
369
+ isSurface = params.isSurface,
370
+ barrierDismissible = params.barrierDismissible,
371
+ draggable = params.draggable,
372
+ useBottomInset = params.useBottomInset,
373
+ footerComponent = params.footerComponent,
374
+ onDismiss = params.onDismiss
375
+ )
376
+ }
377
+
378
+ is OverplayComponentParams.Modal -> {
379
+ ModalScreen(
380
+ content = overplay.content,
381
+ barrierDismissible = params.barrierDismissible,
382
+ onDismiss = params.onDismiss
383
+ )
384
+ }
385
+
386
+ is OverplayComponentParams.SnackBar -> {
387
+ Box(
388
+ modifier = Modifier.fillMaxSize(),
389
+ contentAlignment = Alignment.BottomCenter
390
+ ) {
391
+ overplay.content()
392
+ }
393
+ }
394
+
395
+ null -> {}
396
+ }
397
+ }
398
+ }
@@ -0,0 +1,8 @@
1
+ package vn.momo.kits.navigation
2
+
3
+ import androidx.compose.runtime.Composable
4
+
5
+ @Composable
6
+ internal expect fun RegisterScrollToTop(callback: (() -> Unit)?)
7
+
8
+