@momo-kits/native-kits 0.125.1-securityCustom.0 → 0.127.0-rc.1
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.
- package/build/kotlin/commonizedNativeDistributionLocation.txt +1 -0
- package/compose/build/generated/compose/resourceGenerator/kotlin/androidMainResourceCollectors/vn/momo/uikits/resources/ActualResourceCollectors.kt +49 -0
- package/compose/build/generated/compose/resourceGenerator/kotlin/commonMainResourceAccessors/vn/momo/uikits/resources/Font0.commonMain.kt +187 -0
- package/compose/build/generated/compose/resourceGenerator/kotlin/commonMainResourceCollectors/vn/momo/uikits/resources/ExpectResourceCollectors.kt +25 -0
- package/compose/build/generated/compose/resourceGenerator/kotlin/commonResClass/vn/momo/uikits/resources/Res.kt +49 -0
- package/compose/build/generated/compose/resourceGenerator/kotlin/iosArm64MainResourceCollectors/vn/momo/uikits/resources/ActualResourceCollectors.kt +49 -0
- package/compose/build/generated/compose/resourceGenerator/preparedResources/commonMain/composeResources/font/momosignature.otf +0 -0
- package/compose/build/generated/compose/resourceGenerator/preparedResources/commonMain/composeResources/font/momotrustdisplay.otf +0 -0
- package/compose/build/generated/compose/resourceGenerator/preparedResources/commonMain/composeResources/font/sfprotext_black.otf +0 -0
- package/compose/build/generated/compose/resourceGenerator/preparedResources/commonMain/composeResources/font/sfprotext_black.ttf +0 -0
- package/compose/build/generated/compose/resourceGenerator/preparedResources/commonMain/composeResources/font/sfprotext_bold.ttf +0 -0
- package/compose/build/generated/compose/resourceGenerator/preparedResources/commonMain/composeResources/font/sfprotext_heavy.ttf +0 -0
- package/compose/build/generated/compose/resourceGenerator/preparedResources/commonMain/composeResources/font/sfprotext_light.ttf +0 -0
- package/compose/build/generated/compose/resourceGenerator/preparedResources/commonMain/composeResources/font/sfprotext_medium.ttf +0 -0
- package/compose/build/generated/compose/resourceGenerator/preparedResources/commonMain/composeResources/font/sfprotext_regular.ttf +0 -0
- package/compose/build/generated/compose/resourceGenerator/preparedResources/commonMain/composeResources/font/sfprotext_semibold.ttf +0 -0
- package/compose/build/generated/compose/resourceGenerator/preparedResources/commonMain/composeResources/font/sfprotext_thin.otf +0 -0
- package/compose/build/generated/compose/resourceGenerator/preparedResources/commonMain/composeResources/font/sfprotext_thin.ttf +0 -0
- package/compose/build/generated/compose/resourceGenerator/preparedResources/commonMain/composeResources/font/sfprotext_ultralight.otf +0 -0
- package/compose/build/generated/compose/resourceGenerator/preparedResources/commonMain/composeResources/font/sfprotext_ultralight.ttf +0 -0
- package/compose/build.gradle.kts +17 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +63 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +12 -7
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +79 -25
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +87 -72
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +33 -20
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +33 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +61 -39
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +70 -52
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +131 -94
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +39 -19
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +54 -38
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +50 -28
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +29 -5
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +6 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +15 -5
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +3 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +3 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +161 -21
- package/local.properties +8 -0
- package/package.json +4 -2
- package/settings.gradle.kts +3 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/PlatformApi.kt +0 -12
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
package vn.momo.kits.application
|
|
2
2
|
|
|
3
3
|
import androidx.compose.animation.animateColorAsState
|
|
4
|
+
import androidx.compose.animation.core.AnimationSpec
|
|
4
5
|
import androidx.compose.animation.core.animateFloatAsState
|
|
6
|
+
import androidx.compose.animation.core.tween
|
|
5
7
|
import androidx.compose.runtime.Composable
|
|
8
|
+
import androidx.compose.runtime.derivedStateOf
|
|
6
9
|
import androidx.compose.runtime.getValue
|
|
10
|
+
import androidx.compose.runtime.remember
|
|
7
11
|
import androidx.compose.ui.graphics.Color
|
|
8
12
|
import androidx.compose.ui.unit.Dp
|
|
9
13
|
import androidx.compose.ui.unit.dp
|
|
@@ -21,6 +25,23 @@ data class HeaderAnimations(
|
|
|
21
25
|
|
|
22
26
|
private const val SCREEN_PADDING = 12
|
|
23
27
|
private const val BACK_WIDTH = 28
|
|
28
|
+
private const val HEADER_HEIGHT_CONST = 100f
|
|
29
|
+
|
|
30
|
+
// Optimized animation spec for better performance
|
|
31
|
+
private val defaultAnimationSpec: AnimationSpec<Float> = tween(
|
|
32
|
+
durationMillis = 300,
|
|
33
|
+
delayMillis = 0
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
// Helper function for color interpolation - moved outside composable for better performance
|
|
37
|
+
private fun interpolateColor(from: Color, to: Color, progress: Float): Color {
|
|
38
|
+
return Color(
|
|
39
|
+
red = from.red + (to.red - from.red) * progress,
|
|
40
|
+
green = from.green + (to.green - from.green) * progress,
|
|
41
|
+
blue = from.blue + (to.blue - from.blue) * progress,
|
|
42
|
+
alpha = from.alpha + (to.alpha - from.alpha) * progress
|
|
43
|
+
)
|
|
44
|
+
}
|
|
24
45
|
|
|
25
46
|
@Composable
|
|
26
47
|
fun useHeaderSearchAnimation(
|
|
@@ -29,40 +50,73 @@ fun useHeaderSearchAnimation(
|
|
|
29
50
|
headerRightWidth: Dp,
|
|
30
51
|
isBack: Boolean
|
|
31
52
|
): HeaderAnimations {
|
|
32
|
-
|
|
33
|
-
val
|
|
34
|
-
val
|
|
53
|
+
// Cache screen calculations - call getScreenDimensions directly in composable context
|
|
54
|
+
val screenDimensions = getScreenDimensions()
|
|
55
|
+
val screenWidth = remember(screenDimensions) { screenDimensions.width }
|
|
56
|
+
|
|
57
|
+
// Pre-calculate static values
|
|
58
|
+
val leftPosition = remember(isBack) {
|
|
59
|
+
if (isBack) (BACK_WIDTH + 20).dp else 12.dp
|
|
60
|
+
}
|
|
35
61
|
|
|
62
|
+
val searchWidth = remember(screenWidth) {
|
|
63
|
+
screenWidth - (SCREEN_PADDING * 2)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Use derivedStateOf for computed scroll progress to avoid unnecessary recompositions
|
|
67
|
+
val scrollProgress by remember {
|
|
68
|
+
derivedStateOf {
|
|
69
|
+
(scrollState / HEADER_HEIGHT_CONST).coerceIn(0f, 1f)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Optimize color animation with remember
|
|
74
|
+
val theme = AppTheme.current
|
|
36
75
|
val backgroundSearch by animateColorAsState(
|
|
37
|
-
targetValue =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
76
|
+
targetValue = remember(opacityAni, theme) {
|
|
77
|
+
interpolateColor(
|
|
78
|
+
Colors.black_01,
|
|
79
|
+
theme.colors.background.default,
|
|
80
|
+
opacityAni
|
|
81
|
+
)
|
|
82
|
+
},
|
|
83
|
+
animationSpec = tween(durationMillis = 200),
|
|
84
|
+
label = "backgroundSearch"
|
|
42
85
|
)
|
|
43
86
|
|
|
87
|
+
// Consolidate translation and width calculations
|
|
44
88
|
val animatedTranslateX by animateFloatAsState(
|
|
45
|
-
targetValue = (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
89
|
+
targetValue = remember(scrollProgress, leftPosition) {
|
|
90
|
+
scrollProgress * (leftPosition.value - 12) + 12
|
|
91
|
+
},
|
|
92
|
+
animationSpec = defaultAnimationSpec,
|
|
93
|
+
label = "translateX"
|
|
49
94
|
)
|
|
50
95
|
|
|
51
96
|
val animatedWidth by animateFloatAsState(
|
|
52
|
-
targetValue = (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
97
|
+
targetValue = remember(scrollProgress, searchWidth, leftPosition, headerRightWidth) {
|
|
98
|
+
val targetWidth = searchWidth - leftPosition.value - headerRightWidth.value + 12
|
|
99
|
+
scrollProgress * (targetWidth - searchWidth) + searchWidth
|
|
100
|
+
},
|
|
101
|
+
animationSpec = defaultAnimationSpec,
|
|
102
|
+
label = "width"
|
|
56
103
|
)
|
|
57
104
|
|
|
58
105
|
val animatedTranslateY by animateFloatAsState(
|
|
59
|
-
targetValue = (
|
|
106
|
+
targetValue = remember(scrollProgress) {
|
|
107
|
+
(1 - scrollProgress) * HEADER_HEIGHT_CONST
|
|
108
|
+
},
|
|
109
|
+
animationSpec = defaultAnimationSpec,
|
|
110
|
+
label = "translateY"
|
|
60
111
|
)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
112
|
+
|
|
113
|
+
return remember(opacityAni, backgroundSearch, animatedTranslateX, animatedWidth, animatedTranslateY) {
|
|
114
|
+
HeaderAnimations(
|
|
115
|
+
opacity = opacityAni,
|
|
116
|
+
backgroundSearch = backgroundSearch,
|
|
117
|
+
translateX = animatedTranslateX,
|
|
118
|
+
width = animatedWidth,
|
|
119
|
+
translateY = animatedTranslateY
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -2,7 +2,6 @@ package vn.momo.kits.components
|
|
|
2
2
|
|
|
3
3
|
import androidx.compose.foundation.background
|
|
4
4
|
import androidx.compose.foundation.border
|
|
5
|
-
import androidx.compose.foundation.clickable
|
|
6
5
|
import androidx.compose.foundation.layout.Arrangement
|
|
7
6
|
import androidx.compose.foundation.layout.Box
|
|
8
7
|
import androidx.compose.foundation.layout.Row
|
|
@@ -14,6 +13,7 @@ import androidx.compose.foundation.layout.size
|
|
|
14
13
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
15
14
|
import androidx.compose.material3.CircularProgressIndicator
|
|
16
15
|
import androidx.compose.runtime.Composable
|
|
16
|
+
import androidx.compose.runtime.remember
|
|
17
17
|
import androidx.compose.ui.Alignment
|
|
18
18
|
import androidx.compose.ui.Modifier
|
|
19
19
|
import androidx.compose.ui.draw.clip
|
|
@@ -73,47 +73,56 @@ enum class Size(val value: ButtonSpecs) {
|
|
|
73
73
|
)
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// Cache for style calculations to avoid repeated computations
|
|
77
|
+
private val styleCache = mapOf(
|
|
78
|
+
Size.LARGE to Typography.actionDefaultBold,
|
|
79
|
+
Size.MEDIUM to Typography.actionSBold,
|
|
80
|
+
Size.SMALL to Typography.actionXsBold
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
private val iconSizeCache = mapOf(
|
|
84
|
+
Size.LARGE to 24.dp,
|
|
85
|
+
Size.MEDIUM to 16.dp,
|
|
86
|
+
Size.SMALL to 16.dp
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
private val iconSpaceCache = mapOf(
|
|
90
|
+
Size.SMALL to Spacing.XS,
|
|
91
|
+
Size.MEDIUM to Spacing.S,
|
|
92
|
+
Size.LARGE to Spacing.S
|
|
93
|
+
)
|
|
94
|
+
|
|
76
95
|
fun getStyle(size: Size): TextStyle {
|
|
77
|
-
return
|
|
78
|
-
Size.LARGE.name -> Typography.actionDefaultBold
|
|
79
|
-
Size.MEDIUM.name -> Typography.actionSBold
|
|
80
|
-
Size.SMALL.name -> Typography.actionXsBold
|
|
81
|
-
else -> Typography.actionDefaultBold
|
|
82
|
-
}
|
|
96
|
+
return styleCache[size] ?: Typography.actionDefaultBold
|
|
83
97
|
}
|
|
84
98
|
|
|
85
99
|
fun getIconSize(size: Size): Dp {
|
|
86
|
-
return
|
|
87
|
-
Size.LARGE.name -> 24.dp
|
|
88
|
-
Size.MEDIUM.name -> 16.dp
|
|
89
|
-
Size.SMALL.name -> 16.dp
|
|
90
|
-
else -> 24.dp
|
|
91
|
-
}
|
|
100
|
+
return iconSizeCache[size] ?: 24.dp
|
|
92
101
|
}
|
|
93
102
|
|
|
94
103
|
fun getIconSpace(size: Size): Dp {
|
|
95
|
-
return
|
|
96
|
-
Size.SMALL.name -> Spacing.XS
|
|
97
|
-
else -> Spacing.S
|
|
98
|
-
}
|
|
104
|
+
return iconSpaceCache[size] ?: Spacing.S
|
|
99
105
|
}
|
|
100
106
|
|
|
101
107
|
@Composable
|
|
102
108
|
fun getTextColor(type: ButtonType): Color {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
val theme = AppTheme.current
|
|
110
|
+
return remember(type, theme) {
|
|
111
|
+
when (type) {
|
|
112
|
+
ButtonType.DISABLED -> theme.colors.text.disable
|
|
113
|
+
ButtonType.PRIMARY -> Colors.black_01
|
|
114
|
+
ButtonType.SECONDARY -> theme.colors.text.default
|
|
115
|
+
ButtonType.OUTLINE -> theme.colors.primary
|
|
116
|
+
ButtonType.TONAL -> theme.colors.primary
|
|
117
|
+
ButtonType.DANGER -> Colors.black_01
|
|
118
|
+
ButtonType.TEXT -> theme.colors.primary
|
|
119
|
+
}
|
|
111
120
|
}
|
|
112
121
|
}
|
|
113
122
|
|
|
114
123
|
@Composable
|
|
115
124
|
fun RenderTitle(size: Size, title: String = "", type: ButtonType) {
|
|
116
|
-
val style = getStyle(size)
|
|
125
|
+
val style = remember(size) { getStyle(size) }
|
|
117
126
|
val color = getTextColor(type)
|
|
118
127
|
Text(style = style, text = title, color = color, overflow = TextOverflow.Ellipsis, maxLines = 1)
|
|
119
128
|
}
|
|
@@ -126,9 +135,10 @@ fun RenderLeading(
|
|
|
126
135
|
type: ButtonType,
|
|
127
136
|
iconLeft: String = "",
|
|
128
137
|
) {
|
|
129
|
-
val iconSize = getIconSize(size)
|
|
130
|
-
val marginRight = getIconSpace(size)
|
|
138
|
+
val iconSize = remember(size) { getIconSize(size) }
|
|
139
|
+
val marginRight = remember(size) { getIconSpace(size) }
|
|
131
140
|
val color = if (useTintColor) getTextColor(type) else Color.Unspecified
|
|
141
|
+
|
|
132
142
|
Row(
|
|
133
143
|
verticalAlignment = Alignment.CenterVertically,
|
|
134
144
|
horizontalArrangement = Arrangement.Center
|
|
@@ -142,7 +152,6 @@ fun RenderLeading(
|
|
|
142
152
|
strokeWidth = 2.dp
|
|
143
153
|
)
|
|
144
154
|
}
|
|
145
|
-
|
|
146
155
|
}
|
|
147
156
|
if (iconLeft.isNotEmpty()) {
|
|
148
157
|
Icon(
|
|
@@ -163,9 +172,10 @@ fun RenderTrailing(
|
|
|
163
172
|
type: ButtonType,
|
|
164
173
|
) {
|
|
165
174
|
val color = if (useTintColor) getTextColor(type) else Color.Unspecified
|
|
166
|
-
val marginLeft = getIconSpace(size)
|
|
175
|
+
val marginLeft = remember(size) { getIconSpace(size) }
|
|
176
|
+
|
|
167
177
|
if (iconRight.isNotEmpty()) {
|
|
168
|
-
val iconSize = getIconSize(size)
|
|
178
|
+
val iconSize = remember(size) { getIconSize(size) }
|
|
169
179
|
Icon(
|
|
170
180
|
source = iconRight,
|
|
171
181
|
color = color,
|
|
@@ -182,33 +192,36 @@ fun getTypeStyle(
|
|
|
182
192
|
size: Size,
|
|
183
193
|
): Modifier {
|
|
184
194
|
val theme = AppTheme.current
|
|
195
|
+
val radius = remember(size) { size.value.radius }
|
|
185
196
|
|
|
186
|
-
return
|
|
187
|
-
|
|
188
|
-
.
|
|
197
|
+
return remember(type, color, theme, radius) {
|
|
198
|
+
when (type) {
|
|
199
|
+
ButtonType.DISABLED -> Modifier.background(theme.colors.background.disable)
|
|
200
|
+
.border(0.dp, Color.Unspecified, RoundedCornerShape(radius))
|
|
189
201
|
|
|
190
|
-
|
|
191
|
-
|
|
202
|
+
ButtonType.PRIMARY -> Modifier.background(theme.colors.primary)
|
|
203
|
+
.border(0.dp, Color.Unspecified, RoundedCornerShape(radius))
|
|
192
204
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
205
|
+
ButtonType.SECONDARY -> Modifier
|
|
206
|
+
.background(theme.colors.background.surface)
|
|
207
|
+
.border(1.dp, theme.colors.border.default, RoundedCornerShape(radius))
|
|
196
208
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
209
|
+
ButtonType.OUTLINE -> Modifier
|
|
210
|
+
.background(theme.colors.background.surface)
|
|
211
|
+
.border(
|
|
212
|
+
1.dp,
|
|
213
|
+
color ?: theme.colors.primary,
|
|
214
|
+
RoundedCornerShape(radius)
|
|
215
|
+
)
|
|
204
216
|
|
|
205
|
-
|
|
206
|
-
|
|
217
|
+
ButtonType.TONAL -> Modifier.background(theme.colors.background.tonal)
|
|
218
|
+
.border(0.dp, Color.Unspecified, RoundedCornerShape(radius))
|
|
207
219
|
|
|
208
|
-
|
|
209
|
-
|
|
220
|
+
ButtonType.DANGER -> Modifier.background(theme.colors.error.primary)
|
|
221
|
+
.border(0.dp, Color.Unspecified, RoundedCornerShape(radius))
|
|
210
222
|
|
|
211
|
-
|
|
223
|
+
ButtonType.TEXT -> Modifier
|
|
224
|
+
}
|
|
212
225
|
}
|
|
213
226
|
}
|
|
214
227
|
|
|
@@ -234,30 +247,32 @@ fun Button(
|
|
|
234
247
|
isFull: Boolean = true,
|
|
235
248
|
modifier: Modifier = Modifier,
|
|
236
249
|
) {
|
|
237
|
-
val
|
|
238
|
-
|
|
239
|
-
.fillMaxWidth()
|
|
240
|
-
.clip(RoundedCornerShape(size.value.radius)).activeOpacityClickable(
|
|
241
|
-
activeOpacity = 0.5f,
|
|
242
|
-
enabled = type != ButtonType.DISABLED,
|
|
243
|
-
onClick = onClick
|
|
244
|
-
)
|
|
250
|
+
val radius = remember(size) { size.value.radius }
|
|
251
|
+
val isEnabled = remember(type) { type != ButtonType.DISABLED }
|
|
245
252
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
253
|
+
val customModifier = remember(isFull, radius, isEnabled, modifier) {
|
|
254
|
+
if (isFull) {
|
|
255
|
+
modifier
|
|
256
|
+
.fillMaxWidth()
|
|
257
|
+
.clip(RoundedCornerShape(radius))
|
|
258
|
+
} else {
|
|
259
|
+
modifier
|
|
260
|
+
.clip(RoundedCornerShape(radius))
|
|
261
|
+
}
|
|
262
|
+
}.activeOpacityClickable(
|
|
263
|
+
activeOpacity = 0.5f,
|
|
264
|
+
enabled = isEnabled,
|
|
265
|
+
onClick = onClick
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
val sizeSpecs = remember(size) { size.value }
|
|
254
269
|
|
|
255
270
|
Row(
|
|
256
|
-
modifier = customModifier
|
|
257
|
-
.
|
|
258
|
-
.
|
|
259
|
-
|
|
260
|
-
|
|
271
|
+
modifier = customModifier
|
|
272
|
+
.then(getTypeStyle(type, size = size))
|
|
273
|
+
.padding(horizontal = sizeSpecs.padding)
|
|
274
|
+
.defaultMinSize(minWidth = sizeSpecs.width)
|
|
275
|
+
.height(sizeSpecs.height),
|
|
261
276
|
horizontalArrangement = Arrangement.Center,
|
|
262
277
|
verticalAlignment = Alignment.CenterVertically,
|
|
263
278
|
) {
|
|
@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Spacer
|
|
|
9
9
|
import androidx.compose.foundation.layout.size
|
|
10
10
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
11
11
|
import androidx.compose.runtime.Composable
|
|
12
|
+
import androidx.compose.runtime.remember
|
|
12
13
|
import androidx.compose.ui.Alignment
|
|
13
14
|
import androidx.compose.ui.Modifier
|
|
14
15
|
import androidx.compose.ui.graphics.Color
|
|
@@ -20,6 +21,9 @@ import vn.momo.kits.const.Spacing
|
|
|
20
21
|
import vn.momo.kits.const.Typography
|
|
21
22
|
import vn.momo.kits.modifier.activeOpacityClickable
|
|
22
23
|
|
|
24
|
+
// Pre-computed shape for performance
|
|
25
|
+
private val checkboxShape = RoundedCornerShape(Radius.XS)
|
|
26
|
+
|
|
23
27
|
@Composable
|
|
24
28
|
fun CheckBox(
|
|
25
29
|
disabled: Boolean = false,
|
|
@@ -28,32 +32,40 @@ fun CheckBox(
|
|
|
28
32
|
indeterminate: Boolean = false,
|
|
29
33
|
title: String = "",
|
|
30
34
|
) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
val iconSource = if (indeterminate) "navigation_minus" else "ic_checked"
|
|
35
|
+
// Cache theme access
|
|
36
|
+
val theme = AppTheme.current
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (checked) {
|
|
43
|
-
borderColor = AppTheme.current.colors.background.tonal
|
|
44
|
-
backgroundColor = AppTheme.current.colors.background.tonal
|
|
38
|
+
// Memoize color calculations to avoid repeated conditional logic
|
|
39
|
+
val colors = remember(checked, disabled, theme) {
|
|
40
|
+
when {
|
|
41
|
+
disabled && checked -> Pair(theme.colors.background.tonal, theme.colors.background.tonal)
|
|
42
|
+
disabled -> Pair(theme.colors.border.disable, Color.Transparent)
|
|
43
|
+
checked -> Pair(theme.colors.primary, theme.colors.primary)
|
|
44
|
+
else -> Pair(theme.colors.text.default, Color.Transparent)
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
|
|
48
|
+
val (borderColor, backgroundColor) = colors
|
|
49
|
+
|
|
50
|
+
// Memoize icon source to avoid repeated conditional check
|
|
51
|
+
val iconSource = remember(indeterminate) {
|
|
52
|
+
if (indeterminate) "navigation_minus" else "ic_checked"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Memoize click handler to avoid lambda recreation
|
|
56
|
+
val onClickHandler = remember(onCheckedChange, checked) {
|
|
57
|
+
{ onCheckedChange(!checked) }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
50
61
|
Box(
|
|
51
|
-
modifier = Modifier
|
|
52
|
-
.
|
|
53
|
-
.
|
|
62
|
+
modifier = Modifier
|
|
63
|
+
.size(20.dp)
|
|
64
|
+
.background(backgroundColor, checkboxShape)
|
|
65
|
+
.border(Spacing.XXS, borderColor, checkboxShape)
|
|
54
66
|
.activeOpacityClickable(
|
|
55
67
|
activeOpacity = 0.8f,
|
|
56
|
-
onClick =
|
|
68
|
+
onClick = onClickHandler,
|
|
57
69
|
enabled = !disabled,
|
|
58
70
|
),
|
|
59
71
|
) {
|
|
@@ -65,6 +77,7 @@ fun CheckBox(
|
|
|
65
77
|
)
|
|
66
78
|
}
|
|
67
79
|
}
|
|
80
|
+
|
|
68
81
|
if (title.isNotEmpty()) {
|
|
69
82
|
Spacer(Modifier.size(Spacing.S))
|
|
70
83
|
Text(
|
|
@@ -3,6 +3,7 @@ package vn.momo.kits.components
|
|
|
3
3
|
import androidx.compose.foundation.layout.Box
|
|
4
4
|
import androidx.compose.foundation.layout.size
|
|
5
5
|
import androidx.compose.runtime.Composable
|
|
6
|
+
import androidx.compose.runtime.remember
|
|
6
7
|
import androidx.compose.ui.Modifier
|
|
7
8
|
import androidx.compose.ui.graphics.Color
|
|
8
9
|
import androidx.compose.ui.graphics.ColorFilter
|
|
@@ -19,22 +20,45 @@ import vn.momo.kits.utils.Icons
|
|
|
19
20
|
fun Icon(
|
|
20
21
|
source: String,
|
|
21
22
|
size: Dp = 24.dp,
|
|
22
|
-
color: Color? =
|
|
23
|
+
color: Color? = null,
|
|
23
24
|
modifier: Modifier = Modifier,
|
|
24
25
|
) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
// Cache theme access
|
|
27
|
+
val theme = AppTheme.current
|
|
28
|
+
|
|
29
|
+
// Memoize icon resolution to avoid repeated string operations and map lookups
|
|
30
|
+
val iconUrl = remember(source) {
|
|
31
|
+
if (source.contains("https")) {
|
|
32
|
+
source
|
|
33
|
+
} else {
|
|
34
|
+
Icons[source] ?: ""
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Memoize color calculation
|
|
39
|
+
val iconColor = remember(color, theme) {
|
|
40
|
+
color ?: theme.colors.text.default
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Memoize ColorFilter creation
|
|
44
|
+
val colorFilter = remember(iconColor) {
|
|
45
|
+
ColorFilter.tint(iconColor)
|
|
29
46
|
}
|
|
30
|
-
|
|
47
|
+
|
|
48
|
+
// Memoize content description
|
|
49
|
+
val contentDesc = remember(iconUrl) { "img|$iconUrl" }
|
|
50
|
+
|
|
51
|
+
Box(
|
|
52
|
+
modifier = modifier
|
|
53
|
+
.size(size)
|
|
54
|
+
.semantics { contentDescription = contentDesc }
|
|
55
|
+
) {
|
|
31
56
|
AsyncImage(
|
|
32
57
|
modifier = Modifier.matchParentSize(),
|
|
33
|
-
model =
|
|
58
|
+
model = iconUrl,
|
|
34
59
|
contentDescription = null,
|
|
35
60
|
contentScale = ContentScale.Fit,
|
|
36
|
-
colorFilter =
|
|
61
|
+
colorFilter = colorFilter,
|
|
37
62
|
)
|
|
38
63
|
}
|
|
39
64
|
}
|
|
40
|
-
|
|
@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Box
|
|
|
7
7
|
import androidx.compose.foundation.layout.size
|
|
8
8
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
9
9
|
import androidx.compose.runtime.Composable
|
|
10
|
+
import androidx.compose.runtime.remember
|
|
10
11
|
import androidx.compose.ui.Alignment
|
|
11
12
|
import androidx.compose.ui.Modifier
|
|
12
13
|
import androidx.compose.ui.draw.clip
|
|
@@ -43,53 +44,63 @@ enum class IconSize(val value: IconSpecs) {
|
|
|
43
44
|
)
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
// Pre-computed shape to avoid repeated creation
|
|
48
|
+
private val iconButtonShape = RoundedCornerShape(Radius.XL)
|
|
49
|
+
|
|
50
|
+
// Cache for icon sizes to avoid repeated calculations
|
|
51
|
+
private val iconSizeMap = mapOf(
|
|
52
|
+
IconSize.SMALL to 16.dp,
|
|
53
|
+
IconSize.LARGE to 24.dp
|
|
54
|
+
)
|
|
55
|
+
|
|
46
56
|
@Composable
|
|
47
|
-
fun getIconStyle(type: ButtonType, color: Color? =
|
|
57
|
+
fun getIconStyle(type: ButtonType, color: Color? = null): Modifier {
|
|
48
58
|
val theme = AppTheme.current
|
|
49
59
|
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
60
|
+
return remember(type, color, theme) {
|
|
61
|
+
when (type) {
|
|
62
|
+
ButtonType.DISABLED -> Modifier
|
|
63
|
+
.background(theme.colors.background.disable, iconButtonShape)
|
|
64
|
+
.border(0.dp, Color.Unspecified, iconButtonShape)
|
|
55
65
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
).border(0.dp, Color.Unspecified, RoundedCornerShape(Radius.XL))
|
|
66
|
+
ButtonType.PRIMARY -> Modifier
|
|
67
|
+
.background(theme.colors.primary, iconButtonShape)
|
|
68
|
+
.border(0.dp, Color.Unspecified, iconButtonShape)
|
|
60
69
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
70
|
+
ButtonType.SECONDARY -> Modifier
|
|
71
|
+
.background(theme.colors.background.surface, iconButtonShape)
|
|
72
|
+
.border(1.dp, theme.colors.border.default, iconButtonShape)
|
|
64
73
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
ButtonType.OUTLINE -> Modifier
|
|
75
|
+
.background(theme.colors.background.surface, iconButtonShape)
|
|
76
|
+
.border(1.dp, color ?: theme.colors.primary, iconButtonShape)
|
|
68
77
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
).border(0.dp, Color.Unspecified, RoundedCornerShape(Radius.XL))
|
|
78
|
+
ButtonType.TONAL -> Modifier
|
|
79
|
+
.background(theme.colors.background.tonal, iconButtonShape)
|
|
80
|
+
.border(0.dp, Color.Unspecified, iconButtonShape)
|
|
73
81
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
).border(0.dp, Color.Unspecified, RoundedCornerShape(Radius.XL))
|
|
82
|
+
ButtonType.DANGER -> Modifier
|
|
83
|
+
.background(theme.colors.error.primary, iconButtonShape)
|
|
84
|
+
.border(0.dp, Color.Unspecified, iconButtonShape)
|
|
78
85
|
|
|
79
|
-
|
|
86
|
+
ButtonType.TEXT -> Modifier
|
|
87
|
+
}
|
|
80
88
|
}
|
|
81
89
|
}
|
|
82
90
|
|
|
83
91
|
@Composable
|
|
84
92
|
fun getIconColor(type: ButtonType): Color {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
val theme = AppTheme.current
|
|
94
|
+
return remember(type, theme) {
|
|
95
|
+
when (type) {
|
|
96
|
+
ButtonType.DISABLED -> theme.colors.text.disable
|
|
97
|
+
ButtonType.PRIMARY -> Colors.black_01
|
|
98
|
+
ButtonType.SECONDARY -> theme.colors.text.default
|
|
99
|
+
ButtonType.OUTLINE -> theme.colors.primary
|
|
100
|
+
ButtonType.TONAL -> theme.colors.primary
|
|
101
|
+
ButtonType.DANGER -> Colors.black_01
|
|
102
|
+
ButtonType.TEXT -> theme.colors.primary
|
|
103
|
+
}
|
|
93
104
|
}
|
|
94
105
|
}
|
|
95
106
|
|
|
@@ -100,16 +111,27 @@ fun IconButton(
|
|
|
100
111
|
size: IconSize = IconSize.SMALL,
|
|
101
112
|
icon: String = "",
|
|
102
113
|
) {
|
|
103
|
-
|
|
114
|
+
// Memoize icon size calculation
|
|
115
|
+
val iconSize = remember(size) {
|
|
116
|
+
iconSizeMap[size] ?: 16.dp
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Memoize enabled state
|
|
120
|
+
val isEnabled = remember(type) {
|
|
121
|
+
type != ButtonType.DISABLED
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Memoize radius for clipping
|
|
125
|
+
val radius = remember(size) {
|
|
126
|
+
size.value.radius
|
|
127
|
+
}
|
|
128
|
+
|
|
104
129
|
Box(
|
|
105
130
|
modifier = Modifier
|
|
106
131
|
.size(size.value.size)
|
|
107
132
|
.then(getIconStyle(type))
|
|
108
|
-
.clip(RoundedCornerShape(
|
|
109
|
-
.clickable(
|
|
110
|
-
enabled = type != ButtonType.DISABLED,
|
|
111
|
-
onClick = onClick
|
|
112
|
-
),
|
|
133
|
+
.clip(RoundedCornerShape(radius))
|
|
134
|
+
.clickable(enabled = isEnabled, onClick = onClick),
|
|
113
135
|
contentAlignment = Alignment.Center
|
|
114
136
|
) {
|
|
115
137
|
Icon(
|