@momo-kits/native-kits 0.162.2-debug → 0.162.2-sp.1
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/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 +77 -5
- package/ios/Typography/Typography.swift +9 -0
- package/ios/Uploader/Uploader.swift +183 -0
- package/ios/native-kits.podspec +58 -8
- package/ios-demo/MoMoUIKitsDemo.podspec +18 -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/package.json +1 -1
- package/publish.sh +50 -0
- package/scripts/gen-ios-color.mjs +136 -0
- package/build.gradle.kts +0 -11
- package/compose/build.gradle.kts +0 -186
- package/compose/build.gradle.kts.backup +0 -186
- package/compose/compose.podspec +0 -47
- package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +0 -6
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +0 -123
- 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 +0 -57
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +0 -109
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -201
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -222
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -48
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -305
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +0 -921
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -121
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -403
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +0 -157
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +0 -85
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +0 -32
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +0 -340
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +0 -198
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +0 -357
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +0 -123
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -94
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +0 -136
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +0 -224
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +0 -543
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +0 -23
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +0 -76
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +0 -148
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +0 -188
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +0 -116
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +0 -452
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +0 -172
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +0 -255
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +0 -235
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +0 -233
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +0 -259
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +0 -241
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +0 -364
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +0 -108
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +0 -56
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +0 -41
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +0 -92
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +0 -40
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +0 -352
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +0 -103
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +0 -338
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +0 -70
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +0 -87
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +0 -17
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +0 -96
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +0 -348
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +0 -256
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +0 -494
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +0 -131
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +0 -215
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +0 -96
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +0 -531
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +0 -92
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +0 -135
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +0 -214
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +0 -590
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +0 -177
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +0 -192
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +0 -205
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +0 -29
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +0 -239
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +0 -191
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +0 -306
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +0 -12
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +0 -16
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +0 -188
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +0 -285
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +0 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +0 -35
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +0 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +0 -57
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +0 -68
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +0 -11
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +0 -14
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +0 -50
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +0 -51
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +0 -253
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +0 -133
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +0 -104
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +0 -145
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +0 -345
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +0 -8
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +0 -556
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +0 -161
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +0 -243
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +0 -86
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +0 -187
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +0 -315
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +0 -80
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +0 -306
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +0 -34
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +0 -370
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +0 -131
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +0 -167
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +0 -72
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +0 -66
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +0 -1329
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +0 -70
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +0 -15
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +0 -105
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +0 -33
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +0 -186
- package/gradle/libs.versions.toml +0 -67
- package/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/gradle/wrapper/gradle-wrapper.properties +0 -8
- package/gradle.properties +0 -26
- package/gradlew +0 -252
- package/gradlew.bat +0 -94
- package/settings.gradle.kts +0 -64
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
// MARK: - Constants
|
|
4
|
+
|
|
5
|
+
private let datePickerHeight: CGFloat = 210
|
|
6
|
+
private let datePickerWithLabelsHeight: CGFloat = 238
|
|
7
|
+
|
|
8
|
+
// MARK: - DateTimePicker
|
|
9
|
+
|
|
10
|
+
/// A wheel-style date/time picker matching Compose `DateTimePicker`.
|
|
11
|
+
///
|
|
12
|
+
/// The `format` string drives which columns are shown:
|
|
13
|
+
/// - `DD` day, `MM` month, `YYYY` year, `HH` hour, `mm` minute.
|
|
14
|
+
/// - `"HH:mm"` shows hour + minute; a lone `"HH"` switches the hour wheel to a
|
|
15
|
+
/// 12-hour clock with an AM/PM column.
|
|
16
|
+
public struct DateTimePicker: View {
|
|
17
|
+
// MARK: Lifecycle
|
|
18
|
+
|
|
19
|
+
public init(
|
|
20
|
+
format: String = "DD-MM-YYYY",
|
|
21
|
+
minuteInterval: Int = 1,
|
|
22
|
+
selectedValue: Date? = nil,
|
|
23
|
+
minDate: Date? = nil,
|
|
24
|
+
maxDate: Date? = nil,
|
|
25
|
+
arrayLabelTime: [String] = [],
|
|
26
|
+
onChange: @escaping (Date) -> Void
|
|
27
|
+
) {
|
|
28
|
+
self.format = format
|
|
29
|
+
self.minuteInterval = minuteInterval
|
|
30
|
+
self.selectedValue = selectedValue
|
|
31
|
+
self.minDate = minDate
|
|
32
|
+
self.maxDate = maxDate
|
|
33
|
+
self.arrayLabelTime = arrayLabelTime
|
|
34
|
+
self.onChange = onChange
|
|
35
|
+
|
|
36
|
+
let calendar = Calendar.current
|
|
37
|
+
let effectiveSelected = selectedValue ?? DateTimePickerUtils.getCurrentDateTime()
|
|
38
|
+
let effectiveMin = minDate ?? DateTimePickerUtils.createRelativeDate(years: -10)
|
|
39
|
+
let effectiveMax = maxDate ?? DateTimePickerUtils.createRelativeDate(years: 10)
|
|
40
|
+
|
|
41
|
+
// needCheckRange is true only when the caller supplied no bounds.
|
|
42
|
+
let needCheckRange = (minDate == nil && maxDate == nil)
|
|
43
|
+
|
|
44
|
+
let initialValue: Date
|
|
45
|
+
if !needCheckRange {
|
|
46
|
+
initialValue = effectiveSelected
|
|
47
|
+
} else if effectiveSelected < effectiveMin {
|
|
48
|
+
initialValue = effectiveMin
|
|
49
|
+
} else if effectiveSelected > effectiveMax {
|
|
50
|
+
initialValue = effectiveMax
|
|
51
|
+
} else {
|
|
52
|
+
initialValue = effectiveSelected
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let comps = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: initialValue)
|
|
56
|
+
self._currentData = State(initialValue: DateTimePickerData(
|
|
57
|
+
day: comps.day ?? 1,
|
|
58
|
+
month: comps.month ?? 1,
|
|
59
|
+
year: comps.year ?? 2000,
|
|
60
|
+
hour: comps.hour ?? 0,
|
|
61
|
+
minute: comps.minute ?? 0
|
|
62
|
+
))
|
|
63
|
+
|
|
64
|
+
self.effectiveMinComponents = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: effectiveMin)
|
|
65
|
+
self.effectiveMaxComponents = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: effectiveMax)
|
|
66
|
+
self.needCheckRange = needCheckRange
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// MARK: Public
|
|
70
|
+
|
|
71
|
+
public var body: some View {
|
|
72
|
+
let components = DateTimePickerUtils.getDateComponents(
|
|
73
|
+
format: format,
|
|
74
|
+
currentData: currentData,
|
|
75
|
+
minDate: effectiveMinComponents,
|
|
76
|
+
maxDate: effectiveMaxComponents,
|
|
77
|
+
minuteInterval: minuteInterval
|
|
78
|
+
)
|
|
79
|
+
let pickerHeight = arrayLabelTime.isEmpty ? datePickerHeight : datePickerWithLabelsHeight
|
|
80
|
+
|
|
81
|
+
return HStack(spacing: Spacing.M) {
|
|
82
|
+
ForEach(Array(components.enumerated()), id: \.offset) { index, component in
|
|
83
|
+
let hasLabel = index < arrayLabelTime.count && !arrayLabelTime[index].isEmpty
|
|
84
|
+
|
|
85
|
+
VStack(spacing: 0) {
|
|
86
|
+
if hasLabel {
|
|
87
|
+
MomoText(arrayLabelTime[index], typography: .actionSBold, color: Colors.text)
|
|
88
|
+
.padding(.bottom, Spacing.S)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
WheelPicker(
|
|
92
|
+
name: component.name,
|
|
93
|
+
data: component.data,
|
|
94
|
+
selectedData: selectedDataFor(component.name),
|
|
95
|
+
onChange: onWheelChange
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
.frame(maxWidth: .infinity)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
.frame(maxWidth: .infinity)
|
|
102
|
+
.frame(height: pickerHeight)
|
|
103
|
+
.background(Colors.background)
|
|
104
|
+
.padding(.horizontal, Spacing.M)
|
|
105
|
+
.onAppear { emitIfValid() }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// MARK: Internal
|
|
109
|
+
|
|
110
|
+
let format: String
|
|
111
|
+
let minuteInterval: Int
|
|
112
|
+
let selectedValue: Date?
|
|
113
|
+
let minDate: Date?
|
|
114
|
+
let maxDate: Date?
|
|
115
|
+
let arrayLabelTime: [String]
|
|
116
|
+
let onChange: (Date) -> Void
|
|
117
|
+
|
|
118
|
+
@State private var currentData: DateTimePickerData
|
|
119
|
+
|
|
120
|
+
private let effectiveMinComponents: DateComponents
|
|
121
|
+
private let effectiveMaxComponents: DateComponents
|
|
122
|
+
private let needCheckRange: Bool
|
|
123
|
+
|
|
124
|
+
private func selectedDataFor(_ name: String) -> String {
|
|
125
|
+
switch name {
|
|
126
|
+
case "day": return DateTimePickerUtils.paddingNum(currentData.day)
|
|
127
|
+
case "month": return DateTimePickerUtils.paddingNum(currentData.month)
|
|
128
|
+
case "year": return String(currentData.year)
|
|
129
|
+
case "hour": return DateTimePickerUtils.paddingNum(currentData.hour)
|
|
130
|
+
case "minute": return DateTimePickerUtils.paddingNum(currentData.minute)
|
|
131
|
+
case "timeMode": return currentData.timeMode
|
|
132
|
+
default: return ""
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// Mirrors Compose `onWheelChangeValue`: applies value and coerces day/month
|
|
137
|
+
/// to stay within the effective min/max bounds.
|
|
138
|
+
private func onWheelChange(_ name: String, _ value: String) {
|
|
139
|
+
var day = name == "day" ? (Int(value) ?? currentData.day) : currentData.day
|
|
140
|
+
var month = name == "month" ? (Int(value) ?? currentData.month) : currentData.month
|
|
141
|
+
let year = name == "year" ? (Int(value) ?? currentData.year) : currentData.year
|
|
142
|
+
let hour = name == "hour" ? (Int(value) ?? currentData.hour) : currentData.hour
|
|
143
|
+
let minute = name == "minute" ? (Int(value) ?? currentData.minute) : currentData.minute
|
|
144
|
+
let timeMode = name == "timeMode" ? value : currentData.timeMode
|
|
145
|
+
|
|
146
|
+
let maxYear = effectiveMaxComponents.year ?? year
|
|
147
|
+
let minYear = effectiveMinComponents.year ?? year
|
|
148
|
+
let maxMonth = effectiveMaxComponents.month ?? 12
|
|
149
|
+
let minMonth = effectiveMinComponents.month ?? 1
|
|
150
|
+
let maxDayBound = effectiveMaxComponents.day ?? 31
|
|
151
|
+
let minDayBound = effectiveMinComponents.day ?? 1
|
|
152
|
+
|
|
153
|
+
if name == "year" {
|
|
154
|
+
if year == maxYear {
|
|
155
|
+
month = min(max(month, 1), maxMonth)
|
|
156
|
+
} else if year == minYear {
|
|
157
|
+
month = min(max(month, minMonth), 12)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if name == "month" || name == "year" {
|
|
162
|
+
let maxDayOfMonth: Int
|
|
163
|
+
switch month {
|
|
164
|
+
case 4, 6, 9, 11: maxDayOfMonth = 30
|
|
165
|
+
case 2: maxDayOfMonth = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? 29 : 28
|
|
166
|
+
default: maxDayOfMonth = 31
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if year == maxYear && month == maxMonth {
|
|
170
|
+
day = min(max(day, 1), maxDayBound)
|
|
171
|
+
} else if year == minYear && month == minMonth {
|
|
172
|
+
day = min(max(day, minDayBound), maxDayOfMonth)
|
|
173
|
+
} else if day > maxDayOfMonth {
|
|
174
|
+
day = maxDayOfMonth
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
currentData = DateTimePickerData(
|
|
179
|
+
day: day,
|
|
180
|
+
month: month,
|
|
181
|
+
year: year,
|
|
182
|
+
hour: hour,
|
|
183
|
+
minute: minute,
|
|
184
|
+
timeMode: timeMode
|
|
185
|
+
)
|
|
186
|
+
emitIfValid()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// Emit `onChange` only when the assembled date is valid and (when bounds were
|
|
190
|
+
/// not supplied) within the effective min/max range. Mirrors the Compose
|
|
191
|
+
/// `snapshotFlow.mapNotNull` filter.
|
|
192
|
+
private func emitIfValid() {
|
|
193
|
+
guard let date = currentData.toDate() else { return }
|
|
194
|
+
if !needCheckRange {
|
|
195
|
+
onChange(date)
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
let calendar = Calendar.current
|
|
199
|
+
guard
|
|
200
|
+
let minDate = calendar.date(from: effectiveMinComponents),
|
|
201
|
+
let maxDate = calendar.date(from: effectiveMaxComponents)
|
|
202
|
+
else {
|
|
203
|
+
onChange(date)
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
if date >= minDate && date <= maxDate {
|
|
207
|
+
onChange(date)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
// MARK: - PickerData
|
|
4
|
+
|
|
5
|
+
/// Internal mutable representation of the picker's current selection.
|
|
6
|
+
/// Mirrors Compose `PickerData`.
|
|
7
|
+
public struct DateTimePickerData: Equatable {
|
|
8
|
+
public var day: Int
|
|
9
|
+
public var month: Int
|
|
10
|
+
public var year: Int
|
|
11
|
+
public var hour: Int
|
|
12
|
+
public var minute: Int
|
|
13
|
+
public var timeMode: String
|
|
14
|
+
|
|
15
|
+
public init(
|
|
16
|
+
day: Int,
|
|
17
|
+
month: Int,
|
|
18
|
+
year: Int,
|
|
19
|
+
hour: Int = 0,
|
|
20
|
+
minute: Int = 0,
|
|
21
|
+
timeMode: String = ""
|
|
22
|
+
) {
|
|
23
|
+
self.day = day
|
|
24
|
+
self.month = month
|
|
25
|
+
self.year = year
|
|
26
|
+
self.hour = hour
|
|
27
|
+
self.minute = minute
|
|
28
|
+
self.timeMode = timeMode
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Convert to a `DateComponents` based value. Returns `nil` if the date is invalid.
|
|
32
|
+
public func toDate(calendar: Calendar = Calendar.current) -> Date? {
|
|
33
|
+
var components = DateComponents()
|
|
34
|
+
components.year = year
|
|
35
|
+
components.month = month
|
|
36
|
+
components.day = day
|
|
37
|
+
components.hour = hour
|
|
38
|
+
components.minute = minute
|
|
39
|
+
components.second = 0
|
|
40
|
+
return calendar.date(from: components)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// MARK: - DateComponent
|
|
45
|
+
|
|
46
|
+
/// A single wheel column descriptor. Mirrors Compose `DateComponent`.
|
|
47
|
+
public struct DateTimePickerComponent: Equatable {
|
|
48
|
+
public let name: String
|
|
49
|
+
public let data: [String]
|
|
50
|
+
|
|
51
|
+
public init(name: String, data: [String]) {
|
|
52
|
+
self.name = name
|
|
53
|
+
self.data = data
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
// MARK: - DateTimePickerUtils
|
|
4
|
+
|
|
5
|
+
/// Pure helper functions mirroring Compose `DateTimePickerUtils.kt`.
|
|
6
|
+
public enum DateTimePickerUtils {
|
|
7
|
+
|
|
8
|
+
/// Time mode list for 12-hour clock (only-hour format).
|
|
9
|
+
public static let timeMode: [String] = ["AM", "PM"]
|
|
10
|
+
|
|
11
|
+
/// Pad a number with a leading zero if it's less than 10.
|
|
12
|
+
public static func paddingNum(_ num: Int) -> String {
|
|
13
|
+
return num > 9 ? String(num) : "0\(num)"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/// Format a `Date` into a string, matching Compose `formatLocalDateTime`.
|
|
17
|
+
public static func formatDate(
|
|
18
|
+
_ date: Date,
|
|
19
|
+
format: String = "yyyy-MM-dd HH:mm",
|
|
20
|
+
calendar: Calendar = Calendar.current
|
|
21
|
+
) -> String {
|
|
22
|
+
let c = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: date)
|
|
23
|
+
let year = c.year ?? 0
|
|
24
|
+
let month = c.month ?? 1
|
|
25
|
+
let day = c.day ?? 1
|
|
26
|
+
let hour = c.hour ?? 0
|
|
27
|
+
let minute = c.minute ?? 0
|
|
28
|
+
|
|
29
|
+
switch format {
|
|
30
|
+
case "yyyy-MM-dd HH:mm":
|
|
31
|
+
return "\(year)-\(paddingNum(month))-\(paddingNum(day)) \(paddingNum(hour)):\(paddingNum(minute))"
|
|
32
|
+
case "dd-MM-yyyy":
|
|
33
|
+
return "\(paddingNum(day))-\(paddingNum(month))-\(year)"
|
|
34
|
+
case "HH:mm":
|
|
35
|
+
return "\(paddingNum(hour)):\(paddingNum(minute))"
|
|
36
|
+
default:
|
|
37
|
+
return "\(year)-\(paddingNum(month))-\(paddingNum(day)) \(paddingNum(hour)):\(paddingNum(minute))"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Number of days in a given month/year.
|
|
42
|
+
private static func daysInMonth(year: Int, month: Int) -> Int {
|
|
43
|
+
switch month {
|
|
44
|
+
case 1, 3, 5, 7, 8, 10, 12: return 31
|
|
45
|
+
case 4, 6, 9, 11: return 30
|
|
46
|
+
case 2: return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? 29 : 28
|
|
47
|
+
default: return 31
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Get a list of day strings constrained by min/max date.
|
|
52
|
+
public static func getDaysInMonth(
|
|
53
|
+
year: Int,
|
|
54
|
+
month: Int,
|
|
55
|
+
minDate: DateComponents,
|
|
56
|
+
maxDate: DateComponents
|
|
57
|
+
) -> [String] {
|
|
58
|
+
let maxDay = daysInMonth(year: year, month: month)
|
|
59
|
+
var startDay = 1
|
|
60
|
+
var endDay = maxDay
|
|
61
|
+
|
|
62
|
+
if year == minDate.year && month == minDate.month {
|
|
63
|
+
startDay = minDate.day ?? 1
|
|
64
|
+
}
|
|
65
|
+
if year == maxDate.year && month == maxDate.month {
|
|
66
|
+
endDay = maxDate.day ?? maxDay
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
guard startDay <= endDay else { return [paddingNum(startDay)] }
|
|
70
|
+
return (startDay...endDay).map { paddingNum($0) }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Get a list of month strings constrained by min/max date for a year.
|
|
74
|
+
public static func getMonths(
|
|
75
|
+
minDate: DateComponents,
|
|
76
|
+
maxDate: DateComponents,
|
|
77
|
+
currentYear: Int
|
|
78
|
+
) -> [String] {
|
|
79
|
+
var startMonth = 1
|
|
80
|
+
var endMonth = 12
|
|
81
|
+
|
|
82
|
+
if currentYear == minDate.year {
|
|
83
|
+
startMonth = minDate.month ?? 1
|
|
84
|
+
}
|
|
85
|
+
if currentYear == maxDate.year {
|
|
86
|
+
endMonth = maxDate.month ?? 12
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
guard startMonth <= endMonth else { return [paddingNum(startMonth)] }
|
|
90
|
+
return (startMonth...endMonth).map { paddingNum($0) }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Get a list of year strings between min and max date.
|
|
94
|
+
public static func getYears(minDate: DateComponents, maxDate: DateComponents) -> [String] {
|
|
95
|
+
let startYear = minDate.year ?? 0
|
|
96
|
+
let endYear = maxDate.year ?? 0
|
|
97
|
+
guard startYear <= endYear else { return [String(startYear)] }
|
|
98
|
+
return (startYear...endYear).map { String($0) }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// Get a list of hours for either a 12-hour or 24-hour clock.
|
|
102
|
+
public static func getHours(hourMode: Int) -> [String] {
|
|
103
|
+
var hours: [String] = []
|
|
104
|
+
for i in 0..<hourMode {
|
|
105
|
+
let hour = hourMode == 12 ? i + 1 : i
|
|
106
|
+
hours.append(paddingNum(hour))
|
|
107
|
+
}
|
|
108
|
+
return hours
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// Get a list of minutes stepped by `interval`.
|
|
112
|
+
public static func getMinutes(interval: Int) -> [String] {
|
|
113
|
+
let step = max(1, interval)
|
|
114
|
+
var minutes: [String] = []
|
|
115
|
+
var min = 0
|
|
116
|
+
while min < 60 {
|
|
117
|
+
minutes.append(paddingNum(min))
|
|
118
|
+
min += step
|
|
119
|
+
}
|
|
120
|
+
return minutes
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// Today's date.
|
|
124
|
+
public static func getCurrentDateTime() -> Date {
|
|
125
|
+
return Date()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// Create a date a certain number of years before/after now.
|
|
129
|
+
public static func createRelativeDate(years: Int, calendar: Calendar = Calendar.current) -> Date {
|
|
130
|
+
let now = Date()
|
|
131
|
+
return calendar.date(byAdding: .year, value: years, to: now) ?? now
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// Resolve the wheel columns from a format string. Mirrors Compose `getDateComponents`.
|
|
135
|
+
public static func getDateComponents(
|
|
136
|
+
format: String,
|
|
137
|
+
currentData: DateTimePickerData,
|
|
138
|
+
minDate: DateComponents,
|
|
139
|
+
maxDate: DateComponents,
|
|
140
|
+
minuteInterval: Int
|
|
141
|
+
) -> [DateTimePickerComponent] {
|
|
142
|
+
let formatParts = format.split(whereSeparator: { !$0.isLetter }).map { String($0) }
|
|
143
|
+
let isOnlyHour = formatParts.count == 1 && formatParts[0] == "HH"
|
|
144
|
+
|
|
145
|
+
let dayData = getDaysInMonth(year: currentData.year, month: currentData.month, minDate: minDate, maxDate: maxDate)
|
|
146
|
+
let monthData = getMonths(minDate: minDate, maxDate: maxDate, currentYear: currentData.year)
|
|
147
|
+
let yearData = getYears(minDate: minDate, maxDate: maxDate)
|
|
148
|
+
let hourData = getHours(hourMode: isOnlyHour ? 12 : 24)
|
|
149
|
+
let minData = getMinutes(interval: minuteInterval)
|
|
150
|
+
|
|
151
|
+
var components: [DateTimePickerComponent] = []
|
|
152
|
+
for part in formatParts {
|
|
153
|
+
switch part {
|
|
154
|
+
case "DD": components.append(DateTimePickerComponent(name: "day", data: dayData))
|
|
155
|
+
case "MM": components.append(DateTimePickerComponent(name: "month", data: monthData))
|
|
156
|
+
case "YYYY": components.append(DateTimePickerComponent(name: "year", data: yearData))
|
|
157
|
+
case "HH": components.append(DateTimePickerComponent(name: "hour", data: hourData))
|
|
158
|
+
case "mm": components.append(DateTimePickerComponent(name: "minute", data: minData))
|
|
159
|
+
default: break
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if isOnlyHour {
|
|
163
|
+
components.append(DateTimePickerComponent(name: "timeMode", data: timeMode))
|
|
164
|
+
}
|
|
165
|
+
return components
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
// MARK: - Constants
|
|
4
|
+
|
|
5
|
+
private let wheelItemHeight: CGFloat = 42
|
|
6
|
+
private let wheelVisibleCount: Int = 5
|
|
7
|
+
private let wheelPickerBorderWidth: CGFloat = 1
|
|
8
|
+
|
|
9
|
+
// MARK: - WheelPicker
|
|
10
|
+
|
|
11
|
+
/// A reusable single-column wheel picker, mirroring Compose `WheelPicker.kt`.
|
|
12
|
+
///
|
|
13
|
+
/// The data is padded with two empty rows at the top and bottom so the first/last
|
|
14
|
+
/// real value can be centered. The currently centered row is reported back via
|
|
15
|
+
/// `onChange(name, value)`.
|
|
16
|
+
public struct WheelPicker: View {
|
|
17
|
+
// MARK: Lifecycle
|
|
18
|
+
|
|
19
|
+
public init(
|
|
20
|
+
name: String,
|
|
21
|
+
data: [String],
|
|
22
|
+
selectedData: String,
|
|
23
|
+
onChange: @escaping (String, String) -> Void
|
|
24
|
+
) {
|
|
25
|
+
self.name = name
|
|
26
|
+
self.data = data
|
|
27
|
+
self.selectedData = selectedData
|
|
28
|
+
self.onChange = onChange
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// MARK: Public
|
|
32
|
+
|
|
33
|
+
public var body: some View {
|
|
34
|
+
let paddedData = ["", ""] + data + ["", ""]
|
|
35
|
+
|
|
36
|
+
return GeometryReader { geo in
|
|
37
|
+
let height = geo.size.height
|
|
38
|
+
ScrollViewReader { proxy in
|
|
39
|
+
ScrollView(.vertical, showsIndicators: false) {
|
|
40
|
+
LazyVStack(spacing: 0) {
|
|
41
|
+
ForEach(Array(paddedData.enumerated()), id: \.offset) { index, text in
|
|
42
|
+
WheelPickerItem(
|
|
43
|
+
text: text,
|
|
44
|
+
isSelected: index == selectedIndex,
|
|
45
|
+
index: index,
|
|
46
|
+
scrollOffset: scrollOffset,
|
|
47
|
+
viewportHeight: height
|
|
48
|
+
)
|
|
49
|
+
.frame(height: wheelItemHeight)
|
|
50
|
+
.id(index)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
.background(
|
|
54
|
+
GeometryReader { inner in
|
|
55
|
+
Color.clear.preference(
|
|
56
|
+
key: WheelOffsetKey.self,
|
|
57
|
+
value: inner.frame(in: .named(scrollSpace)).minY
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
.coordinateSpace(name: scrollSpace)
|
|
63
|
+
.onPreferenceChange(WheelOffsetKey.self) { value in
|
|
64
|
+
scrollOffset = value
|
|
65
|
+
updateSelection(paddedData: paddedData, viewportHeight: height)
|
|
66
|
+
}
|
|
67
|
+
.onAppear {
|
|
68
|
+
let initial = max(paddedData.firstIndex(of: selectedData) ?? 2, 2)
|
|
69
|
+
selectedIndex = initial
|
|
70
|
+
DispatchQueue.main.async {
|
|
71
|
+
proxy.scrollTo(initial, anchor: .center)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
.frame(height: wheelItemHeight * CGFloat(wheelVisibleCount))
|
|
77
|
+
.overlay(
|
|
78
|
+
RoundedRectangle(cornerRadius: Radius.S)
|
|
79
|
+
.stroke(Colors.border, lineWidth: wheelPickerBorderWidth)
|
|
80
|
+
)
|
|
81
|
+
.background(Colors.background.clipShape(RoundedRectangle(cornerRadius: Radius.S)))
|
|
82
|
+
.overlay(
|
|
83
|
+
// Centered translucent selection band.
|
|
84
|
+
Rectangle()
|
|
85
|
+
.fill(Colors.blue10)
|
|
86
|
+
.opacity(0.2)
|
|
87
|
+
.frame(height: wheelItemHeight)
|
|
88
|
+
.allowsHitTesting(false)
|
|
89
|
+
)
|
|
90
|
+
.clipShape(RoundedRectangle(cornerRadius: Radius.S))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// MARK: Internal
|
|
94
|
+
|
|
95
|
+
let name: String
|
|
96
|
+
let data: [String]
|
|
97
|
+
let selectedData: String
|
|
98
|
+
let onChange: (String, String) -> Void
|
|
99
|
+
|
|
100
|
+
@State private var selectedIndex: Int = 0
|
|
101
|
+
@State private var scrollOffset: CGFloat = 0
|
|
102
|
+
|
|
103
|
+
private let scrollSpace = "wheelScroll"
|
|
104
|
+
|
|
105
|
+
/// Map the current scroll offset to the centered index and report changes.
|
|
106
|
+
private func updateSelection(paddedData: [String], viewportHeight: CGFloat) {
|
|
107
|
+
// `scrollOffset` is the top of the content relative to the viewport (<= 0 when scrolled).
|
|
108
|
+
let centerY = viewportHeight / 2
|
|
109
|
+
let contentCenter = -scrollOffset + centerY
|
|
110
|
+
let rawIndex = Int((contentCenter / wheelItemHeight).rounded()) - 1
|
|
111
|
+
let clamped = max(2, min(rawIndex, paddedData.count - 3))
|
|
112
|
+
guard clamped != selectedIndex else { return }
|
|
113
|
+
selectedIndex = clamped
|
|
114
|
+
let value = paddedData[clamped]
|
|
115
|
+
if !value.isEmpty {
|
|
116
|
+
onChange(name, value)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// MARK: - WheelPickerItem
|
|
122
|
+
|
|
123
|
+
private struct WheelPickerItem: View {
|
|
124
|
+
let text: String
|
|
125
|
+
let isSelected: Bool
|
|
126
|
+
let index: Int
|
|
127
|
+
let scrollOffset: CGFloat
|
|
128
|
+
let viewportHeight: CGFloat
|
|
129
|
+
|
|
130
|
+
var body: some View {
|
|
131
|
+
let itemCenterInContent = CGFloat(index) * wheelItemHeight + wheelItemHeight / 2
|
|
132
|
+
let itemCenterInViewport = itemCenterInContent + scrollOffset
|
|
133
|
+
let distanceFromCenter = abs(itemCenterInViewport - viewportHeight / 2) / wheelItemHeight
|
|
134
|
+
|
|
135
|
+
let alpha: Double
|
|
136
|
+
switch distanceFromCenter {
|
|
137
|
+
case ..<1: alpha = 1.0
|
|
138
|
+
case ..<2: alpha = 0.8
|
|
139
|
+
default: alpha = 0.4
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let scale: CGFloat = isSelected ? 1.0 : 0.87
|
|
143
|
+
|
|
144
|
+
return ZStack {
|
|
145
|
+
MomoText(text, typography: .actionSBold, color: Colors.text)
|
|
146
|
+
.scaleEffect(scale)
|
|
147
|
+
}
|
|
148
|
+
.frame(maxWidth: .infinity)
|
|
149
|
+
.frame(height: wheelItemHeight)
|
|
150
|
+
.opacity(alpha)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// MARK: - WheelOffsetKey
|
|
155
|
+
|
|
156
|
+
private struct WheelOffsetKey: PreferenceKey {
|
|
157
|
+
static var defaultValue: CGFloat = 0
|
|
158
|
+
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
|
|
159
|
+
value = nextValue()
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import SwiftUI
|
|
3
|
+
|
|
4
|
+
public struct MomoDivider: View {
|
|
5
|
+
@Environment(\.appTheme) private var theme
|
|
6
|
+
|
|
7
|
+
public init() {}
|
|
8
|
+
|
|
9
|
+
public var body: some View {
|
|
10
|
+
Rectangle()
|
|
11
|
+
.fill(theme.colors.border.default)
|
|
12
|
+
.frame(maxWidth: .infinity)
|
|
13
|
+
.frame(height: 1)
|
|
14
|
+
.padding(.vertical, Spacing.XS)
|
|
15
|
+
}
|
|
16
|
+
}
|
package/ios/Icon/Icon.swift
CHANGED
|
@@ -23,6 +23,9 @@ func loadIconSources() -> [String: IconSource] {
|
|
|
23
23
|
// Load the JSON data into a global variable
|
|
24
24
|
let iconSources: [String: IconSource] = loadIconSources()
|
|
25
25
|
|
|
26
|
+
// Icons that must never be tinted (mirror Compose `noThemeIcons`)
|
|
27
|
+
let noThemeIcons: [String] = ["ic_round_momo_tiny"]
|
|
28
|
+
|
|
26
29
|
// Icon ViewModel
|
|
27
30
|
public struct Icon: View {
|
|
28
31
|
var source: String
|
|
@@ -44,7 +47,9 @@ public struct Icon: View {
|
|
|
44
47
|
|
|
45
48
|
public var body: some View {
|
|
46
49
|
if let uri = iconSources[source]?.uri {
|
|
47
|
-
|
|
50
|
+
// Mirror Compose: certain icons (e.g. branded glyphs) are never tinted
|
|
51
|
+
let resolvedColor = noThemeIcons.contains(source) ? nil : color
|
|
52
|
+
ImageView(uri, color: resolvedColor).frame(width: size, height: size)
|
|
48
53
|
}
|
|
49
54
|
}
|
|
50
55
|
}
|