@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
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// MoMoUIKits
|
|
4
4
|
//
|
|
5
5
|
// SwiftUI mirror of Compose's Navigator + DynamicScreenRegistry.
|
|
6
|
-
// Screens are registered by Int id; the
|
|
7
|
-
// by id and
|
|
6
|
+
// Screens are registered by Int id; the stack paths store routes
|
|
7
|
+
// by id and content is looked up from the registry on render.
|
|
8
8
|
//
|
|
9
9
|
|
|
10
10
|
import SwiftUI
|
|
@@ -83,6 +83,9 @@ public struct OverplayItem: Identifiable {
|
|
|
83
83
|
public var header: BottomSheetHeader? = nil
|
|
84
84
|
// Snack-bar payload (mirrors Compose's SnackBar value).
|
|
85
85
|
public var snackBar: SnackBar? = nil
|
|
86
|
+
/// The presented screen it was shown over, nil for the main stack. Like Compose's overplay
|
|
87
|
+
/// root id, it pins the item to that context instead of following later presents/dismisses.
|
|
88
|
+
public internal(set) var presentedId: Int? = nil
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
// MARK: - Navigator
|
|
@@ -92,11 +95,24 @@ public final class Navigator: ObservableObject {
|
|
|
92
95
|
@Published public var path: [DynamicScreenRoute] = [] {
|
|
93
96
|
// Disposal hangs off the path so every route that leaves the stack — pop,
|
|
94
97
|
// replace, reset — is released the same way, after its exit transition.
|
|
95
|
-
didSet { disposeScreens(droppedFrom: oldValue) }
|
|
98
|
+
didSet { disposeScreens(droppedFrom: oldValue, keeping: path) }
|
|
99
|
+
}
|
|
100
|
+
@Published public var presented: DynamicDialogRoute? {
|
|
101
|
+
didSet { syncCover() }
|
|
102
|
+
}
|
|
103
|
+
/// Screens pushed while a screen is presented. Compose's dialog route is a plain back-stack
|
|
104
|
+
/// entry, so later pushes stack on top of it; the cover hosts its own stack for the same result.
|
|
105
|
+
@Published public var presentedPath: [DynamicScreenRoute] = [] {
|
|
106
|
+
didSet { disposeScreens(droppedFrom: oldValue, keeping: presentedPath) }
|
|
96
107
|
}
|
|
97
|
-
@Published public var presented: DynamicDialogRoute?
|
|
98
108
|
@Published public var overplay: OverplayItem?
|
|
99
109
|
|
|
110
|
+
/// What the cover shows. It trails `presented` while the previous cover is still sliding out:
|
|
111
|
+
/// SwiftUI can drop a presentation issued during another one's dismissal.
|
|
112
|
+
@Published private(set) var coverRoute: DynamicDialogRoute?
|
|
113
|
+
private var coverOnScreen = false
|
|
114
|
+
private var presentationGeneration = 0
|
|
115
|
+
|
|
100
116
|
/// Owned per-Navigator (mirrors Compose's `remember { DynamicScreenRegistry() }` +
|
|
101
117
|
/// disposal): screens registered by this navigator are released via ARC when the
|
|
102
118
|
/// navigator itself deallocates, instead of accumulating in a process-wide singleton.
|
|
@@ -108,7 +124,7 @@ public final class Navigator: ObservableObject {
|
|
|
108
124
|
/// actually removed via `finishOverplayDismiss`.
|
|
109
125
|
private var overplayDismissHandler: (() -> Void)?
|
|
110
126
|
|
|
111
|
-
public private(set) var rootRoute: DynamicScreenRoute
|
|
127
|
+
@Published public private(set) var rootRoute: DynamicScreenRoute
|
|
112
128
|
public var composeApi: KitComposeApi?
|
|
113
129
|
|
|
114
130
|
public init(
|
|
@@ -133,7 +149,7 @@ public final class Navigator: ObservableObject {
|
|
|
133
149
|
content: { AnyView(content()) },
|
|
134
150
|
options: options
|
|
135
151
|
)
|
|
136
|
-
|
|
152
|
+
appendToActiveStack(route)
|
|
137
153
|
}
|
|
138
154
|
|
|
139
155
|
public func push<V: View>(
|
|
@@ -146,12 +162,11 @@ public final class Navigator: ObservableObject {
|
|
|
146
162
|
content: { AnyView(content()) },
|
|
147
163
|
options: options
|
|
148
164
|
)
|
|
149
|
-
|
|
165
|
+
appendToActiveStack(route)
|
|
150
166
|
}
|
|
151
167
|
|
|
152
168
|
public func replace<V: View>(@ViewBuilder _ content: @escaping () -> V, options: NavigationOptions? = nil) {
|
|
153
|
-
guard
|
|
154
|
-
path.removeLast()
|
|
169
|
+
guard removeLastFromActiveStack() else { return }
|
|
155
170
|
push(content, options: options)
|
|
156
171
|
}
|
|
157
172
|
|
|
@@ -160,13 +175,37 @@ public final class Navigator: ObservableObject {
|
|
|
160
175
|
@ViewBuilder _ content: @escaping () -> V,
|
|
161
176
|
options: NavigationOptions? = nil
|
|
162
177
|
) {
|
|
163
|
-
guard
|
|
164
|
-
path.removeLast()
|
|
178
|
+
guard removeLastFromActiveStack() else { return }
|
|
165
179
|
push(screenName: screenName, content, options: options)
|
|
166
180
|
}
|
|
167
181
|
|
|
182
|
+
/// The presented flow while one is up, the main stack otherwise.
|
|
183
|
+
private func appendToActiveStack(_ route: DynamicScreenRoute) {
|
|
184
|
+
if presented != nil {
|
|
185
|
+
presentedPath.append(route)
|
|
186
|
+
} else {
|
|
187
|
+
path.append(route)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private func removeLastFromActiveStack() -> Bool {
|
|
192
|
+
if presented != nil {
|
|
193
|
+
// Compose pops the dialog route itself, so the replacement lands on the main stack.
|
|
194
|
+
if presentedPath.isEmpty {
|
|
195
|
+
clearPresented()
|
|
196
|
+
} else {
|
|
197
|
+
presentedPath.removeLast()
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
guard !path.isEmpty else { return false }
|
|
201
|
+
path.removeLast()
|
|
202
|
+
}
|
|
203
|
+
return true
|
|
204
|
+
}
|
|
205
|
+
|
|
168
206
|
public func reset<V: View>(@ViewBuilder _ content: @escaping () -> V, options: NavigationOptions? = nil) {
|
|
169
207
|
let previousRoot = rootRoute.id
|
|
208
|
+
clearPresented()
|
|
170
209
|
path.removeAll()
|
|
171
210
|
let route = registry.register(
|
|
172
211
|
screenName: options?.screenName ?? "",
|
|
@@ -183,6 +222,7 @@ public final class Navigator: ObservableObject {
|
|
|
183
222
|
options: NavigationOptions? = nil
|
|
184
223
|
) {
|
|
185
224
|
let previousRoot = rootRoute.id
|
|
225
|
+
clearPresented()
|
|
186
226
|
path.removeAll()
|
|
187
227
|
let route = registry.register(
|
|
188
228
|
screenName: screenName,
|
|
@@ -241,9 +281,11 @@ public final class Navigator: ObservableObject {
|
|
|
241
281
|
@discardableResult
|
|
242
282
|
private func popScreen() -> Bool {
|
|
243
283
|
if presented != nil {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
284
|
+
if presentedPath.isEmpty {
|
|
285
|
+
clearPresented()
|
|
286
|
+
} else {
|
|
287
|
+
presentedPath.removeLast()
|
|
288
|
+
}
|
|
247
289
|
return true
|
|
248
290
|
} else if !path.isEmpty {
|
|
249
291
|
path.removeLast()
|
|
@@ -254,15 +296,20 @@ public final class Navigator: ObservableObject {
|
|
|
254
296
|
}
|
|
255
297
|
}
|
|
256
298
|
|
|
299
|
+
/// Empties both stacks; a presented screen stays up on its own root.
|
|
257
300
|
public func popToRoot() {
|
|
301
|
+
presentedPath.removeAll()
|
|
258
302
|
path.removeAll()
|
|
259
303
|
}
|
|
260
304
|
|
|
261
305
|
/// The entry a back action applies to — mirrors Compose's `currentScreen()`
|
|
262
|
-
/// (`navController.currentBackStackEntry`):
|
|
306
|
+
/// (`navController.currentBackStackEntry`): the presented flow sits on top of
|
|
263
307
|
/// the stack, otherwise it is the last pushed screen, otherwise the root.
|
|
264
308
|
private func currentScreen() -> DynamicScreen? {
|
|
265
|
-
|
|
309
|
+
if let presented {
|
|
310
|
+
return registry.getScreen(id: presentedPath.last?.id ?? presented.id)
|
|
311
|
+
}
|
|
312
|
+
return registry.getScreen(id: path.last?.id ?? rootRoute.id)
|
|
266
313
|
}
|
|
267
314
|
|
|
268
315
|
public func onBackSafe(callback: (() -> Void)? = nil) {
|
|
@@ -318,7 +365,7 @@ public final class Navigator: ObservableObject {
|
|
|
318
365
|
content: { AnyView(content()) },
|
|
319
366
|
options: options
|
|
320
367
|
)
|
|
321
|
-
|
|
368
|
+
presentScreen(route)
|
|
322
369
|
}
|
|
323
370
|
|
|
324
371
|
public func present<V: View>(
|
|
@@ -331,14 +378,54 @@ public final class Navigator: ObservableObject {
|
|
|
331
378
|
content: { AnyView(content()) },
|
|
332
379
|
options: options
|
|
333
380
|
)
|
|
334
|
-
|
|
381
|
+
presentScreen(route)
|
|
335
382
|
}
|
|
336
383
|
|
|
337
384
|
public func dismiss() {
|
|
338
|
-
|
|
339
|
-
|
|
385
|
+
clearPresented()
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/// One cover per container: presenting from a presented flow stacks onto that flow, the way
|
|
389
|
+
/// Compose stacks a second dialog route.
|
|
390
|
+
private func presentScreen(_ route: DynamicScreenRoute) {
|
|
391
|
+
if presented != nil {
|
|
392
|
+
presentedPath.append(route)
|
|
393
|
+
} else {
|
|
394
|
+
presentationGeneration += 1
|
|
395
|
+
presentedPath = []
|
|
396
|
+
presented = DynamicDialogRoute(id: route.id)
|
|
340
397
|
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/// The cover keeps rendering its stack while it slides out, so the flow is torn down only
|
|
401
|
+
/// once the exit animation is over.
|
|
402
|
+
private func clearPresented() {
|
|
403
|
+
guard let id = presented?.id else { return }
|
|
341
404
|
presented = nil
|
|
405
|
+
let generation = presentationGeneration
|
|
406
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + Self.presentedDisposeDelay) { [weak self] in
|
|
407
|
+
guard let self, self.presentationGeneration == generation else { return }
|
|
408
|
+
self.presentedPath.removeAll()
|
|
409
|
+
}
|
|
410
|
+
disposeScreens([id], after: Self.presentedDisposeDelay)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
private func syncCover() {
|
|
414
|
+
if presented == nil {
|
|
415
|
+
coverRoute = nil
|
|
416
|
+
} else if !coverOnScreen || coverRoute != nil {
|
|
417
|
+
coverRoute = presented
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/// Reported by the container from the cover's appear / disappear.
|
|
422
|
+
func coverDidAppear() {
|
|
423
|
+
coverOnScreen = true
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
func coverDidDisappear() {
|
|
427
|
+
coverOnScreen = false
|
|
428
|
+
syncCover()
|
|
342
429
|
}
|
|
343
430
|
|
|
344
431
|
// MARK: Overplay
|
|
@@ -353,7 +440,8 @@ public final class Navigator: ObservableObject {
|
|
|
353
440
|
type: .modal,
|
|
354
441
|
content: { AnyView(content()) },
|
|
355
442
|
barrierDismissible: barrierDismissible,
|
|
356
|
-
onDismiss: onDismiss
|
|
443
|
+
onDismiss: onDismiss,
|
|
444
|
+
presentedId: presented?.id
|
|
357
445
|
)
|
|
358
446
|
}
|
|
359
447
|
|
|
@@ -371,7 +459,8 @@ public final class Navigator: ObservableObject {
|
|
|
371
459
|
barrierDismissible: barrierDismissible,
|
|
372
460
|
onDismiss: onDismiss,
|
|
373
461
|
isSurface: isSurface,
|
|
374
|
-
header: header
|
|
462
|
+
header: header,
|
|
463
|
+
presentedId: presented?.id
|
|
375
464
|
)
|
|
376
465
|
}
|
|
377
466
|
|
|
@@ -383,7 +472,8 @@ public final class Navigator: ObservableObject {
|
|
|
383
472
|
content: { AnyView(EmptyView()) },
|
|
384
473
|
barrierDismissible: false,
|
|
385
474
|
onDismiss: onDismiss,
|
|
386
|
-
snackBar: snackBar
|
|
475
|
+
snackBar: snackBar,
|
|
476
|
+
presentedId: presented?.id
|
|
387
477
|
)
|
|
388
478
|
}
|
|
389
479
|
|
|
@@ -429,20 +519,23 @@ public final class Navigator: ObservableObject {
|
|
|
429
519
|
/// after the component has taken itself down.
|
|
430
520
|
private static let overplayDismissDelay: TimeInterval = 0.3
|
|
431
521
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
522
|
+
/// A full-screen cover slides out slower than a stack pop.
|
|
523
|
+
private static let presentedDisposeDelay: TimeInterval = 0.6
|
|
524
|
+
|
|
525
|
+
private func disposeScreens(droppedFrom previous: [DynamicScreenRoute], keeping current: [DynamicScreenRoute]) {
|
|
526
|
+
let kept = Set(current.map(\.id))
|
|
527
|
+
disposeScreens(previous.map(\.id).filter { !kept.contains($0) })
|
|
435
528
|
}
|
|
436
529
|
|
|
437
530
|
/// Screens are released only once the pop transition has played out: the outgoing
|
|
438
531
|
/// view is still on screen while it animates and the registry is what renders it —
|
|
439
532
|
/// dropping it right away blanks the screen mid-transition. Liveness is re-checked
|
|
440
533
|
/// when the timer fires, so a route pushed back before then keeps its screen.
|
|
441
|
-
private func disposeScreens(_ ids: [Int]) {
|
|
534
|
+
private func disposeScreens(_ ids: [Int], after delay: TimeInterval = Navigator.screenDisposeDelay) {
|
|
442
535
|
guard !ids.isEmpty else { return }
|
|
443
|
-
DispatchQueue.main.asyncAfter(deadline: .now() +
|
|
536
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
|
444
537
|
guard let self else { return }
|
|
445
|
-
var live = Set(self.path.map(\.id))
|
|
538
|
+
var live = Set(self.path.map(\.id)).union(self.presentedPath.map(\.id))
|
|
446
539
|
live.insert(self.rootRoute.id)
|
|
447
540
|
if let presented = self.presented { live.insert(presented.id) }
|
|
448
541
|
for id in ids where !live.contains(id) {
|
|
@@ -5,18 +5,25 @@ public final class NavigatorManager {
|
|
|
5
5
|
public static let shared = NavigatorManager()
|
|
6
6
|
private init() {}
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
// Weak: a container torn down while its cover is up never reports its disappearance.
|
|
9
|
+
private var navigators: [WeakNavigator] = []
|
|
9
10
|
|
|
10
11
|
public func push(_ navigator: Navigator) {
|
|
11
|
-
|
|
12
|
-
navigators.
|
|
12
|
+
navigators.removeAll { $0.value == nil }
|
|
13
|
+
guard !navigators.contains(where: { $0.value === navigator }) else { return }
|
|
14
|
+
navigators.append(WeakNavigator(value: navigator))
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
public func remove(_ navigator: Navigator) {
|
|
16
|
-
navigators.removeAll { $0 === navigator }
|
|
18
|
+
navigators.removeAll { $0.value == nil || $0.value === navigator }
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
public func checkCanPop() -> Bool {
|
|
21
|
+
public func checkCanPop() -> Bool { navigators.contains { $0.value != nil } }
|
|
20
22
|
|
|
21
|
-
public func pop() { navigators.last?.onBackSafe() }
|
|
23
|
+
public func pop() { navigators.last { $0.value != nil }?.value?.onBackSafe() }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@available(iOS 16.0, *)
|
|
27
|
+
private struct WeakNavigator {
|
|
28
|
+
weak var value: Navigator?
|
|
22
29
|
}
|
|
@@ -45,6 +45,7 @@ struct BottomSheetView<Content: View>: View {
|
|
|
45
45
|
@EnvironmentObject private var navigator: Navigator
|
|
46
46
|
@Environment(\.appTheme) private var theme
|
|
47
47
|
@Environment(\.applicationEnvironment) private var appEnv
|
|
48
|
+
@Environment(\.screenLayout) private var layout
|
|
48
49
|
|
|
49
50
|
/// Vertical translation of the sheet; starts off-screen and animates to 0.
|
|
50
51
|
@State private var sheetOffset: CGFloat = UIScreen.main.bounds.height
|
|
@@ -66,7 +67,36 @@ struct BottomSheetView<Content: View>: View {
|
|
|
66
67
|
/// steady-state (fully open) scrim peaks at 0.6, matching ModalScreen's peak.
|
|
67
68
|
private let maxDynamicAlpha: CGFloat = 0.3
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
// UIScreen stays on the outer display on iPhone Duo, even when unfolded
|
|
71
|
+
private var screenHeight: CGFloat { isDuo ? layout.height : UIScreen.main.bounds.height }
|
|
72
|
+
|
|
73
|
+
private var isDuo: Bool { layout.isDuoDevice }
|
|
74
|
+
// iPhone Duo inner display: center the sheet instead of spanning the width
|
|
75
|
+
private var isDuoCentered: Bool { isDuo && layout.width >= 600 }
|
|
76
|
+
|
|
77
|
+
/// Keeps the portrait width in landscape, like system sheets.
|
|
78
|
+
private var duoCenteredWidth: CGFloat {
|
|
79
|
+
min(
|
|
80
|
+
min(layout.width, layout.height) - Spacing.L * 2,
|
|
81
|
+
layout.width - max(layout.insets.leading, layout.insets.trailing, Spacing.L) * 2
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// Landscape side buttons span the full height, so the content is inset; in portrait the
|
|
86
|
+
/// side status bar only covers the top corner.
|
|
87
|
+
private var sideInsetLeading: CGFloat {
|
|
88
|
+
isDuo && !isDuoCentered && layout.width > layout.height ? layout.insets.leading : 0
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private var sideInsetTrailing: CGFloat {
|
|
92
|
+
isDuo && !isDuoCentered && layout.width > layout.height ? layout.insets.trailing : 0
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// The keyboard filler is part of the sheet, so capping the sheet shrinks the content by it.
|
|
96
|
+
private var duoMaxHeight: CGFloat? {
|
|
97
|
+
guard isDuo else { return nil }
|
|
98
|
+
return layout.height - max(layout.duoTopInset, Spacing.XL)
|
|
99
|
+
}
|
|
70
100
|
private var bottomInset: CGFloat { safeAreaBottomInset() }
|
|
71
101
|
/// While the keyboard is up, it already covers the home-indicator area, so it
|
|
72
102
|
/// replaces (rather than adds to) the usual bottom-inset filler.
|
|
@@ -110,7 +140,7 @@ struct BottomSheetView<Content: View>: View {
|
|
|
110
140
|
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillChangeFrameNotification)) { notification in
|
|
111
141
|
guard let endFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return }
|
|
112
142
|
withAnimation(.easeOut(duration: 0.25)) {
|
|
113
|
-
keyboardHeight =
|
|
143
|
+
keyboardHeight = screenHeight - endFrame.origin.y
|
|
114
144
|
}
|
|
115
145
|
}
|
|
116
146
|
.onAppear {
|
|
@@ -138,6 +168,8 @@ struct BottomSheetView<Content: View>: View {
|
|
|
138
168
|
headerView
|
|
139
169
|
.frame(height: 56)
|
|
140
170
|
.padding(.horizontal, Spacing.M)
|
|
171
|
+
.padding(.leading, sideInsetLeading)
|
|
172
|
+
.padding(.trailing, sideInsetTrailing)
|
|
141
173
|
}
|
|
142
174
|
.frame(maxWidth: .infinity)
|
|
143
175
|
.frame(height: 72)
|
|
@@ -149,12 +181,17 @@ struct BottomSheetView<Content: View>: View {
|
|
|
149
181
|
.frame(height: 1)
|
|
150
182
|
|
|
151
183
|
content()
|
|
184
|
+
.padding(.leading, sideInsetLeading)
|
|
185
|
+
.padding(.trailing, sideInsetTrailing)
|
|
186
|
+
// a tall sheet on iPhone Duo keeps its header and gives the content what is left
|
|
187
|
+
.layoutPriority(-1)
|
|
152
188
|
|
|
153
189
|
backgroundColor
|
|
154
190
|
.frame(height: effectiveBottomInset)
|
|
155
191
|
}
|
|
156
|
-
.frame(maxWidth: .infinity)
|
|
157
|
-
.frame(alignment: .bottom)
|
|
192
|
+
.frame(maxWidth: isDuoCentered ? duoCenteredWidth : .infinity)
|
|
193
|
+
.frame(maxHeight: duoMaxHeight, alignment: .bottom)
|
|
194
|
+
.clipped()
|
|
158
195
|
.background(
|
|
159
196
|
GeometryReader { geo in
|
|
160
197
|
Color.clear.onAppear { sheetHeight = geo.size.height }
|
|
@@ -16,6 +16,7 @@ struct ModalScreen<Content: View>: View {
|
|
|
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
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//
|
|
2
|
+
// DuoScrollGradient.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// Port of RN `DuoScrollGradient` (Components/DuoGlassBackground.tsx): with the iPhone Duo
|
|
6
|
+
// glass header the content scrolls under the bar, so the header gradient is drawn under it.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import SwiftUI
|
|
10
|
+
|
|
11
|
+
// extended headers run their gradient this far below the header, as off Duo
|
|
12
|
+
private let extendedGradientExtra: CGFloat = 52
|
|
13
|
+
// scroll distance over which the extended gradient collapses to the header
|
|
14
|
+
private let extendedCollapseDistance: CGFloat = 100
|
|
15
|
+
|
|
16
|
+
struct DuoScrollGradient: View {
|
|
17
|
+
let scrollOffset: CGFloat
|
|
18
|
+
let headerHeight: CGFloat
|
|
19
|
+
let extended: Bool
|
|
20
|
+
/// Quick return header: the gradient leaves with the content instead of fading.
|
|
21
|
+
var followScroll = false
|
|
22
|
+
|
|
23
|
+
@Environment(\.appTheme) private var theme
|
|
24
|
+
|
|
25
|
+
private var scroll: CGFloat { max(scrollOffset, 0) }
|
|
26
|
+
|
|
27
|
+
var body: some View {
|
|
28
|
+
Group {
|
|
29
|
+
if followScroll {
|
|
30
|
+
gradient
|
|
31
|
+
.frame(height: headerHeight + (extended ? extendedGradientExtra : 0))
|
|
32
|
+
.offset(y: -scroll)
|
|
33
|
+
} else {
|
|
34
|
+
// fades out on scroll as the extended header does off Duo; extended also collapses
|
|
35
|
+
let progress = min(scroll / extendedCollapseDistance, 1)
|
|
36
|
+
gradient
|
|
37
|
+
.frame(height: headerHeight + (extended ? extendedGradientExtra * (1 - progress) : 0))
|
|
38
|
+
.opacity(1 - min(scroll / extendedGradientExtra, 1))
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
|
42
|
+
.ignoresSafeArea()
|
|
43
|
+
.allowsHitTesting(false)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// The fade spans the gradient box.
|
|
47
|
+
private var gradient: LinearGradient {
|
|
48
|
+
LinearGradient(
|
|
49
|
+
colors: [theme.colors.gradient, theme.colors.gradient.opacity(0)],
|
|
50
|
+
startPoint: .top,
|
|
51
|
+
endPoint: .bottom
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -60,7 +60,11 @@ public struct NavigationHeader: View {
|
|
|
60
60
|
self.titleOpacity = titleOpacity
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
@Environment(\.screenLayout) private var layout
|
|
64
|
+
|
|
63
65
|
private var isAnimated: Bool { animatedHeader != nil }
|
|
66
|
+
/// iPhone Duo side bar holds back and the toolkit; a custom header right stays in the bar.
|
|
67
|
+
private var useSideBar: Bool { layout.sideBar != nil }
|
|
64
68
|
private var isSurface: Bool { animatedHeader?.isSurface ?? true }
|
|
65
69
|
|
|
66
70
|
private var headerColor: HeaderColor {
|
|
@@ -79,7 +83,7 @@ public struct NavigationHeader: View {
|
|
|
79
83
|
if headerType != .none {
|
|
80
84
|
ZStack {
|
|
81
85
|
HStack(spacing: 0) {
|
|
82
|
-
if let goBack {
|
|
86
|
+
if let goBack, !useSideBar {
|
|
83
87
|
BackButton(color: headerColor, onBack: goBack)
|
|
84
88
|
Spacer().frame(width: Spacing.S)
|
|
85
89
|
}
|
|
@@ -91,7 +95,7 @@ public struct NavigationHeader: View {
|
|
|
91
95
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
92
96
|
}
|
|
93
97
|
|
|
94
|
-
if let headerRight {
|
|
98
|
+
if let headerRight, !(useSideBar && toolkitHeaderRight(headerRight) != nil) {
|
|
95
99
|
AnyView(headerRight())
|
|
96
100
|
.background(
|
|
97
101
|
GeometryReader { geo in
|
|
@@ -107,8 +111,11 @@ public struct NavigationHeader: View {
|
|
|
107
111
|
.padding(.horizontal, Spacing.M)
|
|
108
112
|
}
|
|
109
113
|
}
|
|
110
|
-
.frame(height:
|
|
114
|
+
.frame(height: layout.headerBarHeight)
|
|
111
115
|
.overlay(alignment: .bottom) { VerticalShadow(opacity: opacity) }
|
|
116
|
+
// the row starts headerStatusBarHeight below the window top: 0 with the status bar off
|
|
117
|
+
// the top edge, lower on the iPhone Duo outer display to center on the camera
|
|
118
|
+
.padding(.top, layout.headerStatusBarHeight - layout.insets.top)
|
|
112
119
|
}
|
|
113
120
|
}
|
|
114
121
|
|
|
@@ -119,9 +126,7 @@ public struct NavigationHeader: View {
|
|
|
119
126
|
if let inputSearchProps {
|
|
120
127
|
InputSearch(props: inputSearchProps)
|
|
121
128
|
} else if case let .user(userData)? = headerTitle {
|
|
122
|
-
|
|
123
|
-
HeaderUserView(userData)
|
|
124
|
-
}
|
|
129
|
+
HeaderUserView(userData)
|
|
125
130
|
} else {
|
|
126
131
|
MomoText(
|
|
127
132
|
headerTitle?.defaultTitle ?? title,
|
|
@@ -43,10 +43,11 @@ public struct NavigationHeaderBackground: View {
|
|
|
43
43
|
)
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
@Environment(\.screenLayout) private var layout
|
|
47
|
+
|
|
46
48
|
public var body: some View {
|
|
47
|
-
let statusBarHeight = safeAreaTopInset()
|
|
48
49
|
let opacity = max(0, 1 - scrollState / CGFloat(HEADER_HEIGHT))
|
|
49
|
-
let minHeight = fullScreenContent ? 0 :
|
|
50
|
+
let minHeight = fullScreenContent ? 0 : layout.headerHeight
|
|
50
51
|
let maxHeight: CGFloat = 154
|
|
51
52
|
let backgroundColor = headerTransparent ? Color.clear : Colors.black01
|
|
52
53
|
let topColor = opacity == 0 ? backgroundColor : Color(hex: 0xFDCADE)
|
|
@@ -69,7 +69,7 @@ public struct NavigationFooter: View {
|
|
|
69
69
|
.padding(.bottom, Self.bottomPadding)
|
|
70
70
|
// Compose paints the surface all the way to the bottom of the screen; the
|
|
71
71
|
// footer's own frame stops at the safe area, so only the fill bleeds down.
|
|
72
|
-
.background(theme.colors.background.surface.ignoresSafeArea(.container, edges: .bottom))
|
|
72
|
+
.background(theme.colors.background.surface.ignoresSafeArea(.container, edges: [.bottom, .horizontal]))
|
|
73
73
|
.overlay(alignment: .top) { shadow }
|
|
74
74
|
.offset(y: Self.verticalOffset(keyboardSize: keyboardSize))
|
|
75
75
|
}
|