@momo-kits/native-kits 0.163.1-beta.2-debug → 0.163.1-beta.20-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 +112 -0
- package/CLAUDE.md +1 -1
- package/compose/build.gradle.kts +2 -1
- package/compose/build.gradle.kts.backup +1 -0
- package/compose/compose.podspec +1 -1
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +7 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +30 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +8 -10
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +2 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +3 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +56 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +11 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +0 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +40 -55
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +69 -26
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +5 -5
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +3 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +21 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +3 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +19 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +22 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +28 -21
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +50 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +25 -22
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +37 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +68 -22
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +7 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +112 -115
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +15 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +33 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +6 -5
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +3 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +0 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +23 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +9 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +11 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +66 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +26 -3
- package/gradle/libs.versions.toml +2 -0
- package/gradle.properties +1 -1
- package/ios/Application/ApplicationEnvironment.swift +15 -3
- package/ios/Application/Components.swift +36 -30
- package/ios/Application/FloatingButton.swift +10 -9
- package/ios/Application/Localize.swift +309 -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 +203 -0
- package/ios/Application/Navigation/NavigationOptions.swift +109 -0
- package/ios/Application/Navigation/Navigator.swift +385 -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/NavigationHeaderRight.swift +196 -0
- package/ios/Application/Screen.swift +2 -1
- package/ios/Application/StackScreen.swift +501 -0
- package/ios/Avatar/Avatar.swift +193 -0
- package/ios/Badge/Badge.swift +11 -14
- package/ios/Badge/BadgeRibbon.swift +91 -17
- package/ios/BaselineView/BaselineView.swift +163 -0
- package/ios/Button/Button.swift +137 -28
- package/ios/Carousel/Carousel.swift +196 -0
- package/ios/Checkbox/Checkbox.swift +23 -28
- package/ios/Chip/Chip.swift +56 -33
- package/ios/Collapse/Collapse.swift +192 -0
- package/ios/Colors+Radius+Spacing/Colors.swift +20 -17
- package/ios/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 +10 -1
- package/ios/IconButton/IconButton.swift +140 -0
- package/ios/Image/Image.swift +16 -3
- package/ios/Information/Information.swift +3 -1
- package/ios/Input/ErrorView.swift +3 -1
- package/ios/Input/Input.swift +72 -33
- package/ios/Input/InputPhoneNumber.swift +33 -21
- package/ios/Input/InputSearch.swift +107 -48
- package/ios/Input/InputTextArea.swift +134 -64
- package/ios/InputDropDown/InputDropDown.swift +201 -0
- package/ios/InputMoney/InputMoney.swift +347 -0
- package/ios/InputOTP/InputOTP.swift +210 -0
- package/ios/Loader/Loader.swift +113 -0
- package/ios/MoMoUIKits.podspec +5 -0
- 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 +11 -20
- package/ios/Popup/PopupInput.swift +2 -8
- package/ios/Popup/PopupNotify.swift +218 -0
- package/ios/Popup/PopupPromotion.swift +5 -3
- package/ios/ProgressInfo/ProgressInfo.swift +296 -0
- package/ios/Radio/Radio.swift +69 -0
- package/ios/Rating/Rating.swift +82 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/AccentColor.colorset/Contents.json +11 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/Background.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/Border.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/Card.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/Contents.json +6 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/Error.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/Primary.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/PrimaryDark.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/PrimaryLight.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/Secondary.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/Success.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/Text.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/TextSecondary.colorset/Contents.json +38 -0
- 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/Resources/MoMoUIKitsColors.xcassets/Warning.colorset/Contents.json +38 -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 +467 -0
- package/ios/SuggestAction/SuggestAction.swift +99 -0
- package/ios/Swipeable/SwipeCell.swift +187 -0
- package/ios/Swipeable/SwipeCellModel.swift +21 -0
- package/ios/Switch/Switch.swift +53 -28
- package/ios/TabView/TabView.swift +534 -0
- package/ios/Tag/Tag.swift +98 -0
- package/ios/Template/TrustBanner/TrustBanner.swift +7 -8
- package/ios/Title/Title.swift +269 -0
- package/ios/Tooltip/Tooltip.swift +485 -0
- package/ios/Typography/FontScaleStore.swift +19 -0
- package/ios/Typography/Text.swift +86 -17
- package/ios/Typography/Typography.swift +12 -14
- package/ios/Uploader/Uploader.swift +174 -0
- package/ios/native-kits.podspec +61 -8
- package/ios-demo/MoMoUIKitsDemo.podspec +20 -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 +24 -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 +64 -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 +279 -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/LocalizeDemo.swift +188 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +229 -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 +51 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoModels.swift +15 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoRegistry.swift +73 -0
- package/local.properties +2 -2
- package/package.json +1 -1
- package/publish.sh +53 -0
- package/scripts/gen-ios-color.mjs +136 -0
- package/example/ios/Example.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- package/example/ios/Example.xcworkspace/xcuserdata/huynhdung.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/example/ios/Example.xcworkspace/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +0 -5
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/MoMoUIKits.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/Pods-Example.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SDWebImage.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SDWebImageSwiftUI.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/SkeletonUI.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/huynhdung.xcuserdatad/xcschemes/xcschememanagement.plist +0 -46
- package/ios/Application/FontScaleStore.swift +0 -59
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
//
|
|
2
|
+
// CurvedContainer.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// SwiftUI mirror of Compose's `CurvedContainer` (navigation/bottomtab/CurvedContainer.kt).
|
|
6
|
+
// Draws the surface-colored notch that sits behind the center floating button,
|
|
7
|
+
// carving a circular cut-out into the tab bar's top edge.
|
|
8
|
+
//
|
|
9
|
+
|
|
10
|
+
import SwiftUI
|
|
11
|
+
|
|
12
|
+
struct CurvedContainer: View {
|
|
13
|
+
var circleSize: CGFloat = 48
|
|
14
|
+
var tabBarItemIconSize: CGFloat = 28
|
|
15
|
+
var circleOverSize: CGFloat = 2
|
|
16
|
+
var width: CGFloat = 71
|
|
17
|
+
var height: CGFloat = 16
|
|
18
|
+
var color: Color = Colors.black01
|
|
19
|
+
|
|
20
|
+
var body: some View {
|
|
21
|
+
let circlePositionTop = circleSize - tabBarItemIconSize - 10
|
|
22
|
+
let circleOverTop = circlePositionTop + circleOverSize
|
|
23
|
+
let circleR = circleSize / 2
|
|
24
|
+
let circleOverR = circleR + circleOverSize
|
|
25
|
+
let centerX = width / 2
|
|
26
|
+
|
|
27
|
+
CurvedShape(circleOverTop: circleOverTop, centerX: centerX, circleOverR: circleOverR, width: width)
|
|
28
|
+
.fill(color)
|
|
29
|
+
.frame(width: width, height: height)
|
|
30
|
+
.offset(y: 24)
|
|
31
|
+
.zIndex(-1)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Mirrors Compose's `getPath`: a flat bar with a smooth circular dip in the
|
|
36
|
+
/// middle, sized to cradle the floating button.
|
|
37
|
+
private struct CurvedShape: Shape {
|
|
38
|
+
let circleOverTop: CGFloat
|
|
39
|
+
let centerX: CGFloat
|
|
40
|
+
let circleOverR: CGFloat
|
|
41
|
+
let width: CGFloat
|
|
42
|
+
|
|
43
|
+
func path(in rect: CGRect) -> Path {
|
|
44
|
+
var path = Path()
|
|
45
|
+
path.move(to: CGPoint(x: 0, y: circleOverTop))
|
|
46
|
+
path.addLine(to: CGPoint(x: centerX - circleOverR - 40, y: circleOverTop))
|
|
47
|
+
|
|
48
|
+
path.addCurve(
|
|
49
|
+
to: CGPoint(x: centerX, y: 0),
|
|
50
|
+
control1: CGPoint(x: centerX - circleOverR, y: circleOverTop),
|
|
51
|
+
control2: CGPoint(x: centerX - circleOverR, y: 0)
|
|
52
|
+
)
|
|
53
|
+
path.addCurve(
|
|
54
|
+
to: CGPoint(x: centerX + circleOverR + 40, y: circleOverTop),
|
|
55
|
+
control1: CGPoint(x: centerX + circleOverR, y: 0),
|
|
56
|
+
control2: CGPoint(x: centerX + circleOverR, y: circleOverTop)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
path.addLine(to: CGPoint(x: width, y: circleOverTop))
|
|
60
|
+
path.addLine(to: CGPoint(x: width, y: 60))
|
|
61
|
+
path.addLine(to: CGPoint(x: 0, y: 60))
|
|
62
|
+
path.closeSubpath()
|
|
63
|
+
return path
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
//
|
|
2
|
+
// HeaderBackProps.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// SwiftUI mirror of Compose's `HeaderBackProps` + `PopupNotifyProps`/`PopupAction`.
|
|
6
|
+
// When `preventBack` is set, `Navigator.onBackSafe` shows a confirm popup
|
|
7
|
+
// before actually leaving the screen — same behavior as Compose.
|
|
8
|
+
//
|
|
9
|
+
|
|
10
|
+
import SwiftUI
|
|
11
|
+
|
|
12
|
+
/// Mirrors Compose's `PopupAction` (a titled button with an optional handler).
|
|
13
|
+
public struct PopupAction {
|
|
14
|
+
public let title: String
|
|
15
|
+
public let onPress: (() -> Void)?
|
|
16
|
+
|
|
17
|
+
public init(title: String, onPress: (() -> Void)? = nil) {
|
|
18
|
+
self.title = title
|
|
19
|
+
self.onPress = onPress
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Mirrors Compose's `PopupNotifyProps` as consumed by `headerBackProps.preventBack`.
|
|
24
|
+
public struct PreventBackProps {
|
|
25
|
+
public var image: String
|
|
26
|
+
public var title: String
|
|
27
|
+
public var description: String
|
|
28
|
+
public var error: String
|
|
29
|
+
public var primary: PopupAction?
|
|
30
|
+
public var secondary: PopupAction?
|
|
31
|
+
public var buttonDirection: PopupButtonDirection
|
|
32
|
+
public var isShowCloseIcon: Bool
|
|
33
|
+
public var onIconClose: (() -> Void)?
|
|
34
|
+
|
|
35
|
+
public init(
|
|
36
|
+
image: String = "",
|
|
37
|
+
title: String = "",
|
|
38
|
+
description: String = "",
|
|
39
|
+
error: String = "",
|
|
40
|
+
primary: PopupAction? = nil,
|
|
41
|
+
secondary: PopupAction? = nil,
|
|
42
|
+
buttonDirection: PopupButtonDirection = .row,
|
|
43
|
+
isShowCloseIcon: Bool = true,
|
|
44
|
+
onIconClose: (() -> Void)? = nil
|
|
45
|
+
) {
|
|
46
|
+
self.image = image
|
|
47
|
+
self.title = title
|
|
48
|
+
self.description = description
|
|
49
|
+
self.error = error
|
|
50
|
+
self.primary = primary
|
|
51
|
+
self.secondary = secondary
|
|
52
|
+
self.buttonDirection = buttonDirection
|
|
53
|
+
self.isShowCloseIcon = isShowCloseIcon
|
|
54
|
+
self.onIconClose = onIconClose
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Mirrors Compose's `HeaderBackProps`.
|
|
59
|
+
public struct HeaderBackProps {
|
|
60
|
+
/// When non-nil, a confirm popup is shown before the back navigation runs.
|
|
61
|
+
public var preventBack: PreventBackProps?
|
|
62
|
+
|
|
63
|
+
public init(preventBack: PreventBackProps? = nil) {
|
|
64
|
+
self.preventBack = preventBack
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//
|
|
2
|
+
// HeaderTitle.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// SwiftUI mirror of Compose's `sealed class HeaderTitle` (navigation/component/Header.kt).
|
|
6
|
+
// The header can show either a plain title (`.default`) or a rich "user" title
|
|
7
|
+
// with an avatar group, subtitle, inline icons and a status dot (`.user`).
|
|
8
|
+
// (Compose's `Location`/`Journey` variants are not rendered yet, so they are
|
|
9
|
+
// omitted here for parity with the implemented `HeaderUser`.)
|
|
10
|
+
//
|
|
11
|
+
|
|
12
|
+
import SwiftUI
|
|
13
|
+
|
|
14
|
+
/// Data backing a `.user` header title (mirrors `HeaderTitle.User`).
|
|
15
|
+
public struct HeaderUserData {
|
|
16
|
+
public var title: String
|
|
17
|
+
public var subTitle: String?
|
|
18
|
+
public var image: [String]?
|
|
19
|
+
public var dotColor: Color?
|
|
20
|
+
public var tintColor: Color?
|
|
21
|
+
public var onPress: (() -> Void)?
|
|
22
|
+
public var icons: [String]
|
|
23
|
+
public var isLoading: Bool
|
|
24
|
+
|
|
25
|
+
public init(
|
|
26
|
+
title: String,
|
|
27
|
+
subTitle: String? = nil,
|
|
28
|
+
image: [String]? = nil,
|
|
29
|
+
dotColor: Color? = nil,
|
|
30
|
+
tintColor: Color? = nil,
|
|
31
|
+
onPress: (() -> Void)? = nil,
|
|
32
|
+
icons: [String] = [],
|
|
33
|
+
isLoading: Bool = false
|
|
34
|
+
) {
|
|
35
|
+
self.title = title
|
|
36
|
+
self.subTitle = subTitle
|
|
37
|
+
self.image = image
|
|
38
|
+
self.dotColor = dotColor
|
|
39
|
+
self.tintColor = tintColor
|
|
40
|
+
self.onPress = onPress
|
|
41
|
+
self.icons = icons
|
|
42
|
+
self.isLoading = isLoading
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Mirrors Compose's `HeaderTitle` sealed class.
|
|
47
|
+
public enum HeaderTitle {
|
|
48
|
+
case `default`(String)
|
|
49
|
+
case user(HeaderUserData)
|
|
50
|
+
|
|
51
|
+
/// Plain title text, when this is a `.default` title (used by the legacy
|
|
52
|
+
/// string-based `Header`/`NavigationOptions` paths).
|
|
53
|
+
public var defaultTitle: String? {
|
|
54
|
+
if case let .default(text) = self { return text }
|
|
55
|
+
return nil
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
//
|
|
2
|
+
// HeaderUser.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// SwiftUI mirror of Compose's `HeaderUser` (navigation/component/HeaderUser.kt).
|
|
6
|
+
// Renders a `HeaderTitle.User`: an avatar group (1..n avatars with overflow),
|
|
7
|
+
// a title with optional inline icons, and an optional subtitle. Shows a
|
|
8
|
+
// skeleton shimmer placeholder while `isLoading`.
|
|
9
|
+
//
|
|
10
|
+
|
|
11
|
+
import SwiftUI
|
|
12
|
+
|
|
13
|
+
@available(iOS 16.0, *)
|
|
14
|
+
public struct HeaderUserView: View {
|
|
15
|
+
private let data: HeaderUserData
|
|
16
|
+
|
|
17
|
+
public init(_ data: HeaderUserData) {
|
|
18
|
+
self.data = data
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private let avatarSize: CGFloat = Spacing.XXL // 32
|
|
22
|
+
|
|
23
|
+
private var maxWidth: CGFloat {
|
|
24
|
+
UIScreen.main.bounds.width - 172
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public var body: some View {
|
|
28
|
+
if data.isLoading {
|
|
29
|
+
shimmer
|
|
30
|
+
} else {
|
|
31
|
+
content
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private var content: some View {
|
|
36
|
+
HStack(spacing: 0) {
|
|
37
|
+
HStack(spacing: 0) {
|
|
38
|
+
ZStack(alignment: .bottomTrailing) {
|
|
39
|
+
AvatarGroup(urls: data.image ?? [], title: data.title, size: avatarSize)
|
|
40
|
+
if let dotColor = data.dotColor {
|
|
41
|
+
Circle()
|
|
42
|
+
.fill(dotColor)
|
|
43
|
+
.frame(width: 10, height: 10)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
.frame(width: avatarSize, height: avatarSize)
|
|
47
|
+
|
|
48
|
+
Spacer().frame(width: 6)
|
|
49
|
+
|
|
50
|
+
VStack(alignment: .leading, spacing: 0) {
|
|
51
|
+
HStack(spacing: 0) {
|
|
52
|
+
MomoText(data.title, typography: .actionXsBold, color: data.tintColor ?? Colors.black17, maxLines: 1)
|
|
53
|
+
.truncationMode(.tail)
|
|
54
|
+
|
|
55
|
+
if !data.icons.isEmpty {
|
|
56
|
+
Spacer().frame(width: 6)
|
|
57
|
+
HStack(spacing: 0) {
|
|
58
|
+
ForEach(Array(data.icons.enumerated()), id: \.offset) { _, icon in
|
|
59
|
+
ImageView(icon)
|
|
60
|
+
.frame(width: 16, height: 16)
|
|
61
|
+
.clipShape(RoundedRectangle(cornerRadius: 4))
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if let subTitle = data.subTitle, !subTitle.isEmpty {
|
|
68
|
+
Spacer().frame(height: 2)
|
|
69
|
+
MomoText(subTitle, typography: .descriptionXsRegular, color: data.tintColor ?? Colors.black17, maxLines: 1)
|
|
70
|
+
.truncationMode(.tail)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
.frame(maxWidth: maxWidth, alignment: .leading)
|
|
75
|
+
|
|
76
|
+
Spacer(minLength: 0)
|
|
77
|
+
}
|
|
78
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
79
|
+
.padding(.vertical, Spacing.XS)
|
|
80
|
+
.contentShape(Rectangle())
|
|
81
|
+
.onTapGesture { data.onPress?() }
|
|
82
|
+
.allowsHitTesting(data.onPress != nil)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private var shimmer: some View {
|
|
86
|
+
HStack(spacing: Spacing.M / 2) {
|
|
87
|
+
Circle()
|
|
88
|
+
.fill(Colors.black05)
|
|
89
|
+
.frame(width: avatarSize, height: avatarSize)
|
|
90
|
+
VStack(alignment: .leading, spacing: Spacing.XXS) {
|
|
91
|
+
RoundedRectangle(cornerRadius: 8)
|
|
92
|
+
.fill(Colors.black05)
|
|
93
|
+
.frame(width: 120, height: 18)
|
|
94
|
+
RoundedRectangle(cornerRadius: 8)
|
|
95
|
+
.fill(Colors.black05)
|
|
96
|
+
.frame(width: 120, height: 12)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
100
|
+
.padding(.vertical, Spacing.XS)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// MARK: - Avatar group
|
|
105
|
+
|
|
106
|
+
/// Mirrors Compose's `AvatarGroup`: lays out 1..4 avatars in a fixed collage,
|
|
107
|
+
/// falls back to initials when empty, and shows a "+n" overflow tile beyond 4.
|
|
108
|
+
@available(iOS 16.0, *)
|
|
109
|
+
private struct AvatarGroup: View {
|
|
110
|
+
let urls: [String]
|
|
111
|
+
let title: String
|
|
112
|
+
let size: CGFloat
|
|
113
|
+
|
|
114
|
+
private var child: CGFloat {
|
|
115
|
+
switch urls.count {
|
|
116
|
+
case 0, 1, 2: return 24
|
|
117
|
+
default: return 16
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
var body: some View {
|
|
122
|
+
switch urls.count {
|
|
123
|
+
case 0:
|
|
124
|
+
InitialsAvatar(name: title, size: size)
|
|
125
|
+
case 1:
|
|
126
|
+
avatar(urls[0]).frame(width: size, height: size)
|
|
127
|
+
case 2:
|
|
128
|
+
ZStack {
|
|
129
|
+
avatar(urls[0]).frame(width: child, height: child)
|
|
130
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
|
|
131
|
+
avatar(urls[1]).frame(width: child, height: child)
|
|
132
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading)
|
|
133
|
+
}
|
|
134
|
+
.frame(width: size, height: size)
|
|
135
|
+
case 3:
|
|
136
|
+
ZStack {
|
|
137
|
+
avatar(urls[0]).frame(width: child, height: child)
|
|
138
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing)
|
|
139
|
+
avatar(urls[1]).frame(width: child, height: child)
|
|
140
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
|
141
|
+
avatar(urls[2]).frame(width: child, height: child)
|
|
142
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading)
|
|
143
|
+
}
|
|
144
|
+
.frame(width: size, height: size)
|
|
145
|
+
.padding(2)
|
|
146
|
+
case 4:
|
|
147
|
+
ZStack {
|
|
148
|
+
avatar(urls[0]).frame(width: child, height: child)
|
|
149
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading)
|
|
150
|
+
avatar(urls[1]).frame(width: child, height: child)
|
|
151
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing)
|
|
152
|
+
avatar(urls[2]).frame(width: child, height: child)
|
|
153
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
|
|
154
|
+
avatar(urls[3]).frame(width: child, height: child)
|
|
155
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
156
|
+
}
|
|
157
|
+
.frame(width: size, height: size)
|
|
158
|
+
.padding(1)
|
|
159
|
+
default:
|
|
160
|
+
ZStack {
|
|
161
|
+
avatar(urls[0]).frame(width: child, height: child)
|
|
162
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading)
|
|
163
|
+
avatar(urls[1]).frame(width: child, height: child)
|
|
164
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
|
|
165
|
+
avatar(urls[2]).frame(width: child, height: child)
|
|
166
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
167
|
+
overflowTile(more: urls.count - 3)
|
|
168
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing)
|
|
169
|
+
}
|
|
170
|
+
.frame(width: size, height: size)
|
|
171
|
+
.padding(1)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private func avatar(_ url: String) -> some View {
|
|
176
|
+
ImageView(url)
|
|
177
|
+
.aspectRatio(contentMode: .fill)
|
|
178
|
+
.clipShape(Circle())
|
|
179
|
+
.overlay(Circle().stroke(Colors.black05, lineWidth: 0.5))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private func overflowTile(more: Int) -> some View {
|
|
183
|
+
MomoText("+\(more)", typography: .descriptionXsRegular, color: Colors.pink03, textAlign: .center, maxLines: 1)
|
|
184
|
+
.frame(width: child, height: child)
|
|
185
|
+
.background(Colors.pink09)
|
|
186
|
+
.clipShape(Circle())
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
@available(iOS 16.0, *)
|
|
191
|
+
private struct InitialsAvatar: View {
|
|
192
|
+
let name: String
|
|
193
|
+
let size: CGFloat
|
|
194
|
+
|
|
195
|
+
private var initials: String {
|
|
196
|
+
let words = name.trimmingCharacters(in: .whitespaces)
|
|
197
|
+
.split(whereSeparator: { $0.isWhitespace })
|
|
198
|
+
.suffix(2)
|
|
199
|
+
return words.compactMap { $0.first.map { String($0).uppercased() } }.joined()
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
var body: some View {
|
|
203
|
+
MomoText(initials, typography: .descriptionXsRegular, color: Colors.pink03, textAlign: .center, maxLines: 1)
|
|
204
|
+
.frame(width: size, height: size)
|
|
205
|
+
.background(Colors.pink09)
|
|
206
|
+
.clipShape(Circle())
|
|
207
|
+
.overlay(Circle().stroke(Colors.black05, lineWidth: 0.5))
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
//
|
|
2
|
+
// NavigationContainer.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// SwiftUI mirror of Compose's NavigationContainer.
|
|
6
|
+
// Wraps NavigationStack, injects Navigator into the environment, and renders
|
|
7
|
+
// every registered screen inside `Screen(options)` chrome — same auto-wrap
|
|
8
|
+
// behavior as Compose's StackScreen.
|
|
9
|
+
//
|
|
10
|
+
|
|
11
|
+
import SwiftUI
|
|
12
|
+
|
|
13
|
+
@available(iOS 16.0, *)
|
|
14
|
+
public struct NavigationContainer<Initial: View>: View {
|
|
15
|
+
@StateObject private var navigator: Navigator
|
|
16
|
+
@StateObject private var applicationEnvironment: ApplicationEnvironment
|
|
17
|
+
@StateObject private var localize: Localize
|
|
18
|
+
|
|
19
|
+
private let setNavigator: ((Navigator) -> Void)?
|
|
20
|
+
private let composeApi: KitComposeApi?
|
|
21
|
+
private let maxApi: MaxApi?
|
|
22
|
+
|
|
23
|
+
public init(
|
|
24
|
+
initialScreenName: String = "",
|
|
25
|
+
@ViewBuilder initialScreen: @escaping () -> Initial,
|
|
26
|
+
options: NavigationOptions? = nil,
|
|
27
|
+
applicationContext: MiniAppContext? = nil,
|
|
28
|
+
composeApi: KitComposeApi? = nil,
|
|
29
|
+
config: KitConfig? = nil,
|
|
30
|
+
maxApi: MaxApi? = nil,
|
|
31
|
+
localize: Localize? = nil,
|
|
32
|
+
setNavigator: ((Navigator) -> Void)? = nil
|
|
33
|
+
) {
|
|
34
|
+
let nav = Navigator(
|
|
35
|
+
initialScreenName: initialScreenName.isEmpty ? (options?.screenName ?? "") : initialScreenName,
|
|
36
|
+
initialContent: { AnyView(initialScreen()) },
|
|
37
|
+
options: options,
|
|
38
|
+
composeApi: composeApi
|
|
39
|
+
)
|
|
40
|
+
_navigator = StateObject(wrappedValue: nav)
|
|
41
|
+
_applicationEnvironment = StateObject(
|
|
42
|
+
wrappedValue: ApplicationEnvironment(
|
|
43
|
+
applicationContext: applicationContext,
|
|
44
|
+
composeApi: composeApi,
|
|
45
|
+
config: config,
|
|
46
|
+
maxApi: maxApi
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
_localize = StateObject(wrappedValue: localize ?? Localize())
|
|
50
|
+
self.composeApi = composeApi
|
|
51
|
+
self.maxApi = maxApi
|
|
52
|
+
self.setNavigator = setNavigator
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public var body: some View {
|
|
56
|
+
NavigationStack(path: $navigator.path) {
|
|
57
|
+
screenView(for: navigator.rootRoute)
|
|
58
|
+
.navigationDestination(for: DynamicScreenRoute.self) { route in
|
|
59
|
+
screenView(for: route)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
.environmentObject(navigator)
|
|
63
|
+
.environmentObject(applicationEnvironment)
|
|
64
|
+
.environment(\.applicationEnvironment, applicationEnvironment)
|
|
65
|
+
.environmentObject(localize)
|
|
66
|
+
.environment(\.localize, localize)
|
|
67
|
+
.fullScreenCover(item: $navigator.presented) { route in
|
|
68
|
+
screenView(forDialog: route)
|
|
69
|
+
.environmentObject(navigator)
|
|
70
|
+
.environmentObject(applicationEnvironment)
|
|
71
|
+
.environment(\.applicationEnvironment, applicationEnvironment)
|
|
72
|
+
.environmentObject(localize)
|
|
73
|
+
.environment(\.localize, localize)
|
|
74
|
+
}
|
|
75
|
+
.overlay {
|
|
76
|
+
if let item = navigator.overplay {
|
|
77
|
+
OverplayHost(item: item)
|
|
78
|
+
.environmentObject(navigator)
|
|
79
|
+
.environmentObject(applicationEnvironment)
|
|
80
|
+
.environment(\.applicationEnvironment, applicationEnvironment)
|
|
81
|
+
.environmentObject(localize)
|
|
82
|
+
.environment(\.localize, localize)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
.onAppear {
|
|
86
|
+
if navigator.composeApi == nil {
|
|
87
|
+
navigator.composeApi = composeApi
|
|
88
|
+
}
|
|
89
|
+
setNavigator?(navigator)
|
|
90
|
+
// Mirrors Compose's LaunchedEffect(maxApi, resolvedLocalize): host-reported
|
|
91
|
+
// language wins over the default "vi" only when it resolves to English.
|
|
92
|
+
maxApi?.getLanguage { data in
|
|
93
|
+
if parseLanguage(data) == Localize.EN {
|
|
94
|
+
localize.changeLanguage(Localize.EN)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@ViewBuilder
|
|
101
|
+
private func screenView(for route: DynamicScreenRoute) -> some View {
|
|
102
|
+
if let screen = navigator.registry.getScreen(id: route.id) {
|
|
103
|
+
ScreenHost(screen: screen, navigator: navigator)
|
|
104
|
+
.navigationBarBackButtonHidden(true)
|
|
105
|
+
} else {
|
|
106
|
+
EmptyView()
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@ViewBuilder
|
|
111
|
+
private func screenView(forDialog route: DynamicDialogRoute) -> some View {
|
|
112
|
+
if let screen = navigator.registry.getScreen(id: route.id) {
|
|
113
|
+
ScreenHost(screen: screen, navigator: navigator)
|
|
114
|
+
} else {
|
|
115
|
+
EmptyView()
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// MARK: - ScreenHost
|
|
121
|
+
|
|
122
|
+
@available(iOS 16.0, *)
|
|
123
|
+
private struct ScreenHost: View {
|
|
124
|
+
let screen: DynamicScreen
|
|
125
|
+
let navigator: Navigator
|
|
126
|
+
|
|
127
|
+
var body: some View {
|
|
128
|
+
let options = screen.options ?? NavigationOptions()
|
|
129
|
+
StackScreen(
|
|
130
|
+
backgroundColor: options.backgroundColor,
|
|
131
|
+
headerTransparent: options.headerTransparent,
|
|
132
|
+
fullScreenContent: options.fullScreenContent,
|
|
133
|
+
tintColor: options.tintColor ?? Colors.black17,
|
|
134
|
+
isBack: !options.hiddenBack,
|
|
135
|
+
headerType: options.headerType,
|
|
136
|
+
useAnimated: options.useAnimated,
|
|
137
|
+
title: options.title,
|
|
138
|
+
headerTitle: options.headerTitle,
|
|
139
|
+
titlePosition: options.titlePosition,
|
|
140
|
+
goBack: options.hiddenBack ? nil : { navigator.onBackSafe() },
|
|
141
|
+
scrollable: options.scrollable,
|
|
142
|
+
scrollToTopCallback: options.scrollToTopCallback,
|
|
143
|
+
useAvoidKeyboard: options.useAvoidKeyboard,
|
|
144
|
+
keyboardShouldPersistTaps: options.keyboardShouldPersistTaps,
|
|
145
|
+
footer: options.footer,
|
|
146
|
+
headerRight: options.resolvedHeaderRight,
|
|
147
|
+
animatedHeader: options.animatedHeader,
|
|
148
|
+
layoutOffset: options.layoutOffset,
|
|
149
|
+
inputSearchProps: options.inputSearchProps,
|
|
150
|
+
fabProps: options.fabProps,
|
|
151
|
+
screenName: screen.name,
|
|
152
|
+
content: { screen.content() }
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// MARK: - Overplay host
|
|
158
|
+
|
|
159
|
+
@available(iOS 16.0, *)
|
|
160
|
+
private struct OverplayHost: View {
|
|
161
|
+
let item: OverplayItem
|
|
162
|
+
@EnvironmentObject var navigator: Navigator
|
|
163
|
+
|
|
164
|
+
var body: some View {
|
|
165
|
+
// Each component self-animates on appear and drives its own exit through
|
|
166
|
+
// the Navigator before the item is removed (mirrors Compose's overplay).
|
|
167
|
+
switch item.type {
|
|
168
|
+
case .modal:
|
|
169
|
+
ModalScreen(barrierDismissible: item.barrierDismissible) {
|
|
170
|
+
item.content()
|
|
171
|
+
}
|
|
172
|
+
.id(item.id)
|
|
173
|
+
case .bottomSheet:
|
|
174
|
+
BottomSheetView(
|
|
175
|
+
header: item.header ?? .title(),
|
|
176
|
+
isSurface: item.isSurface,
|
|
177
|
+
barrierDismissible: item.barrierDismissible
|
|
178
|
+
) {
|
|
179
|
+
item.content()
|
|
180
|
+
}
|
|
181
|
+
.id(item.id)
|
|
182
|
+
case .snackBar:
|
|
183
|
+
if let snackBar = item.snackBar {
|
|
184
|
+
SnackBarView(snackBar: snackBar, bottomInset: safeAreaBottomInset() + Spacing.S)
|
|
185
|
+
.id(item.id)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/// Bottom safe-area inset (home indicator), used to lift the snack bar.
|
|
192
|
+
@available(iOS 16.0, *)
|
|
193
|
+
private func safeAreaBottomInset() -> CGFloat {
|
|
194
|
+
UIApplication.shared.connectedScenes
|
|
195
|
+
.compactMap { ($0 as? UIWindowScene)?.keyWindow?.safeAreaInsets.bottom }
|
|
196
|
+
.first ?? 0
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private func parseLanguage(_ raw: [String: Any?]?) -> String? {
|
|
200
|
+
guard let response = (raw?["response"] as? String)?.trimmingCharacters(in: .whitespaces).lowercased(),
|
|
201
|
+
!response.isEmpty else { return nil }
|
|
202
|
+
return response.hasPrefix(Localize.EN) ? Localize.EN : Localize.VI
|
|
203
|
+
}
|