@momo-kits/native-kits 0.163.1-beta.1 → 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 (155) 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/navigation/ScrollToTop.android.kt +6 -0
  6. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +123 -0
  7. package/compose/src/commonMain/composeResources/font/momosignature.otf +0 -0
  8. package/compose/src/commonMain/composeResources/font/momotrustdisplay.otf +0 -0
  9. package/compose/src/commonMain/composeResources/font/sfprotext_black.otf +0 -0
  10. package/compose/src/commonMain/composeResources/font/sfprotext_black.ttf +0 -0
  11. package/compose/src/commonMain/composeResources/font/sfprotext_bold.ttf +0 -0
  12. package/compose/src/commonMain/composeResources/font/sfprotext_heavy.ttf +0 -0
  13. package/compose/src/commonMain/composeResources/font/sfprotext_light.ttf +0 -0
  14. package/compose/src/commonMain/composeResources/font/sfprotext_medium.ttf +0 -0
  15. package/compose/src/commonMain/composeResources/font/sfprotext_regular.ttf +0 -0
  16. package/compose/src/commonMain/composeResources/font/sfprotext_semibold.ttf +0 -0
  17. package/compose/src/commonMain/composeResources/font/sfprotext_thin.otf +0 -0
  18. package/compose/src/commonMain/composeResources/font/sfprotext_thin.ttf +0 -0
  19. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.otf +0 -0
  20. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.ttf +0 -0
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +52 -0
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +108 -0
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +183 -0
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +207 -0
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +39 -0
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +86 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +76 -0
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +76 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +294 -0
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +33 -0
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +826 -0
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +269 -0
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +107 -0
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +378 -0
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +69 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +157 -0
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +82 -0
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +32 -0
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +338 -0
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +175 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +354 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +113 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +89 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +128 -0
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +209 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +543 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +23 -0
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +76 -0
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +140 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +188 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +112 -0
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +477 -0
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +114 -0
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +214 -0
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +240 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +216 -0
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +259 -0
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +180 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +343 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +108 -0
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +56 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +41 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +84 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +40 -0
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +328 -0
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +97 -0
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +310 -0
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +59 -0
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +87 -0
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +90 -0
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +323 -0
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +217 -0
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +473 -0
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +122 -0
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +199 -0
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +91 -0
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +470 -0
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +88 -0
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +134 -0
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +203 -0
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +498 -0
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +175 -0
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +182 -0
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +193 -0
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +234 -0
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +188 -0
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +306 -0
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +16 -0
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +188 -0
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +269 -0
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +2 -0
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +35 -0
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +2 -0
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +52 -0
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +66 -0
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +104 -0
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +48 -0
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +253 -0
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +129 -0
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +94 -0
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +175 -0
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +360 -0
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +8 -0
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +557 -0
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +155 -0
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +223 -0
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +169 -0
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +319 -0
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +81 -0
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +309 -0
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +34 -0
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +360 -0
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +132 -0
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +128 -0
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +70 -0
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +58 -0
  122. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
  123. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +55 -0
  124. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +15 -0
  125. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +105 -0
  126. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +32 -0
  127. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +184 -0
  128. package/gradle/libs.versions.toml +67 -0
  129. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  130. package/gradle/wrapper/gradle-wrapper.properties +8 -0
  131. package/gradle.properties +26 -0
  132. package/gradlew +252 -0
  133. package/gradlew.bat +94 -0
  134. package/ios/Application/ApplicationEnvironment.swift +1 -0
  135. package/ios/Application/Components.swift +1 -0
  136. package/ios/Application/FloatingButton.swift +1 -0
  137. package/ios/Application/FontScaleStore.swift +59 -0
  138. package/ios/Badge/Badge.swift +1 -0
  139. package/ios/Badge/BadgeRibbon.swift +2 -0
  140. package/ios/Button/Button.swift +1 -1
  141. package/ios/Checkbox/Checkbox.swift +1 -0
  142. package/ios/Information/Information.swift +151 -0
  143. package/ios/Input/Input.swift +1 -0
  144. package/ios/Input/InputPhoneNumber.swift +1 -0
  145. package/ios/Input/InputSearch.swift +3 -0
  146. package/ios/Input/InputTextArea.swift +1 -0
  147. package/ios/OTPKeyboard/KeyboardButton.swift +1 -1
  148. package/ios/Popup/PopupDisplay.swift +6 -0
  149. package/ios/Popup/PopupInput.swift +2 -0
  150. package/ios/Template/TrustBanner/TrustBanner.swift +2 -0
  151. package/ios/Typography/Text.swift +13 -7
  152. package/ios/Typography/Typography.swift +22 -8
  153. package/local.properties +8 -0
  154. package/package.json +1 -1
  155. package/settings.gradle.kts +64 -0
@@ -0,0 +1,557 @@
1
+ package vn.momo.kits.navigation
2
+
3
+ import androidx.compose.animation.animateColorAsState
4
+ import androidx.compose.animation.core.animateDpAsState
5
+ import androidx.compose.foundation.ScrollState
6
+ import androidx.compose.foundation.background
7
+ import androidx.compose.foundation.border
8
+ import androidx.compose.foundation.gestures.ScrollableState
9
+ import androidx.compose.foundation.layout.Box
10
+ import androidx.compose.foundation.layout.Column
11
+ import androidx.compose.foundation.layout.ColumnScope
12
+ import androidx.compose.foundation.layout.Spacer
13
+ import androidx.compose.foundation.layout.WindowInsets
14
+ import androidx.compose.foundation.layout.asPaddingValues
15
+ import androidx.compose.foundation.layout.aspectRatio
16
+ import androidx.compose.foundation.layout.fillMaxSize
17
+ import androidx.compose.foundation.layout.fillMaxWidth
18
+ import androidx.compose.foundation.layout.height
19
+ import androidx.compose.foundation.layout.ime
20
+ import androidx.compose.foundation.layout.imePadding
21
+ import androidx.compose.foundation.layout.offset
22
+ import androidx.compose.foundation.layout.padding
23
+ import androidx.compose.foundation.lazy.LazyListState
24
+ import androidx.compose.foundation.lazy.rememberLazyListState
25
+ import androidx.compose.foundation.rememberScrollState
26
+ import androidx.compose.foundation.verticalScroll
27
+ import androidx.compose.material.ExperimentalMaterialApi
28
+ import androidx.compose.runtime.Composable
29
+ import androidx.compose.runtime.CompositionLocalProvider
30
+ import androidx.compose.runtime.LaunchedEffect
31
+ import androidx.compose.runtime.State
32
+ import androidx.compose.runtime.getValue
33
+ import androidx.compose.runtime.mutableIntStateOf
34
+ import androidx.compose.runtime.mutableStateOf
35
+ import androidx.compose.runtime.remember
36
+ import androidx.compose.runtime.rememberUpdatedState
37
+ import androidx.compose.runtime.snapshotFlow
38
+ import androidx.compose.runtime.staticCompositionLocalOf
39
+ import androidx.compose.ui.Alignment
40
+ import androidx.compose.ui.Modifier
41
+ import androidx.compose.ui.graphics.Brush
42
+ import androidx.compose.ui.graphics.Color
43
+ import androidx.compose.ui.graphics.lerp
44
+ import androidx.compose.ui.layout.onGloballyPositioned
45
+ import androidx.compose.ui.platform.LocalDensity
46
+ import androidx.compose.ui.unit.Dp
47
+ import androidx.compose.ui.unit.dp
48
+ import androidx.compose.ui.zIndex
49
+ import kotlinx.coroutines.flow.Flow
50
+ import vn.momo.kits.components.InputSearch
51
+ import vn.momo.kits.const.AppNavigationBar
52
+ import vn.momo.kits.const.AppStatusBar
53
+ import vn.momo.kits.const.AppTheme
54
+ import vn.momo.kits.application.IsShowBaseLineDebug
55
+ import vn.momo.kits.const.Colors
56
+ import vn.momo.kits.const.Spacing
57
+ import vn.momo.kits.modifier.conditional
58
+ import vn.momo.kits.modifier.hideKeyboardOnTap
59
+ import vn.momo.kits.navigation.component.FABPosition
60
+ import vn.momo.kits.navigation.component.FloatingButton
61
+ import vn.momo.kits.navigation.component.HEADER_HEIGHT
62
+ import vn.momo.kits.navigation.component.Header
63
+ import vn.momo.kits.navigation.component.HeaderBackground
64
+ import vn.momo.kits.navigation.component.HeaderRight
65
+ import vn.momo.kits.navigation.component.HeaderType
66
+ import vn.momo.kits.navigation.component.InputSearchType
67
+ import vn.momo.kits.platform.BackHandler
68
+ import vn.momo.kits.platform.supportsImePadding
69
+ import vn.momo.kits.navigation.tracking.ScreenTracker
70
+ import vn.momo.kits.navigation.tracking.ScreenTrackingState
71
+ import kotlinx.coroutines.delay
72
+ import vn.momo.kits.application.LocalContext
73
+ import kotlin.time.Clock
74
+ import kotlin.time.Duration.Companion.milliseconds
75
+ import kotlin.time.ExperimentalTime
76
+
77
+ internal val LocalFooterHeightPx = staticCompositionLocalOf { mutableIntStateOf(0) }
78
+ internal val LocalHeaderRightWidthPx = staticCompositionLocalOf { mutableIntStateOf(0) }
79
+
80
+ @OptIn(ExperimentalMaterialApi::class, ExperimentalTime::class)
81
+ @Composable
82
+ internal fun StackScreen(
83
+ content: @Composable () -> Unit,
84
+ navigationOptions: NavigationOptions? = null,
85
+ id: Int = -1,
86
+ name: String = "",
87
+ bottomTabIndex: Int = -1,
88
+ onBackHandler: (() -> Unit)? = null,
89
+ ) {
90
+ val navigator = LocalNavigator.current
91
+ val maxApi = LocalMaxApi.current
92
+ val context = LocalContext.current
93
+ val statusBar = AppStatusBar.current
94
+ val density = LocalDensity.current
95
+ val screenRegistry = LocalDynamicScreenRegistry.current
96
+ val navigation = remember { Navigation(id = id, bottomTabIndex = bottomTabIndex, initOptions = navigationOptions, registry = screenRegistry) }
97
+
98
+ val options by navigation.currentOptions
99
+
100
+ // Auto tracking state
101
+ val trackingState = remember {
102
+ ScreenTrackingState().apply {
103
+ time = Clock.System.now().toEpochMilliseconds()
104
+ }
105
+ }
106
+
107
+ // Determine action: push or back
108
+ val action = remember {
109
+ if (ScreenTracker.getLastScreenName() != null) "push" else "back"
110
+ }
111
+
112
+ // Auto tracking effects
113
+ LaunchedEffect(name) {
114
+ // Track screen navigated immediately
115
+ ScreenTracker.trackScreenNavigated(
116
+ maxApi = maxApi,
117
+ context = context,
118
+ screenName = name,
119
+ action = action,
120
+ state = trackingState
121
+ )
122
+
123
+ // Track screen displayed after 2 seconds (debounce)
124
+ delay(2000.milliseconds)
125
+ val loadTime = Clock.System.now().toEpochMilliseconds() - trackingState.time
126
+ ScreenTracker.trackScreenDisplayed(
127
+ maxApi = maxApi,
128
+ context = context,
129
+ screenName = name,
130
+ duration = loadTime,
131
+ state = trackingState
132
+ )
133
+
134
+ // Track screen interacted after displayed
135
+ val interactionTime = Clock.System.now().toEpochMilliseconds() - trackingState.time
136
+ ScreenTracker.trackScreenInteracted(
137
+ maxApi = maxApi,
138
+ context = context,
139
+ screenName = name,
140
+ duration = interactionTime - loadTime,
141
+ totalDuration = interactionTime,
142
+ state = trackingState
143
+ )
144
+ }
145
+
146
+ val limit = with(density) {
147
+ (statusBar).toPx() + HEADER_HEIGHT
148
+ }.toInt()
149
+
150
+ val (scrollState, scrollInProcess) = if (options.scrollData.scrollState is LazyListState)
151
+ (options.scrollData.scrollState as? LazyListState ?: rememberLazyListState()).proxyScrollState(limit, 15)
152
+ else
153
+ (options.scrollData.scrollState as? ScrollState ?: rememberScrollState()).proxyLimitedScrollState(limit, 15)
154
+
155
+ val footerHeightPx = remember { mutableIntStateOf(0) }
156
+ val headerRightWidthPx = remember { mutableIntStateOf(0) }
157
+
158
+ if (navigation.options.scrollData.scrollToTopCallback != null) {
159
+ RegisterScrollToTop(navigation.options.scrollData.scrollToTopCallback)
160
+ }
161
+
162
+ BackHandler(true) { navigator.onBackSafe() }
163
+
164
+ CompositionLocalProvider(
165
+ StackScreenScrollableState provides options.scrollData.scrollState,
166
+ LocalNavigation provides navigation,
167
+ LocalScrollState provides scrollState,
168
+ LocalOptions provides options,
169
+ LocalFooterHeightPx provides footerHeightPx,
170
+ LocalHeaderRightWidthPx provides headerRightWidthPx
171
+ ) {
172
+ Box(
173
+ Modifier
174
+ .fillMaxSize()
175
+ .background(options.backgroundColor ?: AppTheme.current.colors.background.default)
176
+ .conditional(options.keyboardOptions.keyboardShouldPersistTaps) {
177
+ hideKeyboardOnTap()
178
+ }
179
+ .conditional(options.keyboardOptions.useAvoidKeyboard && supportsImePadding()) {
180
+ imePadding()
181
+ }
182
+ ) {
183
+ Box(Modifier.zIndex(1f)) {
184
+ HeaderBackground()
185
+ }
186
+
187
+ Box(Modifier.zIndex(4f)) {
188
+ Header(onBackHandler)
189
+ }
190
+
191
+ Column(Modifier.zIndex(5f)) {
192
+ SearchAnimated(isScrollInProgress = scrollInProcess)
193
+ }
194
+
195
+ Column(Modifier.zIndex(2f).fillMaxSize()) {
196
+ MainContent(content = content)
197
+ FooterContent()
198
+ }
199
+
200
+ Box(Modifier.zIndex(6f)) {
201
+ FloatingContent()
202
+ }
203
+
204
+ OverplayView(bottomTabIndex = bottomTabIndex, id = id)
205
+ }
206
+ }
207
+ }
208
+
209
+ @Suppress("FrequentlyChangingValue")
210
+ @Composable
211
+ internal fun FloatingContent() {
212
+ val options = LocalOptions.current
213
+ val density = LocalDensity.current
214
+ val footerHeightPx = LocalFooterHeightPx.current
215
+ val scrollState = LocalScrollState.current
216
+
217
+ val fabProps = options.floatingButtonProps ?: return
218
+ val bottomPadding = fabProps.bottom
219
+ ?: (Spacing.M + if (options.footerComponent != null) with(density) { footerHeightPx.intValue.toDp() } else 0.dp)
220
+
221
+ FloatingButton(
222
+ scrollPosition = fabProps.scrollState?.value ?: scrollState.value,
223
+ onClick = fabProps.onClick,
224
+ containerColor = AppTheme.current.colors.primary,
225
+ bottom = bottomPadding,
226
+ icon = fabProps.icon,
227
+ iconColor = fabProps.iconColor,
228
+ text = fabProps.label,
229
+ size = fabProps.size,
230
+ position = fabProps.position ?: FABPosition.END,
231
+ )
232
+ }
233
+
234
+ @Composable
235
+ internal fun ColumnScope.MainContent(content: @Composable () -> Unit) {
236
+ val options = LocalOptions.current
237
+ val inputSearchType = getInputSearchType(options)
238
+ val density = LocalDensity.current
239
+ val scrollState = LocalScrollState.current
240
+
241
+ Spacer(
242
+ Modifier.height(
243
+ if (options.headerType is HeaderType.DefaultOrExtended || (options.headerType is HeaderType.Transparent && !options.headerType.isFullScreenContent))
244
+ AppStatusBar.current + HEADER_HEIGHT.dp else 0.dp
245
+ )
246
+ )
247
+ if (inputSearchType == InputSearchType.Animated) {
248
+ val scrollDp = with(density) { scrollState.value.toDp() }
249
+
250
+ val animatedTopPadding by animateDpAsState(
251
+ targetValue = (HEADER_HEIGHT.dp - scrollDp).coerceIn(0.dp, HEADER_HEIGHT.dp),
252
+ label = "AnimatedTopPadding"
253
+ )
254
+ Spacer(Modifier.height(animatedTopPadding))
255
+ }
256
+ Column(
257
+ Modifier
258
+ .fillMaxWidth()
259
+ .weight(1f)
260
+ .conditional(options.scrollData.scrollable && options.scrollData.scrollState is ScrollState) {
261
+ verticalScroll(scrollState)
262
+ }
263
+ ) {
264
+ ScreenContent(content = content)
265
+ }
266
+
267
+ }
268
+
269
+ @Composable
270
+ internal fun ScreenContent(content: @Composable () -> Unit) {
271
+ val scrollState = LocalScrollState.current
272
+ val options = LocalOptions.current
273
+
274
+ if (options.headerType is HeaderType.Animated) {
275
+ val animatedHeader = options.headerType
276
+ Box {
277
+ Box(Modifier.fillMaxWidth().aspectRatio(animatedHeader.aspectRatio.value)) {
278
+ animatedHeader.composable.invoke(scrollState.value)
279
+ }
280
+ Box(Modifier.padding(top = AppStatusBar.current + HEADER_HEIGHT.dp + animatedHeader.layoutOffSet)) {
281
+ content()
282
+ }
283
+ }
284
+ } else {
285
+ content()
286
+ }
287
+ }
288
+
289
+ @Composable
290
+ internal fun FooterContent() {
291
+ val options = LocalOptions.current
292
+ if (options.footerComponent != null) {
293
+ val keyboardSize = keyboardSizeState()
294
+ val bottomPadding = (AppNavigationBar.current - keyboardSize.value).coerceAtLeast(0.dp)
295
+ Footer(footerComponent = options.footerComponent, bottomPadding = bottomPadding)
296
+ }
297
+ }
298
+
299
+ @Composable
300
+ internal fun keyboardSizeState(): State<Dp> {
301
+ val bottom = WindowInsets.ime.asPaddingValues()
302
+ return rememberUpdatedState(bottom.calculateBottomPadding())
303
+ }
304
+
305
+ @Composable
306
+ internal fun OverplayView(bottomTabIndex: Int, id: Int) {
307
+ val overplayType = OverplayComponentRegistry.getOverplayType()
308
+
309
+ if (overplayType != null) {
310
+ Box(Modifier.zIndex(if (overplayType == OverplayComponentType.SNACK_BAR) 3f else 7f).fillMaxSize()) {
311
+ if (bottomTabIndex != -1) return@Box
312
+ if (OverplayComponentRegistry.currentRootId() != id) return@Box
313
+ OverplayComponentRegistry.OverlayComponent()
314
+ }
315
+ }
316
+ }
317
+
318
+ @Composable
319
+ fun Footer(footerComponent: @Composable (() -> Unit)?, bottomPadding: Dp) {
320
+ if (footerComponent == null) return
321
+
322
+ val footerHeightPx = LocalFooterHeightPx.current
323
+
324
+ val shadowBrush = remember {
325
+ Brush.verticalGradient(
326
+ colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.05f))
327
+ )
328
+ }
329
+
330
+ Box(Modifier.onGloballyPositioned {
331
+ if (footerHeightPx.intValue != it.size.height) footerHeightPx.intValue = it.size.height
332
+ }) {
333
+ Box(
334
+ Modifier
335
+ .fillMaxWidth()
336
+ .background(AppTheme.current.colors.background.surface)
337
+ .conditional(IsShowBaseLineDebug) {
338
+ border(1.dp, Colors.blue_03)
339
+ }
340
+ .padding(top = Spacing.S, start = Spacing.M, end = Spacing.M, bottom = bottomPadding + Spacing.S)
341
+ ) {
342
+ footerComponent.invoke()
343
+ }
344
+
345
+ Box(
346
+ modifier = Modifier
347
+ .fillMaxWidth()
348
+ .height(6.dp)
349
+ .offset(x = 0.dp, y = (-6).dp)
350
+ .background(shadowBrush)
351
+ )
352
+ }
353
+ }
354
+
355
+ data class InputSearchLayoutParams(
356
+ val topPadding: Dp,
357
+ val startPadding: Dp,
358
+ val endPadding: Dp
359
+ )
360
+
361
+ @Suppress("FrequentlyChangingValue")
362
+ @Composable
363
+ internal fun SearchAnimated(
364
+ isScrollInProgress: Boolean
365
+ ) {
366
+ val scrollState = LocalScrollState.current
367
+ val options = LocalOptions.current
368
+ val navigator = LocalNavigator.current
369
+ val density = LocalDensity.current
370
+
371
+ val scrollDp = with(density) { scrollState.value.toDp() }
372
+
373
+ val inputSearchType = getInputSearchType(options)
374
+ val headerRightWidthPx = LocalHeaderRightWidthPx.current
375
+ val headerRightWidthDp = with(density) { headerRightWidthPx.intValue.toDp() }
376
+
377
+ if (inputSearchType == InputSearchType.None) return
378
+ val inputSearchProps = (options.headerType as? HeaderType.DefaultOrExtended)?.inputSearchProps ?: return
379
+
380
+ val minTopPadding = AppStatusBar.current
381
+ val maxTopPadding = AppStatusBar.current + HEADER_HEIGHT.dp
382
+ val minStartPadding = Spacing.M
383
+ val maxStartPadding = if (options.hiddenBack) Spacing.M else 48.dp // button back: size + left +right = 28 + 12 + 8 = 48
384
+ val minEndPadding = Spacing.M
385
+ val maxEndPadding = headerRightWidthDp + if (options.headerRight is HeaderRight.None) Spacing.M else Spacing.M * 2
386
+
387
+ val targetColor = lerp(
388
+ AppTheme.current.colors.background.surface,
389
+ AppTheme.current.colors.background.default,
390
+ (scrollDp / minTopPadding).coerceIn(0f, 1f)
391
+ )
392
+ val animatedColor by animateColorAsState(targetValue = targetColor, label = "BackgroundColor")
393
+
394
+ val layoutParams = when (inputSearchType) {
395
+ InputSearchType.Header -> {
396
+ InputSearchLayoutParams(
397
+ topPadding = minTopPadding,
398
+ startPadding = maxStartPadding,
399
+ endPadding = maxEndPadding
400
+ )
401
+ }
402
+
403
+ InputSearchType.Animated -> {
404
+ val animatedTopPadding by animateDpAsState(
405
+ targetValue = (maxTopPadding - scrollDp).coerceIn(minTopPadding, maxTopPadding),
406
+ label = "AnimatedTopPadding"
407
+ )
408
+
409
+ val animatedStartPadding by animateDpAsState(
410
+ targetValue = (minStartPadding + scrollDp).coerceIn(minStartPadding, maxStartPadding),
411
+ label = "AnimatedStartPadding"
412
+ )
413
+
414
+ val animatedEndPadding by animateDpAsState(
415
+ targetValue = (minEndPadding + scrollDp).coerceIn(minEndPadding, maxEndPadding),
416
+ label = "AnimatedEndPadding"
417
+ )
418
+
419
+ val maxPadding = remember(maxTopPadding, maxStartPadding, maxEndPadding) {
420
+ maxOf(maxEndPadding, maxStartPadding, maxTopPadding)
421
+ }
422
+ val snapScrollValue = with(density) { maxPadding.toPx().toInt() }
423
+
424
+ LaunchedEffect(isScrollInProgress && scrollState.value != 0 && scrollState.value != snapScrollValue) {
425
+ if (scrollDp < maxPadding) {
426
+ if (!isScrollInProgress) {
427
+ val midpoint = (maxTopPadding - minTopPadding) / 2
428
+
429
+ if (scrollDp < midpoint) {
430
+ scrollState.animateScrollTo(0)
431
+ } else {
432
+ scrollState.animateScrollTo(snapScrollValue)
433
+ }
434
+ }
435
+ }
436
+ }
437
+
438
+ InputSearchLayoutParams(
439
+ topPadding = animatedTopPadding,
440
+ startPadding = animatedStartPadding,
441
+ endPadding = animatedEndPadding
442
+ )
443
+ }
444
+
445
+ InputSearchType.None -> return
446
+ }
447
+
448
+ Spacer(Modifier.height(layoutParams.topPadding))
449
+ Box(
450
+ modifier = Modifier
451
+ .height(HEADER_HEIGHT.dp)
452
+ .fillMaxWidth()
453
+ .padding(
454
+ start = layoutParams.startPadding,
455
+ end = layoutParams.endPadding
456
+ ),
457
+ contentAlignment = Alignment.Center
458
+ ) {
459
+ InputSearch(
460
+ inputSearchProps = inputSearchProps.copy(
461
+ backgroundColor = animatedColor,
462
+ onPressButtonText = {
463
+ navigator.pop()
464
+ }
465
+ )
466
+ )
467
+ }
468
+ }
469
+
470
+ private fun quantize(value: Int, stepPx: Int): Int {
471
+ if (stepPx <= 1) return value
472
+ return (value / stepPx) * stepPx
473
+ }
474
+
475
+ @Composable
476
+ internal fun LazyListState.proxyScrollState(
477
+ thresholdPx: Int = 200,
478
+ stepPx: Int = 4
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
+ )
491
+
492
+ @Composable
493
+ internal fun ScrollState.proxyLimitedScrollState(
494
+ thresholdPx: Int = 200,
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>,
511
+ ): Pair<ScrollState, Boolean> {
512
+ val proxy = rememberScrollState()
513
+ val locked = remember { mutableStateOf(false) }
514
+
515
+ LaunchedEffect(source, proxy, thresholdPx, stepPx) {
516
+ rawTargetFlow().collect { rawTarget ->
517
+ val shouldLock = rawTarget > thresholdPx
518
+ if (locked.value != shouldLock) locked.value = shouldLock
519
+
520
+ val desired = if (shouldLock) thresholdPx else quantize(rawTarget, stepPx)
521
+
522
+ if (proxy.value != desired) {
523
+ proxy.scrollTo(desired.coerceAtLeast(0))
524
+ }
525
+ }
526
+ }
527
+
528
+ val inProgress = remember { mutableStateOf(false) }
529
+ LaunchedEffect(source, thresholdPx) {
530
+ snapshotFlow { isScrollInProgress() }
531
+ .collect { progressing ->
532
+ inProgress.value = if (locked.value) false else progressing
533
+ }
534
+ }
535
+
536
+ return proxy to inProgress.value
537
+ }
538
+
539
+ private const val SCROLLED_PAST_FIRST_ITEM_THRESHOLD = 10_000
540
+
541
+ internal val LocalScrollState = staticCompositionLocalOf<ScrollState> { error("No Scroll State provided") }
542
+ internal val LocalOptions = staticCompositionLocalOf<NavigationOptions> { error("No NavigationOptions provided") }
543
+
544
+ val StackScreenScrollableState = staticCompositionLocalOf<ScrollableState?> { null }
545
+
546
+ internal fun getInputSearchType(options: NavigationOptions): InputSearchType {
547
+ return when (val headerType = options.headerType) {
548
+ is HeaderType.DefaultOrExtended -> when {
549
+ headerType.inputSearchProps == null -> InputSearchType.None
550
+ headerType.useAnimated -> InputSearchType.Animated
551
+ else -> InputSearchType.Header
552
+ }
553
+
554
+ else -> InputSearchType.None
555
+ }
556
+ }
557
+
@@ -0,0 +1,155 @@
1
+ package vn.momo.kits.navigation.bottomtab
2
+
3
+ import androidx.compose.animation.core.tween
4
+ import androidx.compose.animation.fadeIn
5
+ import androidx.compose.animation.fadeOut
6
+ import androidx.compose.animation.scaleIn
7
+ import androidx.compose.animation.scaleOut
8
+ import androidx.compose.foundation.background
9
+ import androidx.compose.foundation.layout.*
10
+ import androidx.compose.runtime.Composable
11
+ import androidx.compose.runtime.LaunchedEffect
12
+ import androidx.compose.ui.Alignment
13
+ import androidx.compose.ui.Modifier
14
+ import androidx.compose.ui.unit.dp
15
+ import androidx.navigation.compose.NavHost
16
+ import androidx.navigation.compose.composable
17
+ import androidx.navigation.compose.rememberNavController
18
+ import vn.momo.kits.const.AppNavigationBar
19
+ import vn.momo.kits.const.AppTheme
20
+ import vn.momo.kits.const.Spacing
21
+ import vn.momo.kits.navigation.LocalNavigation
22
+ import vn.momo.kits.navigation.LocalNavigator
23
+ import vn.momo.kits.navigation.NavigationOptions
24
+ import vn.momo.kits.navigation.StackScreen
25
+ import vn.momo.kits.navigation.component.HeaderType
26
+ import vn.momo.kits.platform.getScreenHeight
27
+
28
+ private var bottomTabOptionItems : MutableList<NavigationOptions?> = mutableListOf()
29
+ internal fun setBottomTabOption(index: Int, options: NavigationOptions){
30
+ if (index in bottomTabOptionItems.indices) {
31
+ bottomTabOptionItems[index] = options
32
+ }
33
+ }
34
+ internal fun getBottomTabOption(index: Int): NavigationOptions? {
35
+ return if (index in bottomTabOptionItems.indices) {
36
+ bottomTabOptionItems[index]
37
+ } else null
38
+ }
39
+
40
+ @Composable
41
+ fun BottomTab(
42
+ items: List<BottomTabItem>,
43
+ floatingButton: BottomTabFloatingButton? = null
44
+ ) {
45
+ val navigation = LocalNavigation.current
46
+ val navigator = LocalNavigator.current
47
+ val navController = rememberNavController()
48
+
49
+ bottomTabOptionItems = items.mapIndexed { index, item ->
50
+ item.options ?: NavigationOptions()
51
+ }.toMutableList()
52
+
53
+
54
+ LaunchedEffect(Unit){
55
+ navigation.setOptions(
56
+ headerType = HeaderType.None
57
+ )
58
+ }
59
+
60
+ Box(modifier = Modifier.fillMaxWidth().height(getScreenHeight()), contentAlignment = Alignment.BottomCenter) {
61
+ Box(modifier = Modifier
62
+ .fillMaxSize()
63
+ .padding(bottom = BOTTOM_TAB_BAR_HEIGHT.dp + AppNavigationBar.current)
64
+ ) {
65
+ NavHost(
66
+ navController = navController,
67
+ startDestination = "option0"
68
+ ) {
69
+ items.forEachIndexed { index, item ->
70
+ composable(
71
+ route = "option$index",
72
+ enterTransition = {
73
+ fadeIn(animationSpec = tween(200)) +
74
+ scaleIn(
75
+ initialScale = 0.97f,
76
+ animationSpec = tween(200)
77
+ )
78
+ },
79
+ exitTransition = {
80
+ fadeOut(animationSpec = tween(200)) +
81
+ scaleOut(
82
+ targetScale = 0.97f,
83
+ animationSpec = tween(200)
84
+ )
85
+ },
86
+ popEnterTransition = {
87
+ fadeIn(animationSpec = tween(200)) +
88
+ scaleIn(
89
+ initialScale = 0.97f,
90
+ animationSpec = tween(200)
91
+ )
92
+ },
93
+ popExitTransition = {
94
+ fadeOut(animationSpec = tween(200)) +
95
+ scaleOut(
96
+ targetScale = 0.97f,
97
+ animationSpec = tween(200)
98
+ )
99
+ }
100
+ ) {
101
+ val option = getBottomTabOption(index)
102
+
103
+ StackScreen(
104
+ content = item.screen,
105
+ navigationOptions = option,
106
+ bottomTabIndex = index,
107
+ onBackHandler = {
108
+ if (index != 0) {
109
+ navController.popBackStack(
110
+ navController.graph.startDestinationRoute!!,
111
+ inclusive = false
112
+ )
113
+ } else {
114
+ navigator.pop()
115
+ }
116
+ }
117
+ )
118
+ }
119
+ }
120
+ }
121
+ }
122
+ Column {
123
+ BottomTabBar(
124
+ items = items,
125
+ floatingButton = floatingButton,
126
+ navController = navController,
127
+ onTabSelected = {
128
+ val currentRoute = navController.currentBackStackEntry?.destination?.route
129
+ val targetRoute = "option$it"
130
+ if (currentRoute != targetRoute){
131
+ navController.navigate(targetRoute)
132
+ }
133
+ }
134
+ )
135
+ Spacer(modifier = Modifier.fillMaxWidth().height(AppNavigationBar.current + Spacing.S).background(AppTheme.current.colors.background.surface))
136
+ }
137
+ }
138
+ }
139
+
140
+ data class BottomTabItem(
141
+ val name: String,
142
+ val label: String,
143
+ val icon: String,
144
+ val showDot: Boolean = false,
145
+ val badgeLabel: String? = null,
146
+ val screen: @Composable () -> Unit,
147
+ val options: NavigationOptions? = null,
148
+ val initialParams: Any? = null
149
+ )
150
+
151
+ data class BottomTabFloatingButton(
152
+ val icon: String,
153
+ val label: String,
154
+ val onPress: () -> Unit,
155
+ )