@momo-kits/native-kits 0.163.1-sp.3-debug → 0.163.1-sp.4-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.
Files changed (30) hide show
  1. package/.claude/settings.local.json +57 -1
  2. package/compose/build.gradle.kts +1 -1
  3. package/compose/compose.podspec +1 -1
  4. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +5 -5
  5. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +4 -2
  6. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +66 -19
  7. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +15 -3
  8. package/gradle.properties +1 -1
  9. package/ios/Application/Components.swift +14 -14
  10. package/ios/Application/HeaderRight.swift +45 -79
  11. package/ios/Application/Navigation/component/Header.swift +1 -1
  12. package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +11 -0
  13. package/ios/Application/Navigation/component/NavigationFooter.swift +90 -0
  14. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +196 -0
  15. package/ios/Application/StackScreen.swift +18 -21
  16. package/ios/Badge/BadgeRibbon.swift +4 -1
  17. package/ios/Button/Button.swift +54 -80
  18. package/ios/Icon/Icon.swift +13 -4
  19. package/ios/Layout/GridContext.swift +30 -0
  20. package/ios/Layout/Item.swift +42 -0
  21. package/ios/Layout/Section.swift +134 -0
  22. package/ios/native-kits.podspec +2 -1
  23. package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
  24. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ButtonDemo.swift +8 -8
  25. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CheckboxDemo.swift +3 -2
  26. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +5 -12
  27. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconDemo.swift +104 -0
  28. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SectionDemo.swift +100 -0
  29. package/ios-demo/Sources/MoMoUIKitsDemo/SwiftUIDemoBrowserView.swift +3 -1
  30. package/package.json +1 -1
@@ -72,7 +72,63 @@
72
72
  "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -configuration Debug clean build CODE_SIGNING_ALLOWED=NO)",
73
73
  "Bash(git commit *)",
74
74
  "Bash(npm view *)",
75
- "Bash(echo \"EXIT=$? -> not resolvable\")"
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
+ "Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits diff sample/iosApp/iosApp/Demo/ButtonDemo.swift)",
83
+ "Bash(python3 *)",
84
+ "Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits status --short)",
85
+ "Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits log --oneline -3)",
86
+ "Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits log --oneline -5 -- ios/Button/Button.swift)",
87
+ "Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits diff ios/native-kits.podspec compose/compose.podspec gradle.properties package.json)",
88
+ "Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits show HEAD:ios/Button/Button.swift)",
89
+ "Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits diff package.json ios/native-kits.podspec)",
90
+ "Bash(grep -n \"must be a \\\\`.background\\\\` of the label\" /Users/sophia/Workspace/momo/momo-app/node_modules/@momo-kits/native-kits/ios/Button/Button.swift)",
91
+ "Bash(git -C /Users/sophia/Workspace/momo/momo-native-kits show -s --format=\"%h %ci %s\" 953b234d)",
92
+ "Bash(git -C /Users/sophia/Workspace/momo/momo-app show -s --format=\"%h %ci %s\" f8757a14437)",
93
+ "Bash(git -C /Users/sophia/Workspace/momo/momo-app log -S '\"@momo-kits/native-kits\": \"0.163.1-beta.18\"' --format=\"%h %ci %s\" -- package.json)",
94
+ "Bash(swiftc -O repro_isfull.swift -o repro_isfull)",
95
+ "Bash(./repro_isfull)",
96
+ "Bash(grep -n '^\\\\[32;1m$\\\\|section_start\\\\|xcodebuild.*-workspace\\\\|xcodebuild.*archive\\\\|Prebuilt\\\\|prebuilt\\\\|framework.*download\\\\|rugby\\\\|Rugby' /private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/7e2b5bd7-f8b3-42f7-b242-127fa8eabd12/scratchpad/job_trace.log)",
97
+ "Bash(grep -n '\\\\[32;1m\\\\$' /private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/7e2b5bd7-f8b3-42f7-b242-127fa8eabd12/scratchpad/job_trace.log)",
98
+ "Bash(npm pack *)",
99
+ "Bash(mkdir -p b13 b18)",
100
+ "Bash(tar xzf *)",
101
+ "Bash(git *)",
102
+ "Bash(tar -tzf momo-kits-native-kits-0.163.1-beta.18.tgz)",
103
+ "Bash(tar -xzf momo-kits-native-kits-0.163.1-beta.18.tgz package/ios/Button/Button.swift)",
104
+ "Bash(tar -xzf momo-kits-native-kits-0.162.28.tgz package/ios/Button/Button.swift -C .)",
105
+ "Bash(mkdir -p old)",
106
+ "Bash(tar -xzf momo-kits-native-kits-0.162.28.tgz -C old package/ios/Button/Button.swift)",
107
+ "Bash(nm '/private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/7e2b5bd7-f8b3-42f7-b242-127fa8eabd12/scratchpad/ipa_extract/Payload/MoMoPlatform UAT.app/MoMoPlatform UAT')",
108
+ "Bash(xcodebuild -workspace /Users/sophia/Workspace/momo/momo-native-kits/sample/iosApp/iosApp.xcworkspace -list)",
109
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' build)",
110
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -derivedDataPath build-verify build)",
111
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -derivedDataPath build-verify build)",
112
+ "Bash(./gradlew :sample:shared:syncComposeResourcesForIos)",
113
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,id=E6A899C0-098E-473A-95B8-A2BBD2D42A92' -derivedDataPath build-verify build)",
114
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme MoMoUIKits -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -quiet build)",
115
+ "Bash(xcodebuild -workspace sample/iosApp/iosApp.xcworkspace -list)",
116
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -destination 'id=0228A4B2-57E0-4E56-BFAD-DB9E95044AD9' -derivedDataPath /private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/6986983a-795d-419d-9d78-fcc100e87876/scratchpad/dd build)",
117
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -destination 'id=1E6F9A79-5E64-4590-BEA2-828D75D99393' -derivedDataPath /private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/6986983a-795d-419d-9d78-fcc100e87876/scratchpad/dd build)",
118
+ "Bash([ -s $S/kb.png ])",
119
+ "Bash(sips -g pixelWidth -g pixelHeight /private/tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/6986983a-795d-419d-9d78-fcc100e87876/scratchpad/kb.png)",
120
+ "Bash(defaults read *)",
121
+ "Bash(defaults write *)",
122
+ "Bash(xcrun xcodebuild *)",
123
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -quiet build)",
124
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,id=E6A899C0-098E-473A-95B8-A2BBD2D42A92' -quiet build)",
125
+ "Read(//tmp/claude-503/-Users-sophia-Workspace-momo-momo-native-kits/3e8adc08-a7e7-4169-aee6-8b22a6996f38/scratchpad/**)",
126
+ "Bash(echo \"parse-exit=$?\")",
127
+ "Bash(echo \"=== exit $?\")",
128
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -configuration Debug build)",
129
+ "Bash(xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -sdk iphonesimulator -destination 'platform=iOS Simulator,id=E6A899C0-098E-473A-95B8-A2BBD2D42A92' -configuration Debug build)",
130
+ "Bash(./gradlew :sample:shared:compileCommonMainKotlinMetadata -q)",
131
+ "Bash(echo \"gradle=$?\")"
76
132
  ]
77
133
  }
78
134
  }
@@ -40,7 +40,7 @@ kotlin {
40
40
  }
41
41
 
42
42
  cocoapods {
43
- version = "0.163.1-sp.3-debug"
43
+ version = "0.163.1-sp.4-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.163.1-sp.3-debug'
3
+ spec.version = '0.163.1-sp.4-debug'
4
4
  spec.homepage = 'https://momo.vn'
5
5
  spec.source = { :http=> ''}
6
6
  spec.authors = ''
@@ -29,6 +29,7 @@ import androidx.compose.ui.Alignment
29
29
  import androidx.compose.ui.Modifier
30
30
  import androidx.compose.ui.focus.onFocusChanged
31
31
  import androidx.compose.ui.graphics.Color
32
+ import androidx.compose.ui.graphics.takeOrElse
32
33
  import androidx.compose.ui.text.TextStyle
33
34
  import androidx.compose.ui.text.input.KeyboardType
34
35
  import androidx.compose.ui.text.style.TextOverflow
@@ -49,7 +50,7 @@ import vn.momo.kits.utils.ifTrue
49
50
  internal fun RenderRightIconSearch(
50
51
  loading: Boolean,
51
52
  icon: String,
52
- color: Color,
53
+ color: Color?,
53
54
  onClick: () -> Unit,
54
55
  iconModifier: Modifier = Modifier
55
56
  ) {
@@ -57,7 +58,7 @@ internal fun RenderRightIconSearch(
57
58
  Box(Modifier.padding(horizontal = Spacing.S)) {
58
59
  CircularProgressIndicator(
59
60
  modifier = Modifier.size(16.dp),
60
- color = color,
61
+ color = color ?: AppTheme.current.colors.text.default,
61
62
  trackColor = Color.Transparent,
62
63
  strokeWidth = 2.dp
63
64
  )
@@ -90,7 +91,7 @@ data class InputSearchProps(
90
91
  val onPressButtonText: () -> Unit = {},
91
92
  val disabled: Boolean = false,
92
93
  val icon: String = "",
93
- val iconColor: Color = Color(0xff),
94
+ val iconColor: Color? = Color.Unspecified,
94
95
  val onRightIconPressed: () -> Unit = {},
95
96
  val onFocus: () -> Unit = {},
96
97
  val onBlur: () -> Unit = {},
@@ -113,7 +114,6 @@ data class InputSearchProps(
113
114
  fun InputSearch(
114
115
  inputSearchProps: InputSearchProps = InputSearchProps(
115
116
  text = remember { mutableStateOf("") },
116
- iconColor = AppTheme.current.colors.text.default
117
117
  ),
118
118
  ) {
119
119
  var isFocused by remember { mutableStateOf(false) }
@@ -121,7 +121,7 @@ fun InputSearch(
121
121
  val disabledColor = AppTheme.current.colors.text.disable
122
122
  var textColor = AppTheme.current.colors.text.default
123
123
  var placeholderColor = AppTheme.current.colors.text.hint
124
- var iconTintColor = inputSearchProps.iconColor
124
+ var iconTintColor = inputSearchProps.iconColor?.takeOrElse { AppTheme.current.colors.text.default }
125
125
 
126
126
  if (inputSearchProps.disabled) {
127
127
  textColor = disabledColor
@@ -1,6 +1,6 @@
1
1
  package vn.momo.kits.layout
2
2
 
3
- import androidx.compose.foundation.layout.Box
3
+ import androidx.compose.foundation.layout.Column
4
4
  import androidx.compose.foundation.layout.height
5
5
  import androidx.compose.foundation.layout.width
6
6
  import androidx.compose.runtime.Composable
@@ -32,5 +32,7 @@ internal fun Item(
32
32
  m = m.width(grid.sizeForSpan(widthSpan))
33
33
  if (heightSpan > 0) m = m.height(grid.sizeForSpan(heightSpan))
34
34
  }
35
- Box(modifier = m) { content() }
35
+ // A Column (not a Box) so multiple children flow VERTICALLY instead of stacking
36
+ // on top of each other — matches the React Item's flex-column content.
37
+ Column(modifier = m) { content() }
36
38
  }
@@ -28,6 +28,7 @@ import androidx.compose.runtime.remember
28
28
  import androidx.compose.runtime.rememberCoroutineScope
29
29
  import androidx.compose.ui.Alignment
30
30
  import androidx.compose.ui.Modifier
31
+ import androidx.compose.ui.graphics.Brush
31
32
  import androidx.compose.ui.graphics.Color
32
33
  import androidx.compose.ui.input.pointer.pointerInput
33
34
  import androidx.compose.ui.platform.LocalDensity
@@ -57,6 +58,9 @@ internal fun BottomSheet(
57
58
  header: BottomHeader,
58
59
  isSurface: Boolean = false,
59
60
  barrierDismissible: Boolean = true,
61
+ draggable: Boolean = true,
62
+ useBottomInset: Boolean = true,
63
+ footerComponent: (@Composable () -> Unit)? = null,
60
64
  onDismiss: (() -> Unit)?
61
65
  ) {
62
66
  val screenHeightDp = getScreenHeight()
@@ -161,30 +165,35 @@ internal fun BottomSheet(
161
165
  .noFeedbackClickable { },
162
166
  contentAlignment = Alignment.BottomCenter
163
167
  ) {
164
- Column(Modifier.padding(bottom = AppNavigationBar.current)) {
168
+ val bottomInset = if (useBottomInset) {
169
+ (AppNavigationBar.current - keyboardSizeState().value).coerceAtLeast(0.dp)
170
+ } else 0.dp
171
+ Column(Modifier.padding(bottom = bottomInset)) {
165
172
  Box(
166
173
  modifier = Modifier
167
174
  .height(72.dp)
168
175
  .fillMaxWidth()
169
- .pointerInput(Unit) {
170
- detectDragGestures(
171
- onDrag = { change, dragAmount ->
172
- change.consume()
173
- coroutineScope.launch {
174
- val newOffset = (sheetOffset.value + dragAmount.y).coerceAtLeast(0f)
175
- sheetOffset.snapTo(newOffset)
176
- }
177
- },
178
- onDragEnd = {
179
- coroutineScope.launch {
180
- if (sheetOffset.value > sheetCloseOffset) {
181
- closeEvent()
182
- } else {
183
- sheetOffset.animateTo(0f)
176
+ .conditional(draggable) {
177
+ pointerInput(Unit) {
178
+ detectDragGestures(
179
+ onDrag = { change, dragAmount ->
180
+ change.consume()
181
+ coroutineScope.launch {
182
+ val newOffset = (sheetOffset.value + dragAmount.y).coerceAtLeast(0f)
183
+ sheetOffset.snapTo(newOffset)
184
+ }
185
+ },
186
+ onDragEnd = {
187
+ coroutineScope.launch {
188
+ if (sheetOffset.value > sheetCloseOffset) {
189
+ closeEvent()
190
+ } else {
191
+ sheetOffset.animateTo(0f)
192
+ }
184
193
  }
185
194
  }
186
- }
187
- )
195
+ )
196
+ }
188
197
  }
189
198
  ) {
190
199
  Column(
@@ -235,12 +244,50 @@ internal fun BottomSheet(
235
244
  }
236
245
  }
237
246
  Divider()
238
- content()
247
+ if (footerComponent != null) {
248
+ Box(Modifier.weight(1f, fill = false)) {
249
+ content()
250
+ }
251
+ BottomSheetFooter(footerComponent)
252
+ } else {
253
+ content()
254
+ }
239
255
  }
240
256
  }
241
257
  }
242
258
  }
243
259
 
260
+ @Composable
261
+ private fun BottomSheetFooter(footerComponent: @Composable () -> Unit) {
262
+ val shadowBrush = remember {
263
+ Brush.verticalGradient(
264
+ colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.05f))
265
+ )
266
+ }
267
+
268
+ Box {
269
+ Box(
270
+ Modifier
271
+ .fillMaxWidth()
272
+ .background(AppTheme.current.colors.background.surface)
273
+ .conditional(IsShowBaseLineDebug) {
274
+ border(1.dp, Colors.blue_03)
275
+ }
276
+ .padding(vertical = Spacing.S)
277
+ ) {
278
+ footerComponent()
279
+ }
280
+
281
+ Box(
282
+ modifier = Modifier
283
+ .fillMaxWidth()
284
+ .height(6.dp)
285
+ .offset(x = 0.dp, y = (-6).dp)
286
+ .background(shadowBrush)
287
+ )
288
+ }
289
+ }
290
+
244
291
  sealed class BottomHeader {
245
292
  data class Title(
246
293
  val data: String = "Bottom Sheet Title",
@@ -139,10 +139,13 @@ class Navigator(
139
139
  isSurface: Boolean = false,
140
140
  barrierDismissible: Boolean = true,
141
141
  onDismiss: (() -> Unit)? = null,
142
- bottomSheetHeader: BottomHeader? = null
142
+ bottomSheetHeader: BottomHeader? = null,
143
+ draggable: Boolean = true,
144
+ useBottomInset: Boolean = true,
145
+ footerComponent: (@Composable () -> Unit)? = null
143
146
  ){
144
147
  val id = currentScreen()?.id ?: -1
145
- OverplayComponentRegistry.registerOverplay(id, content, OverplayComponentType.BOTTOM_SHEET, isSurface, barrierDismissible, onDismiss, bottomSheetHeader)
148
+ OverplayComponentRegistry.registerOverplay(id, content, OverplayComponentType.BOTTOM_SHEET, isSurface, barrierDismissible, onDismiss, bottomSheetHeader, draggable, useBottomInset, footerComponent)
146
149
  }
147
150
 
148
151
  fun showSnackBar(snackBar: SnackBar, onDismiss: (() -> Unit)? = null) {
@@ -257,6 +260,9 @@ sealed class OverplayComponentParams {
257
260
  val onDismiss: (() -> Unit)? = null,
258
261
  val barrierDismissible: Boolean = true,
259
262
  val bottomSheetHeader: BottomHeader? = null,
263
+ val draggable: Boolean = true,
264
+ val useBottomInset: Boolean = true,
265
+ val footerComponent: (@Composable () -> Unit)? = null,
260
266
  ) : OverplayComponentParams()
261
267
 
262
268
  class SnackBar : OverplayComponentParams()
@@ -288,10 +294,13 @@ object OverplayComponentRegistry {
288
294
  barrierDismissible: Boolean = true,
289
295
  onDismiss: (() -> Unit)?,
290
296
  bottomSheetHeader: BottomHeader? = null,
297
+ draggable: Boolean = true,
298
+ useBottomInset: Boolean = true,
299
+ footerComponent: (@Composable () -> Unit)? = null,
291
300
  ){
292
301
  val params = when(type){
293
302
  OverplayComponentType.MODAL -> OverplayComponentParams.Modal(onDismiss, barrierDismissible)
294
- OverplayComponentType.BOTTOM_SHEET -> OverplayComponentParams.BottomSheet(isSurface, onDismiss, barrierDismissible, bottomSheetHeader)
303
+ OverplayComponentType.BOTTOM_SHEET -> OverplayComponentParams.BottomSheet(isSurface, onDismiss, barrierDismissible, bottomSheetHeader, draggable, useBottomInset, footerComponent)
295
304
  OverplayComponentType.SNACK_BAR -> OverplayComponentParams.SnackBar()
296
305
  }
297
306
 
@@ -333,6 +342,9 @@ object OverplayComponentRegistry {
333
342
  header = params.bottomSheetHeader ?: Title(),
334
343
  isSurface = params.isSurface,
335
344
  barrierDismissible = params.barrierDismissible,
345
+ draggable = params.draggable,
346
+ useBottomInset = params.useBottomInset,
347
+ footerComponent = params.footerComponent,
336
348
  onDismiss = params.onDismiss
337
349
  )
338
350
  }
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-sp.3
21
+ version=0.163.1-sp.4
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
 
@@ -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
+ }