@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.
Files changed (189) hide show
  1. package/CLAUDE.md +17 -8
  2. package/ios/Application/ApplicationEnvironment.swift +6 -0
  3. package/ios/Application/Components.swift +1 -3
  4. package/ios/Application/HeaderRight.swift +19 -27
  5. package/ios/Application/Navigation/BottomTab/BottomTab.swift +9 -30
  6. package/ios/Application/Navigation/BottomTab/BottomTabBar.swift +2 -5
  7. package/ios/Application/Navigation/HeaderUser.swift +1 -7
  8. package/ios/Application/Navigation/NavigationContainer.swift +45 -45
  9. package/ios/Application/Navigation/Navigator.swift +122 -29
  10. package/ios/Application/Navigation/NavigatorManager.swift +13 -6
  11. package/ios/Application/Navigation/Overplay/BottomSheet.swift +4 -41
  12. package/ios/Application/Navigation/Overplay/ModalScreen.swift +1 -5
  13. package/ios/Application/Navigation/component/Header.swift +4 -12
  14. package/ios/Application/Navigation/component/HeaderBackground.swift +2 -3
  15. package/ios/Application/Navigation/component/NavigationFooter.swift +1 -1
  16. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +9 -61
  17. package/ios/Application/StackScreen.swift +18 -135
  18. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +59 -5
  19. package/ios/Colors+Radius+Spacing/Colors.swift +1 -1
  20. package/ios/Extensions/Color++.swift +20 -9
  21. package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
  22. package/ios/Icon/Icon.swift +14 -22
  23. package/ios/Icon/Icons.swift +1329 -0
  24. package/ios/Image/Image.swift +16 -5
  25. package/ios/Lottie/LottieView.swift +4 -2
  26. package/ios/Popup/PopupDisplay.swift +68 -84
  27. package/ios/Popup/PopupNotify.swift +32 -43
  28. package/ios/Popup/PopupPromotion.swift +6 -18
  29. package/ios/Resources/dot_loading.json +558 -0
  30. package/ios/Resources/lottie_circle_loader.json +1 -0
  31. package/ios/Template/TrustBanner/TrustBanner.swift +69 -31
  32. package/ios/native-kits.podspec +1 -1
  33. package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
  34. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +0 -1
  35. package/package.json +1 -1
  36. package/scripts/gen-ios-icons.mjs +36 -0
  37. package/.claude/scheduled_tasks.lock +0 -1
  38. package/.claude/settings.local.json +0 -185
  39. package/build.gradle.kts +0 -11
  40. package/compose/build.gradle.kts +0 -204
  41. package/compose/build.gradle.kts.backup +0 -204
  42. package/compose/compose.podspec +0 -47
  43. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +0 -19
  44. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +0 -6
  45. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +0 -12
  46. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +0 -7
  47. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +0 -153
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +0 -124
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +0 -831
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +0 -269
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +0 -56
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +0 -157
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +0 -82
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +0 -32
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +0 -338
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +0 -175
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +0 -356
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +0 -113
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -89
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +0 -128
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +0 -209
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +0 -542
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +0 -49
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +0 -76
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +0 -125
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +0 -231
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +0 -189
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +0 -475
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +0 -114
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +0 -266
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +0 -240
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +0 -223
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +0 -264
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +0 -185
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +0 -343
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +0 -108
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +0 -56
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +0 -41
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +0 -84
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +0 -40
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +0 -361
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +0 -108
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +0 -310
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +0 -59
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +0 -87
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +0 -21
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +0 -90
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +0 -323
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +0 -220
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +0 -473
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +0 -122
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +0 -199
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +0 -91
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +0 -552
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +0 -88
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +0 -125
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +0 -207
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +0 -498
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +0 -176
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +0 -182
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +0 -193
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +0 -29
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +0 -234
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +0 -188
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +0 -325
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +0 -16
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +0 -18
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +0 -22
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +0 -12
  122. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +0 -16
  123. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +0 -188
  124. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +0 -297
  125. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +0 -54
  126. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +0 -50
  127. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +0 -187
  128. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +0 -41
  129. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +0 -52
  130. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +0 -66
  131. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +0 -11
  132. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +0 -104
  133. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +0 -48
  134. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +0 -51
  135. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +0 -338
  136. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +0 -58
  137. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +0 -137
  138. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +0 -106
  139. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +0 -205
  140. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +0 -393
  141. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +0 -8
  142. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +0 -648
  143. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +0 -148
  144. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +0 -35
  145. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +0 -194
  146. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +0 -232
  147. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +0 -86
  148. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +0 -281
  149. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +0 -169
  150. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +0 -408
  151. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +0 -108
  152. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +0 -105
  153. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +0 -74
  154. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +0 -354
  155. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +0 -37
  156. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +0 -363
  157. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +0 -169
  158. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +0 -132
  159. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +0 -127
  160. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +0 -12
  161. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +0 -91
  162. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +0 -9
  163. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +0 -74
  164. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +0 -1329
  165. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +0 -55
  166. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +0 -15
  167. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +0 -118
  168. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +0 -40
  169. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +0 -45
  170. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +0 -32
  171. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +0 -38
  172. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +0 -66
  173. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +0 -217
  174. package/gradle/libs.versions.toml +0 -69
  175. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  176. package/gradle/wrapper/gradle-wrapper.properties +0 -8
  177. package/gradle.properties +0 -26
  178. package/gradlew +0 -252
  179. package/gradlew.bat +0 -94
  180. package/ios/Application/Layout/ScreenLayout.swift +0 -196
  181. package/ios/Application/Navigation/BottomTab/DuoTabBar.swift +0 -275
  182. package/ios/Application/Navigation/component/DuoScrollGradient.swift +0 -55
  183. package/ios/Application/Navigation/component/SideBar.swift +0 -155
  184. package/ios/Popup/PopupLayout.swift +0 -45
  185. package/ios-demo/README.md +0 -120
  186. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +0 -76
  187. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +0 -170
  188. package/local.properties +0 -8
  189. 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
- ## Screen layout / iPhone Duo (port of RN `useScreenLayout`, momo-kits !664)
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, raw top/bottom + side insets, `contentWidth`, header geometry, the iPhone Duo `sideBar` and `duoTopInset`. `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.
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 at 120pt when the inner display is landscape (height ≥ 600), else 170pt, or 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 / rail.
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
- if #available(iOS 16.0, *) {
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
- @EnvironmentObject private var environment: ApplicationEnvironment
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
- if showsHelpCenter(environment.applicationContext) {
253
- Icon(source: "help_center", size: 20, color: tintColor ?? Colors.black17)
254
- .padding(4)
255
- .onTapGesture {
256
- onPressHelpCenter()
257
- }
258
-
259
- Rectangle()
260
- .fill(tintColor ?? Colors.black20)
261
- .frame(width: 0.5, height: 12)
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
- // the Duo bars overlay the content or sit in the side strip, so only the home indicator is kept clear
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
- if layout.isIPhoneDuo {
53
- DuoTabBar(
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: selectTab
49
+ onTabSelected: { index in
50
+ if index != selectedIndex { selectedIndex = index }
51
+ }
58
52
  )
59
- } else {
60
- // Tab bar + home-indicator filler.
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
- @Environment(\.screenLayout) private var layout
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.ignoresSafeArea(edges: .horizontal))
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
- layout.sideBar != nil ? layout.contentWidth - Spacing.M * 2 : layout.width - 172
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 `Screen(options)` chrome — same auto-wrap
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
- NavigationStack(path: $navigator.path) {
59
- screenView(for: navigator.rootRoute)
60
- .navigationDestination(for: DynamicScreenRoute.self) { route in
61
- screenView(for: route)
62
- }
63
- }
64
- .environmentObject(navigator)
65
- .environmentObject(applicationEnvironment)
66
- .environment(\.applicationEnvironment, applicationEnvironment)
67
- // AI-GENERATED START: route Localize + font-scale injection through the observing modifiers
68
- .momoLocalize(localize)
69
- .momoFontScale(context: applicationEnvironment.applicationContext)
70
- // AI-GENERATED END: route Localize + font-scale injection through the observing modifiers
71
- .fullScreenCover(item: $navigator.presented) { route in
72
- screenView(forDialog: route)
73
- .environmentObject(navigator)
74
- .environmentObject(applicationEnvironment)
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 screenView(forDialog route: DynamicDialogRoute) -> some View {
129
- if let screen = navigator.registry.getScreen(id: route.id) {
130
- ScreenHost(screen: screen, navigator: navigator)
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