@momo-kits/native-kits 0.152.4-beta.3 → 0.152.5

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/package.json +7 -6
  2. package/shared/build.gradle.kts +74 -0
  3. package/CODE_OF_CONDUCT.md +0 -133
  4. package/CONTRIBUTING.md +0 -114
  5. package/LICENSE +0 -20
  6. package/README.md +0 -7
  7. package/build.gradle.kts +0 -32
  8. package/compose/MoMoComposeKits.podspec +0 -54
  9. package/compose/build.gradle.kts +0 -149
  10. package/compose/src/androidMain/AndroidManifest.xml +0 -2
  11. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +0 -105
  12. package/compose/src/commonMain/composeResources/files/lottie_circle_loader.json +0 -1
  13. package/compose/src/commonMain/composeResources/font/momosignature.otf +0 -0
  14. package/compose/src/commonMain/composeResources/font/momotrustdisplay.otf +0 -0
  15. package/compose/src/commonMain/composeResources/font/sfprotext_black.otf +0 -0
  16. package/compose/src/commonMain/composeResources/font/sfprotext_black.ttf +0 -0
  17. package/compose/src/commonMain/composeResources/font/sfprotext_bold.ttf +0 -0
  18. package/compose/src/commonMain/composeResources/font/sfprotext_heavy.ttf +0 -0
  19. package/compose/src/commonMain/composeResources/font/sfprotext_light.ttf +0 -0
  20. package/compose/src/commonMain/composeResources/font/sfprotext_medium.ttf +0 -0
  21. package/compose/src/commonMain/composeResources/font/sfprotext_regular.ttf +0 -0
  22. package/compose/src/commonMain/composeResources/font/sfprotext_semibold.ttf +0 -0
  23. package/compose/src/commonMain/composeResources/font/sfprotext_thin.otf +0 -0
  24. package/compose/src/commonMain/composeResources/font/sfprotext_thin.ttf +0 -0
  25. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.otf +0 -0
  26. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.ttf +0 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -57
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -201
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -222
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -48
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -306
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +0 -715
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -214
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -236
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +0 -77
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +0 -27
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +0 -334
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +0 -345
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -90
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +0 -131
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +0 -543
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +0 -23
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +0 -69
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +0 -143
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +0 -179
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +0 -111
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +0 -384
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +0 -160
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +0 -234
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +0 -223
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +0 -232
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +0 -236
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +0 -228
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +0 -364
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +0 -50
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +0 -34
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +0 -85
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +0 -33
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +0 -338
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +0 -95
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +0 -64
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +0 -89
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +0 -91
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +0 -86
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +0 -84
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +0 -208
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +0 -172
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +0 -199
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +0 -29
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +0 -237
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +0 -191
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +0 -306
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +0 -12
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +0 -13
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +0 -191
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +0 -258
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +0 -2
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +0 -35
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +0 -2
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +0 -59
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +0 -68
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +0 -11
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +0 -49
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +0 -51
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +0 -232
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +0 -111
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +0 -94
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +0 -159
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +0 -232
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScaleSizeScope.kt +0 -17
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +0 -459
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +0 -169
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +0 -216
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +0 -86
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +0 -180
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +0 -251
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +0 -80
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +0 -306
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +0 -31
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +0 -385
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +0 -38
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +0 -1329
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +0 -62
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +0 -88
  110. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +0 -144
  111. package/gradle.properties +0 -19
  112. package/gradlew +0 -240
  113. package/gradlew.bat +0 -91
  114. package/ios/Application/ApplicationEnvironment.swift +0 -50
  115. package/ios/Application/Components.swift +0 -263
  116. package/ios/Application/ComposeApi.swift +0 -22
  117. package/ios/Application/FloatingButton.swift +0 -172
  118. package/ios/Application/HeaderRight.swift +0 -271
  119. package/ios/Application/Screen.swift +0 -249
  120. package/ios/Badge/BadgeDot.swift +0 -31
  121. package/ios/Button/Button.swift +0 -211
  122. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +0 -126
  123. package/ios/Checkbox/Checkbox.swift +0 -81
  124. package/ios/Chip/Chip.swift +0 -96
  125. package/ios/Colors+Radius+Spacing/Colors.swift +0 -172
  126. package/ios/Colors+Radius+Spacing/Radius.swift +0 -22
  127. package/ios/Colors+Radius+Spacing/Spacing.swift +0 -12
  128. package/ios/Extensions/Color++.swift +0 -25
  129. package/ios/Icon/Icon.swift +0 -51
  130. package/ios/Image/Image.swift +0 -70
  131. package/ios/Input/Input.swift +0 -207
  132. package/ios/Input/InputPhoneNumber.swift +0 -176
  133. package/ios/Input/InputSearch.swift +0 -238
  134. package/ios/Input/InputTextArea.swift +0 -242
  135. package/ios/Lottie/LottieView.swift +0 -86
  136. package/ios/OTPKeyboard/KeyboardButton.swift +0 -41
  137. package/ios/OTPKeyboard/OTPKeyboard.swift +0 -145
  138. package/ios/Popup/PopupDisplay.swift +0 -284
  139. package/ios/Popup/PopupInput.swift +0 -96
  140. package/ios/Popup/PopupPromotion.swift +0 -73
  141. package/ios/PopupView/FullscreenPopup.swift +0 -251
  142. package/ios/PopupView/Modifiers.swift +0 -158
  143. package/ios/PopupView/PopupView.swift +0 -289
  144. package/ios/PopupView/Utils++.swift +0 -281
  145. package/ios/ScrollIndicator/ScrollIndicator.swift +0 -110
  146. package/ios/Swipeable/SwipeCell.swift +0 -278
  147. package/ios/Swipeable/SwipeCellModel.swift +0 -86
  148. package/ios/Switch/Switch.swift +0 -44
  149. package/ios/Template/Logo/Logo.swift +0 -75
  150. package/ios/Template/TrustBanner/TrustBanner.swift +0 -120
  151. package/ios/Theme.md +0 -18
  152. package/ios/Typography/Text.swift +0 -140
  153. package/ios/Typography/Typography.swift +0 -95
  154. package/ios/native-kits.podspec +0 -18
  155. package/settings.gradle.kts +0 -25
@@ -1,232 +0,0 @@
1
- package vn.momo.kits.navigation
2
-
3
- import androidx.compose.runtime.Composable
4
- import androidx.compose.runtime.MutableState
5
- import androidx.compose.runtime.mutableStateOf
6
- import androidx.compose.runtime.staticCompositionLocalOf
7
- import androidx.navigation.NavController
8
- import kotlinx.coroutines.CoroutineScope
9
- import kotlinx.coroutines.Dispatchers
10
- import kotlinx.coroutines.SupervisorJob
11
- import kotlinx.coroutines.cancel
12
- import kotlinx.coroutines.delay
13
- import kotlinx.coroutines.launch
14
- import kotlinx.serialization.Serializable
15
- import vn.momo.maxapi.IMaxApi
16
-
17
- class Navigator(
18
- private val navController: NavController,
19
- private val maxApi: IMaxApi?
20
- ) {
21
- private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
22
-
23
- fun push(content: @Composable () -> Unit, options: NavigationOptions? = null) {
24
- val route = DynamicScreenRegistry.register(content, options)
25
- navController.navigate(DynamicScreenRoute(route.id))
26
- }
27
-
28
- fun replace(content: @Composable () -> Unit, options: NavigationOptions? = null) {
29
- if (navController.previousBackStackEntry != null){
30
- val latestScreen = DynamicScreenRegistry.getLatestScreen()
31
- latestScreen?.let { DynamicScreenRegistry.unregisterScreen(it.id) }
32
- navController.popBackStack()
33
- push(content, options)
34
- }
35
- }
36
-
37
- fun pop(count: Int = 1, callBack: (() -> Unit)? = null) {
38
- scope.launch {
39
- repeat(count) {
40
- if (OverplayComponentRegistry.isOverplayShowing()){
41
- OverplayComponentRegistry.clear()
42
- delay(300L)
43
- OverplayComponentRegistry.hardClearAfterDismiss()
44
- } else {
45
- if (navController.previousBackStackEntry != null){
46
- navController.popBackStack()
47
- delay(300L)
48
- DynamicScreenRegistry.getLatestScreen()?.let { it1 ->
49
- DynamicScreenRegistry.unregisterScreen(it1.id)
50
- }
51
- } else {
52
- maxApi?.dismiss { }
53
- }
54
- }
55
- }
56
- callBack?.invoke()
57
- }
58
- }
59
-
60
- fun present(content: @Composable () -> Unit, options: NavigationOptions? = null) {
61
- val route = DynamicScreenRegistry.register(content, options)
62
- navController.navigate(DynamicDialogRoute(route.id))
63
- }
64
-
65
- fun reset(content: @Composable () -> Unit, options: NavigationOptions? = null) {
66
- DynamicScreenRegistry.unregisterAll()
67
-
68
- val route = DynamicScreenRegistry.register(content, options)
69
- navController.navigate(DynamicScreenRoute(route.id)) {
70
- popUpTo(0) { inclusive = true }
71
- }
72
- }
73
-
74
- fun showModal(
75
- content: @Composable () -> Unit,
76
- barrierDismissible: Boolean = true,
77
- onDismiss: (() -> Unit)? = null
78
- ){
79
- OverplayComponentRegistry.registerModal(content, false, barrierDismissible, onDismiss, false)
80
- }
81
-
82
- fun showBottomSheet(
83
- content: @Composable () -> Unit,
84
- isSurface: Boolean = false,
85
- barrierDismissible: Boolean = true,
86
- onDismiss: (() -> Unit)? = null,
87
- bottomSheetHeader: BottomHeader? = null
88
- ){
89
- OverplayComponentRegistry.registerModal(content, isSurface, barrierDismissible, onDismiss, true, bottomSheetHeader)
90
- }
91
-
92
- fun dispose(){
93
- scope.cancel()
94
- }
95
- }
96
-
97
- val LocalNavigator = staticCompositionLocalOf<Navigator> {
98
- error("No NavigationStack provided")
99
- }
100
-
101
- @Serializable
102
- data class DynamicScreenRoute(val id: Int)
103
-
104
- @Serializable
105
- data class DynamicDialogRoute(val id: Int)
106
-
107
- data class DynamicScreen(
108
- val id: Int,
109
- val content: @Composable () -> Unit,
110
- var options: NavigationOptions? = null
111
- )
112
-
113
- object DynamicScreenRegistry {
114
- private val screens = mutableMapOf<Int, DynamicScreen>()
115
- private var idCounter = 1
116
-
117
- fun register(content: @Composable () -> Unit, options: NavigationOptions?): DynamicScreenRoute {
118
- val id = idCounter++
119
- screens[id] = DynamicScreen(
120
- id = id,
121
- content = content,
122
- options = options
123
- )
124
- return DynamicScreenRoute(id)
125
- }
126
-
127
- fun unregisterScreen(id: Int) {
128
- screens.remove(id)
129
- }
130
-
131
- fun getLatestScreen(): DynamicScreen?{
132
- return screens.entries.lastOrNull()?.value
133
- }
134
-
135
- fun unregisterAll(){
136
- val ids = screens.keys.toList()
137
- for (id in ids) {
138
- unregisterScreen(id)
139
- }
140
- }
141
-
142
- fun getScreen(id: Int): DynamicScreen? = screens[id]
143
-
144
- fun setOptions(id: Int, options: NavigationOptions){
145
- screens[id]?.options = options
146
- }
147
- }
148
-
149
-
150
- sealed class OverplayComponentParams {
151
- class Modal(
152
- val onDismiss: (() -> Unit)? = null,
153
- val barrierDismissible: Boolean = true
154
- ) : OverplayComponentParams()
155
-
156
- class BottomSheet(
157
- val isSurface: Boolean = false,
158
- val onDismiss: (() -> Unit)? = null,
159
- val barrierDismissible: Boolean = true,
160
- val bottomSheetHeader: BottomHeader? = null,
161
- ) : OverplayComponentParams()
162
- }
163
-
164
- data class OverplayComponent(
165
- val content: @Composable () -> Unit,
166
- val params: OverplayComponentParams? = null
167
- )
168
-
169
- object OverplayComponentRegistry {
170
- private var currentOverlayComponent : MutableState<OverplayComponent?> = mutableStateOf(null)
171
- private var requestClose: (() -> Unit)? = null
172
- internal fun bindClose(handler: (() -> Unit)?) {
173
- requestClose = handler
174
- }
175
-
176
- fun registerModal(
177
- content: @Composable () -> Unit,
178
- isSurface: Boolean = false,
179
- barrierDismissible: Boolean = true,
180
- onDismiss: (() -> Unit)?,
181
- isBottomSheet: Boolean = false,
182
- bottomSheetHeader: BottomHeader? = null,
183
- ){
184
- val params = if (isBottomSheet) {
185
- OverplayComponentParams.BottomSheet(isSurface, onDismiss, barrierDismissible, bottomSheetHeader)
186
- } else {
187
- OverplayComponentParams.Modal(onDismiss, barrierDismissible)
188
- }
189
- currentOverlayComponent.value = OverplayComponent(
190
- content = content,
191
- params = params
192
- )
193
- }
194
-
195
- fun isOverplayShowing(): Boolean = currentOverlayComponent.value != null
196
-
197
- fun clear(){
198
- if (requestClose != null) {
199
- requestClose?.invoke()
200
- } else {
201
- currentOverlayComponent.value = null
202
- }
203
- }
204
-
205
- internal fun hardClearAfterDismiss() {
206
- currentOverlayComponent.value = null
207
- requestClose = null
208
- }
209
-
210
- @Composable
211
- fun OverlayComponent(){
212
- val overplay = currentOverlayComponent.value ?: return
213
-
214
- if (overplay.params is OverplayComponentParams.BottomSheet) {
215
- val param = overplay.params
216
- BottomSheet(
217
- content = overplay.content,
218
- header = param.bottomSheetHeader ?: BottomHeader.Title(),
219
- isSurface = param.isSurface,
220
- barrierDismissible = param.barrierDismissible,
221
- onDismiss = param.onDismiss
222
- )
223
- } else if (overplay.params is OverplayComponentParams.Modal) {
224
- val param = overplay.params
225
- ModalScreen(
226
- content = overplay.content,
227
- barrierDismissible = param.barrierDismissible,
228
- onDismiss = param.onDismiss
229
- )
230
- }
231
- }
232
- }
@@ -1,17 +0,0 @@
1
- package vn.momo.kits.navigation
2
-
3
- import androidx.compose.runtime.Composable
4
- import androidx.compose.runtime.CompositionLocalProvider
5
- import vn.momo.kits.const.ScaleSizeMaxRate
6
-
7
- @Composable
8
- fun ScaleSizeScope(
9
- scaleSizeMaxRate: Float? = null,
10
- content: @Composable () -> Unit
11
- ) {
12
- CompositionLocalProvider(
13
- ScaleSizeMaxRate provides scaleSizeMaxRate
14
- ) {
15
- content()
16
- }
17
- }
@@ -1,459 +0,0 @@
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.gestures.ScrollableState
8
- import androidx.compose.foundation.layout.Box
9
- import androidx.compose.foundation.layout.Column
10
- import androidx.compose.foundation.layout.ColumnScope
11
- import androidx.compose.foundation.layout.Spacer
12
- import androidx.compose.foundation.layout.WindowInsets
13
- import androidx.compose.foundation.layout.aspectRatio
14
- import androidx.compose.foundation.layout.fillMaxSize
15
- import androidx.compose.foundation.layout.fillMaxWidth
16
- import androidx.compose.foundation.layout.height
17
- import androidx.compose.foundation.layout.ime
18
- import androidx.compose.foundation.layout.imePadding
19
- import androidx.compose.foundation.layout.offset
20
- import androidx.compose.foundation.layout.padding
21
- import androidx.compose.foundation.lazy.LazyListState
22
- import androidx.compose.foundation.lazy.rememberLazyListState
23
- import androidx.compose.foundation.rememberScrollState
24
- import androidx.compose.foundation.verticalScroll
25
- import androidx.compose.material.ExperimentalMaterialApi
26
- import androidx.compose.runtime.Composable
27
- import androidx.compose.runtime.CompositionLocalProvider
28
- import androidx.compose.runtime.LaunchedEffect
29
- import androidx.compose.runtime.getValue
30
- import androidx.compose.runtime.mutableIntStateOf
31
- import androidx.compose.runtime.mutableStateOf
32
- import androidx.compose.runtime.remember
33
- import androidx.compose.runtime.snapshotFlow
34
- import androidx.compose.runtime.staticCompositionLocalOf
35
- import androidx.compose.ui.Alignment
36
- import androidx.compose.ui.Modifier
37
- import androidx.compose.ui.graphics.Brush
38
- import androidx.compose.ui.graphics.Color
39
- import androidx.compose.ui.graphics.lerp
40
- import androidx.compose.ui.layout.onGloballyPositioned
41
- import androidx.compose.ui.platform.LocalDensity
42
- import androidx.compose.ui.unit.Dp
43
- import androidx.compose.ui.unit.dp
44
- import androidx.compose.ui.unit.min
45
- import androidx.compose.ui.zIndex
46
- import vn.momo.kits.components.InputSearch
47
- import vn.momo.kits.const.AppNavigationBar
48
- import vn.momo.kits.const.AppStatusBar
49
- import vn.momo.kits.const.AppTheme
50
- import vn.momo.kits.const.Spacing
51
- import vn.momo.kits.modifier.conditional
52
- import vn.momo.kits.modifier.hideKeyboardOnTap
53
- import vn.momo.kits.navigation.component.FABPosition
54
- import vn.momo.kits.navigation.component.FloatingButton
55
- import vn.momo.kits.navigation.component.HEADER_HEIGHT
56
- import vn.momo.kits.navigation.component.Header
57
- import vn.momo.kits.navigation.component.HeaderBackground
58
- import vn.momo.kits.navigation.component.HeaderRight
59
- import vn.momo.kits.navigation.component.HeaderType
60
- import vn.momo.kits.navigation.component.InputSearchType
61
- import vn.momo.kits.platform.BackHandler
62
- import vn.momo.kits.platform.getAndroidBuildVersion
63
-
64
- internal val LocalFooterHeightPx = staticCompositionLocalOf { mutableIntStateOf(0) }
65
- internal val LocalHeaderRightWidthPx = staticCompositionLocalOf { mutableIntStateOf(0) }
66
-
67
- @OptIn(ExperimentalMaterialApi::class)
68
- @Composable
69
- internal fun StackScreen(
70
- content: @Composable () -> Unit,
71
- navigationOptions: NavigationOptions? = null,
72
- id: Int = -1,
73
- bottomTabIndex: Int = -1,
74
- ) {
75
- val navigator = LocalNavigator.current
76
- val statusBar = AppStatusBar.current
77
- val density = LocalDensity.current
78
- val navigation = remember { Navigation(id = id, bottomTabIndex = bottomTabIndex, initOptions = navigationOptions) }
79
-
80
- val options by navigation.currentOptions
81
-
82
- val limit = with(density) {
83
- (statusBar).toPx() + HEADER_HEIGHT
84
- }.toInt()
85
-
86
- val (scrollState, scrollInProcess) = if (options.scrollData.scrollState is LazyListState)
87
- (options.scrollData.scrollState as? LazyListState ?: rememberLazyListState()).proxyScrollState(limit, 15)
88
- else
89
- (options.scrollData.scrollState as? ScrollState ?: rememberScrollState()).proxyLimitedScrollState(limit, 15)
90
-
91
- val footerHeightPx = remember { mutableIntStateOf(0) }
92
- val headerRightWidthPx = remember { mutableIntStateOf(0) }
93
-
94
- BackHandler(true) { navigator.pop() }
95
-
96
- CompositionLocalProvider(
97
- StackScreenScrollableState provides options.scrollData.scrollState,
98
- LocalNavigation provides navigation,
99
- LocalScrollState provides scrollState,
100
- LocalOptions provides options,
101
- LocalFooterHeightPx provides footerHeightPx,
102
- LocalHeaderRightWidthPx provides headerRightWidthPx
103
- ) {
104
- Box(Modifier
105
- .fillMaxSize()
106
- .background(options.backgroundColor ?: AppTheme.current.colors.background.default)
107
- .conditional(options.keyboardOptions.keyboardShouldPersistTaps) {
108
- hideKeyboardOnTap()
109
- }
110
- .conditional(options.keyboardOptions.useAvoidKeyboard && getAndroidBuildVersion() > 29) {
111
- imePadding()
112
- }
113
- ) {
114
- Box(Modifier.zIndex(1f)) {
115
- HeaderBackground()
116
- }
117
-
118
- Box(Modifier.zIndex(4f)) {
119
- Header()
120
- }
121
-
122
- Column (Modifier.zIndex(5f)) {
123
- SearchAnimated(isScrollInProgress = scrollInProcess)
124
- }
125
-
126
- Column(Modifier.zIndex(2f).fillMaxSize()) {
127
- MainContent(content = content)
128
- }
129
-
130
- Box(Modifier.zIndex(5f)){
131
- FloatingContent()
132
- }
133
-
134
- Box(Modifier.zIndex(6f).fillMaxSize()){
135
- if (bottomTabIndex != -1) return@Box
136
- OverplayComponentRegistry.OverlayComponent()
137
- }
138
- }
139
- }
140
- }
141
-
142
- @Composable
143
- fun FloatingContent(){
144
- val options = LocalOptions.current
145
- val density = LocalDensity.current
146
- val footerHeightPx = LocalFooterHeightPx.current
147
- val scrollState = LocalScrollState.current
148
-
149
- val fabProps = options.floatingButtonProps ?: return
150
- val bottomPadding = fabProps.bottom ?:
151
- (Spacing.M + if (options.footerComponent != null) with(density){ footerHeightPx.intValue.toDp() } else 0.dp)
152
-
153
- FloatingButton(
154
- scrollPosition = fabProps.scrollState?.value ?: scrollState.value,
155
- onClick = fabProps.onClick,
156
- containerColor = AppTheme.current.colors.primary,
157
- bottom = bottomPadding,
158
- icon = fabProps.icon,
159
- iconColor = fabProps.iconColor,
160
- text = fabProps.label,
161
- size = fabProps.size,
162
- position = fabProps.position ?: FABPosition.END,
163
- )
164
- }
165
-
166
- @Composable
167
- fun ColumnScope.MainContent(content: @Composable ()-> Unit){
168
- val options = LocalOptions.current
169
- val inputSearchType = getInputSearchType(options)
170
- val density = LocalDensity.current
171
- val scrollState = LocalScrollState.current
172
-
173
- Spacer(Modifier.height(if (options.headerType is HeaderType.DefaultOrExtended) AppStatusBar.current + HEADER_HEIGHT.dp else 0.dp))
174
- if (inputSearchType == InputSearchType.Animated){
175
- val scrollDp = with(density) { scrollState.value.toDp() }
176
-
177
- val animatedTopPadding by animateDpAsState(
178
- targetValue = (HEADER_HEIGHT.dp - scrollDp).coerceIn(0.dp, HEADER_HEIGHT.dp),
179
- label = "AnimatedTopPadding"
180
- )
181
- Spacer(Modifier.height(animatedTopPadding))
182
- }
183
- Column (Modifier
184
- .fillMaxWidth()
185
- .weight(1f)
186
- .conditional(options.scrollData.scrollable && options.scrollData.scrollState is ScrollState) {
187
- verticalScroll(scrollState)
188
- }
189
- ) {
190
- ScreenContent(content = content)
191
- }
192
-
193
- if (options.footerComponent != null){
194
- val isKeyboardVisible = isKeyboardVisible()
195
- val bottomPadding = min(AppNavigationBar.current, if (isKeyboardVisible) 0.dp else 21.dp)
196
- Footer(footerComponent = options.footerComponent, bottomPadding = bottomPadding)
197
- }
198
- }
199
-
200
- @Composable
201
- fun ScreenContent(content: @Composable () -> Unit){
202
- val scrollState = LocalScrollState.current
203
- val options = LocalOptions.current
204
-
205
- if (options.headerType is HeaderType.Animated){
206
- val animatedHeader = options.headerType
207
- Box {
208
- Box(Modifier.fillMaxWidth().aspectRatio(animatedHeader.aspectRatio.value)){
209
- animatedHeader.composable.invoke(scrollState.value)
210
- }
211
- Box(Modifier.offset(x = 0.dp, y = AppStatusBar.current + HEADER_HEIGHT.dp + animatedHeader.layoutOffSet)){
212
- content()
213
- }
214
- }
215
- } else {
216
- content()
217
- }
218
- }
219
-
220
- @Composable
221
- fun Footer(footerComponent: @Composable (() -> Unit)?, bottomPadding: Dp) {
222
- if (footerComponent == null) return
223
-
224
- val footerHeightPx = LocalFooterHeightPx.current
225
-
226
- val shadowBrush = remember {
227
- Brush.verticalGradient(
228
- colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.05f))
229
- )
230
- }
231
-
232
- Box(Modifier.onGloballyPositioned {
233
- if (footerHeightPx.intValue != it.size.height) footerHeightPx.intValue = it.size.height
234
- }) {
235
- Box(Modifier
236
- .fillMaxWidth()
237
- .background(AppTheme.current.colors.background.surface)
238
- .padding(top = Spacing.S, start = Spacing.M, end = Spacing.M, bottom = bottomPadding + Spacing.S)
239
- ){
240
- footerComponent.invoke()
241
- }
242
-
243
- Box(modifier = Modifier
244
- .fillMaxWidth()
245
- .height(6.dp)
246
- .offset(x = 0.dp, y = (-6).dp)
247
- .background(shadowBrush)
248
- )
249
- }
250
- }
251
-
252
- data class InputSearchLayoutParams(
253
- val topPadding: Dp,
254
- val startPadding: Dp,
255
- val endPadding: Dp
256
- )
257
- @Composable
258
- fun SearchAnimated(
259
- isScrollInProgress: Boolean
260
- ) {
261
- val scrollState = LocalScrollState.current
262
- val options = LocalOptions.current
263
- val navigator = LocalNavigator.current
264
- val density = LocalDensity.current
265
-
266
- val scrollDp = with(density) { scrollState.value.toDp() }
267
-
268
- val inputSearchType = getInputSearchType(options)
269
- val headerRightWidthPx = LocalHeaderRightWidthPx.current
270
- val headerRightWidthDp = with(density) { headerRightWidthPx.intValue.toDp() }
271
-
272
- if (inputSearchType == InputSearchType.None) return
273
- val inputSearchProps = (options.headerType as? HeaderType.DefaultOrExtended)?.inputSearchProps ?: return
274
-
275
- val minTopPadding = AppStatusBar.current
276
- val maxTopPadding = AppStatusBar.current + HEADER_HEIGHT.dp
277
- val minStartPadding = Spacing.M
278
- val maxStartPadding = if (options.hiddenBack) Spacing.M else 52.dp
279
- val minEndPadding = Spacing.M
280
- val maxEndPadding = headerRightWidthDp + if (options.headerRight is HeaderRight.None) Spacing.M else Spacing.M * 2
281
-
282
- val targetColor = lerp(
283
- AppTheme.current.colors.background.surface,
284
- AppTheme.current.colors.background.default,
285
- (scrollDp / minTopPadding).coerceIn(0f, 1f)
286
- )
287
- val animatedColor by animateColorAsState(targetValue = targetColor, label = "BackgroundColor")
288
-
289
- val layoutParams = when (inputSearchType) {
290
- InputSearchType.Header -> {
291
- InputSearchLayoutParams(
292
- topPadding = minTopPadding,
293
- startPadding = maxStartPadding,
294
- endPadding = maxEndPadding
295
- )
296
- }
297
-
298
- InputSearchType.Animated -> {
299
- val animatedTopPadding by animateDpAsState(
300
- targetValue = (maxTopPadding - scrollDp).coerceIn(minTopPadding, maxTopPadding),
301
- label = "AnimatedTopPadding"
302
- )
303
-
304
- val animatedStartPadding by animateDpAsState(
305
- targetValue = (minStartPadding + scrollDp).coerceIn(minStartPadding, maxStartPadding),
306
- label = "AnimatedStartPadding"
307
- )
308
-
309
- val animatedEndPadding by animateDpAsState(
310
- targetValue = (minEndPadding + scrollDp).coerceIn(minEndPadding, maxEndPadding),
311
- label = "AnimatedEndPadding"
312
- )
313
-
314
- val maxPadding = remember(maxTopPadding, maxStartPadding, maxEndPadding) {
315
- maxOf(maxEndPadding, maxStartPadding, maxTopPadding)
316
- }
317
- val snapScrollValue = with(density) { maxPadding.toPx().toInt() }
318
-
319
- LaunchedEffect(isScrollInProgress && scrollState.value != 0 && scrollState.value != snapScrollValue) {
320
- if (scrollDp < maxPadding) {
321
- if (!isScrollInProgress) {
322
- val midpoint = (maxTopPadding - minTopPadding) / 2
323
-
324
- if (scrollDp < midpoint) {
325
- scrollState.animateScrollTo(0)
326
- } else {
327
- scrollState.animateScrollTo(snapScrollValue)
328
- }
329
- }
330
- }
331
- }
332
-
333
- InputSearchLayoutParams(
334
- topPadding = animatedTopPadding,
335
- startPadding = animatedStartPadding,
336
- endPadding = animatedEndPadding
337
- )
338
- }
339
-
340
- InputSearchType.None -> return
341
- }
342
-
343
- Spacer(Modifier.height(layoutParams.topPadding))
344
- Box(
345
- modifier = Modifier
346
- .height(HEADER_HEIGHT.dp)
347
- .fillMaxWidth()
348
- .padding(
349
- start = layoutParams.startPadding,
350
- end = layoutParams.endPadding
351
- ),
352
- contentAlignment = Alignment.Center
353
- ) {
354
- InputSearch(
355
- inputSearchProps = inputSearchProps.copy(
356
- backgroundColor = animatedColor,
357
- onPressButtonText = {
358
- navigator.pop()
359
- }
360
- )
361
- )
362
- }
363
- }
364
-
365
- @Composable
366
- internal fun isKeyboardVisible(): Boolean {
367
- val ime = WindowInsets.ime
368
- val density = LocalDensity.current
369
- return ime.getBottom(density) > 0
370
- }
371
- private fun quantize(value: Int, stepPx: Int): Int {
372
- if (stepPx <= 1) return value
373
- return (value / stepPx) * stepPx
374
- }
375
-
376
- @Composable
377
- fun LazyListState.proxyScrollState(
378
- thresholdPx: Int = 200,
379
- stepPx: Int = 10
380
- ): Pair<ScrollState, Boolean> {
381
- val scrollState = rememberScrollState()
382
- val locked = remember { mutableStateOf(false) }
383
-
384
- LaunchedEffect(this, scrollState, thresholdPx, stepPx) {
385
- snapshotFlow { firstVisibleItemIndex to firstVisibleItemScrollOffset }
386
- .collect { (index, offset) ->
387
- val rawTarget = if (index == 0) offset else SCROLLED_PAST_FIRST_ITEM_THRESHOLD
388
- val shouldLock = rawTarget > thresholdPx
389
- if (locked.value != shouldLock) locked.value = shouldLock
390
-
391
- val desired = if (shouldLock) thresholdPx else quantize(rawTarget, stepPx)
392
-
393
- if (scrollState.value != desired) {
394
- scrollState.scrollTo(desired.coerceAtLeast(0))
395
- }
396
- }
397
- }
398
-
399
- val inProgress = remember { mutableStateOf(false) }
400
- LaunchedEffect(this, thresholdPx) {
401
- snapshotFlow { isScrollInProgress }
402
- .collect { progressing ->
403
- inProgress.value = if (locked.value) false else progressing
404
- }
405
- }
406
-
407
- return scrollState to inProgress.value
408
- }
409
-
410
- @Composable
411
- fun ScrollState.proxyLimitedScrollState(
412
- thresholdPx: Int = 200,
413
- stepPx: Int = 10
414
- ): Pair<ScrollState, Boolean> {
415
- val proxy = rememberScrollState()
416
- val locked = remember { mutableStateOf(false) }
417
-
418
- LaunchedEffect(this, proxy, thresholdPx, stepPx) {
419
- snapshotFlow { value }
420
- .collect { rawTarget ->
421
- val shouldLock = rawTarget > thresholdPx
422
- if (locked.value != shouldLock) locked.value = shouldLock
423
-
424
- val desired = if (shouldLock) thresholdPx else quantize(rawTarget, stepPx)
425
-
426
- if (proxy.value != desired) {
427
- proxy.scrollTo(desired.coerceAtLeast(0))
428
- }
429
- }
430
- }
431
-
432
- val inProgress = remember { mutableStateOf(false) }
433
- LaunchedEffect(this, thresholdPx) {
434
- snapshotFlow { isScrollInProgress }
435
- .collect { progressing ->
436
- inProgress.value = if (locked.value) false else progressing
437
- }
438
- }
439
-
440
- return proxy to inProgress.value
441
- }
442
-
443
- private const val SCROLLED_PAST_FIRST_ITEM_THRESHOLD = 10_000
444
-
445
- internal val LocalScrollState = staticCompositionLocalOf<ScrollState> { error("No Scroll State provided") }
446
- internal val LocalOptions = staticCompositionLocalOf<NavigationOptions> { error("No NavigationOptions provided") }
447
-
448
- val StackScreenScrollableState = staticCompositionLocalOf<ScrollableState?> { null }
449
-
450
- internal fun getInputSearchType(options: NavigationOptions): InputSearchType{
451
- return when (val headerType = options.headerType) {
452
- is HeaderType.DefaultOrExtended -> when {
453
- headerType.inputSearchProps == null -> InputSearchType.None
454
- headerType.useAnimated -> InputSearchType.Animated
455
- else -> InputSearchType.Header
456
- }
457
- else -> InputSearchType.None
458
- }
459
- }