@momo-kits/native-kits 0.152.4-beta.6 → 0.152.4-beta.7

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 (156) hide show
  1. package/CODE_OF_CONDUCT.md +133 -0
  2. package/CONTRIBUTING.md +114 -0
  3. package/LICENSE +20 -0
  4. package/README.md +7 -0
  5. package/build.gradle.kts +32 -0
  6. package/compose/MoMoComposeKits.podspec +54 -0
  7. package/compose/build.gradle.kts +149 -0
  8. package/compose/src/androidMain/AndroidManifest.xml +2 -0
  9. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +105 -0
  10. package/compose/src/commonMain/composeResources/files/lottie_circle_loader.json +1 -0
  11. package/compose/src/commonMain/composeResources/font/momosignature.otf +0 -0
  12. package/compose/src/commonMain/composeResources/font/momotrustdisplay.otf +0 -0
  13. package/compose/src/commonMain/composeResources/font/sfprotext_black.otf +0 -0
  14. package/compose/src/commonMain/composeResources/font/sfprotext_black.ttf +0 -0
  15. package/compose/src/commonMain/composeResources/font/sfprotext_bold.ttf +0 -0
  16. package/compose/src/commonMain/composeResources/font/sfprotext_heavy.ttf +0 -0
  17. package/compose/src/commonMain/composeResources/font/sfprotext_light.ttf +0 -0
  18. package/compose/src/commonMain/composeResources/font/sfprotext_medium.ttf +0 -0
  19. package/compose/src/commonMain/composeResources/font/sfprotext_regular.ttf +0 -0
  20. package/compose/src/commonMain/composeResources/font/sfprotext_semibold.ttf +0 -0
  21. package/compose/src/commonMain/composeResources/font/sfprotext_thin.otf +0 -0
  22. package/compose/src/commonMain/composeResources/font/sfprotext_thin.ttf +0 -0
  23. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.otf +0 -0
  24. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.ttf +0 -0
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +57 -0
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +201 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +222 -0
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +48 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +86 -0
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +76 -0
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +76 -0
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +306 -0
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +33 -0
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +715 -0
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +214 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +392 -0
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +69 -0
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +77 -0
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +27 -0
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +334 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +345 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +90 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +131 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +543 -0
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +23 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +58 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +143 -0
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +179 -0
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +111 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +384 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +160 -0
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +234 -0
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +223 -0
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +232 -0
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +236 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +228 -0
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +364 -0
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +50 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +34 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +85 -0
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +33 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +338 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +95 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +64 -0
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +89 -0
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +91 -0
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +86 -0
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +84 -0
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +208 -0
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +172 -0
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +199 -0
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +237 -0
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +191 -0
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +306 -0
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +13 -0
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +191 -0
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +258 -0
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +2 -0
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +35 -0
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +2 -0
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +59 -0
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +68 -0
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +49 -0
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +232 -0
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +111 -0
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +94 -0
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +159 -0
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +302 -0
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScaleSizeScope.kt +17 -0
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +484 -0
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +169 -0
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +216 -0
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +180 -0
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +251 -0
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +80 -0
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +306 -0
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +31 -0
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +385 -0
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +123 -0
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +38 -0
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +62 -0
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +88 -0
  109. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +144 -0
  110. package/gradle.properties +19 -0
  111. package/gradlew +240 -0
  112. package/gradlew.bat +91 -0
  113. package/ios/Application/ApplicationEnvironment.swift +50 -0
  114. package/ios/Application/Components.swift +263 -0
  115. package/ios/Application/ComposeApi.swift +22 -0
  116. package/ios/Application/FloatingButton.swift +172 -0
  117. package/ios/Application/HeaderRight.swift +271 -0
  118. package/ios/Application/Screen.swift +249 -0
  119. package/ios/Badge/BadgeDot.swift +31 -0
  120. package/ios/Button/Button.swift +211 -0
  121. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +126 -0
  122. package/ios/Checkbox/Checkbox.swift +81 -0
  123. package/ios/Chip/Chip.swift +96 -0
  124. package/ios/Colors+Radius+Spacing/Colors.swift +172 -0
  125. package/ios/Colors+Radius+Spacing/Radius.swift +22 -0
  126. package/ios/Colors+Radius+Spacing/Spacing.swift +12 -0
  127. package/ios/Extensions/Color++.swift +25 -0
  128. package/ios/Icon/Icon.swift +51 -0
  129. package/ios/Image/Image.swift +70 -0
  130. package/ios/Input/Input.swift +207 -0
  131. package/ios/Input/InputPhoneNumber.swift +176 -0
  132. package/ios/Input/InputSearch.swift +238 -0
  133. package/ios/Input/InputTextArea.swift +242 -0
  134. package/ios/Lottie/LottieView.swift +86 -0
  135. package/ios/OTPKeyboard/KeyboardButton.swift +41 -0
  136. package/ios/OTPKeyboard/OTPKeyboard.swift +145 -0
  137. package/ios/Popup/PopupDisplay.swift +284 -0
  138. package/ios/Popup/PopupInput.swift +96 -0
  139. package/ios/Popup/PopupPromotion.swift +73 -0
  140. package/ios/PopupView/FullscreenPopup.swift +251 -0
  141. package/ios/PopupView/Modifiers.swift +158 -0
  142. package/ios/PopupView/PopupView.swift +289 -0
  143. package/ios/PopupView/Utils++.swift +281 -0
  144. package/ios/ScrollIndicator/ScrollIndicator.swift +110 -0
  145. package/ios/Swipeable/SwipeCell.swift +278 -0
  146. package/ios/Swipeable/SwipeCellModel.swift +86 -0
  147. package/ios/Switch/Switch.swift +44 -0
  148. package/ios/Template/Logo/Logo.swift +75 -0
  149. package/ios/Template/TrustBanner/TrustBanner.swift +120 -0
  150. package/ios/Theme.md +18 -0
  151. package/ios/Typography/Text.swift +140 -0
  152. package/ios/Typography/Typography.swift +95 -0
  153. package/ios/native-kits.podspec +18 -0
  154. package/package.json +6 -7
  155. package/settings.gradle.kts +25 -0
  156. package/shared/build.gradle.kts +0 -74
@@ -0,0 +1,214 @@
1
+ package vn.momo.kits.application
2
+
3
+ import androidx.compose.runtime.Composable
4
+ import androidx.compose.runtime.CompositionLocalProvider
5
+ import androidx.compose.runtime.Immutable
6
+ import androidx.compose.runtime.LaunchedEffect
7
+ import androidx.compose.runtime.getValue
8
+ import androidx.compose.runtime.mutableStateOf
9
+ import androidx.compose.runtime.remember
10
+ import androidx.compose.runtime.setValue
11
+ import androidx.compose.runtime.staticCompositionLocalOf
12
+ import androidx.compose.ui.unit.Dp
13
+ import vn.momo.kits.components.TrustBannerData
14
+ import vn.momo.kits.const.AppStatusBar
15
+ import vn.momo.kits.const.AppTheme
16
+ import vn.momo.kits.const.Theme
17
+ import vn.momo.kits.const.ThemeAssets
18
+ import vn.momo.kits.const.defaultTheme
19
+ import vn.momo.kits.platform.getStatusBarHeight
20
+
21
+ @Deprecated("Use IMaxApi instead", ReplaceWith("IMaxApi"))
22
+ interface ComposeApi {
23
+ fun request(funcName: String, params: Any?): String
24
+ fun request(funcName: String, params: Any?, onResponse: ((String) -> Unit)?): String
25
+ fun requestCallback(funcName: String, params: Any?, onResponse: ((String) -> Unit)?)
26
+ fun removeCallback(id: String)
27
+ }
28
+
29
+ class Navigator {
30
+ fun push(content: @Composable () -> Unit) {
31
+ //implement
32
+ }
33
+
34
+ fun present(content: @Composable () -> Unit) {
35
+ //implement
36
+ }
37
+
38
+ fun reset(content: @Composable () -> Unit) {
39
+ //implement
40
+ }
41
+
42
+ fun pop() {
43
+ //implement
44
+ }
45
+
46
+ fun replace(content: @Composable () -> Unit) {
47
+ //implement
48
+ }
49
+
50
+ fun popToTop() {
51
+ //implement
52
+ }
53
+
54
+ fun showModal(
55
+ onClose: () -> Unit = {},
56
+ canBackgroundClose: Boolean = true,
57
+ content: @Composable () -> Unit,
58
+ ) {
59
+ //implement
60
+ }
61
+
62
+ fun showBottomSheet(content: @Composable () -> Unit) {
63
+ //implement
64
+ }
65
+ }
66
+
67
+ @Immutable
68
+ data class MiniAppContext(
69
+ val appIcon: String = "",
70
+ val appName: Any? = null,
71
+ val appId: String = "",
72
+ val appCode: String = "",
73
+ val description: Any? = null,
74
+ val support: Map<String, Any?> = emptyMap(),
75
+ val toolkitConfig: Map<String, Any?> = emptyMap(),
76
+ val providerId: String = "",
77
+ val permissions: List<Map<String, Any>>? = emptyList()
78
+ ) {
79
+ companion object {
80
+ private const val KEY_ICON = "icon"
81
+ private const val KEY_NAME = "name"
82
+ private const val KEY_APP_ID = "appId"
83
+ private const val KEY_CODE = "code"
84
+ private const val KEY_DESCRIPTION = "description"
85
+ private const val KEY_SUPPORT = "support"
86
+ private const val KEY_TOOLKIT_CFG = "toolkitConfig"
87
+ private const val KEY_PERMISSIONS = "permissions"
88
+ private const val KEY_ORIGIN_APP = "originAppId"
89
+
90
+ fun toMap(context: MiniAppContext?): Map<String, Any?> = mapOf(
91
+ "icon" to (context?.appIcon ?: ""),
92
+ "name" to context?.appName,
93
+ "appId" to (context?.appId ?: ""),
94
+ "code" to (context?.appCode ?: ""),
95
+ "description" to (context?.description ?: ""),
96
+ "support" to (context?.support ?: emptyMap<String, Any?>()),
97
+ "toolkitConfig" to (context?.toolkitConfig ?: emptyMap<String, Any?>()),
98
+ "providerId" to (context?.providerId ?: ""),
99
+ "permissions" to (context?.permissions ?: emptyList<Map<String, Any>>())
100
+ )
101
+
102
+ fun fromMap(data: Map<String, Any?>?): MiniAppContext = MiniAppContext(
103
+ appIcon = data.string(KEY_ICON) ?: "",
104
+ appName = data?.get(KEY_NAME),
105
+ appId = data.string(KEY_APP_ID) ?: "",
106
+ appCode = data.string(KEY_CODE) ?: "",
107
+ description = data?.get(KEY_DESCRIPTION),
108
+ support = data.mapStringAny(KEY_SUPPORT),
109
+ toolkitConfig = data.mapStringAny(KEY_TOOLKIT_CFG),
110
+ providerId = computeProviderId(
111
+ originAppId = data.string(KEY_ORIGIN_APP),
112
+ appId = data.string(KEY_APP_ID)
113
+ ),
114
+ permissions = data.listOfMapStringAny(KEY_PERMISSIONS)
115
+ )
116
+
117
+ private fun computeProviderId(originAppId: String?, appId: String?): String {
118
+ val id = when {
119
+ !originAppId.isNullOrBlank() -> originAppId
120
+ !appId.isNullOrBlank() -> appId
121
+ else -> null
122
+ } ?: return "unknown"
123
+
124
+ val parts = id.split('.')
125
+ return parts.getOrNull(1) ?: "unknown"
126
+ }
127
+ }
128
+ }
129
+
130
+ private fun Map<String, Any?>?.string(key: String): String? =
131
+ (this?.get(key) as? String)
132
+
133
+ @Suppress("UNCHECKED_CAST")
134
+ private fun Map<String, Any?>?.mapStringAny(key: String): Map<String, Any?> =
135
+ (this?.get(key) as? Map<String, Any?>) ?: emptyMap()
136
+
137
+ @Suppress("UNCHECKED_CAST")
138
+ private fun Map<String, Any?>?.listOfMapStringAny(key: String): List<Map<String, Any>> =
139
+ (this?.get(key) as? List<Map<String, Any>>) ?: emptyList()
140
+
141
+ @Deprecated("Use LocalApi instead", ReplaceWith("LocalApi"))
142
+ val PlatformApi = staticCompositionLocalOf<Any?> { null }
143
+
144
+ @Deprecated("Use LocalNavigator instead", ReplaceWith("LocalNavigator"))
145
+ val AppNavigator = staticCompositionLocalOf<Navigator?> {
146
+ null
147
+ }
148
+
149
+ val ApplicationContext = staticCompositionLocalOf<MiniAppContext?> {
150
+ null
151
+ }
152
+
153
+ val AppConfig = staticCompositionLocalOf<KitConfig?> {
154
+ null
155
+ }
156
+
157
+ val AppLanguage = staticCompositionLocalOf<String?> {
158
+ null
159
+ }
160
+
161
+ @Immutable
162
+ data class KitConfig(
163
+ val trustBanner: TrustBannerData? = null,
164
+ val headerBar: String? = null,
165
+ val headerGradient: String? = null,
166
+ )
167
+
168
+ internal var DesignSystemWhiteList: Boolean? = null
169
+
170
+ @Deprecated("Use NavigationContainer instead", ReplaceWith("NavigationContainer"))
171
+ @Composable
172
+ fun ApplicationContainer(
173
+ theme: Theme = defaultTheme,
174
+ composeApi: ComposeApi? = null,
175
+ statusBarHeight: Dp? = AppStatusBar.current,
176
+ applicationContext: MiniAppContext? = null,
177
+ config: KitConfig? = null,
178
+ language: String? = null,
179
+ isWhiteList: Boolean = false,
180
+ content: @Composable () -> Unit,
181
+ ) {
182
+ var appTheme by remember { mutableStateOf(theme) }
183
+
184
+ LaunchedEffect(Unit) {
185
+ DesignSystemWhiteList = isWhiteList
186
+ try {
187
+ val headerBar = config?.headerBar
188
+ if (headerBar != null && appTheme.assets.headerBackground == null) {
189
+ appTheme = appTheme.copy(
190
+ assets = ThemeAssets(
191
+ headerBackground = headerBar
192
+ )
193
+ )
194
+ }
195
+ } catch (e: Exception) {
196
+ print("@@ == NavigationContainer get config error $e")
197
+ }
198
+ }
199
+
200
+ val appStatusBarHeight = statusBarHeight ?: getStatusBarHeight()
201
+
202
+ CompositionLocalProvider(
203
+ AppTheme provides appTheme,
204
+ PlatformApi provides composeApi,
205
+ AppNavigator provides Navigator(),
206
+ AppStatusBar provides appStatusBarHeight,
207
+ ApplicationContext provides applicationContext,
208
+ AppConfig provides config,
209
+ AppLanguage provides language,
210
+ ) {
211
+ content()
212
+ }
213
+
214
+ }
@@ -0,0 +1,392 @@
1
+ package vn.momo.kits.application
2
+
3
+ import androidx.compose.animation.core.Animatable
4
+ import androidx.compose.animation.core.animateFloatAsState
5
+ import androidx.compose.animation.core.tween
6
+ import androidx.compose.foundation.ScrollState
7
+ import androidx.compose.foundation.background
8
+ import androidx.compose.foundation.gestures.detectTapGestures
9
+ import androidx.compose.foundation.layout.Arrangement
10
+ import androidx.compose.foundation.layout.Box
11
+ import androidx.compose.foundation.layout.Column
12
+ import androidx.compose.foundation.layout.Spacer
13
+ import androidx.compose.foundation.layout.WindowInsets
14
+ import androidx.compose.foundation.layout.asPaddingValues
15
+ import androidx.compose.foundation.layout.aspectRatio
16
+ import androidx.compose.foundation.layout.fillMaxSize
17
+ import androidx.compose.foundation.layout.fillMaxWidth
18
+ import androidx.compose.foundation.layout.height
19
+ import androidx.compose.foundation.layout.ime
20
+ import androidx.compose.foundation.layout.imePadding
21
+ import androidx.compose.foundation.layout.offset
22
+ import androidx.compose.foundation.layout.padding
23
+ import androidx.compose.foundation.layout.systemBars
24
+ import androidx.compose.foundation.rememberScrollState
25
+ import androidx.compose.foundation.verticalScroll
26
+ import androidx.compose.runtime.Composable
27
+ import androidx.compose.runtime.CompositionLocalProvider
28
+ import androidx.compose.runtime.DisposableEffect
29
+ import androidx.compose.runtime.LaunchedEffect
30
+ import androidx.compose.runtime.getValue
31
+ import androidx.compose.runtime.mutableStateOf
32
+ import androidx.compose.runtime.remember
33
+ import androidx.compose.runtime.setValue
34
+ import androidx.compose.runtime.staticCompositionLocalOf
35
+ import androidx.compose.ui.Alignment
36
+ import androidx.compose.ui.Modifier
37
+ import androidx.compose.ui.graphics.Color
38
+ import androidx.compose.ui.input.pointer.pointerInput
39
+ import androidx.compose.ui.layout.LayoutCoordinates
40
+ import androidx.compose.ui.layout.onGloballyPositioned
41
+ import androidx.compose.ui.platform.LocalDensity
42
+ import androidx.compose.ui.platform.LocalSoftwareKeyboardController
43
+ import androidx.compose.ui.unit.Dp
44
+ import androidx.compose.ui.unit.IntOffset
45
+ import androidx.compose.ui.unit.dp
46
+ import androidx.compose.ui.unit.min
47
+ import androidx.compose.ui.zIndex
48
+ import kotlinx.coroutines.CoroutineScope
49
+ import kotlinx.coroutines.Dispatchers
50
+ import kotlinx.coroutines.SupervisorJob
51
+ import kotlinx.coroutines.cancel
52
+ import kotlinx.coroutines.delay
53
+ import kotlinx.coroutines.launch
54
+ import vn.momo.kits.components.InputSearchProps
55
+ import vn.momo.kits.const.AppNavigationBar
56
+ import vn.momo.kits.const.AppTheme
57
+ import vn.momo.kits.const.Colors
58
+ import vn.momo.kits.const.Spacing
59
+ import vn.momo.kits.modifier.conditional
60
+ import vn.momo.kits.modifier.shadow
61
+ import vn.momo.kits.navigation.component.SnackBar
62
+ import vn.momo.kits.platform.getAndroidBuildVersion
63
+ import vn.momo.kits.utils.getAppStatusBarHeight
64
+
65
+ enum class HeaderType {
66
+ DEFAULT,
67
+ EXTENDED,
68
+ NONE
69
+ }
70
+
71
+ const val HEADER_HEIGHT = 52
72
+
73
+ @Deprecated("Use NavigationContainer(StackScreen) instead", ReplaceWith("NavigationContainer(StackScreen)"))
74
+ @Composable
75
+ fun Screen(
76
+ backgroundColor: Color? = null,
77
+ tintColor: Color? = null,
78
+ headerTransparent: Boolean = false,
79
+ fullScreenContent: Boolean = false,
80
+ isBack: Boolean = true,
81
+ headerType: HeaderType = HeaderType.DEFAULT,
82
+ verticalArrangement: Arrangement.Vertical = Arrangement.Top,
83
+ horizontalAlignment: Alignment.Horizontal = Alignment.Start,
84
+ title: String = "Stack",
85
+ titlePosition: TitlePosition = TitlePosition.LEFT,
86
+ goBack: (() -> Unit)? = null,
87
+ scrollable: Boolean = true,
88
+ scrollState: ScrollState = rememberScrollState(),
89
+ onContentLayout: ((LayoutCoordinates) -> Unit)? = null,
90
+ useAvoidKeyboard: Boolean = true,
91
+ footer: @Composable (() -> Unit)? = null,
92
+ headerRight: @Composable (() -> Unit)? = null,
93
+ fabProps: FabProps? = null,
94
+ animatedHeader: AnimatedHeader? = null,
95
+ layoutOffset: Dp = 56.dp,
96
+ inputSearchProps: InputSearchProps? = null,
97
+ useAnimationSearch: Boolean = false,
98
+ headerRightWidth: Dp = 0.dp,
99
+ content: @Composable () -> Unit,
100
+ ) {
101
+ val statusBarHeight = getAppStatusBarHeight()
102
+ val keyboardController = LocalSoftwareKeyboardController.current
103
+
104
+ val isKeyboardVisible = isKeyboardVisible()
105
+ val indicator = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding()
106
+ val bottomPadding = min(indicator, if (isKeyboardVisible) 0.dp else 21.dp)
107
+
108
+ val headerHeight = if (animatedHeader !== null)
109
+ with(LocalDensity.current) { layoutOffset.roundToPx() }
110
+ else HEADER_HEIGHT
111
+ val opacity by animateFloatAsState(
112
+ targetValue = ((scrollState.value.toFloat() / headerHeight)).coerceIn(0f, 1f),
113
+ )
114
+
115
+ val headerAnimated =
116
+ @Composable {
117
+ Box(
118
+ modifier = Modifier
119
+ .fillMaxWidth()
120
+ .aspectRatio(animatedHeader?.aspectRatio?.value ?: AnimatedHeaderRatio.RATIO_16_9.value)
121
+ ) {
122
+ animatedHeader?.composable?.invoke(scrollState.value)
123
+ }
124
+ }
125
+ val helper = remember { ScreenHelper() }
126
+
127
+ DisposableEffect(Unit) {
128
+ onDispose { helper.dispose() }
129
+ }
130
+
131
+ CompositionLocalProvider(
132
+ LocalScreenHelper provides helper
133
+ ) {
134
+ Box(
135
+ Modifier.fillMaxSize()
136
+ .background(backgroundColor ?: AppTheme.current.colors.background.default)
137
+ .conditional(useAvoidKeyboard && getAndroidBuildVersion() > 29) {
138
+ imePadding()
139
+ }
140
+ ) {
141
+ val footerHeightPx = remember { mutableStateOf(0) }
142
+
143
+ Box(Modifier.zIndex(1f)) {
144
+ if (animatedHeader === null) {
145
+ HeaderBackground(
146
+ headerType = headerType,
147
+ scrollState = scrollState.value,
148
+ headerTransparent = headerTransparent
149
+ )
150
+ }
151
+ }
152
+
153
+ Box(Modifier.zIndex(5f)) {
154
+ Header(
155
+ headerType = headerType,
156
+ title = title,
157
+ titlePosition = titlePosition,
158
+ headerRight = headerRight,
159
+ headerRightWidth = headerRightWidth,
160
+ goBack = goBack,
161
+ opacity = opacity,
162
+ animatedHeader = animatedHeader,
163
+ inputSearchProps = inputSearchProps,
164
+ scrollState = scrollState.value,
165
+ useAnimationSearch = useAnimationSearch,
166
+ tintColor = tintColor
167
+ )
168
+ }
169
+
170
+ Box(Modifier.zIndex(2f).fillMaxSize()){
171
+ Column(
172
+ modifier = Modifier.fillMaxSize()
173
+ .padding( top = when {
174
+ animatedHeader != null -> 0.dp
175
+ headerType == HeaderType.NONE -> 0.dp
176
+ fullScreenContent -> 0.dp
177
+ else -> statusBarHeight + HEADER_HEIGHT.dp
178
+ })
179
+ .pointerInput(Unit) {
180
+ detectTapGestures(onTap = {
181
+ keyboardController?.hide()
182
+ })
183
+ }
184
+ .zIndex(1f),
185
+ ) {
186
+
187
+ Column(
188
+ modifier = Modifier
189
+ .conditional(scrollable) { weight(1f) }
190
+ .conditional(onContentLayout != null) {
191
+ onGloballyPositioned(onContentLayout ?: {})
192
+ }
193
+ .conditional(scrollable) { verticalScroll(scrollState) },
194
+ verticalArrangement = verticalArrangement,
195
+ horizontalAlignment = horizontalAlignment
196
+ ) {
197
+ Box {
198
+ if (animatedHeader !== null) headerAnimated()
199
+ Column {
200
+ if (animatedHeader !== null) {
201
+ Spacer(modifier = Modifier.padding(top = statusBarHeight + HEADER_HEIGHT.dp + layoutOffset))
202
+ }
203
+
204
+ if (useAnimationSearch && inputSearchProps != null && headerType == HeaderType.EXTENDED) {
205
+ Spacer(modifier = Modifier.padding(top = (HEADER_HEIGHT.dp + Spacing.S)))
206
+ }
207
+ content()
208
+ }
209
+ }
210
+ }
211
+
212
+ footer?.let {
213
+ val footerHeight = with(LocalDensity.current) { footerHeightPx.value.toDp() }
214
+ Spacer(Modifier.height(footerHeight))
215
+ }
216
+ }
217
+ }
218
+
219
+ Box(Modifier.zIndex(4f).align(Alignment.BottomCenter)) {
220
+ footer?.let {
221
+ Footer(
222
+ footer = footer,
223
+ bottom = bottomPadding,
224
+ onFooterMeasured = { footerHeightPx.value = it }
225
+ )
226
+ }
227
+ }
228
+
229
+ Column (Modifier.zIndex(6f)) {
230
+ if (fabProps != null) {
231
+ FloatingButton(
232
+ scrollPosition = fabProps.scrollState?.value ?: scrollState.value,
233
+ onClick = fabProps.onClick,
234
+ containerColor = AppTheme.current.colors.primary,
235
+ bottom = fabProps.bottom
236
+ ?: if (footer != null) bottomPadding + 76.dp else bottomPadding + 12.dp,
237
+ icon = fabProps.icon,
238
+ iconColor = fabProps.iconColor,
239
+ text = fabProps.label,
240
+ size = fabProps.size,
241
+ position = fabProps.position ?: FABPosition.END,
242
+ )
243
+ }
244
+ }
245
+
246
+ Box(
247
+ modifier = Modifier
248
+ .align(Alignment.BottomCenter)
249
+ .zIndex(3f)
250
+ ) {
251
+ ScreenSnackBarHost(footerHeightPx.value)
252
+ }
253
+ }
254
+ }
255
+ }
256
+
257
+ @Composable
258
+ internal fun isKeyboardVisible(): Boolean {
259
+ val ime = WindowInsets.ime
260
+ val density = LocalDensity.current
261
+ val bottom = ime.getBottom(density)
262
+ return bottom > 0
263
+ }
264
+
265
+ @Composable
266
+ fun Footer(
267
+ footer: @Composable (() -> Unit)? = null,
268
+ bottom: Dp = 0.dp,
269
+ onFooterMeasured: ((Int) -> Unit)? = null
270
+ ) {
271
+ Box(
272
+ Modifier
273
+ .shadow(
274
+ color = Colors.black_20.copy(alpha = 0.05f),
275
+ blurRadius = 24f,
276
+ offsetX = 0.dp,
277
+ offsetY = (-4).dp
278
+ )
279
+ .background(AppTheme.current.colors.background.surface)
280
+ .onGloballyPositioned {
281
+ onFooterMeasured?.invoke(it.size.height)
282
+ }
283
+ ) {
284
+ Box(
285
+ Modifier.fillMaxWidth()
286
+ .padding(bottom = bottom)
287
+ .padding(vertical = Spacing.S, horizontal = Spacing.M)
288
+ ) {
289
+ footer?.invoke()
290
+ }
291
+ }
292
+ }
293
+
294
+
295
+ class ScreenHelper {
296
+
297
+ private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
298
+
299
+ internal var snackBarState by mutableStateOf<SnackBarData?>(null)
300
+
301
+ internal var requestHide by mutableStateOf(false)
302
+
303
+ fun showSnackBar(snackBar: SnackBar, onDismiss: (() -> Unit)? = null) {
304
+ scope.launch {
305
+ requestHide = false
306
+ snackBarState = SnackBarData(snackBar, onDismiss)
307
+ }
308
+ }
309
+
310
+ fun hideSnackBar() {
311
+ scope.launch {
312
+ requestHide = true
313
+ }
314
+ }
315
+
316
+ internal fun removeSnackBarNow() {
317
+ snackBarState = null
318
+ requestHide = false
319
+ }
320
+
321
+ fun dispose() {
322
+ scope.cancel()
323
+ }
324
+ }
325
+ data class SnackBarData(
326
+ val type: SnackBar,
327
+ val onDismiss: (() -> Unit)?
328
+ )
329
+
330
+ val LocalScreenHelper = staticCompositionLocalOf<ScreenHelper> {
331
+ error("No Screen helper provided")
332
+ }
333
+
334
+ @Composable
335
+ fun ScreenSnackBarHost(footerHeightPx: Int) {
336
+ val helper = LocalScreenHelper.current
337
+ val snackBarData = helper.snackBarState ?: return
338
+
339
+
340
+ val snackBarHeight = when (snackBarData.type) {
341
+ is SnackBar.Custom -> snackBarData.type.heightContent
342
+ is SnackBar.Toast -> 300f
343
+ }
344
+
345
+ val startPosition = if (footerHeightPx > 0) 0f else snackBarHeight
346
+ val targetPosition = if (footerHeightPx > 0) -footerHeightPx.toFloat() else -with(LocalDensity.current) { AppNavigationBar.current.toPx() }
347
+
348
+ val offsetY = remember { Animatable(startPosition) }
349
+
350
+ LaunchedEffect(snackBarData, footerHeightPx) {
351
+ offsetY.snapTo(startPosition)
352
+ offsetY.animateTo(targetPosition, tween(350))
353
+ }
354
+
355
+ LaunchedEffect(helper.requestHide) {
356
+ if (helper.requestHide) {
357
+ offsetY.animateTo(startPosition, tween(200))
358
+ helper.removeSnackBarNow()
359
+ snackBarData.onDismiss?.invoke()
360
+ }
361
+ }
362
+
363
+ LaunchedEffect(snackBarData.type.duration) {
364
+ val duration = snackBarData.type.duration
365
+ if (duration != null) {
366
+ delay(duration)
367
+ helper.hideSnackBar()
368
+ }
369
+ }
370
+
371
+ DisposableEffect(Unit) {
372
+ onDispose { snackBarData.onDismiss?.invoke() }
373
+ }
374
+
375
+ Box(
376
+ modifier = Modifier
377
+ .offset { IntOffset(0, offsetY.value.toInt()) }
378
+ .fillMaxWidth()
379
+ ) {
380
+ when (val type = snackBarData.type) {
381
+ is SnackBar.Custom -> type.content()
382
+ is SnackBar.Toast -> {}
383
+ }
384
+ }
385
+ }
386
+
387
+
388
+
389
+
390
+
391
+
392
+
@@ -0,0 +1,69 @@
1
+ package vn.momo.kits.application
2
+
3
+ import androidx.compose.animation.animateColorAsState
4
+ import androidx.compose.animation.core.animateFloatAsState
5
+ import androidx.compose.runtime.Composable
6
+ import androidx.compose.runtime.getValue
7
+ import androidx.compose.ui.graphics.Color
8
+ import androidx.compose.ui.unit.Dp
9
+ import androidx.compose.ui.unit.dp
10
+ import vn.momo.kits.const.AppTheme
11
+ import vn.momo.kits.const.Colors
12
+ import vn.momo.kits.platform.getScreenDimensions
13
+
14
+ data class HeaderAnimations(
15
+ val opacity: Float,
16
+ val backgroundSearch: Color,
17
+ val translateX: Float,
18
+ val width: Float,
19
+ val translateY: Float
20
+ )
21
+
22
+ private const val SCREEN_PADDING = 12
23
+ private const val BACK_WIDTH = 28
24
+
25
+ @Deprecated("Use vn.momo.kits.navigation.component.Header instead", ReplaceWith("vn.momo.kits.navigation.component.Header"))
26
+ @Composable
27
+ fun useHeaderSearchAnimation(
28
+ opacityAni: Float,
29
+ scrollState: Int,
30
+ headerRightWidth: Dp,
31
+ isBack: Boolean
32
+ ): HeaderAnimations {
33
+ val screenWidth = getScreenDimensions().width
34
+ val leftPosition = if (isBack) (BACK_WIDTH + 20).dp else 12.dp
35
+ val searchWidth = screenWidth - (SCREEN_PADDING * 2)
36
+
37
+ val backgroundSearch by animateColorAsState(
38
+ targetValue = animateColor(
39
+ Colors.black_01,
40
+ AppTheme.current.colors.background.default,
41
+ opacityAni
42
+ ),
43
+ )
44
+
45
+ val animatedTranslateX by animateFloatAsState(
46
+ targetValue = (scrollState / HEADER_HEIGHT * 1f).coerceIn(
47
+ 0f,
48
+ 1f
49
+ ) * (leftPosition.value - 12) + 12,
50
+ )
51
+
52
+ val animatedWidth by animateFloatAsState(
53
+ targetValue = (scrollState / HEADER_HEIGHT * 1f).coerceIn(
54
+ 0f,
55
+ 1f
56
+ ) * ((searchWidth - leftPosition.value - headerRightWidth.value + 12) - searchWidth) + searchWidth,
57
+ )
58
+
59
+ val animatedTranslateY by animateFloatAsState(
60
+ targetValue = (1 - (scrollState / HEADER_HEIGHT * 1f).coerceIn(0f, 1f)) * HEADER_HEIGHT
61
+ )
62
+ return HeaderAnimations(
63
+ opacity = opacityAni,
64
+ backgroundSearch = backgroundSearch,
65
+ translateX = animatedTranslateX,
66
+ width = animatedWidth,
67
+ translateY = animatedTranslateY
68
+ )
69
+ }