@momo-kits/native-kits 0.162.6 → 0.163.1-beta.1-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 (166) hide show
  1. package/CLAUDE.md +78 -0
  2. package/build.gradle.kts +11 -0
  3. package/compose/build.gradle.kts +186 -0
  4. package/compose/build.gradle.kts.backup +186 -0
  5. package/compose/compose.podspec +47 -0
  6. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +6 -0
  7. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +123 -0
  8. package/compose/src/commonMain/composeResources/font/momosignature.otf +0 -0
  9. package/compose/src/commonMain/composeResources/font/momotrustdisplay.otf +0 -0
  10. package/compose/src/commonMain/composeResources/font/sfprotext_black.otf +0 -0
  11. package/compose/src/commonMain/composeResources/font/sfprotext_black.ttf +0 -0
  12. package/compose/src/commonMain/composeResources/font/sfprotext_bold.ttf +0 -0
  13. package/compose/src/commonMain/composeResources/font/sfprotext_heavy.ttf +0 -0
  14. package/compose/src/commonMain/composeResources/font/sfprotext_light.ttf +0 -0
  15. package/compose/src/commonMain/composeResources/font/sfprotext_medium.ttf +0 -0
  16. package/compose/src/commonMain/composeResources/font/sfprotext_regular.ttf +0 -0
  17. package/compose/src/commonMain/composeResources/font/sfprotext_semibold.ttf +0 -0
  18. package/compose/src/commonMain/composeResources/font/sfprotext_thin.otf +0 -0
  19. package/compose/src/commonMain/composeResources/font/sfprotext_thin.ttf +0 -0
  20. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.otf +0 -0
  21. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.ttf +0 -0
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +57 -0
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +109 -0
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +201 -0
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +222 -0
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +48 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +86 -0
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +76 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +76 -0
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +305 -0
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +33 -0
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +921 -0
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +269 -0
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +120 -0
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +403 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +69 -0
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +157 -0
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +85 -0
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +32 -0
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +340 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +198 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +357 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +123 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +94 -0
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +136 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +224 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +543 -0
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +23 -0
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +76 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +148 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +188 -0
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +116 -0
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +452 -0
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +172 -0
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +255 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +235 -0
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +233 -0
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +259 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +241 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +364 -0
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +108 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +56 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +41 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +92 -0
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +40 -0
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +352 -0
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +103 -0
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +338 -0
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +70 -0
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +87 -0
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +17 -0
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +96 -0
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +348 -0
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +256 -0
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +494 -0
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +131 -0
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +215 -0
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +96 -0
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +531 -0
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +92 -0
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +135 -0
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +214 -0
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +590 -0
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +177 -0
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +192 -0
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +205 -0
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +239 -0
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +191 -0
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +306 -0
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +16 -0
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +188 -0
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +285 -0
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +2 -0
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +35 -0
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +2 -0
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +57 -0
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +68 -0
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +14 -0
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +50 -0
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +253 -0
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +133 -0
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +104 -0
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +135 -0
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +345 -0
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +8 -0
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +556 -0
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +161 -0
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +243 -0
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +187 -0
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +315 -0
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +80 -0
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +306 -0
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +34 -0
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +370 -0
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +131 -0
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +167 -0
  122. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +72 -0
  123. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +66 -0
  124. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
  125. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +70 -0
  126. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +15 -0
  127. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +105 -0
  128. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +33 -0
  129. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +186 -0
  130. package/example/ios/Example.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
  131. package/example/ios/Example.xcworkspace/xcuserdata/huynhdung.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  132. package/example/ios/Example.xcworkspace/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +5 -0
  133. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/MoMoUIKits.xcscheme +58 -0
  134. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/Pods-Example.xcscheme +58 -0
  135. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SDWebImage.xcscheme +58 -0
  136. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SDWebImageSwiftUI.xcscheme +58 -0
  137. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SkeletonUI.xcscheme +58 -0
  138. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +46 -0
  139. package/gradle/libs.versions.toml +67 -0
  140. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  141. package/gradle/wrapper/gradle-wrapper.properties +8 -0
  142. package/gradle.properties +26 -0
  143. package/gradlew +252 -0
  144. package/gradlew.bat +94 -0
  145. package/ios/Application/ApplicationEnvironment.swift +0 -1
  146. package/ios/Application/Components.swift +0 -1
  147. package/ios/Application/FloatingButton.swift +0 -1
  148. package/ios/Badge/Badge.swift +0 -1
  149. package/ios/Badge/BadgeRibbon.swift +0 -2
  150. package/ios/Button/Button.swift +1 -1
  151. package/ios/Checkbox/Checkbox.swift +0 -1
  152. package/ios/Input/Input.swift +0 -1
  153. package/ios/Input/InputPhoneNumber.swift +0 -1
  154. package/ios/Input/InputSearch.swift +0 -3
  155. package/ios/Input/InputTextArea.swift +0 -1
  156. package/ios/OTPKeyboard/KeyboardButton.swift +1 -1
  157. package/ios/Popup/PopupDisplay.swift +0 -6
  158. package/ios/Popup/PopupInput.swift +0 -2
  159. package/ios/Template/TrustBanner/TrustBanner.swift +0 -2
  160. package/ios/Typography/Text.swift +7 -13
  161. package/ios/Typography/Typography.swift +8 -22
  162. package/local.properties +8 -0
  163. package/package.json +1 -1
  164. package/settings.gradle.kts +64 -0
  165. package/ios/Application/FontScaleStore.swift +0 -59
  166. package/ios/Information/Information.swift +0 -151
@@ -0,0 +1,403 @@
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.navigationBars
22
+ import androidx.compose.foundation.layout.offset
23
+ import androidx.compose.foundation.layout.padding
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.zIndex
47
+ import kotlinx.coroutines.CoroutineScope
48
+ import kotlinx.coroutines.Dispatchers
49
+ import kotlinx.coroutines.SupervisorJob
50
+ import kotlinx.coroutines.cancel
51
+ import kotlinx.coroutines.delay
52
+ import kotlinx.coroutines.launch
53
+ import vn.momo.kits.components.InputSearchProps
54
+ import vn.momo.kits.const.AppNavigationBar
55
+ import vn.momo.kits.const.AppTheme
56
+ import vn.momo.kits.const.Colors
57
+ import vn.momo.kits.const.Spacing
58
+ import vn.momo.kits.modifier.DeprecatedModifier
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.supportsImePadding
63
+ import vn.momo.kits.utils.getAppStatusBarHeight
64
+ import vn.momo.kits.utils.getNavigationBarHeight
65
+
66
+ enum class HeaderType {
67
+ DEFAULT,
68
+ EXTENDED,
69
+ NONE
70
+ }
71
+
72
+ const val HEADER_HEIGHT = 52
73
+
74
+ @Deprecated("Use NavigationContainer(StackScreen) instead", ReplaceWith("NavigationContainer(StackScreen)"))
75
+ @Composable
76
+ fun Screen(
77
+ backgroundColor: Color? = null,
78
+ tintColor: Color? = null,
79
+ headerTransparent: Boolean = false,
80
+ fullScreenContent: Boolean = false,
81
+ isBack: Boolean = true,
82
+ headerType: HeaderType = HeaderType.DEFAULT,
83
+ verticalArrangement: Arrangement.Vertical = Arrangement.Top,
84
+ horizontalAlignment: Alignment.Horizontal = Alignment.Start,
85
+ title: String = "Stack",
86
+ titlePosition: TitlePosition = TitlePosition.LEFT,
87
+ goBack: (() -> Unit)? = null,
88
+ scrollable: Boolean = true,
89
+ scrollState: ScrollState = rememberScrollState(),
90
+ onContentLayout: ((LayoutCoordinates) -> Unit)? = null,
91
+ useAvoidKeyboard: Boolean = true,
92
+ footer: @Composable (() -> Unit)? = null,
93
+ headerRight: @Composable (() -> Unit)? = null,
94
+ fabProps: FabProps? = null,
95
+ animatedHeader: AnimatedHeader? = null,
96
+ layoutOffset: Dp = 56.dp,
97
+ inputSearchProps: InputSearchProps? = null,
98
+ useAnimationSearch: Boolean = false,
99
+ headerRightWidth: Dp = 0.dp,
100
+ content: @Composable () -> Unit,
101
+ ) {
102
+ val statusBarHeight = getAppStatusBarHeight()
103
+ val keyboardController = LocalSoftwareKeyboardController.current
104
+
105
+ val isKeyboardVisible = isKeyboardVisible()
106
+ val indicator = getNavigationBarHeight()
107
+ val bottomPadding = if (isKeyboardVisible) 0.dp else indicator
108
+
109
+ val headerHeight = if (animatedHeader !== null)
110
+ with(LocalDensity.current) { layoutOffset.roundToPx() }
111
+ else HEADER_HEIGHT
112
+ val opacity by animateFloatAsState(
113
+ targetValue = ((scrollState.value.toFloat() / headerHeight)).coerceIn(0f, 1f),
114
+ )
115
+
116
+ val headerAnimated =
117
+ @Composable {
118
+ Box(
119
+ modifier = Modifier
120
+ .fillMaxWidth()
121
+ .aspectRatio(animatedHeader?.aspectRatio?.value ?: AnimatedHeaderRatio.RATIO_16_9.value)
122
+ ) {
123
+ animatedHeader?.composable?.invoke(scrollState.value)
124
+ }
125
+ }
126
+ val helper = remember { ScreenHelper() }
127
+
128
+ DisposableEffect(Unit) {
129
+ onDispose { helper.dispose() }
130
+ }
131
+
132
+ CompositionLocalProvider(
133
+ LocalScreenHelper provides helper
134
+ ) {
135
+ Box(
136
+ Modifier.fillMaxSize()
137
+ .background(backgroundColor ?: AppTheme.current.colors.background.default)
138
+ .conditional(useAvoidKeyboard && supportsImePadding()) {
139
+ imePadding()
140
+ }.then(DeprecatedModifier())
141
+ ) {
142
+ val footerHeightPx = remember { mutableStateOf(0) }
143
+
144
+ Box(Modifier.zIndex(1f)) {
145
+ if (animatedHeader === null) {
146
+ HeaderBackground(
147
+ headerType = headerType,
148
+ scrollState = scrollState.value,
149
+ headerTransparent = headerTransparent
150
+ )
151
+ }
152
+ }
153
+
154
+ Box(Modifier.zIndex(5f)) {
155
+ Header(
156
+ headerType = headerType,
157
+ title = title,
158
+ titlePosition = titlePosition,
159
+ headerRight = headerRight,
160
+ headerRightWidth = headerRightWidth,
161
+ goBack = goBack,
162
+ opacity = opacity,
163
+ animatedHeader = animatedHeader,
164
+ inputSearchProps = inputSearchProps,
165
+ scrollState = scrollState.value,
166
+ useAnimationSearch = useAnimationSearch,
167
+ tintColor = tintColor
168
+ )
169
+ }
170
+
171
+ Box(Modifier.zIndex(2f).fillMaxSize()){
172
+ Column(
173
+ modifier = Modifier.fillMaxSize()
174
+ .padding( top = when {
175
+ animatedHeader != null -> 0.dp
176
+ headerType == HeaderType.NONE -> 0.dp
177
+ fullScreenContent -> 0.dp
178
+ else -> statusBarHeight + HEADER_HEIGHT.dp
179
+ })
180
+ .pointerInput(Unit) {
181
+ detectTapGestures(onTap = {
182
+ keyboardController?.hide()
183
+ })
184
+ }
185
+ .zIndex(1f),
186
+ ) {
187
+
188
+ Column(
189
+ modifier = Modifier
190
+ .conditional(scrollable) { weight(1f) }
191
+ .conditional(onContentLayout != null) {
192
+ onGloballyPositioned(onContentLayout ?: {})
193
+ }
194
+ .conditional(scrollable) { verticalScroll(scrollState) },
195
+ verticalArrangement = verticalArrangement,
196
+ horizontalAlignment = horizontalAlignment
197
+ ) {
198
+ Box {
199
+ if (animatedHeader !== null) headerAnimated()
200
+ Column {
201
+ if (animatedHeader !== null) {
202
+ Spacer(modifier = Modifier.padding(top = statusBarHeight + HEADER_HEIGHT.dp + layoutOffset))
203
+ }
204
+
205
+ if (useAnimationSearch && inputSearchProps != null && headerType == HeaderType.EXTENDED) {
206
+ Spacer(modifier = Modifier.padding(top = (HEADER_HEIGHT.dp + Spacing.S)))
207
+ }
208
+ content()
209
+ }
210
+ }
211
+ }
212
+
213
+ footer?.let {
214
+ val footerHeight = with(LocalDensity.current) { footerHeightPx.value.toDp() }
215
+ Spacer(Modifier.height(footerHeight))
216
+ }
217
+ }
218
+ }
219
+
220
+ Box(Modifier.zIndex(4f).align(Alignment.BottomCenter)) {
221
+ footer?.let {
222
+ Footer(
223
+ footer = footer,
224
+ bottom = bottomPadding,
225
+ onFooterMeasured = { footerHeightPx.value = it }
226
+ )
227
+ }
228
+ }
229
+
230
+ Column (Modifier.zIndex(6f)) {
231
+ if (fabProps != null) {
232
+ FloatingButton(
233
+ scrollPosition = fabProps.scrollState?.value ?: scrollState.value,
234
+ onClick = fabProps.onClick,
235
+ containerColor = AppTheme.current.colors.primary,
236
+ bottom = fabProps.bottom
237
+ ?: if (footer != null) bottomPadding + 76.dp else bottomPadding + 12.dp,
238
+ icon = fabProps.icon,
239
+ iconColor = fabProps.iconColor,
240
+ text = fabProps.label,
241
+ size = fabProps.size,
242
+ position = fabProps.position ?: FABPosition.END,
243
+ )
244
+ }
245
+ }
246
+
247
+ Box(
248
+ modifier = Modifier
249
+ .align(Alignment.BottomCenter)
250
+ .zIndex(3f)
251
+ ) {
252
+ ScreenSnackBarHost(if (footer == null) 0 else footerHeightPx.value)
253
+ }
254
+ }
255
+ }
256
+ }
257
+
258
+ @Composable
259
+ internal fun isKeyboardVisible(): Boolean {
260
+ val ime = WindowInsets.ime
261
+ val density = LocalDensity.current
262
+ val bottom = ime.getBottom(density)
263
+ return bottom > 0
264
+ }
265
+
266
+ @Composable
267
+ fun Footer(
268
+ footer: @Composable (() -> Unit)? = null,
269
+ bottom: Dp = 0.dp,
270
+ onFooterMeasured: ((Int) -> Unit)? = null
271
+ ) {
272
+ Box(
273
+ Modifier
274
+ .shadow(
275
+ color = Colors.black_20.copy(alpha = 0.05f),
276
+ blurRadius = 24f,
277
+ offsetX = 0.dp,
278
+ offsetY = (-4).dp
279
+ )
280
+ .background(AppTheme.current.colors.background.surface)
281
+ .onGloballyPositioned {
282
+ onFooterMeasured?.invoke(it.size.height)
283
+ }
284
+ ) {
285
+ Box(
286
+ Modifier.fillMaxWidth()
287
+ .padding(bottom = bottom)
288
+ .padding(vertical = Spacing.S, horizontal = Spacing.M)
289
+ ) {
290
+ footer?.invoke()
291
+ }
292
+ }
293
+ }
294
+
295
+
296
+ class ScreenHelper {
297
+
298
+ private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
299
+
300
+ internal var snackBarState by mutableStateOf<SnackBarData?>(null)
301
+
302
+ internal var requestHide by mutableStateOf(false)
303
+
304
+ fun showSnackBar(snackBar: SnackBar, onDismiss: (() -> Unit)? = null) {
305
+ scope.launch {
306
+ requestHide = false
307
+ snackBarState = SnackBarData(snackBar, onDismiss)
308
+ }
309
+ }
310
+
311
+ fun hideSnackBar() {
312
+ scope.launch {
313
+ requestHide = true
314
+ }
315
+ }
316
+
317
+ internal fun removeSnackBarNow() {
318
+ snackBarState = null
319
+ requestHide = false
320
+ }
321
+
322
+ fun dispose() {
323
+ scope.cancel()
324
+ }
325
+ }
326
+ data class SnackBarData(
327
+ val type: SnackBar,
328
+ val onDismiss: (() -> Unit)?
329
+ )
330
+
331
+ val LocalScreenHelper = staticCompositionLocalOf<ScreenHelper> {
332
+ error("No Screen helper provided")
333
+ }
334
+
335
+ @Composable
336
+ fun ScreenSnackBarHost(footerHeightPx: Int) {
337
+ val helper = LocalScreenHelper.current
338
+ val snackBarData = helper.snackBarState ?: return
339
+ val density = LocalDensity.current
340
+ val navigationBar = AppNavigationBar.current
341
+
342
+ val footerHeight = if (footerHeightPx > 0) {
343
+ footerHeightPx
344
+ } else {
345
+ with(density) {
346
+ navigationBar.toPx()
347
+ }
348
+ }.toInt()
349
+
350
+ var startPosition by remember { mutableStateOf(Float.MAX_VALUE) }
351
+ val targetPosition = 0f
352
+
353
+ var offsetY by remember { mutableStateOf(Animatable(startPosition)) }
354
+
355
+ LaunchedEffect(startPosition) {
356
+ if (startPosition != Float.MAX_VALUE){
357
+ offsetY.snapTo(startPosition)
358
+ offsetY.animateTo(targetPosition, tween(350))
359
+ }
360
+ }
361
+
362
+ LaunchedEffect(helper.requestHide) {
363
+ if (helper.requestHide) {
364
+ offsetY.animateTo(startPosition, tween(200))
365
+ helper.removeSnackBarNow()
366
+ snackBarData.onDismiss?.invoke()
367
+ }
368
+ }
369
+
370
+ LaunchedEffect(snackBarData.type.duration) {
371
+ val duration = snackBarData.type.duration
372
+ if (duration != null) {
373
+ delay(duration)
374
+ helper.hideSnackBar()
375
+ }
376
+ }
377
+
378
+ DisposableEffect(Unit) {
379
+ onDispose { snackBarData.onDismiss?.invoke() }
380
+ }
381
+
382
+ Box(
383
+ modifier = Modifier
384
+ .offset { IntOffset(0, offsetY.value.toInt() - footerHeight) }
385
+ .onGloballyPositioned {
386
+ if (startPosition != it.size.height.toFloat()) {
387
+ startPosition = it.size.height.toFloat()
388
+ offsetY = Animatable(startPosition)
389
+ }
390
+ }
391
+ .fillMaxWidth()
392
+ ) {
393
+ when (val type = snackBarData.type) {
394
+ is SnackBar.Custom -> type.content()
395
+ is SnackBar.Toast -> {}
396
+ }
397
+ }
398
+ }
399
+
400
+
401
+
402
+
403
+
@@ -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
+ }
@@ -0,0 +1,157 @@
1
+ package vn.momo.kits.components
2
+
3
+ import androidx.compose.foundation.background
4
+ import androidx.compose.foundation.border
5
+ import androidx.compose.foundation.layout.Box
6
+ import androidx.compose.foundation.layout.fillMaxSize
7
+ import androidx.compose.foundation.layout.offset
8
+ import androidx.compose.foundation.layout.size
9
+ import androidx.compose.foundation.shape.RoundedCornerShape
10
+ import androidx.compose.runtime.Composable
11
+ import androidx.compose.runtime.remember
12
+ import androidx.compose.ui.Alignment
13
+ import androidx.compose.ui.Modifier
14
+ import androidx.compose.ui.draw.clip
15
+ import androidx.compose.ui.semantics.contentDescription
16
+ import androidx.compose.ui.semantics.semantics
17
+ import androidx.compose.ui.unit.dp
18
+ import androidx.compose.ui.zIndex
19
+ import vn.momo.kits.application.IsShowBaseLineDebug
20
+ import vn.momo.kits.const.AppTheme
21
+ import vn.momo.kits.const.Colors
22
+ import vn.momo.kits.const.Spacing
23
+ import vn.momo.kits.const.Typography
24
+ import vn.momo.kits.modifier.conditional
25
+ import vn.momo.kits.modifier.setAutomationId
26
+
27
+ private const val LOGO_MOMO_URL =
28
+ "https://static.momocdn.net/app/img/kits/logo_momo_circle.png"
29
+ private const val ICON_SUPPORT_DEFAULT_URL =
30
+ "https://static.momocdn.net/app/img/kits/_indicator.png"
31
+
32
+ enum class AvatarSize(val sizeDp: Int, val iconSizeDp: Int) {
33
+ SIZE_24(24, 16),
34
+ SIZE_32(32, 16),
35
+ SIZE_40(40, 24),
36
+ SIZE_48(48, 32),
37
+ SIZE_56(56, 32),
38
+ SIZE_72(72, 40),
39
+ }
40
+
41
+ @Composable
42
+ fun Avatar(
43
+ modifier: Modifier = Modifier,
44
+ size: AvatarSize = AvatarSize.SIZE_32,
45
+ rounded: Boolean = true,
46
+ name: String? = null,
47
+ image: String? = null,
48
+ showIconMomo: Boolean = false,
49
+ showIconSupport: Boolean = false,
50
+ iconSupport: String = ICON_SUPPORT_DEFAULT_URL,
51
+ accessibilityLabel: String? = null,
52
+ ) {
53
+ val theme = AppTheme.current
54
+
55
+ val cornerRadius = remember(rounded, size) {
56
+ if (rounded) (size.sizeDp / 2).dp else Spacing.XS
57
+ }
58
+ val shape = remember(cornerRadius) { RoundedCornerShape(cornerRadius) }
59
+
60
+ val badgeSizeDp = remember(size) { (size.iconSizeDp / 2).dp }
61
+ val badgeShape = remember(size) {
62
+ RoundedCornerShape((size.iconSizeDp / 4).dp)
63
+ }
64
+
65
+ val shouldShowIconTop = showIconMomo && rounded
66
+ val shouldShowIconBottom = showIconSupport && rounded
67
+
68
+ val initials = remember(name) {
69
+ name?.let { avatarInitials(it) }
70
+ }
71
+
72
+ val automationId = remember(accessibilityLabel, name) {
73
+ accessibilityLabel ?: "Avatar${if (name != null) "/$name" else ""}"
74
+ }
75
+
76
+ Box(
77
+ modifier = modifier
78
+ .size(size.sizeDp.dp)
79
+ .conditional(IsShowBaseLineDebug) { border(1.dp, Colors.blue_03) }
80
+ .setAutomationId(automationId)
81
+ .semantics { contentDescription = automationId },
82
+ ) {
83
+ // Inner box with clip — holds avatar content
84
+ Box(
85
+ modifier = Modifier
86
+ .fillMaxSize()
87
+ .clip(shape)
88
+ .background(color = Colors.pink_09)
89
+ .border(width = 1.dp, color = theme.colors.border.default, shape = shape),
90
+ contentAlignment = Alignment.Center,
91
+ ) {
92
+ when {
93
+ image != null -> {
94
+ Image(
95
+ source = image,
96
+ modifier = Modifier
97
+ .fillMaxSize()
98
+ .clip(shape)
99
+ .semantics { contentDescription = "$automationId|img" },
100
+ )
101
+ }
102
+
103
+ name != null -> {
104
+ Text(
105
+ text = initials ?: "",
106
+ style = Typography.descriptionXsRegular,
107
+ color = Colors.pink_03,
108
+ modifier = Modifier
109
+ .semantics { contentDescription = "$automationId|text" },
110
+ )
111
+ }
112
+
113
+ else -> {
114
+ Icon(
115
+ source = "basic_person",
116
+ size = size.iconSizeDp.dp,
117
+ color = Colors.pink_03,
118
+ modifier = Modifier
119
+ .semantics { contentDescription = "$automationId|icon" },
120
+ )
121
+ }
122
+ }
123
+ }
124
+
125
+ // Badge icons — outside clip so they are not cut by border
126
+ if (shouldShowIconTop) {
127
+ Image(
128
+ source = LOGO_MOMO_URL,
129
+ modifier = Modifier
130
+ .zIndex(1f)
131
+ .align(Alignment.TopEnd)
132
+ .offset(x = 1.dp, y = (-1).dp)
133
+ .size(badgeSizeDp)
134
+ .clip(badgeShape)
135
+ .semantics { contentDescription = "$automationId|top-icon" },
136
+ )
137
+ }
138
+
139
+ if (shouldShowIconBottom) {
140
+ Image(
141
+ source = iconSupport,
142
+ modifier = Modifier
143
+ .zIndex(1f)
144
+ .align(Alignment.BottomEnd)
145
+ .offset(x = 1.dp, y = 1.dp)
146
+ .size(badgeSizeDp)
147
+ .clip(badgeShape)
148
+ .semantics { contentDescription = "$automationId|bottom-icon" },
149
+ )
150
+ }
151
+ }
152
+ }
153
+
154
+ private fun avatarInitials(name: String): String {
155
+ val words = name.trim().split(" ").filter { it.isNotEmpty() }
156
+ return words.takeLast(2).joinToString("") { it.first().uppercaseChar().toString() }
157
+ }