@momo-kits/native-kits 0.167.1-beta.9-debug → 0.167.1-duo-search.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 +36 -0
- package/ios/Application/ApplicationEnvironment.swift +6 -0
- package/ios/Application/Components.swift +1 -3
- package/ios/Application/HeaderRight.swift +32 -14
- package/ios/Application/Layout/ScreenLayout.swift +312 -0
- package/ios/Application/Navigation/BottomTab/BottomTab.swift +67 -10
- package/ios/Application/Navigation/BottomTab/BottomTabBar.swift +5 -2
- package/ios/Application/Navigation/BottomTab/DuoBarCompression.swift +105 -0
- package/ios/Application/Navigation/BottomTab/DuoTabBar.swift +304 -0
- package/ios/Application/Navigation/HeaderUser.swift +4 -4
- package/ios/Application/Navigation/NavigationContainer.swift +48 -42
- package/ios/Application/Navigation/Navigator.swift +122 -29
- package/ios/Application/Navigation/NavigatorManager.swift +13 -6
- package/ios/Application/Navigation/Overplay/BottomSheet.swift +41 -4
- package/ios/Application/Navigation/Overplay/ModalScreen.swift +7 -0
- package/ios/Application/Navigation/component/DuoScrollGradient.swift +54 -0
- package/ios/Application/Navigation/component/Header.swift +11 -6
- package/ios/Application/Navigation/component/HeaderBackground.swift +3 -2
- package/ios/Application/Navigation/component/NavigationFooter.swift +1 -1
- package/ios/Application/Navigation/component/NavigationHeaderRight.swift +93 -9
- package/ios/Application/Navigation/component/SideBar.swift +155 -0
- package/ios/Application/StackScreen.swift +175 -18
- package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +59 -5
- package/ios/Colors+Radius+Spacing/Colors.swift +1 -1
- package/ios/Extensions/Color++.swift +20 -9
- package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
- package/ios/Icon/Icon.swift +14 -22
- package/ios/Icon/Icons.swift +1329 -0
- package/ios/Image/Image.swift +16 -5
- package/ios/Lottie/LottieView.swift +4 -2
- package/ios/Popup/PopupDisplay.swift +84 -67
- package/ios/Popup/PopupLayout.swift +45 -0
- package/ios/Popup/PopupNotify.swift +44 -26
- package/ios/Popup/PopupPromotion.swift +18 -6
- package/ios/Resources/dot_loading.json +558 -0
- package/ios/Resources/lottie_circle_loader.json +1 -0
- package/ios/Template/TrustBanner/TrustBanner.swift +69 -31
- package/ios/Typography/FontScale.swift +3 -3
- package/ios/native-kits.podspec +1 -1
- package/ios-demo/MoMoUIKitsDemo.podspec +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/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/scripts/gen-ios-icons.mjs +36 -0
- package/build.gradle.kts +0 -11
- package/compose/build.gradle.kts +0 -190
- package/compose/build.gradle.kts.backup +0 -190
- package/compose/compose.podspec +0 -47
- package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +0 -19
- package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +0 -6
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +0 -12
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +0 -7
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +0 -151
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +0 -124
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +0 -831
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +0 -269
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +0 -56
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +0 -157
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +0 -82
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +0 -32
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +0 -338
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +0 -175
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +0 -356
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +0 -113
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -89
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +0 -128
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +0 -209
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +0 -542
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +0 -49
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +0 -76
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +0 -125
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +0 -231
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +0 -189
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +0 -475
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +0 -114
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +0 -266
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +0 -240
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +0 -223
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +0 -264
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +0 -185
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +0 -343
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +0 -108
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +0 -56
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +0 -41
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +0 -84
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +0 -40
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +0 -328
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +0 -97
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +0 -310
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +0 -59
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +0 -87
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +0 -21
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +0 -90
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +0 -323
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +0 -220
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +0 -473
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +0 -122
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +0 -199
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +0 -91
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +0 -552
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +0 -88
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +0 -125
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +0 -207
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +0 -498
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +0 -176
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +0 -182
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +0 -193
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +0 -29
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +0 -234
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +0 -188
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +0 -325
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +0 -16
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +0 -14
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +0 -22
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +0 -12
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +0 -16
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +0 -221
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +0 -270
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +0 -52
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +0 -38
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +0 -39
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +0 -52
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +0 -66
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +0 -11
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +0 -104
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +0 -48
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +0 -51
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +0 -299
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +0 -58
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +0 -129
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +0 -106
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +0 -201
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +0 -393
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +0 -8
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +0 -595
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +0 -148
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +0 -35
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +0 -175
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +0 -225
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +0 -86
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +0 -169
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +0 -358
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +0 -83
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +0 -106
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +0 -75
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +0 -309
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +0 -40
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +0 -364
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +0 -132
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +0 -127
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +0 -12
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +0 -91
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +0 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +0 -71
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +0 -1329
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +0 -55
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +0 -15
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +0 -105
- package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +0 -40
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +0 -45
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +0 -32
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +0 -38
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +0 -66
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +0 -213
- package/gradle/libs.versions.toml +0 -69
- package/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/gradle/wrapper/gradle-wrapper.properties +0 -8
- package/gradle.properties +0 -26
- package/gradlew +0 -252
- package/gradlew.bat +0 -94
- package/ios-demo/README.md +0 -120
- package/local.properties +0 -8
- package/settings.gradle.kts +0 -64
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
//
|
|
2
|
+
// DuoBarCompression.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// Port of RN `BottomTab/DuoBarCompression.tsx` (HIG, Designing for iPhone Duo: Vertical controls):
|
|
6
|
+
// keeps the side bar's toolbar and tab bar clear of each other.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import SwiftUI
|
|
10
|
+
|
|
11
|
+
/// iPhone Duo: which side bar bar starts compressed when the strip cannot hold both whole.
|
|
12
|
+
public enum DuoBarCompressionBehavior {
|
|
13
|
+
case toolbar, tabBar
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
enum DuoBar {
|
|
17
|
+
case toolbar, tabBar
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
struct DuoBarCompression {
|
|
21
|
+
var toolbarCompressed = false
|
|
22
|
+
var tabBarCompressed = false
|
|
23
|
+
/// The open rail needs the toolbar button slot too: only Back stays.
|
|
24
|
+
var toolbarHidden = false
|
|
25
|
+
var expandToolbar: () -> Void = {}
|
|
26
|
+
var expandTabBar: () -> Void = {}
|
|
27
|
+
/// A rail opened from the compressed state closes once a tab is picked.
|
|
28
|
+
var onTabSelected: () -> Void = {}
|
|
29
|
+
/// Side bar toolkits report their full height so the tab bar knows what is left.
|
|
30
|
+
var setToolbarHeight: (CGFloat) -> Void = { _ in }
|
|
31
|
+
|
|
32
|
+
/// Space kept between the top and the bottom groups (HIG Vertical controls).
|
|
33
|
+
static let groupGap = Spacing.M
|
|
34
|
+
/// Back, then the right toolkit: help + home capsule.
|
|
35
|
+
static let defaultToolbarHeight = DuoMetrics.controlSize + Spacing.M + 2 * DuoMetrics.itemPitch
|
|
36
|
+
/// Back, then the single button the toolbar folds into.
|
|
37
|
+
private static let compressedToolbarHeight = 2 * DuoMetrics.controlSize + Spacing.M
|
|
38
|
+
/// Back alone, while a rail opened from the compressed state takes the toolbar slot.
|
|
39
|
+
private static let backOnlyHeight = DuoMetrics.controlSize
|
|
40
|
+
|
|
41
|
+
/// - both fit: both stay whole
|
|
42
|
+
/// - one fits whole beside the other compressed: one is compressed, tapping it swaps them
|
|
43
|
+
/// - otherwise both rest compressed; tapping one opens it for a moment (the rail closes
|
|
44
|
+
/// again once a tab is picked)
|
|
45
|
+
static func resolve(
|
|
46
|
+
layout: ScreenLayout,
|
|
47
|
+
tabCount: Int,
|
|
48
|
+
hasQr: Bool,
|
|
49
|
+
toolbarHeight: CGFloat,
|
|
50
|
+
expanded: Binding<DuoBar>,
|
|
51
|
+
opened: Binding<DuoBar?>,
|
|
52
|
+
setToolbarHeight: @escaping (CGFloat) -> Void
|
|
53
|
+
) -> DuoBarCompression {
|
|
54
|
+
guard let sideBar = layout.sideBar else { return DuoBarCompression() }
|
|
55
|
+
let room = layout.height - sideBar.bottom - sideBar.top - groupGap
|
|
56
|
+
let tabBar = duoRailHeight(itemCount: tabCount, hasQr: hasQr)
|
|
57
|
+
let compressedTabBar = duoRailHeight(itemCount: 1, hasQr: hasQr, compressed: true)
|
|
58
|
+
func fits(_ toolbar: CGFloat, _ tabs: CGFloat) -> Bool { toolbar + tabs <= room }
|
|
59
|
+
|
|
60
|
+
if fits(toolbarHeight, tabBar) {
|
|
61
|
+
return DuoBarCompression(setToolbarHeight: setToolbarHeight)
|
|
62
|
+
}
|
|
63
|
+
if fits(compressedToolbarHeight, tabBar) && fits(toolbarHeight, compressedTabBar) {
|
|
64
|
+
return DuoBarCompression(
|
|
65
|
+
toolbarCompressed: expanded.wrappedValue == .tabBar,
|
|
66
|
+
tabBarCompressed: expanded.wrappedValue == .toolbar,
|
|
67
|
+
expandToolbar: { expanded.wrappedValue = .toolbar },
|
|
68
|
+
expandTabBar: { expanded.wrappedValue = .tabBar },
|
|
69
|
+
setToolbarHeight: setToolbarHeight
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let expandToolbar = { opened.wrappedValue = .toolbar }
|
|
74
|
+
let expandTabBar = { opened.wrappedValue = .tabBar }
|
|
75
|
+
if opened.wrappedValue == .tabBar && fits(backOnlyHeight, tabBar) {
|
|
76
|
+
return DuoBarCompression(
|
|
77
|
+
toolbarCompressed: true,
|
|
78
|
+
tabBarCompressed: false,
|
|
79
|
+
toolbarHidden: true,
|
|
80
|
+
expandToolbar: expandToolbar,
|
|
81
|
+
expandTabBar: expandTabBar,
|
|
82
|
+
onTabSelected: { opened.wrappedValue = nil },
|
|
83
|
+
setToolbarHeight: setToolbarHeight
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
return DuoBarCompression(
|
|
87
|
+
toolbarCompressed: opened.wrappedValue != .toolbar,
|
|
88
|
+
tabBarCompressed: true,
|
|
89
|
+
expandToolbar: expandToolbar,
|
|
90
|
+
expandTabBar: expandTabBar,
|
|
91
|
+
setToolbarHeight: setToolbarHeight
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private struct DuoBarCompressionKey: EnvironmentKey {
|
|
97
|
+
static let defaultValue = DuoBarCompression()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
extension EnvironmentValues {
|
|
101
|
+
var duoBarCompression: DuoBarCompression {
|
|
102
|
+
get { self[DuoBarCompressionKey.self] }
|
|
103
|
+
set { self[DuoBarCompressionKey.self] = newValue }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
//
|
|
2
|
+
// DuoTabBar.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// iPhone Duo tab bar, port of RN `BottomTab/DuoTabBar.tsx` (Figma Fold-Design-System MT1/MT2):
|
|
6
|
+
// a glass rail in the side bar, or a floating glass bar when unfolded portrait.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import SwiftUI
|
|
10
|
+
|
|
11
|
+
private let itemSize = DuoMetrics.controlSize
|
|
12
|
+
private let iconSize: CGFloat = 28
|
|
13
|
+
private let qrIconSize: CGFloat = 24
|
|
14
|
+
private let railPadding: CGFloat = 6
|
|
15
|
+
private let railGap = Spacing.XXS
|
|
16
|
+
// selection bubble overflows the 48 rail cell by 5 above and below (Figma MT2)
|
|
17
|
+
private let railBubbleWidth: CGFloat = 44
|
|
18
|
+
private let railBubbleHeight: CGFloat = 58
|
|
19
|
+
let duoFloatingTabBarHeight: CGFloat = 62
|
|
20
|
+
private let floatingPadding = Spacing.XS
|
|
21
|
+
private let floatingQrSize: CGFloat = 44
|
|
22
|
+
// floating bar spans 6 of the 8 portrait grid columns, like the content
|
|
23
|
+
private let gridColumns: CGFloat = 8
|
|
24
|
+
private let gridSpan: CGFloat = 6
|
|
25
|
+
private let tabSpring = Animation.interpolatingSpring(mass: 0.8, stiffness: 220, damping: 18)
|
|
26
|
+
|
|
27
|
+
/// Room tab screens leave for the Duo tab bar, provided by BottomTab to its tab StackScreens.
|
|
28
|
+
struct DuoTabBarInsets: Equatable {
|
|
29
|
+
/// Floating bar overlays the content: scroll content, footer and FAB keep clear of it.
|
|
30
|
+
var overlay: CGFloat = 0
|
|
31
|
+
/// Rail sits at the bottom of the side strip: the header side bar stays this far above the window bottom.
|
|
32
|
+
var sideBarReserve: CGFloat = 0
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private struct DuoTabBarInsetsKey: EnvironmentKey {
|
|
36
|
+
static let defaultValue = DuoTabBarInsets()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
extension EnvironmentValues {
|
|
40
|
+
var duoTabBarInsets: DuoTabBarInsets {
|
|
41
|
+
get { self[DuoTabBarInsetsKey.self] }
|
|
42
|
+
set { self[DuoTabBarInsetsKey.self] = newValue }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Rail plus the QR button under it; compressed, the rail is a single button.
|
|
47
|
+
func duoRailHeight(itemCount: Int, hasQr: Bool, compressed: Bool = false) -> CGFloat {
|
|
48
|
+
let count = CGFloat(itemCount)
|
|
49
|
+
let rail = compressed ? itemSize : railPadding * 2 + count * itemSize + max(count - 1, 0) * railGap
|
|
50
|
+
return rail + (hasQr ? Spacing.M + itemSize : 0)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
extension ScreenLayout {
|
|
54
|
+
func duoTabBarInsets(itemCount: Int, hasFloatingButton: Bool, tabBarCompressed: Bool = false) -> DuoTabBarInsets {
|
|
55
|
+
guard isIPhoneDuo else { return DuoTabBarInsets() }
|
|
56
|
+
guard let sideBar else {
|
|
57
|
+
return DuoTabBarInsets(overlay: duoFloatingTabBarHeight + Spacing.M)
|
|
58
|
+
}
|
|
59
|
+
let rail = duoRailHeight(itemCount: itemCount, hasQr: hasFloatingButton, compressed: tabBarCompressed)
|
|
60
|
+
return DuoTabBarInsets(sideBarReserve: sideBar.bottom + rail + DuoBarCompression.groupGap)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Glass on iOS 26, a system blur before it (RN GlassView's fallback).
|
|
65
|
+
struct DuoBarMaterial<S: Shape>: ViewModifier {
|
|
66
|
+
let shape: S
|
|
67
|
+
var visible: Bool = true
|
|
68
|
+
|
|
69
|
+
func body(content: Content) -> some View {
|
|
70
|
+
content.background(material.allowsHitTesting(false))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@ViewBuilder
|
|
74
|
+
private var material: some View {
|
|
75
|
+
#if compiler(>=6.2)
|
|
76
|
+
if #available(iOS 26.0, *) {
|
|
77
|
+
// glass materializes by toggling its effect, it does not fade with opacity
|
|
78
|
+
Color.clear
|
|
79
|
+
.glassEffect(visible ? .regular : .identity, in: shape)
|
|
80
|
+
.shadow(color: Color.black.opacity(visible ? 0.16 : 0), radius: 16, x: 0, y: 6)
|
|
81
|
+
.animation(.easeInOut(duration: 0.25), value: visible)
|
|
82
|
+
} else {
|
|
83
|
+
blur
|
|
84
|
+
}
|
|
85
|
+
#else
|
|
86
|
+
blur
|
|
87
|
+
#endif
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private var blur: some View {
|
|
91
|
+
shape.fill(.regularMaterial)
|
|
92
|
+
.shadow(color: Color.black.opacity(0.16), radius: 16, x: 0, y: 6)
|
|
93
|
+
.opacity(visible ? 1 : 0)
|
|
94
|
+
.animation(.easeInOut(duration: 0.25), value: visible)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
struct DuoTabBar: View {
|
|
99
|
+
let items: [BottomTabItem]
|
|
100
|
+
let floatingButton: BottomTabFloatingButton?
|
|
101
|
+
let selectedIndex: Int
|
|
102
|
+
let onTabSelected: (Int) -> Void
|
|
103
|
+
var compression = DuoBarCompression()
|
|
104
|
+
|
|
105
|
+
@Environment(\.screenLayout) private var layout
|
|
106
|
+
|
|
107
|
+
var body: some View {
|
|
108
|
+
if let sideBar = layout.sideBar {
|
|
109
|
+
DuoRailTabBar(sideBar: sideBar, items: items, floatingButton: floatingButton,
|
|
110
|
+
selectedIndex: selectedIndex, onTabSelected: onTabSelected, compression: compression)
|
|
111
|
+
} else {
|
|
112
|
+
DuoFloatingTabBar(items: items, floatingButton: floatingButton,
|
|
113
|
+
selectedIndex: selectedIndex, onTabSelected: onTabSelected)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Vertical bar anchored to the bottom of the side bar, QR button below it (Figma MT1-C/E).
|
|
119
|
+
/// When the side bar is short it can shrink to one button showing the current tab
|
|
120
|
+
/// (HIG "Tab bar compressed"); tapping it expands the rail and compresses the toolbar.
|
|
121
|
+
private struct DuoRailTabBar: View {
|
|
122
|
+
let sideBar: SideBarLayout
|
|
123
|
+
let items: [BottomTabItem]
|
|
124
|
+
let floatingButton: BottomTabFloatingButton?
|
|
125
|
+
let selectedIndex: Int
|
|
126
|
+
let onTabSelected: (Int) -> Void
|
|
127
|
+
let compression: DuoBarCompression
|
|
128
|
+
|
|
129
|
+
@Environment(\.appTheme) private var theme
|
|
130
|
+
|
|
131
|
+
private var isRight: Bool { sideBar.edge == .right }
|
|
132
|
+
|
|
133
|
+
var body: some View {
|
|
134
|
+
VStack(spacing: Spacing.M) {
|
|
135
|
+
if compression.tabBarCompressed, items.indices.contains(selectedIndex) {
|
|
136
|
+
SwiftUI.Button(action: compression.expandTabBar) {
|
|
137
|
+
DuoTabIcon(item: items[selectedIndex], selected: true)
|
|
138
|
+
.frame(width: itemSize, height: itemSize)
|
|
139
|
+
.contentShape(Circle())
|
|
140
|
+
}
|
|
141
|
+
.buttonStyle(.plain)
|
|
142
|
+
.modifier(DuoBarMaterial(shape: Circle()))
|
|
143
|
+
.accessibilityLabel(items[selectedIndex].label)
|
|
144
|
+
.accessibilityIdentifier("bottom_tab_bar")
|
|
145
|
+
} else {
|
|
146
|
+
rail
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if let floatingButton {
|
|
150
|
+
DuoQrButton(data: floatingButton, size: itemSize)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
.padding(isRight ? .leading : .trailing, Spacing.M)
|
|
154
|
+
.frame(width: sideBar.width, alignment: isRight ? .leading : .trailing)
|
|
155
|
+
.padding(.bottom, sideBar.bottom)
|
|
156
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: isRight ? .bottomTrailing : .bottomLeading)
|
|
157
|
+
.ignoresSafeArea()
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/// Glass column of tab items with the sliding selection bubble.
|
|
161
|
+
private var rail: some View {
|
|
162
|
+
ZStack(alignment: .top) {
|
|
163
|
+
Capsule()
|
|
164
|
+
.fill(theme.colors.background.selected)
|
|
165
|
+
.frame(width: railBubbleWidth, height: railBubbleHeight)
|
|
166
|
+
.offset(y: railPadding - (railBubbleHeight - itemSize) / 2
|
|
167
|
+
+ CGFloat(selectedIndex) * (itemSize + railGap))
|
|
168
|
+
.animation(tabSpring, value: selectedIndex)
|
|
169
|
+
|
|
170
|
+
VStack(spacing: railGap) {
|
|
171
|
+
ForEach(items.indices, id: \.self) { index in
|
|
172
|
+
SwiftUI.Button {
|
|
173
|
+
onTabSelected(index)
|
|
174
|
+
compression.onTabSelected()
|
|
175
|
+
} label: {
|
|
176
|
+
DuoTabIcon(item: items[index], selected: index == selectedIndex)
|
|
177
|
+
.frame(width: itemSize, height: itemSize)
|
|
178
|
+
.contentShape(Rectangle())
|
|
179
|
+
}
|
|
180
|
+
.buttonStyle(.plain)
|
|
181
|
+
.accessibilityLabel(items[index].label)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
.padding(.vertical, railPadding)
|
|
185
|
+
}
|
|
186
|
+
.frame(width: itemSize)
|
|
187
|
+
.modifier(DuoBarMaterial(shape: RoundedRectangle(cornerRadius: Radius.XL)))
|
|
188
|
+
.accessibilityIdentifier("bottom_tab_bar")
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// Floating labelled bar for the unfolded portrait display, QR in the middle cell (Figma MT1-M).
|
|
193
|
+
private struct DuoFloatingTabBar: View {
|
|
194
|
+
let items: [BottomTabItem]
|
|
195
|
+
let floatingButton: BottomTabFloatingButton?
|
|
196
|
+
let selectedIndex: Int
|
|
197
|
+
let onTabSelected: (Int) -> Void
|
|
198
|
+
|
|
199
|
+
@Environment(\.screenLayout) private var layout
|
|
200
|
+
@Environment(\.appTheme) private var theme
|
|
201
|
+
|
|
202
|
+
private var barWidth: CGFloat {
|
|
203
|
+
let columnWidth = (layout.width - 2 * Spacing.M - (gridColumns - 1) * Spacing.M) / gridColumns
|
|
204
|
+
return columnWidth * gridSpan + (gridSpan - 1) * Spacing.M
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private var useQr: Bool { floatingButton != nil && items.count % 2 == 0 }
|
|
208
|
+
private var middle: Int { items.count / 2 }
|
|
209
|
+
private var cellCount: Int { items.count + (useQr ? 1 : 0) }
|
|
210
|
+
private var cellWidth: CGFloat { (barWidth - floatingPadding * 2) / CGFloat(max(cellCount, 1)) }
|
|
211
|
+
private var visualIndex: Int { useQr && selectedIndex >= middle ? selectedIndex + 1 : selectedIndex }
|
|
212
|
+
|
|
213
|
+
var body: some View {
|
|
214
|
+
ZStack(alignment: .leading) {
|
|
215
|
+
Capsule()
|
|
216
|
+
.fill(theme.colors.background.selected)
|
|
217
|
+
.frame(width: cellWidth)
|
|
218
|
+
.offset(x: CGFloat(visualIndex) * cellWidth)
|
|
219
|
+
.animation(tabSpring, value: visualIndex)
|
|
220
|
+
|
|
221
|
+
HStack(spacing: 0) {
|
|
222
|
+
ForEach(0..<cellCount, id: \.self) { cell in
|
|
223
|
+
if useQr && cell == middle, let floatingButton {
|
|
224
|
+
DuoQrButton(data: floatingButton, size: floatingQrSize)
|
|
225
|
+
.frame(width: cellWidth)
|
|
226
|
+
} else {
|
|
227
|
+
let index = useQr && cell > middle ? cell - 1 : cell
|
|
228
|
+
tabCell(index)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
.padding(floatingPadding)
|
|
234
|
+
.frame(width: barWidth, height: duoFloatingTabBarHeight)
|
|
235
|
+
.modifier(DuoBarMaterial(shape: Capsule()))
|
|
236
|
+
.accessibilityIdentifier("bottom_tab_bar")
|
|
237
|
+
.padding(.bottom, layout.insets.bottom + Spacing.M)
|
|
238
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
|
|
239
|
+
.ignoresSafeArea()
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private func tabCell(_ index: Int) -> some View {
|
|
243
|
+
let selected = index == selectedIndex
|
|
244
|
+
return SwiftUI.Button { onTabSelected(index) } label: {
|
|
245
|
+
VStack(spacing: Spacing.XXS) {
|
|
246
|
+
DuoTabIcon(item: items[index], selected: selected)
|
|
247
|
+
MomoText(
|
|
248
|
+
items[index].label,
|
|
249
|
+
typography: .labelXsMedium,
|
|
250
|
+
color: selected ? theme.colors.primary : theme.colors.text.default,
|
|
251
|
+
textAlign: .center,
|
|
252
|
+
maxLines: 1
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
.frame(width: cellWidth)
|
|
256
|
+
.frame(maxHeight: .infinity)
|
|
257
|
+
.contentShape(Rectangle())
|
|
258
|
+
}
|
|
259
|
+
.buttonStyle(.plain)
|
|
260
|
+
.accessibilityLabel(items[index].label)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private struct DuoTabIcon: View {
|
|
265
|
+
let item: BottomTabItem
|
|
266
|
+
let selected: Bool
|
|
267
|
+
|
|
268
|
+
@Environment(\.appTheme) private var theme
|
|
269
|
+
|
|
270
|
+
private var badgeLabel: String? {
|
|
271
|
+
guard let label = item.badgeLabel else { return nil }
|
|
272
|
+
if let n = Int(label), n == 0 { return nil }
|
|
273
|
+
return label
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
var body: some View {
|
|
277
|
+
Icon(source: item.icon, size: iconSize, color: selected ? theme.colors.primary : theme.colors.text.default)
|
|
278
|
+
.overlay(alignment: .topTrailing) {
|
|
279
|
+
if let badgeLabel, !badgeLabel.isEmpty {
|
|
280
|
+
Badge(label: badgeLabel).offset(x: Spacing.S, y: -Spacing.XS)
|
|
281
|
+
} else if badgeLabel != nil || item.showDot {
|
|
282
|
+
BadgeDot(size: .small)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private struct DuoQrButton: View {
|
|
289
|
+
let data: BottomTabFloatingButton
|
|
290
|
+
let size: CGFloat
|
|
291
|
+
|
|
292
|
+
@Environment(\.appTheme) private var theme
|
|
293
|
+
|
|
294
|
+
var body: some View {
|
|
295
|
+
SwiftUI.Button(action: data.onPress) {
|
|
296
|
+
Circle()
|
|
297
|
+
.fill(theme.colors.primary)
|
|
298
|
+
.frame(width: size, height: size)
|
|
299
|
+
.overlay(Icon(source: data.icon, size: qrIconSize, color: Colors.black01))
|
|
300
|
+
}
|
|
301
|
+
.buttonStyle(.plain)
|
|
302
|
+
.accessibilityLabel(data.label)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
@@ -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,8 +19,11 @@ public struct HeaderUserView: View {
|
|
|
20
19
|
|
|
21
20
|
private let avatarSize: CGFloat = Spacing.XXL // 32
|
|
22
21
|
|
|
22
|
+
@Environment(\.screenLayout) private var layout
|
|
23
|
+
|
|
24
|
+
// back button and toolkit share the bar unless the iPhone Duo side bar holds them
|
|
23
25
|
private var maxWidth: CGFloat {
|
|
24
|
-
|
|
26
|
+
layout.sideBar != nil ? layout.contentWidth - Spacing.M * 2 : layout.width - 172
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
public var body: some View {
|
|
@@ -105,7 +107,6 @@ public struct HeaderUserView: View {
|
|
|
105
107
|
|
|
106
108
|
/// Mirrors Compose's `AvatarGroup`: lays out 1..4 avatars in a fixed collage,
|
|
107
109
|
/// falls back to initials when empty, and shows a "+n" overflow tile beyond 4.
|
|
108
|
-
@available(iOS 16.0, *)
|
|
109
110
|
private struct AvatarGroup: View {
|
|
110
111
|
let urls: [String]
|
|
111
112
|
let title: String
|
|
@@ -187,7 +188,6 @@ private struct AvatarGroup: View {
|
|
|
187
188
|
}
|
|
188
189
|
}
|
|
189
190
|
|
|
190
|
-
@available(iOS 16.0, *)
|
|
191
191
|
private struct InitialsAvatar: View {
|
|
192
192
|
let name: String
|
|
193
193
|
let size: CGFloat
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
// MoMoUIKits
|
|
4
4
|
//
|
|
5
5
|
// SwiftUI mirror of Compose's NavigationContainer.
|
|
6
|
-
// Wraps NavigationStack, injects Navigator into the environment, and renders
|
|
7
|
-
// every registered screen inside `
|
|
8
|
-
// behavior as Compose's StackScreen.
|
|
6
|
+
// Wraps NavigationStack (iOS 16+), injects Navigator into the environment, and renders
|
|
7
|
+
// every registered screen inside `StackScreen` chrome built from its options.
|
|
9
8
|
//
|
|
10
9
|
|
|
11
10
|
import SwiftUI
|
|
@@ -55,40 +54,23 @@ public struct NavigationContainer<Initial: View>: View {
|
|
|
55
54
|
}
|
|
56
55
|
|
|
57
56
|
public var body: some View {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
.environment(\.applicationEnvironment, applicationEnvironment)
|
|
76
|
-
// AI-GENERATED START: route Localize + font-scale injection through the observing modifiers
|
|
77
|
-
.momoLocalize(localize)
|
|
78
|
-
.momoFontScale(context: applicationEnvironment.applicationContext)
|
|
79
|
-
// AI-GENERATED END: route Localize + font-scale injection through the observing modifiers
|
|
80
|
-
}
|
|
81
|
-
.overlay {
|
|
82
|
-
if let item = navigator.overplay {
|
|
83
|
-
OverplayHost(item: item)
|
|
84
|
-
.environmentObject(navigator)
|
|
85
|
-
.environmentObject(applicationEnvironment)
|
|
86
|
-
.environment(\.applicationEnvironment, applicationEnvironment)
|
|
87
|
-
// AI-GENERATED START: route Localize + font-scale injection through the observing modifiers
|
|
88
|
-
.momoLocalize(localize)
|
|
89
|
-
.momoFontScale(context: applicationEnvironment.applicationContext)
|
|
90
|
-
// AI-GENERATED END: route Localize + font-scale injection through the observing modifiers
|
|
91
|
-
}
|
|
57
|
+
withKitEnvironment(
|
|
58
|
+
stack(path: $navigator.path, root: navigator.rootRoute)
|
|
59
|
+
.overlay { overplayLayer(presentedId: nil) }
|
|
60
|
+
)
|
|
61
|
+
.fullScreenCover(item: Binding(
|
|
62
|
+
get: { navigator.coverRoute },
|
|
63
|
+
// A close that bypasses the navigator (e.g. `dismiss()`) still tears the flow down.
|
|
64
|
+
// SwiftUI also writes nil after a navigator-driven close; by then a new screen may
|
|
65
|
+
// already be queued, so only a cover that is still showing counts.
|
|
66
|
+
set: { if $0 == nil, navigator.coverRoute != nil { navigator.dismiss() } }
|
|
67
|
+
)) { route in
|
|
68
|
+
withKitEnvironment(
|
|
69
|
+
stack(path: $navigator.presentedPath, root: DynamicScreenRoute(id: route.id))
|
|
70
|
+
.overlay { overplayLayer(presentedId: route.id) }
|
|
71
|
+
)
|
|
72
|
+
.onAppear { navigator.coverDidAppear() }
|
|
73
|
+
.onDisappear { navigator.coverDidDisappear() }
|
|
92
74
|
}
|
|
93
75
|
.onAppear {
|
|
94
76
|
if navigator.composeApi == nil {
|
|
@@ -109,10 +91,23 @@ public struct NavigationContainer<Initial: View>: View {
|
|
|
109
91
|
// AI-GENERATED END: demote the MaxApi language pull to a seed of last resort
|
|
110
92
|
}
|
|
111
93
|
.onDisappear {
|
|
94
|
+
// Our own cover hides the stack without leaving it; host back must still reach it.
|
|
95
|
+
guard navigator.presented == nil else { return }
|
|
112
96
|
NavigatorManager.shared.remove(navigator)
|
|
113
97
|
}
|
|
114
98
|
}
|
|
115
99
|
|
|
100
|
+
private func stack(path: Binding<[DynamicScreenRoute]>, root: DynamicScreenRoute) -> some View {
|
|
101
|
+
NavigationStack(path: path) {
|
|
102
|
+
screenView(for: root)
|
|
103
|
+
// A reset swaps the root in place; without the id it would inherit the old root's state.
|
|
104
|
+
.id(root.id)
|
|
105
|
+
.navigationDestination(for: DynamicScreenRoute.self) { route in
|
|
106
|
+
screenView(for: route)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
116
111
|
@ViewBuilder
|
|
117
112
|
private func screenView(for route: DynamicScreenRoute) -> some View {
|
|
118
113
|
if let screen = navigator.registry.getScreen(id: route.id) {
|
|
@@ -123,14 +118,23 @@ public struct NavigationContainer<Initial: View>: View {
|
|
|
123
118
|
}
|
|
124
119
|
}
|
|
125
120
|
|
|
121
|
+
/// Each context draws only the overplays shown over it, so one never re-mounts elsewhere.
|
|
126
122
|
@ViewBuilder
|
|
127
|
-
private func
|
|
128
|
-
if let
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
EmptyView()
|
|
123
|
+
private func overplayLayer(presentedId: Int?) -> some View {
|
|
124
|
+
if let item = navigator.overplay, item.presentedId == presentedId {
|
|
125
|
+
OverplayHost(item: item)
|
|
126
|
+
.momoScreenLayout()
|
|
132
127
|
}
|
|
133
128
|
}
|
|
129
|
+
|
|
130
|
+
private func withKitEnvironment<Content: View>(_ content: Content) -> some View {
|
|
131
|
+
content
|
|
132
|
+
.environmentObject(navigator)
|
|
133
|
+
.environmentObject(applicationEnvironment)
|
|
134
|
+
.environment(\.applicationEnvironment, applicationEnvironment)
|
|
135
|
+
.momoLocalize(localize)
|
|
136
|
+
.momoFontScale(context: applicationEnvironment.applicationContext)
|
|
137
|
+
}
|
|
134
138
|
}
|
|
135
139
|
|
|
136
140
|
// MARK: - ScreenHost
|
|
@@ -167,6 +171,8 @@ private struct ScreenHost: View {
|
|
|
167
171
|
screenName: screen.name,
|
|
168
172
|
content: { screen.content() }
|
|
169
173
|
)
|
|
174
|
+
// Per screen, not around the NavigationStack: a state-driven reader there stops path pushes rendering.
|
|
175
|
+
.momoScreenLayout()
|
|
170
176
|
}
|
|
171
177
|
}
|
|
172
178
|
|