@momo-kits/native-kits 0.118.1-beta.2 → 0.120.0-beta.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.
@@ -27,6 +27,7 @@ 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
30
31
  import androidx.compose.ui.unit.Dp
31
32
  import androidx.compose.ui.unit.dp
32
33
  import androidx.compose.ui.zIndex
@@ -167,9 +168,11 @@ fun Header(
167
168
  HeaderTitle(
168
169
  title = title,
169
170
  color = tintIconColor,
170
- modifier = Modifier.semantics {
171
- testTag = "title_navigation_header"
172
- }
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
173
176
  )
174
177
  }
175
178
  }
@@ -4,6 +4,7 @@ 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
7
8
  import androidx.compose.ui.unit.sp
8
9
  import androidx.compose.ui.zIndex
9
10
  import vn.momo.kits.components.Text
@@ -25,6 +26,7 @@ fun HeaderTitle(
25
26
  lineHeight = 22.sp,
26
27
  ),
27
28
  color = color,
28
- maxLines = 1
29
+ maxLines = 1,
30
+ overflow = TextOverflow.Ellipsis
29
31
  )
30
32
  }
@@ -200,9 +200,8 @@ 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)
204
203
  .shadow(
205
- Colors.black_20.copy(alpha = 0.1f),
204
+ Colors.black_20.copy(alpha = 0.2f),
206
205
  borderRadius = 0.dp,
207
206
  10f,
208
207
  offsetX = 0.dp,
@@ -212,7 +211,9 @@ fun Footer(footer: @Composable (() -> Unit)? = null, keyboardSize: Dp = 0.dp, bo
212
211
  .background(AppTheme.current.colors.background.surface)
213
212
  ) {
214
213
  Box(
215
- Modifier.fillMaxWidth().padding(vertical = Spacing.S, horizontal = Spacing.M)
214
+ Modifier.fillMaxWidth()
215
+ .padding(bottom = bottom)
216
+ .padding(vertical = Spacing.S, horizontal = Spacing.M)
216
217
  ) {
217
218
  footer?.invoke()
218
219
  }
@@ -2,10 +2,6 @@ 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
9
5
  import androidx.compose.ui.Alignment
10
6
  import androidx.compose.ui.Modifier
11
7
  import androidx.compose.ui.graphics.ColorFilter
@@ -14,8 +10,6 @@ import androidx.compose.ui.graphics.painter.Painter
14
10
  import androidx.compose.ui.layout.ContentScale
15
11
  import androidx.compose.ui.semantics.contentDescription
16
12
  import androidx.compose.ui.semantics.semantics
17
- import coil3.compose.AsyncImage
18
- import vn.momo.kits.const.AppTheme
19
13
 
20
14
  data class Options(
21
15
  val alignment: Alignment = Alignment.TopStart,
@@ -28,6 +22,47 @@ enum class ImageState {
28
22
  Loading, Success, Error
29
23
  }
30
24
 
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
+
31
66
  @Composable
32
67
  fun Image(
33
68
  source: Any,
@@ -35,41 +70,59 @@ fun Image(
35
70
  options: Options? = null,
36
71
  loading: Boolean = true,
37
72
  ) {
38
- var imageOptions = Options()
39
- var imageState by remember { mutableStateOf(ImageState.Loading) }
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
40
77
 
41
78
  options?.let {
42
- imageOptions = Options(
43
- alignment = options.alignment,
44
- contentScale = options.contentScale,
45
- colorFilter = options.colorFilter,
46
- alpha = DefaultAlpha,
47
- )
79
+ // imageOptions = Options(
80
+ // alignment = options.alignment,
81
+ // contentScale = options.contentScale,
82
+ // colorFilter = options.colorFilter,
83
+ // alpha = DefaultAlpha,
84
+ // )
48
85
  }
49
86
 
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
- }
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
+ // }
73
126
  }
74
127
 
75
128
  }
@@ -57,6 +57,7 @@ fun PopupNotify(
57
57
  secondary: PopupAction? = null,
58
58
  buttonDirection: PopupActionDirection = PopupActionDirection.ROW,
59
59
  onIconClose: () -> Unit,
60
+ isShowCloseIcon: Boolean = true,
60
61
  ) {
61
62
  var isScroll by remember { mutableStateOf(false) }
62
63
  val layoutResult = remember { mutableStateOf<TextLayoutResult?>(null) }
@@ -107,13 +108,13 @@ fun PopupNotify(
107
108
  RoundedCornerShape(Radius.L)
108
109
  )
109
110
  ) {
110
- image.ifNotNull(
111
+ if(image.isNotEmpty()) {
111
112
  Image(
112
113
  source = image,
113
114
  modifier = Modifier.fillMaxWidth().aspectRatio(1.777f),
114
- options = Options(alignment = Alignment.Center)
115
- ), null
116
- )
115
+ options = Options(alignment = Alignment.Center)
116
+ )
117
+ }
117
118
 
118
119
  Column(modifier = Modifier.padding(Spacing.XL)) {
119
120
  Text(
@@ -125,7 +126,7 @@ fun PopupNotify(
125
126
  content(Modifier.padding(top = Spacing.S))
126
127
  if (error.isNotEmpty()) {
127
128
  Text(
128
- text = error,
129
+ text = "Mã lỗi: " + error,
129
130
  style = Typography.descriptionXsRegular,
130
131
  color = AppTheme.current.colors.text.hint,
131
132
  maxLines = 1,
@@ -144,34 +145,36 @@ fun PopupNotify(
144
145
  }
145
146
  }
146
147
 
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,
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",
158
173
  color = AppTheme.current.colors.background.surface,
159
- shape = RoundedCornerShape(Radius.M)
174
+ size = 16.dp,
175
+ modifier = Modifier.setAutomationId("ic_popup_close")
160
176
  )
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
- )
177
+ }
175
178
  }
176
179
  }
177
180
  }
@@ -24,6 +24,7 @@ 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
27
28
 
28
29
  @Composable
29
30
  fun PopupPromotion(
@@ -32,7 +33,7 @@ fun PopupPromotion(
32
33
  onClose: (() -> Unit) = {},
33
34
  ) {
34
35
  Column(Modifier.semantics { testTag = "popup_notify" }) {
35
- if (source.isNotEmpty()) {
36
+ source.ifNotNull(
36
37
  Image(
37
38
  source = source,
38
39
  modifier = Modifier.fillMaxWidth()
@@ -43,8 +44,8 @@ fun PopupPromotion(
43
44
  onClick = onPress
44
45
  ),
45
46
  options = Options(alignment = Alignment.Center, contentScale = ContentScale.Crop)
46
- )
47
- }
47
+ ), null
48
+ )
48
49
  Box(
49
50
  modifier = Modifier
50
51
  .padding(top = Spacing.S)
@@ -17,6 +17,7 @@ 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
20
21
  import vn.momo.kits.utils.ifTrue
21
22
 
22
23
  @Composable
@@ -24,6 +25,8 @@ fun Switch(
24
25
  value: Boolean = false,
25
26
  onChange: (Boolean) -> Unit = {},
26
27
  disabled: Boolean = false,
28
+ title: String? = "",
29
+ accessibilityId: String? = ""
27
30
  ) {
28
31
  val circleBackgroundColor = value.ifTrue(Colors.black_01, Colors.black_03)
29
32
  var backgroundColor = value.ifTrue(Colors.green_03, Colors.black_07)
@@ -32,27 +35,35 @@ fun Switch(
32
35
  }
33
36
 
34
37
  Box(
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)
38
+ modifier = Modifier.setAutomationId( accessibilityId ?: "toggle_${title ?: "unknown"}_${value}")
47
39
  ) {
48
40
  Box(
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)))
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
+ }
56
67
  }
57
68
  }
58
69
  }
@@ -8,6 +8,7 @@ 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
11
12
  import androidx.compose.ui.unit.sp
12
13
  import vn.momo.kits.const.AppTheme
13
14
  import vn.momo.kits.const.Typography
@@ -27,6 +28,9 @@ fun Text(
27
28
  textDecoration: TextDecoration? = null,
28
29
  onTextLayout: ((TextLayoutResult) -> Unit)? = null,
29
30
  fontFamily: String = "SFProText",
31
+ minLines: Int = 1,
32
+ letterSpacing: TextUnit = TextUnit.Unspecified,
33
+ softWrap: Boolean = true,
30
34
  accessibilityId: String? = null
31
35
  ) {
32
36
  val fontSize = scaleSize(style.fontSize.value).sp
@@ -41,8 +45,11 @@ fun Text(
41
45
  fontSize = fontSize,
42
46
  fontFamily = font,
43
47
  maxLines = maxLines,
48
+ softWrap = softWrap,
49
+ letterSpacing = letterSpacing,
50
+ minLines = minLines,
44
51
  textDecoration = textDecoration,
45
52
  onTextLayout = onTextLayout,
46
53
  overflow = overflow
47
54
  )
48
- }
55
+ }
@@ -2,7 +2,6 @@ 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
6
5
  import androidx.compose.ui.text.TextStyle
7
6
  import androidx.compose.ui.text.font.FontFamily
8
7
  import androidx.compose.ui.text.font.FontWeight
@@ -13,31 +12,17 @@ import org.jetbrains.compose.resources.FontResource
13
12
  import org.jetbrains.compose.resources.InternalResourceApi
14
13
  import org.jetbrains.compose.resources.ResourceItem
15
14
  import vn.momo.kits.platform.getScreenDimensions
16
- import kotlin.math.min
17
15
 
18
16
  const val DEFAULT_SCREEN_SIZE = 375f
19
- const val MAX_FONT_SCALE = 1.2f
20
- const val MAX_DEVICE_SCALE = 5
21
17
 
22
18
  @Composable
23
19
  fun scaleSize(size: Float): Float {
24
20
  val deviceWidth = getScreenDimensions().width
25
- val deviceScale = deviceWidth / DEFAULT_SCREEN_SIZE
26
-
27
- val density = LocalDensity.current
28
- val fontScale = density.fontScale
29
-
30
- var fontSize = size / fontScale //To ignore OS scale
31
-
32
- if (deviceScale > 1) {
33
- fontSize = min(deviceScale * fontSize, fontSize + MAX_DEVICE_SCALE)
21
+ val scaleRate = deviceWidth / DEFAULT_SCREEN_SIZE
22
+ if (scaleRate > 1) {
23
+ return if ((scaleRate * size) > (size + 5)) size + 5 else scaleRate * size
34
24
  }
35
-
36
- if (fontScale > 1) {
37
- fontSize = min(fontScale * fontSize, fontSize * MAX_FONT_SCALE)
38
- }
39
-
40
- return fontSize
25
+ return size
41
26
  }
42
27
 
43
28
  @OptIn(InternalResourceApi::class)
@@ -61,15 +46,15 @@ fun getFont(font: String): FontFamily {
61
46
  fun getFontFamily(fontFamily: String, fontWeight: FontWeight? = FontWeight.Normal): FontFamily {
62
47
  val key = "$fontFamily-${fontWeight?.weight}"
63
48
  val fontMap = mapOf(
64
- "SFProText-100" to "sfprotext_thin.otf",
65
- "SFProText-200" to "sfprotext_ultralight.otf",
49
+ "SFProText-100" to "sfprotext_thin.ttf",
50
+ "SFProText-200" to "sfprotext_ultralight.ttf",
66
51
  "SFProText-300" to "sfprotext_light.ttf",
67
52
  "SFProText-400" to "sfprotext_regular.ttf",
68
53
  "SFProText-500" to "sfprotext_medium.ttf",
69
54
  "SFProText-600" to "sfprotext_semibold.ttf",
70
55
  "SFProText-700" to "sfprotext_bold.ttf",
71
56
  "SFProText-800" to "sfprotext_heavy.ttf",
72
- "SFProText-900" to "sfprotext_black.otf",
57
+ "SFProText-900" to "sfprotext_black.ttf",
73
58
 
74
59
  "MoMoSignature-100" to "momosignature.otf",
75
60
  "MoMoSignature-200" to "momosignature.otf",
@@ -88,7 +88,8 @@ public struct Screen<Content: View>: View {
88
88
  let screenHeight = height + topInset + bottomInset
89
89
 
90
90
  ZStack {
91
- (backgroundColor ?? Color.white).edgesIgnoringSafeArea(.all)
91
+ (backgroundColor ?? Color.white)
92
+ .edgesIgnoringSafeArea(.top)
92
93
 
93
94
  VStack(spacing: 0) {
94
95
  ZStack(alignment: .top) {
@@ -184,6 +185,8 @@ struct Footer: View {
184
185
  VStack {
185
186
  footerView
186
187
  }
188
+ .padding(.vertical, Spacing.S)
189
+ .padding(.horizontal, Spacing.M)
187
190
  .padding(.bottom, keyboardSize)
188
191
  .background(Colors.black01)
189
192
  }
@@ -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)
45
+ renderTitle(title: title, type: type, size: size).lineLimit(1).truncationMode(.tail)
46
46
  iconRight
47
47
  }
48
48
  .buttonSize(size)
@@ -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.XS)
126
+ .padding([.leading, .trailing], Spacing.L)
127
127
  case .medium:
128
128
  return self
129
129
  .frame(minWidth: 80, maxWidth: .infinity, minHeight: 36, maxHeight: 36)
130
- .padding([.leading, .trailing], Spacing.XS)
130
+ .padding([.leading, .trailing], Spacing.M)
131
131
  case .small:
132
132
  return self
133
133
  .frame(minWidth: 56, maxWidth: 76, minHeight: 28, maxHeight: 28)
134
- .padding([.leading, .trailing], Spacing.XS)
134
+ .padding([.leading, .trailing], Spacing.S)
135
135
  }
136
136
  }
137
137
  }
@@ -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 = "") {
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) {
25
25
  self._isPresented = isPresented
26
26
  self.title = title
27
27
  self.description = description
@@ -34,6 +34,7 @@ public struct PopupDisplay: View {
34
34
  self.closeButtonTitle = closeButtonTitle
35
35
  self.actionButtonTitle = actionButtonTitle
36
36
  self.errorCode = errorCode
37
+ self.isShowCloseIcon = isShowCloseIcon
37
38
  }
38
39
 
39
40
  // MARK: Public
@@ -48,17 +49,19 @@ public struct PopupDisplay: View {
48
49
 
49
50
  VStack(spacing: 12) {
50
51
  ZStack(alignment: .topTrailing) {
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")
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
+ }
62
65
 
63
66
  VStack {
64
67
  if(!url.isEmpty) {
@@ -88,8 +91,7 @@ public struct PopupDisplay: View {
88
91
  ScrollView(showsIndicators: false) {
89
92
  Text(description)
90
93
  .font(.paragraph)
91
- .foregroundColor(.black)
92
- .opacity(0.6)
94
+ .foregroundColor(Colors.black17)
93
95
  .multilineTextAlignment(.leading)
94
96
  .background(GeometryReader { geo in
95
97
  Color.clear
@@ -102,8 +104,7 @@ public struct PopupDisplay: View {
102
104
  } else {
103
105
  Text(description)
104
106
  .font(.paragraph)
105
- .foregroundColor(.black)
106
- .opacity(0.6)
107
+ .foregroundColor(Colors.black17)
107
108
  .multilineTextAlignment(.leading)
108
109
  .background(GeometryReader { geo in
109
110
  Color.clear
@@ -116,7 +117,7 @@ public struct PopupDisplay: View {
116
117
  }.padding(.bottom, 8)
117
118
 
118
119
  if(!errorCode.isEmpty) {
119
- Text(errorCode)
120
+ Text("Mã lỗi: " + errorCode)
120
121
  .foregroundColor(Colors.black12)
121
122
  .font(.description_xs_regular)
122
123
  .lineLimit(1)
@@ -164,6 +165,7 @@ public struct PopupDisplay: View {
164
165
  var actionButtonTitle: String
165
166
  var closeButtonTitle: String
166
167
  var errorCode: String
168
+ var isShowCloseIcon: Bool
167
169
 
168
170
  var RowTextButtons: some View {
169
171
  HStack {
@@ -201,7 +203,9 @@ public struct PopupDisplay: View {
201
203
 
202
204
  var RowButtons: some View {
203
205
  HStack {
204
- Button(title: closeButtonTitle, action: onPressCloseButton, type: .secondary, size: .medium).accessibility(identifier: "btn_popup_cancel")
206
+ if(!closeButtonTitle.isEmpty) {
207
+ Button(title: closeButtonTitle, action: onPressCloseButton, type: .secondary, size: .medium).accessibility(identifier: "btn_popup_cancel")
208
+ }
205
209
  Button(title: actionButtonTitle, action: onPressAction, size: .medium)
206
210
  .accessibility(identifier: "btn_popup_allow")
207
211
  }
@@ -217,8 +221,9 @@ public struct PopupDisplay: View {
217
221
  action: onPressAction,
218
222
  size: .medium
219
223
  ).accessibility(identifier: "btn_popup_allow")
220
- Button(title: closeButtonTitle, action: onPressCloseButton, type: .secondary, size: .medium).accessibility(identifier: "btn_popup_cancel")
221
-
224
+ if(!closeButtonTitle.isEmpty) {
225
+ Button(title: closeButtonTitle, action: onPressCloseButton, type: .secondary, size: .medium).accessibility(identifier: "btn_popup_cancel")
226
+ }
222
227
  }
223
228
  .frame(maxWidth: .infinity, alignment: .trailing)
224
229
  .padding(.horizontal, 24)
@@ -19,22 +19,26 @@ public struct PopupPromotion: View {
19
19
 
20
20
  public var body: some View {
21
21
  VStack(spacing: 8) {
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
-
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
+ }
38
42
  buildCloseIcon()
39
43
  }
40
44
  .accessibility(identifier: "popup_promotion")
@@ -0,0 +1,113 @@
1
+ import SwiftUI
2
+
3
+ public enum TypographyStyle {
4
+ case headlineDefaultBold
5
+ case headerMBold
6
+ case headerDefaultBold
7
+ case headerSSemibold
8
+ case headerXsSemibold
9
+ case bodyDefaultRegular
10
+ case bodyDefaultRegularStrikethrough
11
+ case descriptionDefaultRegular
12
+ case descriptionDefaultRegularStrikethrough
13
+ case descriptionXsRegular
14
+ case descriptionXsRegularStrikethrough
15
+ case labelDefaultMedium
16
+ case labelSMedium
17
+ case labelXsMedium
18
+ case actionDefaultBold
19
+ case actionSBold
20
+ case actionXsBold
21
+ case actionXxsBold
22
+ }
23
+
24
+ public extension TypographyStyle {
25
+ var fontSize: CGFloat {
26
+ switch self {
27
+ case .headlineDefaultBold: return 24
28
+ case .headerMBold: return 18
29
+ case .headerDefaultBold: return 16
30
+ case .headerSSemibold: return 14
31
+ case .headerXsSemibold: return 12
32
+ case .bodyDefaultRegular, .bodyDefaultRegularStrikethrough: return 14
33
+ case .descriptionDefaultRegular, .descriptionDefaultRegularStrikethrough: return 12
34
+ case .descriptionXsRegular, .descriptionXsRegularStrikethrough: return 10
35
+ case .labelDefaultMedium: return 14
36
+ case .labelSMedium: return 12
37
+ case .labelXsMedium: return 10
38
+ case .actionDefaultBold: return 16
39
+ case .actionSBold: return 14
40
+ case .actionXsBold: return 12
41
+ case .actionXxsBold: return 10
42
+ }
43
+ }
44
+ var lineHeight: CGFloat {
45
+ switch self {
46
+ case .headlineDefaultBold: return 34
47
+ case .headerMBold: return 26
48
+ case .headerDefaultBold: return 22
49
+ case .headerSSemibold: return 20
50
+ case .headerXsSemibold: return 18
51
+ case .bodyDefaultRegular, .bodyDefaultRegularStrikethrough: return 20
52
+ case .descriptionDefaultRegular, .descriptionDefaultRegularStrikethrough: return 18
53
+ case .descriptionXsRegular, .descriptionXsRegularStrikethrough: return 14
54
+ case .labelDefaultMedium: return 20
55
+ case .labelSMedium: return 18
56
+ case .labelXsMedium: return 14
57
+ case .actionDefaultBold: return 22
58
+ case .actionSBold: return 20
59
+ case .actionXsBold: return 18
60
+ case .actionXxsBold: return 14
61
+ }
62
+ }
63
+ var fontWeight: Font.Weight {
64
+ switch self {
65
+ case .headlineDefaultBold, .headerMBold, .headerDefaultBold,
66
+ .actionDefaultBold, .actionSBold, .actionXsBold, .actionXxsBold:
67
+ return .bold
68
+ case .headerSSemibold, .headerXsSemibold:
69
+ return .semibold
70
+ case .bodyDefaultRegular, .bodyDefaultRegularStrikethrough,
71
+ .descriptionDefaultRegular, .descriptionDefaultRegularStrikethrough,
72
+ .descriptionXsRegular, .descriptionXsRegularStrikethrough:
73
+ return .regular
74
+ case .labelDefaultMedium, .labelSMedium, .labelXsMedium:
75
+ return .medium
76
+ }
77
+ }
78
+ var isStrikethrough: Bool {
79
+ switch self {
80
+ case .bodyDefaultRegularStrikethrough,
81
+ .descriptionDefaultRegularStrikethrough,
82
+ .descriptionXsRegularStrikethrough:
83
+ return true
84
+ default:
85
+ return false
86
+ }
87
+ }
88
+ }
89
+
90
+ public struct MomoText: View {
91
+ private let content: String
92
+ private let style: TypographyStyle
93
+ private let color: Color
94
+
95
+ public init(
96
+ _ content: String,
97
+ style: TypographyStyle = .bodyDefaultRegular,
98
+ color: Color = Colors.text
99
+ ) {
100
+ self.content = content
101
+ self.style = style
102
+ self.color = color
103
+ }
104
+
105
+ public var body: some View {
106
+ var text = SwiftUI.Text(content)
107
+ .font(.system(size: style.fontSize, weight: style.fontWeight))
108
+ .foregroundColor(color)
109
+ .lineSpacing(style.lineHeight - style.fontSize)
110
+
111
+ return text
112
+ }
113
+ }
@@ -2,28 +2,7 @@ import SwiftUI
2
2
 
3
3
  public extension Font {
4
4
  static func appFont(size: CGFloat) -> Font {
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)
5
+ return Font.system(size: size)
27
6
  }
28
7
 
29
8
  // 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
- #Tue Apr 08 11:13:31 ICT 2025
8
- sdk.dir=/Users/sonnguyen/Library/Android/sdk
7
+ #Wed Aug 21 14:20:12 ICT 2024
8
+ sdk.dir=/Users/huynhdung/Library/Android/sdk
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.118.1-beta.2",
3
+ "version": "0.120.0-beta.3",
4
4
  "private": false,
5
5
  "dependencies": {},
6
+ "devDependencies": {},
6
7
  "license": "MoMo"
7
8
  }