@momo-kits/native-kits 0.113.1-beta.2 → 0.113.1-beta.4
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/Components.kt +120 -50
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +261 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +57 -60
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +15 -4
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +3 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +3 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +47 -37
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +6 -1
- package/ios/Application/ApplicationEnvironment.swift +34 -0
- package/ios/Application/Components.swift +6 -7
- package/ios/Application/ComposeApi.swift +22 -0
- package/ios/Application/HeaderRight.swift +249 -0
- package/ios/Application/Screen.swift +18 -19
- package/ios/Badge/BadgeDot.swift +31 -0
- package/ios/Button/Button.swift +2 -1
- package/ios/Icon/Icon.swift +1 -3
- package/ios/Image/Image.swift +17 -1
- package/ios/Popup/PopupDisplay.swift +8 -9
- package/local.properties +2 -2
- package/package.json +1 -1
|
@@ -62,11 +62,12 @@ fun Screen(
|
|
|
62
62
|
onContentLayout: ((LayoutCoordinates) -> Unit)? = null,
|
|
63
63
|
useAvoidKeyboard: Boolean = true,
|
|
64
64
|
footer: @Composable (() -> Unit)? = null,
|
|
65
|
-
headerRight: @Composable (() -> Unit)? =
|
|
65
|
+
headerRight: @Composable (() -> Unit)? = { HeaderRight() },
|
|
66
66
|
fabProps: FabProps? = null,
|
|
67
67
|
animatedHeader: AnimatedHeader? = null,
|
|
68
68
|
layoutOffset: Dp = 56.dp,
|
|
69
69
|
inputSearchProps: InputSearchProps? = null,
|
|
70
|
+
useAnimationSearch: Boolean = false,
|
|
70
71
|
content: @Composable () -> Unit,
|
|
71
72
|
) {
|
|
72
73
|
val statusBarHeight = AppStatusBar.current ?: getStatusBarHeight()
|
|
@@ -83,7 +84,7 @@ fun Screen(
|
|
|
83
84
|
val bottomPadding = min(indicator, 21.dp)
|
|
84
85
|
|
|
85
86
|
val opacity by animateFloatAsState(
|
|
86
|
-
targetValue = ((scrollState.value /
|
|
87
|
+
targetValue = ((scrollState.value / 52f)).coerceIn(0f, 1f),
|
|
87
88
|
)
|
|
88
89
|
|
|
89
90
|
val headerAnimated =
|
|
@@ -93,13 +94,15 @@ fun Screen(
|
|
|
93
94
|
|
|
94
95
|
Box(
|
|
95
96
|
Modifier.fillMaxSize()
|
|
96
|
-
.background(backgroundColor ?: AppTheme.current.colors.background.
|
|
97
|
+
.background(backgroundColor ?: AppTheme.current.colors.background.surface)
|
|
97
98
|
) {
|
|
98
99
|
|
|
99
100
|
if (animatedHeader === null) {
|
|
100
101
|
HeaderBackground(
|
|
101
102
|
headerType = headerType,
|
|
102
103
|
scrollState = scrollState.value,
|
|
104
|
+
inputSearchProps= inputSearchProps,
|
|
105
|
+
useAnimationSearch = useAnimationSearch
|
|
103
106
|
)
|
|
104
107
|
} else {
|
|
105
108
|
Box(
|
|
@@ -108,8 +111,11 @@ fun Screen(
|
|
|
108
111
|
.graphicsLayer { alpha = opacity }
|
|
109
112
|
) {
|
|
110
113
|
HeaderBackground(
|
|
114
|
+
isBack = isBack,
|
|
111
115
|
headerType = headerType,
|
|
112
116
|
scrollState = scrollState.value,
|
|
117
|
+
inputSearchProps= inputSearchProps,
|
|
118
|
+
useAnimationSearch = useAnimationSearch
|
|
113
119
|
)
|
|
114
120
|
}
|
|
115
121
|
}
|
|
@@ -123,7 +129,8 @@ fun Screen(
|
|
|
123
129
|
opacity = opacity,
|
|
124
130
|
animatedHeader = animatedHeader,
|
|
125
131
|
inputSearchProps = inputSearchProps,
|
|
126
|
-
scrollState = scrollState.value
|
|
132
|
+
scrollState = scrollState.value,
|
|
133
|
+
useAnimationSearch = useAnimationSearch
|
|
127
134
|
)
|
|
128
135
|
|
|
129
136
|
Column(
|
|
@@ -154,6 +161,10 @@ fun Screen(
|
|
|
154
161
|
if (animatedHeader !== null) {
|
|
155
162
|
Spacer(modifier = Modifier.padding(top = statusBarHeight + 52.dp + layoutOffset))
|
|
156
163
|
}
|
|
164
|
+
|
|
165
|
+
if (useAnimationSearch && inputSearchProps != null) {
|
|
166
|
+
Spacer(modifier = Modifier.padding(top = statusBarHeight))
|
|
167
|
+
}
|
|
157
168
|
content()
|
|
158
169
|
}
|
|
159
170
|
}
|
|
@@ -17,9 +17,9 @@ enum class DotSize(val size: Int) {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
@Composable
|
|
20
|
-
fun BadgeDot(size: DotSize = DotSize.Large) {
|
|
20
|
+
fun BadgeDot(size: DotSize = DotSize.Large, modifier: Modifier = Modifier) {
|
|
21
21
|
Box(
|
|
22
|
-
modifier =
|
|
22
|
+
modifier = modifier
|
|
23
23
|
.size(size.size.dp)
|
|
24
24
|
.border(width = 1.dp, color = Colors.black_01, shape = RoundedCornerShape(Radius.S))
|
|
25
25
|
.background(color = Colors.red_03, shape = RoundedCornerShape(Radius.S))
|
|
@@ -75,6 +75,7 @@ fun OTPCaret() {
|
|
|
75
75
|
|
|
76
76
|
@Composable
|
|
77
77
|
fun InputOTP(
|
|
78
|
+
value: String = "",
|
|
78
79
|
length: Int? = null,
|
|
79
80
|
errorMessage: String = "",
|
|
80
81
|
errorSpacing: Boolean = false,
|
|
@@ -85,9 +86,9 @@ fun InputOTP(
|
|
|
85
86
|
onBlur: () -> Unit = {},
|
|
86
87
|
dataType: String = "number",
|
|
87
88
|
accessibilityLabel: String? = null,
|
|
89
|
+
modifier: Modifier = Modifier
|
|
88
90
|
) {
|
|
89
91
|
val MAX_LENGTH = 10
|
|
90
|
-
var value by remember { mutableStateOf("") }
|
|
91
92
|
var isFocused by remember { mutableStateOf(false) }
|
|
92
93
|
var isBlurred = false
|
|
93
94
|
|
|
@@ -96,7 +97,6 @@ fun InputOTP(
|
|
|
96
97
|
(dataType != "number" || !text.any { !it.isDigit() }) &&
|
|
97
98
|
(text.length < value.length || value.length < text.length)
|
|
98
99
|
) {
|
|
99
|
-
value = text
|
|
100
100
|
onChangeText(text)
|
|
101
101
|
}
|
|
102
102
|
}
|
|
@@ -108,7 +108,7 @@ fun InputOTP(
|
|
|
108
108
|
onValueChange = handleChangeText,
|
|
109
109
|
singleLine = true,
|
|
110
110
|
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = if (dataType == "number") KeyboardType.Number else KeyboardType.Ascii),
|
|
111
|
-
modifier =
|
|
111
|
+
modifier = modifier.height(56.dp).onFocusChanged {
|
|
112
112
|
isFocused = it.isFocused
|
|
113
113
|
if (it.isFocused) {
|
|
114
114
|
onFocus()
|
|
@@ -31,6 +31,7 @@ import androidx.compose.ui.graphics.Color
|
|
|
31
31
|
import androidx.compose.ui.text.TextStyle
|
|
32
32
|
import androidx.compose.ui.text.input.KeyboardType
|
|
33
33
|
import androidx.compose.ui.text.style.TextOverflow
|
|
34
|
+
import androidx.compose.ui.unit.Dp
|
|
34
35
|
import androidx.compose.ui.unit.dp
|
|
35
36
|
import androidx.compose.ui.unit.sp
|
|
36
37
|
import vn.momo.kits.const.AppTheme
|
|
@@ -98,7 +99,8 @@ data class InputSearchProps(
|
|
|
98
99
|
val keyboardActions: KeyboardActions = KeyboardActions.Default,
|
|
99
100
|
val modifier: Modifier = Modifier,
|
|
100
101
|
val iconModifier: Modifier = Modifier,
|
|
101
|
-
val onClearPress: () -> Unit = {}
|
|
102
|
+
val onClearPress: () -> Unit = {},
|
|
103
|
+
val leftPosition: Dp? = null,
|
|
102
104
|
)
|
|
103
105
|
|
|
104
106
|
@Composable
|
|
@@ -115,14 +115,14 @@ fun PopupNotify(
|
|
|
115
115
|
)
|
|
116
116
|
|
|
117
117
|
Column(modifier = Modifier.padding(Spacing.XL)) {
|
|
118
|
-
Text(style = Typography.titleXs, maxLines =
|
|
118
|
+
Text(style = Typography.titleXs, maxLines = 1, text = title)
|
|
119
119
|
content(Modifier.padding(top = Spacing.S))
|
|
120
120
|
if (error.isNotEmpty()) {
|
|
121
121
|
Text(
|
|
122
122
|
text = error,
|
|
123
123
|
style = Typography.descriptionXs,
|
|
124
124
|
color = AppTheme.current.colors.text.hint,
|
|
125
|
-
maxLines =
|
|
125
|
+
maxLines = 1,
|
|
126
126
|
modifier = Modifier.padding(top = Spacing.S)
|
|
127
127
|
)
|
|
128
128
|
}
|
|
@@ -19,12 +19,13 @@ import androidx.compose.ui.graphics.Color
|
|
|
19
19
|
import androidx.compose.ui.layout.ContentScale
|
|
20
20
|
import androidx.compose.ui.unit.dp
|
|
21
21
|
import androidx.compose.ui.unit.sp
|
|
22
|
+
import vn.momo.kits.application.AppConfig
|
|
22
23
|
import vn.momo.kits.modifier.noFeedbackClickable
|
|
23
24
|
|
|
24
25
|
val defaultBanner = TrustBannerData(
|
|
25
26
|
content = mapOf(
|
|
26
|
-
"vi" to "
|
|
27
|
-
"en" to "
|
|
27
|
+
"vi" to "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.",
|
|
28
|
+
"en" to "Ensuring financial security and data privacy is MoMo's highest priority."
|
|
28
29
|
),
|
|
29
30
|
subContent = mapOf(
|
|
30
31
|
"vi" to "Tìm hiểu thêm",
|
|
@@ -47,25 +48,25 @@ const val contentColor = 0xFF484848
|
|
|
47
48
|
const val subContentColor = 0xFFEB2F96
|
|
48
49
|
|
|
49
50
|
data class TrustBannerData(
|
|
50
|
-
val content: Map<String, String
|
|
51
|
-
val subContent: Map<String, String
|
|
52
|
-
val pciImage: String,
|
|
53
|
-
val momoImage: String,
|
|
54
|
-
val sslImage: String,
|
|
55
|
-
val icons: List<String
|
|
56
|
-
val urlConfig: String
|
|
51
|
+
val content: Map<String, String> = mapOf("vi" to "", "en" to ""),
|
|
52
|
+
val subContent: Map<String, String> = mapOf("vi" to "", "en" to ""),
|
|
53
|
+
val pciImage: String? = null,
|
|
54
|
+
val momoImage: String? = null,
|
|
55
|
+
val sslImage: String? = null,
|
|
56
|
+
val icons: List<String>? = null,
|
|
57
|
+
val urlConfig: String = ""
|
|
57
58
|
)
|
|
58
59
|
|
|
59
60
|
@Composable
|
|
60
61
|
fun TrustBanner(
|
|
61
|
-
trustBanner: TrustBannerData = defaultBanner,
|
|
62
62
|
serviceName: String = "",
|
|
63
63
|
screenName: String = "",
|
|
64
64
|
onPress: ((Map<String, String>) -> Unit)? = null,
|
|
65
65
|
trackEvent: ((String, Map<String, String>) -> Unit)? = null,
|
|
66
66
|
language: String = "vi"
|
|
67
|
-
|
|
68
67
|
) {
|
|
68
|
+
val appConfig = AppConfig.current
|
|
69
|
+
val trustBanner = appConfig?.trustBanner ?: defaultBanner
|
|
69
70
|
val trackParams = mapOf(
|
|
70
71
|
"service_name" to serviceName,
|
|
71
72
|
"screen_name" to screenName,
|
|
@@ -103,15 +104,17 @@ fun TrustBanner(
|
|
|
103
104
|
}
|
|
104
105
|
)
|
|
105
106
|
) {
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
if (trustBanner.momoImage != null) {
|
|
108
|
+
Image(
|
|
109
|
+
source = trustBanner.momoImage,
|
|
110
|
+
modifier = Modifier
|
|
111
|
+
.width(64.dp)
|
|
112
|
+
.height(64.dp)
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
Column(
|
|
108
116
|
modifier = Modifier
|
|
109
|
-
.
|
|
110
|
-
.height(64.dp)
|
|
111
|
-
)
|
|
112
|
-
Column (
|
|
113
|
-
modifier = Modifier
|
|
114
|
-
.padding(start = 10.dp)
|
|
117
|
+
.padding(start = 10.dp)
|
|
115
118
|
) {
|
|
116
119
|
Text(
|
|
117
120
|
text = trustBanner.content[language] ?: "",
|
|
@@ -121,10 +124,13 @@ fun TrustBanner(
|
|
|
121
124
|
color = Color(contentColor),
|
|
122
125
|
maxLines = 2
|
|
123
126
|
)
|
|
124
|
-
Row(
|
|
125
|
-
|
|
127
|
+
Row(
|
|
128
|
+
modifier = Modifier.fillMaxWidth(),
|
|
129
|
+
horizontalArrangement = Arrangement.SpaceBetween
|
|
130
|
+
) {
|
|
131
|
+
Row(
|
|
126
132
|
verticalAlignment = Alignment.CenterVertically,
|
|
127
|
-
)
|
|
133
|
+
) {
|
|
128
134
|
Text(
|
|
129
135
|
text = trustBanner.subContent[language] ?: "",
|
|
130
136
|
lineHeight = 16.sp,
|
|
@@ -138,23 +144,27 @@ fun TrustBanner(
|
|
|
138
144
|
)
|
|
139
145
|
}
|
|
140
146
|
Row {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
147
|
+
if (trustBanner.pciImage != null) {
|
|
148
|
+
Image(
|
|
149
|
+
source = trustBanner.pciImage, modifier = Modifier
|
|
150
|
+
.padding(end = 4.dp)
|
|
151
|
+
.width(27.dp)
|
|
152
|
+
.height(20.dp), options = Options(
|
|
153
|
+
contentScale = ContentScale.Fit,
|
|
154
|
+
)
|
|
148
155
|
)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
+
}
|
|
157
|
+
if (trustBanner.sslImage != null) {
|
|
158
|
+
Image(
|
|
159
|
+
source = trustBanner.sslImage, modifier = Modifier
|
|
160
|
+
.width(52.dp)
|
|
161
|
+
.height(20.dp),
|
|
162
|
+
options = Options(
|
|
163
|
+
contentScale = ContentScale.Fit,
|
|
164
|
+
)
|
|
156
165
|
)
|
|
157
|
-
|
|
166
|
+
}
|
|
167
|
+
|
|
158
168
|
}
|
|
159
169
|
}
|
|
160
170
|
}
|
|
@@ -1318,5 +1318,10 @@ val Icons = mapOf(
|
|
|
1318
1318
|
"ic_checked" to "https://img.mservice.com.vn/app/img/kits/checked_ic.png",
|
|
1319
1319
|
"ic_minus" to "https://img.mservice.com.vn/app/img/kits/minus.png",
|
|
1320
1320
|
"ic_navigation" to "https://static.momocdn.net/app/img/kits/ic_navigation.png",
|
|
1321
|
-
"ic_money_bag" to "https://static.momocdn.net/app/img/kits/ic_money_bag.png"
|
|
1321
|
+
"ic_money_bag" to "https://static.momocdn.net/app/img/kits/ic_money_bag.png",
|
|
1322
|
+
"ic_support" to "https://static.momocdn.net/app/img/kits/ic_support.png",
|
|
1323
|
+
"navigation_more_icon" to "https://static.momocdn.net/app/img/kits/navigation_more_icon.png",
|
|
1324
|
+
"help_center" to "https://static.momocdn.net/app/img/kits/help-center.png",
|
|
1325
|
+
"pin_star" to "https://static.momocdn.net/app/img/kits/pin_star.png",
|
|
1326
|
+
"pin_star_checked" to "https://static.momocdn.net/app/img/kits/pin_star_checked.png"
|
|
1322
1327
|
)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
public protocol KitComposeApi {
|
|
4
|
+
func request(funcName: String, params: Any?) -> String
|
|
5
|
+
func request(funcName: String, params: Any?, onResponse: ((String) -> Void)?) -> String
|
|
6
|
+
func requestCallback(funcName: String, params: Any?, onResponse: ((String) -> Void)?)
|
|
7
|
+
func removeCallback(id: String)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public class MiniAppContext {
|
|
11
|
+
public var appId: String = ""
|
|
12
|
+
public var appCode: String = ""
|
|
13
|
+
public var appName: Any? = nil
|
|
14
|
+
public var appIcon: String = ""
|
|
15
|
+
public var description: Any? = nil
|
|
16
|
+
|
|
17
|
+
public init(appId: String, appCode: String, appName: Any? = nil, appIcon: String, description: Any? = nil) {
|
|
18
|
+
self.appId = appId
|
|
19
|
+
self.appCode = appCode
|
|
20
|
+
self.appName = appName
|
|
21
|
+
self.appIcon = appIcon
|
|
22
|
+
self.description = description
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public class ApplicationEnvironment: ObservableObject {
|
|
27
|
+
let applicationContext: MiniAppContext?
|
|
28
|
+
let composeApi: KitComposeApi
|
|
29
|
+
|
|
30
|
+
public init(applicationContext: MiniAppContext?, composeApi: KitComposeApi) {
|
|
31
|
+
self.applicationContext = applicationContext
|
|
32
|
+
self.composeApi = composeApi
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -105,7 +105,7 @@ public struct Header: View {
|
|
|
105
105
|
var headerType: HeaderType = .default
|
|
106
106
|
var titlePosition: TitlePosition
|
|
107
107
|
var title: String
|
|
108
|
-
var headerRight:
|
|
108
|
+
var headerRight: (()->any View)? = {HeaderRight()}
|
|
109
109
|
var goBack: (() -> Void)?
|
|
110
110
|
var opacity: CGFloat? = nil
|
|
111
111
|
var animatedHeader: AnimatedHeader? = nil
|
|
@@ -118,7 +118,7 @@ public struct Header: View {
|
|
|
118
118
|
headerType: HeaderType = .default,
|
|
119
119
|
titlePosition: TitlePosition,
|
|
120
120
|
title: String,
|
|
121
|
-
headerRight:
|
|
121
|
+
headerRight: (()->any View)? = {HeaderRight()},
|
|
122
122
|
goBack: (() -> Void)? = nil,
|
|
123
123
|
opacity: CGFloat? = nil,
|
|
124
124
|
animatedHeader: AnimatedHeader? = nil,
|
|
@@ -160,11 +160,12 @@ public struct Header: View {
|
|
|
160
160
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
Spacer()
|
|
163
|
+
Spacer()
|
|
164
164
|
|
|
165
|
-
if
|
|
166
|
-
headerRight()
|
|
165
|
+
if (headerRight != nil) {
|
|
166
|
+
AnyView(headerRight!())
|
|
167
167
|
}
|
|
168
|
+
|
|
168
169
|
}
|
|
169
170
|
.frame(height: 52)
|
|
170
171
|
.padding(.horizontal)
|
|
@@ -204,12 +205,10 @@ public struct Header: View {
|
|
|
204
205
|
.foregroundColor(tintIconColor)
|
|
205
206
|
.frame(maxWidth: titlePosition == .center ? .infinity : nil, alignment: titlePosition == .center ? .center : .leading)
|
|
206
207
|
}
|
|
207
|
-
|
|
208
208
|
Spacer()
|
|
209
209
|
}
|
|
210
210
|
.frame(height: 52)
|
|
211
211
|
.padding(.horizontal)
|
|
212
|
-
|
|
213
212
|
}
|
|
214
213
|
.background(Color.clear)
|
|
215
214
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
public protocol ComposeApi {
|
|
4
|
+
func request(funcName: String, params: Any, _ completion: ((String) -> Void)?)
|
|
5
|
+
func request(funcName: String, params: Any) -> String
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Default implementation
|
|
9
|
+
public class ComposeApiImpl: ComposeApi {
|
|
10
|
+
public static let shared = ComposeApiImpl()
|
|
11
|
+
|
|
12
|
+
private init() {}
|
|
13
|
+
|
|
14
|
+
public func request(funcName: String, params: Any, _ completion: ((String) -> Void)?) {
|
|
15
|
+
// Implement your API request logic here
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public func request(funcName: String, params: Any) -> String {
|
|
19
|
+
// Implement your API request logic here
|
|
20
|
+
return ""
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
public struct HeaderRightData {
|
|
5
|
+
var useShortcut: Bool
|
|
6
|
+
var useMore: Bool
|
|
7
|
+
var tools: [ToolGroup]
|
|
8
|
+
var toolCallback: ((String) -> Void)?
|
|
9
|
+
|
|
10
|
+
public init(
|
|
11
|
+
useShortcut: Bool = false,
|
|
12
|
+
useMore: Bool = false,
|
|
13
|
+
tools: [ToolGroup] = [],
|
|
14
|
+
toolCallback: ((String) -> Void)? = nil
|
|
15
|
+
) {
|
|
16
|
+
self.useShortcut = useShortcut
|
|
17
|
+
self.useMore = useMore
|
|
18
|
+
self.tools = tools
|
|
19
|
+
self.toolCallback = toolCallback
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public struct Tool {
|
|
24
|
+
var key: String
|
|
25
|
+
var icon: String
|
|
26
|
+
var showBadge: Bool
|
|
27
|
+
var name: [String: String]
|
|
28
|
+
|
|
29
|
+
public init(
|
|
30
|
+
key: String,
|
|
31
|
+
icon: String,
|
|
32
|
+
name: [String: String] = [:],
|
|
33
|
+
showBadge: Bool = false
|
|
34
|
+
) {
|
|
35
|
+
self.key = key
|
|
36
|
+
self.icon = icon
|
|
37
|
+
self.showBadge = showBadge
|
|
38
|
+
self.name = name
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func toMap() -> [String: Any] {
|
|
42
|
+
return [
|
|
43
|
+
"key": key,
|
|
44
|
+
"icon": icon,
|
|
45
|
+
"showBadge": showBadge,
|
|
46
|
+
"name": name
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public struct ToolGroup {
|
|
52
|
+
var title: [String: String]
|
|
53
|
+
var items: [Tool]
|
|
54
|
+
|
|
55
|
+
public init(
|
|
56
|
+
title: [String: String] = [:],
|
|
57
|
+
items: [Tool]
|
|
58
|
+
) {
|
|
59
|
+
self.title = title
|
|
60
|
+
self.items = items
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
func toMap() -> [String: Any] {
|
|
64
|
+
return [
|
|
65
|
+
"title": title,
|
|
66
|
+
"items": items.map { $0.toMap() }
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
struct NavigationButtonConfig {
|
|
72
|
+
let icon: String
|
|
73
|
+
let onPress: () -> Void
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// MARK: - Components
|
|
77
|
+
public struct HeaderRight: View {
|
|
78
|
+
public init(headerRight: HeaderRightData? = nil) {
|
|
79
|
+
self.headerRight = headerRight
|
|
80
|
+
}
|
|
81
|
+
var headerRight: HeaderRightData?
|
|
82
|
+
|
|
83
|
+
public var body: some View {
|
|
84
|
+
HStack(alignment: .center) {
|
|
85
|
+
ToolkitHeaderRight(headerRight: headerRight)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
struct ToolkitHeaderRight: View {
|
|
91
|
+
var headerRight: HeaderRightData?
|
|
92
|
+
@State private var isFavorite: Bool = false
|
|
93
|
+
@State private var isLoading: Bool = false
|
|
94
|
+
@EnvironmentObject private var environment: ApplicationEnvironment
|
|
95
|
+
|
|
96
|
+
// MARK: - Actions
|
|
97
|
+
private func onPressShortcut() {
|
|
98
|
+
environment.composeApi.request(
|
|
99
|
+
funcName: "onToolAction",
|
|
100
|
+
params: ["item": ["key": "onFavorite"]]
|
|
101
|
+
) { _ in
|
|
102
|
+
isFavorite.toggle()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private func onPressHelpCenter() {
|
|
107
|
+
let context = environment.applicationContext
|
|
108
|
+
let paramMap: [String: Any] = [
|
|
109
|
+
"appId": context?.appId,
|
|
110
|
+
"code": context?.appCode,
|
|
111
|
+
"name": context?.appName,
|
|
112
|
+
"icon": context?.appIcon,
|
|
113
|
+
"description": context?.description
|
|
114
|
+
]
|
|
115
|
+
environment.composeApi.request(
|
|
116
|
+
funcName: "showHelpCenter",
|
|
117
|
+
params: paramMap
|
|
118
|
+
) { _ in }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private func onPressClose() {
|
|
122
|
+
environment.composeApi.request(
|
|
123
|
+
funcName: "dismiss",
|
|
124
|
+
params: ""
|
|
125
|
+
) { _ in }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private func onPressMore() {
|
|
129
|
+
let context = environment.applicationContext
|
|
130
|
+
let params: [String: Any] = [
|
|
131
|
+
"tools": headerRight?.tools.map { $0.toMap() } ?? [],
|
|
132
|
+
"context": [
|
|
133
|
+
"appId": context?.appId,
|
|
134
|
+
"code": context?.appCode,
|
|
135
|
+
"name": context?.appName,
|
|
136
|
+
"icon": context?.appIcon,
|
|
137
|
+
"description": context?.description
|
|
138
|
+
]
|
|
139
|
+
]
|
|
140
|
+
environment.composeApi.request(
|
|
141
|
+
funcName: "showTools",
|
|
142
|
+
params: params
|
|
143
|
+
) { response in
|
|
144
|
+
do {
|
|
145
|
+
if let jsonData = response.data(using: .utf8),
|
|
146
|
+
let json = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
|
|
147
|
+
let toolResponse = json["response"] as? String {
|
|
148
|
+
headerRight?.toolCallback?(toolResponse)
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
print("Error parsing response:", error)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private func getNavigationButtonConfig() -> NavigationButtonConfig {
|
|
157
|
+
let totalTools = headerRight?.tools.reduce(0) { $0 + $1.items.count } ?? 0
|
|
158
|
+
var icon = isFavorite ? "pin_star_checked" : "pin_star"
|
|
159
|
+
var onClickHandler: () -> Void = onPressShortcut
|
|
160
|
+
|
|
161
|
+
if totalTools > 1 || headerRight?.useMore == true {
|
|
162
|
+
icon = "navigation_more_icon"
|
|
163
|
+
onClickHandler = onPressMore
|
|
164
|
+
} else if totalTools == 1, let singleTool = headerRight?.tools.first?.items.first {
|
|
165
|
+
icon = singleTool.icon
|
|
166
|
+
onClickHandler = {
|
|
167
|
+
headerRight?.toolCallback?(singleTool.key)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return NavigationButtonConfig(icon: icon, onPress: onClickHandler)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
var body: some View {
|
|
175
|
+
let navButtonConfig = getNavigationButtonConfig()
|
|
176
|
+
let showBadge = headerRight?.tools.contains { group in
|
|
177
|
+
group.items.contains { $0.showBadge }
|
|
178
|
+
} ?? false
|
|
179
|
+
|
|
180
|
+
HStack(alignment: .center) {
|
|
181
|
+
if headerRight?.useShortcut == true {
|
|
182
|
+
NavigationButton(
|
|
183
|
+
disabled: isLoading,
|
|
184
|
+
icon: navButtonConfig.icon,
|
|
185
|
+
showBadge: showBadge,
|
|
186
|
+
onClick: navButtonConfig.onPress
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
HStack(alignment: .center, spacing: 0) {
|
|
191
|
+
Icon(source: "help_center", size: 20)
|
|
192
|
+
.padding(4)
|
|
193
|
+
.onTapGesture {
|
|
194
|
+
onPressHelpCenter()
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
Rectangle()
|
|
198
|
+
.fill(Colors.black20)
|
|
199
|
+
.frame(width: 0.5, height: 12)
|
|
200
|
+
|
|
201
|
+
Icon(source: "16_navigation_close_circle", size: 20, color: Colors.black17)
|
|
202
|
+
.padding(4)
|
|
203
|
+
.onTapGesture {
|
|
204
|
+
onPressClose()
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
.frame(width: 65, height: 28)
|
|
208
|
+
.background(Color.white.opacity(0.6))
|
|
209
|
+
.cornerRadius(14)
|
|
210
|
+
.overlay(
|
|
211
|
+
RoundedRectangle(cornerRadius: 14)
|
|
212
|
+
.stroke(Color.black.opacity(0.2), lineWidth: 0.2)
|
|
213
|
+
)
|
|
214
|
+
.padding(.leading, Spacing.S)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
struct NavigationButton: View {
|
|
220
|
+
var disabled: Bool
|
|
221
|
+
var icon: String
|
|
222
|
+
var showBadge: Bool
|
|
223
|
+
var onClick: () -> Void
|
|
224
|
+
|
|
225
|
+
var body: some View {
|
|
226
|
+
ZStack {
|
|
227
|
+
Circle()
|
|
228
|
+
.fill(Color.white.opacity(0.6))
|
|
229
|
+
.overlay(
|
|
230
|
+
Circle()
|
|
231
|
+
.stroke(Color.black.opacity(0.2), lineWidth: 0.2)
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
Icon(source: icon, size: 16)
|
|
235
|
+
|
|
236
|
+
if showBadge {
|
|
237
|
+
BadgeDot(size: .small)
|
|
238
|
+
.offset(x: -2, y: -2)
|
|
239
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
.frame(width: 28, height: 28)
|
|
243
|
+
.onTapGesture {
|
|
244
|
+
if !disabled {
|
|
245
|
+
onClick()
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|