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