@momo-kits/native-kits 0.167.1-duo.18-debug → 0.167.1-duo.19
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 +5 -3
- package/ios/Application/MaxApi.swift +12 -3
- package/ios/Colors+Radius+Spacing/Radius.swift +6 -0
- package/ios/Layout/GridContext.swift +67 -3
- package/ios/Navigation/BottomSheet.swift +30 -4
- package/ios/Navigation/BottomTab/BottomTab.swift +0 -2
- package/ios/Navigation/Component/SnackBar.swift +0 -1
- package/ios/Navigation/HostBackGestureBridge.swift +27 -6
- package/ios/Navigation/ModalScreen.swift +0 -1
- package/ios/Navigation/Navigation.swift +14 -0
- package/ios/Navigation/NavigationContainer.swift +26 -56
- package/ios/Navigation/Navigator.swift +102 -165
- package/ios/Navigation/StackScreen.swift +19 -2
- package/ios/Navigation/StackTransition.swift +400 -0
- package/ios/Navigation/SwipeBackHaptic.swift +33 -0
- package/package.json +1 -1
- package/build.gradle.kts +0 -11
- package/compose/build.gradle.kts +0 -204
- package/compose/build.gradle.kts.backup +0 -204
- 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 -153
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +0 -6
- 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 -916
- 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 -81
- 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 -357
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +0 -115
- 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 -86
- 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 -478
- 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 -265
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +0 -235
- 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 -265
- 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 -361
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +0 -108
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +0 -308
- 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 -330
- 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 -207
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +0 -92
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +0 -560
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +0 -86
- 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 -501
- 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 -236
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +0 -180
- 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 -20
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +0 -18
- 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 -194
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +0 -283
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +0 -54
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +0 -50
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +0 -309
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +0 -41
- 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 -338
- 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 -137
- 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 -205
- 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 -713
- 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 -218
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +0 -231
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +0 -86
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +0 -98
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +0 -312
- 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 -447
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +0 -103
- 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 -74
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +0 -379
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +0 -39
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +0 -363
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +0 -192
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +0 -133
- 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 -74
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +0 -13
- 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 -118
- package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +0 -78
- 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 -217
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +0 -94
- 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/settings.gradle.kts +0 -64
|
@@ -99,36 +99,31 @@ public struct OverplayItem: Identifiable {
|
|
|
99
99
|
public var footer: (() -> AnyView)? = nil
|
|
100
100
|
// Snack-bar payload (mirrors Compose's SnackBar value).
|
|
101
101
|
public var snackBar: SnackBar? = nil
|
|
102
|
-
/// The
|
|
103
|
-
///
|
|
104
|
-
public internal(set) var
|
|
102
|
+
/// The screen it was shown over (Compose's overplay root id): it renders inside that screen,
|
|
103
|
+
/// so a later push slides over it instead of re-mounting it on the new top.
|
|
104
|
+
public internal(set) var screenId: Int? = nil
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// MARK: - Stack
|
|
108
|
+
|
|
109
|
+
/// One back-stack entry, root first. A presented screen is an entry like any other (Compose's
|
|
110
|
+
/// `DynamicDialogRoute` in the same NavHost): it rises instead of sliding in, and later pushes
|
|
111
|
+
/// stack on top of it.
|
|
112
|
+
struct StackEntry: Hashable, Identifiable {
|
|
113
|
+
let id: Int
|
|
114
|
+
let isDialog: Bool
|
|
105
115
|
}
|
|
106
116
|
|
|
107
117
|
// MARK: - Navigator
|
|
108
118
|
|
|
109
|
-
@available(iOS 16.0, *)
|
|
110
119
|
public final class Navigator: ObservableObject {
|
|
111
|
-
@Published
|
|
112
|
-
// Disposal hangs off the
|
|
113
|
-
//
|
|
114
|
-
didSet { disposeScreens(droppedFrom: oldValue, keeping:
|
|
115
|
-
}
|
|
116
|
-
@Published public var presented: DynamicDialogRoute? {
|
|
117
|
-
didSet { syncCover() }
|
|
118
|
-
}
|
|
119
|
-
/// Screens pushed while a screen is presented. Compose's dialog route is a plain back-stack
|
|
120
|
-
/// entry, so later pushes stack on top of it; the cover hosts its own stack for the same result.
|
|
121
|
-
@Published public var presentedPath: [DynamicScreenRoute] = [] {
|
|
122
|
-
didSet { disposeScreens(droppedFrom: oldValue, keeping: presentedPath) }
|
|
120
|
+
@Published private(set) var entries: [StackEntry] = [] {
|
|
121
|
+
// Disposal hangs off the stack so every entry that leaves it — pop, replace, reset,
|
|
122
|
+
// dismiss — is released the same way, after its exit transition.
|
|
123
|
+
didSet { disposeScreens(droppedFrom: oldValue, keeping: entries) }
|
|
123
124
|
}
|
|
124
125
|
@Published public var overplay: OverplayItem?
|
|
125
126
|
|
|
126
|
-
/// What the cover shows. It trails `presented` while the previous cover is still sliding out:
|
|
127
|
-
/// SwiftUI can drop a presentation issued during another one's dismissal.
|
|
128
|
-
@Published private(set) var coverRoute: DynamicDialogRoute?
|
|
129
|
-
private var coverOnScreen = false
|
|
130
|
-
private var presentationGeneration = 0
|
|
131
|
-
|
|
132
127
|
/// Owned per-Navigator (mirrors Compose's `remember { DynamicScreenRegistry() }` +
|
|
133
128
|
/// disposal): screens registered by this navigator are released via ARC when the
|
|
134
129
|
/// navigator itself deallocates, instead of accumulating in a process-wide singleton.
|
|
@@ -140,7 +135,7 @@ public final class Navigator: ObservableObject {
|
|
|
140
135
|
/// actually removed via `finishOverplayDismiss`.
|
|
141
136
|
private var overplayDismissHandler: (() -> Void)?
|
|
142
137
|
|
|
143
|
-
|
|
138
|
+
public var rootRoute: DynamicScreenRoute { DynamicScreenRoute(id: entries[0].id) }
|
|
144
139
|
public var composeApi: KitComposeApi?
|
|
145
140
|
/// Host dismiss fallback when there is no `composeApi` (Compose's `maxApi.dismiss`).
|
|
146
141
|
public var maxApi: MaxApi?
|
|
@@ -150,6 +145,11 @@ public final class Navigator: ObservableObject {
|
|
|
150
145
|
/// over the tabs still pops normally.
|
|
151
146
|
var tabBackHandler: (screenId: Int, handler: () -> Void)?
|
|
152
147
|
|
|
148
|
+
/// The container's stack view, which plays (and lets the host gesture scrub) the transitions.
|
|
149
|
+
weak var stackController: StackController?
|
|
150
|
+
private var isSeekingBack = false
|
|
151
|
+
private var swipeHaptic = SwipeBackHaptic()
|
|
152
|
+
|
|
153
153
|
public init(
|
|
154
154
|
initialScreenName: String = "",
|
|
155
155
|
initialContent: @escaping () -> AnyView,
|
|
@@ -157,22 +157,18 @@ public final class Navigator: ObservableObject {
|
|
|
157
157
|
composeApi: KitComposeApi? = nil
|
|
158
158
|
) {
|
|
159
159
|
self.composeApi = composeApi
|
|
160
|
-
|
|
160
|
+
let root = registry.register(
|
|
161
161
|
screenName: initialScreenName,
|
|
162
162
|
content: initialContent,
|
|
163
163
|
options: options
|
|
164
164
|
)
|
|
165
|
+
entries = [StackEntry(id: root.id, isDialog: false)]
|
|
165
166
|
}
|
|
166
167
|
|
|
167
168
|
// MARK: Stack
|
|
168
169
|
|
|
169
170
|
public func push<V: View>(@ViewBuilder _ content: @escaping () -> V, options: NavigationOptions? = nil) {
|
|
170
|
-
|
|
171
|
-
screenName: options?.screenName ?? "",
|
|
172
|
-
content: { AnyView(content()) },
|
|
173
|
-
options: options
|
|
174
|
-
)
|
|
175
|
-
appendToActiveStack(route)
|
|
171
|
+
push(screenName: options?.screenName ?? "", content, options: options)
|
|
176
172
|
}
|
|
177
173
|
|
|
178
174
|
public func push<V: View>(
|
|
@@ -180,63 +176,26 @@ public final class Navigator: ObservableObject {
|
|
|
180
176
|
@ViewBuilder _ content: @escaping () -> V,
|
|
181
177
|
options: NavigationOptions? = nil
|
|
182
178
|
) {
|
|
183
|
-
|
|
184
|
-
screenName: screenName,
|
|
185
|
-
content: { AnyView(content()) },
|
|
186
|
-
options: options
|
|
187
|
-
)
|
|
188
|
-
appendToActiveStack(route)
|
|
179
|
+
entries.append(register(screenName, content, options, isDialog: false))
|
|
189
180
|
}
|
|
190
181
|
|
|
191
182
|
public func replace<V: View>(@ViewBuilder _ content: @escaping () -> V, options: NavigationOptions? = nil) {
|
|
192
|
-
|
|
193
|
-
push(content, options: options)
|
|
183
|
+
replace(screenName: options?.screenName ?? "", content, options: options)
|
|
194
184
|
}
|
|
195
185
|
|
|
186
|
+
/// Compose pops the top entry (a presented one included) and navigates, so the replacement
|
|
187
|
+
/// always lands as a pushed screen; on the root it becomes the new root.
|
|
196
188
|
public func replace<V: View>(
|
|
197
189
|
screenName: String,
|
|
198
190
|
@ViewBuilder _ content: @escaping () -> V,
|
|
199
191
|
options: NavigationOptions? = nil
|
|
200
192
|
) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/// The presented flow while one is up, the main stack otherwise.
|
|
206
|
-
private func appendToActiveStack(_ route: DynamicScreenRoute) {
|
|
207
|
-
if presented != nil {
|
|
208
|
-
presentedPath.append(route)
|
|
209
|
-
} else {
|
|
210
|
-
path.append(route)
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
private func removeLastFromActiveStack() -> Bool {
|
|
215
|
-
if presented != nil {
|
|
216
|
-
// Compose pops the dialog route itself, so the replacement lands on the main stack.
|
|
217
|
-
if presentedPath.isEmpty {
|
|
218
|
-
clearPresented()
|
|
219
|
-
} else {
|
|
220
|
-
presentedPath.removeLast()
|
|
221
|
-
}
|
|
222
|
-
} else {
|
|
223
|
-
guard !path.isEmpty else { return false }
|
|
224
|
-
path.removeLast()
|
|
225
|
-
}
|
|
226
|
-
return true
|
|
193
|
+
let entry = register(screenName, content, options, isDialog: false)
|
|
194
|
+
entries = Array(entries.dropLast()) + [entry]
|
|
227
195
|
}
|
|
228
196
|
|
|
229
197
|
public func reset<V: View>(@ViewBuilder _ content: @escaping () -> V, options: NavigationOptions? = nil) {
|
|
230
|
-
|
|
231
|
-
clearPresented()
|
|
232
|
-
path.removeAll()
|
|
233
|
-
let route = registry.register(
|
|
234
|
-
screenName: options?.screenName ?? "",
|
|
235
|
-
content: { AnyView(content()) },
|
|
236
|
-
options: options
|
|
237
|
-
)
|
|
238
|
-
rootRoute = route
|
|
239
|
-
disposeScreens([previousRoot])
|
|
198
|
+
reset(screenName: options?.screenName ?? "", content, options: options)
|
|
240
199
|
}
|
|
241
200
|
|
|
242
201
|
public func reset<V: View>(
|
|
@@ -244,16 +203,21 @@ public final class Navigator: ObservableObject {
|
|
|
244
203
|
@ViewBuilder _ content: @escaping () -> V,
|
|
245
204
|
options: NavigationOptions? = nil
|
|
246
205
|
) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
206
|
+
entries = [register(screenName, content, options, isDialog: false)]
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private func register<V: View>(
|
|
210
|
+
_ screenName: String,
|
|
211
|
+
_ content: @escaping () -> V,
|
|
212
|
+
_ options: NavigationOptions?,
|
|
213
|
+
isDialog: Bool
|
|
214
|
+
) -> StackEntry {
|
|
250
215
|
let route = registry.register(
|
|
251
216
|
screenName: screenName,
|
|
252
217
|
content: { AnyView(content()) },
|
|
253
218
|
options: options
|
|
254
219
|
)
|
|
255
|
-
|
|
256
|
-
disposeScreens([previousRoot])
|
|
220
|
+
return StackEntry(id: route.id, isDialog: isDialog)
|
|
257
221
|
}
|
|
258
222
|
|
|
259
223
|
public func pop(_ count: Int = 1, callback: (() -> Void)? = nil) {
|
|
@@ -299,24 +263,15 @@ public final class Navigator: ObservableObject {
|
|
|
299
263
|
}
|
|
300
264
|
}
|
|
301
265
|
|
|
302
|
-
/// Pops the
|
|
303
|
-
/// a host dismiss) when there is nothing left to pop.
|
|
266
|
+
/// Pops the top entry. Returns `false` (and requests a host dismiss) on the root.
|
|
304
267
|
@discardableResult
|
|
305
268
|
private func popScreen() -> Bool {
|
|
306
|
-
|
|
307
|
-
if presentedPath.isEmpty {
|
|
308
|
-
clearPresented()
|
|
309
|
-
} else {
|
|
310
|
-
presentedPath.removeLast()
|
|
311
|
-
}
|
|
312
|
-
return true
|
|
313
|
-
} else if !path.isEmpty {
|
|
314
|
-
path.removeLast()
|
|
315
|
-
return true
|
|
316
|
-
} else {
|
|
269
|
+
guard canPop() else {
|
|
317
270
|
requestHostDismiss()
|
|
318
271
|
return false
|
|
319
272
|
}
|
|
273
|
+
entries.removeLast()
|
|
274
|
+
return true
|
|
320
275
|
}
|
|
321
276
|
|
|
322
277
|
private func requestHostDismiss() {
|
|
@@ -327,26 +282,23 @@ public final class Navigator: ObservableObject {
|
|
|
327
282
|
}
|
|
328
283
|
}
|
|
329
284
|
|
|
330
|
-
/// Whether
|
|
331
|
-
///
|
|
285
|
+
/// Whether there is a screen to go back to (Compose's `canPop`); at the root a back
|
|
286
|
+
/// dismisses the mini app instead.
|
|
332
287
|
public func canPop() -> Bool {
|
|
333
|
-
|
|
288
|
+
entries.count > 1
|
|
334
289
|
}
|
|
335
290
|
|
|
336
|
-
///
|
|
291
|
+
/// Back to the root of the active flow: the topmost presented screen while one is up,
|
|
292
|
+
/// the stack root otherwise.
|
|
337
293
|
public func popToRoot() {
|
|
338
|
-
|
|
339
|
-
|
|
294
|
+
let root = entries.lastIndex(where: \.isDialog) ?? 0
|
|
295
|
+
entries = Array(entries[...root])
|
|
340
296
|
}
|
|
341
297
|
|
|
342
|
-
/// The entry a back action applies to —
|
|
343
|
-
/// (`navController.currentBackStackEntry`)
|
|
344
|
-
/// the stack, otherwise it is the last pushed screen, otherwise the root.
|
|
298
|
+
/// The entry a back action applies to — Compose's `currentScreen()`
|
|
299
|
+
/// (`navController.currentBackStackEntry`).
|
|
345
300
|
private func currentScreen() -> DynamicScreen? {
|
|
346
|
-
|
|
347
|
-
return registry.getScreen(id: presentedPath.last?.id ?? presented.id)
|
|
348
|
-
}
|
|
349
|
-
return registry.getScreen(id: path.last?.id ?? rootRoute.id)
|
|
301
|
+
entries.last.flatMap { registry.getScreen(id: $0.id) }
|
|
350
302
|
}
|
|
351
303
|
|
|
352
304
|
public func onBackSafe(callback: (() -> Void)? = nil) {
|
|
@@ -402,12 +354,7 @@ public final class Navigator: ObservableObject {
|
|
|
402
354
|
// MARK: Modal-style screen (vertical slide)
|
|
403
355
|
|
|
404
356
|
public func present<V: View>(@ViewBuilder _ content: @escaping () -> V, options: NavigationOptions? = nil) {
|
|
405
|
-
|
|
406
|
-
screenName: options?.screenName ?? "",
|
|
407
|
-
content: { AnyView(content()) },
|
|
408
|
-
options: options
|
|
409
|
-
)
|
|
410
|
-
presentScreen(route)
|
|
357
|
+
present(screenName: options?.screenName ?? "", content, options: options)
|
|
411
358
|
}
|
|
412
359
|
|
|
413
360
|
public func present<V: View>(
|
|
@@ -415,59 +362,55 @@ public final class Navigator: ObservableObject {
|
|
|
415
362
|
@ViewBuilder _ content: @escaping () -> V,
|
|
416
363
|
options: NavigationOptions? = nil
|
|
417
364
|
) {
|
|
418
|
-
|
|
419
|
-
screenName: screenName,
|
|
420
|
-
content: { AnyView(content()) },
|
|
421
|
-
options: options
|
|
422
|
-
)
|
|
423
|
-
presentScreen(route)
|
|
365
|
+
entries.append(register(screenName, content, options, isDialog: true))
|
|
424
366
|
}
|
|
425
367
|
|
|
368
|
+
/// Closes the topmost presented screen together with everything pushed over it.
|
|
426
369
|
public func dismiss() {
|
|
427
|
-
|
|
370
|
+
guard let presented = entries.lastIndex(where: \.isDialog) else { return }
|
|
371
|
+
entries = Array(entries[..<presented])
|
|
428
372
|
}
|
|
429
373
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
374
|
+
// MARK: Host back gesture (Compose's HostBackTarget)
|
|
375
|
+
|
|
376
|
+
/// Only a plain pop is scrubbed. A back the screen handles itself — tab back, custom
|
|
377
|
+
/// handler, prevent-back popup, an open overplay — or one closing a presented screen
|
|
378
|
+
/// waits for the release and goes through `onBackSafe`, as RN turns the gesture off there.
|
|
379
|
+
private var canSeekBack: Bool {
|
|
380
|
+
guard canPop(), overplay == nil, let top = entries.last, !top.isDialog else { return false }
|
|
381
|
+
if let tabBackHandler, tabBackHandler.screenId == -1 || tabBackHandler.screenId == top.id {
|
|
382
|
+
return false
|
|
439
383
|
}
|
|
384
|
+
let options = registry.getScreen(id: top.id)?.options
|
|
385
|
+
return options?.onBackHandler == nil && options?.headerBackProps?.preventBack == nil
|
|
440
386
|
}
|
|
441
387
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
guard let id = presented?.id else { return }
|
|
446
|
-
presented = nil
|
|
447
|
-
let generation = presentationGeneration
|
|
448
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + Self.presentedDisposeDelay) { [weak self] in
|
|
449
|
-
guard let self, self.presentationGeneration == generation else { return }
|
|
450
|
-
self.presentedPath.removeAll()
|
|
451
|
-
}
|
|
452
|
-
disposeScreens([id], after: Self.presentedDisposeDelay)
|
|
388
|
+
func backStarted() {
|
|
389
|
+
swipeHaptic.reset()
|
|
390
|
+
isSeekingBack = canSeekBack && stackController?.beginBackSeek() == true
|
|
453
391
|
}
|
|
454
392
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
coverRoute = presented
|
|
460
|
-
}
|
|
393
|
+
func backProgressed(_ progress: CGFloat) {
|
|
394
|
+
guard isSeekingBack, let stackController else { return }
|
|
395
|
+
stackController.seekBack(to: progress)
|
|
396
|
+
swipeHaptic.update(travelled: progress * stackController.view.bounds.width, maxApi: maxApi)
|
|
461
397
|
}
|
|
462
398
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
399
|
+
func backCompleted() {
|
|
400
|
+
let wasSeeking = isSeekingBack
|
|
401
|
+
isSeekingBack = false
|
|
402
|
+
// A seek cut short by a programmatic change falls back to a regular back.
|
|
403
|
+
guard wasSeeking, stackController?.finishBackSeek() == true else {
|
|
404
|
+
onBackSafe()
|
|
405
|
+
return
|
|
406
|
+
}
|
|
407
|
+
entries.removeLast()
|
|
466
408
|
}
|
|
467
409
|
|
|
468
|
-
func
|
|
469
|
-
|
|
470
|
-
|
|
410
|
+
func backCancelled() {
|
|
411
|
+
guard isSeekingBack else { return }
|
|
412
|
+
isSeekingBack = false
|
|
413
|
+
stackController?.cancelBackSeek()
|
|
471
414
|
}
|
|
472
415
|
|
|
473
416
|
// MARK: Overplay
|
|
@@ -483,7 +426,7 @@ public final class Navigator: ObservableObject {
|
|
|
483
426
|
content: { AnyView(content()) },
|
|
484
427
|
barrierDismissible: barrierDismissible,
|
|
485
428
|
onDismiss: onDismiss,
|
|
486
|
-
|
|
429
|
+
screenId: entries.last?.id
|
|
487
430
|
)
|
|
488
431
|
}
|
|
489
432
|
|
|
@@ -508,7 +451,7 @@ public final class Navigator: ObservableObject {
|
|
|
508
451
|
draggable: draggable,
|
|
509
452
|
useBottomInset: useBottomInset,
|
|
510
453
|
footer: footer,
|
|
511
|
-
|
|
454
|
+
screenId: entries.last?.id
|
|
512
455
|
)
|
|
513
456
|
}
|
|
514
457
|
|
|
@@ -521,7 +464,7 @@ public final class Navigator: ObservableObject {
|
|
|
521
464
|
barrierDismissible: false,
|
|
522
465
|
onDismiss: onDismiss,
|
|
523
466
|
snackBar: snackBar,
|
|
524
|
-
|
|
467
|
+
screenId: entries.last?.id
|
|
525
468
|
)
|
|
526
469
|
}
|
|
527
470
|
|
|
@@ -575,25 +518,19 @@ public final class Navigator: ObservableObject {
|
|
|
575
518
|
/// after the component has taken itself down.
|
|
576
519
|
private static let overplayDismissDelay: TimeInterval = 0.3
|
|
577
520
|
|
|
578
|
-
|
|
579
|
-
private static let presentedDisposeDelay: TimeInterval = 0.6
|
|
580
|
-
|
|
581
|
-
private func disposeScreens(droppedFrom previous: [DynamicScreenRoute], keeping current: [DynamicScreenRoute]) {
|
|
521
|
+
private func disposeScreens(droppedFrom previous: [StackEntry], keeping current: [StackEntry]) {
|
|
582
522
|
let kept = Set(current.map(\.id))
|
|
583
523
|
disposeScreens(previous.map(\.id).filter { !kept.contains($0) })
|
|
584
524
|
}
|
|
585
525
|
|
|
586
526
|
/// Screens are released only once the pop transition has played out: the outgoing
|
|
587
|
-
/// view is still on screen while it animates
|
|
588
|
-
///
|
|
589
|
-
|
|
590
|
-
private func disposeScreens(_ ids: [Int], after delay: TimeInterval = Navigator.screenDisposeDelay) {
|
|
527
|
+
/// view is still on screen while it animates. Liveness is re-checked when the timer
|
|
528
|
+
/// fires, so an entry back on the stack by then keeps its screen.
|
|
529
|
+
private func disposeScreens(_ ids: [Int]) {
|
|
591
530
|
guard !ids.isEmpty else { return }
|
|
592
|
-
DispatchQueue.main.asyncAfter(deadline: .now() +
|
|
531
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + Self.screenDisposeDelay) { [weak self] in
|
|
593
532
|
guard let self else { return }
|
|
594
|
-
|
|
595
|
-
live.insert(self.rootRoute.id)
|
|
596
|
-
if let presented = self.presented { live.insert(presented.id) }
|
|
533
|
+
let live = Set(self.entries.map(\.id))
|
|
597
534
|
for id in ids where !live.contains(id) {
|
|
598
535
|
self.registry.unregister(id: id)
|
|
599
536
|
}
|
|
@@ -13,6 +13,9 @@ public struct StackScreen<Content: View>: View {
|
|
|
13
13
|
// MARK: - Properties
|
|
14
14
|
|
|
15
15
|
var backgroundColor: Color?
|
|
16
|
+
// AI-GENERATED START: custom screen background view (Compose backgroundComponent)
|
|
17
|
+
var background: (() -> AnyView)?
|
|
18
|
+
// AI-GENERATED END: custom screen background view (Compose backgroundComponent)
|
|
16
19
|
var headerTransparent: Bool = false
|
|
17
20
|
var fullScreenContent: Bool = false
|
|
18
21
|
var tintColor: Color? = Colors.black17
|
|
@@ -43,6 +46,9 @@ public struct StackScreen<Content: View>: View {
|
|
|
43
46
|
|
|
44
47
|
public init(
|
|
45
48
|
backgroundColor: Color? = nil,
|
|
49
|
+
// AI-GENERATED START: custom screen background view (Compose backgroundComponent)
|
|
50
|
+
background: (() -> AnyView)? = nil,
|
|
51
|
+
// AI-GENERATED END: custom screen background view (Compose backgroundComponent)
|
|
46
52
|
headerTransparent: Bool = false,
|
|
47
53
|
fullScreenContent: Bool = false,
|
|
48
54
|
tintColor: Color = Colors.black17,
|
|
@@ -72,6 +78,9 @@ public struct StackScreen<Content: View>: View {
|
|
|
72
78
|
@ViewBuilder content: @escaping () -> Content
|
|
73
79
|
) {
|
|
74
80
|
self.backgroundColor = backgroundColor
|
|
81
|
+
// AI-GENERATED START: custom screen background view (Compose backgroundComponent)
|
|
82
|
+
self.background = background
|
|
83
|
+
// AI-GENERATED END: custom screen background view (Compose backgroundComponent)
|
|
75
84
|
self.headerTransparent = headerTransparent
|
|
76
85
|
self.fullScreenContent = fullScreenContent
|
|
77
86
|
self.tintColor = tintColor
|
|
@@ -238,8 +247,16 @@ public struct StackScreen<Content: View>: View {
|
|
|
238
247
|
public var body: some View {
|
|
239
248
|
GeometryReader { _ in
|
|
240
249
|
ZStack(alignment: .top) {
|
|
241
|
-
|
|
242
|
-
|
|
250
|
+
// AI-GENERATED START: custom screen background view (Compose backgroundComponent)
|
|
251
|
+
Group {
|
|
252
|
+
if let background {
|
|
253
|
+
background()
|
|
254
|
+
} else {
|
|
255
|
+
backgroundColor ?? theme.colors.background.default
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
.edgesIgnoringSafeArea(.all)
|
|
259
|
+
// AI-GENERATED END: custom screen background view (Compose backgroundComponent)
|
|
243
260
|
|
|
244
261
|
VStack(spacing: 0) {
|
|
245
262
|
ZStack(alignment: fabProps?.position == .center ? .bottom : .bottomTrailing) {
|