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