@momo-kits/native-kits 0.163.1-beta.8 → 0.163.1-beta.9-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 (334) hide show
  1. package/CLAUDE.md +1 -1
  2. package/build.gradle.kts +11 -0
  3. package/compose/build.gradle.kts +190 -0
  4. package/compose/build.gradle.kts.backup +190 -0
  5. package/compose/compose.podspec +47 -0
  6. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +6 -0
  7. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +7 -0
  8. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +150 -0
  9. package/compose/src/commonMain/composeResources/font/momosignature.otf +0 -0
  10. package/compose/src/commonMain/composeResources/font/momotrustdisplay.otf +0 -0
  11. package/compose/src/commonMain/composeResources/font/sfprotext_black.otf +0 -0
  12. package/compose/src/commonMain/composeResources/font/sfprotext_black.ttf +0 -0
  13. package/compose/src/commonMain/composeResources/font/sfprotext_bold.ttf +0 -0
  14. package/compose/src/commonMain/composeResources/font/sfprotext_heavy.ttf +0 -0
  15. package/compose/src/commonMain/composeResources/font/sfprotext_light.ttf +0 -0
  16. package/compose/src/commonMain/composeResources/font/sfprotext_medium.ttf +0 -0
  17. package/compose/src/commonMain/composeResources/font/sfprotext_regular.ttf +0 -0
  18. package/compose/src/commonMain/composeResources/font/sfprotext_semibold.ttf +0 -0
  19. package/compose/src/commonMain/composeResources/font/sfprotext_thin.otf +0 -0
  20. package/compose/src/commonMain/composeResources/font/sfprotext_thin.ttf +0 -0
  21. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.otf +0 -0
  22. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.ttf +0 -0
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +52 -0
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +106 -0
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +183 -0
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +207 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +39 -0
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +86 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +76 -0
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +76 -0
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +294 -0
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +33 -0
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +826 -0
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +269 -0
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +110 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +378 -0
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +56 -0
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +69 -0
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +157 -0
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +82 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +32 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +338 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +175 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +356 -0
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +113 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +89 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +128 -0
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +209 -0
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +543 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +23 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +76 -0
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +125 -0
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +231 -0
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +112 -0
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +477 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +114 -0
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +214 -0
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +240 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +216 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +259 -0
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +182 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +343 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +108 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +56 -0
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +41 -0
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +84 -0
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +40 -0
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +328 -0
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +97 -0
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +310 -0
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +59 -0
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +87 -0
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +21 -0
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +90 -0
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +323 -0
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +217 -0
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +473 -0
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +122 -0
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +199 -0
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +91 -0
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +470 -0
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +88 -0
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +131 -0
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +203 -0
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +498 -0
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +175 -0
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +182 -0
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +193 -0
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +234 -0
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +188 -0
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +306 -0
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +22 -0
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +16 -0
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +188 -0
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +276 -0
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +65 -0
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +48 -0
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +51 -0
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +52 -0
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +66 -0
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +104 -0
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +48 -0
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +253 -0
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +129 -0
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +100 -0
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +172 -0
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +360 -0
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +8 -0
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +581 -0
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +156 -0
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +225 -0
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +169 -0
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +320 -0
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +81 -0
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +309 -0
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +34 -0
  122. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +360 -0
  123. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +132 -0
  124. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +128 -0
  125. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +91 -0
  126. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +9 -0
  127. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +69 -0
  128. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
  129. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +55 -0
  130. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +15 -0
  131. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +105 -0
  132. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +32 -0
  133. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +66 -0
  134. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +207 -0
  135. package/gradle/libs.versions.toml +69 -0
  136. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  137. package/gradle/wrapper/gradle-wrapper.properties +8 -0
  138. package/gradle.properties +26 -0
  139. package/gradlew +252 -0
  140. package/gradlew.bat +94 -0
  141. package/ios/Application/ApplicationEnvironment.swift +15 -2
  142. package/ios/Application/Components.swift +50 -44
  143. package/ios/Application/FloatingButton.swift +10 -9
  144. package/ios/Application/HeaderRight.swift +79 -45
  145. package/ios/Application/Localize.swift +277 -0
  146. package/ios/Application/MaxApi.swift +18 -0
  147. package/ios/Application/Navigation/BottomTab/BottomTab.swift +104 -0
  148. package/ios/Application/Navigation/BottomTab/BottomTabBar.swift +215 -0
  149. package/ios/Application/Navigation/BottomTab/CurvedContainer.swift +65 -0
  150. package/ios/Application/Navigation/HeaderBackProps.swift +66 -0
  151. package/ios/Application/Navigation/HeaderTitle.swift +57 -0
  152. package/ios/Application/Navigation/HeaderUser.swift +209 -0
  153. package/ios/Application/Navigation/NavigationContainer.swift +200 -0
  154. package/ios/Application/Navigation/NavigationOptions.swift +109 -0
  155. package/ios/Application/Navigation/Navigator.swift +385 -0
  156. package/ios/Application/Navigation/Overplay/BottomSheet.swift +256 -0
  157. package/ios/Application/Navigation/Overplay/ModalScreen.swift +74 -0
  158. package/ios/Application/Navigation/Overplay/SnackBar.swift +144 -0
  159. package/ios/Application/Navigation/Tracking/ScreenTracker.swift +117 -0
  160. package/ios/Application/Navigation/component/Header.swift +178 -0
  161. package/ios/Application/Navigation/component/HeaderBackground.swift +83 -0
  162. package/ios/Application/Navigation/component/HeaderColor.swift +65 -0
  163. package/ios/Application/Screen.swift +2 -1
  164. package/ios/Application/StackScreen.swift +501 -0
  165. package/ios/Avatar/Avatar.swift +193 -0
  166. package/ios/Badge/Badge.swift +11 -14
  167. package/ios/Badge/BadgeRibbon.swift +92 -21
  168. package/ios/BaselineView/BaselineView.swift +163 -0
  169. package/ios/Button/Button.swift +124 -29
  170. package/ios/Carousel/Carousel.swift +196 -0
  171. package/ios/Checkbox/Checkbox.swift +23 -28
  172. package/ios/Chip/Chip.swift +56 -33
  173. package/ios/Collapse/Collapse.swift +192 -0
  174. package/ios/Colors+Radius+Spacing/Colors.swift +20 -17
  175. package/ios/Colors+Radius+Spacing/Spacing.swift +3 -1
  176. package/ios/Colors+Radius+Spacing/Theme.swift +295 -0
  177. package/ios/DateTimePicker/DateTimePicker.swift +212 -0
  178. package/ios/DateTimePicker/DateTimePickerTypes.swift +55 -0
  179. package/ios/DateTimePicker/DateTimePickerUtils.swift +167 -0
  180. package/ios/DateTimePicker/WheelPicker.swift +165 -0
  181. package/ios/Divider/Divider.swift +16 -0
  182. package/ios/Extensions/ActiveOpacityButtonStyle.swift +14 -0
  183. package/ios/Extensions/Color++.swift +32 -0
  184. package/ios/Icon/Icon.swift +10 -1
  185. package/ios/IconButton/IconButton.swift +140 -0
  186. package/ios/Image/Image.swift +16 -3
  187. package/ios/Information/Information.swift +3 -1
  188. package/ios/Input/ErrorView.swift +3 -1
  189. package/ios/Input/Input.swift +72 -33
  190. package/ios/Input/InputPhoneNumber.swift +33 -21
  191. package/ios/Input/InputSearch.swift +107 -48
  192. package/ios/Input/InputTextArea.swift +134 -64
  193. package/ios/InputDropDown/InputDropDown.swift +201 -0
  194. package/ios/InputMoney/InputMoney.swift +347 -0
  195. package/ios/InputOTP/InputOTP.swift +210 -0
  196. package/ios/Loader/Loader.swift +113 -0
  197. package/ios/MoMoUIKits.podspec +5 -0
  198. package/ios/Pagination/PaginationDot.swift +61 -0
  199. package/ios/Pagination/PaginationNumber.swift +35 -0
  200. package/ios/Pagination/PaginationScroll.swift +101 -0
  201. package/ios/Pagination/PaginationWhiteDot.swift +37 -0
  202. package/ios/Popup/PopupDisplay.swift +11 -20
  203. package/ios/Popup/PopupInput.swift +2 -8
  204. package/ios/Popup/PopupNotify.swift +218 -0
  205. package/ios/Popup/PopupPromotion.swift +16 -8
  206. package/ios/ProgressInfo/ProgressInfo.swift +296 -0
  207. package/ios/Radio/Radio.swift +69 -0
  208. package/ios/Rating/Rating.swift +82 -0
  209. package/ios/Resources/MoMoUIKitsColors.xcassets/AccentColor.colorset/Contents.json +11 -0
  210. package/ios/Resources/MoMoUIKitsColors.xcassets/Background.colorset/Contents.json +38 -0
  211. package/ios/Resources/MoMoUIKitsColors.xcassets/Border.colorset/Contents.json +38 -0
  212. package/ios/Resources/MoMoUIKitsColors.xcassets/Card.colorset/Contents.json +38 -0
  213. package/ios/Resources/MoMoUIKitsColors.xcassets/Contents.json +6 -0
  214. package/ios/Resources/MoMoUIKitsColors.xcassets/Error.colorset/Contents.json +38 -0
  215. package/ios/Resources/MoMoUIKitsColors.xcassets/Primary.colorset/Contents.json +38 -0
  216. package/ios/Resources/MoMoUIKitsColors.xcassets/PrimaryDark.colorset/Contents.json +38 -0
  217. package/ios/Resources/MoMoUIKitsColors.xcassets/PrimaryLight.colorset/Contents.json +38 -0
  218. package/ios/Resources/MoMoUIKitsColors.xcassets/Secondary.colorset/Contents.json +38 -0
  219. package/ios/Resources/MoMoUIKitsColors.xcassets/Success.colorset/Contents.json +38 -0
  220. package/ios/Resources/MoMoUIKitsColors.xcassets/Text.colorset/Contents.json +38 -0
  221. package/ios/Resources/MoMoUIKitsColors.xcassets/TextSecondary.colorset/Contents.json +38 -0
  222. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/Contents.json +9 -0
  223. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-default.colorset/Contents.json +38 -0
  224. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-disable.colorset/Contents.json +38 -0
  225. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-pressed.colorset/Contents.json +38 -0
  226. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-selected.colorset/Contents.json +38 -0
  227. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-surface.colorset/Contents.json +38 -0
  228. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-tonal.colorset/Contents.json +38 -0
  229. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/border-default.colorset/Contents.json +38 -0
  230. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/border-disable.colorset/Contents.json +38 -0
  231. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/gradient.colorset/Contents.json +38 -0
  232. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/primary.colorset/Contents.json +38 -0
  233. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/secondary.colorset/Contents.json +38 -0
  234. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-container.colorset/Contents.json +38 -0
  235. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-primary.colorset/Contents.json +38 -0
  236. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-secondary.colorset/Contents.json +38 -0
  237. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-default.colorset/Contents.json +38 -0
  238. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-disable.colorset/Contents.json +38 -0
  239. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-hint.colorset/Contents.json +38 -0
  240. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-secondary.colorset/Contents.json +38 -0
  241. package/ios/Resources/MoMoUIKitsColors.xcassets/Warning.colorset/Contents.json +38 -0
  242. package/ios/Skeleton/Skeleton.swift +99 -0
  243. package/ios/Slider/Slider.swift +237 -0
  244. package/ios/Stepper/Stepper.swift +220 -0
  245. package/ios/Steps/Steps.swift +467 -0
  246. package/ios/SuggestAction/SuggestAction.swift +99 -0
  247. package/ios/Swipeable/SwipeCell.swift +187 -0
  248. package/ios/Swipeable/SwipeCellModel.swift +21 -0
  249. package/ios/Switch/Switch.swift +53 -28
  250. package/ios/TabView/TabView.swift +534 -0
  251. package/ios/Tag/Tag.swift +98 -0
  252. package/ios/Template/TrustBanner/TrustBanner.swift +6 -7
  253. package/ios/Title/Title.swift +269 -0
  254. package/ios/Tooltip/Tooltip.swift +485 -0
  255. package/ios/Typography/FontScaleStore.swift +19 -0
  256. package/ios/Typography/Text.swift +79 -5
  257. package/ios/Typography/Typography.swift +3 -0
  258. package/ios/Uploader/Uploader.swift +174 -0
  259. package/ios/native-kits.podspec +61 -8
  260. package/ios-demo/MoMoUIKitsDemo.podspec +20 -0
  261. package/ios-demo/README.md +120 -0
  262. package/ios-demo/Resources/dot_loading.json +558 -0
  263. package/ios-demo/Resources/icon.json +4052 -0
  264. package/ios-demo/Resources/lottie_circle_loader.json +1 -0
  265. package/ios-demo/Sources/MoMoUIKitsDemo/DemoScaffold.swift +48 -0
  266. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsBadgeDemoView.swift +25 -0
  267. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsButtonDemoView.swift +26 -0
  268. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsCheckboxDemoView.swift +19 -0
  269. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsChipDemoView.swift +33 -0
  270. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsInputDemoView.swift +48 -0
  271. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsPopupDemoView.swift +21 -0
  272. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsRadioDemoView.swift +17 -0
  273. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsTypographyDemoView.swift +28 -0
  274. package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoContext.swift +31 -0
  275. package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoFactory.swift +24 -0
  276. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AnimatedHeaderDemo.swift +110 -0
  277. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AvatarDemo.swift +93 -0
  278. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BadgeDemo.swift +269 -0
  279. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BaselineView.swift +117 -0
  280. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BottomTabDemo.swift +89 -0
  281. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ButtonDemo.swift +100 -0
  282. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CarouselDemo.swift +126 -0
  283. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CheckboxDemo.swift +63 -0
  284. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ChipDemo.swift +20 -0
  285. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CollapseDemo.swift +93 -0
  286. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +107 -0
  287. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DateTimePickerDemo.swift +124 -0
  288. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DividerDemo.swift +14 -0
  289. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/HeaderUserDemo.swift +49 -0
  290. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +279 -0
  291. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconButtonDemo.swift +58 -0
  292. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ImageDemo.swift +78 -0
  293. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InformationDemo.swift +138 -0
  294. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDemo.swift +111 -0
  295. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDropDownDemo.swift +136 -0
  296. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputMoneyDemo.swift +107 -0
  297. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputOTPDemo.swift +73 -0
  298. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputPhoneNumberDemo.swift +100 -0
  299. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputSearchDemo.swift +32 -0
  300. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputTextAreaDemo.swift +57 -0
  301. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LoaderDemo.swift +70 -0
  302. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LocalizeDemo.swift +188 -0
  303. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +229 -0
  304. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PaginationDemo.swift +72 -0
  305. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +175 -0
  306. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +121 -0
  307. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +76 -0
  308. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ProgressInfoDemo.swift +89 -0
  309. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RadioDemo.swift +32 -0
  310. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RatingDemo.swift +56 -0
  311. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SkeletonDemo.swift +32 -0
  312. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SliderDemo.swift +150 -0
  313. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SnackBarDemo.swift +138 -0
  314. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepperDemo.swift +103 -0
  315. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepsDemo.swift +94 -0
  316. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SuggestActionDemo.swift +79 -0
  317. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwipeDemo.swift +152 -0
  318. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwitchDemo.swift +33 -0
  319. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TabViewDemo.swift +190 -0
  320. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TagDemo.swift +34 -0
  321. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ThemeDemo.swift +182 -0
  322. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TitleDemo.swift +109 -0
  323. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TooltipDemo.swift +187 -0
  324. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TypographyDemo.swift +87 -0
  325. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/UploaderDemo.swift +131 -0
  326. package/ios-demo/Sources/MoMoUIKitsDemo/SwiftUIDemoBrowserView.swift +51 -0
  327. package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoModels.swift +15 -0
  328. package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoRegistry.swift +73 -0
  329. package/local.properties +8 -0
  330. package/package.json +1 -1
  331. package/publish.sh +53 -0
  332. package/scripts/gen-ios-color.mjs +136 -0
  333. package/settings.gradle.kts +64 -0
  334. package/.claude/settings.local.json +0 -62
@@ -0,0 +1,501 @@
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 screenName: String = ""
43
+ var content: () -> Content
44
+
45
+ public init(
46
+ backgroundColor: Color? = nil,
47
+ headerTransparent: Bool = false,
48
+ fullScreenContent: Bool = false,
49
+ tintColor: Color = Colors.black17,
50
+ isBack: Bool = true,
51
+ headerType: HeaderType = .default,
52
+ useAnimated: Bool = false,
53
+ verticalAlignment: VerticalAlignment = .top,
54
+ horizontalAlignment: HorizontalAlignment = .leading,
55
+ title: String = "Stack",
56
+ headerTitle: HeaderTitle? = nil,
57
+ titlePosition: TitlePosition = .left,
58
+ goBack: (() -> Void)? = nil,
59
+ scrollable: Bool = true,
60
+ scrollToTopCallback: (() -> Void)? = nil,
61
+ onContentLayout: ((CGRect) -> Void)? = nil,
62
+ useAvoidKeyboard: Bool = true,
63
+ keyboardShouldPersistTaps: Bool = false,
64
+ footer: (() -> AnyView)? = nil,
65
+ headerRight: (()->any View)? = {HeaderRight()},
66
+ animatedHeader: AnimatedHeader? = nil,
67
+ layoutOffset: CGFloat = 56,
68
+ inputSearchProps: InputSearchProps? = nil,
69
+ fabProps: FabProps? = nil,
70
+ screenName: String = "",
71
+ @ViewBuilder content: @escaping () -> Content
72
+ ) {
73
+ self.backgroundColor = backgroundColor
74
+ self.headerTransparent = headerTransparent
75
+ self.fullScreenContent = fullScreenContent
76
+ self.tintColor = tintColor
77
+ self.isBack = isBack
78
+ self.headerType = headerType
79
+ self.useAnimated = useAnimated
80
+ self.verticalAlignment = verticalAlignment
81
+ self.horizontalAlignment = horizontalAlignment
82
+ self.title = title
83
+ self.headerTitle = headerTitle
84
+ self.titlePosition = titlePosition
85
+ self.goBack = goBack
86
+ self.scrollable = scrollable
87
+ self.scrollToTopCallback = scrollToTopCallback
88
+ self.onContentLayout = onContentLayout
89
+ self.useAvoidKeyboard = useAvoidKeyboard
90
+ self.keyboardShouldPersistTaps = keyboardShouldPersistTaps
91
+ self.footer = footer
92
+ self.headerRight = headerRight
93
+ self.animatedHeader = animatedHeader
94
+ self.layoutOffset = layoutOffset
95
+ self.inputSearchProps = inputSearchProps
96
+ self.fabProps = fabProps
97
+ self.screenName = screenName
98
+ self.content = content
99
+ }
100
+
101
+ @State private var scrollOffset: CGFloat = 0
102
+ @State private var keyboardHeight: CGFloat = 0
103
+ @State private var headerRightWidth: CGFloat = 0
104
+ @State private var isBottomTabRoot: Bool = false
105
+
106
+ @Environment(\.applicationEnvironment) private var appEnv
107
+
108
+ // Auto-tracking state (mirrors Compose's `remember { ScreenTrackingState() }` +
109
+ // the once-computed push/back `action`).
110
+ @State private var trackingState = ScreenTrackingState()
111
+ @State private var trackingAction: String = ScreenTracker.getLastScreenName() != nil ? "push" : "back"
112
+
113
+ /// Header background fade tied to scroll: 0 → transparent (over a hero image
114
+ /// or transparent header), 1 → opaque surface with the drop shadow.
115
+ private var scrollOpacity: CGFloat { min(scrollOffset / 114, 1) }
116
+
117
+ // MARK: - Search header (mirrors Compose's getInputSearchType / SearchAnimated)
118
+
119
+ private enum InputSearchType { case none, header, animated }
120
+
121
+ private var inputSearchType: InputSearchType {
122
+ guard inputSearchProps != nil else { return .none }
123
+ return useAnimated ? .animated : .header
124
+ }
125
+
126
+ private static var headerHeight: CGFloat { 52 }
127
+
128
+ /// Extra top space reserved inside the content so the animated search bar has
129
+ /// room below the header at scroll 0; it shrinks to 0 as the bar slides up.
130
+ private var animatedSearchInset: CGFloat {
131
+ guard inputSearchType == .animated else { return 0 }
132
+ let scroll = max(scrollOffset, 0)
133
+ return min(max(Self.headerHeight - scroll, 0), Self.headerHeight)
134
+ }
135
+
136
+ /// Title fades out as the animated search bar rises into the header.
137
+ private var titleOpacity: CGFloat {
138
+ guard inputSearchType == .animated else { return 1 }
139
+ let scroll = max(scrollOffset, 0)
140
+ return 1 - min(max(scroll / Self.headerHeight, 0), 1)
141
+ }
142
+
143
+ // MARK: - Body
144
+
145
+ public var body: some View {
146
+ GeometryReader { geometry in
147
+ let keyboardSize: CGFloat = useAvoidKeyboard ? max(keyboardHeight, 0) : 0
148
+ let bottomInset = min(geometry.safeAreaInsets.bottom, 21)
149
+ // Skip this screen's own bottom-inset reservation when hosting a footer
150
+ // OR a self-contained BottomTab surface (mirrors Compose's
151
+ // `if (options.footerComponent != null || isBottomTab) 0.dp else navigationBar`).
152
+ let contentBottomInset = (footer == nil && !isBottomTabRoot) ? bottomInset : 0
153
+
154
+ ZStack(alignment: .top) {
155
+ // Background color
156
+ (backgroundColor ?? Color.white)
157
+ .edgesIgnoringSafeArea(.all)
158
+
159
+ VStack(spacing: 0) {
160
+ ZStack(alignment: fabProps?.position == .center ? .bottom : .bottomTrailing) {
161
+ VStack(alignment: horizontalAlignment, spacing: 0) {
162
+ headerAndContent
163
+ }
164
+ .padding(.bottom, contentBottomInset)
165
+
166
+ FloatingContent(
167
+ fabProps: fabProps,
168
+ keyboardOffset: 0,
169
+ scrollOffset: scrollOffset
170
+ )
171
+ }
172
+
173
+ FooterContent(footer: footer, keyboardSize: keyboardSize, bottomInset: bottomInset)
174
+ }
175
+
176
+ // Animated search bar overlay (mirrors Compose's SearchAnimated layer).
177
+ if inputSearchType == .animated, let props = inputSearchProps {
178
+ SearchAnimatedHeader(
179
+ props: props,
180
+ scrollOffset: scrollOffset,
181
+ hiddenBack: !isBack,
182
+ hasHeaderRight: headerRight != nil,
183
+ headerRightWidth: headerRightWidth,
184
+ headerHeight: Self.headerHeight
185
+ )
186
+ }
187
+ }
188
+ .ignoresSafeArea(.keyboard, edges: .bottom)
189
+ .onPreferenceChange(HeaderRightWidthKey.self) { width in
190
+ if headerRightWidth != width { headerRightWidth = width }
191
+ }
192
+ .onPreferenceChange(IsBottomTabRootKey.self) { isRoot in
193
+ if isBottomTabRoot != isRoot { isBottomTabRoot = isRoot }
194
+ }
195
+ .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillChangeFrameNotification)) { notification in
196
+ guard useAvoidKeyboard,
197
+ let endFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
198
+ withAnimation(.easeOut(duration: 0.25)) {
199
+ keyboardHeight = UIScreen.main.bounds.height - endFrame.origin.y
200
+ }
201
+ }
202
+ // Mirrors Compose's `hideKeyboardOnTap()`: a simultaneous gesture so
203
+ // taps still reach buttons/fields while also dismissing the keyboard.
204
+ .modifier(HideKeyboardOnTap(enabled: keyboardShouldPersistTaps))
205
+ }
206
+ .task(id: screenName) {
207
+ ScreenTracker.trackScreenNavigated(
208
+ maxApi: appEnv.maxApi,
209
+ context: appEnv.applicationContext,
210
+ screenName: screenName,
211
+ action: trackingAction,
212
+ state: trackingState
213
+ )
214
+ do {
215
+ try await Task.sleep(nanoseconds: 2_000_000_000)
216
+ } catch {
217
+ return
218
+ }
219
+ let loadTime = Date().timeIntervalSince1970 * 1000 - trackingState.time
220
+ ScreenTracker.trackScreenDisplayed(
221
+ maxApi: appEnv.maxApi,
222
+ context: appEnv.applicationContext,
223
+ screenName: screenName,
224
+ duration: loadTime,
225
+ state: trackingState
226
+ )
227
+ let interactionTime = Date().timeIntervalSince1970 * 1000 - trackingState.time
228
+ ScreenTracker.trackScreenInteracted(
229
+ maxApi: appEnv.maxApi,
230
+ context: appEnv.applicationContext,
231
+ screenName: screenName,
232
+ duration: interactionTime - loadTime,
233
+ totalDuration: interactionTime,
234
+ state: trackingState
235
+ )
236
+ }
237
+ }
238
+
239
+ // MARK: - Header + content layer
240
+
241
+ @ViewBuilder
242
+ private var headerAndContent: some View {
243
+ if let animatedHeader = animatedHeader {
244
+ // Animated (hero) header: the image is the top of the scroll content
245
+ // and the body overlaps its lower part; the header bar background
246
+ // fades in over the image as the screen scrolls (mirrors Compose's
247
+ // ScreenContent for HeaderType.Animated).
248
+ ZStack(alignment: .top) {
249
+ StackScreenContent(
250
+ scrollable: scrollable,
251
+ scrollOffset: $scrollOffset,
252
+ scrollToTopCallback: scrollToTopCallback,
253
+ animatedSearchInset: animatedSearchInset,
254
+ content: { animatedHeroBody(animatedHeader) }
255
+ )
256
+ .edgesIgnoringSafeArea(.all)
257
+
258
+ header
259
+ .background(
260
+ Colors.black01
261
+ .opacity(scrollOpacity)
262
+ .edgesIgnoringSafeArea(.top)
263
+ )
264
+ .zIndex(100)
265
+ }
266
+ } else if headerTransparent && fullScreenContent {
267
+ ZStack(alignment: .top) {
268
+ header
269
+ .background(
270
+ NavigationHeaderBackground(
271
+ headerType: headerType,
272
+ scrollState: scrollOffset,
273
+ headerTransparent: headerTransparent,
274
+ fullScreenContent: fullScreenContent
275
+ )
276
+ )
277
+ .zIndex(100)
278
+
279
+ StackScreenContent(scrollable: scrollable, scrollOffset: $scrollOffset, scrollToTopCallback: scrollToTopCallback, animatedSearchInset: animatedSearchInset, content: content)
280
+ .edgesIgnoringSafeArea(.all)
281
+ }
282
+ } else {
283
+ header
284
+ .background(
285
+ NavigationHeaderBackground(
286
+ headerType: headerType,
287
+ scrollState: scrollOffset,
288
+ headerTransparent: headerTransparent,
289
+ fullScreenContent: fullScreenContent
290
+ )
291
+ .edgesIgnoringSafeArea(.top)
292
+ )
293
+
294
+ StackScreenContent(scrollable: scrollable, scrollOffset: $scrollOffset, scrollToTopCallback: scrollToTopCallback, animatedSearchInset: animatedSearchInset, content: content)
295
+ }
296
+ }
297
+
298
+ /// Hero image + body for an animated header. The image fills the width at its
299
+ /// aspect-ratio height; the body is offset below the header band so it overlaps
300
+ /// the lower part of the image (mirrors Compose's image box + padded content).
301
+ @ViewBuilder
302
+ private func animatedHeroBody(_ animatedHeader: AnimatedHeader) -> some View {
303
+ let width = UIScreen.main.bounds.width
304
+ let imageHeight = width / animatedHeader.aspectRatio.value
305
+ let contentTopInset = safeAreaTopInset() + HEADER_HEIGHT + layoutOffset
306
+
307
+ ZStack(alignment: .top) {
308
+ animatedHeader.composable(scrollOffset)
309
+ .frame(width: width, height: imageHeight)
310
+ .clipped()
311
+
312
+ content()
313
+ .padding(.top, contentTopInset)
314
+ }
315
+ .frame(maxWidth: .infinity, alignment: .top)
316
+ }
317
+
318
+ private var header: NavigationHeader {
319
+ NavigationHeader(
320
+ headerType: headerType,
321
+ titlePosition: titlePosition,
322
+ title: title,
323
+ headerTitle: headerTitle,
324
+ headerRight: headerRight,
325
+ goBack: goBack,
326
+ opacity: scrollOpacity,
327
+ animatedHeader: animatedHeader,
328
+ scrollState: scrollOffset,
329
+ // For the animated search the bar is rendered by the overlay, so the
330
+ // header shows the (fading) title instead of an inline search field.
331
+ inputSearchProps: inputSearchType == .header ? inputSearchProps : nil,
332
+ tintColor: tintColor,
333
+ titleOpacity: titleOpacity
334
+ )
335
+ }
336
+ }
337
+
338
+ // MARK: - Screen content (scrollable body)
339
+
340
+ /// Mirrors Compose's `ScreenContent`/`MainContent`: renders the screen body and,
341
+ /// when scrollable, tracks the scroll offset so the header/FAB can react to it.
342
+ private struct StackScreenContent<Content: View>: View {
343
+ let scrollable: Bool
344
+ @Binding var scrollOffset: CGFloat
345
+ /// Mirrors Compose's `ScrollData.scrollToTopCallback`; fired alongside the
346
+ /// scroll-to-top triggered by a status-bar tap.
347
+ let scrollToTopCallback: (() -> Void)?
348
+ /// Extra top space reserved for the animated search bar (mirrors Compose's
349
+ /// MainContent animated top spacer). Zero for non-animated screens.
350
+ var animatedSearchInset: CGFloat = 0
351
+ let content: () -> Content
352
+
353
+ private let topAnchorID = "stack_screen_top_anchor"
354
+ private let scrollSpace = "stack_screen_scroll_space"
355
+
356
+ var body: some View {
357
+ if scrollable {
358
+ ScrollViewReader { proxy in
359
+ ScrollView(.vertical, showsIndicators: false) {
360
+ VStack(spacing: 0) {
361
+ // Top probe + scroll-to-top anchor. Measured in the scroll's
362
+ // own coordinate space so the offset is a clean 0-based
363
+ // "pixels scrolled" value, independent of the inset below.
364
+ Color.clear
365
+ .frame(height: 0)
366
+ .id(topAnchorID)
367
+ .background(GeometryReader { geo in
368
+ Color.clear
369
+ .onAppear {
370
+ scrollOffset = -geo.frame(in: .named(scrollSpace)).minY
371
+ }
372
+ .onChange(of: geo.frame(in: .named(scrollSpace)).minY) { minY in
373
+ scrollOffset = -minY
374
+ }
375
+ })
376
+
377
+ if animatedSearchInset > 0 {
378
+ Spacer().frame(height: animatedSearchInset)
379
+ }
380
+
381
+ VStack {
382
+ content()
383
+ }
384
+ }
385
+ }
386
+ .coordinateSpace(name: scrollSpace)
387
+ // Status-bar tap (via MoMoStatusBarTap swizzle) scrolls to top and
388
+ // notifies the consumer, mirroring Compose's scrollToTopCallback.
389
+ .onReceive(NotificationCenter.default.publisher(for: MoMoStatusBarTap.notificationName)) { _ in
390
+ withAnimation {
391
+ proxy.scrollTo(topAnchorID, anchor: .top)
392
+ }
393
+ scrollToTopCallback?()
394
+ }
395
+ }
396
+ } else {
397
+ content()
398
+ }
399
+ }
400
+ }
401
+
402
+ // MARK: - Animated search header
403
+
404
+ /// Mirrors Compose's `SearchAnimated`: the search bar slides from below the
405
+ /// header (scroll 0) up into the header band as the user scrolls, while its
406
+ /// horizontal insets grow to make room for the back button and header-right.
407
+ /// Note: Compose's scroll-end snap and background-color lerp are not ported.
408
+ private struct SearchAnimatedHeader: View {
409
+ let props: InputSearchProps
410
+ let scrollOffset: CGFloat
411
+ let hiddenBack: Bool
412
+ let hasHeaderRight: Bool
413
+ let headerRightWidth: CGFloat
414
+ let headerHeight: CGFloat
415
+
416
+ var body: some View {
417
+ let scroll = max(scrollOffset, 0)
418
+
419
+ // Insets are measured from the safe-area top (status bar handled by layout).
420
+ let minTop: CGFloat = 0
421
+ let maxTop = headerHeight
422
+ let minSide = Spacing.M
423
+ let maxStart: CGFloat = hiddenBack ? Spacing.M : 48
424
+ let maxEnd = headerRightWidth + (hasHeaderRight ? Spacing.M * 2 : Spacing.M)
425
+
426
+ let top = min(max(maxTop - scroll, minTop), maxTop)
427
+ let start = min(max(minSide + scroll, minSide), maxStart)
428
+ let end = min(max(minSide + scroll, minSide), maxEnd)
429
+
430
+ VStack(spacing: 0) {
431
+ Spacer().frame(height: top)
432
+ InputSearch(props: props)
433
+ .frame(height: headerHeight)
434
+ .padding(.leading, start)
435
+ .padding(.trailing, end)
436
+ Spacer(minLength: 0)
437
+ }
438
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
439
+ }
440
+ }
441
+
442
+ // MARK: - Floating content
443
+
444
+ /// Mirrors Compose's `FloatingContent`: no FAB props means nothing renders.
445
+ private struct FloatingContent: View {
446
+ let fabProps: FabProps?
447
+ let keyboardOffset: CGFloat
448
+ let scrollOffset: CGFloat
449
+
450
+ var body: some View {
451
+ if let fabProps = fabProps {
452
+ FloatingButton(
453
+ props: fabProps,
454
+ keyboardOffset: keyboardOffset,
455
+ scrollOffset: scrollOffset
456
+ )
457
+ .padding(.horizontal, Spacing.M)
458
+ .ignoresSafeArea(.keyboard, edges: .bottom)
459
+ }
460
+ }
461
+ }
462
+
463
+ // MARK: - Hide keyboard on tap
464
+
465
+ /// Mirrors Compose's `Modifier.hideKeyboardOnTap()`. When enabled, a tap
466
+ /// anywhere resigns the first responder. Uses `simultaneousGesture` so the tap
467
+ /// is not consumed — buttons and text fields keep working.
468
+ private struct HideKeyboardOnTap: ViewModifier {
469
+ let enabled: Bool
470
+
471
+ func body(content: Content) -> some View {
472
+ if enabled {
473
+ content.simultaneousGesture(
474
+ TapGesture().onEnded {
475
+ UIApplication.shared.sendAction(
476
+ #selector(UIResponder.resignFirstResponder),
477
+ to: nil, from: nil, for: nil
478
+ )
479
+ }
480
+ )
481
+ } else {
482
+ content
483
+ }
484
+ }
485
+ }
486
+
487
+ // MARK: - Footer content
488
+
489
+ /// Mirrors Compose's `FooterContent`: renders the footer chrome only when a
490
+ /// footer component is supplied. Reuses the existing `Footer` view from Screen.swift.
491
+ private struct FooterContent: View {
492
+ let footer: (() -> AnyView)?
493
+ let keyboardSize: CGFloat
494
+ let bottomInset: CGFloat
495
+
496
+ var body: some View {
497
+ if let footerView = footer?() {
498
+ Footer(footerView: footerView, keyboardSize: keyboardSize, bottomInset: bottomInset)
499
+ }
500
+ }
501
+ }
@@ -0,0 +1,193 @@
1
+ import SwiftUI
2
+
3
+ let LOGO_MOMO_URL =
4
+ "https://static.momocdn.net/app/img/kits/logo_momo_circle.png"
5
+ let ICON_SUPPORT_DEFAULT_URL =
6
+ "https://static.momocdn.net/app/img/kits/_indicator.png"
7
+
8
+ public enum AvatarSize {
9
+ case size24
10
+ case size32
11
+ case size40
12
+ case size48
13
+ case size56
14
+ case size72
15
+
16
+ var sizeDp: CGFloat {
17
+ switch self {
18
+ case .size24: return 24
19
+ case .size32: return 32
20
+ case .size40: return 40
21
+ case .size48: return 48
22
+ case .size56: return 56
23
+ case .size72: return 72
24
+ }
25
+ }
26
+
27
+ var iconSizeDp: CGFloat {
28
+ switch self {
29
+ case .size24: return 16
30
+ case .size32: return 16
31
+ case .size40: return 24
32
+ case .size48: return 32
33
+ case .size56: return 32
34
+ case .size72: return 40
35
+ }
36
+ }
37
+ }
38
+
39
+ public struct Avatar: View {
40
+ @Environment(\.appTheme) private var theme
41
+
42
+ // MARK: Lifecycle
43
+
44
+ public init(
45
+ size: AvatarSize = .size32,
46
+ rounded: Bool = true,
47
+ name: String? = nil,
48
+ image: String? = nil,
49
+ showIconMomo: Bool = false,
50
+ showIconSupport: Bool = false,
51
+ iconSupport: String = "https://static.momocdn.net/app/img/kits/_indicator.png",
52
+ accessibilityLabel: String? = nil
53
+ ) {
54
+ self.size = size
55
+ self.rounded = rounded
56
+ self.name = name
57
+ self.image = image
58
+ self.showIconMomo = showIconMomo
59
+ self.showIconSupport = showIconSupport
60
+ self.iconSupport = iconSupport
61
+ self.accessibilityLabel = accessibilityLabel
62
+ }
63
+
64
+ // MARK: Public
65
+
66
+ public var body: some View {
67
+ ZStack {
68
+ // Inner content clipped by shape with border
69
+ ZStack {
70
+ content
71
+ }
72
+ .frame(width: size.sizeDp, height: size.sizeDp)
73
+ .background(Colors.pink09)
74
+ .clipShape(RoundedRectangle(cornerRadius: cornerRadius))
75
+ .overlay(
76
+ RoundedRectangle(cornerRadius: cornerRadius)
77
+ .stroke(theme.colors.border.default, lineWidth: 1)
78
+ )
79
+
80
+ // Badge icons — outside clip so they are not cut by border
81
+ if shouldShowIconTop {
82
+ ImageView(LOGO_MOMO_URL)
83
+ .frame(width: badgeSizeDp, height: badgeSizeDp)
84
+ .clipShape(RoundedRectangle(cornerRadius: badgeCornerRadius))
85
+ .offset(x: 1, y: -1)
86
+ .frame(
87
+ width: size.sizeDp,
88
+ height: size.sizeDp,
89
+ alignment: .topTrailing
90
+ )
91
+ .zIndex(1)
92
+ }
93
+
94
+ if shouldShowIconBottom {
95
+ ImageView(iconSupport)
96
+ .frame(width: badgeSizeDp, height: badgeSizeDp)
97
+ .clipShape(RoundedRectangle(cornerRadius: badgeCornerRadius))
98
+ .offset(x: 1, y: 1)
99
+ .frame(
100
+ width: size.sizeDp,
101
+ height: size.sizeDp,
102
+ alignment: .bottomTrailing
103
+ )
104
+ .zIndex(1)
105
+ }
106
+ }
107
+ .frame(width: size.sizeDp, height: size.sizeDp)
108
+ .accessibilityElement(children: .ignore)
109
+ .accessibilityLabel(automationId)
110
+ }
111
+
112
+ // MARK: Internal
113
+
114
+ let size: AvatarSize
115
+ let rounded: Bool
116
+ let name: String?
117
+ let image: String?
118
+ let showIconMomo: Bool
119
+ let showIconSupport: Bool
120
+ let iconSupport: String
121
+ let accessibilityLabel: String?
122
+
123
+ @ViewBuilder
124
+ private var content: some View {
125
+ if let image {
126
+ ImageView(image, contentMode: .fill)
127
+ .frame(width: size.sizeDp, height: size.sizeDp)
128
+ .clipShape(RoundedRectangle(cornerRadius: cornerRadius))
129
+ } else if let name {
130
+ MomoText(
131
+ Self.avatarInitials(name),
132
+ typography: .descriptionXsRegular,
133
+ color: Colors.pink03
134
+ )
135
+ } else {
136
+ Icon(
137
+ source: "basic_person",
138
+ size: size.iconSizeDp,
139
+ color: Colors.pink03
140
+ )
141
+ }
142
+ }
143
+
144
+ private var cornerRadius: CGFloat {
145
+ rounded ? size.sizeDp / 2 : Spacing.XS
146
+ }
147
+
148
+ private var badgeSizeDp: CGFloat {
149
+ size.iconSizeDp / 2
150
+ }
151
+
152
+ private var badgeCornerRadius: CGFloat {
153
+ size.iconSizeDp / 4
154
+ }
155
+
156
+ private var shouldShowIconTop: Bool {
157
+ showIconMomo && rounded
158
+ }
159
+
160
+ private var shouldShowIconBottom: Bool {
161
+ showIconSupport && rounded
162
+ }
163
+
164
+ private var automationId: String {
165
+ accessibilityLabel ?? "Avatar\(name != nil ? "/\(name!)" : "")"
166
+ }
167
+
168
+ private static func avatarInitials(_ name: String) -> String {
169
+ let words = name.trimmingCharacters(in: .whitespaces)
170
+ .split(separator: " ")
171
+ .filter { !$0.isEmpty }
172
+ return words.suffix(2)
173
+ .compactMap { $0.first?.uppercased() }
174
+ .joined()
175
+ }
176
+ }
177
+
178
+ // MARK: - Preview
179
+ #if DEBUG
180
+ struct Avatar_Previews: PreviewProvider {
181
+ static var previews: some View {
182
+ HStack(spacing: 12) {
183
+ Avatar(size: .size40)
184
+ Avatar(size: .size40, name: "Nguyen Van A")
185
+ Avatar(size: .size40, rounded: false, name: "John Doe")
186
+ Avatar(size: .size56, image: "https://i.pravatar.cc/150?img=1", showIconMomo: true)
187
+ Avatar(size: .size56, showIconSupport: true)
188
+ }
189
+ .padding()
190
+ .previewLayout(.sizeThatFits)
191
+ }
192
+ }
193
+ #endif