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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/CLAUDE.md +5 -3
  2. package/build.gradle.kts +11 -0
  3. package/compose/build.gradle.kts +204 -0
  4. package/compose/build.gradle.kts.backup +204 -0
  5. package/compose/compose.podspec +47 -0
  6. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +19 -0
  7. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +6 -0
  8. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +12 -0
  9. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +7 -0
  10. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +153 -0
  11. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +6 -0
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +52 -0
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +124 -0
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +183 -0
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +207 -0
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +39 -0
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +86 -0
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +76 -0
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +76 -0
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +293 -0
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +33 -0
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +916 -0
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +269 -0
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +110 -0
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +378 -0
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +56 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +69 -0
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +157 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +81 -0
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +32 -0
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +338 -0
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +175 -0
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +357 -0
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +115 -0
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +89 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +128 -0
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +209 -0
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +542 -0
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +49 -0
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +86 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +125 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +231 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +189 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +478 -0
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +114 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +265 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +235 -0
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +223 -0
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +265 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +185 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +343 -0
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +108 -0
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +56 -0
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +41 -0
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +84 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +40 -0
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +361 -0
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +108 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +308 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +59 -0
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +87 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +21 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +90 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +330 -0
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +220 -0
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +473 -0
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +122 -0
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +207 -0
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +92 -0
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +560 -0
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +86 -0
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +125 -0
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +207 -0
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +501 -0
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +176 -0
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +182 -0
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +193 -0
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +236 -0
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +180 -0
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +325 -0
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +20 -0
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +18 -0
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +22 -0
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +16 -0
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +194 -0
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +283 -0
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +54 -0
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +50 -0
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +309 -0
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +41 -0
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +52 -0
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +66 -0
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +104 -0
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +48 -0
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +338 -0
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +58 -0
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +137 -0
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +116 -0
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +205 -0
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +393 -0
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +8 -0
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +727 -0
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +148 -0
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +35 -0
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +218 -0
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +231 -0
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +98 -0
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +312 -0
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +169 -0
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +447 -0
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +103 -0
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +106 -0
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +74 -0
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +379 -0
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +39 -0
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +363 -0
  122. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +192 -0
  123. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +133 -0
  124. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +127 -0
  125. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +12 -0
  126. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +91 -0
  127. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +9 -0
  128. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +74 -0
  129. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +13 -0
  130. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
  131. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +55 -0
  132. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +15 -0
  133. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +118 -0
  134. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +78 -0
  135. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +45 -0
  136. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +32 -0
  137. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +38 -0
  138. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +66 -0
  139. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +217 -0
  140. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +94 -0
  141. package/gradle/libs.versions.toml +69 -0
  142. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  143. package/gradle/wrapper/gradle-wrapper.properties +8 -0
  144. package/gradle.properties +26 -0
  145. package/gradlew +252 -0
  146. package/gradlew.bat +94 -0
  147. package/ios/Application/MaxApi.swift +12 -3
  148. package/ios/Colors+Radius+Spacing/Radius.swift +6 -0
  149. package/ios/Layout/GridContext.swift +67 -3
  150. package/ios/Navigation/BottomSheet.swift +30 -4
  151. package/ios/Navigation/BottomTab/BottomTab.swift +0 -2
  152. package/ios/Navigation/Component/SnackBar.swift +0 -1
  153. package/ios/Navigation/HostBackGestureBridge.swift +27 -6
  154. package/ios/Navigation/ModalScreen.swift +0 -1
  155. package/ios/Navigation/Navigation.swift +14 -0
  156. package/ios/Navigation/NavigationContainer.swift +26 -56
  157. package/ios/Navigation/Navigator.swift +102 -165
  158. package/ios/Navigation/StackScreen.swift +19 -2
  159. package/ios/Navigation/StackTransition.swift +400 -0
  160. package/ios/Navigation/SwipeBackHaptic.swift +33 -0
  161. package/ios-demo/README.md +120 -0
  162. package/package.json +1 -1
  163. package/settings.gradle.kts +64 -0
@@ -0,0 +1,400 @@
1
+ //
2
+ // StackTransition.swift
3
+ // MoMoUIKits
4
+ //
5
+ // SwiftUI mirror of Compose's `navigation/StackTransition.kt` (a 1:1 port of the RN kit's
6
+ // `forStackTransitionWithOverlay`) and of the NavHost it decorates. Every stack entry lives in
7
+ // its own UIHostingController, kept while the entry is on the stack and taken out of the window
8
+ // while covered, as UINavigationController does: a covered screen keeps its state, stops
9
+ // rendering and gets onDisappear / onAppear. The motion itself is drawn in SwiftUI, because an
10
+ // `offset` never moves the safe area the way a UIKit transform would.
11
+ //
12
+
13
+ import SwiftUI
14
+ import UIKit
15
+
16
+ let STACK_TRANSITION_DURATION: TimeInterval = 0.3
17
+
18
+ private let PARALLAX_RATIO: CGFloat = 0.3
19
+ private let SCRIM_MAX_ALPHA: CGFloat = 0.6
20
+ private let EDGE_SHADOW_MAX_ALPHA: CGFloat = 0.3
21
+ private let EDGE_SHADOW_WIDTH: CGFloat = 8
22
+
23
+ // MARK: - Host
24
+
25
+ /// The stack view behind `NavigationContainer`. `screen` builds an entry's content once;
26
+ /// `decorate` re-applies the kit environment on every update.
27
+ struct StackHost: UIViewControllerRepresentable {
28
+ let navigator: Navigator
29
+ let entries: [StackEntry]
30
+ let screen: (StackEntry) -> AnyView
31
+ let decorate: (AnyView) -> AnyView
32
+
33
+ func makeUIViewController(context: Context) -> StackController {
34
+ let controller = StackController()
35
+ navigator.stackController = controller
36
+ return controller
37
+ }
38
+
39
+ func updateUIViewController(_ controller: StackController, context: Context) {
40
+ controller.update(
41
+ entries: entries,
42
+ environment: context.environment,
43
+ screen: screen,
44
+ decorate: decorate
45
+ )
46
+ }
47
+ }
48
+
49
+ // MARK: - Controller
50
+
51
+ final class StackController: UIViewController {
52
+ private final class Card {
53
+ let entry: StackEntry
54
+ let motion = CardMotion()
55
+ var host: UIHostingController<AnyView>!
56
+
57
+ init(entry: StackEntry) {
58
+ self.entry = entry
59
+ }
60
+ }
61
+
62
+ /// One push or pop between two cards. `coverage` is how far `top` covers `under`: 1 in place,
63
+ /// 0 gone. Push plays 0 → 1, pop 1 → 0; a host gesture sets it straight from the finger.
64
+ private final class Transition {
65
+ enum Kind { case slide, rise }
66
+
67
+ let kind: Kind
68
+ let top: Card
69
+ let under: Card
70
+ let isSeek: Bool
71
+ var coverage: CGFloat
72
+ var from: CGFloat = 0
73
+ var target: CGFloat = 0
74
+ var duration: TimeInterval = 0
75
+ var startTime: CFTimeInterval?
76
+ var isAnimating = false
77
+
78
+ init(kind: Kind, top: Card, under: Card, isSeek: Bool = false, coverage: CGFloat) {
79
+ self.kind = kind
80
+ self.top = top
81
+ self.under = under
82
+ self.isSeek = isSeek
83
+ self.coverage = coverage
84
+ }
85
+ }
86
+
87
+ private var entries: [StackEntry] = []
88
+ private var cards: [Int: Card] = [:]
89
+ private var transition: Transition?
90
+ private var displayLink: CADisplayLink?
91
+ private var environment = EnvironmentValues()
92
+ private var screen: (StackEntry) -> AnyView = { _ in AnyView(EmptyView()) }
93
+ private var decorate: (AnyView) -> AnyView = { $0 }
94
+
95
+ override func viewDidLoad() {
96
+ super.viewDidLoad()
97
+ view.backgroundColor = .clear
98
+ view.clipsToBounds = true
99
+ }
100
+
101
+ override func viewDidLayoutSubviews() {
102
+ super.viewDidLayoutSubviews()
103
+ for subview in view.subviews { subview.frame = view.bounds }
104
+ }
105
+
106
+ override func viewDidDisappear(_ animated: Bool) {
107
+ super.viewDidDisappear(animated)
108
+ settle()
109
+ }
110
+
111
+ func update(
112
+ entries new: [StackEntry],
113
+ environment: EnvironmentValues,
114
+ screen: @escaping (StackEntry) -> AnyView,
115
+ decorate: @escaping (AnyView) -> AnyView
116
+ ) {
117
+ self.environment = environment
118
+ self.screen = screen
119
+ self.decorate = decorate
120
+ for card in cards.values { card.host.rootView = rootView(for: card) }
121
+ guard new != entries else { return }
122
+
123
+ settle()
124
+ let old = entries
125
+ let oldTop = old.last.flatMap { cards[$0.id] }
126
+ entries = new
127
+
128
+ let live = Set(new.map(\.id))
129
+ for (id, card) in cards where !live.contains(id) {
130
+ cards[id] = nil
131
+ // the old top may still have to animate out
132
+ if card !== oldTop { tearDown(card) }
133
+ }
134
+ for entry in new where cards[entry.id] == nil {
135
+ cards[entry.id] = makeCard(entry)
136
+ }
137
+ guard let newTop = new.last.flatMap({ cards[$0.id] }) else { return }
138
+ guard let oldTop, oldTop !== newTop else {
139
+ if oldTop == nil { attach(newTop) }
140
+ return
141
+ }
142
+
143
+ let isPush = !old.contains(newTop.entry)
144
+ guard view.window != nil, isPush || !live.contains(oldTop.entry.id) else {
145
+ attach(newTop)
146
+ leave(oldTop)
147
+ return
148
+ }
149
+ if isPush {
150
+ let kind: Transition.Kind = newTop.entry.isDialog ? .rise : .slide
151
+ play(Transition(kind: kind, top: newTop, under: oldTop, coverage: 0), to: 1)
152
+ } else {
153
+ // closing a presented flow slides it down whole, whatever was pushed inside it
154
+ let closesPresented = old.drop(while: { $0 != newTop.entry }).dropFirst().contains(where: \.isDialog)
155
+ play(Transition(kind: closesPresented ? .rise : .slide, top: oldTop, under: newTop, coverage: 1), to: 0)
156
+ }
157
+ }
158
+
159
+ // MARK: Host back gesture
160
+
161
+ func beginBackSeek() -> Bool {
162
+ settle()
163
+ guard view.window != nil, entries.count > 1,
164
+ let top = cards[entries[entries.count - 1].id], !top.entry.isDialog,
165
+ let under = cards[entries[entries.count - 2].id] else { return false }
166
+ let transition = Transition(kind: .slide, top: top, under: under, isSeek: true, coverage: 1)
167
+ self.transition = transition
168
+ view.isUserInteractionEnabled = false
169
+ attach(under, below: top)
170
+ apply(transition)
171
+ return true
172
+ }
173
+
174
+ func seekBack(to progress: CGFloat) {
175
+ guard let transition, transition.isSeek, !transition.isAnimating else { return }
176
+ transition.coverage = 1 - progress
177
+ apply(transition)
178
+ }
179
+
180
+ /// Commits the pop here; the navigator drops the same entry right after.
181
+ func finishBackSeek() -> Bool {
182
+ guard let transition, transition.isSeek, !transition.isAnimating else { return false }
183
+ entries.removeLast()
184
+ cards[transition.top.entry.id] = nil
185
+ animate(transition, to: 0)
186
+ return true
187
+ }
188
+
189
+ func cancelBackSeek() {
190
+ guard let transition, transition.isSeek, !transition.isAnimating else { return }
191
+ animate(transition, to: 1)
192
+ }
193
+
194
+ // MARK: Transitions
195
+
196
+ private func play(_ transition: Transition, to target: CGFloat) {
197
+ self.transition = transition
198
+ view.isUserInteractionEnabled = false
199
+ if transition.top.host.view.superview !== view { attach(transition.top) }
200
+ attach(transition.under, below: transition.top)
201
+ apply(transition)
202
+ animate(transition, to: target)
203
+ }
204
+
205
+ /// The rest of the way plays at full-transition pace, so a released drag keeps its speed.
206
+ private func animate(_ transition: Transition, to target: CGFloat) {
207
+ transition.from = transition.coverage
208
+ transition.target = target
209
+ transition.duration = STACK_TRANSITION_DURATION * Double(abs(target - transition.coverage))
210
+ transition.startTime = nil
211
+ transition.isAnimating = true
212
+ guard transition.duration > 0 else {
213
+ finish(transition)
214
+ return
215
+ }
216
+ startDisplayLink()
217
+ }
218
+
219
+ @objc private func tick(_ link: CADisplayLink) {
220
+ guard let transition, transition.isAnimating else {
221
+ stopDisplayLink()
222
+ return
223
+ }
224
+ // the clock starts on the first frame, so a heavy first layout doesn't eat the motion
225
+ let start = transition.startTime ?? link.targetTimestamp
226
+ transition.startTime = start
227
+ let fraction = min(max((link.targetTimestamp - start) / transition.duration, 0), 1)
228
+ // linear for the stack: the gesture maps the finger straight onto this timeline
229
+ let eased = transition.kind == .rise ? fastOutSlowIn(CGFloat(fraction)) : CGFloat(fraction)
230
+ transition.coverage = fraction >= 1
231
+ ? transition.target
232
+ : transition.from + (transition.target - transition.from) * eased
233
+ apply(transition)
234
+ if fraction >= 1 { finish(transition) }
235
+ }
236
+
237
+ /// Jumps a running transition to its end; an unreleased drag snaps back.
238
+ private func settle() {
239
+ guard let transition else { return }
240
+ if transition.isSeek, !transition.isAnimating { transition.target = 1 }
241
+ transition.coverage = transition.target
242
+ apply(transition)
243
+ finish(transition)
244
+ }
245
+
246
+ private func finish(_ transition: Transition) {
247
+ transition.isAnimating = false
248
+ if self.transition === transition { self.transition = nil }
249
+ stopDisplayLink()
250
+ view.isUserInteractionEnabled = true
251
+ transition.top.motion.state = CardMotion.State()
252
+ transition.under.motion.state = CardMotion.State()
253
+ leave(transition.target >= 1 ? transition.under : transition.top)
254
+ }
255
+
256
+ private func apply(_ transition: Transition) {
257
+ let size = view.bounds.size
258
+ let coverage = transition.coverage
259
+ switch transition.kind {
260
+ case .slide:
261
+ transition.top.motion.state = CardMotion.State(
262
+ offset: CGSize(width: (1 - coverage) * size.width, height: 0),
263
+ shadow: EDGE_SHADOW_MAX_ALPHA * coverage
264
+ )
265
+ transition.under.motion.state = CardMotion.State(
266
+ offset: CGSize(width: -PARALLAX_RATIO * size.width * coverage, height: 0),
267
+ scrim: SCRIM_MAX_ALPHA * coverage
268
+ )
269
+ case .rise:
270
+ transition.top.motion.state = CardMotion.State(offset: CGSize(width: 0, height: (1 - coverage) * size.height))
271
+ }
272
+ }
273
+
274
+ private func startDisplayLink() {
275
+ guard displayLink == nil else { return }
276
+ let link = CADisplayLink(target: self, selector: #selector(tick))
277
+ link.preferredFrameRateRange = CAFrameRateRange(minimum: 60, maximum: 120, preferred: 120)
278
+ link.add(to: .main, forMode: .common)
279
+ displayLink = link
280
+ }
281
+
282
+ private func stopDisplayLink() {
283
+ displayLink?.invalidate()
284
+ displayLink = nil
285
+ }
286
+
287
+ // MARK: Cards
288
+
289
+ private func makeCard(_ entry: StackEntry) -> Card {
290
+ let card = Card(entry: entry)
291
+ let host = UIHostingController(rootView: rootView(for: card))
292
+ host.view.backgroundColor = .clear
293
+ addChild(host)
294
+ host.didMove(toParent: self)
295
+ card.host = host
296
+ return card
297
+ }
298
+
299
+ /// The card's own environment is the container's (host environment objects included), with
300
+ /// the kit's re-applied on top; the screen itself is built once and never re-run from here.
301
+ private func rootView(for card: Card) -> AnyView {
302
+ let entry = card.entry
303
+ let screen = screen
304
+ let content = StackCard(motion: card.motion) {
305
+ StackCardContent(id: entry.id) { screen(entry) }.equatable()
306
+ }
307
+ return AnyView(decorate(AnyView(content)).environment(\.self, environment))
308
+ }
309
+
310
+ private func attach(_ card: Card, below other: Card? = nil) {
311
+ let hostView: UIView = card.host.view
312
+ hostView.frame = view.bounds
313
+ if let other, other.host.view.superview === view {
314
+ view.insertSubview(hostView, belowSubview: other.host.view)
315
+ } else {
316
+ view.addSubview(hostView)
317
+ }
318
+ }
319
+
320
+ /// Off the window while covered, released once off the stack.
321
+ private func leave(_ card: Card) {
322
+ if cards[card.entry.id] === card {
323
+ card.host.view.removeFromSuperview()
324
+ } else {
325
+ tearDown(card)
326
+ }
327
+ }
328
+
329
+ private func tearDown(_ card: Card) {
330
+ card.host.willMove(toParent: nil)
331
+ card.host.view.removeFromSuperview()
332
+ card.host.removeFromParent()
333
+ }
334
+ }
335
+
336
+ // MARK: - Card
337
+
338
+ final class CardMotion: ObservableObject {
339
+ struct State: Equatable {
340
+ var offset: CGSize = .zero
341
+ var scrim: CGFloat = 0
342
+ var shadow: CGFloat = 0
343
+ }
344
+
345
+ @Published var state = State()
346
+ }
347
+
348
+ /// Compose's `StackTransitionDecor`: the covered-screen scrim and the top card's left-edge shadow.
349
+ private struct StackCard<Content: View>: View {
350
+ @ObservedObject var motion: CardMotion
351
+ let content: Content
352
+
353
+ init(motion: CardMotion, @ViewBuilder content: () -> Content) {
354
+ self.motion = motion
355
+ self.content = content()
356
+ }
357
+
358
+ var body: some View {
359
+ let state = motion.state
360
+ content
361
+ .overlay {
362
+ Colors.pink09.opacity(state.scrim)
363
+ .ignoresSafeArea()
364
+ .allowsHitTesting(false)
365
+ }
366
+ .background(alignment: .leading) {
367
+ LinearGradient(colors: [.clear, Colors.black20], startPoint: .leading, endPoint: .trailing)
368
+ .frame(width: EDGE_SHADOW_WIDTH)
369
+ .offset(x: -EDGE_SHADOW_WIDTH)
370
+ .opacity(state.shadow)
371
+ .ignoresSafeArea()
372
+ .allowsHitTesting(false)
373
+ }
374
+ .offset(state.offset)
375
+ }
376
+ }
377
+
378
+ /// Equal by entry, so a motion frame or a root refresh never re-runs the screen body.
379
+ private struct StackCardContent: View, Equatable {
380
+ let id: Int
381
+ let build: () -> AnyView
382
+
383
+ static func == (lhs: Self, rhs: Self) -> Bool { lhs.id == rhs.id }
384
+
385
+ var body: some View { build() }
386
+ }
387
+
388
+ /// Compose's `FastOutSlowInEasing`, cubic-bezier(0.4, 0, 0.2, 1).
389
+ private func fastOutSlowIn(_ t: CGFloat) -> CGFloat {
390
+ func bezier(_ s: CGFloat, _ p1: CGFloat, _ p2: CGFloat) -> CGFloat {
391
+ 3 * (1 - s) * (1 - s) * s * p1 + 3 * (1 - s) * s * s * p2 + s * s * s
392
+ }
393
+ var low: CGFloat = 0
394
+ var high: CGFloat = 1
395
+ for _ in 0..<20 {
396
+ let mid = (low + high) / 2
397
+ if bezier(mid, 0.4, 0.2) < t { low = mid } else { high = mid }
398
+ }
399
+ return bezier((low + high) / 2, 0, 1)
400
+ }
@@ -0,0 +1,33 @@
1
+ //
2
+ // SwipeBackHaptic.swift
3
+ // MoMoUIKits
4
+ //
5
+ // SwiftUI mirror of Compose's `navigation/SwipeBackHaptic.kt`: one light tick when the host back
6
+ // gesture has dragged the screen 50pt, re-armed once it goes back under.
7
+ //
8
+
9
+ import UIKit
10
+
11
+ struct SwipeBackHaptic {
12
+ // Unscaled, so the tick lands at the same physical distance on every device.
13
+ private static let threshold: CGFloat = 50
14
+
15
+ private var fired = false
16
+
17
+ mutating func update(travelled: CGFloat, maxApi: MaxApi?) {
18
+ if travelled >= Self.threshold, !fired {
19
+ fired = true
20
+ if let maxApi {
21
+ maxApi.triggerEventVibration("light")
22
+ } else {
23
+ UIImpactFeedbackGenerator(style: .light).impactOccurred()
24
+ }
25
+ } else if travelled < Self.threshold {
26
+ fired = false
27
+ }
28
+ }
29
+
30
+ mutating func reset() {
31
+ fired = false
32
+ }
33
+ }
@@ -0,0 +1,120 @@
1
+ # MoMoUIKitsDemo
2
+
3
+ SwiftUI demo browser for `MoMoUIKits`.
4
+
5
+ The package can optionally host a Compose UIKits screen by receiving a
6
+ `UIViewController` factory from the host app. It does not import the KMP
7
+ `shared` module directly.
8
+
9
+ SwiftUI mode reuses the full demo screen set from
10
+ `sample/iosApp/iosApp/Demo`, including the sample `HomeView` registry and demo
11
+ screens for all currently available components.
12
+
13
+ ## CocoaPods
14
+
15
+ In the host app `Podfile`, include both local pods:
16
+
17
+ ```ruby
18
+ pod 'MoMoUIKits', :path => '../momo-native-kits/ios/native-kits.podspec'
19
+ pod 'MoMoUIKitsDemo', :path => '../momo-native-kits/ios-demo'
20
+ ```
21
+
22
+ Adjust the relative paths to match the host app checkout.
23
+
24
+ ## Host App Usage
25
+
26
+ ```swift
27
+ import MoMoUIKitsDemo
28
+ import shared
29
+
30
+ class UIKitsViewController: BaseComposeAppViewController {
31
+ override func getViewController() -> UIViewController? {
32
+ let params = self.application.data as? [String: Any]
33
+
34
+ return MoMoUIKitsDemoViewControllerFactory.make(
35
+ context: MoMoUIKitsDemoContext(
36
+ appId: params?["appId"] as? String ?? "",
37
+ appCode: params?["code"] as? String ?? "",
38
+ appName: params?["name"] as? String,
39
+ appIcon: params?["icon"] as? String ?? ""
40
+ ),
41
+ composeViewController: {
42
+ UIKitsScreenComposeViewControllerKt.UIKitsScreenComposeViewController(
43
+ params: params
44
+ )
45
+ }
46
+ )
47
+ }
48
+
49
+ override func viewDidDisappear(_ animated: Bool) {
50
+ super.viewDidDisappear(animated)
51
+ composeViewController = nil
52
+ }
53
+ }
54
+ ```
55
+
56
+ If the host does not pass `composeViewController`, the demo opens directly in
57
+ SwiftUI mode.
58
+
59
+ ## Included SwiftUI Demos
60
+
61
+ The pod includes the sample demo screens for:
62
+
63
+ - Animated Header
64
+ - Avatar
65
+ - Badge
66
+ - Baseline View
67
+ - Bottom Tab
68
+ - Button
69
+ - Carousel
70
+ - Checkbox
71
+ - Chip
72
+ - Collapse
73
+ - DateTimePicker
74
+ - Divider
75
+ - Header User
76
+ - Icon Button
77
+ - Image
78
+ - Information
79
+ - Input
80
+ - Input DropDown
81
+ - Input Money
82
+ - Input OTP
83
+ - Input Phone Number
84
+ - Input Search
85
+ - Input TextArea
86
+ - Loader
87
+ - Navigation
88
+ - Pagination
89
+ - Popup Display
90
+ - Popup Notify
91
+ - Popup Promotion
92
+ - Progress Info
93
+ - Radio
94
+ - Rating
95
+ - Skeleton
96
+ - Slider
97
+ - SnackBar
98
+ - Stepper
99
+ - Steps
100
+ - Suggest Action
101
+ - Swipe
102
+ - Switch
103
+ - TabView
104
+ - Tag
105
+ - Theme
106
+ - Title
107
+ - Tooltip
108
+ - Typography
109
+ - Uploader
110
+
111
+ ## Validation Notes
112
+
113
+ Validated locally with:
114
+
115
+ ```sh
116
+ pod ipc spec ios/MoMoUIKits.podspec
117
+ pod ipc spec ios-demo/MoMoUIKitsDemo.podspec
118
+ pod lib lint ios/MoMoUIKits.podspec --allow-warnings --skip-import-validation
119
+ pod lib lint ios-demo/MoMoUIKitsDemo.podspec --allow-warnings --skip-import-validation --include-podspecs=ios/MoMoUIKits.podspec
120
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.167.1-duo.18",
3
+ "version": "0.167.1-duo.19-debug",
4
4
  "private": false,
5
5
  "dependeNavigationncies": {},
6
6
  "devDependencies": {},
@@ -0,0 +1,64 @@
1
+ import org.gradle.kotlin.dsl.maven
2
+
3
+ rootProject.name = "momo-native-kits"
4
+ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
5
+
6
+ pluginManagement {
7
+ repositories {
8
+ google {
9
+ mavenContent {
10
+ includeGroupAndSubgroups("androidx")
11
+ includeGroupAndSubgroups("com.android")
12
+ includeGroupAndSubgroups("com.google")
13
+ }
14
+ }
15
+ mavenCentral()
16
+ gradlePluginPortal()
17
+ maven {
18
+ url = uri("https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven")
19
+ credentials {username = "download_packages"; password = "gldt-bjDqLpU_sPcHDuXau2ws" }
20
+ }
21
+ maven {
22
+ url = uri("https://artifacts.mservice.com.vn/repository/maven-momo-app/")
23
+ credentials {
24
+ username = "viewer"
25
+ password = "viewer"
26
+ }
27
+ isAllowInsecureProtocol = true
28
+ }
29
+ }
30
+ }
31
+
32
+ dependencyResolutionManagement {
33
+ repositories {
34
+ google {
35
+ mavenContent {
36
+ includeGroupAndSubgroups("androidx")
37
+ includeGroupAndSubgroups("com.android")
38
+ includeGroupAndSubgroups("com.google")
39
+ }
40
+ }
41
+ mavenCentral()
42
+ maven { url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") }
43
+ maven {
44
+ url = uri("https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven")
45
+ credentials {
46
+ username = "download_packages";
47
+ password = "gldt-bjDqLpU_sPcHDuXau2ws"
48
+ }
49
+ }
50
+ maven {
51
+ url = uri("https://artifacts.mservice.com.vn/repository/maven-momo-app/")
52
+ credentials {
53
+ username = "viewer"
54
+ password = "viewer"
55
+ }
56
+ isAllowInsecureProtocol = true
57
+ }
58
+ }
59
+ }
60
+
61
+ include(":compose")
62
+
63
+ include(":sample:androidApp")
64
+ include(":sample:shared")