@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.
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +6 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +3 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +4 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +90 -37
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +34 -31
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +4 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +30 -19
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +8 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +7 -22
- package/ios/Application/Screen.swift +4 -1
- package/ios/Button/Button.swift +4 -4
- package/ios/Popup/PopupDisplay.swift +25 -20
- package/ios/Popup/PopupPromotion.swift +20 -16
- package/ios/Typography/Text.swift +113 -0
- package/ios/Typography/Typography.swift +1 -22
- package/local.properties +2 -2
- package/package.json +2 -1
|
@@ -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
|
|
171
|
-
|
|
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.
|
|
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()
|
|
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(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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.
|
|
111
|
+
if(image.isNotEmpty()) {
|
|
111
112
|
Image(
|
|
112
113
|
source = image,
|
|
113
114
|
modifier = Modifier.fillMaxWidth().aspectRatio(1.777f),
|
|
114
|
-
options = Options(alignment =
|
|
115
|
-
)
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
174
|
+
size = 16.dp,
|
|
175
|
+
modifier = Modifier.setAutomationId("ic_popup_close")
|
|
160
176
|
)
|
|
161
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
50
|
-
.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
26
|
-
|
|
27
|
-
|
|
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.
|
|
65
|
-
"SFProText-200" to "sfprotext_ultralight.
|
|
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.
|
|
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)
|
|
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
|
}
|
package/ios/Button/Button.swift
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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(.
|
|
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(.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
8
|
-
sdk.dir=/Users/
|
|
7
|
+
#Wed Aug 21 14:20:12 ICT 2024
|
|
8
|
+
sdk.dir=/Users/huynhdung/Library/Android/sdk
|