@momo-kits/native-kits 0.120.0-beta.3 → 0.120.0-beta.9

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.
@@ -27,7 +27,6 @@ import androidx.compose.ui.graphics.Color
27
27
  import androidx.compose.ui.graphics.graphicsLayer
28
28
  import androidx.compose.ui.semantics.semantics
29
29
  import androidx.compose.ui.semantics.testTag
30
- import androidx.compose.ui.text.style.TextAlign
31
30
  import androidx.compose.ui.unit.Dp
32
31
  import androidx.compose.ui.unit.dp
33
32
  import androidx.compose.ui.zIndex
@@ -168,11 +167,9 @@ fun Header(
168
167
  HeaderTitle(
169
168
  title = title,
170
169
  color = tintIconColor,
171
- modifier = Modifier
172
- .setAutomationId("title_navigation_header")
173
- .fillMaxWidth(fraction = if(titlePosition == TitlePosition.LEFT) 0.7f else 0.5f)
174
- ,
175
- textAlign = if(titlePosition == TitlePosition.LEFT) TextAlign.Start else TextAlign.Center
170
+ modifier = Modifier.semantics {
171
+ testTag = "title_navigation_header"
172
+ }
176
173
  )
177
174
  }
178
175
  }
@@ -4,7 +4,6 @@ import androidx.compose.runtime.Composable
4
4
  import androidx.compose.ui.Modifier
5
5
  import androidx.compose.ui.graphics.Color
6
6
  import androidx.compose.ui.text.style.TextAlign
7
- import androidx.compose.ui.text.style.TextOverflow
8
7
  import androidx.compose.ui.unit.sp
9
8
  import androidx.compose.ui.zIndex
10
9
  import vn.momo.kits.components.Text
@@ -26,7 +25,6 @@ fun HeaderTitle(
26
25
  lineHeight = 22.sp,
27
26
  ),
28
27
  color = color,
29
- maxLines = 1,
30
- overflow = TextOverflow.Ellipsis
28
+ maxLines = 1
31
29
  )
32
30
  }
@@ -200,8 +200,9 @@ fun Footer(footer: @Composable (() -> Unit)? = null, keyboardSize: Dp = 0.dp, bo
200
200
  Box(
201
201
  Modifier
202
202
  .offset(y = -keyboardSize)
203
+ .padding(bottom = bottom)
203
204
  .shadow(
204
- Colors.black_20.copy(alpha = 0.2f),
205
+ Colors.black_20.copy(alpha = 0.1f),
205
206
  borderRadius = 0.dp,
206
207
  10f,
207
208
  offsetX = 0.dp,
@@ -211,9 +212,7 @@ fun Footer(footer: @Composable (() -> Unit)? = null, keyboardSize: Dp = 0.dp, bo
211
212
  .background(AppTheme.current.colors.background.surface)
212
213
  ) {
213
214
  Box(
214
- Modifier.fillMaxWidth()
215
- .padding(bottom = bottom)
216
- .padding(vertical = Spacing.S, horizontal = Spacing.M)
215
+ Modifier.fillMaxWidth().padding(vertical = Spacing.S, horizontal = Spacing.M)
217
216
  ) {
218
217
  footer?.invoke()
219
218
  }
@@ -2,6 +2,10 @@ package vn.momo.kits.components
2
2
 
3
3
  import androidx.compose.foundation.layout.Box
4
4
  import androidx.compose.runtime.Composable
5
+ import androidx.compose.runtime.getValue
6
+ import androidx.compose.runtime.mutableStateOf
7
+ import androidx.compose.runtime.remember
8
+ import androidx.compose.runtime.setValue
5
9
  import androidx.compose.ui.Alignment
6
10
  import androidx.compose.ui.Modifier
7
11
  import androidx.compose.ui.graphics.ColorFilter
@@ -10,6 +14,8 @@ import androidx.compose.ui.graphics.painter.Painter
10
14
  import androidx.compose.ui.layout.ContentScale
11
15
  import androidx.compose.ui.semantics.contentDescription
12
16
  import androidx.compose.ui.semantics.semantics
17
+ import coil3.compose.AsyncImage
18
+ import vn.momo.kits.const.AppTheme
13
19
 
14
20
  data class Options(
15
21
  val alignment: Alignment = Alignment.TopStart,
@@ -22,47 +28,6 @@ enum class ImageState {
22
28
  Loading, Success, Error
23
29
  }
24
30
 
25
- private val supportedDomains = setOf(
26
- "static.momocdn.net",
27
- "img.mservice.com.vn",
28
- "cdn.mservice.com.vn",
29
- "img.mservice.io",
30
- "image.momocdn.net"
31
- )
32
-
33
- private val sizeChart = mapOf(
34
- "XXS" to 64,
35
- "XS" to 128,
36
- "S" to 256,
37
- "M" to 512,
38
- "L" to 768,
39
- "XL" to 1024,
40
- "XXL" to 2048
41
- )
42
-
43
- private fun closestSizeForWidth(width: Float): String {
44
- return sizeChart.minByOrNull { (_, value) ->
45
- kotlin.math.abs(value - width)
46
- }?.key ?: "M"
47
- }
48
-
49
- private fun getHostFromUrl(url: String): String? {
50
- return try {
51
- val noProtocol = url.substringAfter("//")
52
- noProtocol.substringBefore("/").substringBefore(":")
53
- } catch (_: Exception) {
54
- null
55
- }
56
- }
57
-
58
- private fun appendQueryParam(url: String, key: String, value: String): String {
59
- val parts = url.split("#", limit = 2)
60
- val base = parts[0]
61
- val fragment = if (parts.size > 1) "#" + parts[1] else ""
62
- val sep = if (base.contains("?")) "&" else "?"
63
- return "$base$sep$key=$value$fragment"
64
- }
65
-
66
31
  @Composable
67
32
  fun Image(
68
33
  source: Any,
@@ -70,59 +35,41 @@ fun Image(
70
35
  options: Options? = null,
71
36
  loading: Boolean = true,
72
37
  ) {
73
- // var imageOptions = Options()
74
- // var imageState by remember { mutableStateOf(ImageState.Loading) }
75
- // var urlToLoad by remember(source) { mutableStateOf(source) }
76
- // val density = LocalDensity.current
38
+ var imageOptions = Options()
39
+ var imageState by remember { mutableStateOf(ImageState.Loading) }
77
40
 
78
41
  options?.let {
79
- // imageOptions = Options(
80
- // alignment = options.alignment,
81
- // contentScale = options.contentScale,
82
- // colorFilter = options.colorFilter,
83
- // alpha = DefaultAlpha,
84
- // )
42
+ imageOptions = Options(
43
+ alignment = options.alignment,
44
+ contentScale = options.contentScale,
45
+ colorFilter = options.colorFilter,
46
+ alpha = DefaultAlpha,
47
+ )
85
48
  }
86
49
 
87
- Box(
88
- modifier = modifier
89
- .semantics { contentDescription = "img|$source" }
90
- // .onSizeChanged { size ->
91
- // val src = source as? String ?: return@onSizeChanged
92
- // val host = getHostFromUrl(src)
93
- // if (host != null && supportedDomains.contains(host)) {
94
- // val widthPx = size.width.toFloat()
95
- // val pixelRatio = density.density
96
- // val pixelFitForWidth = widthPx * pixelRatio
97
- // val sizeKey = closestSizeForWidth(pixelFitForWidth)
98
- // urlToLoad = appendQueryParam(src, "size", sizeKey)
99
- // } else {
100
- // urlToLoad = src
101
- // }
102
- // }
103
- ) {
104
- // AsyncImage(
105
- // modifier = Modifier.matchParentSize(),
106
- // model = urlToLoad,
107
- // contentDescription = null,
108
- // contentScale = imageOptions.contentScale,
109
- // alignment = imageOptions.alignment,
110
- // colorFilter = imageOptions.colorFilter,
111
- // alpha = imageOptions.alpha,
112
- // onLoading = { imageState = ImageState.Loading },
113
- // onSuccess = { imageState = ImageState.Success },
114
- // onError = { imageState = ImageState.Error },
115
- // )
116
- // when (imageState) {
117
- // ImageState.Loading -> if (loading) Skeleton()
118
- // ImageState.Error -> Icon(
119
- // source = "media_fail",
120
- // color = AppTheme.current.colors.text.disable,
121
- // modifier = modifier
122
- // )
123
- //
124
- // ImageState.Success -> {}
125
- // }
50
+ Box(modifier = modifier.semantics { contentDescription = "img|$source" }) {
51
+ AsyncImage(
52
+ modifier = Modifier.matchParentSize(),
53
+ model = source,
54
+ contentDescription = null,
55
+ contentScale = imageOptions.contentScale,
56
+ alignment = imageOptions.alignment,
57
+ colorFilter = imageOptions.colorFilter,
58
+ alpha = imageOptions.alpha,
59
+ onLoading = { imageState = ImageState.Loading },
60
+ onSuccess = { imageState = ImageState.Success },
61
+ onError = { imageState = ImageState.Error },
62
+ )
63
+ when (imageState) {
64
+ ImageState.Loading -> if (loading) Skeleton()
65
+ ImageState.Error -> Icon(
66
+ source = "media_fail",
67
+ color = AppTheme.current.colors.text.disable,
68
+ modifier = modifier
69
+ )
70
+
71
+ ImageState.Success -> {}
72
+ }
126
73
  }
127
74
 
128
75
  }
@@ -57,7 +57,6 @@ fun PopupNotify(
57
57
  secondary: PopupAction? = null,
58
58
  buttonDirection: PopupActionDirection = PopupActionDirection.ROW,
59
59
  onIconClose: () -> Unit,
60
- isShowCloseIcon: Boolean = true,
61
60
  ) {
62
61
  var isScroll by remember { mutableStateOf(false) }
63
62
  val layoutResult = remember { mutableStateOf<TextLayoutResult?>(null) }
@@ -108,13 +107,13 @@ fun PopupNotify(
108
107
  RoundedCornerShape(Radius.L)
109
108
  )
110
109
  ) {
111
- if(image.isNotEmpty()) {
110
+ image.ifNotNull(
112
111
  Image(
113
112
  source = image,
114
113
  modifier = Modifier.fillMaxWidth().aspectRatio(1.777f),
115
- options = Options(alignment = Alignment.Center)
116
- )
117
- }
114
+ options = Options(alignment = Alignment.Center)
115
+ ), null
116
+ )
118
117
 
119
118
  Column(modifier = Modifier.padding(Spacing.XL)) {
120
119
  Text(
@@ -126,7 +125,7 @@ fun PopupNotify(
126
125
  content(Modifier.padding(top = Spacing.S))
127
126
  if (error.isNotEmpty()) {
128
127
  Text(
129
- text = "Mã lỗi: " + error,
128
+ text = error,
130
129
  style = Typography.descriptionXsRegular,
131
130
  color = AppTheme.current.colors.text.hint,
132
131
  maxLines = 1,
@@ -145,36 +144,34 @@ fun PopupNotify(
145
144
  }
146
145
  }
147
146
 
148
- if(isShowCloseIcon) {
149
- Box(
150
- Modifier
151
- .width(22.dp)
152
- .height(22.dp)
153
- .offset(x = -(1).dp, y = (-11).dp)
154
- .background(
155
- color = AppTheme.current.colors.text.default,
156
- shape = RoundedCornerShape(Radius.M)
157
- )
158
- .border(
159
- width = 2.dp,
160
- color = AppTheme.current.colors.background.surface,
161
- shape = RoundedCornerShape(Radius.M)
162
- )
163
- .clip(RoundedCornerShape(100))
164
- .clickable(
165
- interactionSource = remember { MutableInteractionSource() },
166
- indication = null,
167
- onClick = { onClose { onIconClose() } }
168
- ),
169
- contentAlignment = Alignment.Center
170
- ) {
171
- Icon(
172
- source = "navigation_close",
147
+ Box(
148
+ Modifier
149
+ .width(22.dp)
150
+ .height(22.dp)
151
+ .offset(x = -(1).dp, y = (-11).dp)
152
+ .background(
153
+ color = AppTheme.current.colors.text.default,
154
+ shape = RoundedCornerShape(Radius.M)
155
+ )
156
+ .border(
157
+ width = 2.dp,
173
158
  color = AppTheme.current.colors.background.surface,
174
- size = 16.dp,
175
- modifier = Modifier.setAutomationId("ic_popup_close")
159
+ shape = RoundedCornerShape(Radius.M)
176
160
  )
177
- }
161
+ .clip(RoundedCornerShape(100))
162
+ .clickable(
163
+ interactionSource = remember { MutableInteractionSource() },
164
+ indication = null,
165
+ onClick = { onClose { onIconClose() } }
166
+ ),
167
+ contentAlignment = Alignment.Center
168
+ ) {
169
+ Icon(
170
+ source = "navigation_close",
171
+ color = AppTheme.current.colors.background.surface,
172
+ size = 16.dp,
173
+ modifier = Modifier.setAutomationId("ic_popup_close")
174
+ )
178
175
  }
179
176
  }
180
177
  }
@@ -24,7 +24,6 @@ import androidx.compose.ui.unit.dp
24
24
  import vn.momo.kits.const.AppTheme
25
25
  import vn.momo.kits.const.Radius
26
26
  import vn.momo.kits.const.Spacing
27
- import vn.momo.kits.utils.ifNotNull
28
27
 
29
28
  @Composable
30
29
  fun PopupPromotion(
@@ -33,7 +32,7 @@ fun PopupPromotion(
33
32
  onClose: (() -> Unit) = {},
34
33
  ) {
35
34
  Column(Modifier.semantics { testTag = "popup_notify" }) {
36
- source.ifNotNull(
35
+ if (source.isNotEmpty()) {
37
36
  Image(
38
37
  source = source,
39
38
  modifier = Modifier.fillMaxWidth()
@@ -44,8 +43,8 @@ fun PopupPromotion(
44
43
  onClick = onPress
45
44
  ),
46
45
  options = Options(alignment = Alignment.Center, contentScale = ContentScale.Crop)
47
- ), null
48
- )
46
+ )
47
+ }
49
48
  Box(
50
49
  modifier = Modifier
51
50
  .padding(top = Spacing.S)
@@ -17,7 +17,6 @@ import androidx.compose.ui.graphics.Color
17
17
  import androidx.compose.ui.unit.dp
18
18
  import vn.momo.kits.const.Colors
19
19
  import vn.momo.kits.const.Spacing
20
- import vn.momo.kits.modifier.setAutomationId
21
20
  import vn.momo.kits.utils.ifTrue
22
21
 
23
22
  @Composable
@@ -25,8 +24,6 @@ fun Switch(
25
24
  value: Boolean = false,
26
25
  onChange: (Boolean) -> Unit = {},
27
26
  disabled: Boolean = false,
28
- title: String? = "",
29
- accessibilityId: String? = ""
30
27
  ) {
31
28
  val circleBackgroundColor = value.ifTrue(Colors.black_01, Colors.black_03)
32
29
  var backgroundColor = value.ifTrue(Colors.green_03, Colors.black_07)
@@ -35,35 +32,27 @@ fun Switch(
35
32
  }
36
33
 
37
34
  Box(
38
- modifier = Modifier.setAutomationId( accessibilityId ?: "toggle_${title ?: "unknown"}_${value}")
35
+ modifier = Modifier.then(
36
+ Modifier.width(38.dp).height(20.dp).border(
37
+ 0.dp, Color.Unspecified,
38
+ RoundedCornerShape(20.dp)
39
+ ).background(backgroundColor, RoundedCornerShape(20.dp)).clip(RoundedCornerShape(20.dp))
40
+ .clickable(
41
+ enabled = !disabled,
42
+ onClick = { onChange(!value) }
43
+ )
44
+ .padding(horizontal = Spacing.XS)
45
+ ),
46
+ contentAlignment = value.ifTrue(Alignment.CenterEnd, Alignment.CenterStart)
39
47
  ) {
40
48
  Box(
41
- modifier = Modifier.then(
42
- Modifier.width(38.dp).height(20.dp).border(
43
- 0.dp, Color.Unspecified,
44
- RoundedCornerShape(20.dp)
45
- ).background(backgroundColor, RoundedCornerShape(20.dp))
46
- .clip(RoundedCornerShape(20.dp))
47
- .clickable(
48
- enabled = !disabled,
49
- onClick = { onChange(!value) }
50
- )
51
- .padding(horizontal = Spacing.XS)
52
- ),
53
- contentAlignment = value.ifTrue(Alignment.CenterEnd, Alignment.CenterStart)
54
- ) {
55
- Box(
56
- modifier = Modifier.size(14.dp)
57
- .border(0.dp, Color.Unspecified, RoundedCornerShape(percent = 100))
58
- .background(circleBackgroundColor, RoundedCornerShape(percent = 100)),
59
- contentAlignment = Alignment.Center
60
- )
61
- {
62
- Box(
63
- Modifier.size(6.dp)
64
- .background(backgroundColor, RoundedCornerShape(percent = 100))
65
- )
66
- }
49
+ modifier = Modifier.size(14.dp)
50
+ .border(0.dp, Color.Unspecified, RoundedCornerShape(percent = 100))
51
+ .background(circleBackgroundColor, RoundedCornerShape(percent = 100)),
52
+ contentAlignment = Alignment.Center
53
+ )
54
+ {
55
+ Box(Modifier.size(6.dp).background(backgroundColor, RoundedCornerShape(percent = 100)))
67
56
  }
68
57
  }
69
58
  }
@@ -8,7 +8,6 @@ import androidx.compose.ui.text.TextStyle
8
8
  import androidx.compose.ui.text.style.TextAlign
9
9
  import androidx.compose.ui.text.style.TextDecoration
10
10
  import androidx.compose.ui.text.style.TextOverflow
11
- import androidx.compose.ui.unit.TextUnit
12
11
  import androidx.compose.ui.unit.sp
13
12
  import vn.momo.kits.const.AppTheme
14
13
  import vn.momo.kits.const.Typography
@@ -28,9 +27,6 @@ fun Text(
28
27
  textDecoration: TextDecoration? = null,
29
28
  onTextLayout: ((TextLayoutResult) -> Unit)? = null,
30
29
  fontFamily: String = "SFProText",
31
- minLines: Int = 1,
32
- letterSpacing: TextUnit = TextUnit.Unspecified,
33
- softWrap: Boolean = true,
34
30
  accessibilityId: String? = null
35
31
  ) {
36
32
  val fontSize = scaleSize(style.fontSize.value).sp
@@ -45,11 +41,8 @@ fun Text(
45
41
  fontSize = fontSize,
46
42
  fontFamily = font,
47
43
  maxLines = maxLines,
48
- softWrap = softWrap,
49
- letterSpacing = letterSpacing,
50
- minLines = minLines,
51
44
  textDecoration = textDecoration,
52
45
  onTextLayout = onTextLayout,
53
46
  overflow = overflow
54
47
  )
55
- }
48
+ }
@@ -2,6 +2,7 @@ package vn.momo.kits.const
2
2
 
3
3
  import androidx.compose.runtime.Composable
4
4
  import androidx.compose.runtime.Immutable
5
+ import androidx.compose.ui.platform.LocalDensity
5
6
  import androidx.compose.ui.text.TextStyle
6
7
  import androidx.compose.ui.text.font.FontFamily
7
8
  import androidx.compose.ui.text.font.FontWeight
@@ -12,17 +13,35 @@ import org.jetbrains.compose.resources.FontResource
12
13
  import org.jetbrains.compose.resources.InternalResourceApi
13
14
  import org.jetbrains.compose.resources.ResourceItem
14
15
  import vn.momo.kits.platform.getScreenDimensions
16
+ import kotlin.math.max
17
+ import kotlin.math.min
15
18
 
16
19
  const val DEFAULT_SCREEN_SIZE = 375f
20
+ const val MAX_FONT_SCALE = 1.5f
21
+ const val MAX_DEVICE_SCALE = 5
17
22
 
18
23
  @Composable
19
24
  fun scaleSize(size: Float): Float {
20
25
  val deviceWidth = getScreenDimensions().width
21
- val scaleRate = deviceWidth / DEFAULT_SCREEN_SIZE
22
- if (scaleRate > 1) {
23
- return if ((scaleRate * size) > (size + 5)) size + 5 else scaleRate * size
26
+ val deviceScale = deviceWidth / DEFAULT_SCREEN_SIZE
27
+
28
+ val density = LocalDensity.current
29
+ val fontScale = density.fontScale
30
+
31
+ val defaultFontSize = size / fontScale //To ignore OS scale
32
+ var fontSizeScaleDevice = defaultFontSize
33
+ var fontSizeScaleOS = defaultFontSize
34
+
35
+ if (deviceScale > 1) {
36
+ fontSizeScaleDevice =
37
+ min(deviceScale * fontSizeScaleDevice, fontSizeScaleDevice + MAX_DEVICE_SCALE)
24
38
  }
25
- return size
39
+
40
+ if (fontScale > 1) {
41
+ fontSizeScaleOS = min(fontScale * fontSizeScaleOS, fontSizeScaleOS * MAX_FONT_SCALE)
42
+ }
43
+
44
+ return max(fontSizeScaleDevice, fontSizeScaleOS)
26
45
  }
27
46
 
28
47
  @OptIn(InternalResourceApi::class)
@@ -214,8 +214,8 @@ public struct Header: View {
214
214
  }
215
215
  } else {
216
216
  Text(title)
217
- .font(Font.system(size: 17).weight(.bold))
218
- .lineSpacing(22)
217
+ .font(Font.system(size: scaleSize(17)).weight(.bold))
218
+ .lineSpacing(scaleSize(22))
219
219
  .foregroundColor(tintColor)
220
220
  .frame(maxWidth: titlePosition == .center ? UIScreen.main.bounds.width - 252 : nil)
221
221
  .frame(maxWidth: titlePosition == .center ? .infinity : UIScreen.main.bounds.width - 172, alignment: titlePosition == .center ? .center : .leading)
@@ -88,8 +88,7 @@ public struct Screen<Content: View>: View {
88
88
  let screenHeight = height + topInset + bottomInset
89
89
 
90
90
  ZStack {
91
- (backgroundColor ?? Color.white)
92
- .edgesIgnoringSafeArea(.top)
91
+ (backgroundColor ?? Color.white).edgesIgnoringSafeArea(.all)
93
92
 
94
93
  VStack(spacing: 0) {
95
94
  ZStack(alignment: .top) {
@@ -185,8 +184,6 @@ struct Footer: View {
185
184
  VStack {
186
185
  footerView
187
186
  }
188
- .padding(.vertical, Spacing.S)
189
- .padding(.horizontal, Spacing.M)
190
187
  .padding(.bottom, keyboardSize)
191
188
  .background(Colors.black01)
192
189
  }
@@ -42,7 +42,7 @@ public struct Button: View {
42
42
  SwiftUI.Button(action: action) {
43
43
  HStack {
44
44
  iconLeft
45
- renderTitle(title: title, type: type, size: size).lineLimit(1).truncationMode(.tail)
45
+ renderTitle(title: title, type: type, size: size)
46
46
  iconRight
47
47
  }
48
48
  .buttonSize(size)
@@ -62,11 +62,11 @@ public struct Button: View {
62
62
  func renderTitle(title: String, type: ButtonType, size: ButtonSize) -> Text {
63
63
  switch size {
64
64
  case .large:
65
- return Text(title).font(.system(size: 16, weight: Font.Weight.bold))
65
+ return Text(title).font(.system(size: scaleSize(16), weight: Font.Weight.bold))
66
66
  case .medium:
67
- return Text(title).font(.system(size: 14, weight: Font.Weight.bold))
67
+ return Text(title).font(.system(size: scaleSize(14), weight: Font.Weight.bold))
68
68
  case .small:
69
- return Text(title).font(.system(size: 12, weight: Font.Weight.bold))
69
+ return Text(title).font(.system(size: scaleSize(12), weight: Font.Weight.bold))
70
70
  }
71
71
  }
72
72
  }
@@ -123,15 +123,15 @@ private extension View {
123
123
  case .large:
124
124
  return self
125
125
  .frame(maxWidth: .infinity, minHeight: 48, maxHeight: 48)
126
- .padding([.leading, .trailing], Spacing.L)
126
+ .padding([.leading, .trailing], Spacing.XS)
127
127
  case .medium:
128
128
  return self
129
129
  .frame(minWidth: 80, maxWidth: .infinity, minHeight: 36, maxHeight: 36)
130
- .padding([.leading, .trailing], Spacing.M)
130
+ .padding([.leading, .trailing], Spacing.XS)
131
131
  case .small:
132
132
  return self
133
133
  .frame(minWidth: 56, maxWidth: 76, minHeight: 28, maxHeight: 28)
134
- .padding([.leading, .trailing], Spacing.S)
134
+ .padding([.leading, .trailing], Spacing.XS)
135
135
  }
136
136
  }
137
137
  }
@@ -53,8 +53,7 @@ public struct Checkbox: View{
53
53
 
54
54
  // Title
55
55
  if !title.isEmpty {
56
- Text(title)
57
- .font(.description1)
56
+ MomoText(title, typography: .bodyDefaultRegular)
58
57
  }
59
58
  }
60
59
  }
@@ -5,6 +5,7 @@ import SwiftUI
5
5
  fileprivate struct FloatingComponent: View {
6
6
  var floatingValue: String
7
7
  var floatingIcon: AnyView?
8
+ var disabled: Bool = false
8
9
 
9
10
  public init (_ floatingValue: String = "", floatingIcon: AnyView? = nil){
10
11
  self.floatingValue = floatingValue
@@ -13,12 +14,9 @@ fileprivate struct FloatingComponent: View {
13
14
 
14
15
  public var body: some View {
15
16
  return HStack {
16
- Text(floatingValue)
17
- .font(.paragraph)
18
- .foregroundColor(Colors.black09)
19
- .font(.system(size: 12))
20
- .padding(.leading, 8)
21
- .padding(.trailing, 8)
17
+ MomoText(floatingValue, typography: .labelSMedium, color: self.disabled ? Colors.black12 : Colors.black08)
18
+ .padding(.leading, Spacing.XS)
19
+ .padding(.trailing, Spacing.XS)
22
20
  floatingIcon
23
21
  }
24
22
  .background(Colors.black01)
@@ -113,84 +111,83 @@ public struct Input: View {
113
111
  }
114
112
 
115
113
  public var body: some View {
116
- VStack(alignment: .leading, spacing: 4) {
117
- ZStack(alignment: .topLeading) {
118
- HStack(alignment: .center) {
119
- if let leadingIcon = leadingIcon {
120
- Icon(source: leadingIcon, size: 16, color: leadingIconColor)
121
- }
122
-
123
- if !isSecure {
124
- TextField("", text: $value, onEditingChanged: { isChange in
125
- self.focused = isChange
126
- })
127
- .disabled(disabled)
128
- .font(fontWeight == .bold ? .headline : .body)
129
- } else {
130
- DotTextField(value: $value)
131
- .disabled(disabled)
132
- }
133
-
134
- if loading {
135
- ActivityIndicator(isAnimating: .constant(true), style: .medium)
136
- .frame(width: 20, height: 20)
137
- } else if !value.isEmpty {
138
- SwiftButton(action: onCancel){
139
- Image(systemName: "x.circle.fill")
140
- .foregroundColor(Colors.text)
141
- .frame(width: 16, height: 16)
142
- .padding(.leading, -4)
143
- .padding(.trailing, 8)
144
- }
145
- }
146
-
147
- rightIcon
114
+ VStack(alignment: .leading, spacing: 4) {
115
+ ZStack(alignment: .topLeading) {
116
+ HStack(alignment: .center) {
117
+ if let leadingIcon = leadingIcon {
118
+ Icon(source: leadingIcon, size: 16, color: leadingIconColor)
148
119
  }
149
- .padding(.horizontal, 12)
150
- .frame(height: size == .small ? 40 : 48)
151
- .background(disabled ? Colors.black01 : Color.white)
152
- .overlay(RoundedRectangle(cornerRadius: 8).stroke(focused ? Colors.pink03 : Colors.border, lineWidth: 1))
153
120
 
154
- if value.isEmpty {
155
- Text(placeholder + (required ? " *" : ""))
156
- .foregroundColor(Colors.black09)
157
- .padding(.horizontal, leadingIcon == nil ? 12 : 32)
158
- .padding(.top, 12)
159
- .allowsHitTesting(false)
121
+ if !isSecure {
122
+ TextField("", text: $value ,onEditingChanged: { isChange in
123
+ self.focused = isChange
124
+ })
125
+ .disabled(disabled)
126
+ .frame(maxHeight: .infinity)
127
+ .multilineTextAlignment(.leading)
128
+ .font(.system(size: scaleSize(14)))
129
+ } else {
130
+ DotTextField(value: $value)
131
+ .disabled(disabled)
160
132
  }
161
133
 
162
- if !floatingValue.isEmpty {
163
- FloatingComponent(floatingValue, floatingIcon: floatingIconURL.map { url in
164
- AnyView(
165
- Icon(source: url, size: 16, color: floatingIconColor)
166
- )
167
- })
168
-
134
+ if loading {
135
+ ActivityIndicator(isAnimating: .constant(true), style: .medium)
136
+ .frame(width: 20, height: 20)
137
+ } else if !value.isEmpty {
138
+ SwiftButton(action: onCancel){
139
+ Image(systemName: "x.circle.fill")
140
+ .foregroundColor(Colors.text)
141
+ .frame(width: 16, height: 16)
142
+ .padding(.leading, -4)
143
+ .padding(.trailing, 8)
144
+ }
169
145
  }
146
+
147
+ rightIcon
170
148
  }
149
+ .padding(.horizontal, 12)
150
+ .frame(height: size == .small ? scaleSize(40) : scaleSize(48))
151
+ .background(disabled ? Colors.black01 : Color.white)
152
+ .overlay(RoundedRectangle(cornerRadius: 8).stroke(focused ? Colors.pink03 : Colors.border, lineWidth: 1))
171
153
 
172
- if !errorMessage.isEmpty {
173
- Text(errorMessage)
174
- .foregroundColor(.red)
175
- .font(.caption)
176
- } else if let hint = hintText {
177
- Text(hint)
154
+ if value.isEmpty {
155
+ Text(placeholder + (required ? " *" : ""))
156
+ .font(.system(size: scaleSize(14)))
178
157
  .foregroundColor(Colors.black09)
179
- .font(.caption)
158
+ .padding(.horizontal, leadingIcon == nil ? 12 : 32)
159
+ .padding(.top, 12)
160
+ .allowsHitTesting(false)
180
161
  }
162
+
163
+ if !floatingValue.isEmpty {
164
+ FloatingComponent(floatingValue, floatingIcon: floatingIconURL.map { url in
165
+ AnyView(
166
+ Icon(source: url, size: 16, color: floatingIconColor)
167
+ )
168
+ })
169
+
170
+ }
171
+ }
172
+
173
+ if !errorMessage.isEmpty {
174
+ MomoText(errorMessage, typography: .descriptionDefaultRegular, color: Colors.red03)
175
+ } else if hintText != nil {
176
+ MomoText(errorMessage, typography: .descriptionDefaultRegular, color: Colors.black12)
181
177
  }
182
178
  }
179
+ }
183
180
  }
184
181
 
185
182
 
186
183
  struct ActivityIndicator: UIViewRepresentable {
187
184
  @Binding var isAnimating: Bool
188
185
  let style: UIActivityIndicatorView.Style
189
-
186
+
190
187
  func makeUIView(context: UIViewRepresentableContext<ActivityIndicator>) -> UIActivityIndicatorView {
191
188
  return UIActivityIndicatorView(style: style)
192
189
  }
193
-
190
+
194
191
  func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<ActivityIndicator>) {
195
192
  isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
196
193
  }
@@ -21,7 +21,7 @@ public enum PopupButtonType {
21
21
  public struct PopupDisplay: View {
22
22
  // MARK: Lifecycle
23
23
 
24
- public init(isPresented: Binding<Bool>, title: String = "", description: String = "", url: String = "", buttonDirection: PopupButtonDirection = .column, buttonType: PopupButtonType = .button, actionButtonTitle: String = "", closeButtonTitle: String = "", onPressAction: @escaping () -> Void = {}, onPressCloseButton: @escaping () -> Void = {}, onClose: @escaping () -> Void = {}, errorCode: String = "", isShowCloseIcon: Bool = true) {
24
+ public init(isPresented: Binding<Bool>, title: String = "", description: String = "", url: String = "", buttonDirection: PopupButtonDirection = .column, buttonType: PopupButtonType = .button, actionButtonTitle: String = "", closeButtonTitle: String = "", onPressAction: @escaping () -> Void = {}, onPressCloseButton: @escaping () -> Void = {}, onClose: @escaping () -> Void = {}, errorCode: String = "") {
25
25
  self._isPresented = isPresented
26
26
  self.title = title
27
27
  self.description = description
@@ -34,7 +34,6 @@ public struct PopupDisplay: View {
34
34
  self.closeButtonTitle = closeButtonTitle
35
35
  self.actionButtonTitle = actionButtonTitle
36
36
  self.errorCode = errorCode
37
- self.isShowCloseIcon = isShowCloseIcon
38
37
  }
39
38
 
40
39
  // MARK: Public
@@ -49,19 +48,17 @@ public struct PopupDisplay: View {
49
48
 
50
49
  VStack(spacing: 12) {
51
50
  ZStack(alignment: .topTrailing) {
52
- if(isShowCloseIcon) {
53
- SwiftUI.Button(action: onClose) {
54
- Image(systemName: "xmark.circle.fill")
55
- .resizable()
56
- .frame(width: 22, height: 22)
57
- .overlay(RoundedRectangle(cornerRadius: Radius.L).stroke(Colors.black01, lineWidth: 2))
58
- }.foregroundColor(Colors.black20)
59
- .background(Colors.black01.cornerRadius(15))
60
- .padding(.trailing, -11)
61
- .padding(.top, -11)
62
- .zIndex(1)
63
- .accessibility(identifier: "ic_popup_close")
64
- }
51
+ SwiftUI.Button(action: onClose) {
52
+ Image(systemName: "xmark.circle.fill")
53
+ .resizable()
54
+ .frame(width: 22, height: 22)
55
+ .overlay(RoundedRectangle(cornerRadius: Radius.L).stroke(Colors.black01, lineWidth: 2))
56
+ }.foregroundColor(Colors.black20)
57
+ .background(Colors.black01.cornerRadius(15))
58
+ .padding(.trailing, -11)
59
+ .padding(.top, -11)
60
+ .zIndex(1)
61
+ .accessibility(identifier: "ic_popup_close")
65
62
 
66
63
  VStack {
67
64
  if(!url.isEmpty) {
@@ -91,7 +88,8 @@ public struct PopupDisplay: View {
91
88
  ScrollView(showsIndicators: false) {
92
89
  Text(description)
93
90
  .font(.paragraph)
94
- .foregroundColor(Colors.black17)
91
+ .foregroundColor(.black)
92
+ .opacity(0.6)
95
93
  .multilineTextAlignment(.leading)
96
94
  .background(GeometryReader { geo in
97
95
  Color.clear
@@ -104,7 +102,8 @@ public struct PopupDisplay: View {
104
102
  } else {
105
103
  Text(description)
106
104
  .font(.paragraph)
107
- .foregroundColor(Colors.black17)
105
+ .foregroundColor(.black)
106
+ .opacity(0.6)
108
107
  .multilineTextAlignment(.leading)
109
108
  .background(GeometryReader { geo in
110
109
  Color.clear
@@ -117,7 +116,7 @@ public struct PopupDisplay: View {
117
116
  }.padding(.bottom, 8)
118
117
 
119
118
  if(!errorCode.isEmpty) {
120
- Text("Mã lỗi: " + errorCode)
119
+ Text(errorCode)
121
120
  .foregroundColor(Colors.black12)
122
121
  .font(.description_xs_regular)
123
122
  .lineLimit(1)
@@ -165,7 +164,6 @@ public struct PopupDisplay: View {
165
164
  var actionButtonTitle: String
166
165
  var closeButtonTitle: String
167
166
  var errorCode: String
168
- var isShowCloseIcon: Bool
169
167
 
170
168
  var RowTextButtons: some View {
171
169
  HStack {
@@ -203,9 +201,7 @@ public struct PopupDisplay: View {
203
201
 
204
202
  var RowButtons: some View {
205
203
  HStack {
206
- if(!closeButtonTitle.isEmpty) {
207
- Button(title: closeButtonTitle, action: onPressCloseButton, type: .secondary, size: .medium).accessibility(identifier: "btn_popup_cancel")
208
- }
204
+ Button(title: closeButtonTitle, action: onPressCloseButton, type: .secondary, size: .medium).accessibility(identifier: "btn_popup_cancel")
209
205
  Button(title: actionButtonTitle, action: onPressAction, size: .medium)
210
206
  .accessibility(identifier: "btn_popup_allow")
211
207
  }
@@ -221,9 +217,8 @@ public struct PopupDisplay: View {
221
217
  action: onPressAction,
222
218
  size: .medium
223
219
  ).accessibility(identifier: "btn_popup_allow")
224
- if(!closeButtonTitle.isEmpty) {
225
- Button(title: closeButtonTitle, action: onPressCloseButton, type: .secondary, size: .medium).accessibility(identifier: "btn_popup_cancel")
226
- }
220
+ Button(title: closeButtonTitle, action: onPressCloseButton, type: .secondary, size: .medium).accessibility(identifier: "btn_popup_cancel")
221
+
227
222
  }
228
223
  .frame(maxWidth: .infinity, alignment: .trailing)
229
224
  .padding(.horizontal, 24)
@@ -19,26 +19,22 @@ public struct PopupPromotion: View {
19
19
 
20
20
  public var body: some View {
21
21
  VStack(spacing: 8) {
22
- if(imageUrl.isEmpty) {
23
- Icon(source: "media_fail", size: UIScreen.main.bounds.width - Spacing.XL * 2)
24
- }
25
- else {
26
- WebImage(url: URL(string: imageUrl)!)
27
- .resizable()
28
- .placeholder {
29
- Color.gray
30
- .aspectRatio(0.72, contentMode: .fit)
31
- .frame(maxWidth: UIScreen.main.bounds.width - Spacing.XL * 2)
32
- .clipped()
33
- }
34
- .scaledToFill()
35
- .aspectRatio(0.72, contentMode: .fit)
36
- .frame(maxWidth: UIScreen.main.bounds.width - Spacing.XL * 2, alignment: .center)
37
- .clipped()
38
- .onTapGesture {
39
- onAction?()
40
- }
41
- }
22
+ WebImage(url: URL(string: imageUrl)!)
23
+ .resizable()
24
+ .placeholder {
25
+ Color.gray
26
+ .aspectRatio(0.72, contentMode: .fit)
27
+ .frame(maxWidth: UIScreen.main.bounds.width - Spacing.XL * 2)
28
+ .clipped()
29
+ }
30
+ .scaledToFill()
31
+ .aspectRatio(0.72, contentMode: .fit)
32
+ .frame(maxWidth: UIScreen.main.bounds.width - Spacing.XL * 2, alignment: .center)
33
+ .clipped()
34
+ .onTapGesture {
35
+ onAction?()
36
+ }
37
+
42
38
  buildCloseIcon()
43
39
  }
44
40
  .accessibility(identifier: "popup_promotion")
@@ -1,5 +1,32 @@
1
1
  import SwiftUI
2
2
 
3
+ public func scaleSize(_ size: CGFloat) -> CGFloat {
4
+ let defaultScreenSize: CGFloat = 375
5
+ let maxFontScale: CGFloat = 1.5
6
+ let maxDeviceScale: CGFloat = 5
7
+
8
+ let deviceWidth = UIScreen.main.bounds.width
9
+ let deviceScale = deviceWidth / defaultScreenSize
10
+
11
+ let defaultFont = UIFont.systemFont(ofSize: UIFont.labelFontSize)
12
+ let scaledFont = UIFontMetrics.default.scaledFont(for: defaultFont)
13
+ let fontScale = scaledFont.pointSize / defaultFont.pointSize
14
+
15
+ var fontSizeDeviceScale = size
16
+ var fontSizeOSScale = size
17
+
18
+
19
+ if deviceScale > 1 {
20
+ fontSizeDeviceScale = min(fontSizeDeviceScale * deviceScale, fontSizeDeviceScale + maxDeviceScale)
21
+ }
22
+
23
+ if fontScale > 1 {
24
+ fontSizeOSScale = min(fontSizeOSScale * fontScale, fontSizeOSScale * maxFontScale)
25
+ }
26
+
27
+ return max(fontSizeDeviceScale, fontSizeOSScale)
28
+ }
29
+
3
30
  public enum TypographyStyle {
4
31
  case headlineDefaultBold
5
32
  case headerMBold
@@ -63,13 +90,13 @@ public extension TypographyStyle {
63
90
  var fontWeight: Font.Weight {
64
91
  switch self {
65
92
  case .headlineDefaultBold, .headerMBold, .headerDefaultBold,
66
- .actionDefaultBold, .actionSBold, .actionXsBold, .actionXxsBold:
93
+ .actionDefaultBold, .actionSBold, .actionXsBold, .actionXxsBold:
67
94
  return .bold
68
95
  case .headerSSemibold, .headerXsSemibold:
69
96
  return .semibold
70
97
  case .bodyDefaultRegular, .bodyDefaultRegularStrikethrough,
71
- .descriptionDefaultRegular, .descriptionDefaultRegularStrikethrough,
72
- .descriptionXsRegular, .descriptionXsRegularStrikethrough:
98
+ .descriptionDefaultRegular, .descriptionDefaultRegularStrikethrough,
99
+ .descriptionXsRegular, .descriptionXsRegularStrikethrough:
73
100
  return .regular
74
101
  case .labelDefaultMedium, .labelSMedium, .labelXsMedium:
75
102
  return .medium
@@ -78,8 +105,8 @@ public extension TypographyStyle {
78
105
  var isStrikethrough: Bool {
79
106
  switch self {
80
107
  case .bodyDefaultRegularStrikethrough,
81
- .descriptionDefaultRegularStrikethrough,
82
- .descriptionXsRegularStrikethrough:
108
+ .descriptionDefaultRegularStrikethrough,
109
+ .descriptionXsRegularStrikethrough:
83
110
  return true
84
111
  default:
85
112
  return false
@@ -89,25 +116,25 @@ public extension TypographyStyle {
89
116
 
90
117
  public struct MomoText: View {
91
118
  private let content: String
92
- private let style: TypographyStyle
119
+ private let typography: TypographyStyle
93
120
  private let color: Color
94
-
121
+
95
122
  public init(
96
123
  _ content: String,
97
- style: TypographyStyle = .bodyDefaultRegular,
124
+ typography: TypographyStyle = .bodyDefaultRegular,
98
125
  color: Color = Colors.text
99
126
  ) {
100
127
  self.content = content
101
- self.style = style
128
+ self.typography = typography
102
129
  self.color = color
103
130
  }
104
-
131
+
105
132
  public var body: some View {
106
- var text = SwiftUI.Text(content)
107
- .font(.system(size: style.fontSize, weight: style.fontWeight))
133
+ let text = SwiftUI.Text(content)
134
+ .font(.system(size: scaleSize(typography.fontSize), weight: typography.fontWeight))
108
135
  .foregroundColor(color)
109
- .lineSpacing(style.lineHeight - style.fontSize)
110
-
136
+ .lineSpacing(scaleSize(typography.lineHeight) - scaleSize(typography.fontSize))
137
+
111
138
  return text
112
139
  }
113
- }
140
+ }
@@ -2,7 +2,28 @@ import SwiftUI
2
2
 
3
3
  public extension Font {
4
4
  static func appFont(size: CGFloat) -> Font {
5
- return Font.system(size: size)
5
+ let defaultScreenSize: CGFloat = 375
6
+ let maxFontScale: CGFloat = 1.2
7
+ let maxDeviceScale: CGFloat = 5
8
+
9
+ let deviceWidth = UIScreen.main.bounds.width
10
+ let deviceScale = deviceWidth / defaultScreenSize
11
+
12
+ let defaultFont = UIFont.systemFont(ofSize: UIFont.labelFontSize)
13
+ let scaledFont = UIFontMetrics.default.scaledFont(for: defaultFont)
14
+ let fontScale = scaledFont.pointSize / defaultFont.pointSize
15
+
16
+ var fontSize = size
17
+
18
+ if deviceScale > 1 {
19
+ fontSize = min(fontSize * deviceScale, fontSize + maxDeviceScale)
20
+ }
21
+
22
+ if fontScale > 1 {
23
+ fontSize = min(fontSize * fontScale, fontSize * maxFontScale)
24
+ }
25
+
26
+ return Font.system(size: fontSize)
6
27
  }
7
28
 
8
29
  // New supported typography styles
package/local.properties CHANGED
@@ -4,5 +4,5 @@
4
4
  # Location of the SDK. This is only used by Gradle.
5
5
  # For customization when using a Version Control System, please read the
6
6
  # header note.
7
- #Wed Aug 21 14:20:12 ICT 2024
8
- sdk.dir=/Users/huynhdung/Library/Android/sdk
7
+ #Tue Apr 08 11:13:31 ICT 2025
8
+ sdk.dir=/Users/sonnguyen/Library/Android/sdk
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.120.0-beta.3",
3
+ "version": "0.120.0-beta.9",
4
4
  "private": false,
5
5
  "dependencies": {},
6
6
  "devDependencies": {},