@momo-kits/native-kits 0.300.3 → 0.300.4-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.md +51 -7
- package/README.md +2 -2
- package/build.gradle.kts +13 -0
- package/compose/build.gradle.kts +197 -0
- package/compose/build.gradle.kts.backup +197 -0
- package/compose/compose.podspec +47 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +20 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +6 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +12 -0
- 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 +153 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +6 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +110 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/DesignSystemKits.kt +62 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +1071 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +269 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +65 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +157 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +81 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +32 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +338 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +175 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +357 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +115 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +89 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +128 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +209 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +542 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +49 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +86 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +125 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +231 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +189 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +478 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +114 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +265 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +235 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +223 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +265 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +185 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +343 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +108 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +56 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +41 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +84 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +40 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +361 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +108 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +308 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +59 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +87 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +21 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +90 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +330 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +220 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +473 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +122 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +207 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +92 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +560 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +86 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +125 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +207 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +501 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +176 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +182 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +193 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +236 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +180 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +325 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +17 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +16 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +194 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +287 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +54 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +50 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +306 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +41 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +52 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +66 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +104 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +48 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +338 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +58 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +137 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +116 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +215 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +404 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +8 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +732 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +148 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +40 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +233 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +217 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +97 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +377 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +169 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +455 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +103 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +106 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +74 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +413 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +39 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +363 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HelpCenterRequest.kt +37 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +192 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +133 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/ToolkitRequests.kt +41 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +135 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +12 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +91 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +9 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +74 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +20 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +55 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +15 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +118 -0
- package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/HelpCenterRequestTest.kt +44 -0
- package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/ToolkitRequestsTest.kt +62 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +42 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +45 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +36 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +38 -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 +217 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +138 -0
- package/gradle/libs.versions.toml +68 -0
- package/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/gradle/wrapper/gradle-wrapper.properties +8 -0
- package/gradle.properties +26 -0
- package/gradlew +252 -0
- package/gradlew.bat +94 -0
- package/ios/Application/ApplicationEnvironment.swift +24 -6
- package/ios/Application/DesignSystemKits.swift +100 -0
- package/ios/Application/DesignSystemKitsPayload.swift +68 -0
- package/ios/Application/MaxApi.swift +30 -3
- package/ios/Avatar/Avatar.swift +4 -3
- package/ios/Badge/Badge.swift +6 -4
- package/ios/Badge/BadgeDot.swift +1 -1
- package/ios/Badge/BadgeRibbon.swift +8 -2
- package/ios/Button/Button.swift +15 -12
- package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +64 -18
- package/ios/Carousel/Carousel.swift +29 -12
- package/ios/Checkbox/Checkbox.swift +7 -3
- package/ios/Chip/Chip.swift +5 -4
- package/ios/Collapse/Collapse.swift +70 -19
- package/ios/Colors+Radius+Spacing/Colors.swift +10 -1
- package/ios/Colors+Radius+Spacing/Radius.swift +6 -0
- package/ios/DateTimePicker/DateTimePicker.swift +54 -20
- package/ios/DateTimePicker/WheelPicker.swift +103 -105
- package/ios/Divider/Divider.swift +37 -4
- package/ios/Extensions/Color++.swift +38 -9
- package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
- package/ios/Extensions/SkeletonUICompat.swift +11 -0
- package/ios/Icon/Icon.swift +21 -23
- package/ios/Icon/Icons.swift +1329 -0
- package/ios/IconButton/IconButton.swift +15 -7
- package/ios/Image/Image.swift +7 -23
- package/ios/Information/Information.swift +27 -9
- package/ios/Input/ErrorView.swift +2 -2
- package/ios/Input/Input.swift +171 -148
- package/ios/Input/InputPhoneNumber.swift +10 -9
- package/ios/Input/InputSearch.swift +107 -78
- package/ios/Input/InputTextArea.swift +133 -148
- package/ios/InputDropDown/InputDropDown.swift +10 -7
- package/ios/InputMoney/InputMoney.swift +32 -11
- package/ios/InputOTP/InputOTP.swift +22 -12
- package/ios/Layout/Card.swift +50 -0
- package/ios/Layout/GridContext.swift +187 -3
- package/ios/Layout/Item.swift +5 -4
- package/ios/Layout/ScreenLayout.swift +415 -0
- package/ios/Layout/Section.swift +4 -100
- package/ios/Loader/Loader.swift +5 -9
- package/ios/Lottie/LottieView.swift +14 -2
- package/ios/{Application/Navigation/Overplay → Navigation}/BottomSheet.swift +169 -29
- package/ios/Navigation/BottomTab/BottomTab.swift +332 -0
- package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +18 -7
- package/ios/Navigation/BottomTab/DuoBarCompression.swift +104 -0
- package/ios/Navigation/BottomTab/DuoTabBar.swift +329 -0
- package/ios/{Application → Navigation/Component}/FloatingButton.swift +43 -54
- package/ios/Navigation/Component/Header.swift +545 -0
- package/ios/Navigation/Component/HeaderBackground.swift +154 -0
- package/ios/Navigation/Component/HeaderJourney.swift +73 -0
- package/ios/Navigation/Component/HeaderLocation.swift +48 -0
- package/ios/Navigation/Component/HeaderRight.swift +425 -0
- package/ios/Navigation/Component/HeaderTitle.swift +36 -0
- package/ios/{Application/Navigation → Navigation/Component}/HeaderUser.swift +21 -24
- package/ios/Navigation/Component/SideBar.swift +183 -0
- package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +8 -8
- package/ios/Navigation/HostBackGestureBridge.swift +78 -0
- package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +9 -2
- package/ios/Navigation/Navigation.swift +204 -0
- package/ios/Navigation/NavigationContainer.swift +195 -0
- package/ios/{Application/Navigation → Navigation}/Navigator.swift +207 -107
- package/ios/Navigation/StackScreen.swift +930 -0
- package/ios/Navigation/StackTransition.swift +400 -0
- package/ios/Navigation/SwipeBackHaptic.swift +33 -0
- package/ios/{Application/Navigation → Navigation}/Tracking/ScreenTracker.swift +12 -5
- package/ios/OTPKeyboard/OTPKeyboard.swift +8 -11
- package/ios/Pagination/PaginationScroll.swift +2 -2
- package/ios/Popup/PopupDisplay.swift +72 -135
- package/ios/Popup/PopupInput.swift +10 -13
- package/ios/Popup/PopupLayout.swift +138 -0
- package/ios/Popup/PopupNotify.swift +59 -69
- package/ios/Popup/PopupPromotion.swift +31 -47
- package/ios/PopupView/FullscreenPopup.swift +13 -19
- package/ios/PopupView/PopupView.swift +1 -1
- package/ios/PopupView/Utils++.swift +0 -8
- package/ios/ProgressInfo/ProgressInfo.swift +4 -2
- package/ios/Radio/Radio.swift +2 -1
- package/ios/Rating/Rating.swift +9 -1
- package/ios/Resources/dot_loading.json +558 -0
- package/ios/Resources/lottie_circle_loader.json +1 -0
- package/ios/ScrollIndicator/ScrollIndicator.swift +11 -10
- package/ios/Skeleton/Skeleton.swift +22 -27
- package/ios/Slider/Slider.swift +16 -18
- package/ios/Stepper/Stepper.swift +7 -3
- package/ios/Steps/Steps.swift +8 -6
- package/ios/SuggestAction/SuggestAction.swift +5 -0
- package/ios/Swipeable/SwipeCell.swift +10 -6
- package/ios/Switch/Switch.swift +22 -20
- package/ios/TabView/TabView.swift +102 -21
- package/ios/Tag/Tag.swift +15 -4
- package/ios/Template/Logo/Logo.swift +2 -1
- package/ios/Template/TrustBanner/TrustBanner.swift +109 -55
- package/ios/Title/Title.swift +6 -17
- package/ios/Tooltip/Tooltip.swift +29 -14
- package/ios/Typography/FontScale.swift +17 -14
- package/ios/Typography/Text.swift +156 -20
- package/ios/Typography/Typography.swift +2 -2
- package/ios/Uploader/Uploader.swift +3 -1
- package/ios/native-kits.podspec +2 -3
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -3
- package/ios-demo/README.md +120 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +1 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +80 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +2 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +1 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +2 -2
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +2 -0
- package/package.json +1 -1
- package/publish.sh +19 -2
- package/scripts/gen-ios-icons.mjs +36 -0
- package/settings.gradle.kts +64 -0
- package/ios/Application/Components.swift +0 -270
- package/ios/Application/ComposeApi.swift +0 -22
- package/ios/Application/HeaderRight.swift +0 -306
- package/ios/Application/Navigation/BottomTab/BottomTab.swift +0 -104
- package/ios/Application/Navigation/HeaderBackProps.swift +0 -66
- package/ios/Application/Navigation/HeaderTitle.swift +0 -57
- package/ios/Application/Navigation/NavigationContainer.swift +0 -213
- package/ios/Application/Navigation/NavigationOptions.swift +0 -109
- package/ios/Application/Navigation/NavigatorManager.swift +0 -22
- package/ios/Application/Navigation/component/Header.swift +0 -178
- package/ios/Application/Navigation/component/HeaderBackground.swift +0 -83
- package/ios/Application/Navigation/component/HeaderColor.swift +0 -65
- package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +0 -11
- package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
- package/ios/Application/Navigation/component/NavigationHeaderRight.swift +0 -196
- package/ios/Application/Screen.swift +0 -250
- package/ios/Application/StackScreen.swift +0 -492
- package/ios/Typography/FontScaleStore.swift +0 -58
- package/ios/Typography/KitDesignSystem.swift +0 -17
- /package/ios/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// HeaderUser.swift
|
|
3
3
|
// MoMoUIKits
|
|
4
4
|
//
|
|
5
|
-
// SwiftUI mirror of Compose's `
|
|
5
|
+
// SwiftUI mirror of Compose's `navigation/component/HeaderUser.kt`.
|
|
6
6
|
// Renders a `HeaderTitle.User`: an avatar group (1..n avatars with overflow),
|
|
7
7
|
// a title with optional inline icons, and an optional subtitle. Shows a
|
|
8
8
|
// skeleton shimmer placeholder while `isLoading`.
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
|
|
11
11
|
import SwiftUI
|
|
12
12
|
|
|
13
|
-
@available(iOS 16.0, *)
|
|
14
13
|
public struct HeaderUserView: View {
|
|
15
14
|
private let data: HeaderUserData
|
|
16
15
|
|
|
@@ -20,9 +19,7 @@ public struct HeaderUserView: View {
|
|
|
20
19
|
|
|
21
20
|
private let avatarSize: CGFloat = Spacing.XXL // 32
|
|
22
21
|
|
|
23
|
-
private var
|
|
24
|
-
UIScreen.main.bounds.width - 172
|
|
25
|
-
}
|
|
22
|
+
@Environment(\.screenLayout) private var layout
|
|
26
23
|
|
|
27
24
|
public var body: some View {
|
|
28
25
|
if data.isLoading {
|
|
@@ -33,6 +30,14 @@ public struct HeaderUserView: View {
|
|
|
33
30
|
}
|
|
34
31
|
|
|
35
32
|
private var content: some View {
|
|
33
|
+
SwiftUI.Button(action: { data.onPress?() }) {
|
|
34
|
+
row
|
|
35
|
+
}
|
|
36
|
+
.buttonStyle(ActiveOpacityButtonStyle())
|
|
37
|
+
.disabled(data.onPress == nil)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private var row: some View {
|
|
36
41
|
HStack(spacing: 0) {
|
|
37
42
|
HStack(spacing: 0) {
|
|
38
43
|
ZStack(alignment: .bottomTrailing) {
|
|
@@ -49,8 +54,7 @@ public struct HeaderUserView: View {
|
|
|
49
54
|
|
|
50
55
|
VStack(alignment: .leading, spacing: 0) {
|
|
51
56
|
HStack(spacing: 0) {
|
|
52
|
-
MomoText(data.title, typography: .actionXsBold, color: data.tintColor
|
|
53
|
-
.truncationMode(.tail)
|
|
57
|
+
MomoText(data.title, typography: .actionXsBold, color: data.tintColor, maxLines: 1, fontFamily: headerFontFamily)
|
|
54
58
|
|
|
55
59
|
if !data.icons.isEmpty {
|
|
56
60
|
Spacer().frame(width: 6)
|
|
@@ -66,34 +70,27 @@ public struct HeaderUserView: View {
|
|
|
66
70
|
|
|
67
71
|
if let subTitle = data.subTitle, !subTitle.isEmpty {
|
|
68
72
|
Spacer().frame(height: 2)
|
|
69
|
-
MomoText(subTitle, typography: .descriptionXsRegular, color: data.tintColor
|
|
70
|
-
.truncationMode(.tail)
|
|
73
|
+
MomoText(subTitle, typography: .descriptionXsRegular, color: data.tintColor, maxLines: 1)
|
|
71
74
|
}
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
|
-
.frame(maxWidth:
|
|
77
|
+
.frame(maxWidth: headerTitleMaxWidth(layout), alignment: .leading)
|
|
75
78
|
|
|
76
79
|
Spacer(minLength: 0)
|
|
77
80
|
}
|
|
78
81
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
79
82
|
.padding(.vertical, Spacing.XS)
|
|
80
83
|
.contentShape(Rectangle())
|
|
81
|
-
.onTapGesture { data.onPress?() }
|
|
82
|
-
.allowsHitTesting(data.onPress != nil)
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
private var shimmer: some View {
|
|
86
87
|
HStack(spacing: Spacing.M / 2) {
|
|
87
|
-
|
|
88
|
-
.fill(Colors.black05)
|
|
88
|
+
Skeleton()
|
|
89
89
|
.frame(width: avatarSize, height: avatarSize)
|
|
90
|
+
.clipShape(Circle())
|
|
90
91
|
VStack(alignment: .leading, spacing: Spacing.XXS) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
.frame(width: 120, height: 18)
|
|
94
|
-
RoundedRectangle(cornerRadius: 8)
|
|
95
|
-
.fill(Colors.black05)
|
|
96
|
-
.frame(width: 120, height: 12)
|
|
92
|
+
titleSkeleton(height: 18)
|
|
93
|
+
titleSkeleton(height: 12)
|
|
97
94
|
}
|
|
98
95
|
}
|
|
99
96
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
@@ -105,8 +102,8 @@ public struct HeaderUserView: View {
|
|
|
105
102
|
|
|
106
103
|
/// Mirrors Compose's `AvatarGroup`: lays out 1..4 avatars in a fixed collage,
|
|
107
104
|
/// falls back to initials when empty, and shows a "+n" overflow tile beyond 4.
|
|
108
|
-
@available(iOS 16.0, *)
|
|
109
105
|
private struct AvatarGroup: View {
|
|
106
|
+
@Environment(\.appTheme) private var theme
|
|
110
107
|
let urls: [String]
|
|
111
108
|
let title: String
|
|
112
109
|
let size: CGFloat
|
|
@@ -176,7 +173,7 @@ private struct AvatarGroup: View {
|
|
|
176
173
|
ImageView(url)
|
|
177
174
|
.aspectRatio(contentMode: .fill)
|
|
178
175
|
.clipShape(Circle())
|
|
179
|
-
.overlay(Circle().stroke(
|
|
176
|
+
.overlay(Circle().stroke(theme.colors.border.default, lineWidth: 0.5))
|
|
180
177
|
}
|
|
181
178
|
|
|
182
179
|
private func overflowTile(more: Int) -> some View {
|
|
@@ -187,8 +184,8 @@ private struct AvatarGroup: View {
|
|
|
187
184
|
}
|
|
188
185
|
}
|
|
189
186
|
|
|
190
|
-
@available(iOS 16.0, *)
|
|
191
187
|
private struct InitialsAvatar: View {
|
|
188
|
+
@Environment(\.appTheme) private var theme
|
|
192
189
|
let name: String
|
|
193
190
|
let size: CGFloat
|
|
194
191
|
|
|
@@ -204,6 +201,6 @@ private struct InitialsAvatar: View {
|
|
|
204
201
|
.frame(width: size, height: size)
|
|
205
202
|
.background(Colors.pink09)
|
|
206
203
|
.clipShape(Circle())
|
|
207
|
-
.overlay(Circle().stroke(
|
|
204
|
+
.overlay(Circle().stroke(theme.colors.border.default, lineWidth: 0.5))
|
|
208
205
|
}
|
|
209
206
|
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
//
|
|
2
|
+
// SideBar.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// SwiftUI mirror of Compose's `navigation/component/SideBar.kt`. iPhone Duo: back and toolkit
|
|
6
|
+
// leave the header bar for a column in the side safe area, as the system bars do.
|
|
7
|
+
// Port of RN `SideBarButton.tsx` + `useSideBarItemStyle`.
|
|
8
|
+
//
|
|
9
|
+
|
|
10
|
+
import SwiftUI
|
|
11
|
+
|
|
12
|
+
private let sideBarIconSize: CGFloat = 24
|
|
13
|
+
|
|
14
|
+
/// The back button and toolkit column, placed in window coordinates over the side strip.
|
|
15
|
+
struct HeaderSideBar: View {
|
|
16
|
+
let sideBar: SideBarLayout
|
|
17
|
+
let goBack: (() -> Void)?
|
|
18
|
+
let toolkit: (data: HeaderRightData?, tintColor: Color?)?
|
|
19
|
+
let headerColor: HeaderColor
|
|
20
|
+
/// What footer / tab bar / home indicator take from the bottom, gap included; the column stays above it.
|
|
21
|
+
let bottomInset: CGFloat
|
|
22
|
+
|
|
23
|
+
@Environment(\.screenLayout) private var layout
|
|
24
|
+
@Environment(\.applicationEnvironment) private var environment
|
|
25
|
+
@Environment(\.duoBarCompression) private var compression
|
|
26
|
+
@State private var columnHeight: CGFloat = 0
|
|
27
|
+
|
|
28
|
+
private var isRight: Bool { sideBar.edge == .right }
|
|
29
|
+
|
|
30
|
+
private var top: CGFloat {
|
|
31
|
+
let maxY = layout.height - bottomInset - columnHeight
|
|
32
|
+
return max(min(sideBar.top, maxY), 0)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Full height, so the tab bar compression decision follows this screen's toolkit.
|
|
36
|
+
private var fullHeight: CGFloat {
|
|
37
|
+
let toolkitHeight = toolkit.map {
|
|
38
|
+
Spacing.M + toolkitColumnHeight($0.data ?? HeaderRightData(), context: environment.applicationContext)
|
|
39
|
+
}
|
|
40
|
+
return DuoMetrics.controlSize + (toolkitHeight ?? 0)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
var body: some View {
|
|
44
|
+
VStack(alignment: isRight ? .leading : .trailing, spacing: Spacing.M) {
|
|
45
|
+
if let goBack {
|
|
46
|
+
SideBarButton(
|
|
47
|
+
icon: "arrow-back",
|
|
48
|
+
headerColor: headerColor,
|
|
49
|
+
accessibilityId: "btn_navigation_back",
|
|
50
|
+
action: goBack
|
|
51
|
+
)
|
|
52
|
+
} else {
|
|
53
|
+
// keep the slot so the toolkit does not jump between root and pushed screens
|
|
54
|
+
Color.clear.frame(width: DuoMetrics.controlSize, height: DuoMetrics.controlSize)
|
|
55
|
+
}
|
|
56
|
+
if let toolkit {
|
|
57
|
+
if compression.toolbarHidden {
|
|
58
|
+
// an open tab rail takes this slot for a moment
|
|
59
|
+
EmptyView()
|
|
60
|
+
} else if compression.toolbarCompressed {
|
|
61
|
+
// HIG "Toolbar compressed": tools fold into one button, distinct from the useMore icon
|
|
62
|
+
SideBarButton(
|
|
63
|
+
icon: "navigation_menu",
|
|
64
|
+
headerColor: headerColor,
|
|
65
|
+
showBadge: toolkit.data?.showBadge ?? false,
|
|
66
|
+
accessibilityId: "btn_navigation_toolbar",
|
|
67
|
+
action: compression.expandToolbar
|
|
68
|
+
)
|
|
69
|
+
} else {
|
|
70
|
+
Toolkit(
|
|
71
|
+
headerRight: toolkit.data ?? HeaderRightData(),
|
|
72
|
+
tintColor: toolkit.tintColor,
|
|
73
|
+
layout: .sideBarColumn
|
|
74
|
+
)
|
|
75
|
+
.environment(\.headerColor, headerColor)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
.padding(isRight ? .leading : .trailing, Spacing.M)
|
|
80
|
+
.frame(width: sideBar.width, alignment: isRight ? .leading : .trailing)
|
|
81
|
+
.background(
|
|
82
|
+
GeometryReader { proxy in
|
|
83
|
+
Color.clear
|
|
84
|
+
.onAppear { columnHeight = proxy.size.height }
|
|
85
|
+
.onChange(of: proxy.size.height) { columnHeight = $0 }
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
.padding(.top, top)
|
|
89
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: isRight ? .topTrailing : .topLeading)
|
|
90
|
+
.ignoresSafeArea()
|
|
91
|
+
.onAppear { compression.setToolbarHeight(fullHeight) }
|
|
92
|
+
.onChange(of: fullHeight) { compression.setToolbarHeight($0) }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Dark glass under light icons (banner headers), light glass otherwise.
|
|
97
|
+
private struct SideBarGlass<S: Shape>: ViewModifier {
|
|
98
|
+
let headerColor: HeaderColor
|
|
99
|
+
let shape: S
|
|
100
|
+
|
|
101
|
+
private var isDark: Bool { headerColor.tintIconColor == Colors.black01 || headerColor.tintIconColor == .white }
|
|
102
|
+
|
|
103
|
+
func body(content: Content) -> some View {
|
|
104
|
+
if isDark {
|
|
105
|
+
content
|
|
106
|
+
.background(shape.fill(Colors.black20.opacity(0.5)))
|
|
107
|
+
.overlay(shape.stroke(Colors.black01.opacity(0.2), lineWidth: 0.5))
|
|
108
|
+
} else {
|
|
109
|
+
content
|
|
110
|
+
.background(
|
|
111
|
+
shape.fill(Colors.black01.opacity(0.85))
|
|
112
|
+
.shadow(color: Color.black.opacity(0.12), radius: 4, x: 0, y: 2)
|
|
113
|
+
)
|
|
114
|
+
.overlay(shape.stroke(Colors.black20.opacity(0.08), lineWidth: 0.5))
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// 48pt glass circle for a single side bar control.
|
|
120
|
+
struct SideBarButton: View {
|
|
121
|
+
let icon: String
|
|
122
|
+
let headerColor: HeaderColor
|
|
123
|
+
var showBadge: Bool = false
|
|
124
|
+
var disabled: Bool = false
|
|
125
|
+
var accessibilityId: String?
|
|
126
|
+
let action: () -> Void
|
|
127
|
+
|
|
128
|
+
var body: some View {
|
|
129
|
+
SwiftUI.Button(action: action) {
|
|
130
|
+
Icon(source: icon, size: sideBarIconSize, color: headerColor.tintIconColor)
|
|
131
|
+
.frame(width: DuoMetrics.controlSize, height: DuoMetrics.controlSize)
|
|
132
|
+
.modifier(SideBarGlass(headerColor: headerColor, shape: Circle()))
|
|
133
|
+
.overlay(alignment: .topTrailing) {
|
|
134
|
+
if showBadge {
|
|
135
|
+
BadgeDot(size: .small).offset(x: -Spacing.XXS, y: Spacing.XXS)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
.buttonStyle(ActiveOpacityButtonStyle())
|
|
140
|
+
.disabled(disabled)
|
|
141
|
+
.accessibilityIdentifier(accessibilityId ?? "")
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/// 48pt glass capsule grouping related controls, one item per 50pt: vertical in the side bar,
|
|
146
|
+
/// horizontal on a header row.
|
|
147
|
+
struct SideBarGroup<Content: View>: View {
|
|
148
|
+
let headerColor: HeaderColor
|
|
149
|
+
let itemCount: Int
|
|
150
|
+
var horizontal: Bool = false
|
|
151
|
+
@ViewBuilder let content: () -> Content
|
|
152
|
+
|
|
153
|
+
var body: some View {
|
|
154
|
+
let length = itemCount > 1 ? DuoMetrics.itemPitch * CGFloat(itemCount) : DuoMetrics.controlSize
|
|
155
|
+
Group {
|
|
156
|
+
if horizontal {
|
|
157
|
+
HStack(spacing: 0) { content() }
|
|
158
|
+
.frame(width: length, height: DuoMetrics.controlSize)
|
|
159
|
+
} else {
|
|
160
|
+
VStack(spacing: 0) { content() }
|
|
161
|
+
.frame(width: DuoMetrics.controlSize, height: length)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
.modifier(SideBarGlass(headerColor: headerColor, shape: RoundedRectangle(cornerRadius: Radius.XL)))
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
struct SideBarGroupItem: View {
|
|
169
|
+
let icon: String
|
|
170
|
+
let headerColor: HeaderColor
|
|
171
|
+
let accessibilityId: String
|
|
172
|
+
let action: () -> Void
|
|
173
|
+
|
|
174
|
+
var body: some View {
|
|
175
|
+
SwiftUI.Button(action: action) {
|
|
176
|
+
Icon(source: icon, size: sideBarIconSize, color: headerColor.tintIconColor)
|
|
177
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
178
|
+
.contentShape(Rectangle())
|
|
179
|
+
}
|
|
180
|
+
.buttonStyle(ActiveOpacityButtonStyle())
|
|
181
|
+
.accessibilityIdentifier(accessibilityId)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
// SnackBar.swift
|
|
3
3
|
// MoMoUIKits
|
|
4
4
|
//
|
|
5
|
-
// SwiftUI mirror of Compose's `
|
|
6
|
-
//
|
|
7
|
-
// indicator), stays for `duration`, then slides back down and is removed.
|
|
5
|
+
// SwiftUI mirror of Compose's `navigation/component/SnackBar.kt`: slides up above the screen's
|
|
6
|
+
// footer (or the home indicator), stays for `duration`, then slides back down.
|
|
8
7
|
// `Navigator.showSnackBar(_:)` drives it through the overplay host.
|
|
9
8
|
//
|
|
10
9
|
|
|
@@ -50,11 +49,11 @@ public struct SnackBar {
|
|
|
50
49
|
|
|
51
50
|
/// Self-animating container. Slides the content up on appear, schedules an
|
|
52
51
|
/// auto-dismiss after `duration`, and animates out before removing the overplay.
|
|
53
|
-
@available(iOS 16.0, *)
|
|
54
52
|
struct SnackBarView: View {
|
|
53
|
+
let overplayId: UUID
|
|
55
54
|
let snackBar: SnackBar
|
|
56
|
-
///
|
|
57
|
-
/// (
|
|
55
|
+
/// Distance from the window bottom to the top of the screen's footer, or the navigation bar
|
|
56
|
+
/// inset without one (Compose's `footerHeight` offset).
|
|
58
57
|
let bottomInset: CGFloat
|
|
59
58
|
|
|
60
59
|
@EnvironmentObject private var navigator: Navigator
|
|
@@ -78,6 +77,7 @@ struct SnackBarView: View {
|
|
|
78
77
|
.padding(.bottom, bottomInset)
|
|
79
78
|
}
|
|
80
79
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
80
|
+
.ignoresSafeArea()
|
|
81
81
|
.onAppear {
|
|
82
82
|
guard !didAppear else { return }
|
|
83
83
|
didAppear = true
|
|
@@ -122,7 +122,7 @@ struct SnackBarView: View {
|
|
|
122
122
|
offsetY = max(contentHeight, 200) + bottomInset
|
|
123
123
|
}
|
|
124
124
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
|
125
|
-
navigator.finishOverplayDismiss()
|
|
125
|
+
navigator.finishOverplayDismiss(id: overplayId)
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -134,7 +134,7 @@ struct SnackBarView: View {
|
|
|
134
134
|
guard let duration = snackBar.duration else { return }
|
|
135
135
|
let workItem = DispatchWorkItem {
|
|
136
136
|
// Only auto-dismiss if this snack bar is still the active overplay.
|
|
137
|
-
if navigator.overplay?.
|
|
137
|
+
if navigator.overplay?.id == overplayId {
|
|
138
138
|
navigator.hideSnackBar()
|
|
139
139
|
}
|
|
140
140
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//
|
|
2
|
+
// HostBackGestureBridge.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// SwiftUI mirror of Compose's `navigation/HostBackGestureBridge.kt`: the host owns the edge-pan
|
|
6
|
+
// recogniser and routes a back into the topmost kit stack. NavigationContainer attaches itself.
|
|
7
|
+
// `started` / `progressed` / `completed` / `cancelled` scrub the pop transition with the finger;
|
|
8
|
+
// a host that only calls `pop` gets the regular animated back.
|
|
9
|
+
//
|
|
10
|
+
|
|
11
|
+
import UIKit
|
|
12
|
+
|
|
13
|
+
public final class HostBackGestureBridge {
|
|
14
|
+
public static let shared = HostBackGestureBridge()
|
|
15
|
+
private init() {}
|
|
16
|
+
|
|
17
|
+
// Weak: a container torn down without disappearing must not keep receiving backs.
|
|
18
|
+
private var navigators: [WeakNavigator] = []
|
|
19
|
+
private weak var gestureTarget: Navigator?
|
|
20
|
+
|
|
21
|
+
/// Set while a bottom tab other than the first is selected; the host shows its floating arrow.
|
|
22
|
+
public var shouldUseFloatingArrowBack = false
|
|
23
|
+
|
|
24
|
+
public func attach(_ navigator: Navigator) {
|
|
25
|
+
navigators.removeAll { $0.value == nil }
|
|
26
|
+
guard !navigators.contains(where: { $0.value === navigator }) else { return }
|
|
27
|
+
navigators.append(WeakNavigator(value: navigator))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public func detach(_ navigator: Navigator) {
|
|
31
|
+
navigators.removeAll { $0.value == nil || $0.value === navigator }
|
|
32
|
+
if gestureTarget === navigator { gestureTarget = nil }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Whether a kit stack is on screen at all.
|
|
36
|
+
public func checkCanPop() -> Bool { top() != nil }
|
|
37
|
+
|
|
38
|
+
/// Whether the top stack pops a screen itself; false means a back dismisses the mini app.
|
|
39
|
+
public func canPopInner() -> Bool { top()?.canPop() == true }
|
|
40
|
+
|
|
41
|
+
public func pop() { top()?.onBackSafe() }
|
|
42
|
+
|
|
43
|
+
public func started() {
|
|
44
|
+
guard let target = top() else { return }
|
|
45
|
+
gestureTarget = target
|
|
46
|
+
target.backStarted()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// `progress` is the touch travel over the window width, 0...1.
|
|
50
|
+
public func progressed(_ progress: CGFloat) {
|
|
51
|
+
gestureTarget?.backProgressed(min(max(progress, 0), 1))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public func completed() {
|
|
55
|
+
(gestureTarget ?? top())?.backCompleted()
|
|
56
|
+
gestureTarget = nil
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public func cancelled() {
|
|
60
|
+
gestureTarget?.backCancelled()
|
|
61
|
+
gestureTarget = nil
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@available(*, deprecated, renamed: "attach")
|
|
65
|
+
public func push(_ navigator: Navigator) { attach(navigator) }
|
|
66
|
+
|
|
67
|
+
@available(*, deprecated, renamed: "detach")
|
|
68
|
+
public func remove(_ navigator: Navigator) { detach(navigator) }
|
|
69
|
+
|
|
70
|
+
private func top() -> Navigator? { navigators.last { $0.value != nil }?.value }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@available(*, deprecated, renamed: "HostBackGestureBridge")
|
|
74
|
+
public typealias NavigatorManager = HostBackGestureBridge
|
|
75
|
+
|
|
76
|
+
private struct WeakNavigator {
|
|
77
|
+
weak var value: Navigator?
|
|
78
|
+
}
|
|
@@ -9,13 +9,14 @@
|
|
|
9
9
|
|
|
10
10
|
import SwiftUI
|
|
11
11
|
|
|
12
|
-
@available(iOS 16.0, *)
|
|
13
12
|
struct ModalScreen<Content: View>: View {
|
|
13
|
+
let overplayId: UUID
|
|
14
14
|
let barrierDismissible: Bool
|
|
15
15
|
let content: () -> Content
|
|
16
16
|
|
|
17
17
|
@EnvironmentObject private var navigator: Navigator
|
|
18
18
|
@Environment(\.applicationEnvironment) private var appEnv
|
|
19
|
+
@Environment(\.screenLayout) private var layout
|
|
19
20
|
|
|
20
21
|
@State private var scrimOpacity: CGFloat = 0
|
|
21
22
|
@State private var contentScale: CGFloat = 0.8
|
|
@@ -35,7 +36,13 @@ struct ModalScreen<Content: View>: View {
|
|
|
35
36
|
content()
|
|
36
37
|
.scaleEffect(contentScale)
|
|
37
38
|
.opacity(contentOpacity)
|
|
39
|
+
// iPhone Duo: center below the side status bar and above the home indicator
|
|
40
|
+
.padding(.top, layout.isDuoDevice ? layout.duoTopInset : 0)
|
|
41
|
+
.padding(.bottom, layout.isDuoDevice ? layout.insets.bottom : 0)
|
|
38
42
|
}
|
|
43
|
+
// Full screen like RN's Modal, so asymmetric landscape insets don't shift the content sideways.
|
|
44
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
45
|
+
.ignoresSafeArea(.container)
|
|
39
46
|
.onAppear {
|
|
40
47
|
guard !didAppear else { return }
|
|
41
48
|
didAppear = true
|
|
@@ -68,7 +75,7 @@ struct ModalScreen<Content: View>: View {
|
|
|
68
75
|
contentOpacity = 0
|
|
69
76
|
}
|
|
70
77
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
|
71
|
-
navigator.finishOverplayDismiss()
|
|
78
|
+
navigator.finishOverplayDismiss(id: overplayId)
|
|
72
79
|
}
|
|
73
80
|
}
|
|
74
81
|
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Navigation.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// SwiftUI mirror of Compose's `navigation/Navigation.kt`: a screen's `NavigationOptions` and the
|
|
6
|
+
// per-screen `Navigation` that updates them at runtime.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import SwiftUI
|
|
10
|
+
|
|
11
|
+
/// Per-screen handle (Compose `LocalNavigation`). Read it inside a screen with
|
|
12
|
+
/// `@EnvironmentObject var navigation: Navigation` and call `setOptions` to change the header,
|
|
13
|
+
/// footer or back handling after the screen is shown.
|
|
14
|
+
public final class Navigation: ObservableObject {
|
|
15
|
+
public let id: Int
|
|
16
|
+
public let bottomTabIndex: Int
|
|
17
|
+
@Published public private(set) var options: NavigationOptions
|
|
18
|
+
|
|
19
|
+
private weak var registry: DynamicScreenRegistry?
|
|
20
|
+
/// What the footer (or navigation bar) takes from the window bottom; lifts snack bars.
|
|
21
|
+
var bottomObstruction: CGFloat = 0
|
|
22
|
+
|
|
23
|
+
init(
|
|
24
|
+
id: Int = -1,
|
|
25
|
+
bottomTabIndex: Int = -1,
|
|
26
|
+
options: NavigationOptions?,
|
|
27
|
+
registry: DynamicScreenRegistry? = nil
|
|
28
|
+
) {
|
|
29
|
+
self.id = id
|
|
30
|
+
self.bottomTabIndex = bottomTabIndex
|
|
31
|
+
self.options = options ?? NavigationOptions()
|
|
32
|
+
self.registry = registry
|
|
33
|
+
if id != -1 { registry?.bind(self) }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public func setOptions(_ options: NavigationOptions) {
|
|
37
|
+
update(options)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Changes only what the closure touches, like Compose's named-argument `setOptions`.
|
|
41
|
+
public func setOptions(_ transform: (inout NavigationOptions) -> Void) {
|
|
42
|
+
var updated = options
|
|
43
|
+
transform(&updated)
|
|
44
|
+
update(updated)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private func update(_ updated: NavigationOptions) {
|
|
48
|
+
options = updated
|
|
49
|
+
// back handling reads the registry copy
|
|
50
|
+
if id != -1 { registry?.setOptions(id: id, options: updated) }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public struct NavigationOptions {
|
|
55
|
+
public var screenName: String?
|
|
56
|
+
public var title: String
|
|
57
|
+
/// Rich header title (Compose `headerTitle`); `title` is used when nil.
|
|
58
|
+
public var headerTitle: HeaderTitle?
|
|
59
|
+
public var titlePosition: TitlePosition
|
|
60
|
+
public var headerType: HeaderType
|
|
61
|
+
/// Compose's `HeaderType.Default/Extended(useAnimated:)`: with `inputSearchProps`, the search
|
|
62
|
+
/// rises into the header on scroll.
|
|
63
|
+
public var useAnimated: Bool
|
|
64
|
+
/// Compose's `HeaderType.Transparent`; with `fullScreenContent` the content starts under it.
|
|
65
|
+
public var headerTransparent: Bool
|
|
66
|
+
public var fullScreenContent: Bool
|
|
67
|
+
public var hiddenBack: Bool
|
|
68
|
+
public var backgroundColor: Color?
|
|
69
|
+
// AI-GENERATED START: custom screen background view (Compose backgroundComponent)
|
|
70
|
+
/// Drawn instead of `backgroundColor` over the whole window: under the header and the status bar,
|
|
71
|
+
/// across the iPhone Duo side strips, and not shrunk by the keyboard. Pair with `headerTransparent`.
|
|
72
|
+
public var background: (() -> AnyView)?
|
|
73
|
+
// AI-GENERATED END: custom screen background view (Compose backgroundComponent)
|
|
74
|
+
public var tintColor: Color?
|
|
75
|
+
public var scrollable: Bool
|
|
76
|
+
/// Compose's `ScrollData.scrollToTopCallback`, fired on a status-bar tap.
|
|
77
|
+
public var scrollToTopCallback: (() -> Void)?
|
|
78
|
+
public var useAvoidKeyboard: Bool
|
|
79
|
+
/// Compose's `KeyboardOptions.keyboardShouldPersistTaps`: a tap anywhere hides the keyboard.
|
|
80
|
+
public var keyboardShouldPersistTaps: Bool
|
|
81
|
+
public var headerBackProps: HeaderBackProps?
|
|
82
|
+
/// Toolkit data for the default header right (Compose `HeaderRight.Toolkit`). Use `headerRight`
|
|
83
|
+
/// for a custom view and nil for none.
|
|
84
|
+
public var headerRightData: HeaderRightData?
|
|
85
|
+
public var headerRight: (() -> any View)?
|
|
86
|
+
public var footer: (() -> AnyView)?
|
|
87
|
+
public var footerTransparent: Bool
|
|
88
|
+
public var footerUseMargin: Bool
|
|
89
|
+
public var animatedHeader: AnimatedHeader?
|
|
90
|
+
public var layoutOffset: CGFloat
|
|
91
|
+
public var inputSearchProps: InputSearchProps?
|
|
92
|
+
public var fabProps: FabProps?
|
|
93
|
+
public var onBackHandler: (() -> Void)?
|
|
94
|
+
|
|
95
|
+
public init(
|
|
96
|
+
screenName: String? = nil,
|
|
97
|
+
title: String = "Stack",
|
|
98
|
+
headerTitle: HeaderTitle? = nil,
|
|
99
|
+
titlePosition: TitlePosition = .left,
|
|
100
|
+
headerType: HeaderType = .default,
|
|
101
|
+
useAnimated: Bool = false,
|
|
102
|
+
headerTransparent: Bool = false,
|
|
103
|
+
fullScreenContent: Bool = false,
|
|
104
|
+
hiddenBack: Bool = false,
|
|
105
|
+
backgroundColor: Color? = nil,
|
|
106
|
+
// AI-GENERATED START: custom screen background view (Compose backgroundComponent)
|
|
107
|
+
background: (() -> AnyView)? = nil,
|
|
108
|
+
// AI-GENERATED END: custom screen background view (Compose backgroundComponent)
|
|
109
|
+
tintColor: Color? = Colors.black17,
|
|
110
|
+
scrollable: Bool = true,
|
|
111
|
+
scrollToTopCallback: (() -> Void)? = nil,
|
|
112
|
+
useAvoidKeyboard: Bool = true,
|
|
113
|
+
keyboardShouldPersistTaps: Bool = false,
|
|
114
|
+
headerBackProps: HeaderBackProps? = nil,
|
|
115
|
+
headerRightData: HeaderRightData? = nil,
|
|
116
|
+
headerRight: (() -> any View)? = { HeaderRight() },
|
|
117
|
+
footer: (() -> AnyView)? = nil,
|
|
118
|
+
footerTransparent: Bool = false,
|
|
119
|
+
footerUseMargin: Bool = true,
|
|
120
|
+
animatedHeader: AnimatedHeader? = nil,
|
|
121
|
+
layoutOffset: CGFloat = 56,
|
|
122
|
+
inputSearchProps: InputSearchProps? = nil,
|
|
123
|
+
fabProps: FabProps? = nil,
|
|
124
|
+
onBackHandler: (() -> Void)? = nil
|
|
125
|
+
) {
|
|
126
|
+
self.screenName = screenName
|
|
127
|
+
self.title = title
|
|
128
|
+
self.headerTitle = headerTitle
|
|
129
|
+
self.titlePosition = titlePosition
|
|
130
|
+
self.headerType = headerType
|
|
131
|
+
self.useAnimated = useAnimated
|
|
132
|
+
self.headerTransparent = headerTransparent
|
|
133
|
+
self.fullScreenContent = fullScreenContent
|
|
134
|
+
self.hiddenBack = hiddenBack
|
|
135
|
+
self.backgroundColor = backgroundColor
|
|
136
|
+
// AI-GENERATED START: custom screen background view (Compose backgroundComponent)
|
|
137
|
+
self.background = background
|
|
138
|
+
// AI-GENERATED END: custom screen background view (Compose backgroundComponent)
|
|
139
|
+
self.tintColor = tintColor
|
|
140
|
+
self.scrollable = scrollable
|
|
141
|
+
self.scrollToTopCallback = scrollToTopCallback
|
|
142
|
+
self.useAvoidKeyboard = useAvoidKeyboard
|
|
143
|
+
self.keyboardShouldPersistTaps = keyboardShouldPersistTaps
|
|
144
|
+
self.headerBackProps = headerBackProps
|
|
145
|
+
self.headerRightData = headerRightData
|
|
146
|
+
self.headerRight = headerRight
|
|
147
|
+
self.footer = footer
|
|
148
|
+
self.footerTransparent = footerTransparent
|
|
149
|
+
self.footerUseMargin = footerUseMargin
|
|
150
|
+
self.animatedHeader = animatedHeader
|
|
151
|
+
self.layoutOffset = layoutOffset
|
|
152
|
+
self.inputSearchProps = inputSearchProps
|
|
153
|
+
self.fabProps = fabProps
|
|
154
|
+
self.onBackHandler = onBackHandler
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
extension NavigationOptions {
|
|
159
|
+
var resolvedHeaderRight: (() -> any View)? {
|
|
160
|
+
guard let headerRightData else { return headerRight }
|
|
161
|
+
return { HeaderRight(headerRight: headerRightData, tintColor: tintColor) }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// `StackScreen` built from options, shared by stack screens and bottom-tab screens.
|
|
166
|
+
struct OptionsStackScreen: View {
|
|
167
|
+
let options: NavigationOptions
|
|
168
|
+
let screenName: String
|
|
169
|
+
let goBack: (() -> Void)?
|
|
170
|
+
let content: () -> AnyView
|
|
171
|
+
|
|
172
|
+
var body: some View {
|
|
173
|
+
StackScreen(
|
|
174
|
+
backgroundColor: options.backgroundColor,
|
|
175
|
+
// AI-GENERATED START: custom screen background view (Compose backgroundComponent)
|
|
176
|
+
background: options.background,
|
|
177
|
+
// AI-GENERATED END: custom screen background view (Compose backgroundComponent)
|
|
178
|
+
headerTransparent: options.headerTransparent,
|
|
179
|
+
fullScreenContent: options.fullScreenContent,
|
|
180
|
+
tintColor: options.tintColor ?? Colors.black17,
|
|
181
|
+
isBack: !options.hiddenBack,
|
|
182
|
+
headerType: options.headerType,
|
|
183
|
+
useAnimated: options.useAnimated,
|
|
184
|
+
title: options.title,
|
|
185
|
+
headerTitle: options.headerTitle,
|
|
186
|
+
titlePosition: options.titlePosition,
|
|
187
|
+
goBack: options.hiddenBack ? nil : goBack,
|
|
188
|
+
scrollable: options.scrollable,
|
|
189
|
+
scrollToTopCallback: options.scrollToTopCallback,
|
|
190
|
+
useAvoidKeyboard: options.useAvoidKeyboard,
|
|
191
|
+
keyboardShouldPersistTaps: options.keyboardShouldPersistTaps,
|
|
192
|
+
footer: options.footer,
|
|
193
|
+
footerTransparent: options.footerTransparent,
|
|
194
|
+
footerUseMargin: options.footerUseMargin,
|
|
195
|
+
headerRight: options.resolvedHeaderRight,
|
|
196
|
+
animatedHeader: options.animatedHeader,
|
|
197
|
+
layoutOffset: options.layoutOffset,
|
|
198
|
+
inputSearchProps: options.inputSearchProps,
|
|
199
|
+
fabProps: options.fabProps,
|
|
200
|
+
screenName: screenName,
|
|
201
|
+
content: content
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
}
|