@momo-kits/native-kits 0.162.2-sp.5 → 0.162.2-sp.6

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.
@@ -102,68 +102,97 @@ struct ToolkitHeaderRight: View {
102
102
  @State private var isFavorite: Bool = false
103
103
  @State private var isLoading: Bool = false
104
104
  @EnvironmentObject private var environment: ApplicationEnvironment
105
+
106
+ private func apiValue(_ value: Any?) -> Any {
107
+ value ?? NSNull()
108
+ }
109
+
110
+ private var contextMap: [String: Any] {
111
+ let context = environment.applicationContext
112
+ return [
113
+ "appId": apiValue(context?.appId),
114
+ "code": apiValue(context?.appCode),
115
+ "name": apiValue(context?.appName),
116
+ "icon": apiValue(context?.appIcon),
117
+ "description": apiValue(context?.description),
118
+ "support": apiValue(context?.support),
119
+ "toolkitConfig": apiValue(context?.toolkitConfig),
120
+ "providerId": apiValue(context?.providerId),
121
+ "permissions": apiValue(context?.permissions)
122
+ ]
123
+ }
124
+
125
+ private var shouldShowShortcut: Bool {
126
+ headerRight?.useShortcut == true && !(headerRight?.useMore == true && environment.applicationContext == nil)
127
+ }
128
+
129
+ private func parseResponse(_ response: String) -> Any? {
130
+ guard let jsonData = response.data(using: .utf8),
131
+ let json = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] else {
132
+ return nil
133
+ }
134
+ return json["response"]
135
+ }
105
136
 
106
137
  // MARK: - Actions
138
+ private func loadFavoriteState() {
139
+ environment.composeApi?.request(
140
+ funcName: "isFavoriteApp",
141
+ params: ["code": apiValue(environment.applicationContext?.appCode)]
142
+ ) { response in
143
+ if let isFavorite = parseResponse(response) as? Bool {
144
+ self.isFavorite = isFavorite
145
+ }
146
+ }
147
+ }
148
+
107
149
  private func onPressShortcut() {
108
150
  environment.composeApi?.request(
109
151
  funcName: "onToolAction",
110
- params: ["item": ["key": "onFavorite"]]
111
- ) { _ in
112
- isFavorite.toggle()
152
+ params: [
153
+ "item": ["key": "onFavorite"],
154
+ "context": contextMap
155
+ ]
156
+ ) { response in
157
+ if let result = parseResponse(response) as? [String: Any],
158
+ result["success"] as? Bool == true {
159
+ isFavorite.toggle()
160
+ }
113
161
  }
114
162
  }
115
163
 
116
164
  private func onPressHelpCenter() {
117
- let context = environment.applicationContext
118
- let paramMap: [String: Any] = [
119
- "appId": context?.appId,
120
- "code": context?.appCode,
121
- "name": context?.appName,
122
- "icon": context?.appIcon,
123
- "description": context?.description
124
- ]
125
165
  environment.composeApi?.request(
126
166
  funcName: "showHelpCenter",
127
- params: paramMap
167
+ params: [
168
+ "appId": apiValue(environment.applicationContext?.appId),
169
+ "code": apiValue(environment.applicationContext?.appCode),
170
+ "name": apiValue(environment.applicationContext?.appName),
171
+ "icon": apiValue(environment.applicationContext?.appIcon),
172
+ "description": apiValue(environment.applicationContext?.description)
173
+ ]
128
174
  ) { _ in }
129
175
  }
130
176
 
131
177
  private func onPressClose() {
132
178
  environment.composeApi?.request(
133
179
  funcName: "dismissAll",
134
- params: ""
180
+ params: nil
135
181
  ) { _ in }
136
182
  }
137
183
 
138
184
  private func onPressMore() {
139
- let context = environment.applicationContext
140
185
  let params: [String: Any] = [
141
- "useSystemTools": headerRight?.useSystemTools,
186
+ "useSystemTools": headerRight?.useSystemTools ?? true,
142
187
  "tools": headerRight?.tools.map { $0.toMap() } ?? [],
143
- "context": [
144
- "appId": context?.appId,
145
- "code": context?.appCode,
146
- "name": context?.appName,
147
- "icon": context?.appIcon,
148
- "description": context?.description,
149
- "support": context?.support,
150
- "toolkitConfig": context?.toolkitConfig,
151
- "providerId": context?.providerId,
152
- "permissions": context?.permissions
153
- ]
188
+ "context": contextMap
154
189
  ]
155
190
  environment.composeApi?.request(
156
191
  funcName: "showTools",
157
192
  params: params
158
193
  ) { response in
159
- do {
160
- if let jsonData = response.data(using: .utf8),
161
- let json = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
162
- let toolResponse = json["response"] as? String {
163
- headerRight?.toolCallback?(toolResponse)
164
- }
165
- } catch {
166
- print("Error parsing response:", error)
194
+ if let toolResponse = parseResponse(response) as? String {
195
+ headerRight?.toolCallback?(toolResponse)
167
196
  }
168
197
  }
169
198
  }
@@ -195,7 +224,7 @@ struct ToolkitHeaderRight: View {
195
224
  } ?? false
196
225
 
197
226
  HStack(alignment: .center, spacing: 0) {
198
- if headerRight?.useShortcut == true {
227
+ if shouldShowShortcut {
199
228
  NavigationButton(
200
229
  disabled: isLoading,
201
230
  icon: navButtonConfig.icon,
@@ -206,15 +235,17 @@ struct ToolkitHeaderRight: View {
206
235
  }
207
236
 
208
237
  HStack(alignment: .center, spacing: 0) {
209
- Icon(source: "help_center", size: 20, color: tintColor ?? Colors.black17)
210
- .padding(4)
211
- .onTapGesture {
212
- onPressHelpCenter()
213
- }
214
-
215
- Rectangle()
216
- .fill(tintColor ?? Colors.black20)
217
- .frame(width: 0.5, height: 12)
238
+ if environment.applicationContext != nil {
239
+ Icon(source: "help_center", size: 20, color: tintColor ?? Colors.black17)
240
+ .padding(4)
241
+ .onTapGesture {
242
+ onPressHelpCenter()
243
+ }
244
+
245
+ Rectangle()
246
+ .fill(tintColor ?? Colors.black20)
247
+ .frame(width: 0.5, height: 12)
248
+ }
218
249
 
219
250
  Icon(source: "16_basic_home", size: 20, color: tintColor ?? Colors.black17)
220
251
  .padding(4)
@@ -231,6 +262,9 @@ struct ToolkitHeaderRight: View {
231
262
  )
232
263
  .padding(.leading, Spacing.S)
233
264
  }
265
+ .onAppear {
266
+ loadFavoriteState()
267
+ }
234
268
  }
235
269
  }
236
270
 
@@ -96,7 +96,8 @@ public struct BottomTab: View {
96
96
  /// Bottom safe-area inset (home indicator).
97
97
  @available(iOS 16.0, *)
98
98
  private func safeAreaBottomInset() -> CGFloat {
99
- UIApplication.shared.connectedScenes
99
+ let bottomInset = UIApplication.shared.connectedScenes
100
100
  .compactMap { ($0 as? UIWindowScene)?.keyWindow?.safeAreaInsets.bottom }
101
101
  .first ?? 0
102
+ return min(bottomInset, 21)
102
103
  }
@@ -15,19 +15,23 @@ public struct NavigationContainer<Initial: View>: View {
15
15
  @StateObject private var navigator: Navigator
16
16
 
17
17
  private let setNavigator: ((Navigator) -> Void)?
18
+ private let composeApi: KitComposeApi?
18
19
 
19
20
  public init(
20
21
  initialScreenName: String = "",
21
22
  @ViewBuilder initialScreen: @escaping () -> Initial,
22
23
  options: NavigationOptions? = nil,
24
+ composeApi: KitComposeApi? = nil,
23
25
  setNavigator: ((Navigator) -> Void)? = nil
24
26
  ) {
25
27
  let nav = Navigator(
26
28
  initialScreenName: initialScreenName.isEmpty ? (options?.screenName ?? "") : initialScreenName,
27
29
  initialContent: { AnyView(initialScreen()) },
28
- options: options
30
+ options: options,
31
+ composeApi: composeApi
29
32
  )
30
33
  _navigator = StateObject(wrappedValue: nav)
34
+ self.composeApi = composeApi
31
35
  self.setNavigator = setNavigator
32
36
  }
33
37
 
@@ -49,7 +53,12 @@ public struct NavigationContainer<Initial: View>: View {
49
53
  .environmentObject(navigator)
50
54
  }
51
55
  }
52
- .onAppear { setNavigator?(navigator) }
56
+ .onAppear {
57
+ if navigator.composeApi == nil {
58
+ navigator.composeApi = composeApi
59
+ }
60
+ setNavigator?(navigator)
61
+ }
53
62
  }
54
63
 
55
64
  @ViewBuilder
@@ -102,12 +102,15 @@ public final class Navigator: ObservableObject {
102
102
  private var overplayDismissHandler: (() -> Void)?
103
103
 
104
104
  public private(set) var rootRoute: DynamicScreenRoute
105
+ public var composeApi: KitComposeApi?
105
106
 
106
107
  public init(
107
108
  initialScreenName: String = "",
108
109
  initialContent: @escaping () -> AnyView,
109
- options: NavigationOptions? = nil
110
+ options: NavigationOptions? = nil,
111
+ composeApi: KitComposeApi? = nil
110
112
  ) {
113
+ self.composeApi = composeApi
111
114
  self.rootRoute = DynamicScreenRegistry.shared.register(
112
115
  screenName: initialScreenName,
113
116
  content: initialContent,
@@ -198,6 +201,7 @@ public final class Navigator: ObservableObject {
198
201
  let removed = path.removeLast()
199
202
  DynamicScreenRegistry.shared.unregister(id: removed.id)
200
203
  } else {
204
+ composeApi?.requestCallback(funcName: "dismiss", params: nil, onResponse: nil)
201
205
  break
202
206
  }
203
207
  remaining -= 1
@@ -54,6 +54,7 @@ struct BottomSheetView<Content: View>: View {
54
54
  private let closeThreshold: CGFloat = 100
55
55
 
56
56
  private var screenHeight: CGFloat { UIScreen.main.bounds.height }
57
+ private var bottomInset: CGFloat { safeAreaBottomInset() }
57
58
 
58
59
  private var backgroundColor: Color {
59
60
  // Compose uses background.surface (white) for `isSurface`, otherwise
@@ -106,6 +107,9 @@ struct BottomSheetView<Content: View>: View {
106
107
  .frame(height: 1)
107
108
 
108
109
  content()
110
+
111
+ backgroundColor
112
+ .frame(height: bottomInset)
109
113
  }
110
114
  .frame(maxWidth: .infinity)
111
115
  .frame(maxHeight: screenHeight - safeAreaTopInset() - 90, alignment: .bottom)
@@ -174,3 +178,11 @@ struct BottomSheetView<Content: View>: View {
174
178
  }
175
179
  }
176
180
  }
181
+
182
+ @available(iOS 16.0, *)
183
+ private func safeAreaBottomInset() -> CGFloat {
184
+ let bottomInset = UIApplication.shared.connectedScenes
185
+ .compactMap { ($0 as? UIWindowScene)?.keyWindow?.safeAreaInsets.bottom }
186
+ .first ?? 0
187
+ return min(bottomInset, 21)
188
+ }
@@ -229,6 +229,7 @@ public struct Screen<Content: View>: View {
229
229
  struct Footer: View {
230
230
  var footerView: AnyView
231
231
  var keyboardSize: CGFloat
232
+ var bottomInset: CGFloat = 0
232
233
 
233
234
  var body: some View {
234
235
  VStack {
@@ -236,7 +237,7 @@ struct Footer: View {
236
237
  }
237
238
  .padding(.vertical, Spacing.S)
238
239
  .padding(.horizontal, Spacing.M)
239
- .padding(.bottom, keyboardSize)
240
+ .padding(.bottom, keyboardSize + bottomInset)
240
241
  .background(Colors.black01)
241
242
  .overlay(
242
243
  Rectangle()
@@ -132,8 +132,10 @@ public struct StackScreen<Content: View>: View {
132
132
  // MARK: - Body
133
133
 
134
134
  public var body: some View {
135
- GeometryReader { _ in
135
+ GeometryReader { geometry in
136
136
  let keyboardSize: CGFloat = useAvoidKeyboard ? max(keyboardHeight, 0) : 0
137
+ let bottomInset = min(geometry.safeAreaInsets.bottom, 21)
138
+ let contentBottomInset = footer == nil ? bottomInset : 0
137
139
 
138
140
  ZStack(alignment: .top) {
139
141
  // Background color
@@ -145,6 +147,7 @@ public struct StackScreen<Content: View>: View {
145
147
  VStack(alignment: horizontalAlignment, spacing: 0) {
146
148
  headerAndContent
147
149
  }
150
+ .padding(.bottom, contentBottomInset)
148
151
 
149
152
  FloatingContent(
150
153
  fabProps: fabProps,
@@ -153,7 +156,7 @@ public struct StackScreen<Content: View>: View {
153
156
  )
154
157
  }
155
158
 
156
- FooterContent(footer: footer, keyboardSize: keyboardSize)
159
+ FooterContent(footer: footer, keyboardSize: keyboardSize, bottomInset: bottomInset)
157
160
  }
158
161
 
159
162
  // Animated search bar overlay (mirrors Compose's SearchAnimated layer).
@@ -440,10 +443,11 @@ private struct HideKeyboardOnTap: ViewModifier {
440
443
  private struct FooterContent: View {
441
444
  let footer: (() -> AnyView)?
442
445
  let keyboardSize: CGFloat
446
+ let bottomInset: CGFloat
443
447
 
444
448
  var body: some View {
445
449
  if let footerView = footer?() {
446
- Footer(footerView: footerView, keyboardSize: keyboardSize)
450
+ Footer(footerView: footerView, keyboardSize: keyboardSize, bottomInset: bottomInset)
447
451
  }
448
452
  }
449
453
  }
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'MoMoUIKits'
3
- s.version = '0.162.2-sp.5'
3
+ s.version = '0.162.2-sp.6'
4
4
  s.summary = 'MoMoUIKits for iOS'
5
5
  s.homepage = 'https://momo.vn'
6
6
  s.license = { :type => 'MIT' }
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'MoMoUIKitsDemo'
3
- s.version = '0.162.2-sp.5'
3
+ s.version = '0.162.2-sp.6'
4
4
  s.summary = 'Demo browser for MoMoUIKits SwiftUI components'
5
5
  s.homepage = 'https://momo.vn'
6
6
  s.license = { :type => 'MIT' }
@@ -6,67 +6,19 @@ public enum MoMoUIKitsDemoViewControllerFactory {
6
6
  public static func make(
7
7
  context: MoMoUIKitsDemoContext = MoMoUIKitsDemoContext(),
8
8
  composeViewController: (() -> UIViewController)? = nil
9
+ ) -> UIViewController {
10
+ composeViewController?() ?? makeSwiftUI(context: context)
11
+ }
12
+
13
+ public static func makeSwiftUI(
14
+ context: MoMoUIKitsDemoContext = MoMoUIKitsDemoContext(),
15
+ onOpenCompose: @escaping () -> Void = {}
9
16
  ) -> UIViewController {
10
17
  UIHostingController(
11
- rootView: MoMoUIKitsDemoView(
18
+ rootView: SwiftUIDemoBrowserView(
12
19
  context: context,
13
- composeViewController: composeViewController
20
+ onOpenCompose: onOpenCompose
14
21
  )
15
22
  )
16
23
  }
17
24
  }
18
-
19
- public struct MoMoUIKitsDemoView: View {
20
- private let context: MoMoUIKitsDemoContext
21
- private let composeViewController: (() -> UIViewController)?
22
- @State private var mode: MoMoUIKitsDemoMode
23
-
24
- public init(
25
- context: MoMoUIKitsDemoContext = MoMoUIKitsDemoContext(),
26
- composeViewController: (() -> UIViewController)? = nil
27
- ) {
28
- self.context = context
29
- self.composeViewController = composeViewController
30
- _mode = State(initialValue: composeViewController == nil ? .swiftUI : .compose)
31
- }
32
-
33
- public var body: some View {
34
- VStack(spacing: 0) {
35
- Picker("", selection: $mode) {
36
- if composeViewController != nil {
37
- Text("Compose").tag(MoMoUIKitsDemoMode.compose)
38
- }
39
- Text("SwiftUI").tag(MoMoUIKitsDemoMode.swiftUI)
40
- }
41
- .pickerStyle(.segmented)
42
- .padding(.horizontal, 16)
43
- .padding(.vertical, 12)
44
- .background(Color(.systemBackground))
45
-
46
- Group {
47
- if mode == .compose, let composeViewController {
48
- ComposeDemoHostView(makeViewController: composeViewController)
49
- } else {
50
- SwiftUIDemoBrowserView(context: context)
51
- }
52
- }
53
- .frame(maxWidth: .infinity, maxHeight: .infinity)
54
- }
55
- }
56
- }
57
-
58
- private enum MoMoUIKitsDemoMode: String {
59
- case compose
60
- case swiftUI
61
- }
62
-
63
- private struct ComposeDemoHostView: UIViewControllerRepresentable {
64
- let makeViewController: () -> UIViewController
65
-
66
- func makeUIViewController(context: Context) -> UIViewController {
67
- makeViewController()
68
- }
69
-
70
- func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
71
- }
72
-
@@ -96,8 +96,11 @@ public struct HomeView: View {
96
96
  // Mirrors Compose's `inputSearch` state: bound into the header search and
97
97
  // forwarded into the content for filtering.
98
98
  @State private var searchText = ""
99
+ private let onOpenCompose: () -> Void
99
100
 
100
- public init() {}
101
+ public init(onOpenCompose: @escaping () -> Void = {}) {
102
+ self.onOpenCompose = onOpenCompose
103
+ }
101
104
 
102
105
  public var body: some View {
103
106
  NavigationContainer(
@@ -124,7 +127,7 @@ public struct HomeView: View {
124
127
  AnyView(
125
128
  MoMoUIKits.Button(
126
129
  title: "Footer Button",
127
- action: { print("Footer Button") },
130
+ action: onOpenCompose,
128
131
  type: .primary,
129
132
  size: .large
130
133
  )
@@ -3,12 +3,13 @@ import MoMoUIKits
3
3
 
4
4
  struct SwiftUIDemoBrowserView: View {
5
5
  let context: MoMoUIKitsDemoContext
6
+ let onOpenCompose: () -> Void
6
7
  @State private var searchText = ""
7
8
 
8
9
  var body: some View {
9
10
  Group {
10
11
  if #available(iOS 16.0, *) {
11
- HomeView()
12
+ HomeView(onOpenCompose: onOpenCompose)
12
13
  } else {
13
14
  fallbackBrowser
14
15
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.162.2-sp.5",
3
+ "version": "0.162.2-sp.6",
4
4
  "private": false,
5
5
  "dependencies": {},
6
6
  "devDependencies": {},