@momo-kits/native-kits 0.152.4-maxapi → 0.152.4-scale.3

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 (27) hide show
  1. package/compose/build.gradle.kts +1 -1
  2. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +2 -0
  3. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +2 -2
  4. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +246 -78
  5. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +3 -2
  6. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +21 -19
  7. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +12 -1
  8. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +22 -11
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +6 -2
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +4 -1
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +19 -24
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +24 -19
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +25 -18
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +3 -3
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +0 -2
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +42 -7
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +106 -36
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScaleSizeScope.kt +8 -3
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +34 -10
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +11 -10
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +1 -1
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +125 -0
  23. package/ios/Badge/Badge.swift +85 -0
  24. package/ios/Badge/BadgeRibbon.swift +242 -0
  25. package/ios/Button/Button.swift +7 -7
  26. package/local.properties +8 -0
  27. package/package.json +1 -1
@@ -76,7 +76,7 @@ kotlin {
76
76
  implementation(libs.coil.multiplatform.network.ktor)
77
77
  implementation(libs.jetbrains.serialization.json)
78
78
  implementation(libs.kotlinx.datetime)
79
- api(libs.native.max.api)
79
+ api(project(":NativeMaxApi"))
80
80
  }
81
81
  }
82
82
  val androidMain by getting {
@@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.width
14
14
  import androidx.compose.foundation.shape.CircleShape
15
15
  import androidx.compose.foundation.shape.RoundedCornerShape
16
16
  import androidx.compose.runtime.Composable
17
+ import androidx.compose.runtime.Stable
17
18
  import androidx.compose.runtime.getValue
18
19
  import androidx.compose.runtime.mutableStateOf
19
20
  import androidx.compose.runtime.remember
@@ -32,6 +33,7 @@ import vn.momo.kits.components.Icon
32
33
  import vn.momo.kits.const.AppTheme
33
34
  import vn.momo.kits.const.Colors
34
35
 
36
+ @Stable
35
37
  data class HeaderRightData(
36
38
  val useShortcut: Boolean = false,
37
39
  val useMore: Boolean = false,
@@ -85,7 +85,6 @@ import vn.momo.kits.modifier.shadow
85
85
  import vn.momo.kits.utils.getAppStatusBarHeight
86
86
  import kotlin.math.max
87
87
 
88
- @Deprecated("Use NavigationContainer(StackScreen) instead", ReplaceWith("NavigationContainer(StackScreen)"))
89
88
  @Composable
90
89
  fun LiteScreen(
91
90
  scrollable: Boolean = true,
@@ -352,7 +351,8 @@ private class LiteScreenHeaderPolicy(
352
351
  val realConstraints = constraints.copy(
353
352
  minWidth = 0,
354
353
  minHeight = 0,
355
- maxWidth = constraints.maxWidth - spacing12 * 2,
354
+ maxWidth = (constraints.maxWidth - spacing12 * 2)
355
+ .coerceAtLeast(0),
356
356
  )
357
357
  val backIconPlaceable =
358
358
  measurables.find { it.layoutId == HeaderId.BACK_ID }?.measure(realConstraints)
@@ -1,6 +1,8 @@
1
1
  package vn.momo.kits.application
2
2
 
3
+ import androidx.compose.animation.core.Animatable
3
4
  import androidx.compose.animation.core.animateFloatAsState
5
+ import androidx.compose.animation.core.tween
4
6
  import androidx.compose.foundation.ScrollState
5
7
  import androidx.compose.foundation.background
6
8
  import androidx.compose.foundation.gestures.detectTapGestures
@@ -13,14 +15,23 @@ import androidx.compose.foundation.layout.asPaddingValues
13
15
  import androidx.compose.foundation.layout.aspectRatio
14
16
  import androidx.compose.foundation.layout.fillMaxSize
15
17
  import androidx.compose.foundation.layout.fillMaxWidth
18
+ import androidx.compose.foundation.layout.height
16
19
  import androidx.compose.foundation.layout.ime
17
20
  import androidx.compose.foundation.layout.imePadding
21
+ import androidx.compose.foundation.layout.offset
18
22
  import androidx.compose.foundation.layout.padding
19
23
  import androidx.compose.foundation.layout.systemBars
20
24
  import androidx.compose.foundation.rememberScrollState
21
25
  import androidx.compose.foundation.verticalScroll
22
26
  import androidx.compose.runtime.Composable
27
+ import androidx.compose.runtime.CompositionLocalProvider
28
+ import androidx.compose.runtime.DisposableEffect
29
+ import androidx.compose.runtime.LaunchedEffect
23
30
  import androidx.compose.runtime.getValue
31
+ import androidx.compose.runtime.mutableStateOf
32
+ import androidx.compose.runtime.remember
33
+ import androidx.compose.runtime.setValue
34
+ import androidx.compose.runtime.staticCompositionLocalOf
24
35
  import androidx.compose.ui.Alignment
25
36
  import androidx.compose.ui.Modifier
26
37
  import androidx.compose.ui.graphics.Color
@@ -30,15 +41,24 @@ import androidx.compose.ui.layout.onGloballyPositioned
30
41
  import androidx.compose.ui.platform.LocalDensity
31
42
  import androidx.compose.ui.platform.LocalSoftwareKeyboardController
32
43
  import androidx.compose.ui.unit.Dp
44
+ import androidx.compose.ui.unit.IntOffset
33
45
  import androidx.compose.ui.unit.dp
34
46
  import androidx.compose.ui.unit.min
35
47
  import androidx.compose.ui.zIndex
48
+ import kotlinx.coroutines.CoroutineScope
49
+ import kotlinx.coroutines.Dispatchers
50
+ import kotlinx.coroutines.SupervisorJob
51
+ import kotlinx.coroutines.cancel
52
+ import kotlinx.coroutines.delay
53
+ import kotlinx.coroutines.launch
36
54
  import vn.momo.kits.components.InputSearchProps
55
+ import vn.momo.kits.const.AppNavigationBar
37
56
  import vn.momo.kits.const.AppTheme
38
57
  import vn.momo.kits.const.Colors
39
58
  import vn.momo.kits.const.Spacing
40
59
  import vn.momo.kits.modifier.conditional
41
60
  import vn.momo.kits.modifier.shadow
61
+ import vn.momo.kits.navigation.component.SnackBar
42
62
  import vn.momo.kits.platform.getAndroidBuildVersion
43
63
  import vn.momo.kits.utils.getAppStatusBarHeight
44
64
 
@@ -102,97 +122,134 @@ fun Screen(
102
122
  animatedHeader?.composable?.invoke(scrollState.value)
103
123
  }
104
124
  }
125
+ val helper = remember { ScreenHelper() }
105
126
 
106
- Box(
107
- Modifier.fillMaxSize()
108
- .background(backgroundColor ?: AppTheme.current.colors.background.default)
109
- .conditional(useAvoidKeyboard && getAndroidBuildVersion() > 29) {
110
- imePadding()
111
- }
112
- ) {
113
-
114
- if (animatedHeader === null) {
115
- HeaderBackground(
116
- headerType = headerType,
117
- scrollState = scrollState.value,
118
- headerTransparent = headerTransparent
119
- )
120
- }
127
+ DisposableEffect(Unit) {
128
+ onDispose { helper.dispose() }
129
+ }
121
130
 
122
- Header(
123
- headerType = headerType,
124
- title = title,
125
- titlePosition = titlePosition,
126
- headerRight = headerRight,
127
- headerRightWidth = headerRightWidth,
128
- goBack = goBack,
129
- opacity = opacity,
130
- animatedHeader = animatedHeader,
131
- inputSearchProps = inputSearchProps,
132
- scrollState = scrollState.value,
133
- useAnimationSearch = useAnimationSearch,
134
- tintColor = tintColor
135
- )
136
-
137
- Column(
138
- modifier = Modifier.fillMaxSize()
139
- .padding( top = when {
140
- animatedHeader != null -> 0.dp
141
- headerType == HeaderType.NONE -> 0.dp
142
- fullScreenContent -> 0.dp
143
- else -> statusBarHeight + HEADER_HEIGHT.dp
144
- })
145
- .pointerInput(Unit) {
146
- detectTapGestures(onTap = {
147
- keyboardController?.hide()
148
- })
131
+ CompositionLocalProvider(
132
+ LocalScreenHelper provides helper
133
+ ) {
134
+ Box(
135
+ Modifier.fillMaxSize()
136
+ .background(backgroundColor ?: AppTheme.current.colors.background.default)
137
+ .conditional(useAvoidKeyboard && getAndroidBuildVersion() > 29) {
138
+ imePadding()
149
139
  }
150
- .zIndex(1f),
151
140
  ) {
141
+ val footerHeightPx = remember { mutableStateOf(0) }
152
142
 
153
- Column(
154
- modifier = Modifier
155
- .conditional(scrollable) { weight(1f) }
156
- .conditional(onContentLayout != null) {
157
- onGloballyPositioned(onContentLayout ?: {})
158
- }
159
- .conditional(scrollable) { verticalScroll(scrollState) },
160
- verticalArrangement = verticalArrangement,
161
- horizontalAlignment = horizontalAlignment
162
- ) {
163
- Box {
164
- if (animatedHeader !== null) headerAnimated()
165
- Column {
166
- if (animatedHeader !== null) {
167
- Spacer(modifier = Modifier.padding(top = statusBarHeight + HEADER_HEIGHT.dp + layoutOffset))
143
+ Box(Modifier.zIndex(1f)) {
144
+ if (animatedHeader === null) {
145
+ HeaderBackground(
146
+ headerType = headerType,
147
+ scrollState = scrollState.value,
148
+ headerTransparent = headerTransparent
149
+ )
150
+ }
151
+ }
152
+
153
+ Box(Modifier.zIndex(5f)) {
154
+ Header(
155
+ headerType = headerType,
156
+ title = title,
157
+ titlePosition = titlePosition,
158
+ headerRight = headerRight,
159
+ headerRightWidth = headerRightWidth,
160
+ goBack = goBack,
161
+ opacity = opacity,
162
+ animatedHeader = animatedHeader,
163
+ inputSearchProps = inputSearchProps,
164
+ scrollState = scrollState.value,
165
+ useAnimationSearch = useAnimationSearch,
166
+ tintColor = tintColor
167
+ )
168
+ }
169
+
170
+ Box(Modifier.zIndex(2f).fillMaxSize()){
171
+ Column(
172
+ modifier = Modifier.fillMaxSize()
173
+ .padding( top = when {
174
+ animatedHeader != null -> 0.dp
175
+ headerType == HeaderType.NONE -> 0.dp
176
+ fullScreenContent -> 0.dp
177
+ else -> statusBarHeight + HEADER_HEIGHT.dp
178
+ })
179
+ .pointerInput(Unit) {
180
+ detectTapGestures(onTap = {
181
+ keyboardController?.hide()
182
+ })
168
183
  }
184
+ .zIndex(1f),
185
+ ) {
169
186
 
170
- if (useAnimationSearch && inputSearchProps != null && headerType == HeaderType.EXTENDED) {
171
- Spacer(modifier = Modifier.padding(top = (HEADER_HEIGHT.dp + Spacing.S)))
187
+ Column(
188
+ modifier = Modifier
189
+ .conditional(scrollable) { weight(1f) }
190
+ .conditional(onContentLayout != null) {
191
+ onGloballyPositioned(onContentLayout ?: {})
192
+ }
193
+ .conditional(scrollable) { verticalScroll(scrollState) },
194
+ verticalArrangement = verticalArrangement,
195
+ horizontalAlignment = horizontalAlignment
196
+ ) {
197
+ Box {
198
+ if (animatedHeader !== null) headerAnimated()
199
+ Column {
200
+ if (animatedHeader !== null) {
201
+ Spacer(modifier = Modifier.padding(top = statusBarHeight + HEADER_HEIGHT.dp + layoutOffset))
202
+ }
203
+
204
+ if (useAnimationSearch && inputSearchProps != null && headerType == HeaderType.EXTENDED) {
205
+ Spacer(modifier = Modifier.padding(top = (HEADER_HEIGHT.dp + Spacing.S)))
206
+ }
207
+ content()
208
+ }
172
209
  }
173
- content()
210
+ }
211
+
212
+ footer?.let {
213
+ val footerHeight = with(LocalDensity.current) { footerHeightPx.value.toDp() }
214
+ Spacer(Modifier.height(footerHeight))
174
215
  }
175
216
  }
176
217
  }
177
218
 
178
- footer?.let {
179
- Footer(footer, bottomPadding)
219
+ Box(Modifier.zIndex(4f).align(Alignment.BottomCenter)) {
220
+ footer?.let {
221
+ Footer(
222
+ footer = footer,
223
+ bottom = bottomPadding,
224
+ onFooterMeasured = { footerHeightPx.value = it }
225
+ )
226
+ }
180
227
  }
181
- }
182
228
 
183
- if (fabProps != null) {
184
- FloatingButton(
185
- scrollPosition = fabProps.scrollState?.value ?: scrollState.value,
186
- onClick = fabProps.onClick,
187
- containerColor = AppTheme.current.colors.primary,
188
- bottom = fabProps.bottom
189
- ?: if (footer != null) bottomPadding + 76.dp else bottomPadding + 12.dp,
190
- icon = fabProps.icon,
191
- iconColor = fabProps.iconColor,
192
- text = fabProps.label,
193
- size = fabProps.size,
194
- position = fabProps.position ?: FABPosition.END,
195
- )
229
+ Column (Modifier.zIndex(6f)) {
230
+ if (fabProps != null) {
231
+ FloatingButton(
232
+ scrollPosition = fabProps.scrollState?.value ?: scrollState.value,
233
+ onClick = fabProps.onClick,
234
+ containerColor = AppTheme.current.colors.primary,
235
+ bottom = fabProps.bottom
236
+ ?: if (footer != null) bottomPadding + 76.dp else bottomPadding + 12.dp,
237
+ icon = fabProps.icon,
238
+ iconColor = fabProps.iconColor,
239
+ text = fabProps.label,
240
+ size = fabProps.size,
241
+ position = fabProps.position ?: FABPosition.END,
242
+ )
243
+ }
244
+ }
245
+
246
+ Box(
247
+ modifier = Modifier
248
+ .align(Alignment.BottomCenter)
249
+ .zIndex(3f)
250
+ ) {
251
+ ScreenSnackBarHost(if (footer == null) 0 else footerHeightPx.value)
252
+ }
196
253
  }
197
254
  }
198
255
  }
@@ -206,7 +263,11 @@ internal fun isKeyboardVisible(): Boolean {
206
263
  }
207
264
 
208
265
  @Composable
209
- fun Footer(footer: @Composable (() -> Unit)? = null, bottom: Dp = 0.dp) {
266
+ fun Footer(
267
+ footer: @Composable (() -> Unit)? = null,
268
+ bottom: Dp = 0.dp,
269
+ onFooterMeasured: ((Int) -> Unit)? = null
270
+ ) {
210
271
  Box(
211
272
  Modifier
212
273
  .shadow(
@@ -216,6 +277,9 @@ fun Footer(footer: @Composable (() -> Unit)? = null, bottom: Dp = 0.dp) {
216
277
  offsetY = (-4).dp
217
278
  )
218
279
  .background(AppTheme.current.colors.background.surface)
280
+ .onGloballyPositioned {
281
+ onFooterMeasured?.invoke(it.size.height)
282
+ }
219
283
  ) {
220
284
  Box(
221
285
  Modifier.fillMaxWidth()
@@ -228,6 +292,110 @@ fun Footer(footer: @Composable (() -> Unit)? = null, bottom: Dp = 0.dp) {
228
292
  }
229
293
 
230
294
 
295
+ class ScreenHelper {
296
+
297
+ private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
298
+
299
+ internal var snackBarState by mutableStateOf<SnackBarData?>(null)
300
+
301
+ internal var requestHide by mutableStateOf(false)
302
+
303
+ fun showSnackBar(snackBar: SnackBar, onDismiss: (() -> Unit)? = null) {
304
+ scope.launch {
305
+ requestHide = false
306
+ snackBarState = SnackBarData(snackBar, onDismiss)
307
+ }
308
+ }
309
+
310
+ fun hideSnackBar() {
311
+ scope.launch {
312
+ requestHide = true
313
+ }
314
+ }
315
+
316
+ internal fun removeSnackBarNow() {
317
+ snackBarState = null
318
+ requestHide = false
319
+ }
320
+
321
+ fun dispose() {
322
+ scope.cancel()
323
+ }
324
+ }
325
+ data class SnackBarData(
326
+ val type: SnackBar,
327
+ val onDismiss: (() -> Unit)?
328
+ )
329
+
330
+ val LocalScreenHelper = staticCompositionLocalOf<ScreenHelper> {
331
+ error("No Screen helper provided")
332
+ }
333
+
334
+ @Composable
335
+ fun ScreenSnackBarHost(footerHeightPx: Int) {
336
+ val helper = LocalScreenHelper.current
337
+ val snackBarData = helper.snackBarState ?: return
338
+ val density = LocalDensity.current
339
+ val navigationBar = AppNavigationBar.current
340
+
341
+ val footerHeight = if (footerHeightPx > 0) {
342
+ footerHeightPx
343
+ } else {
344
+ with(density) {
345
+ min(navigationBar, 21.dp).toPx()
346
+ }
347
+ }.toInt()
348
+
349
+ var startPosition by remember { mutableStateOf(Float.MAX_VALUE) }
350
+ val targetPosition = 0f
351
+
352
+ var offsetY by remember { mutableStateOf(Animatable(startPosition)) }
353
+
354
+ LaunchedEffect(startPosition) {
355
+ if (startPosition != Float.MAX_VALUE){
356
+ offsetY.snapTo(startPosition)
357
+ offsetY.animateTo(targetPosition, tween(350))
358
+ }
359
+ }
360
+
361
+ LaunchedEffect(helper.requestHide) {
362
+ if (helper.requestHide) {
363
+ offsetY.animateTo(startPosition, tween(200))
364
+ helper.removeSnackBarNow()
365
+ snackBarData.onDismiss?.invoke()
366
+ }
367
+ }
368
+
369
+ LaunchedEffect(snackBarData.type.duration) {
370
+ val duration = snackBarData.type.duration
371
+ if (duration != null) {
372
+ delay(duration)
373
+ helper.hideSnackBar()
374
+ }
375
+ }
376
+
377
+ DisposableEffect(Unit) {
378
+ onDispose { snackBarData.onDismiss?.invoke() }
379
+ }
380
+
381
+ Box(
382
+ modifier = Modifier
383
+ .offset { IntOffset(0, offsetY.value.toInt() - footerHeight) }
384
+ .onGloballyPositioned {
385
+ if (startPosition != it.size.height.toFloat()) {
386
+ startPosition = it.size.height.toFloat()
387
+ offsetY = Animatable(startPosition)
388
+ }
389
+ }
390
+ .fillMaxWidth()
391
+ ) {
392
+ when (val type = snackBarData.type) {
393
+ is SnackBar.Custom -> type.content()
394
+ is SnackBar.Toast -> {}
395
+ }
396
+ }
397
+ }
398
+
231
399
 
232
400
 
233
401
 
@@ -59,11 +59,12 @@ fun Badge(label: String = "Label", backgroundColor: Color? = null) {
59
59
  if (backgroundColor != null && primaryColors.contains(backgroundColor)) {
60
60
  badgeColor = backgroundColor
61
61
  }
62
+ val scaleSize = scaleSize(16f)
62
63
 
63
64
  Box(
64
65
  modifier = Modifier
65
- .height(scaleSize(16.toFloat()).dp)
66
- .widthIn(min = scaleSize(16.toFloat()).dp)
66
+ .height(scaleSize.dp)
67
+ .widthIn(min = scaleSize.dp)
67
68
  .background(color = badgeColor, shape = RoundedCornerShape(Radius.M))
68
69
  .border(width = 1.dp, shape = RoundedCornerShape(Radius.M), color = Colors.black_01)
69
70
  .padding(horizontal = Spacing.XS), contentAlignment = Alignment.Center
@@ -60,27 +60,29 @@ fun Chip(
60
60
  val (height, horizontal, iconSize, iconSpacing) =
61
61
  listOf(scaleSize(dims.height), scaleSize(dims.horizontal), scaleSize(dims.iconSize), scaleSize(dims.iconSpacing))
62
62
 
63
+ val radius = scaleSize(Radius.L)
64
+
63
65
  Row(
64
66
  modifier
65
67
  .wrapContentWidth()
66
- .height(height)
67
- .clip(RoundedCornerShape(Radius.L))
68
+ .height(height.dp)
69
+ .clip(RoundedCornerShape(radius))
68
70
  .background(bg)
69
71
  .conditional(selected) {
70
- Modifier.border(width = 2.dp, color = theme.colors.secondary, shape = RoundedCornerShape(Radius.L))
72
+ Modifier.border(width = 2.dp, color = theme.colors.secondary, shape = RoundedCornerShape(radius))
71
73
  }
72
74
  .activeOpacityClickable {
73
75
  onClick()
74
76
  }
75
- .padding(horizontal = horizontal)
77
+ .padding(horizontal = horizontal.dp)
76
78
  .conditional(accessibilityLabel != null) {
77
79
  setAutomationId(accessibilityLabel.toString())
78
80
  },
79
81
  verticalAlignment = Alignment.CenterVertically,
80
- horizontalArrangement = Arrangement.spacedBy(iconSpacing)
82
+ horizontalArrangement = Arrangement.spacedBy(iconSpacing.dp)
81
83
  ) {
82
84
  if (iconLeft != null) {
83
- Icon(source = iconLeft, size = dims.iconSize, color = leftTint)
85
+ Icon(source = iconLeft, size = dims.iconSize.dp, color = leftTint)
84
86
  }
85
87
 
86
88
  if (!label.isNullOrEmpty()) {
@@ -99,7 +101,7 @@ fun Chip(
99
101
  }
100
102
 
101
103
  if (iconRight != null) {
102
- Icon(source = iconRight, size = iconSize, color = rightTint)
104
+ Icon(source = iconRight, size = iconSize.dp, color = rightTint)
103
105
  }
104
106
  }
105
107
  }
@@ -109,23 +111,23 @@ enum class ChipSize { SMALL, LARGE }
109
111
  object ChipDefaults {
110
112
  @Immutable
111
113
  data class Dimensions(
112
- val height: Dp,
113
- val horizontal: Dp,
114
- val iconSize: Dp,
115
- val iconSpacing: Dp,
114
+ val height: Float,
115
+ val horizontal: Float,
116
+ val iconSize: Float,
117
+ val iconSpacing: Float,
116
118
  )
117
119
 
118
120
  val Large = Dimensions(
119
- height = 32.dp,
120
- horizontal = 12.dp,
121
- iconSize = 20.dp,
122
- iconSpacing = 4.dp,
121
+ height = 32f,
122
+ horizontal = 12f,
123
+ iconSize = 20f,
124
+ iconSpacing = 4f,
123
125
  )
124
126
 
125
127
  val Small = Dimensions(
126
- height = 24.dp,
127
- horizontal = 10.dp,
128
- iconSize = 16.dp,
129
- iconSpacing = 4.dp,
128
+ height = 24f,
129
+ horizontal = 10f,
130
+ iconSize = 16f,
131
+ iconSpacing = 4f,
130
132
  )
131
133
  }
@@ -13,6 +13,8 @@ import androidx.compose.ui.semantics.semantics
13
13
  import androidx.compose.ui.unit.Dp
14
14
  import androidx.compose.ui.unit.dp
15
15
  import coil3.compose.AsyncImage
16
+ import coil3.compose.LocalPlatformContext
17
+ import coil3.request.ImageRequest
16
18
  import vn.momo.kits.const.AppTheme
17
19
  import vn.momo.kits.utils.Icons
18
20
  import vn.momo.kits.utils.noThemeIcons
@@ -24,6 +26,9 @@ fun Icon(
24
26
  color: Color? = AppTheme.current.colors.text.default,
25
27
  modifier: Modifier = Modifier,
26
28
  ) {
29
+ // decode image without downscaling it
30
+ val context = LocalPlatformContext.current
31
+
27
32
  val iconUrl = remember(source) {
28
33
  if (source.contains("https")) {
29
34
  source
@@ -49,7 +54,13 @@ fun Icon(
49
54
  ) {
50
55
  AsyncImage(
51
56
  modifier = Modifier.matchParentSize(),
52
- model = iconUrl,
57
+ model = ImageRequest.Builder(context)
58
+ .data(iconUrl)
59
+ .size(
60
+ coil3.size.Dimension.Undefined,
61
+ coil3.size.Dimension.Undefined
62
+ )
63
+ .build(),
53
64
  contentDescription = null,
54
65
  contentScale = ContentScale.Fit,
55
66
  colorFilter = colorFilter,
@@ -34,6 +34,7 @@ import androidx.compose.ui.text.font.FontWeight
34
34
  import androidx.compose.ui.text.input.KeyboardType
35
35
  import androidx.compose.ui.text.input.PasswordVisualTransformation
36
36
  import androidx.compose.ui.text.input.VisualTransformation
37
+ import androidx.compose.ui.text.style.TextAlign
37
38
  import androidx.compose.ui.unit.Dp
38
39
  import androidx.compose.ui.unit.dp
39
40
  import androidx.compose.ui.unit.sp
@@ -42,7 +43,9 @@ import vn.momo.kits.const.AppTheme
42
43
  import vn.momo.kits.const.Radius
43
44
  import vn.momo.kits.const.Spacing
44
45
  import vn.momo.kits.const.Typography
46
+ import vn.momo.kits.const.scaleSize
45
47
  import vn.momo.kits.modifier.setAutomationId
48
+ import vn.momo.kits.navigation.ScaleSizeScope
46
49
 
47
50
  data class InputSizeDetail(
48
51
  val borderWidth: Dp,
@@ -213,20 +216,24 @@ fun Input(
213
216
  if (disabled) "input_${floatingValue}_disabled" else "input_$floatingValue"
214
217
  }
215
218
 
219
+ val fontSize = scaleSize(16.sp)
220
+ val lineHeight = scaleSize(24.sp)
221
+
216
222
  val textStyle = remember(textColor, fontWeight) {
217
223
  TextStyle(
218
224
  color = textColor,
219
- fontSize = 16.sp,
220
- lineHeight = 24.sp,
225
+ fontSize = fontSize,
226
+ lineHeight = lineHeight,
221
227
  fontWeight = fontWeight.value
222
228
  )
223
229
  }
224
230
 
225
231
  val placeholderStyle = remember(placeholderColor, fontWeight) {
226
232
  TextStyle(
227
- fontSize = 16.sp,
228
- lineHeight = 24.sp,
229
- fontWeight = fontWeight.value
233
+ fontSize = fontSize,
234
+ lineHeight = lineHeight,
235
+ fontWeight = fontWeight.value,
236
+ textAlign = TextAlign.Center
230
237
  )
231
238
  }
232
239
 
@@ -331,13 +338,17 @@ fun Input(
331
338
  )
332
339
  }
333
340
 
334
- Box(Modifier.weight(1f)) {
341
+ Box(Modifier.weight(1f), contentAlignment = Alignment.CenterStart) {
335
342
  if (text.value.isEmpty()) {
336
- Text(
337
- text = placeholder,
338
- style = placeholderStyle,
339
- color = placeholderColor
340
- )
343
+ ScaleSizeScope(
344
+ disableScale = true
345
+ ) {
346
+ Text(
347
+ text = placeholder,
348
+ style = placeholderStyle,
349
+ color = placeholderColor
350
+ )
351
+ }
341
352
  }
342
353
  innerTextField()
343
354
  }
@@ -30,6 +30,7 @@ import androidx.compose.ui.zIndex
30
30
  import vn.momo.kits.const.AppTheme
31
31
  import vn.momo.kits.const.Spacing
32
32
  import vn.momo.kits.const.Typography
33
+ import vn.momo.kits.const.scaleSize
33
34
 
34
35
  @Composable
35
36
  fun InputDropDown(
@@ -76,6 +77,9 @@ fun InputDropDown(
76
77
 
77
78
  val testId = if (disabled) "input_${floatingValue}_disabled" else "input_$floatingValue"
78
79
 
80
+ val fontSize = scaleSize(16.sp)
81
+ val lineHeight = scaleSize(24.sp)
82
+
79
83
  Column(modifier = Modifier.clickable(enabled = !disabled, onClick = onPress).semantics {
80
84
  contentDescription = floatingValue; testTag = testId
81
85
  }) {
@@ -142,8 +146,8 @@ fun InputDropDown(
142
146
  Text(
143
147
  text = value.value.ifEmpty { placeholder },
144
148
  style = TextStyle(
145
- fontSize = 16.sp,
146
- lineHeight = 24.sp
149
+ fontSize = fontSize,
150
+ lineHeight = lineHeight
147
151
  ),
148
152
  color = if (value.value.isEmpty()) placeholderColor else textColor
149
153
  )