@momo-kits/native-kits 0.168.1-beta.11-debug → 0.168.1-beta.13

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