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

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/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 +236 -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 +232 -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 +459 -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/platform/Platform.kt +38 -0
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +62 -0
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +88 -0
  108. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +144 -0
  109. package/gradle.properties +19 -0
  110. package/gradlew +240 -0
  111. package/gradlew.bat +91 -0
  112. package/ios/Application/ApplicationEnvironment.swift +50 -0
  113. package/ios/Application/Components.swift +263 -0
  114. package/ios/Application/ComposeApi.swift +22 -0
  115. package/ios/Application/FloatingButton.swift +172 -0
  116. package/ios/Application/HeaderRight.swift +271 -0
  117. package/ios/Application/Screen.swift +249 -0
  118. package/ios/Badge/BadgeDot.swift +31 -0
  119. package/ios/Button/Button.swift +211 -0
  120. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +126 -0
  121. package/ios/Checkbox/Checkbox.swift +81 -0
  122. package/ios/Chip/Chip.swift +96 -0
  123. package/ios/Colors+Radius+Spacing/Colors.swift +172 -0
  124. package/ios/Colors+Radius+Spacing/Radius.swift +22 -0
  125. package/ios/Colors+Radius+Spacing/Spacing.swift +12 -0
  126. package/ios/Extensions/Color++.swift +25 -0
  127. package/ios/Icon/Icon.swift +51 -0
  128. package/ios/Image/Image.swift +70 -0
  129. package/ios/Input/Input.swift +207 -0
  130. package/ios/Input/InputPhoneNumber.swift +176 -0
  131. package/ios/Input/InputSearch.swift +238 -0
  132. package/ios/Input/InputTextArea.swift +242 -0
  133. package/ios/Lottie/LottieView.swift +86 -0
  134. package/ios/OTPKeyboard/KeyboardButton.swift +41 -0
  135. package/ios/OTPKeyboard/OTPKeyboard.swift +145 -0
  136. package/ios/Popup/PopupDisplay.swift +284 -0
  137. package/ios/Popup/PopupInput.swift +96 -0
  138. package/ios/Popup/PopupPromotion.swift +73 -0
  139. package/ios/PopupView/FullscreenPopup.swift +251 -0
  140. package/ios/PopupView/Modifiers.swift +158 -0
  141. package/ios/PopupView/PopupView.swift +289 -0
  142. package/ios/PopupView/Utils++.swift +281 -0
  143. package/ios/ScrollIndicator/ScrollIndicator.swift +110 -0
  144. package/ios/Swipeable/SwipeCell.swift +278 -0
  145. package/ios/Swipeable/SwipeCellModel.swift +86 -0
  146. package/ios/Switch/Switch.swift +44 -0
  147. package/ios/Template/Logo/Logo.swift +75 -0
  148. package/ios/Template/TrustBanner/TrustBanner.swift +120 -0
  149. package/ios/Theme.md +18 -0
  150. package/ios/Typography/Text.swift +140 -0
  151. package/ios/Typography/Typography.swift +95 -0
  152. package/ios/native-kits.podspec +18 -0
  153. package/package.json +6 -7
  154. package/settings.gradle.kts +25 -0
  155. package/shared/build.gradle.kts +0 -74
@@ -0,0 +1,338 @@
1
+ package vn.momo.kits.components
2
+
3
+ import androidx.compose.foundation.background
4
+ import androidx.compose.foundation.border
5
+ import androidx.compose.foundation.clickable
6
+ import androidx.compose.foundation.interaction.MutableInteractionSource
7
+ import androidx.compose.foundation.layout.Arrangement
8
+ import androidx.compose.foundation.layout.Box
9
+ import androidx.compose.foundation.layout.Column
10
+ import androidx.compose.foundation.layout.Row
11
+ import androidx.compose.foundation.layout.Spacer
12
+ import androidx.compose.foundation.layout.aspectRatio
13
+ import androidx.compose.foundation.layout.fillMaxWidth
14
+ import androidx.compose.foundation.layout.height
15
+ import androidx.compose.foundation.layout.offset
16
+ import androidx.compose.foundation.layout.padding
17
+ import androidx.compose.foundation.layout.width
18
+ import androidx.compose.foundation.lazy.LazyColumn
19
+ import androidx.compose.foundation.shape.RoundedCornerShape
20
+ import androidx.compose.runtime.Composable
21
+ import androidx.compose.runtime.LaunchedEffect
22
+ import androidx.compose.runtime.MutableState
23
+ import androidx.compose.runtime.getValue
24
+ import androidx.compose.runtime.mutableStateOf
25
+ import androidx.compose.runtime.remember
26
+ import androidx.compose.runtime.setValue
27
+ import androidx.compose.ui.Alignment
28
+ import androidx.compose.ui.Modifier
29
+ import androidx.compose.ui.draw.clip
30
+ import androidx.compose.ui.text.TextLayoutResult
31
+ import androidx.compose.ui.unit.dp
32
+ import vn.momo.kits.const.AppTheme
33
+ import vn.momo.kits.const.Radius
34
+ import vn.momo.kits.const.Spacing
35
+ import vn.momo.kits.const.Typography
36
+ import vn.momo.kits.modifier.setAutomationId
37
+ import androidx.compose.ui.platform.LocalDensity
38
+ import androidx.compose.ui.unit.Dp
39
+ import kotlin.math.min
40
+
41
+ data class PopupAction(
42
+ val title: String,
43
+ val onPress: (() -> Unit)?,
44
+ )
45
+
46
+ enum class PopupActionDirection {
47
+ ROW,
48
+ COLUMN,
49
+ AUTO
50
+ }
51
+
52
+ private fun heightForLines(
53
+ layout: TextLayoutResult,
54
+ targetLines: Float
55
+ ): Float {
56
+ val clampedLines = targetLines.coerceAtLeast(1f)
57
+ val wholeLines = kotlin.math.floor(clampedLines).toInt()
58
+ val fractionalPart = clampedLines - wholeLines
59
+
60
+ val baseLineIndex = (wholeLines - 1)
61
+ .coerceAtLeast(0)
62
+ .coerceAtMost(layout.lineCount - 1)
63
+
64
+ var heightPx = layout.getLineBottom(baseLineIndex)
65
+
66
+ if (fractionalPart > 0f) {
67
+ val nextLineIndex = (baseLineIndex + 1)
68
+ .coerceAtMost(layout.lineCount - 1)
69
+ val nextBottomPx = layout.getLineBottom(nextLineIndex)
70
+ heightPx += (nextBottomPx - heightPx) * fractionalPart
71
+ }
72
+ return heightPx
73
+ }
74
+
75
+ @Composable
76
+ fun PopupNotify(
77
+ image: String = "",
78
+ title: String = "Title",
79
+ description: String = "Description",
80
+ error: String = "",
81
+ primary: PopupAction? = null,
82
+ secondary: PopupAction? = null,
83
+ buttonDirection: PopupActionDirection = PopupActionDirection.ROW,
84
+ onIconClose: () -> Unit,
85
+ isShowCloseIcon: Boolean = true,
86
+ ) {
87
+ var isScroll by remember { mutableStateOf(false) }
88
+ val layoutResult = remember { mutableStateOf<TextLayoutResult?>(null) }
89
+
90
+ val density = LocalDensity.current
91
+ var scrollHeight: Dp by remember { mutableStateOf(0.dp) }
92
+
93
+ LaunchedEffect(layoutResult.value) {
94
+ if ((layoutResult.value?.lineCount ?: 0) > 3) {
95
+ isScroll = true
96
+ }
97
+ }
98
+
99
+ LaunchedEffect(layoutResult.value) {
100
+ layoutResult.value?.let { v ->
101
+ // cap visible height at ~8.5 lines
102
+ val textPx = v.size.height.toFloat()
103
+ val capPx = heightForLines(v, 8.5f)
104
+
105
+ isScroll = textPx >= capPx
106
+ scrollHeight = with(density) { min(textPx, capPx).toDp() }
107
+ }
108
+ }
109
+
110
+ val content: @Composable (Modifier) -> Unit = if (isScroll) {
111
+ { modifier ->
112
+ LazyColumn(
113
+ modifier = modifier.height(scrollHeight)
114
+ ) {
115
+ item {
116
+ Text(
117
+ text = description,
118
+ onTextLayout = { layoutResult.value = it },
119
+ style = Typography.bodyDefaultRegular
120
+ )
121
+ }
122
+ }
123
+ }
124
+ } else {
125
+ { modifier ->
126
+ Box(modifier = modifier) {
127
+ Text(
128
+ text = description,
129
+ onTextLayout = { layoutResult.value = it },
130
+ style = Typography.bodyDefaultRegular
131
+ )
132
+ }
133
+ }
134
+ }
135
+
136
+ fun onClose(callback: (() -> Unit)?) {
137
+ callback?.invoke()
138
+ }
139
+
140
+ Box(
141
+ Modifier.setAutomationId("popup_notify"), contentAlignment = Alignment.TopEnd
142
+ ) {
143
+ Column(
144
+ modifier = Modifier
145
+ .fillMaxWidth()
146
+ .padding(horizontal = 12.dp)
147
+ .clip(RoundedCornerShape(Radius.L))
148
+ .background(
149
+ AppTheme.current.colors.background.surface,
150
+ RoundedCornerShape(Radius.L)
151
+ )
152
+ ) {
153
+ if(image.isNotEmpty()) {
154
+ Image(
155
+ source = image,
156
+ modifier = Modifier.fillMaxWidth().aspectRatio(1.777f),
157
+ options = Options(alignment = Alignment.Center)
158
+ )
159
+ }
160
+
161
+ Column(modifier = Modifier.padding(Spacing.XL)) {
162
+ Text(
163
+ style = Typography.headerDefaultBold,
164
+ maxLines = 2,
165
+ text = title,
166
+ modifier = Modifier.setAutomationId("title_popup_permission")
167
+ )
168
+ content(Modifier.padding(top = Spacing.S))
169
+ if (error.isNotEmpty()) {
170
+ Text(
171
+ text = "Mã lỗi: " + error,
172
+ style = Typography.descriptionXsRegular,
173
+ color = AppTheme.current.colors.text.hint,
174
+ maxLines = 1,
175
+ modifier = Modifier.padding(top = Spacing.S)
176
+ )
177
+ }
178
+ }
179
+
180
+ Box(
181
+ modifier = Modifier.padding(horizontal = Spacing.XL)
182
+ .padding(bottom = Spacing.XL)
183
+ ) {
184
+ BuildAction(primary, secondary, buttonDirection, onAction = { callback ->
185
+ onClose(callback)
186
+ })
187
+ }
188
+ }
189
+
190
+ if(isShowCloseIcon) {
191
+ Box(
192
+ Modifier
193
+ .width(22.dp)
194
+ .height(22.dp)
195
+ .offset(x = -(1).dp, y = (-11).dp)
196
+ .background(
197
+ color = AppTheme.current.colors.text.default,
198
+ shape = RoundedCornerShape(Radius.M)
199
+ )
200
+ .border(
201
+ width = 2.dp,
202
+ color = AppTheme.current.colors.background.surface,
203
+ shape = RoundedCornerShape(Radius.M)
204
+ )
205
+ .clip(RoundedCornerShape(100))
206
+ .clickable(
207
+ interactionSource = remember { MutableInteractionSource() },
208
+ indication = null,
209
+ onClick = { onClose { onIconClose() } }
210
+ ),
211
+ contentAlignment = Alignment.Center
212
+ ) {
213
+ Icon(
214
+ source = "navigation_close",
215
+ color = AppTheme.current.colors.background.surface,
216
+ size = 16.dp,
217
+ modifier = Modifier.setAutomationId("ic_popup_close")
218
+ )
219
+ }
220
+ }
221
+ }
222
+ }
223
+
224
+ @Composable
225
+ fun BuildAction(
226
+ primary: PopupAction?,
227
+ secondary: PopupAction?,
228
+ buttonDirection: PopupActionDirection = PopupActionDirection.ROW,
229
+ onAction: (onPress: (() -> Unit)?) -> Unit,
230
+ ) {
231
+ val closeAction = remember { mutableStateOf("button_action") }
232
+ when (buttonDirection) {
233
+ PopupActionDirection.AUTO -> {
234
+ if (secondary != null && (secondary.title.length > 12 || primary?.title?.length!! > 12)) {
235
+ renderColumn(
236
+ secondary = secondary,
237
+ onAction = onAction,
238
+ closeAction = closeAction,
239
+ primary = primary
240
+ )
241
+ } else {
242
+ renderRow(
243
+ secondary = secondary,
244
+ onAction = onAction,
245
+ closeAction = closeAction,
246
+ primary = primary
247
+ )
248
+ }
249
+ }
250
+
251
+ PopupActionDirection.ROW -> renderRow(
252
+ secondary = secondary,
253
+ onAction = onAction,
254
+ closeAction = closeAction,
255
+ primary = primary
256
+ )
257
+
258
+ else -> renderColumn(
259
+ secondary = secondary,
260
+ onAction = onAction,
261
+ closeAction = closeAction,
262
+ primary = primary
263
+ )
264
+ }
265
+ }
266
+
267
+ @Composable
268
+ fun renderRow(
269
+ secondary: PopupAction?,
270
+ closeAction: MutableState<String>,
271
+ primary: PopupAction?,
272
+ onAction: (onPress: (() -> Unit)?) -> Unit,
273
+ ) {
274
+ Row(horizontalArrangement = Arrangement.Center) {
275
+ secondary?.let {
276
+ Box(Modifier.weight(1f)) {
277
+ Button(
278
+ size = Size.MEDIUM,
279
+ onClick = {
280
+ closeAction.value = "button_action"
281
+ onAction(it.onPress)
282
+ },
283
+ title = it.title,
284
+ type = ButtonType.TEXT,
285
+ modifier = Modifier.setAutomationId("btn_popup_cancel")
286
+ )
287
+ }
288
+ Spacer(modifier = Modifier.width(Spacing.S))
289
+ }
290
+ Box(Modifier.weight(1f)) {
291
+ Button(
292
+ size = Size.MEDIUM,
293
+ onClick = {
294
+ closeAction.value = "button_action"
295
+ onAction(primary?.onPress)
296
+ },
297
+ title = primary?.title ?: "",
298
+ modifier = Modifier.setAutomationId("btn_popup_allow")
299
+ )
300
+ }
301
+ }
302
+ }
303
+
304
+ @Composable
305
+ fun renderColumn(
306
+ secondary: PopupAction?,
307
+ closeAction: MutableState<String>,
308
+ primary: PopupAction?,
309
+ onAction: (onPress: (() -> Unit)?) -> Unit,
310
+ ) {
311
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
312
+ Button(
313
+ size = Size.MEDIUM,
314
+ onClick = {
315
+ closeAction.value = "button_action"
316
+ onAction(primary?.onPress)
317
+ },
318
+ title = primary?.title ?: "",
319
+ modifier = Modifier.setAutomationId("btn_popup_allow")
320
+ )
321
+ secondary?.let {
322
+ Spacer(modifier = Modifier.height(Spacing.S))
323
+ secondary.let {
324
+ Button(
325
+ size = Size.MEDIUM,
326
+ onClick = {
327
+ closeAction.value = "button_action"
328
+ onAction(it.onPress)
329
+ },
330
+ title = it.title,
331
+ type = ButtonType.TEXT,
332
+ modifier = Modifier.setAutomationId("btn_popup_cancel")
333
+ )
334
+ }
335
+ }
336
+ }
337
+ }
338
+
@@ -0,0 +1,95 @@
1
+ package vn.momo.kits.components
2
+
3
+ import androidx.compose.foundation.background
4
+ import androidx.compose.foundation.border
5
+ import androidx.compose.foundation.clickable
6
+ import androidx.compose.foundation.interaction.MutableInteractionSource
7
+ import androidx.compose.foundation.layout.Box
8
+ import androidx.compose.foundation.layout.Column
9
+ import androidx.compose.foundation.layout.aspectRatio
10
+ import androidx.compose.foundation.layout.fillMaxWidth
11
+ import androidx.compose.foundation.layout.height
12
+ import androidx.compose.foundation.layout.padding
13
+ import androidx.compose.foundation.layout.width
14
+ import androidx.compose.foundation.shape.RoundedCornerShape
15
+ import androidx.compose.runtime.Composable
16
+ import androidx.compose.runtime.remember
17
+ import androidx.compose.ui.Alignment
18
+ import androidx.compose.ui.Modifier
19
+ import androidx.compose.ui.draw.clip
20
+ import androidx.compose.ui.layout.ContentScale
21
+ import androidx.compose.ui.unit.dp
22
+ import vn.momo.kits.const.AppTheme
23
+ import vn.momo.kits.const.Radius
24
+ import vn.momo.kits.const.Spacing
25
+ import vn.momo.kits.modifier.setAutomationId
26
+ import vn.momo.kits.utils.ifNotNull
27
+
28
+ @Composable
29
+ fun PopupPromotion(
30
+ source: String = "",
31
+ onPress: (() -> Unit) = {},
32
+ onClose: (() -> Unit) = {},
33
+ ) {
34
+ Column(Modifier.padding(horizontal = Spacing.M).setAutomationId("popup_notify")) {
35
+ source.ifNotNull(
36
+ Image(
37
+ source = source,
38
+ modifier = Modifier.fillMaxWidth()
39
+ .aspectRatio(0.72f)
40
+ .clickable(
41
+ interactionSource = remember { MutableInteractionSource() },
42
+ indication = null,
43
+ onClick = onPress
44
+ ),
45
+ options = Options(alignment = Alignment.Center, contentScale = ContentScale.Crop)
46
+ ), null
47
+ )
48
+ Box(
49
+ modifier = Modifier
50
+ .padding(top = Spacing.S)
51
+ .fillMaxWidth()
52
+ .width(40.dp)
53
+ .height(40.dp),
54
+ contentAlignment = Alignment.Center
55
+ ) {
56
+ Box(
57
+ modifier = Modifier
58
+ .width(40.dp)
59
+ .height(40.dp)
60
+ .clickable(
61
+ interactionSource = remember { MutableInteractionSource() },
62
+ indication = null,
63
+ onClick = onClose
64
+ ),
65
+ contentAlignment = Alignment.Center
66
+ ) {
67
+ Box(
68
+ Modifier
69
+ .width(22.dp)
70
+ .height(22.dp)
71
+ .background(
72
+ color = AppTheme.current.colors.text.default,
73
+ shape = RoundedCornerShape(Radius.M)
74
+ )
75
+ .border(
76
+ width = 2.dp,
77
+ color = AppTheme.current.colors.background.surface,
78
+ shape = RoundedCornerShape(Radius.M)
79
+ )
80
+ .clip(RoundedCornerShape(100))
81
+ ,
82
+ contentAlignment = Alignment.Center
83
+ ) {
84
+ Icon(
85
+ source = "navigation_close",
86
+ color = AppTheme.current.colors.background.surface,
87
+ size = 16.dp,
88
+ modifier = Modifier.setAutomationId("ic_popup_close")
89
+ )
90
+ }
91
+ }
92
+
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,64 @@
1
+ package vn.momo.kits.components
2
+
3
+ import androidx.compose.foundation.border
4
+ import androidx.compose.foundation.clickable
5
+ import androidx.compose.foundation.interaction.MutableInteractionSource
6
+ import androidx.compose.foundation.layout.Arrangement
7
+ import androidx.compose.foundation.layout.Box
8
+ import androidx.compose.foundation.layout.Row
9
+ import androidx.compose.foundation.layout.padding
10
+ import androidx.compose.foundation.layout.size
11
+ import androidx.compose.foundation.shape.RoundedCornerShape
12
+ import androidx.compose.runtime.Composable
13
+ import androidx.compose.runtime.remember
14
+ import androidx.compose.ui.Alignment
15
+ import androidx.compose.ui.Modifier
16
+ import androidx.compose.ui.draw.clip
17
+ import androidx.compose.ui.unit.dp
18
+ import vn.momo.kits.const.AppTheme
19
+ import vn.momo.kits.const.Radius
20
+ import vn.momo.kits.const.Spacing
21
+ import vn.momo.kits.const.Typography
22
+ import vn.momo.kits.modifier.activeOpacityClickable
23
+
24
+ @Composable
25
+ fun Radio(
26
+ value: Any,
27
+ groupValue: Any,
28
+ disabled: Boolean = false,
29
+ onChange: () -> Unit = {},
30
+ label: String = "",
31
+ modifier: Modifier = Modifier,
32
+ ) {
33
+ val selected = value == groupValue
34
+ val borderColor = when {
35
+ disabled && selected -> AppTheme.current.colors.background.tonal
36
+ disabled -> AppTheme.current.colors.text.disable
37
+ selected -> AppTheme.current.colors.primary
38
+ else -> AppTheme.current.colors.text.default
39
+ }
40
+ val borderWidth = if (selected) 6.dp else 2.dp
41
+
42
+ Row(
43
+ modifier = modifier.activeOpacityClickable(
44
+ onClick = onChange,
45
+ enabled = !disabled,
46
+ ),
47
+ horizontalArrangement = Arrangement.Center,
48
+ verticalAlignment = Alignment.CenterVertically,
49
+ ) {
50
+ Box(
51
+ modifier = modifier
52
+ .padding(end = if (label.isEmpty()) 0.dp else Spacing.XS)
53
+ .border(borderWidth, borderColor, RoundedCornerShape(Radius.M))
54
+ .clip(RoundedCornerShape(Radius.M))
55
+ .size(20.dp)
56
+ )
57
+ if (label.isNotEmpty()) {
58
+ Text(
59
+ style = Typography.descriptionDefaultRegular,
60
+ text = label,
61
+ )
62
+ }
63
+ }
64
+ }
@@ -0,0 +1,89 @@
1
+ package vn.momo.kits.components
2
+
3
+ import androidx.compose.animation.core.LinearEasing
4
+ import androidx.compose.animation.core.RepeatMode
5
+ import androidx.compose.animation.core.animateFloat
6
+ import androidx.compose.animation.core.infiniteRepeatable
7
+ import androidx.compose.animation.core.rememberInfiniteTransition
8
+ import androidx.compose.animation.core.tween
9
+ import androidx.compose.foundation.background
10
+ import androidx.compose.foundation.layout.Box
11
+ import androidx.compose.foundation.layout.BoxWithConstraints
12
+ import androidx.compose.foundation.layout.fillMaxSize
13
+ import androidx.compose.runtime.Composable
14
+ import androidx.compose.runtime.remember
15
+ import androidx.compose.ui.Modifier
16
+ import androidx.compose.ui.draw.drawBehind
17
+ import androidx.compose.ui.geometry.Offset
18
+ import androidx.compose.ui.graphics.Brush
19
+ import vn.momo.kits.const.Colors
20
+
21
+ // Pre-computed shimmer colors to avoid repeated list creation
22
+ private val shimmerColors = listOf(
23
+ Colors.black_05,
24
+ Colors.black_03,
25
+ Colors.black_05,
26
+ )
27
+
28
+ // Constants for better performance
29
+ private const val SHADOW_BRUSH_PERCENTAGE = 0.6f
30
+ private const val DEFAULT_DURATION_MILLIS = 1000
31
+
32
+ @Composable
33
+ fun Skeleton() {
34
+ BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
35
+ // Memoize width calculation
36
+ val maxWidthValue = remember(maxWidth) { maxWidth.value.toInt() }
37
+
38
+ Box(
39
+ modifier = Modifier
40
+ .fillMaxSize()
41
+ .background(Colors.black_05)
42
+ .shimmerLoadingAnimation(maxWidth = maxWidthValue)
43
+ )
44
+ }
45
+ }
46
+
47
+ @Composable
48
+ fun Modifier.shimmerLoadingAnimation(
49
+ maxWidth: Int,
50
+ angleOfAxisY: Float = 0f,
51
+ durationMillis: Int = DEFAULT_DURATION_MILLIS,
52
+ ): Modifier {
53
+ // Memoize calculations to avoid repeated computations
54
+ val widthOfShadowBrush = remember(maxWidth) {
55
+ (maxWidth * SHADOW_BRUSH_PERCENTAGE).toInt()
56
+ }
57
+
58
+ val animationTarget = remember(durationMillis, widthOfShadowBrush) {
59
+ (durationMillis + widthOfShadowBrush).toFloat()
60
+ }
61
+
62
+ val transition = rememberInfiniteTransition(label = "shimmer")
63
+ val translateAnimation = transition.animateFloat(
64
+ initialValue = 0f,
65
+ targetValue = animationTarget,
66
+ animationSpec = infiniteRepeatable(
67
+ animation = tween(
68
+ durationMillis = durationMillis,
69
+ easing = LinearEasing,
70
+ ),
71
+ repeatMode = RepeatMode.Restart,
72
+ ),
73
+ label = "shimmerTranslate"
74
+ )
75
+
76
+ return drawBehind {
77
+ // Memoize brush creation by using remember in the calling composable
78
+ val startX = translateAnimation.value - widthOfShadowBrush
79
+ val endX = translateAnimation.value
80
+
81
+ drawRect(
82
+ brush = Brush.linearGradient(
83
+ colors = shimmerColors,
84
+ start = Offset(x = startX, y = 0f),
85
+ end = Offset(x = endX, y = angleOfAxisY)
86
+ )
87
+ )
88
+ }
89
+ }
@@ -0,0 +1,91 @@
1
+ package vn.momo.kits.components
2
+
3
+ import androidx.compose.foundation.background
4
+ import androidx.compose.foundation.border
5
+ import androidx.compose.foundation.clickable
6
+ import androidx.compose.foundation.layout.Box
7
+ import androidx.compose.foundation.layout.height
8
+ import androidx.compose.foundation.layout.padding
9
+ import androidx.compose.foundation.layout.size
10
+ import androidx.compose.foundation.layout.width
11
+ import androidx.compose.foundation.shape.RoundedCornerShape
12
+ import androidx.compose.runtime.Composable
13
+ import androidx.compose.runtime.remember
14
+ import androidx.compose.ui.Alignment
15
+ import androidx.compose.ui.Modifier
16
+ import androidx.compose.ui.draw.clip
17
+ import androidx.compose.ui.graphics.Color
18
+ import androidx.compose.ui.unit.dp
19
+ import vn.momo.kits.const.Colors
20
+ import vn.momo.kits.const.Spacing
21
+ import vn.momo.kits.modifier.setAutomationId
22
+
23
+ // Pre-computed shapes to avoid repeated creation
24
+ private val switchShape = RoundedCornerShape(20.dp)
25
+ private val circleShape = RoundedCornerShape(percent = 100)
26
+
27
+ @Composable
28
+ fun Switch(
29
+ value: Boolean = false,
30
+ onChange: (Boolean) -> Unit = {},
31
+ disabled: Boolean = false,
32
+ title: String? = "",
33
+ accessibilityId: String? = ""
34
+ ) {
35
+ // Memoize color calculations to avoid repeated conditional logic
36
+ val colors = remember(value, disabled) {
37
+ val circleColor = if (value) Colors.black_01 else Colors.black_03
38
+ val bgColor = when {
39
+ disabled && value -> Colors.green_09
40
+ disabled && !value -> Colors.black_05
41
+ value -> Colors.green_03
42
+ else -> Colors.black_07
43
+ }
44
+ circleColor to bgColor
45
+ }
46
+
47
+ val (circleBackgroundColor, backgroundColor) = colors
48
+
49
+ // Memoize automation ID to avoid string concatenation on every render
50
+ val automationIdValue = remember(accessibilityId, title, value) {
51
+ accessibilityId ?: "toggle_${title ?: "unknown"}_$value"
52
+ }
53
+
54
+ // Memoize alignment calculation
55
+ val contentAlignment = remember(value) {
56
+ if (value) Alignment.CenterEnd else Alignment.CenterStart
57
+ }
58
+
59
+ // Memoize click handler to avoid lambda recreation
60
+ val onClickHandler = remember(onChange, value) {
61
+ { onChange(!value) }
62
+ }
63
+
64
+ Box(modifier = Modifier.setAutomationId(automationIdValue)) {
65
+ Box(
66
+ modifier = Modifier
67
+ .width(38.dp)
68
+ .height(20.dp)
69
+ .border(0.dp, Color.Unspecified, switchShape)
70
+ .background(backgroundColor, switchShape)
71
+ .clip(switchShape)
72
+ .clickable(enabled = !disabled, onClick = onClickHandler)
73
+ .padding(horizontal = Spacing.XS),
74
+ contentAlignment = contentAlignment
75
+ ) {
76
+ Box(
77
+ modifier = Modifier
78
+ .size(14.dp)
79
+ .border(0.dp, Color.Unspecified, circleShape)
80
+ .background(circleBackgroundColor, circleShape),
81
+ contentAlignment = Alignment.Center
82
+ ) {
83
+ Box(
84
+ Modifier
85
+ .size(6.dp)
86
+ .background(backgroundColor, circleShape)
87
+ )
88
+ }
89
+ }
90
+ }
91
+ }