@momo-kits/native-kits 0.167.1-duo.3-debug → 0.167.1-duo.4
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 +17 -8
- package/ios/Application/ApplicationEnvironment.swift +6 -0
- package/ios/Application/Components.swift +1 -3
- package/ios/Application/HeaderRight.swift +19 -27
- package/ios/Application/Navigation/BottomTab/BottomTab.swift +9 -30
- package/ios/Application/Navigation/BottomTab/BottomTabBar.swift +2 -5
- package/ios/Application/Navigation/HeaderUser.swift +1 -7
- package/ios/Application/Navigation/NavigationContainer.swift +45 -45
- package/ios/Application/Navigation/Navigator.swift +122 -29
- package/ios/Application/Navigation/NavigatorManager.swift +13 -6
- package/ios/Application/Navigation/Overplay/BottomSheet.swift +4 -41
- package/ios/Application/Navigation/Overplay/ModalScreen.swift +1 -5
- package/ios/Application/Navigation/component/Header.swift +4 -12
- package/ios/Application/Navigation/component/HeaderBackground.swift +2 -3
- package/ios/Application/Navigation/component/NavigationFooter.swift +1 -1
- package/ios/Application/Navigation/component/NavigationHeaderRight.swift +9 -61
- package/ios/Application/StackScreen.swift +18 -135
- package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +59 -5
- package/ios/Colors+Radius+Spacing/Colors.swift +1 -1
- package/ios/Extensions/Color++.swift +20 -9
- package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
- package/ios/Icon/Icon.swift +14 -22
- package/ios/Icon/Icons.swift +1329 -0
- package/ios/Image/Image.swift +16 -5
- package/ios/Lottie/LottieView.swift +4 -2
- package/ios/Popup/PopupDisplay.swift +68 -84
- package/ios/Popup/PopupNotify.swift +32 -43
- package/ios/Popup/PopupPromotion.swift +6 -18
- package/ios/Resources/dot_loading.json +558 -0
- package/ios/Resources/lottie_circle_loader.json +1 -0
- package/ios/Template/TrustBanner/TrustBanner.swift +69 -31
- package/ios/native-kits.podspec +1 -1
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +0 -1
- package/package.json +1 -1
- package/scripts/gen-ios-icons.mjs +36 -0
- package/.claude/scheduled_tasks.lock +0 -1
- package/.claude/settings.local.json +0 -185
- 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/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +0 -124
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +0 -831
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +0 -269
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +0 -56
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +0 -157
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +0 -82
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +0 -32
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +0 -338
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +0 -175
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +0 -356
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +0 -113
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -89
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +0 -128
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +0 -209
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +0 -542
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +0 -49
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +0 -76
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +0 -125
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +0 -231
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +0 -189
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +0 -475
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +0 -114
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +0 -266
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +0 -240
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +0 -223
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +0 -264
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +0 -185
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +0 -343
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +0 -108
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +0 -56
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +0 -41
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +0 -84
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +0 -40
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +0 -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 -310
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +0 -59
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +0 -87
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +0 -21
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +0 -90
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +0 -323
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +0 -220
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +0 -473
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +0 -122
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +0 -199
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +0 -91
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +0 -552
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +0 -88
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +0 -125
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +0 -207
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +0 -498
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +0 -176
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +0 -182
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +0 -193
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +0 -29
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +0 -234
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +0 -188
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +0 -325
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +0 -16
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +0 -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 -188
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +0 -297
- 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 -187
- 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 -648
- 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 -194
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +0 -232
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +0 -86
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +0 -281
- 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 -408
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +0 -108
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +0 -105
- 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 -354
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +0 -37
- 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 -169
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +0 -132
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +0 -127
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +0 -12
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +0 -91
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +0 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +0 -74
- 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 -40
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +0 -45
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +0 -32
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +0 -38
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +0 -66
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +0 -217
- 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/Application/Layout/ScreenLayout.swift +0 -196
- package/ios/Application/Navigation/BottomTab/DuoTabBar.swift +0 -275
- package/ios/Application/Navigation/component/DuoScrollGradient.swift +0 -55
- package/ios/Application/Navigation/component/SideBar.swift +0 -155
- package/ios/Popup/PopupLayout.swift +0 -45
- package/ios-demo/README.md +0 -120
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +0 -76
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +0 -170
- package/local.properties +0 -8
- package/settings.gradle.kts +0 -64
package/CLAUDE.md
CHANGED
|
@@ -35,6 +35,13 @@ No unit-test suite is configured. Validate changes by compiling the relevant tar
|
|
|
35
35
|
./gradlew :sample:androidApp:installDebug # run demo on device/emulator
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
The SwiftUI kit (`ios/`, pod `MoMoUIKits`) compiles on its own through the sample's Pods project (`pod install` in `sample/iosApp` first when files are added):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
xcodebuild -project sample/iosApp/Pods/Pods.xcodeproj -scheme MoMoUIKits -sdk iphonesimulator \
|
|
42
|
+
-destination 'generic/platform=iOS Simulator' build
|
|
43
|
+
```
|
|
44
|
+
|
|
38
45
|
Publishing is automated (`publish.sh` + GitLab CI on `[ci build]` / `main`). Do not publish manually unless asked: `sh publish.sh beta` / `sh publish.sh release` (Maven→GitLab + NPM).
|
|
39
46
|
|
|
40
47
|
## Architecture essentials
|
|
@@ -47,16 +54,18 @@ Publishing is automated (`publish.sh` + GitLab CI on `[ci build]` / `main`). Do
|
|
|
47
54
|
- Navigation uses a runtime `DynamicScreenRegistry`; overlays (modal/bottom-sheet/snackbar) go through `Navigator` + `OverplayComponentRegistry`.
|
|
48
55
|
- **iOS swipe-back:** the host owns the only edge-pan recogniser and replays it through `HostBackGestureBridge` (`navigation/HostBackGestureBridge.kt`; the kit framework is never exported, so the host reaches it through its own Koin-provided facade): `checkCanPop` / `canPopInner` / `pop` route the back into the kit stack, `started` / `progressed` / `completed` / `cancelled` seek the NavHost transition, `shouldUseFloatingArrowBack` is set by bottom tabs. `NavigationContainer` attaches itself automatically (`AttachHostBackGesture`, iOS only, detached by identity on dispose) — hosts need no wrapper and no `setNavigator`. `maxApi` is a required parameter of `NavigationContainer` — every container, nested or root, is handed its host bridge explicitly.
|
|
49
56
|
|
|
50
|
-
##
|
|
57
|
+
## SwiftUI kit (`ios/`)
|
|
58
|
+
|
|
59
|
+
- The pod's minimum is **iOS 15**, but the navigation shell (`NavigationContainer`, `Navigator`, `NavigatorManager`, `BottomTab`, overplay views) is **iOS 16+** (`@available(iOS 16.0, *)`) because it is built on `NavigationStack`. Keep components outside that shell iOS 15-compatible.
|
|
60
|
+
- `present` opens one `fullScreenCover` that hosts its own `NavigationStack` (`Navigator.presentedPath`): pushes and pops act on the presented flow while it is up, and each overplay (modal / bottom sheet / snackbar) renders in the context it was shown over (`OverplayItem.presentedId`).
|
|
61
|
+
- 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.
|
|
62
|
+
- Bundled assets (colors, Lottie JSON) live in `ios/Resources` and ship in the `MoMoUIKitsResources` bundle; load them through `MoMoUIKitsResources.bundle`, never `Bundle.main`.
|
|
63
|
+
|
|
64
|
+
## Screen layout / iPhone Duo (port of RN `useScreenLayout`)
|
|
51
65
|
|
|
52
|
-
- `layout/ScreenLayout.kt`: `getScreenLayout()` returns window size,
|
|
53
|
-
- iPhone-only: everything gates on `isPhoneIdiom()` (`platform/`), never on "iOS", so iPad windows keep the regular layout. Two detectors, as in RN: `isIPhoneDuo` (window aspect < 1.6 or asymmetric side insets) drives layout; `isIPhoneDuoDevice` (short side of screen or window > 440, RN `device.ts`) drives popups, modal and bottom sheet.
|
|
66
|
+
- `layout/ScreenLayout.kt`: `getScreenLayout()` returns window size, side safe-area insets, `contentWidth`, header geometry and the iPhone Duo `sideBar`. `NavigationContainer` computes it once and provides `LocalScreenLayout`; reactive to fold/unfold and rotation (window size + `WindowInsets`). Metrics (`DuoMetrics`, 72pt iPhone header floor, 20pt status-bar threshold) mirror RN `Layout/utils.ts` — change both together.
|
|
54
67
|
- Header height is `headerHeight` (not `AppStatusBar + HEADER_HEIGHT`); the bar row is `headerBarHeight`. When the status bar is off the top edge (iPhone landscape, iPhone Duo) `headerStatusBarHeight` is 0 so the title stays centered.
|
|
55
|
-
- iPhone Duo with a side strip ≥ 60pt: back + toolkit leave the bar for `HeaderSideBar` (`navigation/component/SideBar.kt`, 48pt glass controls); custom header-right stays in the bar. The column starts
|
|
56
|
-
- Tab bar on Duo is `bottomtab/DuoTabBar.kt`: a rail in the side strip (anchored 24pt from the bottom, QR below it) or, without a side bar, a floating bar spanning 6/8 grid columns. Both overlay the content; `duoFloatingTabBarInset` pads tab content, footer and FAB.
|
|
57
|
-
- Glass header: on Duo with a side bar, Default/Extended headers without search and with a `ScrollState` let content scroll under the bar (`usesDuoGlassHeader`); the bar fades in a translucent surface + hairline over 16pt (Compose has no backdrop blur — RN's fallback look), the gradient scrolls away / collapses.
|
|
58
|
-
- Overlays on Duo: popups cap at 440pt wide and the room between `duoTopInset` and the bottom inset (body scrolls, actions stay); modal centers inside that room; bottom sheet uses the window height, centers at width ≥ 600 and pads by side insets in landscape.
|
|
59
|
-
- Font scale deliberately uses the short side (`min(w, h)`), unlike RN which reads the window width — landscape must not inflate every font.
|
|
68
|
+
- iPhone Duo with a side strip ≥ 60pt: back + toolkit leave the bar for `HeaderSideBar` (`navigation/component/SideBar.kt`, 48pt glass controls); custom header-right stays in the bar. The column starts below the camera / status-bar cluster: 170pt portrait, 120pt in any landscape (folded too — RN still uses 170 there), below the header when the status bar is on top; UIKit's `statusBarFrame` is useless on Duo. Unlike RN it is clamped 24pt above footer / tab bar, so a long toolkit (shortcut + group) never covers the footer.
|
|
60
69
|
- `StackScreen` pads content/footer/search/FAB by the side insets and provides an inset-consumed layout to its content; a bottom-tab root stays full width (tab bar + each tab's StackScreen handle insets). Grid columns use `contentWidth`.
|
|
61
70
|
|
|
62
71
|
## i18n / Localize (port of RN `Localize`)
|
|
@@ -41,6 +41,12 @@ public class KitConfig {
|
|
|
41
41
|
public var trustBanner: TrustBannerData? = nil
|
|
42
42
|
public var headerBar: String? = nil
|
|
43
43
|
public var headerGradient: String? = nil
|
|
44
|
+
|
|
45
|
+
public init(trustBanner: TrustBannerData? = nil, headerBar: String? = nil, headerGradient: String? = nil) {
|
|
46
|
+
self.trustBanner = trustBanner
|
|
47
|
+
self.headerBar = headerBar
|
|
48
|
+
self.headerGradient = headerGradient
|
|
49
|
+
}
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
public class ApplicationEnvironment: ObservableObject {
|
|
@@ -248,9 +248,7 @@ public struct Header: View {
|
|
|
248
248
|
if let inputProps = inputSearchProps {
|
|
249
249
|
InputSearch(props: inputProps)
|
|
250
250
|
} else if case let .user(userData)? = headerTitle {
|
|
251
|
-
|
|
252
|
-
HeaderUserView(userData)
|
|
253
|
-
}
|
|
251
|
+
HeaderUserView(userData)
|
|
254
252
|
} else {
|
|
255
253
|
MomoText(headerTitle?.defaultTitle ?? title, typography: .headerDefaultBold, color: tintColor ?? Colors.black17, maxLines: 1)
|
|
256
254
|
.opacity(titleOpacity)
|
|
@@ -7,31 +7,20 @@ public struct HeaderRightData {
|
|
|
7
7
|
var tools: [ToolGroup]
|
|
8
8
|
var toolCallback: ((String) -> Void)?
|
|
9
9
|
var useSystemTools: Bool
|
|
10
|
-
var useCloseIcon: Bool
|
|
11
10
|
|
|
12
11
|
public init(
|
|
13
12
|
useShortcut: Bool = false,
|
|
14
13
|
useMore: Bool = false,
|
|
15
14
|
tools: [ToolGroup] = [],
|
|
16
15
|
toolCallback: ((String) -> Void)? = nil,
|
|
17
|
-
useSystemTools: Bool = true
|
|
18
|
-
useCloseIcon: Bool = false
|
|
16
|
+
useSystemTools: Bool = true
|
|
19
17
|
) {
|
|
20
18
|
self.useShortcut = useShortcut
|
|
21
19
|
self.useMore = useMore
|
|
22
20
|
self.tools = tools
|
|
23
21
|
self.toolCallback = toolCallback
|
|
24
22
|
self.useSystemTools = useSystemTools
|
|
25
|
-
self.useCloseIcon = useCloseIcon
|
|
26
23
|
}
|
|
27
|
-
|
|
28
|
-
/// Close-circle instead of home, as RN `headerRight.useCloseIcon`.
|
|
29
|
-
var closeIcon: String { useCloseIcon ? "16_navigation_close_circle" : "16_basic_home" }
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/// The Help Center mini-app hides its own help-center button.
|
|
33
|
-
func showsHelpCenter(_ context: MiniAppContext?) -> Bool {
|
|
34
|
-
context?.appId != "vn.momo.helpcenter"
|
|
35
24
|
}
|
|
36
25
|
|
|
37
26
|
public struct Tool {
|
|
@@ -112,8 +101,13 @@ struct ToolkitHeaderRight: View {
|
|
|
112
101
|
var tintColor: Color?
|
|
113
102
|
@State private var isFavorite: Bool = false
|
|
114
103
|
@State private var isLoading: Bool = false
|
|
115
|
-
|
|
116
|
-
|
|
104
|
+
// Legacy hosts inject with `.environmentObject`, containers with the keyed value; outside
|
|
105
|
+
// both this used to trap, so read the object optionally and fall back to the keyed one.
|
|
106
|
+
@OptionalEnvironmentObject private var injectedEnvironment: ApplicationEnvironment?
|
|
107
|
+
@Environment(\.applicationEnvironment) private var keyedEnvironment
|
|
108
|
+
|
|
109
|
+
private var environment: ApplicationEnvironment { injectedEnvironment ?? keyedEnvironment }
|
|
110
|
+
|
|
117
111
|
// MARK: - Actions
|
|
118
112
|
private func onPressShortcut() {
|
|
119
113
|
// onToolAction needs the app context (appId/code) so the host knows which app to (un)favorite;
|
|
@@ -249,19 +243,17 @@ struct ToolkitHeaderRight: View {
|
|
|
249
243
|
}
|
|
250
244
|
|
|
251
245
|
HStack(alignment: .center, spacing: 0) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
Icon(source: headerRight?.closeIcon ?? "16_basic_home", size: 20, color: tintColor ?? Colors.black17)
|
|
246
|
+
Icon(source: "help_center", size: 20, color: tintColor ?? Colors.black17)
|
|
247
|
+
.padding(4)
|
|
248
|
+
.onTapGesture {
|
|
249
|
+
onPressHelpCenter()
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
Rectangle()
|
|
253
|
+
.fill(tintColor ?? Colors.black20)
|
|
254
|
+
.frame(width: 0.5, height: 12)
|
|
255
|
+
|
|
256
|
+
Icon(source: "16_basic_home", size: 20, color: tintColor ?? Colors.black17)
|
|
265
257
|
.padding(4)
|
|
266
258
|
.onTapGesture {
|
|
267
259
|
onPressClose()
|
|
@@ -16,7 +16,6 @@ public struct BottomTab: View {
|
|
|
16
16
|
private let floatingButton: BottomTabFloatingButton?
|
|
17
17
|
|
|
18
18
|
@EnvironmentObject private var navigator: Navigator
|
|
19
|
-
@Environment(\.screenLayout) private var layout
|
|
20
19
|
@State private var selectedIndex: Int = 0
|
|
21
20
|
|
|
22
21
|
public init(items: [BottomTabItem], floatingButton: BottomTabFloatingButton? = nil) {
|
|
@@ -25,14 +24,7 @@ public struct BottomTab: View {
|
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
private var bottomInset: CGFloat { safeAreaBottomInset() }
|
|
28
|
-
|
|
29
|
-
private var reservedBottom: CGFloat {
|
|
30
|
-
layout.isIPhoneDuo ? layout.insets.bottom : BOTTOM_TAB_BAR_HEIGHT + bottomInset + Spacing.S
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
private var duoInsets: DuoTabBarInsets {
|
|
34
|
-
layout.duoTabBarInsets(itemCount: items.count, hasFloatingButton: floatingButton != nil)
|
|
35
|
-
}
|
|
27
|
+
private var reservedBottom: CGFloat { BOTTOM_TAB_BAR_HEIGHT + bottomInset + Spacing.S }
|
|
36
28
|
|
|
37
29
|
public var body: some View {
|
|
38
30
|
ZStack(alignment: .bottom) {
|
|
@@ -46,29 +38,20 @@ public struct BottomTab: View {
|
|
|
46
38
|
}
|
|
47
39
|
}
|
|
48
40
|
.padding(.bottom, reservedBottom)
|
|
49
|
-
.environment(\.duoTabBarInsets, duoInsets)
|
|
50
41
|
.animation(.easeInOut(duration: 0.2), value: selectedIndex)
|
|
51
42
|
|
|
52
|
-
|
|
53
|
-
|
|
43
|
+
// Tab bar + home-indicator filler.
|
|
44
|
+
VStack(spacing: 0) {
|
|
45
|
+
BottomTabBar(
|
|
54
46
|
items: items,
|
|
55
47
|
floatingButton: floatingButton,
|
|
56
48
|
selectedIndex: selectedIndex,
|
|
57
|
-
onTabSelected:
|
|
49
|
+
onTabSelected: { index in
|
|
50
|
+
if index != selectedIndex { selectedIndex = index }
|
|
51
|
+
}
|
|
58
52
|
)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
VStack(spacing: 0) {
|
|
62
|
-
BottomTabBar(
|
|
63
|
-
items: items,
|
|
64
|
-
floatingButton: floatingButton,
|
|
65
|
-
selectedIndex: selectedIndex,
|
|
66
|
-
onTabSelected: selectTab
|
|
67
|
-
)
|
|
68
|
-
Colors.black01
|
|
69
|
-
.frame(height: bottomInset + Spacing.S)
|
|
70
|
-
.ignoresSafeArea(edges: .horizontal)
|
|
71
|
-
}
|
|
53
|
+
Colors.black01
|
|
54
|
+
.frame(height: bottomInset + Spacing.S)
|
|
72
55
|
}
|
|
73
56
|
}
|
|
74
57
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
@@ -76,10 +59,6 @@ public struct BottomTab: View {
|
|
|
76
59
|
.preference(key: IsBottomTabRootKey.self, value: true)
|
|
77
60
|
}
|
|
78
61
|
|
|
79
|
-
private func selectTab(_ index: Int) {
|
|
80
|
-
if index != selectedIndex { selectedIndex = index }
|
|
81
|
-
}
|
|
82
|
-
|
|
83
62
|
@ViewBuilder
|
|
84
63
|
private func tabScreen(for index: Int) -> some View {
|
|
85
64
|
let item = items[index]
|
|
@@ -65,10 +65,7 @@ struct BottomTabBar: View {
|
|
|
65
65
|
let selectedIndex: Int
|
|
66
66
|
let onTabSelected: (Int) -> Void
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
// the bar sits between the side safe areas
|
|
71
|
-
private var screenWidth: CGFloat { layout.width - layout.insets.leading - layout.insets.trailing }
|
|
68
|
+
private var screenWidth: CGFloat { UIScreen.main.bounds.width }
|
|
72
69
|
private var mid: Int { items.count / 2 }
|
|
73
70
|
|
|
74
71
|
private var tabWidth: CGFloat {
|
|
@@ -98,7 +95,7 @@ struct BottomTabBar: View {
|
|
|
98
95
|
}
|
|
99
96
|
.frame(maxWidth: .infinity)
|
|
100
97
|
.frame(height: BOTTOM_TAB_BAR_HEIGHT)
|
|
101
|
-
.background(Colors.black01
|
|
98
|
+
.background(Colors.black01)
|
|
102
99
|
|
|
103
100
|
// Animated selection indicator.
|
|
104
101
|
RoundedRectangle(cornerRadius: 2)
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
|
|
11
11
|
import SwiftUI
|
|
12
12
|
|
|
13
|
-
@available(iOS 16.0, *)
|
|
14
13
|
public struct HeaderUserView: View {
|
|
15
14
|
private let data: HeaderUserData
|
|
16
15
|
|
|
@@ -20,11 +19,8 @@ public struct HeaderUserView: View {
|
|
|
20
19
|
|
|
21
20
|
private let avatarSize: CGFloat = Spacing.XXL // 32
|
|
22
21
|
|
|
23
|
-
@Environment(\.screenLayout) private var layout
|
|
24
|
-
|
|
25
|
-
// back button and toolkit share the bar unless the iPhone Duo side bar holds them
|
|
26
22
|
private var maxWidth: CGFloat {
|
|
27
|
-
|
|
23
|
+
UIScreen.main.bounds.width - 172
|
|
28
24
|
}
|
|
29
25
|
|
|
30
26
|
public var body: some View {
|
|
@@ -108,7 +104,6 @@ public struct HeaderUserView: View {
|
|
|
108
104
|
|
|
109
105
|
/// Mirrors Compose's `AvatarGroup`: lays out 1..4 avatars in a fixed collage,
|
|
110
106
|
/// falls back to initials when empty, and shows a "+n" overflow tile beyond 4.
|
|
111
|
-
@available(iOS 16.0, *)
|
|
112
107
|
private struct AvatarGroup: View {
|
|
113
108
|
let urls: [String]
|
|
114
109
|
let title: String
|
|
@@ -190,7 +185,6 @@ private struct AvatarGroup: View {
|
|
|
190
185
|
}
|
|
191
186
|
}
|
|
192
187
|
|
|
193
|
-
@available(iOS 16.0, *)
|
|
194
188
|
private struct InitialsAvatar: View {
|
|
195
189
|
let name: String
|
|
196
190
|
let size: CGFloat
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
// MoMoUIKits
|
|
4
4
|
//
|
|
5
5
|
// SwiftUI mirror of Compose's NavigationContainer.
|
|
6
|
-
// Wraps NavigationStack, injects Navigator into the environment, and renders
|
|
7
|
-
// every registered screen inside `
|
|
8
|
-
// behavior as Compose's StackScreen.
|
|
6
|
+
// Wraps NavigationStack (iOS 16+), injects Navigator into the environment, and renders
|
|
7
|
+
// every registered screen inside `StackScreen` chrome built from its options.
|
|
9
8
|
//
|
|
10
9
|
|
|
11
10
|
import SwiftUI
|
|
@@ -55,41 +54,23 @@ public struct NavigationContainer<Initial: View>: View {
|
|
|
55
54
|
}
|
|
56
55
|
|
|
57
56
|
public var body: some View {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
.environment(\.applicationEnvironment, applicationEnvironment)
|
|
76
|
-
// AI-GENERATED START: route Localize + font-scale injection through the observing modifiers
|
|
77
|
-
.momoLocalize(localize)
|
|
78
|
-
.momoFontScale(context: applicationEnvironment.applicationContext)
|
|
79
|
-
// AI-GENERATED END: route Localize + font-scale injection through the observing modifiers
|
|
80
|
-
}
|
|
81
|
-
.overlay {
|
|
82
|
-
if let item = navigator.overplay {
|
|
83
|
-
OverplayHost(item: item)
|
|
84
|
-
.environmentObject(navigator)
|
|
85
|
-
.environmentObject(applicationEnvironment)
|
|
86
|
-
.environment(\.applicationEnvironment, applicationEnvironment)
|
|
87
|
-
// AI-GENERATED START: route Localize + font-scale injection through the observing modifiers
|
|
88
|
-
.momoLocalize(localize)
|
|
89
|
-
.momoFontScale(context: applicationEnvironment.applicationContext)
|
|
90
|
-
// AI-GENERATED END: route Localize + font-scale injection through the observing modifiers
|
|
91
|
-
.momoScreenLayout()
|
|
92
|
-
}
|
|
57
|
+
withKitEnvironment(
|
|
58
|
+
stack(path: $navigator.path, root: navigator.rootRoute)
|
|
59
|
+
.overlay { overplayLayer(presentedId: nil) }
|
|
60
|
+
)
|
|
61
|
+
.fullScreenCover(item: Binding(
|
|
62
|
+
get: { navigator.coverRoute },
|
|
63
|
+
// A close that bypasses the navigator (e.g. `dismiss()`) still tears the flow down.
|
|
64
|
+
// SwiftUI also writes nil after a navigator-driven close; by then a new screen may
|
|
65
|
+
// already be queued, so only a cover that is still showing counts.
|
|
66
|
+
set: { if $0 == nil, navigator.coverRoute != nil { navigator.dismiss() } }
|
|
67
|
+
)) { route in
|
|
68
|
+
withKitEnvironment(
|
|
69
|
+
stack(path: $navigator.presentedPath, root: DynamicScreenRoute(id: route.id))
|
|
70
|
+
.overlay { overplayLayer(presentedId: route.id) }
|
|
71
|
+
)
|
|
72
|
+
.onAppear { navigator.coverDidAppear() }
|
|
73
|
+
.onDisappear { navigator.coverDidDisappear() }
|
|
93
74
|
}
|
|
94
75
|
.onAppear {
|
|
95
76
|
if navigator.composeApi == nil {
|
|
@@ -110,10 +91,23 @@ public struct NavigationContainer<Initial: View>: View {
|
|
|
110
91
|
// AI-GENERATED END: demote the MaxApi language pull to a seed of last resort
|
|
111
92
|
}
|
|
112
93
|
.onDisappear {
|
|
94
|
+
// Our own cover hides the stack without leaving it; host back must still reach it.
|
|
95
|
+
guard navigator.presented == nil else { return }
|
|
113
96
|
NavigatorManager.shared.remove(navigator)
|
|
114
97
|
}
|
|
115
98
|
}
|
|
116
99
|
|
|
100
|
+
private func stack(path: Binding<[DynamicScreenRoute]>, root: DynamicScreenRoute) -> some View {
|
|
101
|
+
NavigationStack(path: path) {
|
|
102
|
+
screenView(for: root)
|
|
103
|
+
// A reset swaps the root in place; without the id it would inherit the old root's state.
|
|
104
|
+
.id(root.id)
|
|
105
|
+
.navigationDestination(for: DynamicScreenRoute.self) { route in
|
|
106
|
+
screenView(for: route)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
117
111
|
@ViewBuilder
|
|
118
112
|
private func screenView(for route: DynamicScreenRoute) -> some View {
|
|
119
113
|
if let screen = navigator.registry.getScreen(id: route.id) {
|
|
@@ -124,14 +118,22 @@ public struct NavigationContainer<Initial: View>: View {
|
|
|
124
118
|
}
|
|
125
119
|
}
|
|
126
120
|
|
|
121
|
+
/// Each context draws only the overplays shown over it, so one never re-mounts elsewhere.
|
|
127
122
|
@ViewBuilder
|
|
128
|
-
private func
|
|
129
|
-
if let
|
|
130
|
-
|
|
131
|
-
} else {
|
|
132
|
-
EmptyView()
|
|
123
|
+
private func overplayLayer(presentedId: Int?) -> some View {
|
|
124
|
+
if let item = navigator.overplay, item.presentedId == presentedId {
|
|
125
|
+
OverplayHost(item: item)
|
|
133
126
|
}
|
|
134
127
|
}
|
|
128
|
+
|
|
129
|
+
private func withKitEnvironment<Content: View>(_ content: Content) -> some View {
|
|
130
|
+
content
|
|
131
|
+
.environmentObject(navigator)
|
|
132
|
+
.environmentObject(applicationEnvironment)
|
|
133
|
+
.environment(\.applicationEnvironment, applicationEnvironment)
|
|
134
|
+
.momoLocalize(localize)
|
|
135
|
+
.momoFontScale(context: applicationEnvironment.applicationContext)
|
|
136
|
+
}
|
|
135
137
|
}
|
|
136
138
|
|
|
137
139
|
// MARK: - ScreenHost
|
|
@@ -168,8 +170,6 @@ private struct ScreenHost: View {
|
|
|
168
170
|
screenName: screen.name,
|
|
169
171
|
content: { screen.content() }
|
|
170
172
|
)
|
|
171
|
-
// Per screen, not around the NavigationStack: a state-driven reader there stops path pushes rendering.
|
|
172
|
-
.momoScreenLayout()
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
|