@momo-kits/native-kits 0.117.1 → 0.118.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,6 +23,7 @@ import androidx.compose.runtime.setValue
23
23
  import androidx.compose.runtime.snapshotFlow
24
24
  import androidx.compose.ui.Alignment
25
25
  import androidx.compose.ui.Modifier
26
+ import androidx.compose.ui.graphics.Color
26
27
  import androidx.compose.ui.graphics.graphicsLayer
27
28
  import androidx.compose.ui.semantics.semantics
28
29
  import androidx.compose.ui.semantics.testTag
@@ -53,11 +54,13 @@ fun Header(
53
54
  scrollState: Int = 0,
54
55
  inputSearchProps: InputSearchProps? = null,
55
56
  headerRightWidth: Dp = 0.dp,
56
- useAnimationSearch: Boolean = false
57
+ useAnimationSearch: Boolean = false,
58
+ tintColor: Color? = null
57
59
  ) {
58
60
  val statusBarHeight = AppStatusBar.current ?: getStatusBarHeight()
59
- val tintIconColor = AppTheme.current.colors.text.default
60
- val backgroundButton = Colors.black_01.copy(alpha = 0.6f)
61
+ val tintIconColor = tintColor ?: AppTheme.current.colors.text.default
62
+ val backgroundButton = if (tintColor == Colors.black_01) Colors.black_20.copy(alpha = 0.6f) else Colors.black_01.copy(alpha = 0.6f)
63
+ val borderColor = if (tintColor == Colors.black_01) Colors.black_01.copy(alpha = 0.2f) else Colors.black_20.copy(alpha = 0.2f)
61
64
 
62
65
  var colorFraction by remember { mutableStateOf(0f) }
63
66
 
@@ -105,7 +108,7 @@ fun Header(
105
108
  .then(
106
109
  Modifier.border(
107
110
  0.2.dp,
108
- Colors.black_20.copy(alpha = 0.2f),
111
+ borderColor,
109
112
  RoundedCornerShape(100)
110
113
  )
111
114
  )
@@ -122,7 +125,7 @@ fun Header(
122
125
  ) {
123
126
  Icon(
124
127
  source = "arrow-back",
125
- color = Colors.black_20,
128
+ color = tintIconColor,
126
129
  size = 20.dp,
127
130
  )
128
131
  }
@@ -34,6 +34,7 @@ fun animateColor(start: Color, stop: Color, fraction: Float): Color {
34
34
  fun HeaderBackground(
35
35
  headerType: HeaderType = HeaderType.DEFAULT,
36
36
  scrollState: Int,
37
+ headerTransparent: Boolean = false,
37
38
  ) {
38
39
  val statusBarHeight = AppStatusBar.current ?: getStatusBarHeight()
39
40
 
@@ -42,11 +43,15 @@ fun HeaderBackground(
42
43
  )
43
44
 
44
45
  val backgroundColor by animateColorAsState(
45
- targetValue = animateColor(
46
- Color.Transparent,
47
- AppTheme.current.colors.background.surface,
48
- opacityAni
49
- ),
46
+ targetValue = if (headerTransparent) {
47
+ Color.Transparent
48
+ } else {
49
+ animateColor(
50
+ Color.Transparent,
51
+ AppTheme.current.colors.background.surface,
52
+ opacityAni
53
+ )
54
+ }
50
55
  )
51
56
 
52
57
  when (headerType) {
@@ -54,6 +59,8 @@ fun HeaderBackground(
54
59
  HeaderDefault(
55
60
  opacityAni = opacityAni,
56
61
  statusBarHeight = statusBarHeight,
62
+ backgroundColor = backgroundColor,
63
+ headerTransparent = headerTransparent
57
64
  )
58
65
  }
59
66
 
@@ -23,14 +23,16 @@ import vn.momo.kits.utils.bottomBorder
23
23
  fun HeaderDefault(
24
24
  opacityAni: Float,
25
25
  statusBarHeight: Dp,
26
+ backgroundColor: Color?,
27
+ headerTransparent: Boolean = false
26
28
  ) {
27
29
  val height = statusBarHeight + HEADER_HEIGHT.dp
28
30
  Box(
29
31
  modifier = Modifier.fillMaxWidth().height(height)
30
- .background(AppTheme.current.colors.background.surface)
32
+ .background(if(headerTransparent && backgroundColor != null) backgroundColor else AppTheme.current.colors.background.surface)
31
33
  .bottomBorder(
32
34
  strokeWidth = 1.dp,
33
- color = AppTheme.current.colors.border.default
35
+ color = if(headerTransparent && backgroundColor != null) backgroundColor else AppTheme.current.colors.border.default
34
36
  )
35
37
  ) {
36
38
  Box(
@@ -39,14 +41,24 @@ fun HeaderDefault(
39
41
  .fillMaxWidth()
40
42
  .alpha(opacityAni)
41
43
  .background(
42
- Brush.linearGradient(
43
- colors = listOf(
44
- Color(0xFFFDCADE),
45
- Color(0x00FDCADE)
46
- ),
47
- start = Offset(0f, 0f),
48
- end = Offset(0f, with(LocalDensity.current) { 154.dp.toPx() })
49
- )
44
+ if (headerTransparent)
45
+ Brush.linearGradient(
46
+ colors = listOf(
47
+ Color.Transparent,
48
+ Color.Transparent,
49
+ ),
50
+ start = Offset(0f, 0f),
51
+ end = Offset(0f, with(LocalDensity.current) { 154.dp.toPx() })
52
+ )
53
+ else
54
+ Brush.linearGradient(
55
+ colors = listOf(
56
+ Color(0xFFFDCADE),
57
+ Color(0x00FDCADE)
58
+ ),
59
+ start = Offset(0f, 0f),
60
+ end = Offset(0f, with(LocalDensity.current) { 154.dp.toPx() })
61
+ )
50
62
  )
51
63
  )
52
64
  if (AppTheme.current.assets.headerBackground != null) {
@@ -30,6 +30,7 @@ import vn.momo.kits.const.Colors
30
30
  import kotlinx.serialization.json.Json
31
31
  import kotlinx.serialization.json.jsonObject
32
32
  import kotlinx.serialization.json.jsonPrimitive
33
+ import vn.momo.kits.const.AppTheme
33
34
 
34
35
  data class HeaderRightData(
35
36
  val useShortcut: Boolean = false,
@@ -80,11 +81,12 @@ object Spacing {
80
81
  @Composable
81
82
  fun HeaderRight(
82
83
  headerRight: HeaderRightData? = null,
84
+ tintColor: Color? = null,
83
85
  ) {
84
86
  Row(
85
87
  verticalAlignment = Alignment.CenterVertically,
86
88
  ) {
87
- ToolkitHeaderRight(headerRight = headerRight)
89
+ ToolkitHeaderRight(headerRight = headerRight, tintColor = tintColor)
88
90
  }
89
91
  }
90
92
 
@@ -113,7 +115,7 @@ private fun getNavigationButtonConfig(
113
115
  }
114
116
 
115
117
  @Composable
116
- private fun ToolkitHeaderRight(headerRight: HeaderRightData? = null) {
118
+ private fun ToolkitHeaderRight(headerRight: HeaderRightData? = null, tintColor: Color? = null) {
117
119
  var isFavorite by remember { mutableStateOf(false) }
118
120
  val isLoading by remember { mutableStateOf(false) }
119
121
  val api = PlatformApi.current as? ComposeApi
@@ -191,6 +193,10 @@ private fun ToolkitHeaderRight(headerRight: HeaderRightData? = null) {
191
193
  isShowShortcut = false
192
194
  }
193
195
 
196
+ val tintIconColor = tintColor ?: AppTheme.current.colors.text.default
197
+ val backgroundButton = if (tintColor == Colors.black_01) Colors.black_20.copy(alpha = 0.6f) else Colors.black_01.copy(alpha = 0.6f)
198
+ val borderColor = if (tintColor == Colors.black_01) Colors.black_01.copy(alpha = 0.2f) else Colors.black_20.copy(alpha = 0.2f)
199
+
194
200
  Row(
195
201
  verticalAlignment = Alignment.CenterVertically
196
202
  ) {
@@ -199,7 +205,8 @@ private fun ToolkitHeaderRight(headerRight: HeaderRightData? = null) {
199
205
  disabled = isLoading,
200
206
  icon = navButtonConfig.icon,
201
207
  showBadge = showBadge,
202
- onClick = navButtonConfig.onPress
208
+ onClick = navButtonConfig.onPress,
209
+ tintColor = tintIconColor
203
210
  )
204
211
  }
205
212
  Row(
@@ -207,16 +214,17 @@ private fun ToolkitHeaderRight(headerRight: HeaderRightData? = null) {
207
214
  horizontalArrangement = Arrangement.Center,
208
215
  modifier = Modifier
209
216
  .padding(start = Spacing.S)
210
- .border(0.2.dp, Color(0x33000000), shape = RoundedCornerShape(14.dp))
217
+ .border(0.2.dp, borderColor, shape = RoundedCornerShape(14.dp))
211
218
  .width(toolkitWidth)
212
219
  .height(28.dp)
213
220
  .clip(shape = RoundedCornerShape(14.dp))
214
- .background(Color(0x99FFFFFF))
221
+ .background(backgroundButton)
215
222
  ) {
216
223
  if (context != null) {
217
224
  Icon(
218
225
  source = "help_center",
219
226
  size = 20.dp,
227
+ color = tintIconColor,
220
228
  modifier = Modifier.padding(4.dp).clickable {
221
229
  onPressHelpCenter()
222
230
  })
@@ -224,13 +232,14 @@ private fun ToolkitHeaderRight(headerRight: HeaderRightData? = null) {
224
232
  modifier = Modifier
225
233
  .width(0.5.dp)
226
234
  .height(12.dp)
227
- .background(Colors.black_20)
235
+ .background(tintColor ?: Colors.black_20)
228
236
  )
229
237
  }
230
238
 
231
239
  Icon(
232
240
  source = "16_navigation_close_circle",
233
241
  size = 20.dp,
242
+ color = tintIconColor,
234
243
  modifier = Modifier.padding(4.dp).clickable { onPressClose() }
235
244
  )
236
245
  }
@@ -242,8 +251,12 @@ fun NavigationButton(
242
251
  disabled: Boolean,
243
252
  icon: String,
244
253
  showBadge: Boolean? = false,
245
- onClick: () -> Unit
254
+ onClick: () -> Unit,
255
+ tintColor: Color?
246
256
  ) {
257
+ val backgroundButton = if (tintColor == Colors.black_01) Colors.black_20.copy(alpha = 0.6f) else Colors.black_01.copy(alpha = 0.6f)
258
+ val borderColor = if (tintColor == Colors.black_01) Colors.black_01.copy(alpha = 0.2f) else Colors.black_20.copy(alpha = 0.2f)
259
+
247
260
  Box(
248
261
  modifier = Modifier
249
262
  .size(28.dp)
@@ -253,8 +266,8 @@ fun NavigationButton(
253
266
  modifier = Modifier
254
267
  .matchParentSize()
255
268
  .clip(CircleShape)
256
- .background(Color(0x99FFFFFF))
257
- .border(0.2.dp, Color(0x33000000), CircleShape)
269
+ .background(backgroundButton)
270
+ .border(0.2.dp, borderColor, CircleShape)
258
271
  )
259
272
  Box(
260
273
  modifier = Modifier.matchParentSize(),
@@ -262,7 +275,8 @@ fun NavigationButton(
262
275
  ) {
263
276
  Icon(
264
277
  source = icon,
265
- size = 20.dp
278
+ size = 20.dp,
279
+ color = tintColor
266
280
  )
267
281
  }
268
282
 
@@ -52,6 +52,8 @@ const val HEADER_HEIGHT = 52
52
52
  @Composable
53
53
  fun Screen(
54
54
  backgroundColor: Color? = null,
55
+ tintColor: Color? = null,
56
+ headerTransparent: Boolean = false,
55
57
  isBack: Boolean = true,
56
58
  headerType: HeaderType = HeaderType.DEFAULT,
57
59
  verticalArrangement: Arrangement.Vertical = Arrangement.Top,
@@ -103,7 +105,8 @@ fun Screen(
103
105
  if (animatedHeader === null) {
104
106
  HeaderBackground(
105
107
  headerType = headerType,
106
- scrollState = scrollState.value
108
+ scrollState = scrollState.value,
109
+ headerTransparent = headerTransparent
107
110
  )
108
111
  } else {
109
112
  Box(
@@ -129,6 +132,7 @@ fun Screen(
129
132
  inputSearchProps = inputSearchProps,
130
133
  scrollState = scrollState.value,
131
134
  useAnimationSearch = useAnimationSearch,
135
+ tintColor = tintColor
132
136
  )
133
137
 
134
138
  Column(
@@ -8,6 +8,7 @@ import androidx.compose.animation.core.rememberInfiniteTransition
8
8
  import androidx.compose.animation.core.tween
9
9
  import androidx.compose.foundation.background
10
10
  import androidx.compose.foundation.border
11
+ import androidx.compose.foundation.clickable
11
12
  import androidx.compose.foundation.layout.Arrangement
12
13
  import androidx.compose.foundation.layout.Box
13
14
  import androidx.compose.foundation.layout.Column
@@ -35,6 +36,7 @@ import androidx.compose.ui.text.input.KeyboardType
35
36
  import androidx.compose.ui.unit.dp
36
37
  import androidx.compose.ui.zIndex
37
38
  import vn.momo.kits.const.AppTheme
39
+ import vn.momo.kits.const.Colors
38
40
  import vn.momo.kits.const.Radius
39
41
  import vn.momo.kits.const.Spacing
40
42
  import vn.momo.kits.const.Typography
@@ -87,7 +89,7 @@ fun InputOTP(
87
89
  dataType: String = "number",
88
90
  modifier: Modifier = Modifier
89
91
  ) {
90
- val maxLength = 10
92
+ val maxLength = length ?: 10
91
93
  var isFocused by remember { mutableStateOf(false) }
92
94
  var isBlurred = false
93
95
 
@@ -100,6 +102,10 @@ fun InputOTP(
100
102
  }
101
103
  }
102
104
 
105
+ fun onClearText(){
106
+ handleChangeText("")
107
+ }
108
+
103
109
 
104
110
  Column {
105
111
  BasicTextField(
@@ -193,6 +199,22 @@ fun InputOTP(
193
199
  }
194
200
  }
195
201
  }
202
+ Box(
203
+ modifier = Modifier.align(Alignment.Center).zIndex(10f)
204
+ ) {
205
+ Row(
206
+ horizontalArrangement = Arrangement.End,
207
+ modifier = Modifier.fillMaxWidth()
208
+ ) {
209
+ Icon(
210
+ "24_navigation_close_circle_full",
211
+ size = 12.dp,
212
+ modifier = Modifier.padding(end = 12.dp).clickable {
213
+ onClearText()
214
+ }
215
+ )
216
+ }
217
+ }
196
218
  }
197
219
  }
198
220
  )
@@ -110,7 +110,8 @@ fun PopupNotify(
110
110
  image.ifNotNull(
111
111
  Image(
112
112
  source = image,
113
- modifier = Modifier.fillMaxWidth().aspectRatio(1.777f)
113
+ modifier = Modifier.fillMaxWidth().aspectRatio(1.777f),
114
+ options = Options(alignment = Alignment.Center)
114
115
  ), null
115
116
  )
116
117
 
@@ -17,6 +17,7 @@ import androidx.compose.runtime.remember
17
17
  import androidx.compose.ui.Alignment
18
18
  import androidx.compose.ui.Modifier
19
19
  import androidx.compose.ui.draw.clip
20
+ import androidx.compose.ui.layout.ContentScale
20
21
  import androidx.compose.ui.semantics.semantics
21
22
  import androidx.compose.ui.semantics.testTag
22
23
  import androidx.compose.ui.unit.dp
@@ -25,15 +26,23 @@ import vn.momo.kits.const.Radius
25
26
  import vn.momo.kits.const.Spacing
26
27
 
27
28
  @Composable
28
- fun PromotionPopup(
29
+ fun PopupPromotion(
29
30
  source: String = "",
31
+ onPress: (() -> Unit) = {},
30
32
  onClose: (() -> Unit) = {},
31
33
  ) {
32
34
  Column(Modifier.semantics { testTag = "popup_notify" }) {
33
35
  if (source.isNotEmpty()) {
34
36
  Image(
35
37
  source = source,
36
- modifier = Modifier.fillMaxWidth().aspectRatio(0.72f),
38
+ modifier = Modifier.fillMaxWidth()
39
+ .aspectRatio(0.72f)
40
+ .clickable(
41
+ interactionSource = remember { MutableInteractionSource() },
42
+ indication = null,
43
+ onClick = onPress
44
+ ),
45
+ options = Options(alignment = Alignment.Center, contentScale = ContentScale.Crop)
37
46
  )
38
47
  }
39
48
  Box(
@@ -0,0 +1,208 @@
1
+ package vn.momo.kits.components
2
+
3
+ import androidx.compose.foundation.background
4
+ import androidx.compose.foundation.clickable
5
+ import androidx.compose.foundation.layout.Arrangement
6
+ import androidx.compose.foundation.layout.Box
7
+ import androidx.compose.foundation.layout.Column
8
+ import androidx.compose.foundation.layout.IntrinsicSize
9
+ import androidx.compose.foundation.layout.Row
10
+ import androidx.compose.foundation.layout.Spacer
11
+ import androidx.compose.foundation.layout.fillMaxHeight
12
+ import androidx.compose.foundation.layout.fillMaxWidth
13
+ import androidx.compose.foundation.layout.height
14
+ import androidx.compose.foundation.layout.padding
15
+ import androidx.compose.foundation.layout.size
16
+ import androidx.compose.foundation.layout.width
17
+ import androidx.compose.foundation.shape.RoundedCornerShape
18
+ import androidx.compose.runtime.Composable
19
+ import androidx.compose.ui.Alignment
20
+ import androidx.compose.ui.Modifier
21
+ import androidx.compose.ui.graphics.Color
22
+ import androidx.compose.ui.text.TextStyle
23
+ import androidx.compose.ui.text.font.FontWeight
24
+ import androidx.compose.ui.unit.dp
25
+ import androidx.compose.ui.unit.sp
26
+ import vn.momo.kits.const.AppTheme
27
+ import vn.momo.kits.const.Colors
28
+ import vn.momo.kits.const.Spacing
29
+ import vn.momo.kits.const.Typography
30
+
31
+ enum class TitleType { Card, Section }
32
+ enum class TitleSize { Small, Medium, Large }
33
+ enum class IconAlign { Top, Center, Bottom }
34
+ enum class ButtonSize { Small, Large }
35
+
36
+ @Composable
37
+ fun Title(
38
+ title: String,
39
+ type: TitleType = TitleType.Section,
40
+ size: TitleSize = TitleSize.Medium,
41
+ icon: String? = null,
42
+ iconColor: Color? = null,
43
+ iconAlign: IconAlign = IconAlign.Top,
44
+ showRightAction: Boolean = false,
45
+ showTrailingAction: Boolean = false,
46
+ badgeLabel: String? = null,
47
+ buttonTitle: String? = null,
48
+ buttonSize: ButtonSize = ButtonSize.Small,
49
+ onPressRightAction: () -> Unit = {},
50
+ onPressTrailingAction: () -> Unit = {},
51
+ textOnly: Boolean = false,
52
+ description: String? = null,
53
+ modifier: Modifier = Modifier
54
+ ) {
55
+ val theme = AppTheme.current
56
+ val textStyle: TextStyle = when (type) {
57
+ TitleType.Card -> when (size) {
58
+ TitleSize.Small -> TextStyle(fontSize = 14.sp, lineHeight = 20.sp)
59
+ TitleSize.Medium -> TextStyle(fontSize = 16.sp, lineHeight = 22.sp)
60
+ TitleSize.Large -> TextStyle(fontSize = 18.sp, lineHeight = 26.sp)
61
+ }
62
+
63
+ TitleType.Section -> when (size) {
64
+ TitleSize.Small -> TextStyle(
65
+ fontSize = 16.sp,
66
+ lineHeight = 24.sp,
67
+ fontWeight = FontWeight.Bold
68
+ )
69
+
70
+ TitleSize.Medium -> TextStyle(
71
+ fontSize = 18.sp,
72
+ lineHeight = 26.sp,
73
+ fontWeight = FontWeight.Bold
74
+ )
75
+
76
+ TitleSize.Large -> TextStyle(
77
+ fontSize = 20.sp,
78
+ lineHeight = 28.sp,
79
+ fontWeight = FontWeight.Bold
80
+ )
81
+ }
82
+ }
83
+
84
+ if (textOnly) {
85
+ Text(
86
+ text = title,
87
+ style = textStyle,
88
+ modifier = modifier
89
+ )
90
+ return
91
+ }
92
+
93
+ Row(
94
+ modifier = modifier
95
+ .fillMaxWidth()
96
+ .height(IntrinsicSize.Min)
97
+ .padding(8.dp),
98
+ verticalAlignment = Alignment.CenterVertically
99
+ ) {
100
+ icon?.let {
101
+ Column(
102
+ modifier = Modifier.fillMaxHeight().padding(end = Spacing.S),
103
+ verticalArrangement = when (iconAlign) {
104
+ IconAlign.Top -> Arrangement.Top
105
+ IconAlign.Center -> Arrangement.Center
106
+ IconAlign.Bottom -> Arrangement.Bottom
107
+ },
108
+ ) {
109
+ // Icon
110
+ Icon(
111
+ source = it,
112
+ color = iconColor ?: theme.colors.text.default,
113
+ modifier = Modifier.size(24.dp)
114
+ )
115
+ }
116
+ }
117
+
118
+ Column(
119
+ modifier = Modifier.weight(1f)
120
+ ) {
121
+ Row(
122
+ verticalAlignment = Alignment.CenterVertically
123
+ ) {
124
+ Row(
125
+ verticalAlignment = Alignment.CenterVertically,
126
+ modifier = if (showTrailingAction) Modifier else Modifier.weight(1f),
127
+ ) {
128
+ Text(
129
+ text = title,
130
+ style = textStyle,
131
+ maxLines = if (showTrailingAction || badgeLabel != null) 1 else 2,
132
+ )
133
+
134
+ // Badge
135
+ badgeLabel?.let {
136
+ Spacer(modifier = Modifier.width(4.dp))
137
+ Badge(
138
+ label = it
139
+ )
140
+ }
141
+
142
+ }
143
+
144
+ // Trailing action (icon)
145
+ if (showTrailingAction && !showRightAction) {
146
+ Spacer(modifier = Modifier.width(8.dp))
147
+ Box(
148
+ modifier = Modifier
149
+ .size(24.dp)
150
+ .background(
151
+ color = if (type == TitleType.Section) Colors.black_06.copy(0.6f) else Colors.black_06.copy(
152
+ 0.3f
153
+ ),
154
+ shape = RoundedCornerShape(12.dp)
155
+ )
156
+ .clickable { onPressTrailingAction() },
157
+ contentAlignment = Alignment.Center
158
+ ) {
159
+ Icon(
160
+ source = "arrow_chevron_right_small",
161
+ size = 18.dp
162
+ )
163
+ }
164
+ }
165
+ }
166
+
167
+ // Description
168
+ description?.let {
169
+ Spacer(modifier = Modifier.height(4.dp))
170
+ Text(
171
+ text = it,
172
+ style = Typography.descriptionDefaultRegular,
173
+ color = theme.colors.text.secondary
174
+ )
175
+ }
176
+ }
177
+
178
+ // Right action (button or icon)
179
+ if (showRightAction && !showTrailingAction) {
180
+ Spacer(modifier = Modifier.width(8.dp))
181
+ if (buttonTitle != null) {
182
+ Text(
183
+ text = buttonTitle,
184
+ style = if (buttonSize === ButtonSize.Small) Typography.actionXsBold else Typography.actionSBold,
185
+ color = theme.colors.primary,
186
+ modifier = Modifier.clickable { onPressRightAction() }
187
+ )
188
+ } else {
189
+ Box(
190
+ modifier = Modifier
191
+ .size(24.dp)
192
+ .background(
193
+ color = Colors.pink_03.copy(alpha = 0.1f),
194
+ shape = RoundedCornerShape(12.dp)
195
+ )
196
+ .clickable { onPressRightAction() },
197
+ contentAlignment = Alignment.Center
198
+ ) {
199
+ Icon(
200
+ source = "arrow_chevron_right_small",
201
+ modifier = Modifier.size(22.dp),
202
+ color = theme.colors.primary
203
+ )
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
@@ -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,31 @@ 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.min
15
17
 
16
18
  const val DEFAULT_SCREEN_SIZE = 375f
19
+ const val MAX_FONT_SCALE = 1.2f
20
+ const val MAX_DEVICE_SCALE = 5
17
21
 
18
22
  @Composable
19
23
  fun scaleSize(size: Float): Float {
20
24
  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
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)
24
34
  }
25
- return size
35
+
36
+ if (fontScale > 1) {
37
+ fontSize = min(fontScale * fontSize, fontSize * MAX_FONT_SCALE)
38
+ }
39
+
40
+ return fontSize
26
41
  }
27
42
 
28
43
  @OptIn(InternalResourceApi::class)
@@ -46,15 +61,15 @@ fun getFont(font: String): FontFamily {
46
61
  fun getFontFamily(fontFamily: String, fontWeight: FontWeight? = FontWeight.Normal): FontFamily {
47
62
  val key = "$fontFamily-${fontWeight?.weight}"
48
63
  val fontMap = mapOf(
49
- "SFProText-100" to "sfprotext_thin.otf",
50
- "SFProText-200" to "sfprotext_ultralight.otf",
64
+ "SFProText-100" to "sfprotext_thin.ttf",
65
+ "SFProText-200" to "sfprotext_ultralight.ttf",
51
66
  "SFProText-300" to "sfprotext_light.ttf",
52
67
  "SFProText-400" to "sfprotext_regular.ttf",
53
68
  "SFProText-500" to "sfprotext_medium.ttf",
54
69
  "SFProText-600" to "sfprotext_semibold.ttf",
55
70
  "SFProText-700" to "sfprotext_bold.ttf",
56
71
  "SFProText-800" to "sfprotext_heavy.ttf",
57
- "SFProText-900" to "sfprotext_black.otf",
72
+ "SFProText-900" to "sfprotext_black.ttf",
58
73
 
59
74
  "MoMoSignature-100" to "momosignature.otf",
60
75
  "MoMoSignature-200" to "momosignature.otf",
@@ -45,13 +45,24 @@ func safeAreaTopInset() -> CGFloat {
45
45
  public struct HeaderBackground: View {
46
46
  var headerType: HeaderType = .default
47
47
  var scrollState: CGFloat
48
+ var isTransparent: Bool = false
49
+
50
+ public init(
51
+ headerType: HeaderType = .default,
52
+ scrollState: CGFloat = 0,
53
+ isTransparent: Bool = false
54
+ ) {
55
+ self.headerType = headerType
56
+ self.scrollState = scrollState
57
+ self.isTransparent = isTransparent
58
+ }
48
59
 
49
60
  public var body: some View {
50
61
 
51
62
  let statusBarHeight = safeAreaTopInset()
52
63
 
53
64
  let opacity = 1 - CGFloat(scrollState) / 200
54
- let backgroundColor = scrollState == 0 ? Color.clear : Colors.black01
65
+ let backgroundColor = isTransparent ? Color.clear : Colors.black01
55
66
 
56
67
  Group {
57
68
  if headerType == .default {
@@ -62,7 +73,9 @@ public struct HeaderBackground: View {
62
73
  .fill(backgroundColor)
63
74
  .frame(height: height)
64
75
  .shadow(color: Colors.black20.opacity(0.2), radius: 10, x: 0, y: -2)
65
- .overlay(Rectangle()
76
+ .overlay(
77
+ isTransparent ? nil :
78
+ Rectangle()
66
79
  .fill(LinearGradient(
67
80
  gradient: Gradient(colors: [
68
81
  Color(red: 1, green: 0.8, blue: 0.87),
@@ -72,7 +85,9 @@ public struct HeaderBackground: View {
72
85
  endPoint: .bottom))
73
86
  .opacity(opacity)
74
87
  .frame(height: height))
75
- .overlay(Rectangle()
88
+ .overlay(
89
+ isTransparent ? nil :
90
+ Rectangle()
76
91
  .fill(Colors.black04)
77
92
  .frame(height: 1)
78
93
  .frame(maxWidth: .infinity)
@@ -111,6 +126,7 @@ public struct Header: View {
111
126
  var animatedHeader: AnimatedHeader? = nil
112
127
  var scrollState: CGFloat = 0
113
128
  var inputSearchProps: InputSearchProps? = nil
129
+ var tintColor: Color? = nil
114
130
 
115
131
  @State private var colorFraction: CGFloat = 0
116
132
 
@@ -123,7 +139,8 @@ public struct Header: View {
123
139
  opacity: CGFloat? = nil,
124
140
  animatedHeader: AnimatedHeader? = nil,
125
141
  scrollState: CGFloat = 0,
126
- inputSearchProps: InputSearchProps? = nil
142
+ inputSearchProps: InputSearchProps? = nil,
143
+ tintColor: Color? = nil
127
144
  ) {
128
145
  self.headerType = headerType
129
146
  self.titlePosition = titlePosition
@@ -134,12 +151,13 @@ public struct Header: View {
134
151
  self.animatedHeader = animatedHeader
135
152
  self.scrollState = scrollState
136
153
  self.inputSearchProps = inputSearchProps
154
+ self.tintColor = tintColor
137
155
  }
138
156
 
139
157
  public var body: some View {
140
158
 
141
- let tintIconColor = Colors.black17
142
- let backgroundButton = Colors.black01.opacity(0.6)
159
+ let backgroundButtonColor: Color = tintColor == Colors.black01 ? Colors.black20.opacity(0.6) : Colors.black01.opacity(0.6)
160
+ let borderColor: Color = tintColor == Colors.black01 ? Colors.black01.opacity(0.2) : Colors.black20.opacity(0.2)
143
161
 
144
162
  if headerType != .none {
145
163
  VStack(spacing: 0) {
@@ -148,11 +166,11 @@ public struct Header: View {
148
166
  if let goBack = goBack {
149
167
  SwiftUI.Button(action: goBack) {
150
168
  Circle()
151
- .stroke(Color.black.opacity(0.2), lineWidth: 0.2)
152
- .background(Circle().fill(backgroundButton))
169
+ .stroke(borderColor, lineWidth: 0.2)
170
+ .background(Circle().fill(backgroundButtonColor))
153
171
  .frame(width: 28, height: 28)
154
172
  .overlay(
155
- Icon(source: "arrow-back", size: 20, color: Colors.black17)
173
+ Icon(source: "arrow-back", size: 20, color: tintColor)
156
174
  )
157
175
  }
158
176
 
@@ -196,8 +214,9 @@ public struct Header: View {
196
214
  }
197
215
  } else {
198
216
  Text(title)
199
- .font(Font.system(size: 17).weight(.bold)) .lineSpacing(22)
200
- .foregroundColor(tintIconColor)
217
+ .font(Font.system(size: 17).weight(.bold))
218
+ .lineSpacing(22)
219
+ .foregroundColor(tintColor)
201
220
  .frame(maxWidth: titlePosition == .center ? UIScreen.main.bounds.width - 252 : nil)
202
221
  .frame(maxWidth: titlePosition == .center ? .infinity : UIScreen.main.bounds.width - 172, alignment: titlePosition == .center ? .center : .leading)
203
222
  .lineLimit(1)
@@ -75,20 +75,23 @@ struct NavigationButtonConfig {
75
75
 
76
76
  // MARK: - Components
77
77
  public struct HeaderRight: View {
78
- public init(headerRight: HeaderRightData? = nil) {
78
+ public init(headerRight: HeaderRightData? = nil, tintColor: Color? = nil) {
79
79
  self.headerRight = headerRight
80
+ self.tintColor = tintColor
80
81
  }
81
82
  var headerRight: HeaderRightData?
83
+ var tintColor: Color? = nil
82
84
 
83
85
  public var body: some View {
84
86
  HStack(alignment: .center) {
85
- ToolkitHeaderRight(headerRight: headerRight)
87
+ ToolkitHeaderRight(headerRight: headerRight, tintColor: tintColor)
86
88
  }
87
89
  }
88
90
  }
89
91
 
90
92
  struct ToolkitHeaderRight: View {
91
93
  var headerRight: HeaderRightData?
94
+ var tintColor: Color?
92
95
  @State private var isFavorite: Bool = false
93
96
  @State private var isLoading: Bool = false
94
97
  @EnvironmentObject private var environment: ApplicationEnvironment
@@ -176,44 +179,47 @@ struct ToolkitHeaderRight: View {
176
179
  }
177
180
 
178
181
  var body: some View {
182
+ let backgroundButtonColor: Color = tintColor == Colors.black01 ? Colors.black20.opacity(0.6) : Colors.black01.opacity(0.6)
183
+ let borderColor: Color = tintColor == Colors.black01 ? Colors.black01.opacity(0.2) : Colors.black20.opacity(0.2)
179
184
  let navButtonConfig = getNavigationButtonConfig()
180
185
  let showBadge = headerRight?.tools.contains { group in
181
186
  group.items.contains { $0.showBadge }
182
187
  } ?? false
183
188
 
184
- HStack(alignment: .center) {
189
+ HStack(alignment: .center, spacing: 0) {
185
190
  if headerRight?.useShortcut == true {
186
191
  NavigationButton(
187
192
  disabled: isLoading,
188
193
  icon: navButtonConfig.icon,
189
194
  showBadge: showBadge,
190
- onClick: navButtonConfig.onPress
195
+ onClick: navButtonConfig.onPress,
196
+ tintColor: tintColor
191
197
  )
192
198
  }
193
199
 
194
200
  HStack(alignment: .center, spacing: 0) {
195
- Icon(source: "help_center", size: 20)
201
+ Icon(source: "help_center", size: 20, color: tintColor ?? Colors.black17)
196
202
  .padding(4)
197
203
  .onTapGesture {
198
204
  onPressHelpCenter()
199
205
  }
200
206
 
201
207
  Rectangle()
202
- .fill(Colors.black20)
208
+ .fill(tintColor ?? Colors.black20)
203
209
  .frame(width: 0.5, height: 12)
204
210
 
205
- Icon(source: "16_navigation_close_circle", size: 20, color: Colors.black17)
211
+ Icon(source: "16_navigation_close_circle", size: 20, color: tintColor ?? Colors.black17)
206
212
  .padding(4)
207
213
  .onTapGesture {
208
214
  onPressClose()
209
215
  }
210
216
  }
211
217
  .frame(width: 65, height: 28)
212
- .background(Color.white.opacity(0.6))
218
+ .background(backgroundButtonColor)
213
219
  .cornerRadius(14)
214
220
  .overlay(
215
221
  RoundedRectangle(cornerRadius: 14)
216
- .stroke(Color.black.opacity(0.2), lineWidth: 0.2)
222
+ .stroke(borderColor, lineWidth: 0.2)
217
223
  )
218
224
  .padding(.leading, Spacing.S)
219
225
  }
@@ -225,17 +231,21 @@ struct NavigationButton: View {
225
231
  var icon: String
226
232
  var showBadge: Bool
227
233
  var onClick: () -> Void
234
+ var tintColor: Color?
228
235
 
229
236
  var body: some View {
237
+ let backgroundButtonColor: Color = tintColor == Colors.black01 ? Colors.black20.opacity(0.6) : Colors.black01.opacity(0.6)
238
+ let borderColor: Color = tintColor == Colors.black01 ? Colors.black01.opacity(0.2) : Colors.black20.opacity(0.2)
239
+
230
240
  ZStack {
231
241
  Circle()
232
- .fill(Color.white.opacity(0.6))
242
+ .fill(backgroundButtonColor)
233
243
  .overlay(
234
244
  Circle()
235
- .stroke(Color.black.opacity(0.2), lineWidth: 0.2)
245
+ .stroke(borderColor, lineWidth: 0.2)
236
246
  )
237
247
 
238
- Icon(source: icon, size: 16)
248
+ Icon(source: icon, size: 16, color: tintColor)
239
249
 
240
250
  if showBadge {
241
251
  BadgeDot(size: .small)
@@ -9,6 +9,8 @@ import SwiftUI
9
9
 
10
10
  public struct Screen<Content: View>: View {
11
11
  var backgroundColor: Color?
12
+ var headerTransparent: Bool = false
13
+ var tintColor: Color? = Colors.black17
12
14
  var isBack: Bool
13
15
  var headerType: HeaderType
14
16
  var verticalAlignment: VerticalAlignment
@@ -29,6 +31,8 @@ public struct Screen<Content: View>: View {
29
31
 
30
32
  public init(
31
33
  backgroundColor: Color? = nil,
34
+ headerTransparent: Bool = false,
35
+ tintColor: Color = Colors.black17,
32
36
  isBack: Bool = true,
33
37
  headerType: HeaderType = .default,
34
38
  verticalAlignment: VerticalAlignment = .top,
@@ -48,6 +52,8 @@ public struct Screen<Content: View>: View {
48
52
  @ViewBuilder content: @escaping () -> Content
49
53
  ) {
50
54
  self.backgroundColor = backgroundColor
55
+ self.headerTransparent = headerTransparent
56
+ self.tintColor = tintColor
51
57
  self.isBack = isBack
52
58
  self.headerType = headerType
53
59
  self.verticalAlignment = verticalAlignment
@@ -70,6 +76,9 @@ public struct Screen<Content: View>: View {
70
76
  @State private var scrollOffset: CGFloat = 0
71
77
 
72
78
  public var body: some View {
79
+
80
+ let headerRightInstance = {HeaderRight(tintColor: tintColor)}
81
+
73
82
  GeometryReader { geometry in
74
83
  let topInset = geometry.safeAreaInsets.top
75
84
  let keyboardHeight: CGFloat = 0
@@ -95,14 +104,15 @@ public struct Screen<Content: View>: View {
95
104
  headerType: headerType,
96
105
  titlePosition: titlePosition,
97
106
  title: title,
98
- headerRight: headerRight,
107
+ headerRight: headerRight ?? headerRightInstance,
99
108
  goBack: goBack,
100
109
  opacity: min(scrollOffset / 114, 1),
101
110
  animatedHeader: animatedHeader,
102
111
  scrollState: scrollOffset,
103
- inputSearchProps: inputSearchProps
112
+ inputSearchProps: inputSearchProps,
113
+ tintColor: tintColor
104
114
  )
105
- .background(HeaderBackground(headerType: headerType, scrollState: scrollOffset).edgesIgnoringSafeArea(.top))
115
+ .background(HeaderBackground(headerType: headerType, scrollState: scrollOffset, isTransparent: headerTransparent).edgesIgnoringSafeArea(.top))
106
116
  .offset(y: topInset)
107
117
 
108
118
  }
@@ -27,7 +27,7 @@ public struct ImageView: View {
27
27
  error = false
28
28
  }
29
29
  .resizable()
30
- .renderingMode(.original)
30
+ .renderingMode(color != nil ? .template : .original)
31
31
  .placeholder {
32
32
  Rectangle().foregroundColor(.gray)
33
33
  VStack(alignment: .center, content: {
@@ -49,7 +49,7 @@ public struct ImageView: View {
49
49
  .scaledToFit()
50
50
  .transition(.fade)
51
51
  .aspectRatio(aspectRatio, contentMode: contentMode)
52
- .modifier(ColorMultiplyModifier(color: color))
52
+ .foregroundColor(color)
53
53
  }
54
54
 
55
55
  // MARK: Internal
@@ -6,6 +6,7 @@ import SwiftUI
6
6
  public enum PopupButtonDirection {
7
7
  case row
8
8
  case column
9
+ case auto
9
10
  }
10
11
 
11
12
  // MARK: - PopupButtonType
@@ -20,7 +21,7 @@ public enum PopupButtonType {
20
21
  public struct PopupDisplay: View {
21
22
  // MARK: Lifecycle
22
23
 
23
- 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 = {}) {
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
25
  self._isPresented = isPresented
25
26
  self.title = title
26
27
  self.description = description
@@ -32,18 +33,26 @@ public struct PopupDisplay: View {
32
33
  self.onClose = onClose
33
34
  self.closeButtonTitle = closeButtonTitle
34
35
  self.actionButtonTitle = actionButtonTitle
36
+ self.errorCode = errorCode
35
37
  }
36
38
 
37
39
  // MARK: Public
38
40
 
41
+ @State private var textHeight: CGFloat = .zero
42
+ @State private var isScrollable = false
43
+
39
44
  public var body: some View {
45
+ var shouldUseColumn: Bool {
46
+ buttonDirection == .column || (buttonDirection == .auto && (closeButtonTitle.count > 12 || actionButtonTitle.count > 12))
47
+ }
48
+
40
49
  VStack(spacing: 12) {
41
50
  ZStack(alignment: .topTrailing) {
42
- SwiftUI.Button(action: onPressClose) {
51
+ SwiftUI.Button(action: onClose) {
43
52
  Image(systemName: "xmark.circle.fill")
44
53
  .resizable()
45
54
  .frame(width: 22, height: 22)
46
- .overlay(RoundedRectangle(cornerRadius: Radius.L).stroke(Colors.black01, lineWidth: 1))
55
+ .overlay(RoundedRectangle(cornerRadius: Radius.L).stroke(Colors.black01, lineWidth: 2))
47
56
  }.foregroundColor(Colors.black20)
48
57
  .background(Colors.black01.cornerRadius(15))
49
58
  .padding(.trailing, -11)
@@ -51,50 +60,84 @@ public struct PopupDisplay: View {
51
60
  .zIndex(1)
52
61
  .accessibility(identifier: "ic_popup_close")
53
62
 
54
- VStack {
55
- if(!url.isEmpty) {
56
- WebImage(url: URL(string: url), isAnimating: .constant(true))
57
- .resizable()
58
- .placeholder {
59
- Rectangle()
60
- .fill(Color.gray.opacity(0.3))
61
- .frame(maxWidth: .infinity, maxHeight: 184)
62
- }
63
- .scaledToFill()
64
- .frame(maxWidth: .infinity)
65
- .clipShape(RoundedCorner(radius: 15, corners: [.topLeft, .topRight]))
66
- .aspectRatio(2, contentMode: .fit)
67
- }
63
+ VStack {
64
+ if(!url.isEmpty) {
65
+ WebImage(url: URL(string: url), isAnimating: .constant(true))
66
+ .resizable()
67
+ .placeholder {
68
+ Rectangle()
69
+ .fill(Color.gray.opacity(0.3))
70
+ .frame(maxWidth: .infinity, maxHeight: 184)
71
+ }
72
+ .aspectRatio(2, contentMode: .fit)
73
+ .frame(maxWidth: .infinity, alignment: .center)
74
+ .clipShape(RoundedCorner(radius: 15, corners: [.topLeft, .topRight]))
75
+ .clipped()
76
+ }
68
77
  VStack(alignment: .leading) {
69
78
  Text(title)
70
79
  .foregroundColor(.black)
71
80
  .font(.header_default_semibold)
72
- .padding(.top, 16)
81
+ .padding(.top, 24)
73
82
  .padding(.bottom, 8)
74
83
  .lineLimit(2)
75
84
  .accessibility(identifier: "title_popup_permission")
76
85
 
77
- Text(description)
78
- .foregroundColor(.black)
79
- .font(.paragraph)
80
- .opacity(0.6)
81
- .multilineTextAlignment(.leading)
82
- .padding(.bottom, 20)
86
+ Group {
87
+ if isScrollable {
88
+ ScrollView(showsIndicators: false) {
89
+ Text(description)
90
+ .font(.paragraph)
91
+ .foregroundColor(.black)
92
+ .opacity(0.6)
93
+ .multilineTextAlignment(.leading)
94
+ .background(GeometryReader { geo in
95
+ Color.clear
96
+ .onAppear {
97
+ textHeight = geo.size.height
98
+ }
99
+ })
100
+ }
101
+ .frame(height: 60)
102
+ } else {
103
+ Text(description)
104
+ .font(.paragraph)
105
+ .foregroundColor(.black)
106
+ .opacity(0.6)
107
+ .multilineTextAlignment(.leading)
108
+ .background(GeometryReader { geo in
109
+ Color.clear
110
+ .onAppear {
111
+ textHeight = geo.size.height
112
+ isScrollable = textHeight > 60 // adjust threshold to ~3 lines
113
+ }
114
+ })
115
+ }
116
+ }.padding(.bottom, 8)
117
+
118
+ if(!errorCode.isEmpty) {
119
+ Text(errorCode)
120
+ .foregroundColor(Colors.black12)
121
+ .font(.description_xs_regular)
122
+ .lineLimit(1)
123
+ .padding(.bottom, 8)
124
+ }
83
125
  }
84
126
  .frame(maxWidth: .infinity, alignment: .leading)
85
127
  .padding(.horizontal, 24)
128
+ .padding(.bottom, 16)
86
129
 
87
- if buttonDirection == .row {
130
+ if shouldUseColumn {
88
131
  if buttonType == .text {
89
- RowTextButtons
132
+ ColumnTextButtons
90
133
  } else {
91
- RowButtons
134
+ ColumnButtons
92
135
  }
93
136
  } else {
94
137
  if buttonType == .text {
95
- ColumnTextButtons
138
+ RowTextButtons
96
139
  } else {
97
- ColumnButtons
140
+ RowButtons
98
141
  }
99
142
  }
100
143
 
@@ -120,7 +163,8 @@ public struct PopupDisplay: View {
120
163
  var onClose: () -> Void
121
164
  var actionButtonTitle: String
122
165
  var closeButtonTitle: String
123
-
166
+ var errorCode: String
167
+
124
168
  var RowTextButtons: some View {
125
169
  HStack {
126
170
  SwiftUI.Button(action: onPressCloseButton) {
@@ -181,9 +225,4 @@ public struct PopupDisplay: View {
181
225
  .padding(.bottom, 24)
182
226
  }
183
227
 
184
-
185
- func onPressClose(){
186
- isPresented = false
187
- onClose()
188
- }
189
228
  }
@@ -1,52 +1,67 @@
1
- import SDWebImageSwiftUI
2
1
  import SwiftUI
2
+ import SDWebImageSwiftUI
3
3
 
4
4
  public struct PopupPromotion: View {
5
- // MARK: Lifecycle
6
-
7
- public init(isPresented: Binding<Bool>, url: String = "", buttonTitle: String = "", onPressAction: @escaping () -> Void = {}, onClose: @escaping () -> Void = {}) {
8
- self._isPresented = isPresented
9
- self.onPressAction = onPressAction
10
- self.buttonTitle = buttonTitle
11
- self.onClose = onClose
12
- self.url = url
13
- }
5
+ var imageUrl: String
6
+ var onIconClose: (() -> Void)?
7
+ var onAction: (() -> Void)?
14
8
 
15
- // MARK: Public
9
+
10
+ public init(
11
+ imageUrl: String,
12
+ onIconClose: (() -> Void)? = nil,
13
+ onAction: (() -> Void)? = nil
14
+ ) {
15
+ self.imageUrl = imageUrl
16
+ self.onIconClose = onIconClose
17
+ self.onAction = onAction
18
+ }
16
19
 
17
20
  public var body: some View {
18
- VStack(spacing: 12) {
19
- ZStack(alignment: .bottom) {
20
- WebImage(url: URL(string: url), isAnimating: .constant(true))
21
- .resizable()
22
- .placeholder {
23
- Circle().foregroundColor(.gray)
24
- }
25
- .scaledToFill()
26
- .frame(maxWidth: .infinity, maxHeight: .infinity)
27
- .cornerRadius(8)
28
-
29
- Button(title: buttonTitle, action: onPressAction)
30
- .padding(.horizontal, 24)
31
- .padding(.bottom, 24)
32
- }.clipped()
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
+
38
+ buildCloseIcon()
33
39
  }
34
- .frame(maxWidth: .infinity, maxHeight: 400)
35
- .shadow(color: .black.opacity(0.08), radius: 2, x: 0, y: 0)
36
- .shadow(color: .black.opacity(0.16), radius: 24, x: 0, y: 0)
37
- .padding(.horizontal, 40)
40
+ .accessibility(identifier: "popup_promotion")
38
41
  }
39
42
 
40
- // MARK: Internal
43
+ @ViewBuilder
44
+ private func buildCloseIcon() -> some View {
45
+ HStack {
46
+ Spacer()
47
+ SwiftUI.Button(action: {
48
+ onIconClose?()
49
+ }) {
50
+ ZStack {
51
+ Circle()
52
+ .strokeBorder(Colors.black01, lineWidth: 2)
53
+ .background(Circle().fill(Colors.black17))
54
+ .frame(width: 20, height: 20)
41
55
 
42
- @Binding var isPresented: Bool
43
- var onPressAction: () -> Void
44
- var onClose: () -> Void
45
- var buttonTitle: String
46
- var url: String
47
-
48
- func onPressClose(){
49
- isPresented = false
50
- onClose()
56
+ Icon(source: "navigation_close", size: 14, color: Colors.black01)
57
+ }
58
+ }
59
+ .padding(8)
60
+ Spacer()
61
+ }
51
62
  }
63
+
52
64
  }
65
+
66
+
67
+
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.117.1",
3
+ "version": "0.118.0-beta.1",
4
4
  "private": false,
5
5
  "dependencies": {},
6
6
  "devDependencies": {},