@momo-kits/native-kits 0.113.3-beta.6 → 0.113.3-beta.7
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/AnimationSearchInput.kt +58 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +25 -7
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +1 -13
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +38 -99
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +28 -13
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +13 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +9 -14
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +8 -12
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +3 -2
- package/ios/Application/ApplicationEnvironment.swift +10 -2
- package/ios/Application/Components.swift +1 -3
- package/ios/Application/HeaderRight.swift +4 -4
- package/ios/Application/Screen.swift +1 -1
- package/ios/Template/TrustBanner/TrustBanner.swift +75 -144
- package/local.properties +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
package vn.momo.kits.application
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.background
|
|
4
|
+
import androidx.compose.foundation.layout.Box
|
|
5
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
6
|
+
import androidx.compose.foundation.layout.height
|
|
7
|
+
import androidx.compose.foundation.layout.offset
|
|
8
|
+
import androidx.compose.foundation.layout.padding
|
|
9
|
+
import androidx.compose.foundation.layout.width
|
|
10
|
+
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
11
|
+
import androidx.compose.runtime.Composable
|
|
12
|
+
import androidx.compose.ui.Modifier
|
|
13
|
+
import androidx.compose.ui.graphics.Color
|
|
14
|
+
import androidx.compose.ui.unit.dp
|
|
15
|
+
import androidx.compose.ui.zIndex
|
|
16
|
+
import vn.momo.kits.components.InputSearch
|
|
17
|
+
import vn.momo.kits.components.InputSearchProps
|
|
18
|
+
import vn.momo.kits.const.Radius
|
|
19
|
+
import vn.momo.kits.const.Spacing
|
|
20
|
+
|
|
21
|
+
@Composable
|
|
22
|
+
fun AnimationSearchInput(
|
|
23
|
+
animations: HeaderAnimations,
|
|
24
|
+
inputSearchProps: InputSearchProps
|
|
25
|
+
) {
|
|
26
|
+
Box(
|
|
27
|
+
modifier = Modifier
|
|
28
|
+
.fillMaxWidth()
|
|
29
|
+
.offset(
|
|
30
|
+
x = animations.translateX.dp,
|
|
31
|
+
y = animations.translateY.dp
|
|
32
|
+
)
|
|
33
|
+
) {
|
|
34
|
+
Box(
|
|
35
|
+
modifier = Modifier
|
|
36
|
+
.height(HEADER_HEIGHT.dp)
|
|
37
|
+
.padding(vertical = Spacing.S)
|
|
38
|
+
.zIndex(1f)
|
|
39
|
+
) {
|
|
40
|
+
Box(
|
|
41
|
+
modifier = Modifier
|
|
42
|
+
.width(animations.width.dp)
|
|
43
|
+
.background(
|
|
44
|
+
color = animations.backgroundSearch,
|
|
45
|
+
shape = RoundedCornerShape(Radius.XL)
|
|
46
|
+
)
|
|
47
|
+
) {
|
|
48
|
+
InputSearch(
|
|
49
|
+
inputSearchProps = inputSearchProps.copy(
|
|
50
|
+
showButtonText = false,
|
|
51
|
+
backgroundColor = Color.Transparent
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
}
|
|
@@ -26,6 +26,7 @@ import androidx.compose.ui.Modifier
|
|
|
26
26
|
import androidx.compose.ui.graphics.graphicsLayer
|
|
27
27
|
import androidx.compose.ui.semantics.semantics
|
|
28
28
|
import androidx.compose.ui.semantics.testTag
|
|
29
|
+
import androidx.compose.ui.unit.Dp
|
|
29
30
|
import androidx.compose.ui.unit.dp
|
|
30
31
|
import androidx.compose.ui.zIndex
|
|
31
32
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
@@ -47,10 +48,11 @@ fun Header(
|
|
|
47
48
|
title: String,
|
|
48
49
|
headerRight: @Composable (() -> Unit)? = null,
|
|
49
50
|
goBack: (() -> Unit)? = null,
|
|
50
|
-
opacity: Float
|
|
51
|
+
opacity: Float = 1f,
|
|
51
52
|
animatedHeader: AnimatedHeader? = null,
|
|
52
53
|
scrollState: Int = 0,
|
|
53
54
|
inputSearchProps: InputSearchProps? = null,
|
|
55
|
+
headerRightWidth: Dp = 0.dp,
|
|
54
56
|
useAnimationSearch: Boolean = false
|
|
55
57
|
) {
|
|
56
58
|
val statusBarHeight = AppStatusBar.current ?: getStatusBarHeight()
|
|
@@ -65,6 +67,12 @@ fun Header(
|
|
|
65
67
|
.distinctUntilChanged()
|
|
66
68
|
.collect { fraction -> colorFraction = fraction }
|
|
67
69
|
}
|
|
70
|
+
val animations = useHeaderSearchAnimation(
|
|
71
|
+
opacityAni = opacity,
|
|
72
|
+
scrollState = scrollState,
|
|
73
|
+
headerRightWidth = headerRightWidth,
|
|
74
|
+
isBack = goBack != null
|
|
75
|
+
)
|
|
68
76
|
|
|
69
77
|
val backgroundSearch = animateColor(
|
|
70
78
|
Colors.black_01,
|
|
@@ -72,15 +80,18 @@ fun Header(
|
|
|
72
80
|
colorFraction
|
|
73
81
|
)
|
|
74
82
|
|
|
83
|
+
val searchAnimationEnable =
|
|
84
|
+
inputSearchProps != null && useAnimationSearch && headerType == HeaderType.EXTENDED
|
|
85
|
+
|
|
75
86
|
if (headerType != HeaderType.NONE) {
|
|
76
87
|
BoxWithConstraints(
|
|
77
|
-
Modifier.height(statusBarHeight +
|
|
88
|
+
Modifier.height(statusBarHeight + HEADER_HEIGHT.dp)
|
|
78
89
|
.fillMaxWidth()
|
|
79
90
|
.zIndex(10f),
|
|
80
91
|
contentAlignment = Alignment.BottomCenter
|
|
81
92
|
) {
|
|
82
93
|
Row(
|
|
83
|
-
modifier = Modifier.height(
|
|
94
|
+
modifier = Modifier.height(HEADER_HEIGHT.dp)
|
|
84
95
|
.fillMaxWidth()
|
|
85
96
|
.padding(horizontal = Spacing.M),
|
|
86
97
|
verticalAlignment = Alignment.CenterVertically,
|
|
@@ -120,7 +131,7 @@ fun Header(
|
|
|
120
131
|
|
|
121
132
|
if (useAnimationSearch || inputSearchProps == null) {
|
|
122
133
|
Box(Modifier.graphicsLayer {
|
|
123
|
-
alpha = if (animatedHeader !== null) opacity
|
|
134
|
+
alpha = if (animatedHeader !== null) opacity else 1f
|
|
124
135
|
}) {
|
|
125
136
|
headerRight?.invoke()
|
|
126
137
|
}
|
|
@@ -129,7 +140,7 @@ fun Header(
|
|
|
129
140
|
|
|
130
141
|
// Title
|
|
131
142
|
Row(
|
|
132
|
-
modifier = Modifier.height(
|
|
143
|
+
modifier = Modifier.height(HEADER_HEIGHT.dp)
|
|
133
144
|
.fillMaxWidth()
|
|
134
145
|
.padding(horizontal = Spacing.M),
|
|
135
146
|
verticalAlignment = Alignment.CenterVertically,
|
|
@@ -143,9 +154,9 @@ fun Header(
|
|
|
143
154
|
Box(
|
|
144
155
|
Modifier.weight(1f).graphicsLayer {
|
|
145
156
|
alpha = if (useAnimationSearch) {
|
|
146
|
-
1f - (opacity
|
|
157
|
+
1f - (opacity)
|
|
147
158
|
} else {
|
|
148
|
-
if (animatedHeader !== null) opacity
|
|
159
|
+
if (animatedHeader !== null) opacity else 1f
|
|
149
160
|
}
|
|
150
161
|
},
|
|
151
162
|
contentAlignment = if (titlePosition == TitlePosition.LEFT) Alignment.TopStart else Alignment.Center
|
|
@@ -161,6 +172,13 @@ fun Header(
|
|
|
161
172
|
}
|
|
162
173
|
}
|
|
163
174
|
}
|
|
175
|
+
|
|
176
|
+
if (searchAnimationEnable) {
|
|
177
|
+
AnimationSearchInput(
|
|
178
|
+
animations = animations,
|
|
179
|
+
inputSearchProps = inputSearchProps!!
|
|
180
|
+
)
|
|
181
|
+
}
|
|
164
182
|
}
|
|
165
183
|
}
|
|
166
184
|
}
|
|
@@ -8,9 +8,6 @@ import androidx.compose.runtime.Composable
|
|
|
8
8
|
import androidx.compose.runtime.getValue
|
|
9
9
|
import androidx.compose.ui.Modifier
|
|
10
10
|
import androidx.compose.ui.graphics.Color
|
|
11
|
-
import androidx.compose.ui.unit.Dp
|
|
12
|
-
import androidx.compose.ui.unit.dp
|
|
13
|
-
import vn.momo.kits.components.InputSearchProps
|
|
14
11
|
import vn.momo.kits.const.AppStatusBar
|
|
15
12
|
import vn.momo.kits.const.AppTheme
|
|
16
13
|
import vn.momo.kits.platform.getStatusBarHeight
|
|
@@ -35,17 +32,13 @@ fun animateColor(start: Color, stop: Color, fraction: Float): Color {
|
|
|
35
32
|
|
|
36
33
|
@Composable
|
|
37
34
|
fun HeaderBackground(
|
|
38
|
-
isBack: Boolean = true,
|
|
39
35
|
headerType: HeaderType = HeaderType.DEFAULT,
|
|
40
36
|
scrollState: Int,
|
|
41
|
-
inputSearchProps: InputSearchProps? = null,
|
|
42
|
-
headerRightWidth: Dp = 0.dp,
|
|
43
|
-
useAnimationSearch: Boolean = false
|
|
44
37
|
) {
|
|
45
38
|
val statusBarHeight = AppStatusBar.current ?: getStatusBarHeight()
|
|
46
39
|
|
|
47
40
|
val opacityAni by animateFloatAsState(
|
|
48
|
-
targetValue = (1 - (scrollState /
|
|
41
|
+
targetValue = (1 - (scrollState / HEADER_HEIGHT*1f)).coerceIn(0f, 1f),
|
|
49
42
|
)
|
|
50
43
|
|
|
51
44
|
val backgroundColor by animateColorAsState(
|
|
@@ -66,12 +59,7 @@ fun HeaderBackground(
|
|
|
66
59
|
|
|
67
60
|
HeaderType.EXTENDED -> {
|
|
68
61
|
HeaderExtended(
|
|
69
|
-
scrollState = scrollState,
|
|
70
|
-
inputSearchProps = inputSearchProps,
|
|
71
|
-
headerRightWidth = headerRightWidth,
|
|
72
|
-
useAnimationSearch = useAnimationSearch,
|
|
73
62
|
opacityAni = opacityAni,
|
|
74
|
-
isBack = isBack,
|
|
75
63
|
statusBarHeight = statusBarHeight,
|
|
76
64
|
backgroundColor = backgroundColor,
|
|
77
65
|
)
|
|
@@ -24,7 +24,7 @@ fun HeaderDefault(
|
|
|
24
24
|
opacityAni: Float,
|
|
25
25
|
statusBarHeight: Dp,
|
|
26
26
|
) {
|
|
27
|
-
val height = statusBarHeight +
|
|
27
|
+
val height = statusBarHeight + HEADER_HEIGHT.dp
|
|
28
28
|
Box(
|
|
29
29
|
modifier = Modifier.fillMaxWidth().height(height)
|
|
30
30
|
.background(AppTheme.current.colors.background.surface)
|
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
package vn.momo.kits.application
|
|
2
2
|
|
|
3
|
-
import androidx.compose.animation.animateColorAsState
|
|
4
|
-
import androidx.compose.animation.core.animateFloatAsState
|
|
5
3
|
import androidx.compose.foundation.background
|
|
6
4
|
import androidx.compose.foundation.layout.Box
|
|
7
5
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
8
6
|
import androidx.compose.foundation.layout.height
|
|
9
|
-
import androidx.compose.foundation.layout.offset
|
|
10
|
-
import androidx.compose.foundation.layout.padding
|
|
11
|
-
import androidx.compose.foundation.layout.width
|
|
12
|
-
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
13
7
|
import androidx.compose.runtime.Composable
|
|
14
|
-
import androidx.compose.runtime.getValue
|
|
15
|
-
import androidx.compose.ui.Alignment
|
|
16
8
|
import androidx.compose.ui.Modifier
|
|
17
9
|
import androidx.compose.ui.draw.alpha
|
|
18
10
|
import androidx.compose.ui.geometry.Offset
|
|
@@ -22,115 +14,62 @@ import androidx.compose.ui.layout.ContentScale
|
|
|
22
14
|
import androidx.compose.ui.platform.LocalDensity
|
|
23
15
|
import androidx.compose.ui.unit.Dp
|
|
24
16
|
import androidx.compose.ui.unit.dp
|
|
25
|
-
import androidx.compose.ui.zIndex
|
|
26
17
|
import vn.momo.kits.components.Image
|
|
27
|
-
import vn.momo.kits.components.InputSearch
|
|
28
|
-
import vn.momo.kits.components.InputSearchProps
|
|
29
18
|
import vn.momo.kits.components.Options
|
|
30
19
|
import vn.momo.kits.const.AppTheme
|
|
31
|
-
import vn.momo.kits.const.Colors
|
|
32
|
-
import vn.momo.kits.const.Radius
|
|
33
|
-
import vn.momo.kits.const.Spacing
|
|
34
|
-
import vn.momo.kits.platform.getScreenDimensions
|
|
35
20
|
import vn.momo.kits.utils.bottomBorder
|
|
36
21
|
|
|
37
22
|
@Composable
|
|
38
23
|
fun HeaderExtended(
|
|
39
24
|
opacityAni: Float,
|
|
40
|
-
inputSearchProps: InputSearchProps?,
|
|
41
|
-
useAnimationSearch: Boolean = true,
|
|
42
|
-
scrollState: Int,
|
|
43
|
-
headerRightWidth: Dp,
|
|
44
|
-
isBack: Boolean,
|
|
45
25
|
statusBarHeight: Dp,
|
|
46
26
|
backgroundColor: Color
|
|
47
27
|
) {
|
|
48
|
-
val animations = useHeaderSearchAnimation(
|
|
49
|
-
opacityAni = opacityAni,
|
|
50
|
-
scrollState = scrollState,
|
|
51
|
-
headerRightWidth = headerRightWidth,
|
|
52
|
-
statusBarHeight = statusBarHeight,
|
|
53
|
-
isBack = isBack
|
|
54
|
-
)
|
|
55
|
-
|
|
56
28
|
Box(
|
|
57
|
-
modifier = Modifier
|
|
58
|
-
.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
Color(0x00FDCADE)
|
|
70
|
-
),
|
|
71
|
-
start = Offset(0f, 0f),
|
|
72
|
-
end = Offset(0f, with(LocalDensity.current) { 154.dp.toPx() })
|
|
73
|
-
)
|
|
74
|
-
)
|
|
75
|
-
) {
|
|
76
|
-
if (AppTheme.current.assets.headerBackground != null) {
|
|
77
|
-
Box(
|
|
78
|
-
modifier = Modifier
|
|
79
|
-
.fillMaxWidth()
|
|
80
|
-
.background(backgroundColor)
|
|
81
|
-
) {
|
|
82
|
-
Image(
|
|
83
|
-
loading = false,
|
|
84
|
-
source = AppTheme.current.assets.headerBackground!!,
|
|
85
|
-
modifier = Modifier.fillMaxWidth().height(154.dp),
|
|
86
|
-
options = Options(
|
|
87
|
-
contentScale = ContentScale.FillWidth
|
|
88
|
-
)
|
|
89
|
-
)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
Box(
|
|
95
|
-
modifier = Modifier
|
|
96
|
-
.fillMaxWidth()
|
|
97
|
-
.alpha(1f - opacityAni)
|
|
98
|
-
.height(statusBarHeight + 52.dp)
|
|
99
|
-
.background(
|
|
100
|
-
AppTheme.current.colors.background.surface
|
|
101
|
-
)
|
|
102
|
-
.bottomBorder(
|
|
103
|
-
strokeWidth = 1.dp,
|
|
104
|
-
color = AppTheme.current.colors.border.default
|
|
29
|
+
modifier = Modifier
|
|
30
|
+
.fillMaxWidth()
|
|
31
|
+
.height(154.dp)
|
|
32
|
+
.alpha(opacityAni)
|
|
33
|
+
.background(
|
|
34
|
+
Brush.linearGradient(
|
|
35
|
+
colors = listOf(
|
|
36
|
+
Color(0xFFFDCADE),
|
|
37
|
+
Color(0x00FDCADE)
|
|
38
|
+
),
|
|
39
|
+
start = Offset(0f, 0f),
|
|
40
|
+
end = Offset(0f, with(LocalDensity.current) { 154.dp.toPx() })
|
|
105
41
|
)
|
|
106
|
-
|
|
107
|
-
|
|
42
|
+
)
|
|
43
|
+
) {
|
|
44
|
+
if (AppTheme.current.assets.headerBackground != null) {
|
|
108
45
|
Box(
|
|
109
46
|
modifier = Modifier
|
|
110
|
-
.
|
|
111
|
-
.
|
|
112
|
-
x = animations.translateX.dp
|
|
113
|
-
)
|
|
114
|
-
.padding(vertical = Spacing.S)
|
|
115
|
-
.align(Alignment.BottomStart)
|
|
116
|
-
.zIndex(1f)
|
|
47
|
+
.fillMaxWidth()
|
|
48
|
+
.background(backgroundColor)
|
|
117
49
|
) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
)
|
|
125
|
-
) {
|
|
126
|
-
InputSearch(
|
|
127
|
-
inputSearchProps = inputSearchProps.copy(
|
|
128
|
-
showButtonText = false,
|
|
129
|
-
backgroundColor = Color.Transparent
|
|
130
|
-
)
|
|
50
|
+
Image(
|
|
51
|
+
loading = false,
|
|
52
|
+
source = AppTheme.current.assets.headerBackground!!,
|
|
53
|
+
modifier = Modifier.fillMaxWidth().height(154.dp),
|
|
54
|
+
options = Options(
|
|
55
|
+
contentScale = ContentScale.FillWidth
|
|
131
56
|
)
|
|
132
|
-
|
|
57
|
+
)
|
|
133
58
|
}
|
|
134
59
|
}
|
|
135
60
|
}
|
|
61
|
+
|
|
62
|
+
Box(
|
|
63
|
+
modifier = Modifier
|
|
64
|
+
.fillMaxWidth()
|
|
65
|
+
.alpha(1f - opacityAni)
|
|
66
|
+
.height(statusBarHeight + HEADER_HEIGHT.dp)
|
|
67
|
+
.background(
|
|
68
|
+
AppTheme.current.colors.background.surface
|
|
69
|
+
)
|
|
70
|
+
.bottomBorder(
|
|
71
|
+
strokeWidth = 1.dp,
|
|
72
|
+
color = AppTheme.current.colors.border.default
|
|
73
|
+
)
|
|
74
|
+
)
|
|
136
75
|
}
|
|
@@ -115,11 +115,12 @@ fun ToolkitHeaderRight(headerRight: HeaderRightData? = null) {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
fun onPressClose() {
|
|
118
|
-
api?.request("dismiss", "", {_ -> })
|
|
118
|
+
api?.request("dismiss", "", { _ -> })
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
fun onPressMore() {
|
|
122
|
-
api?.request(
|
|
122
|
+
api?.request(
|
|
123
|
+
"showTools",
|
|
123
124
|
mapOf(
|
|
124
125
|
"tools" to headerRight?.tools?.map { it.toMap() },
|
|
125
126
|
"context" to mapOf(
|
|
@@ -180,10 +181,18 @@ fun ToolkitHeaderRight(headerRight: HeaderRightData? = null) {
|
|
|
180
181
|
group.items.any { it.showBadge }
|
|
181
182
|
}
|
|
182
183
|
|
|
184
|
+
val toolkitWidth = if (context !== null) 65.dp else 28.dp
|
|
185
|
+
|
|
186
|
+
var isShowShortcut = headerRight?.useShortcut == true
|
|
187
|
+
//backup case context null of more icon
|
|
188
|
+
if (headerRight?.useMore == true && context == null) {
|
|
189
|
+
isShowShortcut = false
|
|
190
|
+
}
|
|
191
|
+
|
|
183
192
|
Row(
|
|
184
193
|
verticalAlignment = Alignment.CenterVertically
|
|
185
194
|
) {
|
|
186
|
-
if (
|
|
195
|
+
if (isShowShortcut) {
|
|
187
196
|
NavigationButton(
|
|
188
197
|
disabled = isLoading,
|
|
189
198
|
icon = navButtonConfig.icon,
|
|
@@ -197,20 +206,26 @@ fun ToolkitHeaderRight(headerRight: HeaderRightData? = null) {
|
|
|
197
206
|
modifier = Modifier
|
|
198
207
|
.padding(start = Spacing.S)
|
|
199
208
|
.border(0.2.dp, Color(0x33000000), shape = RoundedCornerShape(14.dp))
|
|
200
|
-
.width(
|
|
209
|
+
.width(toolkitWidth)
|
|
201
210
|
.height(28.dp)
|
|
202
211
|
.clip(shape = RoundedCornerShape(14.dp))
|
|
203
212
|
.background(Color(0x99FFFFFF))
|
|
204
213
|
) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
+
if (context != null) {
|
|
215
|
+
Icon(
|
|
216
|
+
source = "help_center",
|
|
217
|
+
size = 20.dp,
|
|
218
|
+
modifier = Modifier.padding(4.dp).clickable {
|
|
219
|
+
onPressHelpCenter()
|
|
220
|
+
})
|
|
221
|
+
Box(
|
|
222
|
+
modifier = Modifier
|
|
223
|
+
.width(0.5.dp)
|
|
224
|
+
.height(12.dp)
|
|
225
|
+
.background(Colors.black_20)
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
|
|
214
229
|
Icon(
|
|
215
230
|
source = "16_navigation_close_circle",
|
|
216
231
|
size = 20.dp,
|
|
@@ -9,6 +9,8 @@ import androidx.compose.runtime.remember
|
|
|
9
9
|
import androidx.compose.runtime.setValue
|
|
10
10
|
import androidx.compose.runtime.staticCompositionLocalOf
|
|
11
11
|
import androidx.compose.ui.unit.Dp
|
|
12
|
+
import kotlinx.serialization.Serializable
|
|
13
|
+
import kotlinx.serialization.json.internal.FormatLanguage
|
|
12
14
|
import vn.momo.kits.components.TrustBannerData
|
|
13
15
|
import vn.momo.kits.const.AppStatusBar
|
|
14
16
|
import vn.momo.kits.const.AppTheme
|
|
@@ -48,6 +50,10 @@ data class MiniAppContext(
|
|
|
48
50
|
val appId: String = "",
|
|
49
51
|
val appCode: String = "",
|
|
50
52
|
val description: Any? = null,
|
|
53
|
+
val support: Map<String, Any?> = emptyMap(),
|
|
54
|
+
val toolkitConfig: Map<String, Any?> = emptyMap(),
|
|
55
|
+
val providerId: String = "",
|
|
56
|
+
val permissions: List<Map<String, Any>>? = emptyList()
|
|
51
57
|
)
|
|
52
58
|
|
|
53
59
|
val PlatformApi = staticCompositionLocalOf<Any?> { null }
|
|
@@ -64,6 +70,10 @@ val AppConfig = staticCompositionLocalOf<KitConfig?> {
|
|
|
64
70
|
null
|
|
65
71
|
}
|
|
66
72
|
|
|
73
|
+
val AppLanguage = staticCompositionLocalOf<String?> {
|
|
74
|
+
null
|
|
75
|
+
}
|
|
76
|
+
|
|
67
77
|
data class KitConfig(
|
|
68
78
|
val trustBanner: TrustBannerData? = null,
|
|
69
79
|
val headerBar: String? = null,
|
|
@@ -77,6 +87,7 @@ fun ApplicationContainer(
|
|
|
77
87
|
statusBarHeight: Dp? = AppStatusBar.current,
|
|
78
88
|
applicationContext: MiniAppContext? = null,
|
|
79
89
|
config: KitConfig? = null,
|
|
90
|
+
language: String? = null,
|
|
80
91
|
content: @Composable () -> Unit,
|
|
81
92
|
) {
|
|
82
93
|
var appTheme by remember { mutableStateOf(theme) }
|
|
@@ -105,7 +116,8 @@ fun ApplicationContainer(
|
|
|
105
116
|
AppNavigator provides Navigator(),
|
|
106
117
|
AppStatusBar provides appStatusBarHeight,
|
|
107
118
|
ApplicationContext provides applicationContext,
|
|
108
|
-
AppConfig provides config
|
|
119
|
+
AppConfig provides config,
|
|
120
|
+
AppLanguage provides language
|
|
109
121
|
) {
|
|
110
122
|
content()
|
|
111
123
|
}
|
|
@@ -47,6 +47,8 @@ enum class HeaderType {
|
|
|
47
47
|
NONE
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
const val HEADER_HEIGHT = 52
|
|
51
|
+
|
|
50
52
|
@Composable
|
|
51
53
|
fun Screen(
|
|
52
54
|
backgroundColor: Color? = null,
|
|
@@ -85,7 +87,7 @@ fun Screen(
|
|
|
85
87
|
val bottomPadding = min(indicator, 21.dp)
|
|
86
88
|
|
|
87
89
|
val opacity by animateFloatAsState(
|
|
88
|
-
targetValue = ((scrollState.value /
|
|
90
|
+
targetValue = ((scrollState.value / HEADER_HEIGHT*1f)).coerceIn(0f, 1f),
|
|
89
91
|
)
|
|
90
92
|
|
|
91
93
|
val headerAnimated =
|
|
@@ -101,10 +103,7 @@ fun Screen(
|
|
|
101
103
|
if (animatedHeader === null) {
|
|
102
104
|
HeaderBackground(
|
|
103
105
|
headerType = headerType,
|
|
104
|
-
scrollState = scrollState.value
|
|
105
|
-
inputSearchProps= inputSearchProps,
|
|
106
|
-
useAnimationSearch = useAnimationSearch,
|
|
107
|
-
headerRightWidth = headerRightWidth
|
|
106
|
+
scrollState = scrollState.value
|
|
108
107
|
)
|
|
109
108
|
} else {
|
|
110
109
|
Box(
|
|
@@ -113,21 +112,17 @@ fun Screen(
|
|
|
113
112
|
.graphicsLayer { alpha = opacity }
|
|
114
113
|
) {
|
|
115
114
|
HeaderBackground(
|
|
116
|
-
isBack = isBack,
|
|
117
115
|
headerType = headerType,
|
|
118
|
-
scrollState = scrollState.value
|
|
119
|
-
inputSearchProps= inputSearchProps,
|
|
120
|
-
useAnimationSearch = useAnimationSearch,
|
|
121
|
-
headerRightWidth = headerRightWidth
|
|
116
|
+
scrollState = scrollState.value
|
|
122
117
|
)
|
|
123
118
|
}
|
|
124
119
|
}
|
|
125
|
-
|
|
126
120
|
Header(
|
|
127
121
|
headerType = headerType,
|
|
128
122
|
title = title,
|
|
129
123
|
titlePosition = titlePosition,
|
|
130
124
|
headerRight = headerRight,
|
|
125
|
+
headerRightWidth = headerRightWidth,
|
|
131
126
|
goBack = goBack,
|
|
132
127
|
opacity = opacity,
|
|
133
128
|
animatedHeader = animatedHeader,
|
|
@@ -138,7 +133,7 @@ fun Screen(
|
|
|
138
133
|
|
|
139
134
|
Column(
|
|
140
135
|
modifier = Modifier.fillMaxSize()
|
|
141
|
-
.padding(top = if (animatedHeader === null && headerType !== HeaderType.NONE) statusBarHeight +
|
|
136
|
+
.padding(top = if (animatedHeader === null && headerType !== HeaderType.NONE) statusBarHeight + HEADER_HEIGHT.dp else 0.dp)
|
|
142
137
|
.pointerInput(Unit) {
|
|
143
138
|
detectTapGestures(onTap = {
|
|
144
139
|
keyboardController?.hide()
|
|
@@ -162,11 +157,11 @@ fun Screen(
|
|
|
162
157
|
headerAnimated()
|
|
163
158
|
Column {
|
|
164
159
|
if (animatedHeader !== null) {
|
|
165
|
-
Spacer(modifier = Modifier.padding(top = statusBarHeight +
|
|
160
|
+
Spacer(modifier = Modifier.padding(top = statusBarHeight + HEADER_HEIGHT.dp + layoutOffset))
|
|
166
161
|
}
|
|
167
162
|
|
|
168
163
|
if (useAnimationSearch && inputSearchProps != null && headerType == HeaderType.EXTENDED) {
|
|
169
|
-
Spacer(modifier = Modifier.padding(top =
|
|
164
|
+
Spacer(modifier = Modifier.padding(top = (HEADER_HEIGHT.dp + Spacing.S)))
|
|
170
165
|
}
|
|
171
166
|
content()
|
|
172
167
|
}
|
|
@@ -16,7 +16,7 @@ data class HeaderAnimations(
|
|
|
16
16
|
val backgroundSearch: Color,
|
|
17
17
|
val translateX: Float,
|
|
18
18
|
val width: Float,
|
|
19
|
-
val
|
|
19
|
+
val translateY: Float
|
|
20
20
|
)
|
|
21
21
|
|
|
22
22
|
private const val SCREEN_PADDING = 12
|
|
@@ -27,43 +27,39 @@ fun useHeaderSearchAnimation(
|
|
|
27
27
|
opacityAni: Float,
|
|
28
28
|
scrollState: Int,
|
|
29
29
|
headerRightWidth: Dp,
|
|
30
|
-
statusBarHeight: Dp,
|
|
31
30
|
isBack: Boolean
|
|
32
31
|
): HeaderAnimations {
|
|
33
32
|
val screenWidth = getScreenDimensions().width
|
|
34
|
-
val leftPosition = if (isBack) (BACK_WIDTH + 20).dp else
|
|
33
|
+
val leftPosition = if (isBack) (BACK_WIDTH + 20).dp else 12.dp
|
|
35
34
|
val searchWidth = screenWidth - (SCREEN_PADDING * 2)
|
|
36
35
|
|
|
37
36
|
val backgroundSearch by animateColorAsState(
|
|
38
37
|
targetValue = animateColor(
|
|
39
|
-
AppTheme.current.colors.background.default,
|
|
40
38
|
Colors.black_01,
|
|
39
|
+
AppTheme.current.colors.background.default,
|
|
41
40
|
opacityAni
|
|
42
41
|
),
|
|
43
42
|
)
|
|
44
43
|
|
|
45
44
|
val animatedTranslateX by animateFloatAsState(
|
|
46
|
-
targetValue = (scrollState /
|
|
45
|
+
targetValue = (scrollState / HEADER_HEIGHT*1f).coerceIn(0f, 1f) * (leftPosition.value - 12) + 12,
|
|
47
46
|
)
|
|
48
47
|
|
|
49
48
|
val animatedWidth by animateFloatAsState(
|
|
50
|
-
targetValue = (scrollState /
|
|
49
|
+
targetValue = (scrollState / HEADER_HEIGHT*1f).coerceIn(
|
|
51
50
|
0f,
|
|
52
51
|
1f
|
|
53
52
|
) * ((searchWidth - leftPosition.value - headerRightWidth.value + 12) - searchWidth) + searchWidth,
|
|
54
53
|
)
|
|
55
54
|
|
|
56
|
-
val
|
|
57
|
-
targetValue = (1 - (scrollState /
|
|
58
|
-
0f,
|
|
59
|
-
1f
|
|
60
|
-
)) * (statusBarHeight.value + 94 - (statusBarHeight.value + 52)) + (statusBarHeight.value + 52),
|
|
55
|
+
val animatedTranslateY by animateFloatAsState(
|
|
56
|
+
targetValue = (1 - (scrollState / HEADER_HEIGHT* 1f).coerceIn(0f, 1f)) * HEADER_HEIGHT
|
|
61
57
|
)
|
|
62
58
|
return HeaderAnimations(
|
|
63
59
|
opacity = opacityAni,
|
|
64
60
|
backgroundSearch = backgroundSearch,
|
|
65
61
|
translateX = animatedTranslateX,
|
|
66
62
|
width = animatedWidth,
|
|
67
|
-
|
|
63
|
+
translateY = animatedTranslateY
|
|
68
64
|
)
|
|
69
65
|
}
|
|
@@ -20,6 +20,7 @@ import androidx.compose.ui.layout.ContentScale
|
|
|
20
20
|
import androidx.compose.ui.unit.dp
|
|
21
21
|
import androidx.compose.ui.unit.sp
|
|
22
22
|
import vn.momo.kits.application.AppConfig
|
|
23
|
+
import vn.momo.kits.application.AppLanguage
|
|
23
24
|
import vn.momo.kits.modifier.noFeedbackClickable
|
|
24
25
|
|
|
25
26
|
val defaultBanner = TrustBannerData(
|
|
@@ -62,10 +63,10 @@ fun TrustBanner(
|
|
|
62
63
|
serviceName: String = "",
|
|
63
64
|
screenName: String = "",
|
|
64
65
|
onPress: ((Map<String, String>) -> Unit)? = null,
|
|
65
|
-
trackEvent: ((String, Map<String, String>) -> Unit)? = null
|
|
66
|
-
language: String = "vi"
|
|
66
|
+
trackEvent: ((String, Map<String, String>) -> Unit)? = null
|
|
67
67
|
) {
|
|
68
68
|
val appConfig = AppConfig.current
|
|
69
|
+
val language = AppLanguage.current ?: "vi"
|
|
69
70
|
val trustBanner = appConfig?.trustBanner ?: defaultBanner
|
|
70
71
|
val trackParams = mapOf(
|
|
71
72
|
"service_name" to serviceName,
|
|
@@ -23,12 +23,20 @@ public class MiniAppContext {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
public class KitConfig {
|
|
27
|
+
public var trustBanner: TrustBannerData? = nil
|
|
28
|
+
public var headerBar: String? = nil
|
|
29
|
+
public var headerGradient: String? = nil
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
public class ApplicationEnvironment: ObservableObject {
|
|
27
33
|
let applicationContext: MiniAppContext?
|
|
28
|
-
let composeApi: KitComposeApi
|
|
34
|
+
let composeApi: KitComposeApi?
|
|
35
|
+
let config: KitConfig?
|
|
29
36
|
|
|
30
|
-
public init(applicationContext: MiniAppContext
|
|
37
|
+
public init(applicationContext: MiniAppContext? = nil, composeApi: KitComposeApi? = nil, config: KitConfig? = nil) {
|
|
31
38
|
self.applicationContext = applicationContext
|
|
32
39
|
self.composeApi = composeApi
|
|
40
|
+
self.config = config
|
|
33
41
|
}
|
|
34
42
|
}
|
|
@@ -203,9 +203,7 @@ public struct Header: View {
|
|
|
203
203
|
Text(title)
|
|
204
204
|
.font(.headline)
|
|
205
205
|
.foregroundColor(tintIconColor)
|
|
206
|
-
.frame(maxWidth: titlePosition == .center ?
|
|
207
|
-
.frame(maxWidth: titlePosition == .center ? .infinity : UIScreen.main.bounds.width - 172, alignment: titlePosition == .center ? .center : .leading)
|
|
208
|
-
.lineLimit(1)
|
|
206
|
+
.frame(maxWidth: titlePosition == .center ? .infinity : nil, alignment: titlePosition == .center ? .center : .leading)
|
|
209
207
|
}
|
|
210
208
|
Spacer()
|
|
211
209
|
}
|
|
@@ -95,7 +95,7 @@ struct ToolkitHeaderRight: View {
|
|
|
95
95
|
|
|
96
96
|
// MARK: - Actions
|
|
97
97
|
private func onPressShortcut() {
|
|
98
|
-
environment.composeApi
|
|
98
|
+
environment.composeApi?.request(
|
|
99
99
|
funcName: "onToolAction",
|
|
100
100
|
params: ["item": ["key": "onFavorite"]]
|
|
101
101
|
) { _ in
|
|
@@ -112,14 +112,14 @@ struct ToolkitHeaderRight: View {
|
|
|
112
112
|
"icon": context?.appIcon,
|
|
113
113
|
"description": context?.description
|
|
114
114
|
]
|
|
115
|
-
environment.composeApi
|
|
115
|
+
environment.composeApi?.request(
|
|
116
116
|
funcName: "showHelpCenter",
|
|
117
117
|
params: paramMap
|
|
118
118
|
) { _ in }
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
private func onPressClose() {
|
|
122
|
-
environment.composeApi
|
|
122
|
+
environment.composeApi?.request(
|
|
123
123
|
funcName: "dismiss",
|
|
124
124
|
params: ""
|
|
125
125
|
) { _ in }
|
|
@@ -137,7 +137,7 @@ struct ToolkitHeaderRight: View {
|
|
|
137
137
|
"description": context?.description
|
|
138
138
|
]
|
|
139
139
|
]
|
|
140
|
-
environment.composeApi
|
|
140
|
+
environment.composeApi?.request(
|
|
141
141
|
funcName: "showTools",
|
|
142
142
|
params: params
|
|
143
143
|
) { response in
|
|
@@ -34,7 +34,7 @@ public struct Screen<Content: View>: View {
|
|
|
34
34
|
verticalAlignment: VerticalAlignment = .top,
|
|
35
35
|
horizontalAlignment: HorizontalAlignment = .leading,
|
|
36
36
|
title: String = "Stack",
|
|
37
|
-
titlePosition: TitlePosition = .
|
|
37
|
+
titlePosition: TitlePosition = .center,
|
|
38
38
|
goBack: (() -> Void)? = nil,
|
|
39
39
|
scrollable: Bool = true,
|
|
40
40
|
onContentLayout: ((CGRect) -> Void)? = nil,
|
|
@@ -11,179 +11,110 @@ import SDWebImageSwiftUI
|
|
|
11
11
|
|
|
12
12
|
let jsonUrl = "https://static.momocdn.net/app/json/component-kits/design_system.json"
|
|
13
13
|
|
|
14
|
-
struct TrustBannerData: Decodable {
|
|
15
|
-
let
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
struct TrustBanner: Decodable {
|
|
19
|
-
let content: Dictionary<String, String>
|
|
14
|
+
public struct TrustBannerData: Decodable {
|
|
15
|
+
let content: [String: String]
|
|
16
|
+
let subContent: [String: String]
|
|
20
17
|
let pciImage: String
|
|
21
18
|
let sslImage: String
|
|
22
19
|
let urlConfig: String
|
|
23
|
-
let featureCode: String
|
|
24
20
|
let icons: [String]
|
|
25
21
|
let momoImage: String
|
|
26
|
-
|
|
22
|
+
|
|
27
23
|
enum CodingKeys: String, CodingKey {
|
|
28
|
-
case content
|
|
24
|
+
case content
|
|
25
|
+
case subContent
|
|
26
|
+
case pciImage
|
|
27
|
+
case sslImage
|
|
28
|
+
case icons
|
|
29
|
+
case momoImage
|
|
29
30
|
case urlConfig = "url_config"
|
|
30
|
-
case featureCode = "feature_code"
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
let defaultBanner =
|
|
35
|
-
content: ["vi": "
|
|
36
|
-
"en": "
|
|
34
|
+
let defaultBanner = TrustBannerData(
|
|
35
|
+
content: ["vi": "An toàn tài sản & Bảo mật thông tin của bạn là ưu tiên hàng đầu của MoMo.",
|
|
36
|
+
"en": "Ensuring financial security and data privacy is MoMo's highest priority."],
|
|
37
|
+
subContent: [
|
|
38
|
+
"vi": "Tìm hiểu thêm",
|
|
39
|
+
"en": "Learn more"
|
|
40
|
+
],
|
|
37
41
|
pciImage: "https://static.momocdn.net/app/img/kits/trustBanner/pci.png",
|
|
38
42
|
sslImage: "https://static.momocdn.net/app/img/kits/trustBanner/ssl.png",
|
|
39
|
-
urlConfig: "
|
|
40
|
-
featureCode: "login_and_security",
|
|
43
|
+
urlConfig: "login_and_security",
|
|
41
44
|
icons: [
|
|
42
45
|
"https://static.momocdn.net/app/img/kits/trustBanner/ic_viettinbank.png",
|
|
43
46
|
"https://static.momocdn.net/app/img/kits/trustBanner/ic_agribank.png",
|
|
44
47
|
"https://static.momocdn.net/app/img/kits/trustBanner/ic_vietcombank.png",
|
|
45
48
|
"https://static.momocdn.net/app/img/kits/trustBanner/ic_bidv.png"
|
|
46
49
|
],
|
|
47
|
-
momoImage: "https://static.momocdn.net/app/img/kits/trustBanner/
|
|
50
|
+
momoImage: "https://static.momocdn.net/app/img/kits/trustBanner/ic_secu.png"
|
|
48
51
|
)
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
init() {
|
|
56
|
-
fetchData(from: jsonUrl)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
func fetchData(from urlString: String) {
|
|
60
|
-
guard let url = URL(string: urlString) else { return }
|
|
61
|
-
|
|
62
|
-
URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
|
|
63
|
-
guard let self = self else { return }
|
|
64
|
-
|
|
65
|
-
if let error = error {
|
|
66
|
-
print("Data fetching error: \(error)")
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
guard let data = data else {
|
|
71
|
-
print("No data received")
|
|
72
|
-
return
|
|
73
|
-
}
|
|
74
|
-
do {
|
|
75
|
-
let decodedData = try JSONDecoder().decode(TrustBannerData.self, from: data)
|
|
76
|
-
DispatchQueue.main.async {
|
|
77
|
-
self.trustBanner = decodedData
|
|
78
|
-
}
|
|
79
|
-
} catch {
|
|
80
|
-
print("Decoding error: \(error)")
|
|
81
|
-
}
|
|
82
|
-
}.resume()
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
public struct TrustBannerView: View {
|
|
87
|
-
var serviceName: String? = ""
|
|
88
|
-
var screenName: String? = ""
|
|
89
|
-
var onPress: ((_ params: NSDictionary) -> Void)?
|
|
90
|
-
var trackEvent: ((_ eventName: String, _ params: NSDictionary) -> Void)?
|
|
91
|
-
var language: String?
|
|
92
|
-
|
|
93
|
-
@ObservedObject private var viewModel = TrustBannerViewModel()
|
|
94
|
-
|
|
95
|
-
public init(serviceName: String?,
|
|
96
|
-
screenName: String?,
|
|
97
|
-
onPress: ((_ params: NSDictionary) -> Void)?,
|
|
98
|
-
trackEvent: ((_ eventName: String,_ params: NSDictionary) -> Void)?, language: String? = "vi") {
|
|
99
|
-
if let trackEvent = trackEvent {
|
|
100
|
-
trackEvent("service_component_displayed", ["service_name": serviceName ?? "", "screen_name": screenName ?? "", "component_name": "logo_trust"])
|
|
101
|
-
}
|
|
53
|
+
public struct TrustBanner: View {
|
|
54
|
+
@EnvironmentObject var applicationEnvironment: ApplicationEnvironment
|
|
55
|
+
|
|
56
|
+
var onPress: ((String)->Void)?
|
|
57
|
+
|
|
58
|
+
public init(onPress: ((String)->Void)?) {
|
|
102
59
|
self.onPress = onPress
|
|
103
|
-
self.trackEvent = trackEvent
|
|
104
|
-
self.screenName = screenName
|
|
105
|
-
self.serviceName = serviceName
|
|
106
|
-
self.language = language
|
|
107
60
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
61
|
+
|
|
62
|
+
var language: String? = "vi"
|
|
63
|
+
|
|
64
|
+
func onPressSecurity() {
|
|
65
|
+
if (onPress != nil) {
|
|
66
|
+
onPress!(applicationEnvironment.config?.trustBanner?.urlConfig ?? defaultBanner.urlConfig)
|
|
112
67
|
}
|
|
113
68
|
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
struct TrustBannerContentView: View {
|
|
117
|
-
var trustBanner: TrustBanner
|
|
118
|
-
var onPress: ((_ params: NSDictionary) -> Void)?
|
|
119
|
-
var trackEvent: ((_ eventName: String, _ params: NSDictionary) -> Void)?
|
|
120
|
-
var serviceName: String?
|
|
121
|
-
var screenName: String?
|
|
122
|
-
var language: String?
|
|
123
69
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
70
|
+
|
|
71
|
+
public var body: some View {
|
|
72
|
+
HStack(alignment: .center, spacing: 8) {
|
|
73
|
+
WebImage(url: URL(string: applicationEnvironment.config?.trustBanner?.momoImage ?? defaultBanner.momoImage))
|
|
74
|
+
.resizable()
|
|
75
|
+
.scaledToFit()
|
|
76
|
+
.frame(width: 64, height: 64)
|
|
77
|
+
VStack(alignment: .leading){
|
|
78
|
+
Text(applicationEnvironment.config?.trustBanner?.content[language ?? "vi"] as? String ?? defaultBanner.content[language ?? "vi"])
|
|
79
|
+
.foregroundColor(Color(hex: "484848"))
|
|
80
|
+
.lineLimit(2)
|
|
81
|
+
.font(.system(size: 13, weight: Font.Weight.regular))
|
|
82
|
+
.lineSpacing(3)
|
|
83
|
+
.padding(.bottom, 8)
|
|
84
|
+
HStack {
|
|
85
|
+
HStack(spacing: 0) {
|
|
86
|
+
Text(applicationEnvironment.config?.trustBanner?.subContent[language ?? "vi"] as? String ?? defaultBanner.subContent[language ?? "vi"])
|
|
87
|
+
.foregroundColor(Colors.pink03)
|
|
88
|
+
.font(.system(size: 13, weight: .bold))
|
|
89
|
+
.padding(.trailing, 4)
|
|
90
|
+
|
|
91
|
+
Icon(source: "arrow_chevron_right_small", color: Colors.pink03)
|
|
92
|
+
}.onTapGesture {
|
|
93
|
+
onPressSecurity()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
Spacer()
|
|
137
97
|
HStack{
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
.frame(width: 52, height: 20)
|
|
147
|
-
}
|
|
148
|
-
Spacer()
|
|
149
|
-
HStack(spacing: -6){
|
|
150
|
-
ForEach(trustBanner.icons, id: \.self) { iconURL in
|
|
151
|
-
WebImage(url: URL(string: iconURL))
|
|
152
|
-
.resizable()
|
|
153
|
-
.scaledToFit()
|
|
154
|
-
.frame(width:24, height: 24)
|
|
155
|
-
.background(Color.white)
|
|
156
|
-
.cornerRadius(12)
|
|
157
|
-
.overlay(
|
|
158
|
-
Circle().stroke(Color(hex: 0xFFE8E8E8), lineWidth: 1)
|
|
159
|
-
)
|
|
160
|
-
}
|
|
161
|
-
Text("36+")
|
|
162
|
-
.font(.system(size: 10, weight: Font.Weight.regular))
|
|
163
|
-
.frame(width: 24, height: 24, alignment: .center)
|
|
164
|
-
.foregroundColor(Color(hex: 0xFFEB2F96))
|
|
165
|
-
.background(Color(hex: 0xFFFDEAF4))
|
|
166
|
-
.cornerRadius(12)
|
|
167
|
-
.overlay(
|
|
168
|
-
Circle()
|
|
169
|
-
.stroke(Color(hex: 0xFFE8E8E8), lineWidth: 1)
|
|
170
|
-
)
|
|
171
|
-
}.frame(width: 96, height: 24)
|
|
98
|
+
WebImage(url: URL(string: applicationEnvironment.config?.trustBanner?.pciImage ?? defaultBanner.pciImage))
|
|
99
|
+
.resizable()
|
|
100
|
+
.scaledToFit()
|
|
101
|
+
.frame(width: 24, height: 20)
|
|
102
|
+
WebImage(url: URL(string: applicationEnvironment.config?.trustBanner?.sslImage ?? defaultBanner.sslImage))
|
|
103
|
+
.resizable()
|
|
104
|
+
.scaledToFit()
|
|
105
|
+
.frame(width: 52, height: 20)
|
|
172
106
|
}
|
|
173
107
|
}
|
|
108
|
+
|
|
174
109
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
if let onPress = onPress {
|
|
184
|
-
onPress(["service_name": self.serviceName ?? "", "screen_name": self.screenName ?? "", "component_name": "logo_trust", "url_config": trustBanner.urlConfig, "feature_code": trustBanner.featureCode])
|
|
185
|
-
}
|
|
186
|
-
}
|
|
110
|
+
|
|
111
|
+
}
|
|
112
|
+
.padding(.all, 12)
|
|
113
|
+
.background(Color(hex: "F2F8FF"))
|
|
114
|
+
.cornerRadius(12)
|
|
115
|
+
.onTapGesture {
|
|
116
|
+
onPressSecurity()
|
|
187
117
|
}
|
|
118
|
+
.frame(maxWidth: UIScreen.main.bounds.size.width)
|
|
188
119
|
}
|
|
189
120
|
}
|
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
|
+
#Tue Apr 23 16:40:15 ICT 2024
|
|
8
|
+
sdk.dir=/Users/hung.dao1/Library/Android/sdk
|