@momo-kits/native-kits 0.300.3 → 0.300.4-debug
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +51 -7
- package/README.md +2 -2
- package/build.gradle.kts +13 -0
- package/compose/build.gradle.kts +197 -0
- package/compose/build.gradle.kts.backup +197 -0
- package/compose/compose.podspec +47 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +20 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +6 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +12 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +7 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +153 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +6 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +110 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/DesignSystemKits.kt +62 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +1071 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +269 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +65 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +157 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +81 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +32 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +338 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +175 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +357 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +115 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +89 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +128 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +209 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +542 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +49 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +86 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +125 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +231 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +189 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +478 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +114 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +265 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +235 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +223 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +265 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +185 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +343 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +108 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +56 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +41 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +84 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +40 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +361 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +108 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +308 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +59 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +87 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +21 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +90 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +330 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +220 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +473 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +122 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +207 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +92 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +560 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +86 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +125 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +207 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +501 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +176 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +182 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +193 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +236 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +180 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +325 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +17 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +16 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +194 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +287 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +54 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +50 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +306 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +41 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +52 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +66 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +104 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +48 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +338 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +58 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +137 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +116 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +215 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +404 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +8 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +732 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +148 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +40 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +233 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +217 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +97 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +377 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +169 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +455 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +103 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +106 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +74 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +413 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +39 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +363 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HelpCenterRequest.kt +37 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +192 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +133 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/ToolkitRequests.kt +41 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +135 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +12 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +91 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +9 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +74 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +20 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +55 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +15 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +118 -0
- package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/HelpCenterRequestTest.kt +44 -0
- package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/ToolkitRequestsTest.kt +62 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +42 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +45 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +36 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +38 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +66 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +217 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +138 -0
- package/gradle/libs.versions.toml +68 -0
- package/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/gradle/wrapper/gradle-wrapper.properties +8 -0
- package/gradle.properties +26 -0
- package/gradlew +252 -0
- package/gradlew.bat +94 -0
- package/ios/Application/ApplicationEnvironment.swift +24 -6
- package/ios/Application/DesignSystemKits.swift +100 -0
- package/ios/Application/DesignSystemKitsPayload.swift +68 -0
- package/ios/Application/MaxApi.swift +30 -3
- package/ios/Avatar/Avatar.swift +4 -3
- package/ios/Badge/Badge.swift +6 -4
- package/ios/Badge/BadgeDot.swift +1 -1
- package/ios/Badge/BadgeRibbon.swift +8 -2
- package/ios/Button/Button.swift +15 -12
- package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +64 -18
- package/ios/Carousel/Carousel.swift +29 -12
- package/ios/Checkbox/Checkbox.swift +7 -3
- package/ios/Chip/Chip.swift +5 -4
- package/ios/Collapse/Collapse.swift +70 -19
- package/ios/Colors+Radius+Spacing/Colors.swift +10 -1
- package/ios/Colors+Radius+Spacing/Radius.swift +6 -0
- package/ios/DateTimePicker/DateTimePicker.swift +54 -20
- package/ios/DateTimePicker/WheelPicker.swift +103 -105
- package/ios/Divider/Divider.swift +37 -4
- package/ios/Extensions/Color++.swift +38 -9
- package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
- package/ios/Extensions/SkeletonUICompat.swift +11 -0
- package/ios/Icon/Icon.swift +21 -23
- package/ios/Icon/Icons.swift +1329 -0
- package/ios/IconButton/IconButton.swift +15 -7
- package/ios/Image/Image.swift +7 -23
- package/ios/Information/Information.swift +27 -9
- package/ios/Input/ErrorView.swift +2 -2
- package/ios/Input/Input.swift +171 -148
- package/ios/Input/InputPhoneNumber.swift +10 -9
- package/ios/Input/InputSearch.swift +107 -78
- package/ios/Input/InputTextArea.swift +133 -148
- package/ios/InputDropDown/InputDropDown.swift +10 -7
- package/ios/InputMoney/InputMoney.swift +32 -11
- package/ios/InputOTP/InputOTP.swift +22 -12
- package/ios/Layout/Card.swift +50 -0
- package/ios/Layout/GridContext.swift +187 -3
- package/ios/Layout/Item.swift +5 -4
- package/ios/Layout/ScreenLayout.swift +415 -0
- package/ios/Layout/Section.swift +4 -100
- package/ios/Loader/Loader.swift +5 -9
- package/ios/Lottie/LottieView.swift +14 -2
- package/ios/{Application/Navigation/Overplay → Navigation}/BottomSheet.swift +169 -29
- package/ios/Navigation/BottomTab/BottomTab.swift +332 -0
- package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +18 -7
- package/ios/Navigation/BottomTab/DuoBarCompression.swift +104 -0
- package/ios/Navigation/BottomTab/DuoTabBar.swift +329 -0
- package/ios/{Application → Navigation/Component}/FloatingButton.swift +43 -54
- package/ios/Navigation/Component/Header.swift +545 -0
- package/ios/Navigation/Component/HeaderBackground.swift +154 -0
- package/ios/Navigation/Component/HeaderJourney.swift +73 -0
- package/ios/Navigation/Component/HeaderLocation.swift +48 -0
- package/ios/Navigation/Component/HeaderRight.swift +425 -0
- package/ios/Navigation/Component/HeaderTitle.swift +36 -0
- package/ios/{Application/Navigation → Navigation/Component}/HeaderUser.swift +21 -24
- package/ios/Navigation/Component/SideBar.swift +183 -0
- package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +8 -8
- package/ios/Navigation/HostBackGestureBridge.swift +78 -0
- package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +9 -2
- package/ios/Navigation/Navigation.swift +204 -0
- package/ios/Navigation/NavigationContainer.swift +195 -0
- package/ios/{Application/Navigation → Navigation}/Navigator.swift +207 -107
- package/ios/Navigation/StackScreen.swift +930 -0
- package/ios/Navigation/StackTransition.swift +400 -0
- package/ios/Navigation/SwipeBackHaptic.swift +33 -0
- package/ios/{Application/Navigation → Navigation}/Tracking/ScreenTracker.swift +12 -5
- package/ios/OTPKeyboard/OTPKeyboard.swift +8 -11
- package/ios/Pagination/PaginationScroll.swift +2 -2
- package/ios/Popup/PopupDisplay.swift +72 -135
- package/ios/Popup/PopupInput.swift +10 -13
- package/ios/Popup/PopupLayout.swift +138 -0
- package/ios/Popup/PopupNotify.swift +59 -69
- package/ios/Popup/PopupPromotion.swift +31 -47
- package/ios/PopupView/FullscreenPopup.swift +13 -19
- package/ios/PopupView/PopupView.swift +1 -1
- package/ios/PopupView/Utils++.swift +0 -8
- package/ios/ProgressInfo/ProgressInfo.swift +4 -2
- package/ios/Radio/Radio.swift +2 -1
- package/ios/Rating/Rating.swift +9 -1
- package/ios/Resources/dot_loading.json +558 -0
- package/ios/Resources/lottie_circle_loader.json +1 -0
- package/ios/ScrollIndicator/ScrollIndicator.swift +11 -10
- package/ios/Skeleton/Skeleton.swift +22 -27
- package/ios/Slider/Slider.swift +16 -18
- package/ios/Stepper/Stepper.swift +7 -3
- package/ios/Steps/Steps.swift +8 -6
- package/ios/SuggestAction/SuggestAction.swift +5 -0
- package/ios/Swipeable/SwipeCell.swift +10 -6
- package/ios/Switch/Switch.swift +22 -20
- package/ios/TabView/TabView.swift +102 -21
- package/ios/Tag/Tag.swift +15 -4
- package/ios/Template/Logo/Logo.swift +2 -1
- package/ios/Template/TrustBanner/TrustBanner.swift +109 -55
- package/ios/Title/Title.swift +6 -17
- package/ios/Tooltip/Tooltip.swift +29 -14
- package/ios/Typography/FontScale.swift +17 -14
- package/ios/Typography/Text.swift +156 -20
- package/ios/Typography/Typography.swift +2 -2
- package/ios/Uploader/Uploader.swift +3 -1
- package/ios/native-kits.podspec +2 -3
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -3
- package/ios-demo/README.md +120 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +1 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +80 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +2 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +1 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +2 -2
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +2 -0
- package/package.json +1 -1
- package/publish.sh +19 -2
- package/scripts/gen-ios-icons.mjs +36 -0
- package/settings.gradle.kts +64 -0
- package/ios/Application/Components.swift +0 -270
- package/ios/Application/ComposeApi.swift +0 -22
- package/ios/Application/HeaderRight.swift +0 -306
- package/ios/Application/Navigation/BottomTab/BottomTab.swift +0 -104
- package/ios/Application/Navigation/HeaderBackProps.swift +0 -66
- package/ios/Application/Navigation/HeaderTitle.swift +0 -57
- package/ios/Application/Navigation/NavigationContainer.swift +0 -213
- package/ios/Application/Navigation/NavigationOptions.swift +0 -109
- package/ios/Application/Navigation/NavigatorManager.swift +0 -22
- package/ios/Application/Navigation/component/Header.swift +0 -178
- package/ios/Application/Navigation/component/HeaderBackground.swift +0 -83
- package/ios/Application/Navigation/component/HeaderColor.swift +0 -65
- package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +0 -11
- package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
- package/ios/Application/Navigation/component/NavigationHeaderRight.swift +0 -196
- package/ios/Application/Screen.swift +0 -250
- package/ios/Application/StackScreen.swift +0 -492
- package/ios/Typography/FontScaleStore.swift +0 -58
- package/ios/Typography/KitDesignSystem.swift +0 -17
- /package/ios/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// Navigator.swift
|
|
3
3
|
// MoMoUIKits
|
|
4
4
|
//
|
|
5
|
-
// SwiftUI mirror of Compose's Navigator
|
|
6
|
-
// Screens are registered by Int id; the
|
|
7
|
-
//
|
|
5
|
+
// SwiftUI mirror of Compose's `navigation/Navigator.kt`: Navigator, DynamicScreenRegistry and
|
|
6
|
+
// the overplay model. Screens are registered by Int id; the stack paths store routes by id and
|
|
7
|
+
// content is looked up from the registry on render.
|
|
8
8
|
//
|
|
9
9
|
|
|
10
10
|
import SwiftUI
|
|
@@ -31,6 +31,7 @@ public struct DynamicScreen {
|
|
|
31
31
|
|
|
32
32
|
public final class DynamicScreenRegistry {
|
|
33
33
|
private var screens: [Int: DynamicScreen] = [:]
|
|
34
|
+
private var navigations: [Int: WeakNavigation] = [:]
|
|
34
35
|
private var idCounter: Int = 1
|
|
35
36
|
|
|
36
37
|
public init() {}
|
|
@@ -49,12 +50,20 @@ public final class DynamicScreenRegistry {
|
|
|
49
50
|
|
|
50
51
|
public func unregister(id: Int) {
|
|
51
52
|
screens.removeValue(forKey: id)
|
|
53
|
+
navigations.removeValue(forKey: id)
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
public func unregisterAll() {
|
|
55
57
|
screens.removeAll()
|
|
58
|
+
navigations.removeAll()
|
|
56
59
|
}
|
|
57
60
|
|
|
61
|
+
func bind(_ navigation: Navigation) {
|
|
62
|
+
navigations[navigation.id] = WeakNavigation(value: navigation)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
func navigation(id: Int) -> Navigation? { navigations[id]?.value }
|
|
66
|
+
|
|
58
67
|
public func getScreen(id: Int) -> DynamicScreen? { screens[id] }
|
|
59
68
|
|
|
60
69
|
public func latest() -> DynamicScreen? {
|
|
@@ -66,6 +75,10 @@ public final class DynamicScreenRegistry {
|
|
|
66
75
|
}
|
|
67
76
|
}
|
|
68
77
|
|
|
78
|
+
private struct WeakNavigation {
|
|
79
|
+
weak var value: Navigation?
|
|
80
|
+
}
|
|
81
|
+
|
|
69
82
|
// MARK: - Overplay
|
|
70
83
|
|
|
71
84
|
public enum OverplayType {
|
|
@@ -81,20 +94,34 @@ public struct OverplayItem: Identifiable {
|
|
|
81
94
|
// Bottom-sheet params (mirrors Compose's OverplayComponentParams.BottomSheet).
|
|
82
95
|
public var isSurface: Bool = false
|
|
83
96
|
public var header: BottomSheetHeader? = nil
|
|
97
|
+
public var draggable: Bool = true
|
|
98
|
+
public var useBottomInset: Bool = true
|
|
99
|
+
public var footer: (() -> AnyView)? = nil
|
|
84
100
|
// Snack-bar payload (mirrors Compose's SnackBar value).
|
|
85
101
|
public var snackBar: SnackBar? = nil
|
|
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
|
|
86
115
|
}
|
|
87
116
|
|
|
88
117
|
// MARK: - Navigator
|
|
89
118
|
|
|
90
|
-
@available(iOS 16.0, *)
|
|
91
119
|
public final class Navigator: ObservableObject {
|
|
92
|
-
@Published
|
|
93
|
-
// Disposal hangs off the
|
|
94
|
-
//
|
|
95
|
-
didSet { disposeScreens(droppedFrom: oldValue) }
|
|
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) }
|
|
96
124
|
}
|
|
97
|
-
@Published public var presented: DynamicDialogRoute?
|
|
98
125
|
@Published public var overplay: OverplayItem?
|
|
99
126
|
|
|
100
127
|
/// Owned per-Navigator (mirrors Compose's `remember { DynamicScreenRegistry() }` +
|
|
@@ -108,8 +135,20 @@ public final class Navigator: ObservableObject {
|
|
|
108
135
|
/// actually removed via `finishOverplayDismiss`.
|
|
109
136
|
private var overplayDismissHandler: (() -> Void)?
|
|
110
137
|
|
|
111
|
-
public
|
|
138
|
+
public var rootRoute: DynamicScreenRoute { DynamicScreenRoute(id: entries[0].id) }
|
|
112
139
|
public var composeApi: KitComposeApi?
|
|
140
|
+
/// Host dismiss fallback when there is no `composeApi` (Compose's `maxApi.dismiss`).
|
|
141
|
+
public var maxApi: MaxApi?
|
|
142
|
+
|
|
143
|
+
/// Set by a bottom tab off its first tab: back on the screen hosting the tabs returns to the
|
|
144
|
+
/// first tab (Compose `tabBackHandler`). Keyed by that screen, so a screen pushed or presented
|
|
145
|
+
/// over the tabs still pops normally.
|
|
146
|
+
var tabBackHandler: (screenId: Int, handler: () -> Void)?
|
|
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()
|
|
113
152
|
|
|
114
153
|
public init(
|
|
115
154
|
initialScreenName: String = "",
|
|
@@ -118,22 +157,18 @@ public final class Navigator: ObservableObject {
|
|
|
118
157
|
composeApi: KitComposeApi? = nil
|
|
119
158
|
) {
|
|
120
159
|
self.composeApi = composeApi
|
|
121
|
-
|
|
160
|
+
let root = registry.register(
|
|
122
161
|
screenName: initialScreenName,
|
|
123
162
|
content: initialContent,
|
|
124
163
|
options: options
|
|
125
164
|
)
|
|
165
|
+
entries = [StackEntry(id: root.id, isDialog: false)]
|
|
126
166
|
}
|
|
127
167
|
|
|
128
168
|
// MARK: Stack
|
|
129
169
|
|
|
130
170
|
public func push<V: View>(@ViewBuilder _ content: @escaping () -> V, options: NavigationOptions? = nil) {
|
|
131
|
-
|
|
132
|
-
screenName: options?.screenName ?? "",
|
|
133
|
-
content: { AnyView(content()) },
|
|
134
|
-
options: options
|
|
135
|
-
)
|
|
136
|
-
path.append(route)
|
|
171
|
+
push(screenName: options?.screenName ?? "", content, options: options)
|
|
137
172
|
}
|
|
138
173
|
|
|
139
174
|
public func push<V: View>(
|
|
@@ -141,40 +176,26 @@ public final class Navigator: ObservableObject {
|
|
|
141
176
|
@ViewBuilder _ content: @escaping () -> V,
|
|
142
177
|
options: NavigationOptions? = nil
|
|
143
178
|
) {
|
|
144
|
-
|
|
145
|
-
screenName: screenName,
|
|
146
|
-
content: { AnyView(content()) },
|
|
147
|
-
options: options
|
|
148
|
-
)
|
|
149
|
-
path.append(route)
|
|
179
|
+
entries.append(register(screenName, content, options, isDialog: false))
|
|
150
180
|
}
|
|
151
181
|
|
|
152
182
|
public func replace<V: View>(@ViewBuilder _ content: @escaping () -> V, options: NavigationOptions? = nil) {
|
|
153
|
-
|
|
154
|
-
path.removeLast()
|
|
155
|
-
push(content, options: options)
|
|
183
|
+
replace(screenName: options?.screenName ?? "", content, options: options)
|
|
156
184
|
}
|
|
157
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.
|
|
158
188
|
public func replace<V: View>(
|
|
159
189
|
screenName: String,
|
|
160
190
|
@ViewBuilder _ content: @escaping () -> V,
|
|
161
191
|
options: NavigationOptions? = nil
|
|
162
192
|
) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
push(screenName: screenName, content, options: options)
|
|
193
|
+
let entry = register(screenName, content, options, isDialog: false)
|
|
194
|
+
entries = Array(entries.dropLast()) + [entry]
|
|
166
195
|
}
|
|
167
196
|
|
|
168
197
|
public func reset<V: View>(@ViewBuilder _ content: @escaping () -> V, options: NavigationOptions? = nil) {
|
|
169
|
-
|
|
170
|
-
path.removeAll()
|
|
171
|
-
let route = registry.register(
|
|
172
|
-
screenName: options?.screenName ?? "",
|
|
173
|
-
content: { AnyView(content()) },
|
|
174
|
-
options: options
|
|
175
|
-
)
|
|
176
|
-
rootRoute = route
|
|
177
|
-
disposeScreens([previousRoot])
|
|
198
|
+
reset(screenName: options?.screenName ?? "", content, options: options)
|
|
178
199
|
}
|
|
179
200
|
|
|
180
201
|
public func reset<V: View>(
|
|
@@ -182,15 +203,21 @@ public final class Navigator: ObservableObject {
|
|
|
182
203
|
@ViewBuilder _ content: @escaping () -> V,
|
|
183
204
|
options: NavigationOptions? = nil
|
|
184
205
|
) {
|
|
185
|
-
|
|
186
|
-
|
|
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 {
|
|
187
215
|
let route = registry.register(
|
|
188
216
|
screenName: screenName,
|
|
189
217
|
content: { AnyView(content()) },
|
|
190
218
|
options: options
|
|
191
219
|
)
|
|
192
|
-
|
|
193
|
-
disposeScreens([previousRoot])
|
|
220
|
+
return StackEntry(id: route.id, isDialog: isDialog)
|
|
194
221
|
}
|
|
195
222
|
|
|
196
223
|
public func pop(_ count: Int = 1, callback: (() -> Void)? = nil) {
|
|
@@ -211,20 +238,19 @@ public final class Navigator: ObservableObject {
|
|
|
211
238
|
// Mirrors Kotlin: a snackbar doesn't block back-navigation, so popping
|
|
212
239
|
// while one is showing pops the screen underneath and clears the
|
|
213
240
|
// snackbar immediately, without playing its dismiss animation.
|
|
214
|
-
|
|
215
|
-
overplay = nil
|
|
241
|
+
replaceOverplay(with: nil)
|
|
216
242
|
guard popScreen() else {
|
|
217
243
|
callback?()
|
|
218
244
|
return
|
|
219
245
|
}
|
|
220
246
|
popStep(remaining: remaining - 1, callback: callback)
|
|
221
|
-
} else if
|
|
247
|
+
} else if let closing = overplay?.id {
|
|
222
248
|
hideOverplay()
|
|
223
249
|
DispatchQueue.main.asyncAfter(deadline: .now() + Self.overplayDismissDelay) { [weak self] in
|
|
224
250
|
guard let self else { return }
|
|
225
251
|
// The component reports back through `finishOverplayDismiss` once its exit
|
|
226
252
|
// animation ends; clear it here if it never did, so the step is not wasted.
|
|
227
|
-
|
|
253
|
+
self.finishOverplayDismiss(id: closing)
|
|
228
254
|
self.popStep(remaining: remaining - 1, callback: callback)
|
|
229
255
|
}
|
|
230
256
|
} else {
|
|
@@ -236,43 +262,62 @@ public final class Navigator: ObservableObject {
|
|
|
236
262
|
}
|
|
237
263
|
}
|
|
238
264
|
|
|
239
|
-
/// Pops the
|
|
240
|
-
/// a host dismiss) when there is nothing left to pop.
|
|
265
|
+
/// Pops the top entry. Returns `false` (and requests a host dismiss) on the root.
|
|
241
266
|
@discardableResult
|
|
242
267
|
private func popScreen() -> Bool {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
presented = nil
|
|
246
|
-
if let id = dismissedId { disposeScreens([id]) }
|
|
247
|
-
return true
|
|
248
|
-
} else if !path.isEmpty {
|
|
249
|
-
path.removeLast()
|
|
250
|
-
return true
|
|
251
|
-
} else {
|
|
252
|
-
composeApi?.requestCallback(funcName: "dismiss", params: nil, onResponse: nil)
|
|
268
|
+
guard canPop() else {
|
|
269
|
+
requestHostDismiss()
|
|
253
270
|
return false
|
|
254
271
|
}
|
|
272
|
+
entries.removeLast()
|
|
273
|
+
return true
|
|
255
274
|
}
|
|
256
275
|
|
|
276
|
+
private func requestHostDismiss() {
|
|
277
|
+
if let composeApi {
|
|
278
|
+
composeApi.requestCallback(funcName: "dismiss", params: nil, onResponse: nil)
|
|
279
|
+
} else {
|
|
280
|
+
maxApi?.dismiss(completion: nil)
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/// Whether there is a screen to go back to (Compose's `canPop`); at the root a back
|
|
285
|
+
/// dismisses the mini app instead.
|
|
286
|
+
public func canPop() -> Bool {
|
|
287
|
+
entries.count > 1
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/// Back to the root of the active flow: the topmost presented screen while one is up,
|
|
291
|
+
/// the stack root otherwise.
|
|
257
292
|
public func popToRoot() {
|
|
258
|
-
|
|
293
|
+
let root = entries.lastIndex(where: \.isDialog) ?? 0
|
|
294
|
+
entries = Array(entries[...root])
|
|
259
295
|
}
|
|
260
296
|
|
|
261
|
-
/// The entry a back action applies to —
|
|
262
|
-
/// (`navController.currentBackStackEntry`)
|
|
263
|
-
/// the stack, otherwise it is the last pushed screen, otherwise the root.
|
|
297
|
+
/// The entry a back action applies to — Compose's `currentScreen()`
|
|
298
|
+
/// (`navController.currentBackStackEntry`).
|
|
264
299
|
private func currentScreen() -> DynamicScreen? {
|
|
265
|
-
registry.getScreen(id:
|
|
300
|
+
entries.last.flatMap { registry.getScreen(id: $0.id) }
|
|
266
301
|
}
|
|
267
302
|
|
|
268
303
|
public func onBackSafe(callback: (() -> Void)? = nil) {
|
|
304
|
+
// An open modal or bottom sheet takes the back first (their BackHandler in Compose).
|
|
305
|
+
if let overplay, overplay.type != .snackBar {
|
|
306
|
+
pop(1, callback: callback)
|
|
307
|
+
return
|
|
308
|
+
}
|
|
269
309
|
let latest = currentScreen()
|
|
310
|
+
if let tabBackHandler, tabBackHandler.screenId == -1 || tabBackHandler.screenId == latest?.id {
|
|
311
|
+
tabBackHandler.handler()
|
|
312
|
+
return
|
|
313
|
+
}
|
|
270
314
|
if let onBack = latest?.options?.onBackHandler {
|
|
271
315
|
onBack()
|
|
272
316
|
return
|
|
273
317
|
}
|
|
274
318
|
if let preventBack = latest?.options?.headerBackProps?.preventBack {
|
|
275
|
-
|
|
319
|
+
// stored in `overplay`, so a strong self would cycle until the popup closes
|
|
320
|
+
showModal { [weak self] in
|
|
276
321
|
PopupNotify(
|
|
277
322
|
props: PopupNotifyProps(
|
|
278
323
|
image: preventBack.image,
|
|
@@ -281,7 +326,7 @@ public final class Navigator: ObservableObject {
|
|
|
281
326
|
error: preventBack.error,
|
|
282
327
|
primary: PopupAction(
|
|
283
328
|
title: preventBack.primary?.title ?? "",
|
|
284
|
-
onPress: {
|
|
329
|
+
onPress: {
|
|
285
330
|
preventBack.primary?.onPress?()
|
|
286
331
|
// Close the popup, then leave the screen (matches Compose's pop(2)).
|
|
287
332
|
self?.pop(2, callback: callback)
|
|
@@ -290,14 +335,14 @@ public final class Navigator: ObservableObject {
|
|
|
290
335
|
secondary: preventBack.secondary.map { secondary in
|
|
291
336
|
PopupAction(
|
|
292
337
|
title: secondary.title,
|
|
293
|
-
onPress: {
|
|
338
|
+
onPress: {
|
|
294
339
|
secondary.onPress?()
|
|
295
340
|
self?.hideOverplay()
|
|
296
341
|
}
|
|
297
342
|
)
|
|
298
343
|
},
|
|
299
344
|
buttonDirection: preventBack.buttonDirection,
|
|
300
|
-
onIconClose: {
|
|
345
|
+
onIconClose: {
|
|
301
346
|
preventBack.onIconClose?()
|
|
302
347
|
self?.hideOverplay()
|
|
303
348
|
},
|
|
@@ -313,12 +358,7 @@ public final class Navigator: ObservableObject {
|
|
|
313
358
|
// MARK: Modal-style screen (vertical slide)
|
|
314
359
|
|
|
315
360
|
public func present<V: View>(@ViewBuilder _ content: @escaping () -> V, options: NavigationOptions? = nil) {
|
|
316
|
-
|
|
317
|
-
screenName: options?.screenName ?? "",
|
|
318
|
-
content: { AnyView(content()) },
|
|
319
|
-
options: options
|
|
320
|
-
)
|
|
321
|
-
presented = DynamicDialogRoute(id: route.id)
|
|
361
|
+
present(screenName: options?.screenName ?? "", content, options: options)
|
|
322
362
|
}
|
|
323
363
|
|
|
324
364
|
public func present<V: View>(
|
|
@@ -326,19 +366,55 @@ public final class Navigator: ObservableObject {
|
|
|
326
366
|
@ViewBuilder _ content: @escaping () -> V,
|
|
327
367
|
options: NavigationOptions? = nil
|
|
328
368
|
) {
|
|
329
|
-
|
|
330
|
-
screenName: screenName,
|
|
331
|
-
content: { AnyView(content()) },
|
|
332
|
-
options: options
|
|
333
|
-
)
|
|
334
|
-
presented = DynamicDialogRoute(id: route.id)
|
|
369
|
+
entries.append(register(screenName, content, options, isDialog: true))
|
|
335
370
|
}
|
|
336
371
|
|
|
372
|
+
/// Closes the topmost presented screen together with everything pushed over it.
|
|
337
373
|
public func dismiss() {
|
|
338
|
-
|
|
339
|
-
|
|
374
|
+
guard let presented = entries.lastIndex(where: \.isDialog) else { return }
|
|
375
|
+
entries = Array(entries[..<presented])
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// MARK: Host back gesture (Compose's HostBackTarget)
|
|
379
|
+
|
|
380
|
+
/// Only a plain pop is scrubbed. A back the screen handles itself — tab back, custom
|
|
381
|
+
/// handler, prevent-back popup, an open overplay — or one closing a presented screen
|
|
382
|
+
/// waits for the release and goes through `onBackSafe`, as RN turns the gesture off there.
|
|
383
|
+
private var canSeekBack: Bool {
|
|
384
|
+
guard canPop(), overplay == nil, let top = entries.last, !top.isDialog else { return false }
|
|
385
|
+
if let tabBackHandler, tabBackHandler.screenId == -1 || tabBackHandler.screenId == top.id {
|
|
386
|
+
return false
|
|
340
387
|
}
|
|
341
|
-
|
|
388
|
+
let options = registry.getScreen(id: top.id)?.options
|
|
389
|
+
return options?.onBackHandler == nil && options?.headerBackProps?.preventBack == nil
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
func backStarted() {
|
|
393
|
+
swipeHaptic.reset()
|
|
394
|
+
isSeekingBack = canSeekBack && stackController?.beginBackSeek() == true
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
func backProgressed(_ progress: CGFloat) {
|
|
398
|
+
guard isSeekingBack, let stackController else { return }
|
|
399
|
+
stackController.seekBack(to: progress)
|
|
400
|
+
swipeHaptic.update(travelled: progress * stackController.view.bounds.width, maxApi: maxApi)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
func backCompleted() {
|
|
404
|
+
let wasSeeking = isSeekingBack
|
|
405
|
+
isSeekingBack = false
|
|
406
|
+
// A seek cut short by a programmatic change falls back to a regular back.
|
|
407
|
+
guard wasSeeking, stackController?.finishBackSeek() == true else {
|
|
408
|
+
onBackSafe()
|
|
409
|
+
return
|
|
410
|
+
}
|
|
411
|
+
entries.removeLast()
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
func backCancelled() {
|
|
415
|
+
guard isSeekingBack else { return }
|
|
416
|
+
isSeekingBack = false
|
|
417
|
+
stackController?.cancelBackSeek()
|
|
342
418
|
}
|
|
343
419
|
|
|
344
420
|
// MARK: Overplay
|
|
@@ -348,13 +424,13 @@ public final class Navigator: ObservableObject {
|
|
|
348
424
|
barrierDismissible: Bool = true,
|
|
349
425
|
onDismiss: (() -> Void)? = nil
|
|
350
426
|
) {
|
|
351
|
-
|
|
352
|
-
overplay = OverplayItem(
|
|
427
|
+
replaceOverplay(with: OverplayItem(
|
|
353
428
|
type: .modal,
|
|
354
429
|
content: { AnyView(content()) },
|
|
355
430
|
barrierDismissible: barrierDismissible,
|
|
356
|
-
onDismiss: onDismiss
|
|
357
|
-
|
|
431
|
+
onDismiss: onDismiss,
|
|
432
|
+
screenId: entries.last?.id
|
|
433
|
+
))
|
|
358
434
|
}
|
|
359
435
|
|
|
360
436
|
public func showBottomSheet<V: View>(
|
|
@@ -362,29 +438,35 @@ public final class Navigator: ObservableObject {
|
|
|
362
438
|
isSurface: Bool = false,
|
|
363
439
|
barrierDismissible: Bool = true,
|
|
364
440
|
header: BottomSheetHeader? = nil,
|
|
365
|
-
onDismiss: (() -> Void)? = nil
|
|
441
|
+
onDismiss: (() -> Void)? = nil,
|
|
442
|
+
draggable: Bool = true,
|
|
443
|
+
useBottomInset: Bool = true,
|
|
444
|
+
footer: (() -> AnyView)? = nil
|
|
366
445
|
) {
|
|
367
|
-
|
|
368
|
-
overplay = OverplayItem(
|
|
446
|
+
replaceOverplay(with: OverplayItem(
|
|
369
447
|
type: .bottomSheet,
|
|
370
448
|
content: { AnyView(content()) },
|
|
371
449
|
barrierDismissible: barrierDismissible,
|
|
372
450
|
onDismiss: onDismiss,
|
|
373
451
|
isSurface: isSurface,
|
|
374
|
-
header: header
|
|
375
|
-
|
|
452
|
+
header: header,
|
|
453
|
+
draggable: draggable,
|
|
454
|
+
useBottomInset: useBottomInset,
|
|
455
|
+
footer: footer,
|
|
456
|
+
screenId: entries.last?.id
|
|
457
|
+
))
|
|
376
458
|
}
|
|
377
459
|
|
|
378
460
|
/// Mirrors Compose's `showSnackBar(snackBar:)`.
|
|
379
461
|
public func showSnackBar(_ snackBar: SnackBar, onDismiss: (() -> Void)? = nil) {
|
|
380
|
-
|
|
381
|
-
overplay = OverplayItem(
|
|
462
|
+
replaceOverplay(with: OverplayItem(
|
|
382
463
|
type: .snackBar,
|
|
383
464
|
content: { AnyView(EmptyView()) },
|
|
384
465
|
barrierDismissible: false,
|
|
385
466
|
onDismiss: onDismiss,
|
|
386
|
-
snackBar: snackBar
|
|
387
|
-
|
|
467
|
+
snackBar: snackBar,
|
|
468
|
+
screenId: entries.last?.id
|
|
469
|
+
))
|
|
388
470
|
}
|
|
389
471
|
|
|
390
472
|
// MARK: Overplay dismissal
|
|
@@ -399,10 +481,23 @@ public final class Navigator: ObservableObject {
|
|
|
399
481
|
/// `hardClearAfterDismiss`). Called by a component once its exit animation
|
|
400
482
|
/// has completed.
|
|
401
483
|
public func finishOverplayDismiss() {
|
|
402
|
-
|
|
484
|
+
replaceOverplay(with: nil)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/// Same, but only while `id` is still the overplay: an exit that ends after a newer overplay
|
|
488
|
+
/// took its place must not take that one down.
|
|
489
|
+
public func finishOverplayDismiss(id: UUID) {
|
|
490
|
+
guard overplay?.id == id else { return }
|
|
491
|
+
replaceOverplay(with: nil)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/// A replaced overplay counts as dismissed (Compose disposes it), so its `onDismiss` fires once,
|
|
495
|
+
/// whether it was still closing or not.
|
|
496
|
+
private func replaceOverplay(with item: OverplayItem?) {
|
|
497
|
+
let replaced = overplay
|
|
403
498
|
overplayDismissHandler = nil
|
|
404
|
-
overplay =
|
|
405
|
-
|
|
499
|
+
overplay = item
|
|
500
|
+
replaced?.onDismiss?()
|
|
406
501
|
}
|
|
407
502
|
|
|
408
503
|
public func hideOverplay() {
|
|
@@ -419,6 +514,14 @@ public final class Navigator: ObservableObject {
|
|
|
419
514
|
if overplay?.type == .snackBar { hideOverplay() }
|
|
420
515
|
}
|
|
421
516
|
|
|
517
|
+
/// Room the current screen's footer (or navigation bar) takes from the window bottom.
|
|
518
|
+
func snackBarBottomInset() -> CGFloat {
|
|
519
|
+
guard let id = currentScreen()?.id, let navigation = registry.navigation(id: id) else {
|
|
520
|
+
return NavigationFooter.navigationBarInset
|
|
521
|
+
}
|
|
522
|
+
return navigation.bottomObstruction
|
|
523
|
+
}
|
|
524
|
+
|
|
422
525
|
// MARK: Screen disposal
|
|
423
526
|
|
|
424
527
|
/// Matches the `delay(300)` Compose waits in `dismissScreen` before unregistering,
|
|
@@ -429,22 +532,19 @@ public final class Navigator: ObservableObject {
|
|
|
429
532
|
/// after the component has taken itself down.
|
|
430
533
|
private static let overplayDismissDelay: TimeInterval = 0.3
|
|
431
534
|
|
|
432
|
-
private func disposeScreens(droppedFrom previous: [
|
|
433
|
-
let
|
|
434
|
-
disposeScreens(previous.map(\.id).filter { !
|
|
535
|
+
private func disposeScreens(droppedFrom previous: [StackEntry], keeping current: [StackEntry]) {
|
|
536
|
+
let kept = Set(current.map(\.id))
|
|
537
|
+
disposeScreens(previous.map(\.id).filter { !kept.contains($0) })
|
|
435
538
|
}
|
|
436
539
|
|
|
437
540
|
/// Screens are released only once the pop transition has played out: the outgoing
|
|
438
|
-
/// view is still on screen while it animates
|
|
439
|
-
///
|
|
440
|
-
/// when the timer fires, so a route pushed back before then keeps its screen.
|
|
541
|
+
/// view is still on screen while it animates. Liveness is re-checked when the timer
|
|
542
|
+
/// fires, so an entry back on the stack by then keeps its screen.
|
|
441
543
|
private func disposeScreens(_ ids: [Int]) {
|
|
442
544
|
guard !ids.isEmpty else { return }
|
|
443
545
|
DispatchQueue.main.asyncAfter(deadline: .now() + Self.screenDisposeDelay) { [weak self] in
|
|
444
546
|
guard let self else { return }
|
|
445
|
-
|
|
446
|
-
live.insert(self.rootRoute.id)
|
|
447
|
-
if let presented = self.presented { live.insert(presented.id) }
|
|
547
|
+
let live = Set(self.entries.map(\.id))
|
|
448
548
|
for id in ids where !live.contains(id) {
|
|
449
549
|
self.registry.unregister(id: id)
|
|
450
550
|
}
|