@momo-kits/native-kits 0.157.6 → 0.157.7-debug

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/build.gradle.kts +11 -0
  2. package/compose/build.gradle.kts +180 -0
  3. package/compose/build.gradle.kts.backup +180 -0
  4. package/compose/compose.podspec +54 -0
  5. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +117 -0
  6. package/compose/src/commonMain/composeResources/font/momosignature.otf +0 -0
  7. package/compose/src/commonMain/composeResources/font/momotrustdisplay.otf +0 -0
  8. package/compose/src/commonMain/composeResources/font/sfprotext_black.otf +0 -0
  9. package/compose/src/commonMain/composeResources/font/sfprotext_black.ttf +0 -0
  10. package/compose/src/commonMain/composeResources/font/sfprotext_bold.ttf +0 -0
  11. package/compose/src/commonMain/composeResources/font/sfprotext_heavy.ttf +0 -0
  12. package/compose/src/commonMain/composeResources/font/sfprotext_light.ttf +0 -0
  13. package/compose/src/commonMain/composeResources/font/sfprotext_medium.ttf +0 -0
  14. package/compose/src/commonMain/composeResources/font/sfprotext_regular.ttf +0 -0
  15. package/compose/src/commonMain/composeResources/font/sfprotext_semibold.ttf +0 -0
  16. package/compose/src/commonMain/composeResources/font/sfprotext_thin.otf +0 -0
  17. package/compose/src/commonMain/composeResources/font/sfprotext_thin.ttf +0 -0
  18. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.otf +0 -0
  19. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.ttf +0 -0
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +57 -0
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +107 -0
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +201 -0
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +222 -0
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +48 -0
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +86 -0
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +76 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +76 -0
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +305 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +33 -0
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +720 -0
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +121 -0
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +405 -0
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +69 -0
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +157 -0
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +85 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +32 -0
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +340 -0
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +198 -0
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +357 -0
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +123 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +94 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +136 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +224 -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 +76 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +148 -0
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +188 -0
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +116 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +448 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +172 -0
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +255 -0
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +231 -0
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +233 -0
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +254 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +241 -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/Loader.kt +108 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +56 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +41 -0
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +92 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +40 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +352 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +103 -0
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +338 -0
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +70 -0
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +87 -0
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +17 -0
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +96 -0
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +348 -0
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +256 -0
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +494 -0
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +131 -0
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +215 -0
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +96 -0
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +531 -0
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +92 -0
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +130 -0
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +214 -0
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +590 -0
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +177 -0
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +192 -0
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +205 -0
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +239 -0
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +191 -0
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +306 -0
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +16 -0
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +188 -0
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +285 -0
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +2 -0
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +35 -0
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +2 -0
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +50 -0
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +68 -0
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +14 -0
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +50 -0
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +239 -0
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +119 -0
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +98 -0
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +161 -0
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +331 -0
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +497 -0
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +162 -0
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +243 -0
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +187 -0
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +279 -0
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +80 -0
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +306 -0
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +32 -0
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +370 -0
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +132 -0
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +46 -0
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +62 -0
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +15 -0
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +88 -0
  122. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +161 -0
  123. package/gradle/libs.versions.toml +57 -0
  124. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  125. package/gradle/wrapper/gradle-wrapper.properties +8 -0
  126. package/gradle.properties +26 -0
  127. package/gradlew +252 -0
  128. package/gradlew.bat +94 -0
  129. package/package.json +1 -1
  130. package/settings.gradle.kts +52 -0
@@ -0,0 +1,198 @@
1
+ package vn.momo.kits.components
2
+
3
+ import androidx.compose.foundation.Canvas
4
+ import androidx.compose.foundation.background
5
+ import androidx.compose.foundation.border
6
+ import androidx.compose.foundation.layout.Box
7
+ import androidx.compose.foundation.layout.WindowInsets
8
+ import androidx.compose.foundation.layout.asPaddingValues
9
+ import androidx.compose.foundation.layout.fillMaxHeight
10
+ import androidx.compose.foundation.layout.fillMaxSize
11
+ import androidx.compose.foundation.layout.fillMaxWidth
12
+ import androidx.compose.foundation.layout.height
13
+ import androidx.compose.foundation.layout.navigationBars
14
+ import androidx.compose.foundation.layout.padding
15
+ import androidx.compose.foundation.layout.width
16
+ import androidx.compose.runtime.Composable
17
+ import androidx.compose.ui.Alignment
18
+ import androidx.compose.ui.Modifier
19
+ import androidx.compose.ui.geometry.Offset
20
+ import androidx.compose.ui.graphics.Color
21
+ import androidx.compose.ui.graphics.PathEffect
22
+ import androidx.compose.ui.graphics.StrokeCap
23
+ import androidx.compose.ui.unit.dp
24
+ import io.ktor.util.Platform
25
+ import vn.momo.kits.application.IsShowBaseLineDebug
26
+ import vn.momo.kits.const.Colors
27
+ import vn.momo.kits.modifier.conditional
28
+ import vn.momo.kits.platform.getPlatformName
29
+ import vn.momo.kits.platform.getStatusBarHeight
30
+
31
+ /**
32
+ * A debug overlay that draws danger/warning baseline guides on top of the screen.
33
+ *
34
+ * Highlights safe-area boundaries, header regions, and bottom navigation zones
35
+ * using colored solid or dotted lines and semi-transparent red zones.
36
+ *
37
+ * @param enabled When `false` the composable renders nothing. Pass `false` when
38
+ * your QC automation flag is active to suppress the overlay.
39
+ */
40
+ @Composable
41
+ fun BaselineView(enabled: Boolean = true) {
42
+ if (!enabled) return
43
+
44
+ val bottomInsetHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
45
+ val topInset = if (getPlatformName() == "Android") getStatusBarHeight() - 14.dp else getStatusBarHeight()
46
+ val bottomInset = if (getPlatformName() == "iOS") minOf(bottomInsetHeight, 21.dp) else bottomInsetHeight
47
+
48
+ Box(modifier = Modifier.fillMaxSize()) {
49
+ // Danger zones
50
+ Box(
51
+ modifier = Modifier
52
+ .fillMaxWidth()
53
+ .height(topInset)
54
+ .background(Color.Red.copy(alpha = 0.15f))
55
+ )
56
+ Box(
57
+ modifier = Modifier
58
+ .width(12.dp)
59
+ .fillMaxHeight()
60
+ .padding(top = topInset, bottom = bottomInset)
61
+ .background(Color.Red.copy(alpha = 0.15f))
62
+ )
63
+ Box(
64
+ modifier = Modifier
65
+ .width(12.dp)
66
+ .fillMaxHeight()
67
+ .padding(top = topInset, bottom = bottomInset)
68
+ .background(Color.Red.copy(alpha = 0.15f))
69
+ .align(Alignment.TopEnd)
70
+ )
71
+ Box(
72
+ modifier = Modifier
73
+ .fillMaxWidth()
74
+ .height(bottomInset)
75
+ .background(Color.Red.copy(alpha = 0.15f))
76
+ .align(Alignment.BottomCenter)
77
+ )
78
+
79
+ // Danger lines
80
+ BaselineDottedLine(
81
+ Modifier
82
+ .padding(top = topInset)
83
+ .fillMaxWidth(),
84
+ isDotted = false,
85
+ color = Color(0xFFE400FF)
86
+ )
87
+ BaselineDottedLine(
88
+ Modifier
89
+ .padding(top = topInset + 52.dp)
90
+ .fillMaxWidth(),
91
+ color = Color(0xFFE400FF)
92
+ )
93
+ BaselineDottedLine(
94
+ Modifier
95
+ .padding(top = topInset + 104.dp)
96
+ .fillMaxWidth(),
97
+ color = Color(0xFFE400FF)
98
+ )
99
+ BaselineDottedLine(
100
+ Modifier
101
+ .padding(bottom = bottomInset + 64.dp)
102
+ .fillMaxWidth()
103
+ .align(Alignment.BottomCenter),
104
+ color = Color(0xFFE400FF)
105
+ )
106
+ BaselineDottedLine(
107
+ Modifier
108
+ .padding(bottom = bottomInset)
109
+ .fillMaxWidth()
110
+ .align(Alignment.BottomCenter),
111
+ isDotted = false,
112
+ color = Color(0xFFE400FF)
113
+ )
114
+ BaselineDottedLine(
115
+ Modifier
116
+ .padding(start = 12.dp)
117
+ .fillMaxHeight(),
118
+ orientation = BaselineOrientation.Vertical,
119
+ isDotted = false,
120
+ color = Color(0xFFE400FF)
121
+ )
122
+ BaselineDottedLine(
123
+ Modifier
124
+ .padding(end = 12.dp)
125
+ .fillMaxHeight()
126
+ .align(Alignment.BottomEnd),
127
+ orientation = BaselineOrientation.Vertical,
128
+ isDotted = false,
129
+ color = Color(0xFFE400FF)
130
+ )
131
+
132
+ // Warning lines
133
+ BaselineDottedLine(
134
+ Modifier
135
+ .padding(top = topInset + 26.dp)
136
+ .fillMaxWidth(),
137
+ color = Color(0xFFFF7A00)
138
+ )
139
+ BaselineDottedLine(
140
+ Modifier
141
+ .padding(bottom = bottomInset + 56.dp)
142
+ .fillMaxWidth()
143
+ .align(Alignment.BottomCenter),
144
+ color = Color(0xFFFFCC00)
145
+ )
146
+ BaselineDottedLine(
147
+ Modifier
148
+ .padding(bottom = bottomInset + 8.dp)
149
+ .fillMaxWidth()
150
+ .align(Alignment.BottomCenter),
151
+ color = Color(0xFFFFCC00)
152
+ )
153
+
154
+ // Header background warning lines
155
+ BaselineDottedLine(
156
+ Modifier
157
+ .padding(start = 40.dp, top = topInset)
158
+ .height(52.dp),
159
+ color = Color(0xFF00C520),
160
+ orientation = BaselineOrientation.Vertical
161
+ )
162
+ BaselineDottedLine(
163
+ Modifier
164
+ .padding(start = 48.dp, top = topInset)
165
+ .height(52.dp),
166
+ color = Color(0xFF00C520),
167
+ orientation = BaselineOrientation.Vertical
168
+ )
169
+ }
170
+ }
171
+
172
+ enum class BaselineOrientation { Horizontal, Vertical }
173
+
174
+ @Composable
175
+ fun BaselineDottedLine(
176
+ modifier: Modifier = Modifier,
177
+ color: Color = Color.Red,
178
+ orientation: BaselineOrientation = BaselineOrientation.Horizontal,
179
+ isDotted: Boolean = true
180
+ ) {
181
+ Canvas(modifier = modifier) {
182
+ val pathEffect = if (isDotted) PathEffect.dashPathEffect(floatArrayOf(8f, 8f)) else null
183
+ drawLine(
184
+ color = color,
185
+ start = if (orientation == BaselineOrientation.Horizontal) Offset(
186
+ 0f,
187
+ size.height / 2
188
+ ) else Offset(size.width / 2, 0f),
189
+ end = if (orientation == BaselineOrientation.Horizontal) Offset(
190
+ size.width,
191
+ size.height / 2
192
+ ) else Offset(size.width / 2, size.height),
193
+ strokeWidth = 1.dp.toPx(),
194
+ pathEffect = pathEffect,
195
+ cap = StrokeCap.Round,
196
+ )
197
+ }
198
+ }
@@ -0,0 +1,357 @@
1
+ package vn.momo.kits.components
2
+
3
+ import androidx.compose.animation.core.animateDpAsState
4
+ import androidx.compose.animation.core.animateFloatAsState
5
+ import androidx.compose.animation.core.tween
6
+ import androidx.compose.foundation.background
7
+ import androidx.compose.foundation.border
8
+ import androidx.compose.foundation.clickable
9
+ import androidx.compose.foundation.interaction.MutableInteractionSource
10
+ import androidx.compose.foundation.interaction.collectIsPressedAsState
11
+ import androidx.compose.foundation.layout.Arrangement
12
+ import androidx.compose.foundation.layout.Box
13
+ import androidx.compose.foundation.layout.Row
14
+ import androidx.compose.foundation.layout.fillMaxWidth
15
+ import androidx.compose.foundation.layout.height
16
+ import androidx.compose.foundation.layout.padding
17
+ import androidx.compose.foundation.layout.size
18
+ import androidx.compose.foundation.layout.wrapContentHeight
19
+ import androidx.compose.foundation.shape.RoundedCornerShape
20
+ import androidx.compose.runtime.Composable
21
+ import androidx.compose.runtime.getValue
22
+ import androidx.compose.runtime.remember
23
+ import androidx.compose.ui.Alignment
24
+ import androidx.compose.ui.Modifier
25
+ import androidx.compose.ui.draw.alpha
26
+ import androidx.compose.ui.draw.clip
27
+ import androidx.compose.ui.graphics.Color
28
+ import androidx.compose.ui.text.TextStyle
29
+ import androidx.compose.ui.text.style.TextOverflow
30
+ import androidx.compose.ui.unit.Dp
31
+ import androidx.compose.ui.unit.dp
32
+ import vn.momo.kits.application.IsShowBaseLineDebug
33
+ import vn.momo.kits.const.AppTheme
34
+ import vn.momo.kits.const.Colors
35
+ import vn.momo.kits.const.Radius
36
+ import vn.momo.kits.const.Spacing
37
+ import vn.momo.kits.const.Typography
38
+ import vn.momo.kits.modifier.conditional
39
+ import vn.momo.kits.platform.LottieAnimation
40
+
41
+ enum class ButtonType {
42
+ PRIMARY,
43
+ SECONDARY,
44
+ TONAL,
45
+ OUTLINE,
46
+ DANGER,
47
+ TEXT,
48
+ DISABLED
49
+ }
50
+
51
+ data class ButtonSpecs(
52
+ val height: Dp,
53
+ val radius: Dp,
54
+ val padding: Dp,
55
+ val width: Dp,
56
+ )
57
+
58
+ enum class Size(val value: ButtonSpecs) {
59
+ LARGE(
60
+ ButtonSpecs(
61
+ height = 48.dp,
62
+ radius = Radius.S,
63
+ padding = Spacing.L,
64
+ width = 128.dp
65
+ )
66
+ ),
67
+ MEDIUM(
68
+ ButtonSpecs(
69
+ height = 36.dp,
70
+ radius = Radius.S,
71
+ padding = Spacing.M,
72
+ width = 80.dp
73
+ )
74
+ ),
75
+ SMALL(
76
+ ButtonSpecs(
77
+ height = 28.dp,
78
+ radius = Radius.S,
79
+ padding = Spacing.S,
80
+ width = 60.dp
81
+ )
82
+ )
83
+ }
84
+
85
+ private val styleCache = mapOf(
86
+ Size.LARGE to Typography.actionDefaultBold,
87
+ Size.MEDIUM to Typography.actionSBold,
88
+ Size.SMALL to Typography.actionXsBold
89
+ )
90
+
91
+ private val iconSizeCache = mapOf(
92
+ Size.LARGE to 24.dp,
93
+ Size.MEDIUM to 16.dp,
94
+ Size.SMALL to 16.dp
95
+ )
96
+
97
+ private val iconSpaceCache = mapOf(
98
+ Size.SMALL to Spacing.XS,
99
+ Size.MEDIUM to Spacing.S,
100
+ Size.LARGE to Spacing.S
101
+ )
102
+
103
+ fun getStyle(size: Size): TextStyle {
104
+ return styleCache[size] ?: Typography.actionDefaultBold
105
+ }
106
+
107
+ fun getIconSize(size: Size): Dp = iconSizeCache[size] ?: 24.dp
108
+ fun getIconSpace(size: Size): Dp = iconSpaceCache[size] ?: Spacing.S
109
+
110
+ @Composable
111
+ fun getTextColor(loading: Boolean, type: ButtonType): Color {
112
+ val theme = AppTheme.current
113
+
114
+ return remember(type, theme, loading) {
115
+ when (type) {
116
+ ButtonType.DISABLED -> theme.colors.text.disable
117
+ ButtonType.PRIMARY -> Colors.black_01
118
+ ButtonType.SECONDARY -> theme.colors.text.default
119
+ ButtonType.OUTLINE -> theme.colors.primary
120
+ ButtonType.TONAL -> theme.colors.primary
121
+ ButtonType.DANGER -> Colors.black_01
122
+ ButtonType.TEXT -> theme.colors.primary
123
+ }.withLoading(loading)
124
+ }
125
+ }
126
+
127
+ @Composable
128
+ fun RenderTitle(size: Size, title: String = "", textColor: Color) {
129
+ val style = remember(size) { getStyle(size) }
130
+ Text(
131
+ style = style,
132
+ text = title,
133
+ color = textColor,
134
+ overflow = TextOverflow.Ellipsis,
135
+ maxLines = 1
136
+ )
137
+ }
138
+
139
+ @Composable
140
+ fun RenderIcon(
141
+ size: Size,
142
+ isIconLeft: Boolean,
143
+ useTintColor: Boolean = true,
144
+ icon: String = "",
145
+ forceLoading: Boolean = false,
146
+ bgColor: Color? = null,
147
+ textColor: Color? = null,
148
+ ) {
149
+ val iconSize = remember(size) { getIconSize(size) }
150
+ val margin = remember(size) { getIconSpace(size) }
151
+ val color = if (useTintColor) textColor else Color.Unspecified
152
+
153
+ val modifier = Modifier.padding(
154
+ start = if (isIconLeft) 0.dp else margin,
155
+ end = if (isIconLeft) margin else 0.dp
156
+ )
157
+
158
+ if (forceLoading) {
159
+ Box(modifier) {
160
+ LottieAnimation(
161
+ modifier = Modifier.size(iconSize),
162
+ bgColor = bgColor,
163
+ tintColor = color,
164
+ path = "files/lottie_circle_loader"
165
+ )
166
+ }
167
+ } else if (icon.isNotEmpty()) {
168
+ Icon(
169
+ source = icon,
170
+ color = color,
171
+ size = iconSize,
172
+ modifier = modifier
173
+ )
174
+ }
175
+ }
176
+
177
+ private fun shouldLoadingOnLeft(iconLeft: String, iconRight: String): Boolean {
178
+ val hasLeft = iconLeft.isNotEmpty()
179
+ val hasRight = iconRight.isNotEmpty()
180
+ return when {
181
+ !hasLeft && !hasRight -> true
182
+ hasLeft && !hasRight -> true
183
+ !hasLeft && hasRight -> false
184
+ hasLeft && hasRight -> false
185
+ else -> true
186
+ }
187
+ }
188
+
189
+ @Composable
190
+ fun getTypeStyle(
191
+ type: ButtonType,
192
+ color: Color? = AppTheme.current.colors.primary,
193
+ size: Size,
194
+ bgColor: Color,
195
+ ): Modifier {
196
+ val theme = AppTheme.current
197
+ val radius = remember(size) { size.value.radius }
198
+ val modifier = Modifier.background(bgColor)
199
+
200
+ return remember(type, color, theme, radius, bgColor) {
201
+ when (type) {
202
+ ButtonType.DISABLED -> modifier
203
+ .border(0.dp, Color.Unspecified, RoundedCornerShape(radius))
204
+
205
+ ButtonType.PRIMARY -> modifier
206
+ .border(0.dp, Color.Unspecified, RoundedCornerShape(radius))
207
+
208
+ ButtonType.SECONDARY -> modifier
209
+ .border(1.dp, theme.colors.border.default, RoundedCornerShape(radius))
210
+
211
+ ButtonType.OUTLINE -> modifier
212
+ .border(1.dp, color ?: theme.colors.primary, RoundedCornerShape(radius))
213
+
214
+ ButtonType.TONAL -> modifier
215
+ .border(0.dp, Color.Unspecified, RoundedCornerShape(radius))
216
+
217
+ ButtonType.DANGER -> modifier
218
+ .border(0.dp, Color.Unspecified, RoundedCornerShape(radius))
219
+
220
+ ButtonType.TEXT -> modifier
221
+ }
222
+ }
223
+ }
224
+
225
+ @Composable
226
+ fun getButtonBackgroundColor(
227
+ loading: Boolean,
228
+ type: ButtonType
229
+ ): Color {
230
+ val theme = AppTheme.current
231
+
232
+ return remember(loading, type, theme) {
233
+ when (type) {
234
+ ButtonType.DISABLED -> theme.colors.background.disable.withLoading(loading)
235
+ ButtonType.PRIMARY -> theme.colors.primary.withLoading(loading)
236
+ ButtonType.SECONDARY -> theme.colors.background.surface.withLoading(loading)
237
+ ButtonType.OUTLINE -> theme.colors.background.surface.withLoading(loading)
238
+ ButtonType.TONAL -> theme.colors.background.tonal.withLoading(loading)
239
+ ButtonType.DANGER -> theme.colors.error.primary.withLoading(loading)
240
+ ButtonType.TEXT -> Color.Unspecified
241
+ }
242
+ }
243
+ }
244
+
245
+ fun Color.withLoading(loading: Boolean): Color =
246
+ this.copy(alpha = if (loading) 0.75f else 1f)
247
+
248
+ @Composable
249
+ fun Button(
250
+ onClick: () -> Unit,
251
+ type: ButtonType = ButtonType.PRIMARY,
252
+ size: Size = Size.LARGE,
253
+ iconRight: String = "",
254
+ iconLeft: String = "",
255
+ title: String = "Button",
256
+ loading: Boolean = false,
257
+ useTintColor: Boolean = true,
258
+ isFull: Boolean = true,
259
+ modifier: Modifier = Modifier,
260
+ ) {
261
+ val radius = remember(size) { size.value.radius }
262
+ val isEnabled = remember(type) { type != ButtonType.DISABLED }
263
+ val loadingOnLeft = remember(iconLeft, iconRight) {
264
+ shouldLoadingOnLeft(iconLeft, iconRight)
265
+ }
266
+
267
+ val sizeSpecs = remember(size) { size.value }
268
+
269
+ val interactionSource = remember { MutableInteractionSource() }
270
+ val isPressed by interactionSource.collectIsPressedAsState()
271
+
272
+ val animatedPadding by animateDpAsState(
273
+ targetValue = if (isPressed && isEnabled && !loading) 2.dp else 0.dp,
274
+ animationSpec = tween(100),
275
+ label = "pressPadding"
276
+ )
277
+
278
+ val targetAlpha = if (isPressed && isEnabled && !loading) 0.5f else 1f
279
+ val alpha by animateFloatAsState(
280
+ targetValue = targetAlpha,
281
+ animationSpec = tween(100),
282
+ label = "buttonPressAlpha"
283
+ )
284
+
285
+ val clickableModifier =
286
+ if (isEnabled && !loading) {
287
+ modifier
288
+ .clip(RoundedCornerShape(radius))
289
+ .clickable(
290
+ enabled = isEnabled && !loading,
291
+ interactionSource = interactionSource,
292
+ indication = null,
293
+ onClick = onClick
294
+ )
295
+ .alpha(alpha)
296
+ } else {
297
+ modifier
298
+ .clip(RoundedCornerShape(radius))
299
+ }
300
+
301
+ val bgColor = getButtonBackgroundColor(loading, type)
302
+ val textColor = getTextColor(loading, type)
303
+
304
+ val rootModifier = if (isFull) {
305
+ Modifier
306
+ .fillMaxWidth()
307
+ .wrapContentHeight()
308
+ } else {
309
+ Modifier
310
+ }
311
+
312
+ Box(
313
+ modifier = rootModifier.then(clickableModifier),
314
+ contentAlignment = Alignment.Center
315
+ ) {
316
+ // Visual background (shrinks on press)
317
+ Box(
318
+ modifier = Modifier
319
+ .matchParentSize()
320
+ .padding(horizontal = animatedPadding)
321
+ .clip(RoundedCornerShape(radius))
322
+ .then(getTypeStyle(type, size = size, bgColor = bgColor))
323
+ .conditional(IsShowBaseLineDebug) {
324
+ border(1.dp, Colors.blue_03)
325
+ }
326
+ )
327
+
328
+ // Content
329
+ Row(
330
+ modifier = Modifier
331
+ .padding(horizontal = sizeSpecs.padding)
332
+ .height(sizeSpecs.height),
333
+ horizontalArrangement = Arrangement.Center,
334
+ verticalAlignment = Alignment.CenterVertically,
335
+ ) {
336
+ RenderIcon(
337
+ size = size,
338
+ isIconLeft = true,
339
+ useTintColor = useTintColor,
340
+ icon = iconLeft,
341
+ forceLoading = loading && loadingOnLeft,
342
+ bgColor = bgColor,
343
+ textColor = textColor
344
+ )
345
+ RenderTitle(size, title, textColor = textColor)
346
+ RenderIcon(
347
+ size = size,
348
+ isIconLeft = false,
349
+ useTintColor = useTintColor,
350
+ icon = iconRight,
351
+ forceLoading = loading && !loadingOnLeft,
352
+ bgColor = bgColor,
353
+ textColor = textColor
354
+ )
355
+ }
356
+ }
357
+ }
@@ -0,0 +1,123 @@
1
+ package vn.momo.kits.components
2
+
3
+ import androidx.compose.animation.core.animateFloatAsState
4
+ import androidx.compose.animation.core.tween
5
+ import androidx.compose.foundation.border
6
+ import androidx.compose.foundation.layout.Box
7
+ import androidx.compose.foundation.layout.PaddingValues
8
+ import androidx.compose.foundation.layout.fillMaxWidth
9
+ import androidx.compose.foundation.pager.HorizontalPager
10
+ import androidx.compose.foundation.pager.rememberPagerState
11
+ import androidx.compose.runtime.Composable
12
+ import androidx.compose.runtime.LaunchedEffect
13
+ import androidx.compose.runtime.getValue
14
+ import androidx.compose.runtime.mutableStateOf
15
+ import androidx.compose.runtime.remember
16
+ import androidx.compose.runtime.setValue
17
+ import androidx.compose.runtime.snapshotFlow
18
+ import androidx.compose.ui.Modifier
19
+ import androidx.compose.ui.graphics.graphicsLayer
20
+ import androidx.compose.ui.unit.dp
21
+ import kotlinx.coroutines.delay
22
+ import kotlinx.coroutines.isActive
23
+ import vn.momo.kits.application.IsShowBaseLineDebug
24
+ import vn.momo.kits.const.Colors
25
+ import vn.momo.kits.modifier.conditional
26
+ import kotlin.math.absoluteValue
27
+
28
+ @Composable
29
+ fun Carousel(
30
+ itemCount: Int,
31
+ modifier: Modifier = Modifier,
32
+ loop: Boolean = false,
33
+ autoplay: Boolean = false,
34
+ autoplayInterval: Long = 3000L,
35
+ enableSnap: Boolean = true,
36
+ scrollEnabled: Boolean = true,
37
+ inactiveSlideOpacity: Float = 1f,
38
+ inactiveSlideScale: Float = 1f,
39
+ contentPadding: PaddingValues = PaddingValues(0.dp),
40
+ onSnapToItem: (Int) -> Unit = {},
41
+ onScrollIndexChanged: (Int) -> Unit = {},
42
+ content: @Composable (index: Int) -> Unit,
43
+ ) {
44
+ if (itemCount <= 0) return
45
+
46
+ val virtualPageCount = if (loop) itemCount * 1000 else itemCount
47
+ val startPage = if (loop) (virtualPageCount / 2) - ((virtualPageCount / 2) % itemCount) else 0
48
+
49
+ val pagerState = rememberPagerState(initialPage = startPage) { virtualPageCount }
50
+
51
+ // Map virtual page to real index
52
+ val realIndex: (Int) -> Int = { page -> page % itemCount }
53
+
54
+ // Track user interaction for autoplay pause
55
+ var isUserScrolling by remember { mutableStateOf(false) }
56
+
57
+ // Autoplay
58
+ if (autoplay && itemCount > 1) {
59
+ LaunchedEffect(autoplayInterval, isUserScrolling) {
60
+ if (isUserScrolling) return@LaunchedEffect
61
+ while (isActive) {
62
+ delay(autoplayInterval)
63
+ if (!isUserScrolling) {
64
+ val nextPage = pagerState.currentPage + 1
65
+ if (nextPage < virtualPageCount) {
66
+ pagerState.animateScrollToPage(nextPage)
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+
73
+ // Track scroll state for callbacks
74
+ LaunchedEffect(pagerState) {
75
+ snapshotFlow { pagerState.isScrollInProgress }.collect { scrolling ->
76
+ isUserScrolling = scrolling
77
+ }
78
+ }
79
+
80
+ LaunchedEffect(pagerState) {
81
+ snapshotFlow { pagerState.currentPage }.collect { page ->
82
+ val real = realIndex(page)
83
+ onSnapToItem(real)
84
+ onScrollIndexChanged(real)
85
+ }
86
+ }
87
+
88
+ HorizontalPager(
89
+ state = pagerState,
90
+ modifier = modifier
91
+ .fillMaxWidth()
92
+ .conditional(IsShowBaseLineDebug) { border(1.dp, Colors.blue_03) },
93
+ contentPadding = contentPadding,
94
+ userScrollEnabled = scrollEnabled,
95
+ beyondViewportPageCount = if (loop) 1 else 0,
96
+ ) { page ->
97
+ val real = realIndex(page)
98
+
99
+ val pageOffset = ((pagerState.currentPage - page) + pagerState.currentPageOffsetFraction).absoluteValue
100
+
101
+ val scale by animateFloatAsState(
102
+ targetValue = if (pageOffset < 0.5f) 1f else inactiveSlideScale,
103
+ animationSpec = tween(150),
104
+ label = "carouselScale"
105
+ )
106
+ val alpha by animateFloatAsState(
107
+ targetValue = if (pageOffset < 0.5f) 1f else inactiveSlideOpacity,
108
+ animationSpec = tween(150),
109
+ label = "carouselAlpha"
110
+ )
111
+
112
+ Box(
113
+ modifier = Modifier
114
+ .graphicsLayer {
115
+ scaleX = scale
116
+ scaleY = scale
117
+ this.alpha = alpha
118
+ }
119
+ ) {
120
+ content(real)
121
+ }
122
+ }
123
+ }