@momo-kits/native-kits 0.163.1-beta.3 → 0.163.1-beta.5

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 (189) hide show
  1. package/CLAUDE.md +1 -1
  2. package/ios/Application/ApplicationEnvironment.swift +11 -1
  3. package/ios/Application/Components.swift +34 -44
  4. package/ios/Application/FloatingButton.swift +10 -9
  5. package/ios/Application/HeaderRight.swift +79 -45
  6. package/ios/Application/Navigation/BottomTab/BottomTab.swift +103 -0
  7. package/ios/Application/Navigation/BottomTab/BottomTabBar.swift +215 -0
  8. package/ios/Application/Navigation/BottomTab/CurvedContainer.swift +65 -0
  9. package/ios/Application/Navigation/HeaderBackProps.swift +66 -0
  10. package/ios/Application/Navigation/HeaderTitle.swift +57 -0
  11. package/ios/Application/Navigation/HeaderUser.swift +209 -0
  12. package/ios/Application/Navigation/NavigationContainer.swift +176 -0
  13. package/ios/Application/Navigation/NavigationOptions.swift +109 -0
  14. package/ios/Application/Navigation/Navigator.swift +364 -0
  15. package/ios/Application/Navigation/Overplay/BottomSheet.swift +188 -0
  16. package/ios/Application/Navigation/Overplay/ModalScreen.swift +68 -0
  17. package/ios/Application/Navigation/Overplay/SnackBar.swift +144 -0
  18. package/ios/Application/Navigation/component/Header.swift +178 -0
  19. package/ios/Application/Navigation/component/HeaderBackground.swift +83 -0
  20. package/ios/Application/Navigation/component/HeaderColor.swift +65 -0
  21. package/ios/Application/Screen.swift +2 -1
  22. package/ios/Application/StackScreen.swift +453 -0
  23. package/ios/Avatar/Avatar.swift +193 -0
  24. package/ios/Badge/Badge.swift +11 -14
  25. package/ios/Badge/BadgeRibbon.swift +12 -21
  26. package/ios/BaselineView/BaselineView.swift +163 -0
  27. package/ios/Button/Button.swift +61 -22
  28. package/ios/Carousel/Carousel.swift +181 -0
  29. package/ios/Checkbox/Checkbox.swift +14 -13
  30. package/ios/Chip/Chip.swift +23 -9
  31. package/ios/Collapse/Collapse.swift +182 -0
  32. package/ios/Colors+Radius+Spacing/Colors.swift +12 -12
  33. package/ios/Colors+Radius+Spacing/Theme.swift +295 -0
  34. package/ios/DateTimePicker/DateTimePicker.swift +210 -0
  35. package/ios/DateTimePicker/DateTimePickerTypes.swift +55 -0
  36. package/ios/DateTimePicker/DateTimePickerUtils.swift +167 -0
  37. package/ios/DateTimePicker/WheelPicker.swift +161 -0
  38. package/ios/Divider/Divider.swift +16 -0
  39. package/ios/Extensions/Color++.swift +32 -0
  40. package/ios/Icon/Icon.swift +6 -1
  41. package/ios/IconButton/IconButton.swift +112 -0
  42. package/ios/Input/Input.swift +41 -25
  43. package/ios/Input/InputPhoneNumber.swift +20 -19
  44. package/ios/Input/InputSearch.swift +100 -47
  45. package/ios/Input/InputTextArea.swift +66 -40
  46. package/ios/InputDropDown/InputDropDown.swift +198 -0
  47. package/ios/InputMoney/InputMoney.swift +335 -0
  48. package/ios/InputOTP/InputOTP.swift +210 -0
  49. package/ios/Loader/Loader.swift +113 -0
  50. package/ios/MoMoUIKits.podspec +5 -0
  51. package/ios/Pagination/PaginationDot.swift +61 -0
  52. package/ios/Pagination/PaginationNumber.swift +35 -0
  53. package/ios/Pagination/PaginationScroll.swift +101 -0
  54. package/ios/Pagination/PaginationWhiteDot.swift +37 -0
  55. package/ios/Popup/PopupDisplay.swift +8 -16
  56. package/ios/Popup/PopupInput.swift +2 -8
  57. package/ios/Popup/PopupNotify.swift +219 -0
  58. package/ios/ProgressInfo/ProgressInfo.swift +294 -0
  59. package/ios/Radio/Radio.swift +70 -0
  60. package/ios/Rating/Rating.swift +82 -0
  61. package/ios/Resources/MoMoUIKitsColors.xcassets/AccentColor.colorset/Contents.json +11 -0
  62. package/ios/Resources/MoMoUIKitsColors.xcassets/Background.colorset/Contents.json +38 -0
  63. package/ios/Resources/MoMoUIKitsColors.xcassets/Border.colorset/Contents.json +38 -0
  64. package/ios/Resources/MoMoUIKitsColors.xcassets/Card.colorset/Contents.json +38 -0
  65. package/ios/Resources/MoMoUIKitsColors.xcassets/Contents.json +6 -0
  66. package/ios/Resources/MoMoUIKitsColors.xcassets/Error.colorset/Contents.json +38 -0
  67. package/ios/Resources/MoMoUIKitsColors.xcassets/Primary.colorset/Contents.json +38 -0
  68. package/ios/Resources/MoMoUIKitsColors.xcassets/PrimaryDark.colorset/Contents.json +38 -0
  69. package/ios/Resources/MoMoUIKitsColors.xcassets/PrimaryLight.colorset/Contents.json +38 -0
  70. package/ios/Resources/MoMoUIKitsColors.xcassets/Secondary.colorset/Contents.json +38 -0
  71. package/ios/Resources/MoMoUIKitsColors.xcassets/Success.colorset/Contents.json +38 -0
  72. package/ios/Resources/MoMoUIKitsColors.xcassets/Text.colorset/Contents.json +38 -0
  73. package/ios/Resources/MoMoUIKitsColors.xcassets/TextSecondary.colorset/Contents.json +38 -0
  74. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/Contents.json +9 -0
  75. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-default.colorset/Contents.json +38 -0
  76. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-disable.colorset/Contents.json +38 -0
  77. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-pressed.colorset/Contents.json +38 -0
  78. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-selected.colorset/Contents.json +38 -0
  79. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-surface.colorset/Contents.json +38 -0
  80. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-tonal.colorset/Contents.json +38 -0
  81. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/border-default.colorset/Contents.json +38 -0
  82. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/border-disable.colorset/Contents.json +38 -0
  83. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/gradient.colorset/Contents.json +38 -0
  84. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/primary.colorset/Contents.json +38 -0
  85. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/secondary.colorset/Contents.json +38 -0
  86. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-container.colorset/Contents.json +38 -0
  87. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-primary.colorset/Contents.json +38 -0
  88. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-secondary.colorset/Contents.json +38 -0
  89. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-default.colorset/Contents.json +38 -0
  90. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-disable.colorset/Contents.json +38 -0
  91. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-hint.colorset/Contents.json +38 -0
  92. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-secondary.colorset/Contents.json +38 -0
  93. package/ios/Resources/MoMoUIKitsColors.xcassets/Warning.colorset/Contents.json +38 -0
  94. package/ios/Skeleton/Skeleton.swift +99 -0
  95. package/ios/Slider/Slider.swift +237 -0
  96. package/ios/Stepper/Stepper.swift +220 -0
  97. package/ios/Steps/Steps.swift +451 -0
  98. package/ios/SuggestAction/SuggestAction.swift +97 -0
  99. package/ios/Swipeable/SwipeCell.swift +185 -0
  100. package/ios/Swipeable/SwipeCellModel.swift +21 -0
  101. package/ios/TabView/TabView.swift +530 -0
  102. package/ios/Tag/Tag.swift +98 -0
  103. package/ios/Title/Title.swift +269 -0
  104. package/ios/Tooltip/Tooltip.swift +482 -0
  105. package/ios/Typography/FontScaleStore.swift +19 -0
  106. package/ios/Typography/Text.swift +86 -17
  107. package/ios/Typography/Typography.swift +12 -14
  108. package/ios/Uploader/Uploader.swift +183 -0
  109. package/ios/native-kits.podspec +61 -8
  110. package/ios-demo/MoMoUIKitsDemo.podspec +20 -0
  111. package/ios-demo/Resources/dot_loading.json +558 -0
  112. package/ios-demo/Resources/icon.json +4052 -0
  113. package/ios-demo/Resources/lottie_circle_loader.json +1 -0
  114. package/ios-demo/Sources/MoMoUIKitsDemo/DemoScaffold.swift +48 -0
  115. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsBadgeDemoView.swift +25 -0
  116. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsButtonDemoView.swift +26 -0
  117. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsCheckboxDemoView.swift +19 -0
  118. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsChipDemoView.swift +33 -0
  119. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsInputDemoView.swift +48 -0
  120. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsPopupDemoView.swift +21 -0
  121. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsRadioDemoView.swift +17 -0
  122. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsTypographyDemoView.swift +28 -0
  123. package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoContext.swift +31 -0
  124. package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoFactory.swift +24 -0
  125. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AnimatedHeaderDemo.swift +110 -0
  126. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AvatarDemo.swift +93 -0
  127. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BadgeDemo.swift +269 -0
  128. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BaselineView.swift +117 -0
  129. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BottomTabDemo.swift +89 -0
  130. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ButtonDemo.swift +100 -0
  131. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CarouselDemo.swift +126 -0
  132. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CheckboxDemo.swift +63 -0
  133. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ChipDemo.swift +20 -0
  134. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CollapseDemo.swift +93 -0
  135. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +107 -0
  136. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DateTimePickerDemo.swift +124 -0
  137. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DividerDemo.swift +14 -0
  138. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/HeaderUserDemo.swift +49 -0
  139. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +278 -0
  140. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconButtonDemo.swift +58 -0
  141. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ImageDemo.swift +78 -0
  142. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InformationDemo.swift +138 -0
  143. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDemo.swift +111 -0
  144. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDropDownDemo.swift +136 -0
  145. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputMoneyDemo.swift +107 -0
  146. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputOTPDemo.swift +73 -0
  147. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputPhoneNumberDemo.swift +100 -0
  148. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputSearchDemo.swift +32 -0
  149. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputTextAreaDemo.swift +57 -0
  150. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LoaderDemo.swift +70 -0
  151. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +227 -0
  152. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PaginationDemo.swift +72 -0
  153. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +175 -0
  154. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +121 -0
  155. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +76 -0
  156. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ProgressInfoDemo.swift +89 -0
  157. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RadioDemo.swift +32 -0
  158. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RatingDemo.swift +56 -0
  159. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SkeletonDemo.swift +32 -0
  160. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SliderDemo.swift +150 -0
  161. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SnackBarDemo.swift +138 -0
  162. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepperDemo.swift +103 -0
  163. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepsDemo.swift +94 -0
  164. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SuggestActionDemo.swift +79 -0
  165. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwipeDemo.swift +152 -0
  166. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwitchDemo.swift +33 -0
  167. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TabViewDemo.swift +190 -0
  168. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TagDemo.swift +34 -0
  169. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ThemeDemo.swift +182 -0
  170. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TitleDemo.swift +109 -0
  171. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TooltipDemo.swift +187 -0
  172. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TypographyDemo.swift +87 -0
  173. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/UploaderDemo.swift +131 -0
  174. package/ios-demo/Sources/MoMoUIKitsDemo/SwiftUIDemoBrowserView.swift +51 -0
  175. package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoModels.swift +15 -0
  176. package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoRegistry.swift +73 -0
  177. package/package.json +1 -1
  178. package/publish.sh +50 -0
  179. package/scripts/gen-ios-color.mjs +136 -0
  180. package/example/ios/Example.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
  181. package/example/ios/Example.xcworkspace/xcuserdata/huynhdung.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  182. package/example/ios/Example.xcworkspace/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +0 -5
  183. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/MoMoUIKits.xcscheme +0 -58
  184. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/Pods-Example.xcscheme +0 -58
  185. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SDWebImage.xcscheme +0 -58
  186. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SDWebImageSwiftUI.xcscheme +0 -58
  187. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SkeletonUI.xcscheme +0 -58
  188. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +0 -46
  189. package/ios/Application/FontScaleStore.swift +0 -59
package/CLAUDE.md CHANGED
@@ -40,7 +40,7 @@ Publishing is automated (`publish.sh` + GitLab CI on `[ci build]` / `main`). Do
40
40
  ## Architecture essentials
41
41
 
42
42
  - **Entry point:** wrap content in `NavigationContainer(...)` from `navigation/`. It builds the `Navigator`, takes a host-supplied `Localize` (falls back to an empty one), and provides the CompositionLocals below. **Do not use** `ApplicationContainer` (`application/NavigationContainer.kt`) — deprecated and does **not** wire `LocalLocalize` / `LocalNavigator` / `LocalMaxApi`.
43
- - **Configuration flows via CompositionLocals**, not params. Public ones: `AppTheme`, `AppStatusBar`, `AppNavigationBar` (`const/Theme.kt`); `ApplicationContext`, `AppConfig`, `AppLanguage`, `LocalLocalize`, `ScaleSizeMaxRate` (`application/`); `LocalNavigator`, `LocalMaxApi` (`navigation/`). Read with `X.current`.
43
+ - **Configuration flows via CompositionLocals**, not params. Public ones: `AppTheme`, `AppStatusBar`, `AppNavigationBar` (`const/Theme.kt`); `ApplicationContext`, `AppConfig`, `AppLanguage`, `LocalLocalize` (`application/`); `LocalNavigator`, `LocalMaxApi` (`navigation/`). Read with `X.current`.
44
44
  - **Platform code** goes through `expect`/`actual` in `platform/` — never reference Android/iOS APIs from `commonMain` directly.
45
45
  - **Native bridge:** `IMaxApi` (`vn.momo.maxapi`, exposed as `LocalMaxApi`) is the host-app bridge (dismiss, tracking, device info, language).
46
46
  - Navigation uses a runtime `DynamicScreenRegistry`; overlays (modal/bottom-sheet/snackbar) go through `Navigator` + `OverplayComponentRegistry`.
@@ -46,6 +46,16 @@ public class ApplicationEnvironment: ObservableObject {
46
46
  self.applicationContext = applicationContext
47
47
  self.composeApi = composeApi
48
48
  self.config = config
49
- FontScaleStore.shared.bind(composeApi)
49
+ }
50
+ }
51
+
52
+ private struct ApplicationEnvironmentKey: EnvironmentKey {
53
+ static let defaultValue = ApplicationEnvironment()
54
+ }
55
+
56
+ public extension EnvironmentValues {
57
+ var applicationEnvironment: ApplicationEnvironment {
58
+ get { self[ApplicationEnvironmentKey.self] }
59
+ set { self[ApplicationEnvironmentKey.self] = newValue }
50
60
  }
51
61
  }
@@ -83,9 +83,8 @@ public struct HeaderBackground: View {
83
83
  self.headerTransparent = headerTransparent
84
84
  self.fullScreenContent = fullScreenContent
85
85
  }
86
-
86
+
87
87
  public var body: some View {
88
-
89
88
  let statusBarHeight = safeAreaTopInset()
90
89
  let opacity = max(0, 1 - scrollState / 200)
91
90
  let height = fullScreenContent ? 0 : statusBarHeight + 52
@@ -101,21 +100,6 @@ public struct HeaderBackground: View {
101
100
  .frame(height: height)
102
101
  .shadow(color: Colors.black20.opacity(0.2), radius: 10, x: 0, y: -2)
103
102
  .background(backgroundColor)
104
- .overlay(
105
- headerTransparent ? AnyView(EmptyView()) :
106
- AnyView(
107
- Rectangle()
108
- .fill(LinearGradient(
109
- gradient: Gradient(colors: [
110
- Color(red: 1, green: 0.8, blue: 0.87),
111
- Color(red: 1, green: 0.8, blue: 0.87).opacity(0.5)
112
- ]),
113
- startPoint: .top,
114
- endPoint: .bottom))
115
- .opacity(opacity)
116
- .frame(height: height))
117
- )
118
-
119
103
  if !headerTransparent {
120
104
  Rectangle()
121
105
  .fill(Color.black.opacity(0.1))
@@ -148,12 +132,26 @@ public struct HeaderBackground: View {
148
132
  }
149
133
  }
150
134
 
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
+
151
146
  // MARK: - Header View
152
147
 
153
148
  public struct Header: View {
154
149
  var headerType: HeaderType = .default
155
150
  var titlePosition: TitlePosition
156
151
  var title: String
152
+ /// Optional rich title (mirrors Compose's `HeaderTitle`); `.user` renders an
153
+ /// avatar group instead of `title`. Defaults to nil → plain `title` text.
154
+ var headerTitle: HeaderTitle?
157
155
  var headerRight: (()->any View)? = {HeaderRight()}
158
156
  var goBack: (() -> Void)?
159
157
  var opacity: CGFloat = 1
@@ -161,22 +159,27 @@ public struct Header: View {
161
159
  var scrollState: CGFloat = 0
162
160
  var inputSearchProps: InputSearchProps?
163
161
  var tintColor: Color?
162
+ /// Title opacity; used by the animated search header to fade the title out on scroll.
163
+ var titleOpacity: CGFloat = 1
164
164
 
165
165
  public init(
166
166
  headerType: HeaderType = .default,
167
167
  titlePosition: TitlePosition,
168
168
  title: String,
169
+ headerTitle: HeaderTitle? = nil,
169
170
  headerRight: (()->any View)? = {HeaderRight()},
170
171
  goBack: (() -> Void)? = nil,
171
172
  opacity: CGFloat = 1,
172
173
  animatedHeader: AnimatedHeader? = nil,
173
174
  scrollState: CGFloat = 0,
174
175
  inputSearchProps: InputSearchProps? = nil,
175
- tintColor: Color? = nil
176
+ tintColor: Color? = nil,
177
+ titleOpacity: CGFloat = 1
176
178
  ) {
177
179
  self.headerType = headerType
178
180
  self.titlePosition = titlePosition
179
181
  self.title = title
182
+ self.headerTitle = headerTitle
180
183
  self.headerRight = headerRight
181
184
  self.goBack = goBack
182
185
  self.opacity = opacity
@@ -184,10 +187,11 @@ public struct Header: View {
184
187
  self.scrollState = scrollState
185
188
  self.inputSearchProps = inputSearchProps
186
189
  self.tintColor = tintColor
190
+ self.titleOpacity = titleOpacity
187
191
  }
188
-
192
+
189
193
  public var body: some View {
190
-
194
+
191
195
  let backgroundButtonColor: Color = tintColor == Colors.black01 ? Colors.black20.opacity(0.6) : Colors.black01.opacity(0.6)
192
196
  let borderColor: Color = tintColor == Colors.black01 ? Colors.black01.opacity(0.2) : Colors.black20.opacity(0.2)
193
197
 
@@ -212,6 +216,9 @@ public struct Header: View {
212
216
 
213
217
  if let headerRight = headerRight {
214
218
  AnyView(headerRight())
219
+ .background(GeometryReader { geo in
220
+ Color.clear.preference(key: HeaderRightWidthKey.self, value: geo.size.width)
221
+ })
215
222
  }
216
223
  }
217
224
  .padding(.horizontal, 12)
@@ -223,33 +230,16 @@ public struct Header: View {
223
230
  }
224
231
 
225
232
  if let inputProps = inputSearchProps {
226
- InputSearch(
227
- text: inputProps.$text,
228
- buttonText: inputProps.buttonText,
229
- showButtonText: inputProps.showButtonText,
230
- showBorder: inputProps.showBorder,
231
- placeholder: inputProps.placeholder,
232
- onChangeText: inputProps.onChangeText,
233
- onPressButtonText: inputProps.onPressButtonText,
234
- error: inputProps.error,
235
- disabled: inputProps.disabled,
236
- icon: inputProps.icon,
237
- iconColor: inputProps.iconColor,
238
- onRightIconPressed: inputProps.onRightIconPressed,
239
- onFocus: inputProps.onFocus,
240
- onBlur: inputProps.onBlur,
241
- loading: inputProps.loading,
242
- fontWeight: inputProps.fontWeight,
243
- keyboardType: inputProps.keyboardType
244
- )
233
+ InputSearch(props: inputProps)
234
+ } else if case let .user(userData)? = headerTitle {
235
+ if #available(iOS 16.0, *) {
236
+ HeaderUserView(userData)
237
+ }
245
238
  } else {
246
- Text(title)
247
- .font(.system(size: 17, weight: .bold))
248
- .ignoreBoldTextSetting()
249
- .foregroundColor(tintColor ?? Colors.black17)
239
+ MomoText(headerTitle?.defaultTitle ?? title, typography: .headerDefaultBold, color: tintColor ?? Colors.black17, maxLines: 1)
240
+ .opacity(titleOpacity)
250
241
  .frame(maxWidth: titlePosition == .center ? UIScreen.main.bounds.width - 252 : nil)
251
242
  .frame(maxWidth: titlePosition == .center ? .infinity : UIScreen.main.bounds.width - 172, alignment: titlePosition == .center ? .center : .leading)
252
- .lineLimit(1)
253
243
  }
254
244
 
255
245
  Spacer()
@@ -3,11 +3,16 @@ import Combine
3
3
 
4
4
  public enum FABSize {
5
5
  case small
6
+ case `default`
7
+ /// Backward-compatible alias for older iOS call sites. Compose names this
8
+ /// size `DEFAULT`.
6
9
  case large
7
-
8
10
  }
9
11
 
10
12
  public enum FABPosition {
13
+ case end
14
+ /// Backward-compatible alias for older iOS call sites. Compose names this
15
+ /// position `END`.
11
16
  case right
12
17
  case center
13
18
  }
@@ -31,10 +36,10 @@ public struct FabProps {
31
36
  label: String? = nil,
32
37
  onClick: (() -> Void)? = nil,
33
38
  containerColor: Color? = Colors.primary,
34
- size: FABSize? = .small,
39
+ size: FABSize? = .default,
35
40
  iconSize: CGFloat? = nil,
36
41
  scrollOffset: CGFloat? = nil,
37
- position: FABPosition? = .right,
42
+ position: FABPosition? = .end,
38
43
  bottomPadding: CGFloat? = nil,
39
44
  renderComponent: (() -> AnyView)? = nil
40
45
  ) {
@@ -65,7 +70,7 @@ public struct FloatingButton: View {
65
70
  private var keyboardOffset: CGFloat
66
71
  private var scrollOffset: CGFloat
67
72
  private var bottomPadding: CGFloat { props.bottomPadding ?? 12 + keyboardOffset}
68
- private var position: FABPosition? { props.position ?? .right }
73
+ private var position: FABPosition? { props.position ?? .end }
69
74
  private var containerColor: Color? { props.containerColor ?? Colors.primary }
70
75
  private var iconColor: Color? { props.iconColor ?? .white }
71
76
  private var label: String? { props.label }
@@ -152,11 +157,7 @@ public struct FloatingButton: View {
152
157
  if let label = label {
153
158
  if showText {
154
159
  Spacer().frame(width: 8)
155
- Text(label)
156
- .foregroundColor(.white)
157
- .font(.system(size: scaleSize(16), weight: .bold))
158
- .ignoreBoldTextSetting()
159
- .lineLimit(1)
160
+ MomoText(label, typography: .actionDefaultBold, color: .white, maxLines: 1)
160
161
  .background(
161
162
  GeometryReader { geo in
162
163
  Color.clear.onAppear {
@@ -101,69 +101,98 @@ 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
- @EnvironmentObject private var environment: ApplicationEnvironment
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
+ }
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
 
@@ -0,0 +1,103 @@
1
+ //
2
+ // BottomTab.swift
3
+ // MoMoUIKits
4
+ //
5
+ // SwiftUI mirror of Compose's `BottomTab` (navigation/bottomtab/BottomTab.kt).
6
+ // Hosts a set of tabbed screens, each wrapped in `StackScreen` chrome built
7
+ // from that tab's `NavigationOptions`, with a `BottomTabBar` pinned to the
8
+ // bottom. Switching tabs cross-fades + scales the content (parity with Compose).
9
+ //
10
+
11
+ import SwiftUI
12
+
13
+ @available(iOS 16.0, *)
14
+ public struct BottomTab: View {
15
+ private let items: [BottomTabItem]
16
+ private let floatingButton: BottomTabFloatingButton?
17
+
18
+ @EnvironmentObject private var navigator: Navigator
19
+ @State private var selectedIndex: Int = 0
20
+
21
+ public init(items: [BottomTabItem], floatingButton: BottomTabFloatingButton? = nil) {
22
+ self.items = items
23
+ self.floatingButton = floatingButton
24
+ }
25
+
26
+ private var bottomInset: CGFloat { safeAreaBottomInset() }
27
+ private var reservedBottom: CGFloat { BOTTOM_TAB_BAR_HEIGHT + bottomInset + Spacing.S }
28
+
29
+ public var body: some View {
30
+ ZStack(alignment: .bottom) {
31
+ // Tab content area, inset above the bar.
32
+ ZStack {
33
+ ForEach(items.indices, id: \.self) { index in
34
+ if index == selectedIndex {
35
+ tabScreen(for: index)
36
+ .transition(.scale(scale: 0.97).combined(with: .opacity))
37
+ }
38
+ }
39
+ }
40
+ .padding(.bottom, reservedBottom)
41
+ .animation(.easeInOut(duration: 0.2), value: selectedIndex)
42
+
43
+ // Tab bar + home-indicator filler.
44
+ VStack(spacing: 0) {
45
+ BottomTabBar(
46
+ items: items,
47
+ floatingButton: floatingButton,
48
+ selectedIndex: selectedIndex,
49
+ onTabSelected: { index in
50
+ if index != selectedIndex { selectedIndex = index }
51
+ }
52
+ )
53
+ Colors.black01
54
+ .frame(height: bottomInset + Spacing.S)
55
+ }
56
+ }
57
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
58
+ .ignoresSafeArea(edges: .bottom)
59
+ }
60
+
61
+ @ViewBuilder
62
+ private func tabScreen(for index: Int) -> some View {
63
+ let item = items[index]
64
+ let options = item.options ?? NavigationOptions()
65
+ StackScreen(
66
+ backgroundColor: options.backgroundColor,
67
+ headerTransparent: options.headerTransparent,
68
+ fullScreenContent: options.fullScreenContent,
69
+ tintColor: options.tintColor ?? Colors.black17,
70
+ isBack: !options.hiddenBack,
71
+ headerType: options.headerType,
72
+ useAnimated: options.useAnimated,
73
+ title: options.title,
74
+ headerTitle: options.headerTitle,
75
+ titlePosition: options.titlePosition,
76
+ // Tab back: non-first tab returns to the first tab, first tab
77
+ // delegates to the navigator (mirrors Compose's onBackHandler).
78
+ goBack: options.hiddenBack ? nil : {
79
+ if index != 0 { selectedIndex = 0 } else { navigator.onBackSafe() }
80
+ },
81
+ scrollable: options.scrollable,
82
+ scrollToTopCallback: options.scrollToTopCallback,
83
+ useAvoidKeyboard: options.useAvoidKeyboard,
84
+ keyboardShouldPersistTaps: options.keyboardShouldPersistTaps,
85
+ footer: options.footer,
86
+ headerRight: options.resolvedHeaderRight,
87
+ animatedHeader: options.animatedHeader,
88
+ layoutOffset: options.layoutOffset,
89
+ inputSearchProps: options.inputSearchProps,
90
+ fabProps: options.fabProps,
91
+ content: { item.screen() }
92
+ )
93
+ }
94
+ }
95
+
96
+ /// Bottom safe-area inset (home indicator).
97
+ @available(iOS 16.0, *)
98
+ private func safeAreaBottomInset() -> CGFloat {
99
+ let bottomInset = UIApplication.shared.connectedScenes
100
+ .compactMap { ($0 as? UIWindowScene)?.keyWindow?.safeAreaInsets.bottom }
101
+ .first ?? 0
102
+ return min(bottomInset, 21)
103
+ }