@momo-kits/native-kits 0.163.1-beta.14-debug → 0.163.1-beta.15-debug

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.
@@ -0,0 +1,84 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\" \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.144.6/skiko-android-0.144.6.pom\")",
5
+ "Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/maven-metadata.xml\")",
6
+ "Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko/maven-metadata.xml\")",
7
+ "Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko/0.144.6/skiko-0.144.6.module\")",
8
+ "Bash(curl -s -o /dev/null -w '%{http_code}' https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.144.6/skiko-android-0.144.6.__TRACKED_VAR__)",
9
+ "Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.144.6/\")",
10
+ "Bash(curl -s \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/\")",
11
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.148.1/skiko-android-0.148.1.pom\")",
12
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://repo.maven.apache.org/maven2/org/jetbrains/skiko/skiko-android/0.144.5/skiko-android-0.144.5.pom\")",
13
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://dl.google.com/dl/android/maven2/org/jetbrains/skiko/skiko-android/0.144.6/skiko-android-0.144.6.pom\")",
14
+ "Bash(curl -s \"https://search.maven.org/solrsearch/select?q=a:skiko-android&rows=5&wt=json\")",
15
+ "Bash(curl -sL -o /dev/null -w \"%{http_code} %{url_effective}\\\\n\" \"https://repo1.maven.org/maven2/org/jetbrains/skiko/skiko-android/0.144.6/skiko-android-0.144.6.pom\")",
16
+ "Bash(curl *)",
17
+ "Bash(./gradlew :sample:androidApp:dependencies --configuration debugRuntimeClasspath)",
18
+ "Bash(./gradlew :sample:shared:compileDebugKotlinAndroid)",
19
+ "Bash(./gradlew :sample:androidApp:compileDebugKotlin)",
20
+ "Bash(ls -d *.xcodeproj)",
21
+ "Bash(grep -c ChipDemo.swift __TRACKED_VAR__/project.pbxproj)",
22
+ "Bash(grep -c PBXFileSystemSynchronizedRootGroup __TRACKED_VAR__/project.pbxproj)",
23
+ "Bash(grep -c InformationDemo.swift __TRACKED_VAR__/project.pbxproj)",
24
+ "Bash(plutil -lint project.pbxproj)",
25
+ "Bash(xcode-select -p)",
26
+ "Bash(/usr/bin/xcrun xcodebuild *)",
27
+ "Bash(echo \"exit: $?\")",
28
+ "Bash(/Applications/Xcode26.app/Contents/Developer/usr/bin/xcodebuild -version)",
29
+ "Bash(./gradlew :compose:tasks --all)",
30
+ "Bash(grep -n \"vanniktech\\\\|mavenPublishing\\\\|^publishing\\\\|version = gitlabVersion\\\\|namespace =\\\\|compileSdk =\\\\|minSdk =\\\\|GitLabPackages\\\\|name = \\\\\"\\\\${gitlabRepo}Packages\\\\\"\")",
31
+ "Bash(git checkout *)",
32
+ "Bash(./gradlew :compose:publishAllPublicationsToGitLabPackagesRepository --dry-run)",
33
+ "Bash(git fetch *)",
34
+ "Bash(git pull *)",
35
+ "Bash(git push *)",
36
+ "Bash(git branch *)",
37
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -list)",
38
+ "Bash(xcrun simctl *)",
39
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14 Pro' -configuration Debug build CODE_SIGNING_ALLOWED=NO)",
40
+ "Bash(pod install *)",
41
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'id=1E6F9A79-5E64-4590-BEA2-828D75D99393' -configuration Debug build CODE_SIGNING_ALLOWED=NO)",
42
+ "Bash(xcrun swiftc *)",
43
+ "Bash(echo \"=== exit: $? \\(1=none found\\) ===\")",
44
+ "Bash(cat Radius.swift)",
45
+ "Bash(cat Spacing.swift)",
46
+ "Bash(echo \"exit=$?\")",
47
+ "Bash(ruby *)",
48
+ "Bash(xcodebuild -list -workspace iosApp.xcworkspace)",
49
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14 Pro' build)",
50
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'platform=iOS Simulator,id=E6A899C0-098E-473A-95B8-A2BBD2D42A92' build)",
51
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,id=E6A899C0-098E-473A-95B8-A2BBD2D42A92' build)",
52
+ "Bash(xcodebuild build *)",
53
+ "Bash(git stash *)",
54
+ "Bash(plutil -lint sample/iosApp/iosApp.xcodeproj/project.pbxproj)",
55
+ "Bash(git add *)",
56
+ "Bash(git commit -m 'feat: DS-646 [ci build] *)",
57
+ "Bash(git commit -q -m 'fix: DS-656 [ci build] *)",
58
+ "Bash(xargs git log --oneline -1)",
59
+ "Bash(git rev-list *)",
60
+ "Bash(git status *)",
61
+ "Bash(git rebase *)",
62
+ "Bash(git merge *)",
63
+ "Bash(git reset *)",
64
+ "Bash(awk '/^ *)",
65
+ "Bash(git ls-tree *)",
66
+ "Bash(awk *)",
67
+ "Read(//Users/sophia/Workspace/momo/momo-app/**)",
68
+ "Bash(git log *)",
69
+ "Bash(xcodebuild -version)",
70
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -configuration Debug build CODE_SIGNING_ALLOWED=NO)",
71
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -configuration Debug build CODE_SIGNING_ALLOWED=NO)",
72
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -configuration Debug clean build CODE_SIGNING_ALLOWED=NO)",
73
+ "Bash(git commit *)",
74
+ "Bash(npm view *)",
75
+ "Bash(echo \"EXIT=$? -> not resolvable\")",
76
+ "Bash(yarn install *)",
77
+ "Bash(RCT_NEW_ARCH_ENABLED=1 pod install)",
78
+ "Bash(xcodebuild -workspace MoMoPlatform.xcworkspace -list)",
79
+ "Bash(git --no-pager diff --stat ios/Application/Localize.swift ios/Application/Navigation/NavigationContainer.swift ios/Popup/PopupDisplay.swift ios/Popup/PopupNotify.swift ios/Template/TrustBanner/TrustBanner.swift)",
80
+ "Bash(git rm *)",
81
+ "Bash(echo \"EXIT=$?\")"
82
+ ]
83
+ }
84
+ }
@@ -40,7 +40,7 @@ kotlin {
40
40
  }
41
41
 
42
42
  cocoapods {
43
- version = "0.163.1-beta.14-debug"
43
+ version = "0.163.1-beta.15-debug"
44
44
  summary = "IOS Shared module"
45
45
  homepage = "https://momo.vn"
46
46
  ios.deploymentTarget = "15.0"
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |spec|
2
2
  spec.name = 'compose'
3
- spec.version = '0.162.2-test.22'
3
+ spec.version = '0.163.1-beta.15-debug'
4
4
  spec.homepage = 'https://momo.vn'
5
5
  spec.source = { :http=> ''}
6
6
  spec.authors = ''
package/gradle.properties CHANGED
@@ -18,7 +18,7 @@ kotlin.apple.xcodeCompatibility.nowarn=true
18
18
  name="ComposeKits"
19
19
  group=vn.momo.kits
20
20
  artifact.id=kits
21
- version=0.163.1-beta.10
21
+ version=0.163.1-beta.15
22
22
 
23
23
  repo=GitLab
24
24
  url=https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven
@@ -100,6 +100,20 @@ public struct HeaderBackground: View {
100
100
  .frame(height: height)
101
101
  .shadow(color: Colors.black20.opacity(0.2), radius: 10, x: 0, y: -2)
102
102
  .background(backgroundColor)
103
+ .overlay(
104
+ headerTransparent ? AnyView(EmptyView()) :
105
+ AnyView(
106
+ Rectangle()
107
+ .fill(LinearGradient(
108
+ gradient: Gradient(colors: [
109
+ Color(red: 1, green: 0.8, blue: 0.87),
110
+ Color(red: 1, green: 0.8, blue: 0.87).opacity(0.5)
111
+ ]),
112
+ startPoint: .top,
113
+ endPoint: .bottom))
114
+ .opacity(opacity)
115
+ .frame(height: height))
116
+ )
103
117
  if !headerTransparent {
104
118
  Rectangle()
105
119
  .fill(Color.black.opacity(0.1))
@@ -132,17 +146,6 @@ public struct HeaderBackground: View {
132
146
  }
133
147
  }
134
148
 
135
- // MARK: - Header right width preference
136
-
137
- /// Reports the measured width of the header-right area so the animated search
138
- /// header can compute its trailing inset (mirrors Compose's headerRightWidthPx).
139
- public struct HeaderRightWidthKey: PreferenceKey {
140
- public static var defaultValue: CGFloat = 0
141
- public static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
142
- value = max(value, nextValue())
143
- }
144
- }
145
-
146
149
  // MARK: - Bottom-tab root preference
147
150
 
148
151
  /// Set by `BottomTab` on appear so its enclosing `StackScreen` can detect that
@@ -232,9 +235,6 @@ public struct Header: View {
232
235
 
233
236
  if let headerRight = headerRight {
234
237
  AnyView(headerRight())
235
- .background(GeometryReader { geo in
236
- Color.clear.preference(key: HeaderRightWidthKey.self, value: geo.size.width)
237
- })
238
238
  }
239
239
  }
240
240
  .padding(.horizontal, 12)
@@ -101,98 +101,69 @@ struct ToolkitHeaderRight: View {
101
101
  var tintColor: Color?
102
102
  @State private var isFavorite: Bool = false
103
103
  @State private var isLoading: Bool = false
104
- @Environment(\.applicationEnvironment) private var environment
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
- }
104
+ @EnvironmentObject private var environment: ApplicationEnvironment
136
105
 
137
106
  // 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
-
149
107
  private func onPressShortcut() {
150
108
  environment.composeApi?.request(
151
109
  funcName: "onToolAction",
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
- }
110
+ params: ["item": ["key": "onFavorite"]]
111
+ ) { _ in
112
+ isFavorite.toggle()
161
113
  }
162
114
  }
163
115
 
164
116
  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
+ ]
165
125
  environment.composeApi?.request(
166
126
  funcName: "showHelpCenter",
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
- ]
127
+ params: paramMap
174
128
  ) { _ in }
175
129
  }
176
130
 
177
131
  private func onPressClose() {
178
132
  environment.composeApi?.request(
179
133
  funcName: "dismissAll",
180
- params: nil
134
+ params: ""
181
135
  ) { _ in }
182
136
  }
183
137
 
184
138
  private func onPressMore() {
139
+ let context = environment.applicationContext
185
140
  let params: [String: Any] = [
186
- "useSystemTools": headerRight?.useSystemTools ?? true,
141
+ "useSystemTools": headerRight?.useSystemTools,
187
142
  "tools": headerRight?.tools.map { $0.toMap() } ?? [],
188
- "context": contextMap
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
+ ]
189
154
  ]
190
155
  environment.composeApi?.request(
191
156
  funcName: "showTools",
192
157
  params: params
193
158
  ) { response in
194
- if let toolResponse = parseResponse(response) as? String {
195
- headerRight?.toolCallback?(toolResponse)
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)
196
167
  }
197
168
  }
198
169
  }
@@ -224,7 +195,7 @@ struct ToolkitHeaderRight: View {
224
195
  } ?? false
225
196
 
226
197
  HStack(alignment: .center, spacing: 0) {
227
- if shouldShowShortcut {
198
+ if headerRight?.useShortcut == true {
228
199
  NavigationButton(
229
200
  disabled: isLoading,
230
201
  icon: navButtonConfig.icon,
@@ -235,17 +206,15 @@ struct ToolkitHeaderRight: View {
235
206
  }
236
207
 
237
208
  HStack(alignment: .center, spacing: 0) {
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
- }
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)
249
218
 
250
219
  Icon(source: "16_basic_home", size: 20, color: tintColor ?? Colors.black17)
251
220
  .padding(4)
@@ -262,9 +231,6 @@ struct ToolkitHeaderRight: View {
262
231
  )
263
232
  .padding(.leading, Spacing.S)
264
233
  }
265
- .onAppear {
266
- loadFavoriteState()
267
- }
268
234
  }
269
235
  }
270
236
 
@@ -11,6 +11,7 @@
11
11
  //
12
12
 
13
13
  import Foundation
14
+ import SwiftUI
14
15
 
15
16
  /// Translation dictionary keyed by language; both `vi` and `en` are required.
16
17
  public struct LocalizationObject {
@@ -275,3 +276,34 @@ public final class Localize: ObservableObject {
275
276
  ]
276
277
  )
277
278
  }
279
+
280
+ /// Snapshots `currentLanguage` alongside the instance so SwiftUI sees an environment
281
+ /// *change* on `changeLanguage`: the `Localize` reference alone never changes, and
282
+ /// `@Environment` does not subscribe to `@Published` the way `@EnvironmentObject` does.
283
+ private struct LocalizeBox: Equatable {
284
+ let localize: Localize
285
+ let language: String
286
+
287
+ init(_ localize: Localize) {
288
+ self.localize = localize
289
+ self.language = localize.currentLanguage
290
+ }
291
+
292
+ static func == (lhs: LocalizeBox, rhs: LocalizeBox) -> Bool {
293
+ lhs.localize === rhs.localize && lhs.language == rhs.language
294
+ }
295
+ }
296
+
297
+ private struct LocalizeKey: EnvironmentKey {
298
+ /// A default-constructed `Localize` already carries the full kit dictionary, so a
299
+ /// consumer that never injects one renders untranslated-but-correct text instead of
300
+ /// trapping the way `@EnvironmentObject` does.
301
+ static let defaultValue = LocalizeBox(Localize())
302
+ }
303
+
304
+ public extension EnvironmentValues {
305
+ var localize: Localize {
306
+ get { self[LocalizeKey.self].localize }
307
+ set { self[LocalizeKey.self] = LocalizeBox(newValue) }
308
+ }
309
+ }
@@ -63,12 +63,14 @@ public struct NavigationContainer<Initial: View>: View {
63
63
  .environmentObject(applicationEnvironment)
64
64
  .environment(\.applicationEnvironment, applicationEnvironment)
65
65
  .environmentObject(localize)
66
+ .environment(\.localize, localize)
66
67
  .fullScreenCover(item: $navigator.presented) { route in
67
68
  screenView(forDialog: route)
68
69
  .environmentObject(navigator)
69
70
  .environmentObject(applicationEnvironment)
70
71
  .environment(\.applicationEnvironment, applicationEnvironment)
71
72
  .environmentObject(localize)
73
+ .environment(\.localize, localize)
72
74
  }
73
75
  .overlay {
74
76
  if let item = navigator.overplay {
@@ -77,6 +79,7 @@ public struct NavigationContainer<Initial: View>: View {
77
79
  .environmentObject(applicationEnvironment)
78
80
  .environment(\.applicationEnvironment, applicationEnvironment)
79
81
  .environmentObject(localize)
82
+ .environment(\.localize, localize)
80
83
  }
81
84
  }
82
85
  .onAppear {
@@ -22,7 +22,7 @@ public struct NavigationHeader: View {
22
22
  /// Optional rich title (mirrors Compose's `HeaderTitle`); `.user` renders an
23
23
  /// avatar group, `.default`/nil renders plain `title`.
24
24
  var headerTitle: HeaderTitle?
25
- var headerRight: (() -> any View)? = { HeaderRight() }
25
+ var headerRight: (() -> any View)? = { NavigationHeaderRight() }
26
26
  var goBack: (() -> Void)?
27
27
  var opacity: CGFloat = 1
28
28
  var animatedHeader: AnimatedHeader?
@@ -0,0 +1,11 @@
1
+ import SwiftUI
2
+
3
+ /// Reports the measured width of the header-right area so the animated search
4
+ /// header can compute its trailing inset (mirrors Compose's headerRightWidthPx).
5
+ /// Written by the header views (`NavigationHeader`) and read by `StackScreen`.
6
+ public struct HeaderRightWidthKey: PreferenceKey {
7
+ public static var defaultValue: CGFloat = 0
8
+ public static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
9
+ value = max(value, nextValue())
10
+ }
11
+ }
@@ -0,0 +1,196 @@
1
+ import SwiftUI
2
+ import Foundation
3
+
4
+ /// Keyed (`@Environment(\.applicationEnvironment)`) variant of `HeaderRight`, for hosts
5
+ /// wired through `NavigationContainer`/`KitContainer` (the injection style introduced by
6
+ /// MR 227 / DS-637). Reuses the shared data types (`HeaderRightData`, `Tool`, `ToolGroup`)
7
+ /// and `NavigationButton` from `HeaderRight.swift`. The legacy `HeaderRight` there stays on
8
+ /// `@EnvironmentObject` for hosts that render `Screen` directly with
9
+ /// `.environmentObject(ApplicationEnvironment(...))`. Opt in per screen with
10
+ /// `headerRight: { NavigationHeaderRight() }`.
11
+ public struct NavigationHeaderRight: View {
12
+ public init(headerRight: HeaderRightData? = nil, tintColor: Color? = nil) {
13
+ self.headerRight = headerRight
14
+ self.tintColor = tintColor
15
+ }
16
+ var headerRight: HeaderRightData?
17
+ var tintColor: Color? = nil
18
+
19
+ public var body: some View {
20
+ HStack(alignment: .center) {
21
+ ToolkitNavigationHeaderRight(headerRight: headerRight, tintColor: tintColor)
22
+ }
23
+ }
24
+ }
25
+
26
+ struct ToolkitNavigationHeaderRight: View {
27
+ var headerRight: HeaderRightData?
28
+ var tintColor: Color?
29
+ @State private var isFavorite: Bool = false
30
+ @State private var isLoading: Bool = false
31
+ @Environment(\.applicationEnvironment) private var environment
32
+
33
+ private func apiValue(_ value: Any?) -> Any {
34
+ value ?? NSNull()
35
+ }
36
+
37
+ private var contextMap: [String: Any] {
38
+ let context = environment.applicationContext
39
+ return [
40
+ "appId": apiValue(context?.appId),
41
+ "code": apiValue(context?.appCode),
42
+ "name": apiValue(context?.appName),
43
+ "icon": apiValue(context?.appIcon),
44
+ "description": apiValue(context?.description),
45
+ "support": apiValue(context?.support),
46
+ "toolkitConfig": apiValue(context?.toolkitConfig),
47
+ "providerId": apiValue(context?.providerId),
48
+ "permissions": apiValue(context?.permissions)
49
+ ]
50
+ }
51
+
52
+ private var shouldShowShortcut: Bool {
53
+ headerRight?.useShortcut == true && !(headerRight?.useMore == true && environment.applicationContext == nil)
54
+ }
55
+
56
+ private func parseResponse(_ response: String) -> Any? {
57
+ guard let jsonData = response.data(using: .utf8),
58
+ let json = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] else {
59
+ return nil
60
+ }
61
+ return json["response"]
62
+ }
63
+
64
+ // MARK: - Actions
65
+ private func loadFavoriteState() {
66
+ environment.composeApi?.request(
67
+ funcName: "isFavoriteApp",
68
+ params: ["code": apiValue(environment.applicationContext?.appCode)]
69
+ ) { response in
70
+ if let isFavorite = parseResponse(response) as? Bool {
71
+ self.isFavorite = isFavorite
72
+ }
73
+ }
74
+ }
75
+
76
+ private func onPressShortcut() {
77
+ environment.composeApi?.request(
78
+ funcName: "onToolAction",
79
+ params: [
80
+ "item": ["key": "onFavorite"],
81
+ "context": contextMap
82
+ ]
83
+ ) { response in
84
+ if let result = parseResponse(response) as? [String: Any],
85
+ result["success"] as? Bool == true {
86
+ isFavorite.toggle()
87
+ }
88
+ }
89
+ }
90
+
91
+ private func onPressHelpCenter() {
92
+ environment.composeApi?.request(
93
+ funcName: "showHelpCenter",
94
+ params: [
95
+ "appId": apiValue(environment.applicationContext?.appId),
96
+ "code": apiValue(environment.applicationContext?.appCode),
97
+ "name": apiValue(environment.applicationContext?.appName),
98
+ "icon": apiValue(environment.applicationContext?.appIcon),
99
+ "description": apiValue(environment.applicationContext?.description)
100
+ ]
101
+ ) { _ in }
102
+ }
103
+
104
+ private func onPressClose() {
105
+ environment.composeApi?.request(
106
+ funcName: "dismissAll",
107
+ params: nil
108
+ ) { _ in }
109
+ }
110
+
111
+ private func onPressMore() {
112
+ let params: [String: Any] = [
113
+ "useSystemTools": headerRight?.useSystemTools ?? true,
114
+ "tools": headerRight?.tools.map { $0.toMap() } ?? [],
115
+ "context": contextMap
116
+ ]
117
+ environment.composeApi?.request(
118
+ funcName: "showTools",
119
+ params: params
120
+ ) { response in
121
+ if let toolResponse = parseResponse(response) as? String {
122
+ headerRight?.toolCallback?(toolResponse)
123
+ }
124
+ }
125
+ }
126
+
127
+ private func getNavigationButtonConfig() -> NavigationButtonConfig {
128
+ let totalTools = headerRight?.tools.reduce(0) { $0 + $1.items.count } ?? 0
129
+ var icon = isFavorite ? "pin_star_checked" : "pin_star"
130
+ var onClickHandler: () -> Void = onPressShortcut
131
+
132
+ if totalTools > 1 || headerRight?.useMore == true {
133
+ icon = "navigation_more_icon"
134
+ onClickHandler = onPressMore
135
+ } else if totalTools == 1, let singleTool = headerRight?.tools.first?.items.first {
136
+ icon = singleTool.icon
137
+ onClickHandler = {
138
+ headerRight?.toolCallback?(singleTool.key)
139
+ }
140
+ }
141
+
142
+ return NavigationButtonConfig(icon: icon, onPress: onClickHandler)
143
+ }
144
+
145
+ var body: some View {
146
+ let backgroundButtonColor: Color = tintColor == Colors.black01 ? Colors.black20.opacity(0.6) : Colors.black01.opacity(0.6)
147
+ let borderColor: Color = tintColor == Colors.black01 ? Colors.black01.opacity(0.2) : Colors.black20.opacity(0.2)
148
+ let navButtonConfig = getNavigationButtonConfig()
149
+ let showBadge = headerRight?.tools.contains { group in
150
+ group.items.contains { $0.showBadge }
151
+ } ?? false
152
+
153
+ HStack(alignment: .center, spacing: 0) {
154
+ if shouldShowShortcut {
155
+ NavigationButton(
156
+ disabled: isLoading,
157
+ icon: navButtonConfig.icon,
158
+ showBadge: showBadge,
159
+ onClick: navButtonConfig.onPress,
160
+ tintColor: tintColor
161
+ )
162
+ }
163
+
164
+ HStack(alignment: .center, spacing: 0) {
165
+ if environment.applicationContext != nil {
166
+ Icon(source: "help_center", size: 20, color: tintColor ?? Colors.black17)
167
+ .padding(4)
168
+ .onTapGesture {
169
+ onPressHelpCenter()
170
+ }
171
+
172
+ Rectangle()
173
+ .fill(tintColor ?? Colors.black20)
174
+ .frame(width: 0.5, height: 12)
175
+ }
176
+
177
+ Icon(source: "16_basic_home", size: 20, color: tintColor ?? Colors.black17)
178
+ .padding(4)
179
+ .onTapGesture {
180
+ onPressClose()
181
+ }
182
+ }
183
+ .frame(width: 65, height: 28)
184
+ .background(backgroundButtonColor)
185
+ .cornerRadius(14)
186
+ .overlay(
187
+ RoundedRectangle(cornerRadius: 14)
188
+ .stroke(borderColor, lineWidth: 0.2)
189
+ )
190
+ .padding(.leading, Spacing.S)
191
+ }
192
+ .onAppear {
193
+ loadFavoriteState()
194
+ }
195
+ }
196
+ }
@@ -54,7 +54,7 @@ extension View {
54
54
  // MARK: - PopupDisplay
55
55
 
56
56
  public struct PopupDisplay: View {
57
- @EnvironmentObject private var localize: Localize
57
+ @Environment(\.localize) private var localize
58
58
 
59
59
  // MARK: Lifecycle
60
60
 
@@ -38,7 +38,7 @@ public struct PopupNotifyProps {
38
38
  }
39
39
 
40
40
  public struct PopupNotify: View {
41
- @EnvironmentObject private var localize: Localize
41
+ @Environment(\.localize) private var localize
42
42
 
43
43
  public let props: PopupNotifyProps
44
44
 
@@ -20,26 +20,21 @@ public struct PopupPromotion: View {
20
20
  }
21
21
 
22
22
  public var body: some View {
23
- // Mirrors Kotlin's `Modifier.fillMaxWidth().aspectRatio(0.72f)` box with
24
- // `ContentScale.FillBounds` inside it: a fixed-ratio frame the image stretches
25
- // to exactly fill (no `.aspectRatio` modifier on the image itself, unlike the
26
- // previous `.fit`, which letterboxed instead of stretching).
27
- let boxWidth = UIScreen.main.bounds.width - Spacing.XL * 2
28
- let boxHeight = boxWidth / 0.72
29
-
30
23
  VStack(spacing: 8) {
31
24
  if(imageUrl.isEmpty) {
32
- Icon(source: "media_fail", size: boxWidth)
25
+ Icon(source: "media_fail", size: UIScreen.main.bounds.width - Spacing.XL * 2)
33
26
  }
34
27
  else {
35
28
  WebImage(url: URL(string: imageUrl)!)
36
29
  .resizable()
37
30
  .placeholder {
38
31
  Color.gray
39
- .frame(width: boxWidth, height: boxHeight)
32
+ .aspectRatio(0.72, contentMode: .fit)
33
+ .frame(maxWidth: UIScreen.main.bounds.width - Spacing.XL * 2)
40
34
  .clipped()
41
35
  }
42
- .frame(width: boxWidth, height: boxHeight, alignment: .center)
36
+ .aspectRatio(0.72, contentMode: .fit)
37
+ .frame(maxWidth: UIScreen.main.bounds.width - Spacing.XL * 2, alignment: .center)
43
38
  .clipped()
44
39
  .onTapGesture {
45
40
  onAction?()
@@ -70,7 +65,6 @@ public struct PopupPromotion: View {
70
65
  .padding(8)
71
66
  .zIndex(1)
72
67
  }
73
- .buttonStyle(.plain)
74
68
  }
75
69
  }
76
70
 
@@ -51,8 +51,8 @@ let defaultBanner = TrustBannerData(
51
51
  )
52
52
 
53
53
  public struct TrustBanner: View {
54
- @EnvironmentObject var applicationEnvironment: ApplicationEnvironment
55
- @EnvironmentObject private var localize: Localize
54
+ @Environment(\.applicationEnvironment) var applicationEnvironment
55
+ @Environment(\.localize) private var localize
56
56
 
57
57
  var onPress: ((String)->Void)?
58
58
 
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'MoMoUIKits'
3
- s.version = '0.162.2-sp.9'
3
+ s.version = '0.163.1-beta.15'
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.9'
3
+ s.version = '0.163.1-beta.15'
4
4
  s.summary = 'Demo browser for MoMoUIKits SwiftUI components'
5
5
  s.homepage = 'https://momo.vn'
6
6
  s.license = { :type => 'MIT' }
@@ -89,7 +89,7 @@ private struct PopupNotifyDemoContent: View {
89
89
  )
90
90
  .padding(.top, Spacing.XS)
91
91
  }
92
- .padding(Spacing.L)
92
+ .padding(Spacing.M * 2)
93
93
  }
94
94
  }
95
95
 
@@ -29,7 +29,7 @@ struct PopupPromotionDemo: View {
29
29
  private struct PopupPromotionDemoContent: View {
30
30
  @EnvironmentObject private var navigator: Navigator
31
31
 
32
- @State private var imageUrl = "https://static.momocdn.net/app/img/popup_permission/img_permission_popup_camera.png"
32
+ @State private var imageUrl = "https://static.momocdn.net/app/app/appx/ABC.png?size=M"
33
33
 
34
34
  var body: some View {
35
35
  ScrollView {
@@ -48,7 +48,7 @@ private struct PopupPromotionDemoContent: View {
48
48
  )
49
49
  .padding(.top, 8)
50
50
  }
51
- .padding(16)
51
+ .padding(Spacing.M * 2)
52
52
  }
53
53
  }
54
54
 
@@ -0,0 +1,8 @@
1
+ ## This file must *NOT* be checked into Version Control Systems,
2
+ # as it contains information specific to your local configuration.
3
+ #
4
+ # Location of the SDK. This is only used by Gradle.
5
+ # For customization when using a Version Control System, please read the
6
+ # header note.
7
+ #Tue Jun 02 17:05:32 ICT 2026
8
+ sdk.dir=/Users/sophia/Library/Android/sdk
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.163.1-beta.14-debug",
3
+ "version": "0.163.1-beta.15-debug",
4
4
  "private": false,
5
5
  "dependencies": {},
6
6
  "devDependencies": {},