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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/CLAUDE.md +5 -3
  2. package/ios/Application/MaxApi.swift +12 -3
  3. package/ios/Colors+Radius+Spacing/Radius.swift +6 -0
  4. package/ios/Layout/GridContext.swift +67 -3
  5. package/ios/Navigation/BottomSheet.swift +30 -4
  6. package/ios/Navigation/BottomTab/BottomTab.swift +0 -2
  7. package/ios/Navigation/Component/SnackBar.swift +0 -1
  8. package/ios/Navigation/HostBackGestureBridge.swift +27 -6
  9. package/ios/Navigation/ModalScreen.swift +0 -1
  10. package/ios/Navigation/Navigation.swift +14 -0
  11. package/ios/Navigation/NavigationContainer.swift +26 -56
  12. package/ios/Navigation/Navigator.swift +102 -165
  13. package/ios/Navigation/StackScreen.swift +19 -2
  14. package/ios/Navigation/StackTransition.swift +400 -0
  15. package/ios/Navigation/SwipeBackHaptic.swift +33 -0
  16. package/package.json +1 -1
  17. package/build.gradle.kts +0 -11
  18. package/compose/build.gradle.kts +0 -204
  19. package/compose/build.gradle.kts.backup +0 -204
  20. package/compose/compose.podspec +0 -47
  21. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +0 -19
  22. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +0 -6
  23. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +0 -12
  24. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +0 -7
  25. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +0 -153
  26. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +0 -6
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +0 -124
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +0 -916
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +0 -269
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +0 -56
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +0 -157
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +0 -81
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +0 -32
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +0 -338
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +0 -175
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +0 -357
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +0 -115
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -89
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +0 -128
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +0 -209
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +0 -542
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +0 -49
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +0 -86
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +0 -125
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +0 -231
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +0 -189
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +0 -478
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +0 -114
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +0 -265
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +0 -235
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +0 -223
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +0 -265
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +0 -185
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +0 -343
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +0 -108
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +0 -56
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +0 -41
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +0 -84
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +0 -40
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +0 -361
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +0 -108
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +0 -308
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +0 -59
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +0 -87
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +0 -21
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +0 -90
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +0 -330
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +0 -220
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +0 -473
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +0 -122
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +0 -207
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +0 -92
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +0 -560
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +0 -86
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +0 -125
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +0 -207
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +0 -501
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +0 -176
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +0 -182
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +0 -193
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +0 -29
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +0 -236
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +0 -180
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +0 -325
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +0 -20
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +0 -18
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +0 -22
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +0 -12
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +0 -16
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +0 -194
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +0 -283
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +0 -54
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +0 -50
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +0 -309
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +0 -41
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +0 -52
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +0 -66
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +0 -11
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +0 -104
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +0 -48
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +0 -51
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +0 -338
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +0 -58
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +0 -137
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +0 -106
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +0 -205
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +0 -393
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +0 -8
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +0 -713
  122. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +0 -148
  123. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +0 -35
  124. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +0 -218
  125. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +0 -231
  126. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +0 -86
  127. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +0 -98
  128. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +0 -312
  129. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +0 -169
  130. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +0 -447
  131. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +0 -103
  132. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +0 -106
  133. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +0 -74
  134. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +0 -379
  135. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +0 -39
  136. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +0 -363
  137. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +0 -192
  138. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +0 -133
  139. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +0 -127
  140. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +0 -12
  141. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +0 -91
  142. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +0 -9
  143. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +0 -74
  144. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +0 -13
  145. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +0 -1329
  146. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +0 -55
  147. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +0 -15
  148. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +0 -118
  149. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +0 -78
  150. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +0 -45
  151. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +0 -32
  152. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +0 -38
  153. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +0 -66
  154. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +0 -217
  155. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +0 -94
  156. package/gradle/libs.versions.toml +0 -69
  157. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  158. package/gradle/wrapper/gradle-wrapper.properties +0 -8
  159. package/gradle.properties +0 -26
  160. package/gradlew +0 -252
  161. package/gradlew.bat +0 -94
  162. package/ios-demo/README.md +0 -120
  163. package/settings.gradle.kts +0 -64
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.167.1-duo.18-debug",
3
+ "version": "0.167.1-duo.19",
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.18-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
- }