@momo-kits/native-kits 0.167.1-duo.17-debug → 0.167.1-duo.18

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 (154) hide show
  1. package/CLAUDE.md +1 -0
  2. package/ios/Layout/ScreenLayout.swift +49 -3
  3. package/ios/Navigation/Component/Header.swift +48 -11
  4. package/ios/Navigation/Component/HeaderRight.swift +34 -28
  5. package/ios/Navigation/Component/SideBar.swift +15 -8
  6. package/ios/Navigation/StackScreen.swift +13 -12
  7. package/package.json +1 -1
  8. package/build.gradle.kts +0 -11
  9. package/compose/build.gradle.kts +0 -204
  10. package/compose/build.gradle.kts.backup +0 -204
  11. package/compose/compose.podspec +0 -47
  12. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +0 -19
  13. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +0 -6
  14. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +0 -12
  15. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +0 -7
  16. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +0 -153
  17. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +0 -6
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +0 -124
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +0 -916
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +0 -269
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +0 -56
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +0 -157
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +0 -81
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +0 -32
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +0 -338
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +0 -175
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +0 -357
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +0 -115
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -89
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +0 -128
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +0 -209
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +0 -542
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +0 -49
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +0 -86
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +0 -125
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +0 -231
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +0 -189
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +0 -478
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +0 -114
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +0 -265
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +0 -235
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +0 -223
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +0 -265
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +0 -185
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +0 -343
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +0 -108
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +0 -56
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +0 -41
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +0 -84
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +0 -40
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +0 -361
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +0 -108
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +0 -308
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +0 -59
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +0 -87
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +0 -21
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +0 -90
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +0 -330
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +0 -220
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +0 -473
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +0 -122
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +0 -207
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +0 -92
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +0 -560
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +0 -86
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +0 -125
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +0 -207
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +0 -501
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +0 -176
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +0 -182
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +0 -193
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +0 -29
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +0 -236
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +0 -180
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +0 -325
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +0 -20
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +0 -18
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +0 -22
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +0 -12
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +0 -16
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +0 -194
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +0 -283
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +0 -54
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +0 -50
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +0 -260
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +0 -41
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +0 -52
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +0 -66
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +0 -11
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +0 -104
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +0 -48
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +0 -51
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +0 -338
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +0 -58
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +0 -137
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +0 -106
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +0 -205
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +0 -393
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +0 -8
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +0 -708
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +0 -148
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +0 -35
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +0 -218
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +0 -231
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +0 -86
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +0 -98
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +0 -312
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +0 -169
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +0 -408
  122. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +0 -103
  123. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +0 -106
  124. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +0 -74
  125. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +0 -366
  126. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +0 -39
  127. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +0 -363
  128. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +0 -185
  129. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +0 -133
  130. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +0 -127
  131. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +0 -12
  132. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +0 -91
  133. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +0 -9
  134. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +0 -74
  135. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +0 -13
  136. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +0 -1329
  137. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +0 -55
  138. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +0 -15
  139. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +0 -118
  140. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +0 -78
  141. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +0 -45
  142. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +0 -32
  143. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +0 -38
  144. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +0 -66
  145. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +0 -217
  146. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +0 -94
  147. package/gradle/libs.versions.toml +0 -69
  148. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  149. package/gradle/wrapper/gradle-wrapper.properties +0 -8
  150. package/gradle.properties +0 -26
  151. package/gradlew +0 -252
  152. package/gradlew.bat +0 -94
  153. package/ios-demo/README.md +0 -120
  154. package/settings.gradle.kts +0 -64
package/CLAUDE.md CHANGED
@@ -69,6 +69,7 @@ Publishing is automated (`publish.sh` + GitLab CI on `[ci build]` / `main`). Do
69
69
  - `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.
70
70
  - 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.
71
71
  - Header height is `headerHeight` (not `AppStatusBar + HEADER_HEIGHT`); the bar row is `headerBarHeight` and starts `headerStatusBarHeight` below the window top. When the status bar is off the top edge (iPhone landscape, iPhone Duo) `headerStatusBarHeight` is 0 so the title stays centered — except on the Duo outer display: in portrait the header is 76pt and the row starts at 14pt so the title centers on the camera punchout; in landscape the row centers on the camera when it sits at the top end of the strip (else on the back button) and the header reaches 8pt below the back button.
72
+ - iPhone Duo inner display in portrait (status bar on top): the header row shares the status bar band — `headerHeight` 94pt (2 × the clock digits' y 47), `headerStatusBarHeight` 0 — and `ScreenLayout.statusBarClearance` keeps it between the clock / battery clusters (from reserved regions, else 128pt on the right). Back and toolkit use the 48pt side bar controls laid out horizontally (`ToolkitLayout.SideBarRow`); the collapsed search and a centered title stay clear of the clusters. Port of momo-kits `90af33ba2`.
72
73
  - 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. `sideBar.top` is 170pt in portrait, 120pt on the inner display in landscape (height ≥ 600), 24pt on the outer display in landscape, or below the header when the status bar is on top; UIKit's `statusBarFrame` is useless on Duo. Unlike RN the column is clamped above footer (24pt gap) / rail (12pt gap).
73
74
  - Reserved regions (iOS 27.1 `reservedRegionsOfKind:`, reached through the ObjC runtime so older SDKs still build): a probe `UIView` (`platform/ReservedRegions.ios.kt` on the Compose root view; `ReservedRegionsProbe` inside SwiftUI `momoScreenLayout()`) re-reads them in `layoutSubviews`, which UIKit re-runs when they change. They push `sideBar.top` below the status cluster as it grows for calls / Live Activities, and `sideBar.bottom` (≥ 24pt) above a camera at the bottom end of the strip.
74
75
  - Tab bar on Duo is `bottomtab/DuoTabBar.kt`: a rail in the side strip (`sideBar.bottom` above the window 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.
@@ -22,6 +22,12 @@ public struct SideBarLayout: Equatable {
22
22
  public let bottom: CGFloat
23
23
  }
24
24
 
25
+ /// Room the header row keeps from each side for the status bar clock and battery clusters.
26
+ public struct StatusBarClearance: Equatable {
27
+ public let left: CGFloat
28
+ public let right: CGFloat
29
+ }
30
+
25
31
  public struct ScreenLayout: Equatable {
26
32
  public let width: CGFloat
27
33
  public let height: CGFloat
@@ -35,6 +41,8 @@ public struct ScreenLayout: Equatable {
35
41
  public let headerStatusBarHeight: CGFloat
36
42
  /// iPhone Duo moves back and toolkit into a column in the side safe area; nil while they stay in the bar.
37
43
  public let sideBar: SideBarLayout?
44
+ /// iPhone Duo inner display in portrait: the header row shares the status bar band, between its clusters.
45
+ public let statusBarClearance: StatusBarClearance?
38
46
 
39
47
  public var headerBarHeight: CGFloat { headerHeight - headerStatusBarHeight }
40
48
  }
@@ -56,6 +64,10 @@ enum DuoMetrics {
56
64
  // Outer display in landscape has no status bar in the strip: controls keep this far from
57
65
  // both ends unless a reserved region (the camera at either end) asks for more.
58
66
  static let edgeInset: CGFloat = 24
67
+ // Inner display in portrait: the clock and connectivity cluster sits at the top right corner
68
+ // (x 550–643 of 669, clock digits centered on y 47); its reserved region is taller than the glyphs.
69
+ static let innerStatusBarCenterY: CGFloat = 47
70
+ static let innerStatusBarWidth: CGFloat = 120
59
71
  // Widest short side of any single-display iPhone.
60
72
  static let iPhoneMaxShortSide: CGFloat = 440
61
73
  }
@@ -102,10 +114,21 @@ extension ScreenLayout {
102
114
  return ScreenLayout(
103
115
  width: width, height: height, isIPhoneDuo: false, insets: insets,
104
116
  insetLeft: 0, insetRight: 0, contentWidth: width,
105
- headerHeight: headerHeight, headerStatusBarHeight: headerStatusBarHeight, sideBar: nil
117
+ headerHeight: headerHeight, headerStatusBarHeight: headerStatusBarHeight, sideBar: nil,
118
+ statusBarClearance: nil
106
119
  )
107
120
  }
108
121
 
122
+ var statusBarClearance: StatusBarClearance?
123
+ let isInnerPortrait = height > width && width >= DuoMetrics.innerMinHeight
124
+ if isInnerPortrait && insets.top >= HeaderMetrics.statusBarMinInset {
125
+ statusBarClearance = statusBarClusters(reservedRegions, windowWidth: width, topInset: insets.top)
126
+ ?? StatusBarClearance(left: 0, right: DuoMetrics.innerStatusBarWidth + Spacing.S)
127
+ // centered on the clock digits; the cluster's region center sits above them
128
+ headerHeight = DuoMetrics.innerStatusBarCenterY * 2
129
+ headerStatusBarHeight = 0
130
+ }
131
+
109
132
  var sideBar: SideBarLayout?
110
133
  let sideInset = max(insets.leading, insets.trailing)
111
134
  // narrower strips cannot host a control 12pt from the inner edge
@@ -116,7 +139,9 @@ extension ScreenLayout {
116
139
  // Activities; region frames already include the system's margin
117
140
  let occlusion = stripOcclusion(reservedRegions, stripLeft: stripLeft, stripRight: stripLeft + sideInset, windowHeight: height)
118
141
 
119
- var top = insets.top + HeaderMetrics.barHeight + Spacing.M
142
+ var top = statusBarClearance != nil
143
+ ? headerHeight + Spacing.M
144
+ : insets.top + HeaderMetrics.barHeight + Spacing.M
120
145
  if insets.top < HeaderMetrics.statusBarMinInset {
121
146
  // status bar lives in the strip: start under its cluster, as the native bar does
122
147
  if width <= height {
@@ -150,7 +175,8 @@ extension ScreenLayout {
150
175
  width: width, height: height, isIPhoneDuo: true, insets: insets,
151
176
  insetLeft: insets.leading, insetRight: insets.trailing,
152
177
  contentWidth: width - insets.leading - insets.trailing,
153
- headerHeight: headerHeight, headerStatusBarHeight: headerStatusBarHeight, sideBar: sideBar
178
+ headerHeight: headerHeight, headerStatusBarHeight: headerStatusBarHeight, sideBar: sideBar,
179
+ statusBarClearance: statusBarClearance
154
180
  )
155
181
  }
156
182
 
@@ -192,6 +218,26 @@ extension ScreenLayout {
192
218
  }
193
219
  }
194
220
 
221
+ /// How far the clock and battery clusters of a status bar on the top edge reach in from each
222
+ /// side; nil without reserved regions there.
223
+ private static func statusBarClusters(_ regions: [CGRect], windowWidth: CGFloat, topInset: CGFloat) -> StatusBarClearance? {
224
+ let midX = windowWidth / 2
225
+ let clusters = regions.filter {
226
+ $0.minY < topInset && $0.maxY <= topInset && ($0.maxX <= midX || $0.minX >= midX)
227
+ }
228
+ guard !clusters.isEmpty else { return nil }
229
+ var left: CGFloat = 0
230
+ var right: CGFloat = 0
231
+ for region in clusters {
232
+ if region.minX < midX {
233
+ left = max(left, region.maxX)
234
+ } else {
235
+ right = max(right, windowWidth - region.minX)
236
+ }
237
+ }
238
+ return StatusBarClearance(left: left, right: right)
239
+ }
240
+
195
241
  // Slab iPhones report equal side insets in landscape; iPhone Duo moves the status bar to one side.
196
242
  private static func isIPhoneDuoWindow(width: CGFloat, height: CGFloat, insets: EdgeInsets) -> Bool {
197
243
  let shortSide = min(width, height)
@@ -13,6 +13,8 @@ public let HEADER_HEIGHT: CGFloat = 52
13
13
 
14
14
  /// Room the back button and toolkit take from the bar (Compose `HEADER_ACTIONS_WIDTH`).
15
15
  let headerActionsWidth: CGFloat = 172
16
+ // Extra width the 48pt back and toolkit take on the iPhone Duo status bar row.
17
+ private let statusBarRowActionsExtra: CGFloat = 55
16
18
 
17
19
  public enum TitlePosition {
18
20
  case left, center
@@ -282,7 +284,9 @@ func headerSearchProps(_ props: InputSearchProps, scroll: CGFloat, layout: Scree
282
284
 
283
285
  /// Max title width: back button and toolkit share the bar unless the iPhone Duo side bar holds them.
284
286
  func headerTitleMaxWidth(_ layout: ScreenLayout) -> CGFloat {
285
- layout.sideBar != nil ? layout.contentWidth - Spacing.M * 2 : layout.width - headerActionsWidth
287
+ let clearance = layout.statusBarClearance.map { $0.left + $0.right } ?? 0
288
+ if layout.sideBar != nil { return layout.contentWidth - Spacing.M * 2 - clearance }
289
+ return layout.width - headerActionsWidth - (layout.statusBarClearance != nil ? statusBarRowActionsExtra : 0) - clearance
286
290
  }
287
291
 
288
292
  // MARK: - Header
@@ -333,10 +337,22 @@ public struct NavigationHeader: View {
333
337
 
334
338
  @Environment(\.screenLayout) private var layout
335
339
  @Environment(\.appTheme) private var theme
340
+ @State private var headerRightWidth: CGFloat = 0
336
341
 
337
342
  /// iPhone Duo side bar holds back and the toolkit; a custom header right stays in the bar.
338
343
  private var useSideBar: Bool { layout.sideBar != nil }
339
344
 
345
+ /// iPhone Duo inner portrait: the row sits on the status bar, between its clusters, with 48pt controls.
346
+ private var statusBarRow: Bool { layout.statusBarClearance != nil && !useSideBar }
347
+
348
+ /// Max centered title width; on the status bar row the clusters are lopsided, so both sides keep the wider one's room.
349
+ private var centerTitleMaxWidth: CGFloat {
350
+ guard let clearance = layout.statusBarClearance else { return (layout.contentWidth - Spacing.M * 2) / 2 }
351
+ let back = goBack == nil || useSideBar ? 0 : DuoMetrics.controlSize + Spacing.S
352
+ let inset = max(clearance.left + back, clearance.right + headerRightWidth) + Spacing.S
353
+ return max(layout.contentWidth - (Spacing.M + inset) * 2, 0)
354
+ }
355
+
340
356
  private var headerColor: HeaderColor {
341
357
  getHeaderColor(
342
358
  isAnimated: animatedHeader != nil,
@@ -357,7 +373,16 @@ public struct NavigationHeader: View {
357
373
  ZStack {
358
374
  HStack(spacing: 0) {
359
375
  if let goBack, !useSideBar {
360
- BackButton(color: headerColor, onBack: goBack)
376
+ if statusBarRow {
377
+ SideBarButton(
378
+ icon: "arrow-back",
379
+ headerColor: headerColor,
380
+ accessibilityId: "btn_navigation_back",
381
+ action: goBack
382
+ )
383
+ } else {
384
+ BackButton(color: headerColor, onBack: goBack)
385
+ }
361
386
  Spacer().frame(width: Spacing.S)
362
387
  }
363
388
 
@@ -369,24 +394,36 @@ public struct NavigationHeader: View {
369
394
  }
370
395
 
371
396
  if let headerRight, !(useSideBar && toolkitHeaderRight(headerRight) != nil) {
372
- AnyView(headerRight())
373
- .environment(\.headerColor, headerColor)
374
- .background(
375
- GeometryReader { geo in
376
- Color.clear.preference(key: HeaderRightWidthKey.self, value: geo.size.width)
377
- }
378
- )
397
+ Group {
398
+ if statusBarRow, let toolkit = toolkitHeaderRight(headerRight) {
399
+ Toolkit(
400
+ headerRight: toolkit.data ?? HeaderRightData(),
401
+ tintColor: toolkit.tintColor,
402
+ layout: .sideBarRow
403
+ )
404
+ } else {
405
+ AnyView(headerRight())
406
+ }
407
+ }
408
+ .environment(\.headerColor, headerColor)
409
+ .background(
410
+ GeometryReader { geo in
411
+ Color.clear.preference(key: HeaderRightWidthKey.self, value: geo.size.width)
412
+ }
413
+ )
379
414
  }
380
415
  }
381
- .padding(.horizontal, Spacing.M)
416
+ .padding(.leading, Spacing.M + (layout.statusBarClearance?.left ?? 0))
417
+ .padding(.trailing, Spacing.M + (layout.statusBarClearance?.right ?? 0))
382
418
 
383
419
  if centerTitle {
384
420
  titleArea(centered: true)
385
421
  // Compose caps a centered title at half the bar
386
- .frame(maxWidth: (layout.contentWidth - Spacing.M * 2) / 2)
422
+ .frame(maxWidth: centerTitleMaxWidth)
387
423
  .padding(.horizontal, Spacing.M)
388
424
  }
389
425
  }
426
+ .onPreferenceChange(HeaderRightWidthKey.self) { headerRightWidth = $0 }
390
427
  .frame(height: layout.headerBarHeight)
391
428
  .overlay(alignment: .bottom) { VerticalShadow(opacity: opacity) }
392
429
  // the row starts headerStatusBarHeight below the window top: 0 with the status bar off
@@ -158,11 +158,15 @@ private struct NavigationButtonConfig {
158
158
  let onPress: () -> Void
159
159
  }
160
160
 
161
+ /// `sideBarColumn`: the iPhone Duo side bar; `sideBarRow`: the Duo header row on the status bar, same 48pt controls.
162
+ enum ToolkitLayout {
163
+ case bar, sideBarColumn, sideBarRow
164
+ }
165
+
161
166
  struct Toolkit: View {
162
167
  let headerRight: HeaderRightData
163
168
  var tintColor: Color?
164
- /// iPhone Duo side bar column instead of the bar row.
165
- var vertical: Bool = false
169
+ var layout: ToolkitLayout = .bar
166
170
 
167
171
  @State private var isFavorite = false
168
172
  @State private var isLoading = false
@@ -276,44 +280,46 @@ struct Toolkit: View {
276
280
 
277
281
  var body: some View {
278
282
  Group {
279
- if vertical {
280
- verticalBody
281
- } else {
283
+ switch layout {
284
+ case .bar:
282
285
  horizontalBody
286
+ case .sideBarColumn:
287
+ VStack(spacing: Spacing.M) { sideBarControls(horizontal: false) }
288
+ case .sideBarRow:
289
+ HStack(spacing: Spacing.M) { sideBarControls(horizontal: true) }
283
290
  }
284
291
  }
285
292
  .onAppear(perform: loadFavoriteState)
286
293
  }
287
294
 
288
- private var verticalBody: some View {
295
+ @ViewBuilder
296
+ private func sideBarControls(horizontal: Bool) -> some View {
289
297
  let config = navigationButtonConfig
290
298
  let useHelpCenter = usesHelpCenter(context)
291
- return VStack(spacing: Spacing.M) {
292
- if showsShortcut(headerRight, context) {
293
- SideBarButton(
294
- icon: config.icon,
295
- headerColor: headerColor,
296
- showBadge: headerRight.showBadge,
297
- disabled: isLoading,
298
- action: config.onPress
299
- )
300
- }
301
- SideBarGroup(headerColor: headerColor, itemCount: useHelpCenter ? 2 : 1) {
302
- if useHelpCenter {
303
- SideBarGroupItem(
304
- icon: "help_center",
305
- headerColor: headerColor,
306
- accessibilityId: "btn_navigation_help_center",
307
- action: onPressHelpCenter
308
- )
309
- }
299
+ if showsShortcut(headerRight, context) {
300
+ SideBarButton(
301
+ icon: config.icon,
302
+ headerColor: headerColor,
303
+ showBadge: headerRight.showBadge,
304
+ disabled: isLoading,
305
+ action: config.onPress
306
+ )
307
+ }
308
+ SideBarGroup(headerColor: headerColor, itemCount: useHelpCenter ? 2 : 1, horizontal: horizontal) {
309
+ if useHelpCenter {
310
310
  SideBarGroupItem(
311
- icon: headerRight.closeIcon,
311
+ icon: "help_center",
312
312
  headerColor: headerColor,
313
- accessibilityId: "btn_navigation_close",
314
- action: onPressClose
313
+ accessibilityId: "btn_navigation_help_center",
314
+ action: onPressHelpCenter
315
315
  )
316
316
  }
317
+ SideBarGroupItem(
318
+ icon: headerRight.closeIcon,
319
+ headerColor: headerColor,
320
+ accessibilityId: "btn_navigation_close",
321
+ action: onPressClose
322
+ )
317
323
  }
318
324
  }
319
325
 
@@ -70,7 +70,7 @@ struct HeaderSideBar: View {
70
70
  Toolkit(
71
71
  headerRight: toolkit.data ?? HeaderRightData(),
72
72
  tintColor: toolkit.tintColor,
73
- vertical: true
73
+ layout: .sideBarColumn
74
74
  )
75
75
  .environment(\.headerColor, headerColor)
76
76
  }
@@ -142,19 +142,26 @@ struct SideBarButton: View {
142
142
  }
143
143
  }
144
144
 
145
- /// 48pt wide glass capsule grouping related controls, one item per 50pt.
145
+ /// 48pt glass capsule grouping related controls, one item per 50pt: vertical in the side bar,
146
+ /// horizontal on a header row.
146
147
  struct SideBarGroup<Content: View>: View {
147
148
  let headerColor: HeaderColor
148
149
  let itemCount: Int
150
+ var horizontal: Bool = false
149
151
  @ViewBuilder let content: () -> Content
150
152
 
151
153
  var body: some View {
152
- VStack(spacing: 0) { content() }
153
- .frame(
154
- width: DuoMetrics.controlSize,
155
- height: itemCount > 1 ? DuoMetrics.itemPitch * CGFloat(itemCount) : DuoMetrics.controlSize
156
- )
157
- .modifier(SideBarGlass(headerColor: headerColor, shape: RoundedRectangle(cornerRadius: Radius.XL)))
154
+ let length = itemCount > 1 ? DuoMetrics.itemPitch * CGFloat(itemCount) : DuoMetrics.controlSize
155
+ Group {
156
+ if horizontal {
157
+ HStack(spacing: 0) { content() }
158
+ .frame(width: length, height: DuoMetrics.controlSize)
159
+ } else {
160
+ VStack(spacing: 0) { content() }
161
+ .frame(width: DuoMetrics.controlSize, height: length)
162
+ }
163
+ }
164
+ .modifier(SideBarGlass(headerColor: headerColor, shape: RoundedRectangle(cornerRadius: Radius.XL)))
158
165
  }
159
166
  }
160
167
 
@@ -618,24 +618,25 @@ private struct SearchAnimatedHeader: View {
618
618
  let minTop = rowTop + (layout.headerBarHeight - HEADER_HEIGHT) / 2
619
619
  let maxTop = layout.headerHeight - layout.insets.top
620
620
  let minSide = Spacing.M
621
- let maxStart: CGFloat = hiddenBack || useSideBar ? Spacing.M : 48
622
- let maxEnd: CGFloat = useSideBar && hasToolkit
621
+ // collapsed on the iPhone Duo status bar row, the search stays between its clusters (48pt back there)
622
+ let clearance = layout.statusBarClearance
623
+ let backWidth: CGFloat = clearance != nil && !useSideBar ? DuoMetrics.controlSize : 28
624
+ let maxStart = (clearance?.left ?? 0) + (hiddenBack || useSideBar ? Spacing.M : backWidth + Spacing.M + Spacing.S)
625
+ let maxEnd = (clearance?.right ?? 0) + (useSideBar && hasToolkit
623
626
  ? Spacing.M
624
- : headerRightWidth + (hasHeaderRight ? Spacing.M * 2 : Spacing.M)
627
+ : headerRightWidth + (hasHeaderRight ? Spacing.M * 2 : Spacing.M))
625
628
 
626
629
  let top = min(max(maxTop - scroll, minTop), maxTop)
627
630
  let start = min(max(minSide + scroll, minSide), maxStart)
628
631
  let end = min(max(minSide + scroll, minSide), maxEnd)
629
632
 
630
- VStack(spacing: 0) {
631
- Spacer().frame(height: top)
632
- InputSearch(props: headerSearchProps(props, scroll: scroll, layout: layout, theme: theme))
633
- .frame(height: HEADER_HEIGHT)
634
- .padding(.leading, start)
635
- .padding(.trailing, end)
636
- Spacer(minLength: 0)
637
- }
638
- .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
633
+ // padding, not a Spacer: on the iPhone Duo status bar row the collapsed top is above the safe area
634
+ InputSearch(props: headerSearchProps(props, scroll: scroll, layout: layout, theme: theme))
635
+ .frame(height: HEADER_HEIGHT)
636
+ .padding(.leading, start)
637
+ .padding(.trailing, end)
638
+ .padding(.top, top)
639
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
639
640
  }
640
641
  }
641
642
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.167.1-duo.17-debug",
3
+ "version": "0.167.1-duo.18",
4
4
  "private": false,
5
5
  "dependeNavigationncies": {},
6
6
  "devDependencies": {},
package/build.gradle.kts DELETED
@@ -1,11 +0,0 @@
1
- plugins {
2
- // this is necessary to avoid the plugins to be loaded multiple times
3
- // in each subproject's classloader
4
- alias(libs.plugins.android.application) apply false
5
- alias(libs.plugins.android.library) apply false
6
- alias(libs.plugins.compose) apply false
7
- alias(libs.plugins.kotlin.compose.compiler) apply false
8
- alias(libs.plugins.jetbrains.kotlin.multiplatform) apply false
9
- alias(libs.plugins.jetbrains.kotlin.serialization) apply false
10
- alias(libs.plugins.vanniktech.mavenPublish) apply false
11
- }
@@ -1,204 +0,0 @@
1
- // import com.vanniktech.maven.publish.JavadocJar
2
- // import com.vanniktech.maven.publish.KotlinMultiplatform
3
- import org.jetbrains.kotlin.gradle.dsl.JvmTarget
4
-
5
- plugins {
6
- alias(libs.plugins.jetbrains.kotlin.multiplatform)
7
- alias(libs.plugins.android.library)
8
- alias(libs.plugins.compose)
9
- alias(libs.plugins.kotlin.compose.compiler)
10
- // alias(libs.plugins.vanniktech.mavenPublish)
11
- id(libs.plugins.jetbrains.kotlin.cocoapods.get().pluginId)
12
- kotlin("plugin.serialization")
13
- }
14
-
15
- val gitlabName: String = findProperty("name") as String? ?: ""
16
- val gitlabRepo: String = findProperty("repo") as String? ?: ""
17
- val gitlabUrl: String = findProperty("url") as String? ?: ""
18
- val gitlabGroup: String = findProperty("group") as String? ?: ""
19
- val gitlabArtifactId: String = findProperty("artifact.id") as String? ?: ""
20
- val gitlabVersion: String = findProperty("version") as String? ?: ""
21
- val gitlabUser: String = findProperty("gitlab.user") as String? ?: ""
22
- val gitlabKey: String = findProperty("gitlab.password") as String? ?: ""
23
-
24
- kotlin {
25
- androidTarget {
26
- compilerOptions {
27
- jvmTarget.set(JvmTarget.JVM_17)
28
- }
29
- publishLibraryVariants("release")
30
- }
31
-
32
- val fwName = gitlabArtifactId
33
- val iosTargets = listOf(iosArm64(), iosSimulatorArm64())
34
- iosTargets.forEach {
35
- it.binaries.framework {
36
- baseName = fwName
37
- binaryOption("bundleId", "$gitlabGroup.$fwName")
38
- isStatic = true
39
- }
40
- }
41
-
42
- cocoapods {
43
- version = "0.167.1-duo.17-debug"
44
- summary = "IOS Shared module"
45
- homepage = "https://momo.vn"
46
- ios.deploymentTarget = "15.0"
47
-
48
- framework {
49
- baseName = fwName
50
- isStatic = true
51
- }
52
-
53
- pod("lottie-ios") {
54
- moduleName = "Lottie"
55
- version = "4.4.3"
56
- extraOpts += listOf("-compiler-option", "-fmodules")
57
- }
58
- }
59
-
60
- sourceSets {
61
- all {
62
- languageSettings.optIn("vn.momo.kits.modifier.InternalApi")
63
- }
64
- commonMain.dependencies {
65
- implementation(compose.runtime)
66
- implementation(compose.material)
67
- implementation(compose.material3)
68
- implementation(compose.components.resources)
69
- implementation(libs.compose.ui.backhandler)
70
- implementation(libs.ktor.client.core)
71
- implementation(libs.jetbrains.coroutines.core)
72
- implementation(libs.navigation.multiplatform)
73
- implementation(libs.coil.multiplatform.compose)
74
- implementation(libs.coil.multiplatform.core)
75
- implementation(libs.coil.multiplatform.network.ktor)
76
- implementation(libs.jetbrains.serialization.json)
77
- implementation(libs.kotlinx.datetime)
78
- api(libs.native.max.api)
79
- implementation(libs.compottie)
80
- implementation(libs.gif.image)
81
- }
82
- androidMain.dependencies {
83
- implementation(libs.ktor.client.okhttp)
84
- implementation(libs.airbnb.lottie)
85
- }
86
- iosMain.dependencies {
87
- implementation(libs.ktor.client.darwin)
88
- }
89
- }
90
- }
91
-
92
- android {
93
- namespace = "vn.momo.kits.kits"
94
- compileSdk = 35
95
- compileOptions {
96
- sourceCompatibility = JavaVersion.VERSION_17
97
- targetCompatibility = JavaVersion.VERSION_17
98
- }
99
- defaultConfig {
100
- minSdk = 24
101
- }
102
- }
103
-
104
- dependencies {
105
- }
106
-
107
- repositories {
108
- google {
109
- mavenContent {
110
- includeGroupAndSubgroups("androidx")
111
- includeGroupAndSubgroups("com.android")
112
- includeGroupAndSubgroups("com.google")
113
- }
114
- }
115
- mavenCentral()
116
- maven { url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") }
117
- maven {
118
- url = uri("https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven")
119
- credentials {username = "download_packages"; password = "gldt-bjDqLpU_sPcHDuXau2ws" }
120
- }
121
- maven {
122
- url = uri("https://artifacts.mservice.com.vn/repository/maven-momo-app/")
123
- credentials {
124
- username = "viewer"
125
- password = "viewer"
126
- }
127
- isAllowInsecureProtocol = true
128
- }
129
- }
130
-
131
- compose {
132
- resources {
133
- publicResClass = true
134
- packageOfResClass = "vn.momo.uikits.resources"
135
- generateResClass = always
136
- }
137
- }
138
-
139
- // publishing {
140
- // repositories {
141
- // maven {
142
- // name = "${gitlabRepo}Packages"
143
- // credentials {
144
- // username = gitlabUser
145
- // password = gitlabKey
146
- // }
147
- // url = uri("$gitlabUrl")
148
- // }
149
- // }
150
- // }
151
-
152
- // mavenPublishing {
153
- // configure(
154
- // KotlinMultiplatform(
155
- // javadocJar = JavadocJar.Empty(),
156
- // sourcesJar = true,
157
- // androidVariantsToPublish = listOf("release")
158
- // )
159
- // )
160
- //
161
- // repositories {
162
- // maven {
163
- // name = "${gitlabRepo}Packages"
164
- // credentials { username = gitlabUser; password = gitlabKey }
165
- // url = uri("$gitlabUrl")
166
- // }
167
- // }
168
- //
169
- // coordinates(gitlabGroup.toString(), gitlabArtifactId, gitlabVersion.toString())
170
- //
171
- // pom {
172
- // name = "$gitlabName Klib"
173
- // description = "$gitlabName"
174
- // url = "https://gitlab.mservice.com.vn"
175
- // licenses {
176
- // license {
177
- // name = "The Apache License, Version 2.0"
178
- // url = "https://www.apache.org/licenses/LICENSE-2.0.txt"
179
- // distribution = "https://www.apache.org/licenses/LICENSE-2.0.txt"
180
- // }
181
- // }
182
- // developers {
183
- // developer {
184
- // id = "kotlin-hands-on"
185
- // name = "Kotlin Developer Advocate"
186
- // url = "https://github.com/kotlin-hands-on/"
187
- // }
188
- // }
189
- // }
190
- // }
191
-
192
- // Xcode 27 rejects pods below iOS 15 (lottie-ios 4.4.3 declares 13.0), and the synthetic Podfile has no post_install hook.
193
- val syntheticPodsProject = layout.buildDirectory.file("cocoapods/synthetic/ios/Pods/Pods.xcodeproj/project.pbxproj")
194
- tasks.matching { it.name.startsWith("podInstallSynthetic") }.configureEach {
195
- doLast {
196
- val pbxproj = syntheticPodsProject.get().asFile
197
- if (!pbxproj.exists()) return@doLast
198
- val target = Regex("IPHONEOS_DEPLOYMENT_TARGET = ([0-9.]+);")
199
- pbxproj.writeText(pbxproj.readText().replace(target) { match ->
200
- val major = match.groupValues[1].substringBefore('.').toIntOrNull() ?: 0
201
- if (major < 15) "IPHONEOS_DEPLOYMENT_TARGET = 15.0;" else match.value
202
- })
203
- }
204
- }