@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
package/CLAUDE.md
CHANGED
|
@@ -56,11 +56,13 @@ Publishing is automated (`publish.sh` + GitLab CI on `[ci build]` / `main`). Do
|
|
|
56
56
|
|
|
57
57
|
## SwiftUI kit (`ios/`)
|
|
58
58
|
|
|
59
|
-
- **Layout mirrors Compose:** `ios/Navigation/` follows `navigation/` file for file (`NavigationContainer`, `Navigator`, `Navigation` (options + per-screen `Navigation`), `StackScreen` (incl. `NavigationFooter`), `BottomSheet`, `ModalScreen`, `HostBackGestureBridge`, `BottomTab/`, `Component/`, `Tracking/`); `ios/Layout/ScreenLayout.swift` mirrors `layout/`. `ios/Application/` keeps only context (`ApplicationEnvironment`), `Localize*`, `MaxApi` and the deprecated `Screen` / `LegacyHeader` (Compose `application/`). Change a navigation piece on both sides together.
|
|
59
|
+
- **Layout mirrors Compose:** `ios/Navigation/` follows `navigation/` file for file (`NavigationContainer`, `Navigator`, `Navigation` (options + per-screen `Navigation`), `StackScreen` (incl. `NavigationFooter`), `StackTransition`, `SwipeBackHaptic`, `BottomSheet`, `ModalScreen`, `HostBackGestureBridge`, `BottomTab/`, `Component/`, `Tracking/`); `ios/Layout/ScreenLayout.swift` mirrors `layout/`. `ios/Application/` keeps only context (`ApplicationEnvironment`), `Localize*`, `MaxApi` and the deprecated `Screen` / `LegacyHeader` (Compose `application/`). Change a navigation piece on both sides together.
|
|
60
60
|
- **File basenames must be unique across the pod** — Swift fails with "filename used twice" even in different folders (hence `LegacyHeader.swift` next to `Navigation/Component/Header.swift`). New folders under `ios/` must be added to `source_files` in `ios/native-kits.podspec`.
|
|
61
61
|
- Screens update their own chrome at runtime through `@EnvironmentObject var navigation: Navigation` → `setOptions { $0.title = … }` (Compose `LocalNavigation`); bottom tabs keep one `Navigation` per tab. The host back bridge is `HostBackGestureBridge.shared` (`NavigatorManager` is a deprecated alias); a bottom tab off its first tab routes back to the first tab (`Navigator.tabBackHandler`, keyed by the hosting screen).
|
|
62
|
-
-
|
|
63
|
-
- `
|
|
62
|
+
- Everything, navigation shell included, supports the pod's minimum **iOS 15** — no `@available` gates on public types. iOS 16+ APIs only behind `#available` with an iOS 15 path (`Layout`: `BottomSheet` `CapHeight`, `GridFlow` → `_VariadicView` flow row).
|
|
63
|
+
- **Usage is `NavigationContainer` + `Navigator` + `Navigation` only**; the kit owns the stack, transitions, overplays and back gesture. There is no `NavigationStack`: `StackTransition.swift` holds `StackHost` (representable) + `StackController`, the NavHost counterpart. Each entry gets its own `UIHostingController`, kept while on the stack and removed from the window while covered (state kept, onDisappear / onAppear like UINavigationController). Motion is a SwiftUI `offset` per card driven by one `coverage` value on a display link (a UIKit transform would move the safe area); it is Compose `StackTransition` 1:1 (RN `forStackTransitionWithOverlay`: linear 300ms slide, 0.3 parallax, `pink09` scrim to 0.6, 8pt edge shadow to 0.3). Card roots get the container environment via `.environment(\.self, …)` with the kit environment re-applied on top, so host environment objects reach screens.
|
|
64
|
+
- One back stack, root first (`Navigator.entries`, Compose's back stack): `present` appends a dialog entry that rises (FastOutSlowIn 300ms) and later pushes stack on top of it; `dismiss` drops the topmost dialog and everything above it (slides down whole); `popToRoot` returns to the active flow's root. Overplays (modal / bottom sheet / snackbar) render inside the screen they were shown over (`OverplayItem.screenId`, Compose `currentRootId`), so a later push slides over them.
|
|
65
|
+
- Host back gesture: `HostBackGestureBridge.started` / `progressed` / `completed` / `cancelled` scrub the pop (`Navigator.backStarted` …) only for a plain pop; tab back, `onBackHandler`, `preventBack`, an open overplay or a presented top wait for the release and go through `onBackSafe`. `SwipeBackHaptic` ticks at 50pt (`MaxApi.triggerEventVibration`, default = system light impact).
|
|
64
66
|
- Icons are compiled in: `ios/Icon/Icons.swift` is generated from Compose `utils/Icons.kt` by `node scripts/gen-ios-icons.mjs` — don't hand-edit it. A host `icon.json` in the main bundle only adds keys the kit lacks.
|
|
65
67
|
- Bundled assets (colors, Lottie JSON) live in `ios/Resources` and ship in the `MoMoUIKitsResources` bundle; load them through `MoMoUIKitsResources.bundle`, never `Bundle.main`.
|
|
66
68
|
|
|
@@ -5,14 +5,23 @@
|
|
|
5
5
|
// Swift mirror of the host-app bridge exposed on Kotlin as `vn.momo.maxapi.IMaxApi`
|
|
6
6
|
// (`LocalMaxApi`). `IMaxApi` itself is an external compiled dependency with no
|
|
7
7
|
// source in this repo, so this protocol intentionally exposes only the methods
|
|
8
|
-
// this kit's Kotlin source actually calls (`track`, `dismiss`, `getLanguage
|
|
9
|
-
// not a full guess at IMaxApi's real surface.
|
|
8
|
+
// this kit's Kotlin source actually calls (`track`, `dismiss`, `getLanguage`,
|
|
9
|
+
// `triggerEventVibration`) — not a full guess at IMaxApi's real surface.
|
|
10
10
|
//
|
|
11
11
|
|
|
12
|
-
import
|
|
12
|
+
import UIKit
|
|
13
13
|
|
|
14
14
|
public protocol MaxApi {
|
|
15
15
|
func track(eventName: String, params: [String: Any?])
|
|
16
16
|
func dismiss(completion: (() -> Void)?)
|
|
17
17
|
func getLanguage(callback: @escaping ([String: Any?]?) -> Void)
|
|
18
|
+
/// `type` is the IMaxApi vibration name (`"light"`, …).
|
|
19
|
+
func triggerEventVibration(_ type: String)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public extension MaxApi {
|
|
23
|
+
/// Hosts that don't bridge haptics still get the system light tick.
|
|
24
|
+
func triggerEventVibration(_ type: String) {
|
|
25
|
+
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
|
26
|
+
}
|
|
18
27
|
}
|
|
@@ -15,6 +15,12 @@ public struct RoundedCorner: Shape {
|
|
|
15
15
|
var radius: CGFloat = .infinity
|
|
16
16
|
var corners: UIRectCorner = .allCorners
|
|
17
17
|
|
|
18
|
+
/// iOS 15 stand-in for `UnevenRoundedRectangle`.
|
|
19
|
+
public init(radius: CGFloat = .infinity, corners: UIRectCorner = .allCorners) {
|
|
20
|
+
self.radius = radius
|
|
21
|
+
self.corners = corners
|
|
22
|
+
}
|
|
23
|
+
|
|
18
24
|
public func path(in rect: CGRect) -> Path {
|
|
19
25
|
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
|
|
20
26
|
return Path(path.cgPath)
|
|
@@ -73,9 +73,7 @@ struct GridFlow<Content: View>: View {
|
|
|
73
73
|
if #available(iOS 16.0, *) {
|
|
74
74
|
FlowRowLayout(gutter: gutter) { content() }
|
|
75
75
|
} else {
|
|
76
|
-
|
|
77
|
-
// Full-width items — the common case — look identical.
|
|
78
|
-
VStack(alignment: .leading, spacing: gutter) { content() }
|
|
76
|
+
_VariadicView.Tree(FlowRowFallback(gutter: gutter, maxWidth: availableWidth)) { content() }
|
|
79
77
|
}
|
|
80
78
|
}
|
|
81
79
|
|
|
@@ -95,6 +93,72 @@ private struct GridWidthKey: PreferenceKey {
|
|
|
95
93
|
}
|
|
96
94
|
}
|
|
97
95
|
|
|
96
|
+
/// iOS 15 has no `Layout`: the children are measured, then pinned to the same rows as
|
|
97
|
+
/// `FlowRowLayout` through alignment guides.
|
|
98
|
+
private struct FlowRowFallback: _VariadicView_UnaryViewRoot {
|
|
99
|
+
let gutter: CGFloat
|
|
100
|
+
let maxWidth: CGFloat
|
|
101
|
+
|
|
102
|
+
func body(children: _VariadicView.Children) -> some View {
|
|
103
|
+
FlowRowPlacement(children: children, gutter: gutter, maxWidth: maxWidth)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private struct FlowRowPlacement: View {
|
|
108
|
+
let children: _VariadicView.Children
|
|
109
|
+
let gutter: CGFloat
|
|
110
|
+
let maxWidth: CGFloat
|
|
111
|
+
@State private var sizes: [AnyHashable: CGSize] = [:]
|
|
112
|
+
|
|
113
|
+
var body: some View {
|
|
114
|
+
let placement = place()
|
|
115
|
+
ZStack(alignment: .topLeading) {
|
|
116
|
+
ForEach(children) { child in
|
|
117
|
+
child
|
|
118
|
+
// FlowRowLayout proposes no height
|
|
119
|
+
.fixedSize(horizontal: false, vertical: true)
|
|
120
|
+
.background(
|
|
121
|
+
GeometryReader { proxy in
|
|
122
|
+
Color.clear.preference(key: FlowChildSizesKey.self, value: [child.id: proxy.size])
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
.alignmentGuide(.leading) { _ in -(placement.origins[child.id]?.x ?? 0) }
|
|
126
|
+
.alignmentGuide(.top) { _ in -(placement.origins[child.id]?.y ?? 0) }
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
.frame(height: placement.height, alignment: .topLeading)
|
|
130
|
+
.onPreferenceChange(FlowChildSizesKey.self) { sizes = $0 }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private func place() -> (origins: [AnyHashable: CGPoint], height: CGFloat) {
|
|
134
|
+
var origins: [AnyHashable: CGPoint] = [:]
|
|
135
|
+
var x: CGFloat = 0
|
|
136
|
+
var y: CGFloat = 0
|
|
137
|
+
var rowHeight: CGFloat = 0
|
|
138
|
+
for child in children {
|
|
139
|
+
let size = sizes[child.id] ?? .zero
|
|
140
|
+
// Sub-point tolerance: spans are computed to land exactly on `maxWidth`.
|
|
141
|
+
if x > 0, x + size.width > maxWidth + 0.5 {
|
|
142
|
+
x = 0
|
|
143
|
+
y += rowHeight + gutter
|
|
144
|
+
rowHeight = 0
|
|
145
|
+
}
|
|
146
|
+
origins[child.id] = CGPoint(x: x, y: y)
|
|
147
|
+
x += size.width + gutter
|
|
148
|
+
rowHeight = max(rowHeight, size.height)
|
|
149
|
+
}
|
|
150
|
+
return (origins, y + rowHeight)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private struct FlowChildSizesKey: PreferenceKey {
|
|
155
|
+
static var defaultValue: [AnyHashable: CGSize] = [:]
|
|
156
|
+
|
|
157
|
+
static func reduce(value: inout [AnyHashable: CGSize], nextValue: () -> [AnyHashable: CGSize]) {
|
|
158
|
+
value.merge(nextValue()) { $1 }
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
98
162
|
/// Wrapping row with equal horizontal/vertical spacing — the Compose `FlowRow` equivalent.
|
|
99
163
|
@available(iOS 16.0, *)
|
|
100
164
|
private struct FlowRowLayout: Layout {
|
|
@@ -35,7 +35,6 @@ public struct BottomSheetHeader {
|
|
|
35
35
|
|
|
36
36
|
// MARK: - Bottom sheet view
|
|
37
37
|
|
|
38
|
-
@available(iOS 16.0, *)
|
|
39
38
|
struct BottomSheetView<Content: View>: View {
|
|
40
39
|
let header: BottomSheetHeader
|
|
41
40
|
let isSurface: Bool
|
|
@@ -294,12 +293,40 @@ struct BottomSheetView<Content: View>: View {
|
|
|
294
293
|
/// Compose's `heightIn(max:)`. `.frame(maxHeight:)` grows to the cap whenever the proposal is
|
|
295
294
|
/// larger, leaving a gap above the header; this sizes the sheet to its content and only caps it
|
|
296
295
|
/// when taller.
|
|
297
|
-
@available(iOS 16.0, *)
|
|
298
296
|
private struct CapHeight: ViewModifier {
|
|
299
297
|
let maxHeight: CGFloat
|
|
300
298
|
|
|
301
299
|
func body(content: Content) -> some View {
|
|
302
|
-
|
|
300
|
+
if #available(iOS 16.0, *) {
|
|
301
|
+
CapHeightLayout(maxHeight: maxHeight) { content }
|
|
302
|
+
} else {
|
|
303
|
+
content.modifier(CapHeightFallback(maxHeight: maxHeight))
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/// iOS 15 has no `Layout`: the sheet hugs its ideal height until that outgrows the cap, then is
|
|
309
|
+
/// held at the cap (the ideal height can't be read again without a second copy of the content).
|
|
310
|
+
private struct CapHeightFallback: ViewModifier {
|
|
311
|
+
let maxHeight: CGFloat
|
|
312
|
+
@State private var capped = false
|
|
313
|
+
|
|
314
|
+
func body(content: Content) -> some View {
|
|
315
|
+
content
|
|
316
|
+
// both modifiers stay in place whatever `capped` is, so the content keeps its identity
|
|
317
|
+
.fixedSize(horizontal: false, vertical: !capped)
|
|
318
|
+
.background(
|
|
319
|
+
GeometryReader { proxy in
|
|
320
|
+
Color.clear
|
|
321
|
+
.onAppear { cap(proxy.size.height) }
|
|
322
|
+
.onChange(of: proxy.size.height) { cap($0) }
|
|
323
|
+
}
|
|
324
|
+
)
|
|
325
|
+
.frame(height: capped ? maxHeight : nil, alignment: .top)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private func cap(_ height: CGFloat) {
|
|
329
|
+
if !capped, height > maxHeight { capped = true }
|
|
303
330
|
}
|
|
304
331
|
}
|
|
305
332
|
|
|
@@ -340,7 +367,6 @@ private struct BottomSheetFooter: View {
|
|
|
340
367
|
}
|
|
341
368
|
}
|
|
342
369
|
|
|
343
|
-
@available(iOS 16.0, *)
|
|
344
370
|
private func safeAreaBottomInset() -> CGFloat {
|
|
345
371
|
let bottomInset = UIApplication.shared.connectedScenes
|
|
346
372
|
.compactMap { ($0 as? UIWindowScene)?.keyWindow?.safeAreaInsets.bottom }
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
|
|
10
10
|
import SwiftUI
|
|
11
11
|
|
|
12
|
-
@available(iOS 16.0, *)
|
|
13
12
|
public struct BottomTab: View {
|
|
14
13
|
private let items: [BottomTabItem]
|
|
15
14
|
private let floatingButton: BottomTabFloatingButton?
|
|
@@ -178,7 +177,6 @@ private struct TabScreenHost: View {
|
|
|
178
177
|
}
|
|
179
178
|
|
|
180
179
|
/// Bottom safe-area inset (home indicator).
|
|
181
|
-
@available(iOS 16.0, *)
|
|
182
180
|
private func safeAreaBottomInset() -> CGFloat {
|
|
183
181
|
let bottomInset = UIApplication.shared.connectedScenes
|
|
184
182
|
.compactMap { ($0 as? UIWindowScene)?.keyWindow?.safeAreaInsets.bottom }
|
|
@@ -49,7 +49,6 @@ public struct SnackBar {
|
|
|
49
49
|
|
|
50
50
|
/// Self-animating container. Slides the content up on appear, schedules an
|
|
51
51
|
/// auto-dismiss after `duration`, and animates out before removing the overplay.
|
|
52
|
-
@available(iOS 16.0, *)
|
|
53
52
|
struct SnackBarView: View {
|
|
54
53
|
let snackBar: SnackBar
|
|
55
54
|
/// Distance from the window bottom to the top of the screen's footer, or the navigation bar
|
|
@@ -4,18 +4,19 @@
|
|
|
4
4
|
//
|
|
5
5
|
// SwiftUI mirror of Compose's `navigation/HostBackGestureBridge.kt`: the host owns the edge-pan
|
|
6
6
|
// recogniser and routes a back into the topmost kit stack. NavigationContainer attaches itself.
|
|
7
|
-
//
|
|
7
|
+
// `started` / `progressed` / `completed` / `cancelled` scrub the pop transition with the finger;
|
|
8
|
+
// a host that only calls `pop` gets the regular animated back.
|
|
8
9
|
//
|
|
9
10
|
|
|
10
|
-
import
|
|
11
|
+
import UIKit
|
|
11
12
|
|
|
12
|
-
@available(iOS 16.0, *)
|
|
13
13
|
public final class HostBackGestureBridge {
|
|
14
14
|
public static let shared = HostBackGestureBridge()
|
|
15
15
|
private init() {}
|
|
16
16
|
|
|
17
|
-
// Weak: a container torn down
|
|
17
|
+
// Weak: a container torn down without disappearing must not keep receiving backs.
|
|
18
18
|
private var navigators: [WeakNavigator] = []
|
|
19
|
+
private weak var gestureTarget: Navigator?
|
|
19
20
|
|
|
20
21
|
/// Set while a bottom tab other than the first is selected; the host shows its floating arrow.
|
|
21
22
|
public var shouldUseFloatingArrowBack = false
|
|
@@ -28,6 +29,7 @@ public final class HostBackGestureBridge {
|
|
|
28
29
|
|
|
29
30
|
public func detach(_ navigator: Navigator) {
|
|
30
31
|
navigators.removeAll { $0.value == nil || $0.value === navigator }
|
|
32
|
+
if gestureTarget === navigator { gestureTarget = nil }
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
/// Whether a kit stack is on screen at all.
|
|
@@ -38,6 +40,27 @@ public final class HostBackGestureBridge {
|
|
|
38
40
|
|
|
39
41
|
public func pop() { top()?.onBackSafe() }
|
|
40
42
|
|
|
43
|
+
public func started() {
|
|
44
|
+
guard let target = top() else { return }
|
|
45
|
+
gestureTarget = target
|
|
46
|
+
target.backStarted()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// `progress` is the touch travel over the window width, 0...1.
|
|
50
|
+
public func progressed(_ progress: CGFloat) {
|
|
51
|
+
gestureTarget?.backProgressed(min(max(progress, 0), 1))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public func completed() {
|
|
55
|
+
(gestureTarget ?? top())?.backCompleted()
|
|
56
|
+
gestureTarget = nil
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public func cancelled() {
|
|
60
|
+
gestureTarget?.backCancelled()
|
|
61
|
+
gestureTarget = nil
|
|
62
|
+
}
|
|
63
|
+
|
|
41
64
|
@available(*, deprecated, renamed: "attach")
|
|
42
65
|
public func push(_ navigator: Navigator) { attach(navigator) }
|
|
43
66
|
|
|
@@ -47,11 +70,9 @@ public final class HostBackGestureBridge {
|
|
|
47
70
|
private func top() -> Navigator? { navigators.last { $0.value != nil }?.value }
|
|
48
71
|
}
|
|
49
72
|
|
|
50
|
-
@available(iOS 16.0, *)
|
|
51
73
|
@available(*, deprecated, renamed: "HostBackGestureBridge")
|
|
52
74
|
public typealias NavigatorManager = HostBackGestureBridge
|
|
53
75
|
|
|
54
|
-
@available(iOS 16.0, *)
|
|
55
76
|
private struct WeakNavigator {
|
|
56
77
|
weak var value: Navigator?
|
|
57
78
|
}
|
|
@@ -66,6 +66,11 @@ public struct NavigationOptions {
|
|
|
66
66
|
public var fullScreenContent: Bool
|
|
67
67
|
public var hiddenBack: Bool
|
|
68
68
|
public var backgroundColor: Color?
|
|
69
|
+
// AI-GENERATED START: custom screen background view (Compose backgroundComponent)
|
|
70
|
+
/// Drawn instead of `backgroundColor` over the whole window: under the header and the status bar,
|
|
71
|
+
/// across the iPhone Duo side strips, and not shrunk by the keyboard. Pair with `headerTransparent`.
|
|
72
|
+
public var background: (() -> AnyView)?
|
|
73
|
+
// AI-GENERATED END: custom screen background view (Compose backgroundComponent)
|
|
69
74
|
public var tintColor: Color?
|
|
70
75
|
public var scrollable: Bool
|
|
71
76
|
/// Compose's `ScrollData.scrollToTopCallback`, fired on a status-bar tap.
|
|
@@ -98,6 +103,9 @@ public struct NavigationOptions {
|
|
|
98
103
|
fullScreenContent: Bool = false,
|
|
99
104
|
hiddenBack: Bool = false,
|
|
100
105
|
backgroundColor: Color? = nil,
|
|
106
|
+
// AI-GENERATED START: custom screen background view (Compose backgroundComponent)
|
|
107
|
+
background: (() -> AnyView)? = nil,
|
|
108
|
+
// AI-GENERATED END: custom screen background view (Compose backgroundComponent)
|
|
101
109
|
tintColor: Color? = Colors.black17,
|
|
102
110
|
scrollable: Bool = true,
|
|
103
111
|
scrollToTopCallback: (() -> Void)? = nil,
|
|
@@ -125,6 +133,9 @@ public struct NavigationOptions {
|
|
|
125
133
|
self.fullScreenContent = fullScreenContent
|
|
126
134
|
self.hiddenBack = hiddenBack
|
|
127
135
|
self.backgroundColor = backgroundColor
|
|
136
|
+
// AI-GENERATED START: custom screen background view (Compose backgroundComponent)
|
|
137
|
+
self.background = background
|
|
138
|
+
// AI-GENERATED END: custom screen background view (Compose backgroundComponent)
|
|
128
139
|
self.tintColor = tintColor
|
|
129
140
|
self.scrollable = scrollable
|
|
130
141
|
self.scrollToTopCallback = scrollToTopCallback
|
|
@@ -161,6 +172,9 @@ struct OptionsStackScreen: View {
|
|
|
161
172
|
var body: some View {
|
|
162
173
|
StackScreen(
|
|
163
174
|
backgroundColor: options.backgroundColor,
|
|
175
|
+
// AI-GENERATED START: custom screen background view (Compose backgroundComponent)
|
|
176
|
+
background: options.background,
|
|
177
|
+
// AI-GENERATED END: custom screen background view (Compose backgroundComponent)
|
|
164
178
|
headerTransparent: options.headerTransparent,
|
|
165
179
|
fullScreenContent: options.fullScreenContent,
|
|
166
180
|
tintColor: options.tintColor ?? Colors.black17,
|
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
// NavigationContainer.swift
|
|
3
3
|
// MoMoUIKits
|
|
4
4
|
//
|
|
5
|
-
// SwiftUI mirror of Compose's `navigation/NavigationContainer.kt`:
|
|
6
|
-
// (
|
|
7
|
-
// screen inside `StackScreen` chrome built from its options.
|
|
5
|
+
// SwiftUI mirror of Compose's `navigation/NavigationContainer.kt`: hosts the kit stack
|
|
6
|
+
// (`StackHost`, the NavHost counterpart), injects Navigator, theme, localize and font scale, and
|
|
7
|
+
// renders every registered screen inside `StackScreen` chrome built from its options.
|
|
8
8
|
//
|
|
9
9
|
|
|
10
10
|
import SwiftUI
|
|
11
11
|
|
|
12
|
-
@available(iOS 16.0, *)
|
|
13
12
|
public struct NavigationContainer<Initial: View>: View {
|
|
14
13
|
@StateObject private var navigator: Navigator
|
|
15
14
|
@StateObject private var applicationEnvironment: ApplicationEnvironment
|
|
@@ -71,24 +70,17 @@ public struct NavigationContainer<Initial: View>: View {
|
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
public var body: some View {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
StackHost(
|
|
74
|
+
navigator: navigator,
|
|
75
|
+
entries: navigator.entries,
|
|
76
|
+
screen: { [navigator] entry in
|
|
77
|
+
guard let screen = navigator.registry.getScreen(id: entry.id) else { return AnyView(EmptyView()) }
|
|
78
|
+
return AnyView(ScreenHost(screen: screen, navigator: navigator))
|
|
79
|
+
},
|
|
80
|
+
decorate: { AnyView(withKitEnvironment($0)) }
|
|
77
81
|
)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
// A close that bypasses the navigator (e.g. `dismiss()`) still tears the flow down.
|
|
81
|
-
// SwiftUI also writes nil after a navigator-driven close; by then a new screen may
|
|
82
|
-
// already be queued, so only a cover that is still showing counts.
|
|
83
|
-
set: { if $0 == nil, navigator.coverRoute != nil { navigator.dismiss() } }
|
|
84
|
-
)) { route in
|
|
85
|
-
withKitEnvironment(
|
|
86
|
-
stack(path: $navigator.presentedPath, root: DynamicScreenRoute(id: route.id))
|
|
87
|
-
.overlay { overplayLayer(presentedId: route.id) }
|
|
88
|
-
)
|
|
89
|
-
.onAppear { navigator.coverDidAppear() }
|
|
90
|
-
.onDisappear { navigator.coverDidDisappear() }
|
|
91
|
-
}
|
|
82
|
+
// edge to edge: each screen's hosting controller takes the safe area and keyboard itself
|
|
83
|
+
.ignoresSafeArea()
|
|
92
84
|
.onAppear {
|
|
93
85
|
if navigator.composeApi == nil {
|
|
94
86
|
navigator.composeApi = composeApi
|
|
@@ -110,42 +102,10 @@ public struct NavigationContainer<Initial: View>: View {
|
|
|
110
102
|
// AI-GENERATED END: demote the MaxApi language pull to a seed of last resort
|
|
111
103
|
}
|
|
112
104
|
.onDisappear {
|
|
113
|
-
// Our own cover hides the stack without leaving it; host back must still reach it.
|
|
114
|
-
guard navigator.presented == nil else { return }
|
|
115
105
|
HostBackGestureBridge.shared.detach(navigator)
|
|
116
106
|
}
|
|
117
107
|
}
|
|
118
108
|
|
|
119
|
-
private func stack(path: Binding<[DynamicScreenRoute]>, root: DynamicScreenRoute) -> some View {
|
|
120
|
-
NavigationStack(path: path) {
|
|
121
|
-
screenView(for: root)
|
|
122
|
-
// A reset swaps the root in place; without the id it would inherit the old root's state.
|
|
123
|
-
.id(root.id)
|
|
124
|
-
.navigationDestination(for: DynamicScreenRoute.self) { route in
|
|
125
|
-
screenView(for: route)
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
@ViewBuilder
|
|
131
|
-
private func screenView(for route: DynamicScreenRoute) -> some View {
|
|
132
|
-
if let screen = navigator.registry.getScreen(id: route.id) {
|
|
133
|
-
ScreenHost(screen: screen, navigator: navigator)
|
|
134
|
-
.navigationBarBackButtonHidden(true)
|
|
135
|
-
} else {
|
|
136
|
-
EmptyView()
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/// Each context draws only the overplays shown over it, so one never re-mounts elsewhere.
|
|
141
|
-
@ViewBuilder
|
|
142
|
-
private func overplayLayer(presentedId: Int?) -> some View {
|
|
143
|
-
if let item = navigator.overplay, item.presentedId == presentedId {
|
|
144
|
-
OverplayHost(item: item)
|
|
145
|
-
.momoScreenLayout()
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
109
|
private func withKitEnvironment<Content: View>(_ content: Content) -> some View {
|
|
150
110
|
content
|
|
151
111
|
.environmentObject(navigator)
|
|
@@ -159,7 +119,6 @@ public struct NavigationContainer<Initial: View>: View {
|
|
|
159
119
|
|
|
160
120
|
// MARK: - ScreenHost
|
|
161
121
|
|
|
162
|
-
@available(iOS 16.0, *)
|
|
163
122
|
private struct ScreenHost: View {
|
|
164
123
|
let screen: DynamicScreen
|
|
165
124
|
let navigator: Navigator
|
|
@@ -180,15 +139,26 @@ private struct ScreenHost: View {
|
|
|
180
139
|
goBack: { navigator.onBackSafe() },
|
|
181
140
|
content: screen.content
|
|
182
141
|
)
|
|
142
|
+
// Compose draws the overplay inside the screen it was shown over, under its LocalNavigation.
|
|
143
|
+
.overlay { OverplayLayer(screenId: screen.id) }
|
|
183
144
|
.environmentObject(navigation)
|
|
184
|
-
// Per screen, not around the NavigationStack: a state-driven reader there stops path pushes rendering.
|
|
185
145
|
.momoScreenLayout()
|
|
186
146
|
}
|
|
187
147
|
}
|
|
188
148
|
|
|
149
|
+
private struct OverplayLayer: View {
|
|
150
|
+
let screenId: Int
|
|
151
|
+
@EnvironmentObject private var navigator: Navigator
|
|
152
|
+
|
|
153
|
+
var body: some View {
|
|
154
|
+
if let item = navigator.overplay, item.screenId == screenId {
|
|
155
|
+
OverplayHost(item: item)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
189
160
|
// MARK: - Overplay host
|
|
190
161
|
|
|
191
|
-
@available(iOS 16.0, *)
|
|
192
162
|
private struct OverplayHost: View {
|
|
193
163
|
let item: OverplayItem
|
|
194
164
|
@EnvironmentObject var navigator: Navigator
|