@momo-kits/native-kits 0.163.1-beta.3-debug → 0.163.1-beta.5-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 (215) hide show
  1. package/CLAUDE.md +1 -1
  2. package/compose/build.gradle.kts +2 -1
  3. package/compose/build.gradle.kts.backup +1 -0
  4. package/compose/compose.podspec +1 -1
  5. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +7 -0
  6. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +8 -10
  7. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +3 -0
  8. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +56 -0
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +11 -9
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +40 -55
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +69 -26
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +3 -1
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +21 -0
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +3 -6
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +22 -0
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +28 -21
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +4 -0
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +112 -115
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +11 -6
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +3 -4
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +3 -1
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +2 -1
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +9 -0
  24. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +66 -0
  25. package/gradle/libs.versions.toml +2 -0
  26. package/gradle.properties +1 -1
  27. package/ios/Application/ApplicationEnvironment.swift +11 -1
  28. package/ios/Application/Components.swift +34 -44
  29. package/ios/Application/FloatingButton.swift +10 -9
  30. package/ios/Application/HeaderRight.swift +79 -45
  31. package/ios/Application/Navigation/BottomTab/BottomTab.swift +103 -0
  32. package/ios/Application/Navigation/BottomTab/BottomTabBar.swift +215 -0
  33. package/ios/Application/Navigation/BottomTab/CurvedContainer.swift +65 -0
  34. package/ios/Application/Navigation/HeaderBackProps.swift +66 -0
  35. package/ios/Application/Navigation/HeaderTitle.swift +57 -0
  36. package/ios/Application/Navigation/HeaderUser.swift +209 -0
  37. package/ios/Application/Navigation/NavigationContainer.swift +176 -0
  38. package/ios/Application/Navigation/NavigationOptions.swift +109 -0
  39. package/ios/Application/Navigation/Navigator.swift +364 -0
  40. package/ios/Application/Navigation/Overplay/BottomSheet.swift +188 -0
  41. package/ios/Application/Navigation/Overplay/ModalScreen.swift +68 -0
  42. package/ios/Application/Navigation/Overplay/SnackBar.swift +144 -0
  43. package/ios/Application/Navigation/component/Header.swift +178 -0
  44. package/ios/Application/Navigation/component/HeaderBackground.swift +83 -0
  45. package/ios/Application/Navigation/component/HeaderColor.swift +65 -0
  46. package/ios/Application/Screen.swift +2 -1
  47. package/ios/Application/StackScreen.swift +453 -0
  48. package/ios/Avatar/Avatar.swift +193 -0
  49. package/ios/Badge/Badge.swift +11 -14
  50. package/ios/Badge/BadgeRibbon.swift +12 -21
  51. package/ios/BaselineView/BaselineView.swift +163 -0
  52. package/ios/Button/Button.swift +61 -22
  53. package/ios/Carousel/Carousel.swift +181 -0
  54. package/ios/Checkbox/Checkbox.swift +14 -13
  55. package/ios/Chip/Chip.swift +23 -9
  56. package/ios/Collapse/Collapse.swift +182 -0
  57. package/ios/Colors+Radius+Spacing/Colors.swift +12 -12
  58. package/ios/Colors+Radius+Spacing/Theme.swift +295 -0
  59. package/ios/DateTimePicker/DateTimePicker.swift +210 -0
  60. package/ios/DateTimePicker/DateTimePickerTypes.swift +55 -0
  61. package/ios/DateTimePicker/DateTimePickerUtils.swift +167 -0
  62. package/ios/DateTimePicker/WheelPicker.swift +161 -0
  63. package/ios/Divider/Divider.swift +16 -0
  64. package/ios/Extensions/Color++.swift +32 -0
  65. package/ios/Icon/Icon.swift +6 -1
  66. package/ios/IconButton/IconButton.swift +112 -0
  67. package/ios/Input/Input.swift +41 -25
  68. package/ios/Input/InputPhoneNumber.swift +20 -19
  69. package/ios/Input/InputSearch.swift +100 -47
  70. package/ios/Input/InputTextArea.swift +66 -40
  71. package/ios/InputDropDown/InputDropDown.swift +198 -0
  72. package/ios/InputMoney/InputMoney.swift +335 -0
  73. package/ios/InputOTP/InputOTP.swift +210 -0
  74. package/ios/Loader/Loader.swift +113 -0
  75. package/ios/MoMoUIKits.podspec +5 -0
  76. package/ios/Pagination/PaginationDot.swift +61 -0
  77. package/ios/Pagination/PaginationNumber.swift +35 -0
  78. package/ios/Pagination/PaginationScroll.swift +101 -0
  79. package/ios/Pagination/PaginationWhiteDot.swift +37 -0
  80. package/ios/Popup/PopupDisplay.swift +8 -16
  81. package/ios/Popup/PopupInput.swift +2 -8
  82. package/ios/Popup/PopupNotify.swift +219 -0
  83. package/ios/ProgressInfo/ProgressInfo.swift +294 -0
  84. package/ios/Radio/Radio.swift +70 -0
  85. package/ios/Rating/Rating.swift +82 -0
  86. package/ios/Resources/MoMoUIKitsColors.xcassets/AccentColor.colorset/Contents.json +11 -0
  87. package/ios/Resources/MoMoUIKitsColors.xcassets/Background.colorset/Contents.json +38 -0
  88. package/ios/Resources/MoMoUIKitsColors.xcassets/Border.colorset/Contents.json +38 -0
  89. package/ios/Resources/MoMoUIKitsColors.xcassets/Card.colorset/Contents.json +38 -0
  90. package/ios/Resources/MoMoUIKitsColors.xcassets/Contents.json +6 -0
  91. package/ios/Resources/MoMoUIKitsColors.xcassets/Error.colorset/Contents.json +38 -0
  92. package/ios/Resources/MoMoUIKitsColors.xcassets/Primary.colorset/Contents.json +38 -0
  93. package/ios/Resources/MoMoUIKitsColors.xcassets/PrimaryDark.colorset/Contents.json +38 -0
  94. package/ios/Resources/MoMoUIKitsColors.xcassets/PrimaryLight.colorset/Contents.json +38 -0
  95. package/ios/Resources/MoMoUIKitsColors.xcassets/Secondary.colorset/Contents.json +38 -0
  96. package/ios/Resources/MoMoUIKitsColors.xcassets/Success.colorset/Contents.json +38 -0
  97. package/ios/Resources/MoMoUIKitsColors.xcassets/Text.colorset/Contents.json +38 -0
  98. package/ios/Resources/MoMoUIKitsColors.xcassets/TextSecondary.colorset/Contents.json +38 -0
  99. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/Contents.json +9 -0
  100. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-default.colorset/Contents.json +38 -0
  101. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-disable.colorset/Contents.json +38 -0
  102. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-pressed.colorset/Contents.json +38 -0
  103. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-selected.colorset/Contents.json +38 -0
  104. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-surface.colorset/Contents.json +38 -0
  105. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-tonal.colorset/Contents.json +38 -0
  106. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/border-default.colorset/Contents.json +38 -0
  107. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/border-disable.colorset/Contents.json +38 -0
  108. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/gradient.colorset/Contents.json +38 -0
  109. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/primary.colorset/Contents.json +38 -0
  110. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/secondary.colorset/Contents.json +38 -0
  111. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-container.colorset/Contents.json +38 -0
  112. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-primary.colorset/Contents.json +38 -0
  113. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-secondary.colorset/Contents.json +38 -0
  114. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-default.colorset/Contents.json +38 -0
  115. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-disable.colorset/Contents.json +38 -0
  116. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-hint.colorset/Contents.json +38 -0
  117. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-secondary.colorset/Contents.json +38 -0
  118. package/ios/Resources/MoMoUIKitsColors.xcassets/Warning.colorset/Contents.json +38 -0
  119. package/ios/Skeleton/Skeleton.swift +99 -0
  120. package/ios/Slider/Slider.swift +237 -0
  121. package/ios/Stepper/Stepper.swift +220 -0
  122. package/ios/Steps/Steps.swift +451 -0
  123. package/ios/SuggestAction/SuggestAction.swift +97 -0
  124. package/ios/Swipeable/SwipeCell.swift +185 -0
  125. package/ios/Swipeable/SwipeCellModel.swift +21 -0
  126. package/ios/TabView/TabView.swift +530 -0
  127. package/ios/Tag/Tag.swift +98 -0
  128. package/ios/Title/Title.swift +269 -0
  129. package/ios/Tooltip/Tooltip.swift +482 -0
  130. package/ios/Typography/FontScaleStore.swift +19 -0
  131. package/ios/Typography/Text.swift +86 -17
  132. package/ios/Typography/Typography.swift +12 -14
  133. package/ios/Uploader/Uploader.swift +183 -0
  134. package/ios/native-kits.podspec +61 -8
  135. package/ios-demo/MoMoUIKitsDemo.podspec +20 -0
  136. package/ios-demo/README.md +120 -0
  137. package/ios-demo/Resources/dot_loading.json +558 -0
  138. package/ios-demo/Resources/icon.json +4052 -0
  139. package/ios-demo/Resources/lottie_circle_loader.json +1 -0
  140. package/ios-demo/Sources/MoMoUIKitsDemo/DemoScaffold.swift +48 -0
  141. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsBadgeDemoView.swift +25 -0
  142. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsButtonDemoView.swift +26 -0
  143. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsCheckboxDemoView.swift +19 -0
  144. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsChipDemoView.swift +33 -0
  145. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsInputDemoView.swift +48 -0
  146. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsPopupDemoView.swift +21 -0
  147. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsRadioDemoView.swift +17 -0
  148. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsTypographyDemoView.swift +28 -0
  149. package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoContext.swift +31 -0
  150. package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoFactory.swift +24 -0
  151. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AnimatedHeaderDemo.swift +110 -0
  152. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AvatarDemo.swift +93 -0
  153. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BadgeDemo.swift +269 -0
  154. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BaselineView.swift +117 -0
  155. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BottomTabDemo.swift +89 -0
  156. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ButtonDemo.swift +100 -0
  157. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CarouselDemo.swift +126 -0
  158. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CheckboxDemo.swift +63 -0
  159. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ChipDemo.swift +20 -0
  160. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CollapseDemo.swift +93 -0
  161. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +107 -0
  162. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DateTimePickerDemo.swift +124 -0
  163. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DividerDemo.swift +14 -0
  164. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/HeaderUserDemo.swift +49 -0
  165. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +278 -0
  166. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconButtonDemo.swift +58 -0
  167. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ImageDemo.swift +78 -0
  168. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InformationDemo.swift +138 -0
  169. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDemo.swift +111 -0
  170. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDropDownDemo.swift +136 -0
  171. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputMoneyDemo.swift +107 -0
  172. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputOTPDemo.swift +73 -0
  173. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputPhoneNumberDemo.swift +100 -0
  174. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputSearchDemo.swift +32 -0
  175. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputTextAreaDemo.swift +57 -0
  176. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LoaderDemo.swift +70 -0
  177. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +227 -0
  178. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PaginationDemo.swift +72 -0
  179. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +175 -0
  180. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +121 -0
  181. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +76 -0
  182. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ProgressInfoDemo.swift +89 -0
  183. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RadioDemo.swift +32 -0
  184. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RatingDemo.swift +56 -0
  185. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SkeletonDemo.swift +32 -0
  186. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SliderDemo.swift +150 -0
  187. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SnackBarDemo.swift +138 -0
  188. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepperDemo.swift +103 -0
  189. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepsDemo.swift +94 -0
  190. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SuggestActionDemo.swift +79 -0
  191. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwipeDemo.swift +152 -0
  192. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwitchDemo.swift +33 -0
  193. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TabViewDemo.swift +190 -0
  194. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TagDemo.swift +34 -0
  195. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ThemeDemo.swift +182 -0
  196. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TitleDemo.swift +109 -0
  197. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TooltipDemo.swift +187 -0
  198. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TypographyDemo.swift +87 -0
  199. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/UploaderDemo.swift +131 -0
  200. package/ios-demo/Sources/MoMoUIKitsDemo/SwiftUIDemoBrowserView.swift +51 -0
  201. package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoModels.swift +15 -0
  202. package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoRegistry.swift +73 -0
  203. package/package.json +1 -1
  204. package/publish.sh +50 -0
  205. package/scripts/gen-ios-color.mjs +136 -0
  206. package/example/ios/Example.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
  207. package/example/ios/Example.xcworkspace/xcuserdata/huynhdung.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  208. package/example/ios/Example.xcworkspace/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +0 -5
  209. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/MoMoUIKits.xcscheme +0 -58
  210. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/Pods-Example.xcscheme +0 -58
  211. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SDWebImage.xcscheme +0 -58
  212. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SDWebImageSwiftUI.xcscheme +0 -58
  213. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SkeletonUI.xcscheme +0 -58
  214. package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +0 -46
  215. package/ios/Application/FontScaleStore.swift +0 -59
@@ -0,0 +1,65 @@
1
+ //
2
+ // HeaderColor.swift
3
+ // MoMoUIKits
4
+ //
5
+ // SwiftUI mirror of Compose's `HeaderColor` + `getHeaderColor`
6
+ // (navigation/component/Header.kt). Resolves the back-button / control colors
7
+ // for the header based on whether it sits over an animated (image) header and
8
+ // how far the screen has scrolled.
9
+ //
10
+
11
+ import SwiftUI
12
+
13
+ /// Header band height, mirrors Compose's `HEADER_HEIGHT`.
14
+ public let HEADER_HEIGHT: CGFloat = 52
15
+
16
+ /// Mirrors Compose's `enum InputSearchType`.
17
+ enum NavInputSearchType {
18
+ case none, header, animated
19
+ }
20
+
21
+ /// Mirrors Compose's `HeaderColor`.
22
+ public struct HeaderColor {
23
+ public let tintIconColor: Color
24
+ public let backgroundButton: Color
25
+ public let borderColor: Color
26
+ }
27
+
28
+ /// Mirrors Compose's `getHeaderColor`. On iOS the "animated" header is expressed
29
+ /// via a non-nil `animatedHeader`, so we pass `isAnimated`/`isSurface` instead of
30
+ /// the Compose `HeaderType.Animated` case.
31
+ func getHeaderColor(
32
+ isAnimated: Bool,
33
+ isSurface: Bool,
34
+ opacity: CGFloat,
35
+ tintColor: Color?,
36
+ defaultColor: Color = Colors.black17
37
+ ) -> HeaderColor {
38
+ if isAnimated {
39
+ if opacity >= 1 || !isSurface {
40
+ return HeaderColor(
41
+ tintIconColor: Colors.black17,
42
+ backgroundButton: Colors.black01.opacity(0.6),
43
+ borderColor: Colors.black20.opacity(0.2)
44
+ )
45
+ }
46
+ return HeaderColor(
47
+ tintIconColor: Colors.black01,
48
+ backgroundButton: Colors.black20.opacity(0.6),
49
+ borderColor: .clear
50
+ )
51
+ }
52
+
53
+ if tintColor == Colors.black01 {
54
+ return HeaderColor(
55
+ tintIconColor: Colors.black01,
56
+ backgroundButton: Colors.black20.opacity(0.6),
57
+ borderColor: Colors.black01.opacity(0.2)
58
+ )
59
+ }
60
+ return HeaderColor(
61
+ tintIconColor: tintColor ?? defaultColor,
62
+ backgroundButton: Colors.black01.opacity(0.6),
63
+ borderColor: Colors.black20.opacity(0.2)
64
+ )
65
+ }
@@ -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()
@@ -0,0 +1,453 @@
1
+ //
2
+ // StackScreen.swift
3
+ // Pods
4
+ //
5
+ // SwiftUI mirror of Compose's `StackScreen`. This is a refactor of `Screen`:
6
+ // the body is decomposed into the same focused pieces Compose uses — header
7
+ // layer, scrollable content (`StackScreenContent`), floating button
8
+ // (`FloatingContent`) and footer (`FooterContent`) — instead of one
9
+ // monolithic, partially-duplicated view. The public init matches `Screen`
10
+ // so call sites (e.g. NavigationContainer's ScreenHost) can swap over.
11
+ //
12
+
13
+ import SwiftUI
14
+
15
+ public struct StackScreen<Content: View>: View {
16
+ // MARK: - Properties
17
+
18
+ var backgroundColor: Color?
19
+ var headerTransparent: Bool = false
20
+ var fullScreenContent: Bool = false
21
+ var tintColor: Color? = Colors.black17
22
+ var isBack: Bool
23
+ var headerType: HeaderType
24
+ var useAnimated: Bool = false
25
+ var verticalAlignment: VerticalAlignment
26
+ var horizontalAlignment: HorizontalAlignment
27
+ var title: String
28
+ var headerTitle: HeaderTitle?
29
+ var titlePosition: TitlePosition
30
+ var goBack: (() -> Void)?
31
+ var scrollable: Bool
32
+ var scrollToTopCallback: (() -> Void)?
33
+ var onContentLayout: ((CGRect) -> Void)?
34
+ var useAvoidKeyboard: Bool
35
+ var keyboardShouldPersistTaps: Bool
36
+ var footer: (() -> AnyView)?
37
+ var headerRight: (()->any View)? = {HeaderRight()}
38
+ var animatedHeader: AnimatedHeader? = nil
39
+ var layoutOffset: CGFloat
40
+ var inputSearchProps: InputSearchProps?
41
+ var fabProps: FabProps?
42
+ var content: () -> Content
43
+
44
+ public init(
45
+ backgroundColor: Color? = nil,
46
+ headerTransparent: Bool = false,
47
+ fullScreenContent: Bool = false,
48
+ tintColor: Color = Colors.black17,
49
+ isBack: Bool = true,
50
+ headerType: HeaderType = .default,
51
+ useAnimated: Bool = false,
52
+ verticalAlignment: VerticalAlignment = .top,
53
+ horizontalAlignment: HorizontalAlignment = .leading,
54
+ title: String = "Stack",
55
+ headerTitle: HeaderTitle? = nil,
56
+ titlePosition: TitlePosition = .left,
57
+ goBack: (() -> Void)? = nil,
58
+ scrollable: Bool = true,
59
+ scrollToTopCallback: (() -> Void)? = nil,
60
+ onContentLayout: ((CGRect) -> Void)? = nil,
61
+ useAvoidKeyboard: Bool = true,
62
+ keyboardShouldPersistTaps: Bool = false,
63
+ footer: (() -> AnyView)? = nil,
64
+ headerRight: (()->any View)? = {HeaderRight()},
65
+ animatedHeader: AnimatedHeader? = nil,
66
+ layoutOffset: CGFloat = 56,
67
+ inputSearchProps: InputSearchProps? = nil,
68
+ fabProps: FabProps? = nil,
69
+ @ViewBuilder content: @escaping () -> Content
70
+ ) {
71
+ self.backgroundColor = backgroundColor
72
+ self.headerTransparent = headerTransparent
73
+ self.fullScreenContent = fullScreenContent
74
+ self.tintColor = tintColor
75
+ self.isBack = isBack
76
+ self.headerType = headerType
77
+ self.useAnimated = useAnimated
78
+ self.verticalAlignment = verticalAlignment
79
+ self.horizontalAlignment = horizontalAlignment
80
+ self.title = title
81
+ self.headerTitle = headerTitle
82
+ self.titlePosition = titlePosition
83
+ self.goBack = goBack
84
+ self.scrollable = scrollable
85
+ self.scrollToTopCallback = scrollToTopCallback
86
+ self.onContentLayout = onContentLayout
87
+ self.useAvoidKeyboard = useAvoidKeyboard
88
+ self.keyboardShouldPersistTaps = keyboardShouldPersistTaps
89
+ self.footer = footer
90
+ self.headerRight = headerRight
91
+ self.animatedHeader = animatedHeader
92
+ self.layoutOffset = layoutOffset
93
+ self.inputSearchProps = inputSearchProps
94
+ self.fabProps = fabProps
95
+ self.content = content
96
+ }
97
+
98
+ @State private var scrollOffset: CGFloat = 0
99
+ @State private var keyboardHeight: CGFloat = 0
100
+ @State private var headerRightWidth: CGFloat = 0
101
+
102
+ /// Header background fade tied to scroll: 0 → transparent (over a hero image
103
+ /// or transparent header), 1 → opaque surface with the drop shadow.
104
+ private var scrollOpacity: CGFloat { min(scrollOffset / 114, 1) }
105
+
106
+ // MARK: - Search header (mirrors Compose's getInputSearchType / SearchAnimated)
107
+
108
+ private enum InputSearchType { case none, header, animated }
109
+
110
+ private var inputSearchType: InputSearchType {
111
+ guard inputSearchProps != nil else { return .none }
112
+ return useAnimated ? .animated : .header
113
+ }
114
+
115
+ private static var headerHeight: CGFloat { 52 }
116
+
117
+ /// Extra top space reserved inside the content so the animated search bar has
118
+ /// room below the header at scroll 0; it shrinks to 0 as the bar slides up.
119
+ private var animatedSearchInset: CGFloat {
120
+ guard inputSearchType == .animated else { return 0 }
121
+ let scroll = max(scrollOffset, 0)
122
+ return min(max(Self.headerHeight - scroll, 0), Self.headerHeight)
123
+ }
124
+
125
+ /// Title fades out as the animated search bar rises into the header.
126
+ private var titleOpacity: CGFloat {
127
+ guard inputSearchType == .animated else { return 1 }
128
+ let scroll = max(scrollOffset, 0)
129
+ return 1 - min(max(scroll / Self.headerHeight, 0), 1)
130
+ }
131
+
132
+ // MARK: - Body
133
+
134
+ public var body: some View {
135
+ GeometryReader { geometry in
136
+ let keyboardSize: CGFloat = useAvoidKeyboard ? max(keyboardHeight, 0) : 0
137
+ let bottomInset = min(geometry.safeAreaInsets.bottom, 21)
138
+ let contentBottomInset = footer == nil ? bottomInset : 0
139
+
140
+ ZStack(alignment: .top) {
141
+ // Background color
142
+ (backgroundColor ?? Color.white)
143
+ .edgesIgnoringSafeArea(.all)
144
+
145
+ VStack(spacing: 0) {
146
+ ZStack(alignment: fabProps?.position == .center ? .bottom : .bottomTrailing) {
147
+ VStack(alignment: horizontalAlignment, spacing: 0) {
148
+ headerAndContent
149
+ }
150
+ .padding(.bottom, contentBottomInset)
151
+
152
+ FloatingContent(
153
+ fabProps: fabProps,
154
+ keyboardOffset: 0,
155
+ scrollOffset: scrollOffset
156
+ )
157
+ }
158
+
159
+ FooterContent(footer: footer, keyboardSize: keyboardSize, bottomInset: bottomInset)
160
+ }
161
+
162
+ // Animated search bar overlay (mirrors Compose's SearchAnimated layer).
163
+ if inputSearchType == .animated, let props = inputSearchProps {
164
+ SearchAnimatedHeader(
165
+ props: props,
166
+ scrollOffset: scrollOffset,
167
+ hiddenBack: !isBack,
168
+ hasHeaderRight: headerRight != nil,
169
+ headerRightWidth: headerRightWidth,
170
+ headerHeight: Self.headerHeight
171
+ )
172
+ }
173
+ }
174
+ .ignoresSafeArea(.keyboard, edges: .bottom)
175
+ .onPreferenceChange(HeaderRightWidthKey.self) { width in
176
+ if headerRightWidth != width { headerRightWidth = width }
177
+ }
178
+ .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillChangeFrameNotification)) { notification in
179
+ guard useAvoidKeyboard,
180
+ let endFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
181
+ withAnimation(.easeOut(duration: 0.25)) {
182
+ keyboardHeight = UIScreen.main.bounds.height - endFrame.origin.y
183
+ }
184
+ }
185
+ // Mirrors Compose's `hideKeyboardOnTap()`: a simultaneous gesture so
186
+ // taps still reach buttons/fields while also dismissing the keyboard.
187
+ .modifier(HideKeyboardOnTap(enabled: keyboardShouldPersistTaps))
188
+ }
189
+ }
190
+
191
+ // MARK: - Header + content layer
192
+
193
+ @ViewBuilder
194
+ private var headerAndContent: some View {
195
+ if let animatedHeader = animatedHeader {
196
+ // Animated (hero) header: the image is the top of the scroll content
197
+ // and the body overlaps its lower part; the header bar background
198
+ // fades in over the image as the screen scrolls (mirrors Compose's
199
+ // ScreenContent for HeaderType.Animated).
200
+ ZStack(alignment: .top) {
201
+ StackScreenContent(
202
+ scrollable: scrollable,
203
+ scrollOffset: $scrollOffset,
204
+ scrollToTopCallback: scrollToTopCallback,
205
+ animatedSearchInset: animatedSearchInset,
206
+ content: { animatedHeroBody(animatedHeader) }
207
+ )
208
+ .edgesIgnoringSafeArea(.all)
209
+
210
+ header
211
+ .background(
212
+ Colors.black01
213
+ .opacity(scrollOpacity)
214
+ .edgesIgnoringSafeArea(.top)
215
+ )
216
+ .zIndex(100)
217
+ }
218
+ } else if headerTransparent && fullScreenContent {
219
+ ZStack(alignment: .top) {
220
+ header
221
+ .background(
222
+ NavigationHeaderBackground(
223
+ headerType: headerType,
224
+ scrollState: scrollOffset,
225
+ headerTransparent: headerTransparent,
226
+ fullScreenContent: fullScreenContent
227
+ )
228
+ )
229
+ .zIndex(100)
230
+
231
+ StackScreenContent(scrollable: scrollable, scrollOffset: $scrollOffset, scrollToTopCallback: scrollToTopCallback, animatedSearchInset: animatedSearchInset, content: content)
232
+ .edgesIgnoringSafeArea(.all)
233
+ }
234
+ } else {
235
+ header
236
+ .background(
237
+ NavigationHeaderBackground(
238
+ headerType: headerType,
239
+ scrollState: scrollOffset,
240
+ headerTransparent: headerTransparent,
241
+ fullScreenContent: fullScreenContent
242
+ )
243
+ .edgesIgnoringSafeArea(.top)
244
+ )
245
+
246
+ StackScreenContent(scrollable: scrollable, scrollOffset: $scrollOffset, scrollToTopCallback: scrollToTopCallback, animatedSearchInset: animatedSearchInset, content: content)
247
+ }
248
+ }
249
+
250
+ /// Hero image + body for an animated header. The image fills the width at its
251
+ /// aspect-ratio height; the body is offset below the header band so it overlaps
252
+ /// the lower part of the image (mirrors Compose's image box + padded content).
253
+ @ViewBuilder
254
+ private func animatedHeroBody(_ animatedHeader: AnimatedHeader) -> some View {
255
+ let width = UIScreen.main.bounds.width
256
+ let imageHeight = width / animatedHeader.aspectRatio.value
257
+ let contentTopInset = safeAreaTopInset() + HEADER_HEIGHT + layoutOffset
258
+
259
+ ZStack(alignment: .top) {
260
+ animatedHeader.composable(scrollOffset)
261
+ .frame(width: width, height: imageHeight)
262
+ .clipped()
263
+
264
+ content()
265
+ .padding(.top, contentTopInset)
266
+ }
267
+ .frame(maxWidth: .infinity, alignment: .top)
268
+ }
269
+
270
+ private var header: NavigationHeader {
271
+ NavigationHeader(
272
+ headerType: headerType,
273
+ titlePosition: titlePosition,
274
+ title: title,
275
+ headerTitle: headerTitle,
276
+ headerRight: headerRight,
277
+ goBack: goBack,
278
+ opacity: scrollOpacity,
279
+ animatedHeader: animatedHeader,
280
+ scrollState: scrollOffset,
281
+ // For the animated search the bar is rendered by the overlay, so the
282
+ // header shows the (fading) title instead of an inline search field.
283
+ inputSearchProps: inputSearchType == .header ? inputSearchProps : nil,
284
+ tintColor: tintColor,
285
+ titleOpacity: titleOpacity
286
+ )
287
+ }
288
+ }
289
+
290
+ // MARK: - Screen content (scrollable body)
291
+
292
+ /// Mirrors Compose's `ScreenContent`/`MainContent`: renders the screen body and,
293
+ /// when scrollable, tracks the scroll offset so the header/FAB can react to it.
294
+ private struct StackScreenContent<Content: View>: View {
295
+ let scrollable: Bool
296
+ @Binding var scrollOffset: CGFloat
297
+ /// Mirrors Compose's `ScrollData.scrollToTopCallback`; fired alongside the
298
+ /// scroll-to-top triggered by a status-bar tap.
299
+ let scrollToTopCallback: (() -> Void)?
300
+ /// Extra top space reserved for the animated search bar (mirrors Compose's
301
+ /// MainContent animated top spacer). Zero for non-animated screens.
302
+ var animatedSearchInset: CGFloat = 0
303
+ let content: () -> Content
304
+
305
+ private let topAnchorID = "stack_screen_top_anchor"
306
+ private let scrollSpace = "stack_screen_scroll_space"
307
+
308
+ var body: some View {
309
+ if scrollable {
310
+ ScrollViewReader { proxy in
311
+ ScrollView(.vertical, showsIndicators: false) {
312
+ VStack(spacing: 0) {
313
+ // Top probe + scroll-to-top anchor. Measured in the scroll's
314
+ // own coordinate space so the offset is a clean 0-based
315
+ // "pixels scrolled" value, independent of the inset below.
316
+ Color.clear
317
+ .frame(height: 0)
318
+ .id(topAnchorID)
319
+ .background(GeometryReader { geo in
320
+ Color.clear
321
+ .onAppear {
322
+ scrollOffset = -geo.frame(in: .named(scrollSpace)).minY
323
+ }
324
+ .onChange(of: geo.frame(in: .named(scrollSpace)).minY) { minY in
325
+ scrollOffset = -minY
326
+ }
327
+ })
328
+
329
+ if animatedSearchInset > 0 {
330
+ Spacer().frame(height: animatedSearchInset)
331
+ }
332
+
333
+ VStack {
334
+ content()
335
+ }
336
+ }
337
+ }
338
+ .coordinateSpace(name: scrollSpace)
339
+ // Status-bar tap (via MoMoStatusBarTap swizzle) scrolls to top and
340
+ // notifies the consumer, mirroring Compose's scrollToTopCallback.
341
+ .onReceive(NotificationCenter.default.publisher(for: MoMoStatusBarTap.notificationName)) { _ in
342
+ withAnimation {
343
+ proxy.scrollTo(topAnchorID, anchor: .top)
344
+ }
345
+ scrollToTopCallback?()
346
+ }
347
+ }
348
+ } else {
349
+ content()
350
+ }
351
+ }
352
+ }
353
+
354
+ // MARK: - Animated search header
355
+
356
+ /// Mirrors Compose's `SearchAnimated`: the search bar slides from below the
357
+ /// header (scroll 0) up into the header band as the user scrolls, while its
358
+ /// horizontal insets grow to make room for the back button and header-right.
359
+ /// Note: Compose's scroll-end snap and background-color lerp are not ported.
360
+ private struct SearchAnimatedHeader: View {
361
+ let props: InputSearchProps
362
+ let scrollOffset: CGFloat
363
+ let hiddenBack: Bool
364
+ let hasHeaderRight: Bool
365
+ let headerRightWidth: CGFloat
366
+ let headerHeight: CGFloat
367
+
368
+ var body: some View {
369
+ let scroll = max(scrollOffset, 0)
370
+
371
+ // Insets are measured from the safe-area top (status bar handled by layout).
372
+ let minTop: CGFloat = 0
373
+ let maxTop = headerHeight
374
+ let minSide = Spacing.M
375
+ let maxStart: CGFloat = hiddenBack ? Spacing.M : 48
376
+ let maxEnd = headerRightWidth + (hasHeaderRight ? Spacing.M * 2 : Spacing.M)
377
+
378
+ let top = min(max(maxTop - scroll, minTop), maxTop)
379
+ let start = min(max(minSide + scroll, minSide), maxStart)
380
+ let end = min(max(minSide + scroll, minSide), maxEnd)
381
+
382
+ VStack(spacing: 0) {
383
+ Spacer().frame(height: top)
384
+ InputSearch(props: props)
385
+ .frame(height: headerHeight)
386
+ .padding(.leading, start)
387
+ .padding(.trailing, end)
388
+ Spacer(minLength: 0)
389
+ }
390
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
391
+ }
392
+ }
393
+
394
+ // MARK: - Floating content
395
+
396
+ /// Mirrors Compose's `FloatingContent`: no FAB props means nothing renders.
397
+ private struct FloatingContent: View {
398
+ let fabProps: FabProps?
399
+ let keyboardOffset: CGFloat
400
+ let scrollOffset: CGFloat
401
+
402
+ var body: some View {
403
+ if let fabProps = fabProps {
404
+ FloatingButton(
405
+ props: fabProps,
406
+ keyboardOffset: keyboardOffset,
407
+ scrollOffset: scrollOffset
408
+ )
409
+ .padding(.horizontal, Spacing.M)
410
+ .ignoresSafeArea(.keyboard, edges: .bottom)
411
+ }
412
+ }
413
+ }
414
+
415
+ // MARK: - Hide keyboard on tap
416
+
417
+ /// Mirrors Compose's `Modifier.hideKeyboardOnTap()`. When enabled, a tap
418
+ /// anywhere resigns the first responder. Uses `simultaneousGesture` so the tap
419
+ /// is not consumed — buttons and text fields keep working.
420
+ private struct HideKeyboardOnTap: ViewModifier {
421
+ let enabled: Bool
422
+
423
+ func body(content: Content) -> some View {
424
+ if enabled {
425
+ content.simultaneousGesture(
426
+ TapGesture().onEnded {
427
+ UIApplication.shared.sendAction(
428
+ #selector(UIResponder.resignFirstResponder),
429
+ to: nil, from: nil, for: nil
430
+ )
431
+ }
432
+ )
433
+ } else {
434
+ content
435
+ }
436
+ }
437
+ }
438
+
439
+ // MARK: - Footer content
440
+
441
+ /// Mirrors Compose's `FooterContent`: renders the footer chrome only when a
442
+ /// footer component is supplied. Reuses the existing `Footer` view from Screen.swift.
443
+ private struct FooterContent: View {
444
+ let footer: (() -> AnyView)?
445
+ let keyboardSize: CGFloat
446
+ let bottomInset: CGFloat
447
+
448
+ var body: some View {
449
+ if let footerView = footer?() {
450
+ Footer(footerView: footerView, keyboardSize: keyboardSize, bottomInset: bottomInset)
451
+ }
452
+ }
453
+ }