@momo-kits/native-kits 0.0.1-beta.4 → 0.0.2-alpha.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.md +99 -0
- package/PUBLISH_GUIDE.md +675 -0
- package/README.md +184 -5
- package/ios/Application/ApplicationEnvironment.swift +71 -0
- package/ios/Application/Components.swift +270 -0
- package/ios/Application/ComposeApi.swift +22 -0
- package/ios/Application/FloatingButton.swift +174 -0
- package/ios/Application/HeaderRight.swift +306 -0
- package/ios/Application/LanguageSource.swift +93 -0
- package/ios/Application/Localize.swift +378 -0
- package/ios/Application/LocalizeModifier.swift +30 -0
- package/ios/Application/MaxApi.swift +18 -0
- package/ios/Application/Navigation/BottomTab/BottomTab.swift +104 -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 +213 -0
- package/ios/Application/Navigation/NavigationOptions.swift +109 -0
- package/ios/Application/Navigation/Navigator.swift +453 -0
- package/ios/Application/Navigation/NavigatorManager.swift +22 -0
- package/ios/Application/Navigation/Overplay/BottomSheet.swift +256 -0
- package/ios/Application/Navigation/Overplay/ModalScreen.swift +74 -0
- package/ios/Application/Navigation/Overplay/SnackBar.swift +144 -0
- package/ios/Application/Navigation/Tracking/ScreenTracker.swift +117 -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/Navigation/component/HeaderRightWidthKey.swift +11 -0
- package/ios/Application/Navigation/component/NavigationFooter.swift +90 -0
- package/ios/Application/Navigation/component/NavigationHeaderRight.swift +196 -0
- package/ios/Application/Screen.swift +250 -0
- package/ios/Application/StackScreen.swift +492 -0
- package/ios/Avatar/Avatar.swift +193 -0
- package/ios/Badge/Badge.swift +84 -0
- package/ios/Badge/BadgeDot.swift +31 -0
- package/ios/Badge/BadgeRibbon.swift +319 -0
- package/ios/BaselineView/BaselineView.swift +163 -0
- package/ios/Button/Button.swift +232 -84
- package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +126 -0
- package/ios/Carousel/Carousel.swift +196 -0
- package/ios/Checkbox/Checkbox.swift +77 -0
- package/ios/Chip/Chip.swift +120 -0
- package/ios/Collapse/Collapse.swift +192 -0
- package/ios/{Colors+Spacing → Colors+Radius+Spacing}/Colors.swift +42 -37
- package/ios/Colors+Radius+Spacing/Radius.swift +22 -0
- package/ios/{Colors+Spacing → Colors+Radius+Spacing}/Spacing.swift +3 -1
- package/ios/Colors+Radius+Spacing/Theme.swift +295 -0
- package/ios/DateTimePicker/DateTimePicker.swift +212 -0
- package/ios/DateTimePicker/DateTimePickerTypes.swift +55 -0
- package/ios/DateTimePicker/DateTimePickerUtils.swift +167 -0
- package/ios/DateTimePicker/WheelPicker.swift +165 -0
- package/ios/Divider/Divider.swift +16 -0
- package/ios/Extensions/ActiveOpacityButtonStyle.swift +14 -0
- package/ios/Extensions/Color++.swift +32 -0
- package/ios/Icon/Icon.swift +69 -0
- package/ios/IconButton/IconButton.swift +140 -0
- package/ios/Image/Image.swift +87 -0
- package/ios/Information/Information.swift +156 -0
- package/ios/Input/ErrorView.swift +58 -0
- package/ios/Input/Input.swift +461 -77
- package/ios/Input/InputPhoneNumber.swift +192 -0
- package/ios/Input/InputSearch.swift +301 -0
- package/ios/Input/InputTextArea.swift +318 -0
- package/ios/InputDropDown/InputDropDown.swift +201 -0
- package/ios/InputMoney/InputMoney.swift +405 -0
- package/ios/InputOTP/InputOTP.swift +211 -0
- package/ios/Layout/GridContext.swift +30 -0
- package/ios/Layout/Item.swift +42 -0
- package/ios/Layout/Section.swift +134 -0
- package/ios/Loader/Loader.swift +113 -0
- package/ios/Lottie/LottieView.swift +86 -0
- package/ios/MoMoUIKits.podspec +5 -0
- package/ios/OTPKeyboard/KeyboardButton.swift +13 -5
- package/ios/OTPKeyboard/OTPKeyboard.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 +279 -0
- package/ios/Popup/PopupInput.swift +92 -0
- package/ios/Popup/PopupNotify.swift +219 -0
- package/ios/Popup/PopupPromotion.swift +74 -0
- package/ios/PopupView/FullscreenPopup.swift +251 -0
- package/ios/{Popup → PopupView}/Modifiers.swift +6 -20
- package/ios/{Popup → PopupView}/PopupView.swift +38 -146
- package/ios/PopupView/Utils++.swift +281 -0
- package/ios/ProgressInfo/ProgressInfo.swift +297 -0
- package/ios/Radio/Radio.swift +69 -0
- package/ios/Rating/Rating.swift +83 -0
- package/{example/ios/Example/Preview Content/Preview Assets.xcassets/Contents.json → ios/Resources/MoMoUIKitsColors.xcassets/AccentColor.colorset/Contents.json} +5 -0
- package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Text.colorset/Contents.json +3 -3
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/Contents.json +9 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-default.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-disable.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-pressed.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-selected.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-surface.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-tonal.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/border-default.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/border-disable.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/gradient.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/primary.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/secondary.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-container.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-primary.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-secondary.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-default.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-disable.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-hint.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-secondary.colorset/Contents.json +38 -0
- package/ios/ScrollIndicator/ScrollIndicator.swift +110 -0
- package/ios/Skeleton/Skeleton.swift +99 -0
- package/ios/Slider/Slider.swift +237 -0
- package/ios/StatusBarTap/StatusBarTap.h +13 -0
- package/ios/StatusBarTap/StatusBarTap.m +75 -0
- package/ios/Stepper/Stepper.swift +221 -0
- package/ios/Steps/Steps.swift +467 -0
- package/ios/SuggestAction/SuggestAction.swift +100 -0
- package/ios/Swipeable/SwipeCell.swift +226 -31
- package/ios/Swipeable/SwipeCellModel.swift +21 -0
- package/ios/Switch/Switch.swift +69 -0
- package/ios/TabView/TabView.swift +534 -0
- package/ios/Tag/Tag.swift +99 -0
- package/ios/Template/Logo/Logo.swift +75 -0
- package/ios/Template/TrustBanner/TrustBanner.swift +123 -0
- package/ios/Title/Title.swift +270 -0
- package/ios/Tooltip/Tooltip.swift +486 -0
- package/ios/Typography/FontScale.swift +103 -0
- package/ios/Typography/FontScaleStore.swift +58 -0
- package/ios/Typography/KitDesignSystem.swift +17 -0
- package/ios/Typography/Text.swift +262 -0
- package/ios/Typography/Typography.swift +80 -23
- package/ios/Uploader/Uploader.swift +174 -0
- package/ios/native-kits.podspec +73 -13
- package/ios-demo/MoMoUIKitsDemo.podspec +20 -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 +24 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AnimatedHeaderDemo.swift +110 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AvatarDemo.swift +107 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BadgeDemo.swift +75 -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 +108 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CarouselDemo.swift +96 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CheckboxDemo.swift +60 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ChipDemo.swift +76 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CollapseDemo.swift +102 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +107 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DateTimePickerDemo.swift +100 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DemoScreen.swift +420 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DividerDemo.swift +56 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/HeaderUserDemo.swift +49 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +276 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconButtonDemo.swift +93 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconDemo.swift +81 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ImageDemo.swift +65 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InformationDemo.swift +96 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDemo.swift +117 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDropDownDemo.swift +94 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputMoneyDemo.swift +94 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputOTPDemo.swift +88 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputPhoneNumberDemo.swift +120 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputSearchDemo.swift +85 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputTextAreaDemo.swift +87 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LoaderDemo.swift +73 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LocalizeDemo.swift +235 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +241 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PaginationDemo.swift +68 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PlaygroundData.swift +76 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +175 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +95 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +67 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ProgressInfoDemo.swift +101 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RadioDemo.swift +64 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RatingDemo.swift +68 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SectionDemo.swift +83 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SkeletonDemo.swift +61 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SliderDemo.swift +90 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SnackBarDemo.swift +82 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepperDemo.swift +93 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepsDemo.swift +114 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SuggestActionDemo.swift +90 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwipeDemo.swift +107 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwitchDemo.swift +58 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TabViewDemo.swift +114 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TagDemo.swift +86 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ThemeDemo.swift +181 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TitleDemo.swift +104 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TooltipDemo.swift +118 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TypographyDemo.swift +87 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/UploaderDemo.swift +85 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SwiftUIDemoBrowserView.swift +53 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoModels.swift +15 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoRegistry.swift +73 -0
- package/package.json +6 -6
- package/publish.sh +554 -20
- package/scripts/gen-ios-color.mjs +136 -0
- package/CODE_OF_CONDUCT.md +0 -133
- package/CONTRIBUTING.md +0 -114
- package/LICENSE +0 -20
- package/android/build.gradle +0 -82
- package/android/gradle.properties +0 -5
- package/android/src/main/AndroidManifest.xml +0 -4
- package/android/src/main/java/com/momoplatform/nativekits/NativeKitsPackage.kt +0 -17
- package/android/src/main/java/com/momoplatform/nativekits/NativeKitsViewManager.kt +0 -20
- package/example/ios/Example/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -63
- package/example/ios/Example/ComponentDetail.swift +0 -35
- package/example/ios/Example/ComponentItem.swift +0 -24
- package/example/ios/Example/ComponentModel.swift +0 -33
- package/example/ios/Example/ContentView.swift +0 -53
- package/example/ios/Example/Example.entitlements +0 -10
- package/example/ios/Example/Foundation/ButtonDemo.swift +0 -80
- package/example/ios/Example/Foundation/Popup/ActionSheets.swift +0 -136
- package/example/ios/Example/Foundation/Popup/Floats.swift +0 -127
- package/example/ios/Example/Foundation/Popup/Popups.swift +0 -101
- package/example/ios/Example/Foundation/PopupDemo.swift +0 -366
- package/example/ios/Example/Foundation/TextInputDemo.swift +0 -31
- package/example/ios/Example/Foundation/TypographyDemo.swift +0 -53
- package/example/ios/Example/Foundation.swift +0 -26
- package/example/ios/Example/Info.plist +0 -18
- package/example/ios/Example/Library/SwipeableDemo.swift +0 -105
- package/example/ios/Example/Library.swift +0 -26
- package/example/ios/Example/Main.swift +0 -11
- package/example/ios/Example/Overview.swift +0 -20
- package/example/ios/Example/Resource/SFProText-Black.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Bold.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Heavy.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Light.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Medium.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Regular.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Semibold.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Thin.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Ultralight.ttf +0 -0
- package/example/ios/Example/Resource/foundation.json +0 -33
- package/example/ios/Example/Resource/library.json +0 -8
- package/example/ios/Example/Utils.swift +0 -30
- package/example/ios/Example/ViewModifier/DarkViewModifier.swift +0 -22
- package/example/ios/Example.xcodeproj/project.pbxproj +0 -570
- package/example/ios/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/example/ios/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/example/ios/Example.xcodeproj/project.xcworkspace/xcuserdata/wem.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/example/ios/Example.xcodeproj/xcuserdata/wem.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- package/example/ios/Example.xcworkspace/contents.xcworkspacedata +0 -10
- package/example/ios/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/example/ios/Example.xcworkspace/xcuserdata/wem.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/example/ios/Example.xcworkspace/xcuserdata/wem.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +0 -6
- package/example/ios/Podfile +0 -6
- package/example/ios/Podfile.lock +0 -16
- package/example/ios/Pods/Local Podspecs/MoMoUIKits.podspec.json +0 -29
- package/example/ios/Pods/Manifest.lock +0 -16
- package/example/ios/Pods/Pods.xcodeproj/project.pbxproj +0 -661
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/wem.xcuserdatad/xcschemes/MoMoUIKits.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/wem.xcuserdatad/xcschemes/Pods-Example.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/wem.xcuserdatad/xcschemes/xcschememanagement.plist +0 -25
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits-dummy.m +0 -5
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits-prefix.pch +0 -12
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits-umbrella.h +0 -16
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits.debug.xcconfig +0 -12
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits.modulemap +0 -6
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits.release.xcconfig +0 -12
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.markdown +0 -3
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.plist +0 -29
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example-dummy.m +0 -5
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example-umbrella.h +0 -16
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig +0 -14
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example.modulemap +0 -6
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig +0 -14
- package/ios/Colors+Spacing/Radius.swift +0 -11
- package/ios/Popup/Utils++.swift +0 -163
- package/ios/Theme.md +0 -18
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Background.colorset/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Border.colorset/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Card.colorset/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Error.colorset/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Primary.colorset/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/PrimaryDark.colorset/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/PrimaryLight.colorset/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Secondary.colorset/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Success.colorset/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/TextSecondary.colorset/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Warning.colorset/Contents.json +0 -0
package/ios/Button/Button.swift
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
|
|
2
1
|
import Foundation
|
|
2
|
+
import Lottie
|
|
3
3
|
import SwiftUI
|
|
4
4
|
|
|
5
5
|
// MARK: - ButtonType
|
|
6
6
|
|
|
7
7
|
public enum ButtonType {
|
|
8
8
|
case primary
|
|
9
|
-
case danger
|
|
10
|
-
case tonal
|
|
11
9
|
case secondary
|
|
10
|
+
case tonal
|
|
12
11
|
case outline
|
|
12
|
+
case danger
|
|
13
|
+
case text
|
|
13
14
|
case disabled
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -21,111 +22,258 @@ public enum ButtonSize {
|
|
|
21
22
|
case small
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
public struct ButtonStyleData {
|
|
26
|
+
let background: Color
|
|
27
|
+
let content: Color
|
|
28
|
+
let border: Color?
|
|
29
|
+
let borderWidth: CGFloat
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public extension ButtonType {
|
|
33
|
+
func backgroundColor(loading: Bool) -> Color {
|
|
34
|
+
backgroundColor(loading: loading, theme: defaultTheme)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
func backgroundColor(loading: Bool, theme: Theme) -> Color {
|
|
38
|
+
switch self {
|
|
39
|
+
case .disabled: return theme.colors.background.disable.opacity(loading ? 0.75 : 1)
|
|
40
|
+
case .primary: return theme.colors.primary.opacity(loading ? 0.75 : 1)
|
|
41
|
+
case .secondary: return theme.colors.background.surface.opacity(loading ? 0.75 : 1)
|
|
42
|
+
case .outline: return theme.colors.background.surface.opacity(loading ? 0.75 : 1)
|
|
43
|
+
case .tonal: return theme.colors.background.tonal.opacity(loading ? 0.75 : 1)
|
|
44
|
+
case .danger: return theme.colors.error.primary.opacity(loading ? 0.75 : 1)
|
|
45
|
+
case .text: return .clear
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func contentColor(loading: Bool) -> Color {
|
|
50
|
+
contentColor(loading: loading, theme: defaultTheme)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
func contentColor(loading: Bool, theme: Theme) -> Color {
|
|
54
|
+
switch self {
|
|
55
|
+
case .disabled: return theme.colors.text.disable.opacity(loading ? 0.75 : 1)
|
|
56
|
+
case .primary: return Colors.black01
|
|
57
|
+
case .secondary: return theme.colors.text.default
|
|
58
|
+
case .outline: return theme.colors.primary
|
|
59
|
+
case .tonal: return theme.colors.primary
|
|
60
|
+
case .danger: return Colors.black01
|
|
61
|
+
case .text: return theme.colors.primary
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
var borderColor: Color? {
|
|
66
|
+
borderColor(theme: defaultTheme)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func borderColor(theme: Theme) -> Color? {
|
|
70
|
+
switch self {
|
|
71
|
+
case .outline: return theme.colors.primary
|
|
72
|
+
case .secondary: return theme.colors.border.default
|
|
73
|
+
default: return nil
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
var borderWidth: CGFloat {
|
|
78
|
+
switch self {
|
|
79
|
+
case .outline: return 1
|
|
80
|
+
case .secondary: return 1
|
|
81
|
+
default: return 0
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
extension ButtonSize {
|
|
87
|
+
var height: CGFloat {
|
|
88
|
+
switch self {
|
|
89
|
+
case .large: return 48
|
|
90
|
+
case .medium: return 36
|
|
91
|
+
case .small: return 28
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
var padding: CGFloat {
|
|
96
|
+
switch self {
|
|
97
|
+
case .large: return Spacing.L
|
|
98
|
+
case .medium: return Spacing.M
|
|
99
|
+
case .small: return Spacing.S
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
var radius: CGFloat {
|
|
104
|
+
Radius.S
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
var iconSize: CGFloat {
|
|
108
|
+
switch self {
|
|
109
|
+
case .large: return 24
|
|
110
|
+
case .medium, .small: return 16
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
var iconSpacing: CGFloat {
|
|
115
|
+
switch self {
|
|
116
|
+
case .small: return Spacing.XS
|
|
117
|
+
case .medium, .large: return Spacing.S
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
var typographyStyle: TypographyStyle {
|
|
122
|
+
switch self {
|
|
123
|
+
case .large: return .actionDefaultBold
|
|
124
|
+
case .medium: return .actionSBold
|
|
125
|
+
case .small: return .actionXsBold
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
25
129
|
|
|
26
130
|
public struct Button: View {
|
|
27
|
-
|
|
131
|
+
@Environment(\.appTheme) private var theme
|
|
132
|
+
var title: String
|
|
133
|
+
var action: () -> Void
|
|
134
|
+
var type: ButtonType
|
|
135
|
+
var size: ButtonSize
|
|
136
|
+
var iconLeft: String
|
|
137
|
+
var iconRight: String
|
|
138
|
+
var isFull: Bool
|
|
139
|
+
var loading: Bool
|
|
140
|
+
var useTintColor: Bool
|
|
28
141
|
|
|
29
|
-
public init(
|
|
142
|
+
public init(
|
|
143
|
+
title: String = "Button",
|
|
144
|
+
action: @escaping () -> Void,
|
|
145
|
+
type: ButtonType = .primary,
|
|
146
|
+
size: ButtonSize = .large,
|
|
147
|
+
iconLeft: String = "",
|
|
148
|
+
iconRight: String = "",
|
|
149
|
+
isFull: Bool = true,
|
|
150
|
+
loading: Bool = false,
|
|
151
|
+
useTintColor: Bool = true
|
|
152
|
+
) {
|
|
30
153
|
self.title = title
|
|
31
154
|
self.action = action
|
|
32
155
|
self.type = type
|
|
33
156
|
self.size = size
|
|
34
157
|
self.iconLeft = iconLeft
|
|
35
158
|
self.iconRight = iconRight
|
|
159
|
+
self.isFull = isFull
|
|
160
|
+
self.loading = loading
|
|
161
|
+
self.useTintColor = useTintColor
|
|
36
162
|
}
|
|
37
163
|
|
|
38
|
-
|
|
164
|
+
private func shouldLoadingOnLeft(_ left: String, _ right: String) -> Bool {
|
|
165
|
+
let hasLeft = !left.isEmpty
|
|
166
|
+
let hasRight = !right.isEmpty
|
|
167
|
+
|
|
168
|
+
switch (hasLeft, hasRight) {
|
|
169
|
+
case (false, false): return true
|
|
170
|
+
case (true, false): return true
|
|
171
|
+
case (false, true): return false
|
|
172
|
+
case (true, true): return false
|
|
173
|
+
}
|
|
174
|
+
}
|
|
39
175
|
|
|
40
176
|
public var body: some View {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
177
|
+
let loadingOnLeft = shouldLoadingOnLeft(iconLeft, iconRight)
|
|
178
|
+
let isEnabled = type != .disabled && !loading
|
|
179
|
+
|
|
180
|
+
let bg = type.backgroundColor(loading: loading, theme: theme)
|
|
181
|
+
let fg = type.contentColor(loading: loading, theme: theme)
|
|
182
|
+
let border = type.borderColor(theme: theme)
|
|
183
|
+
let borderWidth = type.borderWidth
|
|
184
|
+
|
|
185
|
+
SwiftUI.Button(action: {
|
|
186
|
+
if isEnabled {
|
|
187
|
+
action()
|
|
46
188
|
}
|
|
47
|
-
|
|
48
|
-
.
|
|
49
|
-
|
|
189
|
+
}) {
|
|
190
|
+
HStack(spacing: size.iconSpacing) {
|
|
191
|
+
renderIcon(icon: iconLeft, forceLoading: loading && loadingOnLeft, textColor: fg)
|
|
192
|
+
|
|
193
|
+
MomoText(title, typography: size.typographyStyle, color: fg)
|
|
194
|
+
.lineLimit(1)
|
|
195
|
+
.truncationMode(.tail)
|
|
196
|
+
|
|
197
|
+
renderIcon(icon: iconRight, forceLoading: loading && !loadingOnLeft, textColor: fg)
|
|
198
|
+
}
|
|
199
|
+
.frame(maxWidth: isFull ? .infinity : nil)
|
|
200
|
+
.padding(.horizontal, size.padding)
|
|
201
|
+
.frame(height: size.height)
|
|
202
|
+
}
|
|
203
|
+
.buttonStyle(ButtonPressFeedbackStyle(
|
|
204
|
+
isEnabled: isEnabled,
|
|
205
|
+
background: bg,
|
|
206
|
+
border: border,
|
|
207
|
+
borderWidth: borderWidth,
|
|
208
|
+
radius: size.radius,
|
|
209
|
+
height: size.height,
|
|
210
|
+
isFull: isFull
|
|
211
|
+
))
|
|
212
|
+
.disabled(!isEnabled)
|
|
50
213
|
}
|
|
51
214
|
|
|
52
|
-
|
|
215
|
+
/// Mirrors Kotlin `RenderIcon`: the loader replaces the icon slot entirely, and
|
|
216
|
+
/// `useTintColor == false` leaves the icon at its own colors.
|
|
217
|
+
@ViewBuilder
|
|
218
|
+
private func renderIcon(icon: String, forceLoading: Bool, textColor: Color) -> some View {
|
|
219
|
+
let color: Color? = useTintColor ? textColor : nil
|
|
53
220
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
func renderTitle(title: String, type: ButtonType, size: ButtonSize) -> Text {
|
|
62
|
-
switch size {
|
|
63
|
-
case .large:
|
|
64
|
-
return Text(title).font(.system(size: 16, weight: Font.Weight.bold))
|
|
65
|
-
case .medium:
|
|
66
|
-
return Text(title).font(.system(size: 14, weight: Font.Weight.bold))
|
|
67
|
-
case .small:
|
|
68
|
-
return Text(title).font(.system(size: 12, weight: Font.Weight.bold))
|
|
221
|
+
if forceLoading {
|
|
222
|
+
LottieView(name: "lottie_circle_loader", loopMode: .loop)
|
|
223
|
+
.frame(width: size.iconSize, height: size.iconSize)
|
|
224
|
+
.colorMultiply(color ?? Colors.black01)
|
|
225
|
+
} else if !icon.isEmpty {
|
|
226
|
+
Icon(source: icon, size: size.iconSize, color: color)
|
|
69
227
|
}
|
|
70
228
|
}
|
|
71
229
|
}
|
|
72
230
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
case .outline:
|
|
100
|
-
return AnyView(self
|
|
101
|
-
.background(Colors.card)
|
|
102
|
-
.foregroundColor(Colors.primary)
|
|
103
|
-
.cornerRadius(8)
|
|
104
|
-
.overlay(RoundedRectangle(cornerRadius: Radius.S).stroke(Colors.primary, lineWidth: 1))
|
|
105
|
-
.padding(2)
|
|
231
|
+
/// Mirrors Compose's press feedback: the background shrinks via an animated
|
|
232
|
+
/// horizontal inset while the content fades, rather than a plain opacity dim on
|
|
233
|
+
/// the whole button (matching `animatedPadding`/`alpha` in Button.kt).
|
|
234
|
+
private struct ButtonPressFeedbackStyle: ButtonStyle {
|
|
235
|
+
let isEnabled: Bool
|
|
236
|
+
let background: Color
|
|
237
|
+
let border: Color?
|
|
238
|
+
let borderWidth: CGFloat
|
|
239
|
+
let radius: CGFloat
|
|
240
|
+
let height: CGFloat
|
|
241
|
+
let isFull: Bool
|
|
242
|
+
|
|
243
|
+
func makeBody(configuration: Configuration) -> some View {
|
|
244
|
+
let pressed = configuration.isPressed && isEnabled
|
|
245
|
+
|
|
246
|
+
configuration.label
|
|
247
|
+
.modifier(HugContentOnLegacyOS(isFull: isFull))
|
|
248
|
+
.opacity(pressed ? 0.5 : 1)
|
|
249
|
+
.background(
|
|
250
|
+
RoundedRectangle(cornerRadius: radius)
|
|
251
|
+
.fill(background)
|
|
252
|
+
.overlay(
|
|
253
|
+
RoundedRectangle(cornerRadius: radius)
|
|
254
|
+
.strokeBorder(border ?? .clear, lineWidth: border != nil ? borderWidth : 0)
|
|
255
|
+
)
|
|
256
|
+
.padding(.horizontal, pressed ? 2 : 0)
|
|
106
257
|
)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
.foregroundColor(Colors.black06)
|
|
111
|
-
.clipShape(RoundedRectangle(cornerRadius: Radius.S)))
|
|
112
|
-
}
|
|
258
|
+
.frame(height: height)
|
|
259
|
+
.clipShape(RoundedRectangle(cornerRadius: radius))
|
|
260
|
+
.animation(.easeInOut(duration: 0.1), value: pressed)
|
|
113
261
|
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/// `ButtonStyleConfiguration.Label` is greedy on iOS 16 and earlier — it accepts
|
|
265
|
+
/// the full proposed width, so the background drawn in `makeBody` stretches past
|
|
266
|
+
/// the content even when `isFull == false`. iOS 17 fixed the label to hug its
|
|
267
|
+
/// content, so only legacy OSes need the `fixedSize` clamp (kept off on 17+ to
|
|
268
|
+
/// preserve tail truncation of long titles).
|
|
269
|
+
private struct HugContentOnLegacyOS: ViewModifier {
|
|
270
|
+
let isFull: Bool
|
|
114
271
|
|
|
115
|
-
func
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
.padding([.leading, .trailing], Spacing.XS)
|
|
121
|
-
case .medium:
|
|
122
|
-
return self
|
|
123
|
-
.frame(minWidth: 80, maxWidth: 96, minHeight: 36, maxHeight: 36)
|
|
124
|
-
.padding([.leading, .trailing], Spacing.XS)
|
|
125
|
-
case .small:
|
|
126
|
-
return self
|
|
127
|
-
.frame(minWidth: 56, maxWidth: 76, minHeight: 28, maxHeight: 28)
|
|
128
|
-
.padding([.leading, .trailing], Spacing.XS)
|
|
272
|
+
func body(content: Content) -> some View {
|
|
273
|
+
if #available(iOS 17.0, *) {
|
|
274
|
+
content
|
|
275
|
+
} else {
|
|
276
|
+
content.fixedSize(horizontal: !isFull, vertical: false)
|
|
129
277
|
}
|
|
130
278
|
}
|
|
131
279
|
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
|
|
2
|
+
import Foundation
|
|
3
|
+
import SwiftUI
|
|
4
|
+
|
|
5
|
+
// MARK: - CalculatorKeyboard
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
extension String {
|
|
9
|
+
var isNumber: Bool {
|
|
10
|
+
let digitsCharacters = CharacterSet(charactersIn: "0123456789")
|
|
11
|
+
return CharacterSet(charactersIn: self).isSubset(of: digitsCharacters)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
public struct CalculatorKeyboard: View {
|
|
17
|
+
// MARK: Lifecycle
|
|
18
|
+
@Binding var value: String
|
|
19
|
+
@State var tempValue: String = ""
|
|
20
|
+
|
|
21
|
+
public init(_ value: Binding<String>) {
|
|
22
|
+
self._value = value
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func onPressBack(){
|
|
26
|
+
if (value.count > 0 ){
|
|
27
|
+
value.removeLast()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func onPressClear(key: String){
|
|
33
|
+
value = ""
|
|
34
|
+
tempValue = ""
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
func onPressEqual(key: String){
|
|
38
|
+
if (tempValue == "") {
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
if (tempValue.last?.isNumber == false){
|
|
42
|
+
tempValue.removeLast()
|
|
43
|
+
}
|
|
44
|
+
let expression = NSExpression(format: tempValue)
|
|
45
|
+
|
|
46
|
+
value = String(expression.expressionValue(with: nil, context: nil) as! Int)
|
|
47
|
+
if (Int(value) ?? 0 < 0){
|
|
48
|
+
value = ""
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func onPressKey(key:String){
|
|
53
|
+
if (tempValue.last?.isNumber == false){
|
|
54
|
+
if (key.isNumber == false){
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
var convertedKey = key
|
|
59
|
+
if (key == "x") {convertedKey = "*"}
|
|
60
|
+
if (key == "÷") {convertedKey = "/"}
|
|
61
|
+
if (value == "" && (key == "0" || key == "000" || key.isNumber == false)) {
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
value.append(key)
|
|
66
|
+
tempValue.append(convertedKey)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// MARK: Public
|
|
71
|
+
|
|
72
|
+
public var body: some View {
|
|
73
|
+
return GeometryReader { geo in
|
|
74
|
+
VStack {
|
|
75
|
+
HStack {
|
|
76
|
+
KeyboardButton("AC", action: self.onPressClear, color: Colors.pink07, textColor: Colors.black01)
|
|
77
|
+
KeyboardButton("÷", action: self.onPressKey, color: Colors.pink07, textColor: Colors.black01)
|
|
78
|
+
KeyboardButton("x", action: self.onPressKey, color: Colors.pink07, textColor: Colors.black01)
|
|
79
|
+
|
|
80
|
+
SwiftUI.Button(action: onPressBack) {
|
|
81
|
+
HStack {
|
|
82
|
+
Image(
|
|
83
|
+
systemName: "delete.backward"
|
|
84
|
+
).foregroundColor(Colors.black01)
|
|
85
|
+
}.frame(maxWidth: .infinity, minHeight: 48)
|
|
86
|
+
.background(Colors.pink07)
|
|
87
|
+
.foregroundColor(Colors.black01)
|
|
88
|
+
.clipShape(RoundedRectangle(cornerRadius: 8))
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
HStack {
|
|
93
|
+
KeyboardButton("7", action: self.onPressKey)
|
|
94
|
+
KeyboardButton("8", action: self.onPressKey)
|
|
95
|
+
KeyboardButton("9", action: self.onPressKey)
|
|
96
|
+
KeyboardButton("-", action: self.onPressKey, color: Colors.pink07, textColor: Colors.black01)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
HStack {
|
|
100
|
+
KeyboardButton("4", action: self.onPressKey)
|
|
101
|
+
KeyboardButton("5", action: self.onPressKey)
|
|
102
|
+
KeyboardButton("6", action: self.onPressKey)
|
|
103
|
+
KeyboardButton("+", action: self.onPressKey, color: Colors.pink07, textColor: Colors.black01)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
HStack{
|
|
107
|
+
VStack{
|
|
108
|
+
HStack {
|
|
109
|
+
KeyboardButton("1", action: self.onPressKey)
|
|
110
|
+
KeyboardButton("2", action: self.onPressKey)
|
|
111
|
+
KeyboardButton("3", action: self.onPressKey)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
HStack{
|
|
115
|
+
KeyboardButton("000", action: self.onPressKey)
|
|
116
|
+
KeyboardButton("0", action: self.onPressKey, width: CGFloat(geo.size.width * 0.25 - 5))
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
KeyboardButton("=", action: self.onPressEqual, width: CGFloat(geo.size.width * 0.25 - 5), height: 102, color: Colors.primary, textColor: Colors.black01)
|
|
121
|
+
|
|
122
|
+
}
|
|
123
|
+
}.background(Colors.background)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import Combine
|
|
2
|
+
import SwiftUI
|
|
3
|
+
|
|
4
|
+
/// Native SwiftUI port of the Compose `Carousel` component.
|
|
5
|
+
///
|
|
6
|
+
/// Mirrors the Compose API: a paging horizontal carousel driven by an
|
|
7
|
+
/// `itemCount` and a `content` builder that receives the real (modulo) index.
|
|
8
|
+
/// Supports infinite `loop`, `autoplay` with a configurable interval,
|
|
9
|
+
/// inactive-slide `opacity`/`scale` effects, optional `contentPadding`,
|
|
10
|
+
/// snap behavior, and scroll enable/disable. Snap / scroll-index callbacks
|
|
11
|
+
/// report the real item index.
|
|
12
|
+
///
|
|
13
|
+
/// Available iOS 14+ (uses a custom paging ScrollView so behavior is identical
|
|
14
|
+
/// across versions and supports the loop virtualization + transform effects
|
|
15
|
+
/// that `TabView(.page)` cannot express).
|
|
16
|
+
public struct Carousel<Content: View>: View {
|
|
17
|
+
// MARK: Lifecycle
|
|
18
|
+
|
|
19
|
+
public init(
|
|
20
|
+
itemCount: Int,
|
|
21
|
+
loop: Bool = false,
|
|
22
|
+
autoplay: Bool = false,
|
|
23
|
+
autoplayInterval: TimeInterval = 3.0,
|
|
24
|
+
enableSnap: Bool = true,
|
|
25
|
+
scrollEnabled: Bool = true,
|
|
26
|
+
inactiveSlideOpacity: Double = 1.0,
|
|
27
|
+
inactiveSlideScale: Double = 1.0,
|
|
28
|
+
contentPadding: CGFloat = 0,
|
|
29
|
+
onSnapToItem: @escaping (Int) -> Void = { _ in },
|
|
30
|
+
onScrollIndexChanged: @escaping (Int) -> Void = { _ in },
|
|
31
|
+
@ViewBuilder content: @escaping (_ index: Int) -> Content
|
|
32
|
+
) {
|
|
33
|
+
self.itemCount = itemCount
|
|
34
|
+
self.loop = loop
|
|
35
|
+
self.autoplay = autoplay
|
|
36
|
+
self.autoplayInterval = autoplayInterval
|
|
37
|
+
self.enableSnap = enableSnap
|
|
38
|
+
self.scrollEnabled = scrollEnabled
|
|
39
|
+
self.inactiveSlideOpacity = inactiveSlideOpacity
|
|
40
|
+
self.inactiveSlideScale = inactiveSlideScale
|
|
41
|
+
self.contentPadding = contentPadding
|
|
42
|
+
self.onSnapToItem = onSnapToItem
|
|
43
|
+
self.onScrollIndexChanged = onScrollIndexChanged
|
|
44
|
+
self.content = content
|
|
45
|
+
|
|
46
|
+
let virtual = loop ? itemCount * 1000 : itemCount
|
|
47
|
+
let start = loop ? (virtual / 2) - ((virtual / 2) % max(itemCount, 1)) : 0
|
|
48
|
+
self.virtualPageCount = virtual
|
|
49
|
+
_currentPage = State(initialValue: start)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// MARK: Public
|
|
53
|
+
|
|
54
|
+
public var body: some View {
|
|
55
|
+
if itemCount <= 0 {
|
|
56
|
+
EmptyView()
|
|
57
|
+
} else {
|
|
58
|
+
GeometryReader { geo in
|
|
59
|
+
let pageWidth = max(geo.size.width - contentPadding * 2, 1)
|
|
60
|
+
carouselContent(pageWidth: pageWidth, viewportWidth: geo.size.width)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// MARK: Private
|
|
66
|
+
|
|
67
|
+
private let itemCount: Int
|
|
68
|
+
private let loop: Bool
|
|
69
|
+
private let autoplay: Bool
|
|
70
|
+
private let autoplayInterval: TimeInterval
|
|
71
|
+
private let enableSnap: Bool
|
|
72
|
+
private let scrollEnabled: Bool
|
|
73
|
+
private let inactiveSlideOpacity: Double
|
|
74
|
+
private let inactiveSlideScale: Double
|
|
75
|
+
private let contentPadding: CGFloat
|
|
76
|
+
private let onSnapToItem: (Int) -> Void
|
|
77
|
+
private let onScrollIndexChanged: (Int) -> Void
|
|
78
|
+
private let content: (Int) -> Content
|
|
79
|
+
|
|
80
|
+
private let virtualPageCount: Int
|
|
81
|
+
|
|
82
|
+
@State private var currentPage: Int
|
|
83
|
+
@State private var dragOffset: CGFloat = 0
|
|
84
|
+
@State private var isDragging: Bool = false
|
|
85
|
+
@State private var autoplayTick: Int = 0
|
|
86
|
+
|
|
87
|
+
private func realIndex(_ page: Int) -> Int {
|
|
88
|
+
((page % itemCount) + itemCount) % itemCount
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@ViewBuilder
|
|
92
|
+
private func carouselContent(pageWidth: CGFloat, viewportWidth: CGFloat) -> some View {
|
|
93
|
+
let step = pageWidth
|
|
94
|
+
// Offset so the current page is centered within the viewport padding.
|
|
95
|
+
let baseOffset = -CGFloat(currentPage) * step + contentPadding
|
|
96
|
+
|
|
97
|
+
// Windows the rendered range around `currentPage` instead of instantiating all
|
|
98
|
+
// `virtualPageCount` slides up front — with `loop=true`, that's `itemCount * 1000`
|
|
99
|
+
// eager views, a real memory/perf regression versus Compose's lazy `HorizontalPager`.
|
|
100
|
+
// Clear spacers of the skipped width keep the HStack's total layout size (and thus
|
|
101
|
+
// the existing `baseOffset` math) unchanged.
|
|
102
|
+
let windowRadius = 3
|
|
103
|
+
let windowStart = max(0, currentPage - windowRadius)
|
|
104
|
+
let windowEnd = min(virtualPageCount - 1, currentPage + windowRadius)
|
|
105
|
+
|
|
106
|
+
ZStack {
|
|
107
|
+
HStack(spacing: 0) {
|
|
108
|
+
if windowStart > 0 {
|
|
109
|
+
Color.clear.frame(width: CGFloat(windowStart) * step)
|
|
110
|
+
}
|
|
111
|
+
ForEach(windowStart...windowEnd, id: \.self) { page in
|
|
112
|
+
slide(page: page, step: step, viewportWidth: viewportWidth)
|
|
113
|
+
.frame(width: pageWidth)
|
|
114
|
+
}
|
|
115
|
+
if windowEnd < virtualPageCount - 1 {
|
|
116
|
+
Color.clear.frame(width: CGFloat(virtualPageCount - 1 - windowEnd) * step)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
.offset(x: baseOffset + dragOffset)
|
|
120
|
+
.animation(isDragging ? nil : .easeInOut(duration: 0.25), value: currentPage)
|
|
121
|
+
.frame(width: viewportWidth, alignment: .leading)
|
|
122
|
+
.contentShape(Rectangle())
|
|
123
|
+
.gesture(scrollEnabled ? dragGesture(step: step) : nil)
|
|
124
|
+
}
|
|
125
|
+
.frame(width: viewportWidth)
|
|
126
|
+
.clipped()
|
|
127
|
+
.onReceive(autoplayTimer) { _ in
|
|
128
|
+
guard autoplay, itemCount > 1, !isDragging else { return }
|
|
129
|
+
advance()
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@ViewBuilder
|
|
134
|
+
private func slide(page: Int, step: CGFloat, viewportWidth: CGFloat) -> some View {
|
|
135
|
+
// Fractional offset of this page from the current page (incl. drag).
|
|
136
|
+
let dragFraction = step > 0 ? -dragOffset / step : 0
|
|
137
|
+
let pageOffset = abs(CGFloat(currentPage - page) + dragFraction)
|
|
138
|
+
let isActive = pageOffset < 0.5
|
|
139
|
+
let scale = isActive ? 1.0 : inactiveSlideScale
|
|
140
|
+
let alpha = isActive ? 1.0 : inactiveSlideOpacity
|
|
141
|
+
|
|
142
|
+
content(realIndex(page))
|
|
143
|
+
.scaleEffect(scale)
|
|
144
|
+
.opacity(alpha)
|
|
145
|
+
.animation(.easeInOut(duration: 0.15), value: isActive)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private func dragGesture(step: CGFloat) -> some Gesture {
|
|
149
|
+
DragGesture(minimumDistance: 5)
|
|
150
|
+
.onChanged { value in
|
|
151
|
+
isDragging = true
|
|
152
|
+
dragOffset = value.translation.width
|
|
153
|
+
}
|
|
154
|
+
.onEnded { value in
|
|
155
|
+
isDragging = false
|
|
156
|
+
let predicted = value.predictedEndTranslation.width
|
|
157
|
+
let threshold = step / 4
|
|
158
|
+
var target = currentPage
|
|
159
|
+
if enableSnap {
|
|
160
|
+
if predicted < -threshold {
|
|
161
|
+
target = min(currentPage + 1, virtualPageCount - 1)
|
|
162
|
+
} else if predicted > threshold {
|
|
163
|
+
target = max(currentPage - 1, 0)
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
// Free scroll still settles to nearest page boundary.
|
|
167
|
+
let moved = step > 0 ? Int((-dragOffset / step).rounded()) : 0
|
|
168
|
+
target = min(max(currentPage + moved, 0), virtualPageCount - 1)
|
|
169
|
+
}
|
|
170
|
+
dragOffset = 0
|
|
171
|
+
setPage(target)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private func advance() {
|
|
176
|
+
let next = currentPage + 1
|
|
177
|
+
if next < virtualPageCount {
|
|
178
|
+
setPage(next)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private func setPage(_ page: Int) {
|
|
183
|
+
let clamped = min(max(page, 0), virtualPageCount - 1)
|
|
184
|
+
let changed = clamped != currentPage
|
|
185
|
+
currentPage = clamped
|
|
186
|
+
if changed {
|
|
187
|
+
let real = realIndex(clamped)
|
|
188
|
+
onSnapToItem(real)
|
|
189
|
+
onScrollIndexChanged(real)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private var autoplayTimer: Publishers.Autoconnect<Timer.TimerPublisher> {
|
|
194
|
+
Timer.publish(every: autoplayInterval, on: .main, in: .common).autoconnect()
|
|
195
|
+
}
|
|
196
|
+
}
|