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