@momo-kits/native-kits 0.152.4-beta.3 → 0.152.5

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 (155) hide show
  1. package/package.json +7 -6
  2. package/shared/build.gradle.kts +74 -0
  3. package/CODE_OF_CONDUCT.md +0 -133
  4. package/CONTRIBUTING.md +0 -114
  5. package/LICENSE +0 -20
  6. package/README.md +0 -7
  7. package/build.gradle.kts +0 -32
  8. package/compose/MoMoComposeKits.podspec +0 -54
  9. package/compose/build.gradle.kts +0 -149
  10. package/compose/src/androidMain/AndroidManifest.xml +0 -2
  11. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +0 -105
  12. package/compose/src/commonMain/composeResources/files/lottie_circle_loader.json +0 -1
  13. package/compose/src/commonMain/composeResources/font/momosignature.otf +0 -0
  14. package/compose/src/commonMain/composeResources/font/momotrustdisplay.otf +0 -0
  15. package/compose/src/commonMain/composeResources/font/sfprotext_black.otf +0 -0
  16. package/compose/src/commonMain/composeResources/font/sfprotext_black.ttf +0 -0
  17. package/compose/src/commonMain/composeResources/font/sfprotext_bold.ttf +0 -0
  18. package/compose/src/commonMain/composeResources/font/sfprotext_heavy.ttf +0 -0
  19. package/compose/src/commonMain/composeResources/font/sfprotext_light.ttf +0 -0
  20. package/compose/src/commonMain/composeResources/font/sfprotext_medium.ttf +0 -0
  21. package/compose/src/commonMain/composeResources/font/sfprotext_regular.ttf +0 -0
  22. package/compose/src/commonMain/composeResources/font/sfprotext_semibold.ttf +0 -0
  23. package/compose/src/commonMain/composeResources/font/sfprotext_thin.otf +0 -0
  24. package/compose/src/commonMain/composeResources/font/sfprotext_thin.ttf +0 -0
  25. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.otf +0 -0
  26. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.ttf +0 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -57
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -201
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -222
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -48
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -306
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +0 -715
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -214
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -236
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +0 -77
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +0 -27
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +0 -334
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +0 -345
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -90
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +0 -131
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +0 -543
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +0 -23
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +0 -69
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +0 -143
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +0 -179
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +0 -111
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +0 -384
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +0 -160
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +0 -234
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +0 -223
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +0 -232
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +0 -236
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +0 -228
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +0 -364
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +0 -50
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +0 -34
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +0 -85
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +0 -33
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +0 -338
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +0 -95
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +0 -64
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +0 -89
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +0 -91
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +0 -86
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +0 -84
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +0 -208
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +0 -172
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +0 -199
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +0 -29
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +0 -237
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +0 -191
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +0 -306
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +0 -12
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +0 -13
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +0 -191
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +0 -258
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +0 -2
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +0 -35
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +0 -2
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +0 -59
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +0 -68
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +0 -11
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +0 -49
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +0 -51
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +0 -232
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +0 -111
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +0 -94
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +0 -159
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +0 -232
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScaleSizeScope.kt +0 -17
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +0 -459
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +0 -169
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +0 -216
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +0 -86
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +0 -180
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +0 -251
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +0 -80
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +0 -306
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +0 -31
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +0 -385
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +0 -38
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +0 -1329
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +0 -62
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +0 -88
  110. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +0 -144
  111. package/gradle.properties +0 -19
  112. package/gradlew +0 -240
  113. package/gradlew.bat +0 -91
  114. package/ios/Application/ApplicationEnvironment.swift +0 -50
  115. package/ios/Application/Components.swift +0 -263
  116. package/ios/Application/ComposeApi.swift +0 -22
  117. package/ios/Application/FloatingButton.swift +0 -172
  118. package/ios/Application/HeaderRight.swift +0 -271
  119. package/ios/Application/Screen.swift +0 -249
  120. package/ios/Badge/BadgeDot.swift +0 -31
  121. package/ios/Button/Button.swift +0 -211
  122. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +0 -126
  123. package/ios/Checkbox/Checkbox.swift +0 -81
  124. package/ios/Chip/Chip.swift +0 -96
  125. package/ios/Colors+Radius+Spacing/Colors.swift +0 -172
  126. package/ios/Colors+Radius+Spacing/Radius.swift +0 -22
  127. package/ios/Colors+Radius+Spacing/Spacing.swift +0 -12
  128. package/ios/Extensions/Color++.swift +0 -25
  129. package/ios/Icon/Icon.swift +0 -51
  130. package/ios/Image/Image.swift +0 -70
  131. package/ios/Input/Input.swift +0 -207
  132. package/ios/Input/InputPhoneNumber.swift +0 -176
  133. package/ios/Input/InputSearch.swift +0 -238
  134. package/ios/Input/InputTextArea.swift +0 -242
  135. package/ios/Lottie/LottieView.swift +0 -86
  136. package/ios/OTPKeyboard/KeyboardButton.swift +0 -41
  137. package/ios/OTPKeyboard/OTPKeyboard.swift +0 -145
  138. package/ios/Popup/PopupDisplay.swift +0 -284
  139. package/ios/Popup/PopupInput.swift +0 -96
  140. package/ios/Popup/PopupPromotion.swift +0 -73
  141. package/ios/PopupView/FullscreenPopup.swift +0 -251
  142. package/ios/PopupView/Modifiers.swift +0 -158
  143. package/ios/PopupView/PopupView.swift +0 -289
  144. package/ios/PopupView/Utils++.swift +0 -281
  145. package/ios/ScrollIndicator/ScrollIndicator.swift +0 -110
  146. package/ios/Swipeable/SwipeCell.swift +0 -278
  147. package/ios/Swipeable/SwipeCellModel.swift +0 -86
  148. package/ios/Switch/Switch.swift +0 -44
  149. package/ios/Template/Logo/Logo.swift +0 -75
  150. package/ios/Template/TrustBanner/TrustBanner.swift +0 -120
  151. package/ios/Theme.md +0 -18
  152. package/ios/Typography/Text.swift +0 -140
  153. package/ios/Typography/Typography.swift +0 -95
  154. package/ios/native-kits.podspec +0 -18
  155. package/settings.gradle.kts +0 -25
@@ -1,281 +0,0 @@
1
- import SwiftUI
2
- import Combine
3
-
4
- final class DispatchWorkHolder {
5
- var work: DispatchWorkItem?
6
- }
7
-
8
- final class ClassReference<T> {
9
- var value: T
10
-
11
- init(_ value: T) {
12
- self.value = value
13
- }
14
- }
15
-
16
- public extension View {
17
-
18
- @ViewBuilder
19
- func valueChanged<T: Equatable>(value: T, onChange: @escaping (T) -> Void) -> some View {
20
- if #available(iOS 14.0, tvOS 14.0, macOS 11.0, watchOS 7.0, *) {
21
- self.onChange(of: value, perform: onChange)
22
- } else {
23
- self.onReceive(Just(value)) { value in
24
- onChange(value)
25
- }
26
- }
27
- }
28
- }
29
-
30
- public extension View {
31
- @ViewBuilder
32
- func applyIf<T: View>(_ condition: Bool, apply: (Self) -> T) -> some View {
33
- if condition {
34
- apply(self)
35
- } else {
36
- self
37
- }
38
- }
39
-
40
- @ViewBuilder
41
- func addTapIf(if condition: Bool, onTap: @escaping ()->()) -> some View {
42
- if condition {
43
- self.simultaneousGesture(
44
- TapGesture().onEnded {
45
- onTap()
46
- }
47
- )
48
- } else {
49
- self
50
- }
51
- }
52
- }
53
-
54
- struct FrameGetter: ViewModifier {
55
-
56
- @Binding var frame: CGRect
57
-
58
- func body(content: Content) -> some View {
59
- content
60
- .background(
61
- GeometryReader { proxy -> AnyView in
62
- DispatchQueue.main.async {
63
- let rect = proxy.frame(in: .global)
64
- // This avoids an infinite layout loop
65
- if rect.integral != self.frame.integral {
66
- self.frame = rect
67
- }
68
- }
69
- return AnyView(EmptyView())
70
- }
71
- )
72
- }
73
- }
74
-
75
- extension View {
76
- public func frameGetter(_ frame: Binding<CGRect>) -> some View {
77
- modifier(FrameGetter(frame: frame))
78
- }
79
- }
80
-
81
- struct AnimationCompletionObserverModifier<Value>: AnimatableModifier where Value: VectorArithmetic, Value: Comparable {
82
-
83
- /// While animating, SwiftUI changes the old input value to the new target value using this property. This value is set to the old value until the animation completes.
84
- var animatableData: Value {
85
- didSet {
86
- notifyCompletionIfFinished()
87
- }
88
- }
89
-
90
- /// The target value for which we're observing. This value is directly set once the animation starts. During animation, `animatableData` will hold the oldValue and is only updated to the target value once the animation completes.
91
- private var targetValue: Value
92
-
93
- /// The completion callback which is called once the animation completes.
94
- private var completion: () -> Void
95
-
96
- init(observedValue: Value, completion: @escaping () -> Void) {
97
- self.completion = completion
98
- self.animatableData = observedValue
99
- targetValue = observedValue
100
- }
101
-
102
- /// Verifies whether the current animation is finished and calls the completion callback if true.
103
- private func notifyCompletionIfFinished() {
104
- guard animatableData == targetValue else { return }
105
-
106
- /// Dispatching is needed to take the next runloop for the completion callback.
107
- /// This prevents errors like "Modifying state during view update, this will cause undefined behavior."
108
- DispatchQueue.main.async {
109
- self.completion()
110
- }
111
- }
112
-
113
- func body(content: Content) -> some View {
114
- /// We're not really modifying the view so we can directly return the original input value.
115
- return content
116
- }
117
- }
118
-
119
- struct AnimatableModifierDouble: AnimatableModifier {
120
-
121
- var targetValue: Double
122
- static var done = false
123
-
124
- // SwiftUI gradually varies it from old value to the new value
125
- var animatableData: Double {
126
- didSet {
127
- checkIfFinished()
128
- }
129
- }
130
- var completion: () -> ()
131
-
132
- // Re-created every time the control argument changes
133
- init(bindedValue: Double, completion: @escaping () -> ()) {
134
- self.completion = completion
135
-
136
- // Set animatableData to the new value. But SwiftUI again directly
137
- // and gradually varies the value while the body
138
- // is being called to animate. Following line serves the purpose of
139
- // associating the extenal argument with the animatableData.
140
- self.animatableData = bindedValue
141
- targetValue = bindedValue
142
- AnimatableModifierDouble.done = false
143
- }
144
-
145
- func checkIfFinished() -> () {
146
- if AnimatableModifierDouble.done { return }
147
- let delta = 0.1
148
- if animatableData > targetValue - delta &&
149
- animatableData < targetValue + delta {
150
- //print("check", animatableData, targetValue)
151
- AnimatableModifierDouble.done = true
152
- DispatchQueue.main.async {
153
- self.completion()
154
- }
155
- }
156
- }
157
-
158
- func body(content: Content) -> some View {
159
- content
160
- }
161
- }
162
-
163
- extension View {
164
-
165
- func onAnimationCompleted(for value: Double, completion: @escaping () -> Void) -> some View {
166
- modifier(AnimatableModifierDouble(bindedValue: value, completion: completion))
167
- }
168
- }
169
-
170
- extension UIApplication {
171
- var keyWindow: UIWindow? {
172
- connectedScenes
173
- .compactMap {
174
- $0 as? UIWindowScene
175
- }
176
- .flatMap {
177
- $0.windows
178
- }
179
- .first {
180
- $0.isKeyWindow
181
- }
182
- }
183
- }
184
-
185
- private struct SafeAreaInsetsKey: EnvironmentKey {
186
- static var defaultValue: EdgeInsets {
187
- UIApplication.shared.keyWindow?.safeAreaInsets.swiftUiInsets ?? EdgeInsets()
188
- }
189
- }
190
-
191
- extension EnvironmentValues {
192
- var safeAreaInsets: EdgeInsets {
193
- self[SafeAreaInsetsKey.self]
194
- }
195
- }
196
-
197
- private extension UIEdgeInsets {
198
- var swiftUiInsets: EdgeInsets {
199
- EdgeInsets(top: top, leading: left, bottom: bottom, trailing: right)
200
- }
201
- }
202
-
203
- extension View {
204
-
205
- func transparentNonAnimatingFullScreenCover<Content: View>(isPresented: Binding<Bool>, content: @escaping () -> Content) -> some View {
206
- modifier(TransparentNonAnimatableFullScreenModifier(isPresented: isPresented, fullScreenContent: content))
207
- }
208
- }
209
-
210
- private struct TransparentNonAnimatableFullScreenModifier<FullScreenContent: View>: ViewModifier {
211
-
212
- @Binding var isPresented: Bool
213
- let fullScreenContent: () -> (FullScreenContent)
214
-
215
- func body(content: Content) -> some View {
216
- if #available(iOS 14.0, *) {
217
- content
218
- .valueChanged(value: isPresented, onChange: { isPresented in
219
- UIView.setAnimationsEnabled(false)
220
- })
221
- .fullScreenCover(isPresented: $isPresented, content: {
222
- ZStack {
223
- fullScreenContent()
224
- }
225
- .background(FullScreenCoverBackgroundRemovalView())
226
- .onAppear {
227
- if !UIView.areAnimationsEnabled {
228
- UIView.setAnimationsEnabled(true)
229
- }
230
- }
231
- .onDisappear {
232
- if !UIView.areAnimationsEnabled {
233
- UIView.setAnimationsEnabled(true)
234
- }
235
- }
236
- })
237
- } else {
238
- content
239
- .valueChanged(value: isPresented, onChange: { isPresented in
240
- UIView.setAnimationsEnabled(false)
241
- })
242
- .sheet(isPresented: $isPresented, content: {
243
- ZStack {
244
- fullScreenContent()
245
- }
246
- .background(FullScreenCoverBackgroundRemovalView())
247
- .onAppear {
248
- if !UIView.areAnimationsEnabled {
249
- UIView.setAnimationsEnabled(true)
250
- }
251
- }
252
- .onDisappear {
253
- if !UIView.areAnimationsEnabled {
254
- UIView.setAnimationsEnabled(true)
255
- }
256
- }
257
- })
258
- }
259
- }
260
-
261
- }
262
-
263
- private struct FullScreenCoverBackgroundRemovalView: UIViewRepresentable {
264
-
265
- private class BackgroundRemovalView: UIView {
266
-
267
- override func didMoveToWindow() {
268
- super.didMoveToWindow()
269
-
270
- superview?.superview?.backgroundColor = .clear
271
- }
272
-
273
- }
274
-
275
- func makeUIView(context: Context) -> UIView {
276
- return BackgroundRemovalView()
277
- }
278
-
279
- func updateUIView(_ uiView: UIView, context: Context) {}
280
-
281
- }
@@ -1,110 +0,0 @@
1
- import Foundation
2
- import SwiftUI
3
-
4
- public struct ScrollViewWithScrollBar<Content: View>: View {
5
- let INDICATOR_HEIGHT: CGFloat = 4
6
- @State var position = CGPoint(x: 0, y: 2) //2 is INDICATOR_HEIGHT/2
7
- @State var contentSize: CGFloat = 0
8
- @State var scrollViewWidth: CGFloat = 0
9
- var indicatorWidth: CGFloat
10
- var indicatorContainerWidth: CGFloat
11
- var indicatorColor: Color
12
- var indicatorContainerColor: Color
13
- var spacing: CGFloat = 4
14
- @ViewBuilder var content: () -> Content
15
-
16
- public init(
17
- indicatorWidth: CGFloat = 12, indicatorContainerWidth: CGFloat = 36,
18
- indicatorColor: Color = Colors.primary, indicatorContainerColor: Color = Colors.black06,
19
- spacing: CGFloat = 4,
20
- @ViewBuilder content: @escaping () -> Content
21
- ) {
22
- self.indicatorWidth = indicatorWidth
23
- self.indicatorContainerWidth = indicatorContainerWidth
24
- self.indicatorColor = indicatorColor
25
- self.indicatorContainerColor = indicatorContainerColor
26
- self.content = content
27
- self.spacing = spacing
28
- }
29
-
30
- // MARK: Public
31
-
32
- public var body: some View {
33
- return VStack(spacing: spacing) {
34
- ScrollView(.horizontal, showsIndicators: false) {
35
- HStack {
36
- content()
37
- }
38
- .fixedSize(horizontal: true, vertical: false)
39
- .overlay(
40
- GeometryReader { proxy in
41
- Color.clear
42
- .preference(
43
- key: ViewOffsetKey.self,
44
- value: -proxy.frame(in: .named("scroll")).origin.x
45
- )
46
- .preference(
47
- key: ContentSizeKey.self,
48
- value: proxy.size.width)
49
- },
50
- alignment: .topLeading
51
- )
52
- .onPreferenceChange(ContentSizeKey.self) { newContentSize in
53
- if newContentSize > 0 {
54
- self.contentSize = newContentSize
55
- if self.position == CGPoint(x: 0, y: 2) {
56
- self.position = CGPoint(x: indicatorWidth / 2, y: INDICATOR_HEIGHT / 2)
57
- }
58
- }
59
- }
60
- .onPreferenceChange(ViewOffsetKey.self) { offset in
61
- let newPosition =
62
- (offset * ((indicatorContainerWidth - indicatorWidth) / (contentSize - scrollViewWidth)))
63
- + indicatorWidth / 2
64
-
65
- if !newPosition.isNaN {
66
- self.position = CGPoint(x: newPosition, y: INDICATOR_HEIGHT / 2)
67
- }
68
- }
69
- }.background(
70
- GeometryReader { proxy in
71
- Color.clear.onAppear {
72
- self.scrollViewWidth = proxy.size.width
73
- }
74
- }
75
- ).coordinateSpace(name: "scroll")
76
-
77
- HStack(spacing: 0) {
78
- HStack(spacing: 0) {}.frame(width: indicatorWidth, height: INDICATOR_HEIGHT)
79
- .background(indicatorColor)
80
- .cornerRadius(8)
81
- .position(self.position)
82
- }.frame(width: indicatorContainerWidth, height: INDICATOR_HEIGHT, alignment: .leading)
83
- .background(indicatorContainerColor).cornerRadius(8)
84
- }
85
- }
86
- }
87
-
88
- // MARK: - ViewOffsetKey
89
-
90
- struct ViewOffsetKey: PreferenceKey {
91
- typealias Value = CGFloat
92
-
93
- static var defaultValue = CGFloat.zero
94
-
95
- static func reduce(value: inout Value, nextValue: () -> Value) {
96
- value += nextValue()
97
- }
98
- }
99
-
100
- // MARK: - ContentSizeKey
101
-
102
- struct ContentSizeKey: PreferenceKey {
103
- typealias Value = CGFloat
104
-
105
- static var defaultValue = CGFloat.zero
106
-
107
- static func reduce(value: inout Value, nextValue: () -> Value) {
108
- value = nextValue()
109
- }
110
- }
@@ -1,278 +0,0 @@
1
- import Foundation
2
- import SwiftUI
3
-
4
- // MARK: - SwipeCellModifier
5
-
6
- public struct SwipeCellModifier: ViewModifier {
7
- var id: String
8
- var cellWidth: CGFloat = .infinity
9
- var leadingSideGroup: [SwipeCellActionItem] = []
10
- var trailingSideGroup: [SwipeCellActionItem] = []
11
- @Binding var currentUserInteractionCellID: String?
12
- var settings: SwipeCellSettings = .init()
13
-
14
- @State private var offsetX: CGFloat = 0
15
-
16
- let generator = UINotificationFeedbackGenerator()
17
- @State private var hapticFeedbackOccurred: Bool = false
18
- @State private var openSideLock: SwipeGroupSide?
19
-
20
- public func body(content: Content)->some View {
21
- ZStack {
22
- if self.leadingSideGroup.isEmpty == false && self.offsetX != 0 {
23
- self.swipeToRevealArea(swipeItemGroup: self.leadingSideGroup, side: .leading)
24
- }
25
-
26
- if self.trailingSideGroup.isEmpty == false && self.offsetX != 0 {
27
- self.swipeToRevealArea(swipeItemGroup: self.trailingSideGroup, side: .trailing)
28
- }
29
-
30
- content
31
- .offset(x: self.offsetX)
32
- .simultaneousGesture(
33
- DragGesture(minimumDistance: 30, coordinateSpace: .local)
34
- .onChanged { value in
35
- self.dragOnChanged(value: value)
36
- }
37
- .onEnded { value in
38
- self.dragOnEnded(value: value)
39
- }
40
- )
41
-
42
- }.frame(width: cellWidth)
43
- .edgesIgnoringSafeArea(.horizontal)
44
- .clipped()
45
- .valueChanged(value: self.currentUserInteractionCellID) { _ in
46
- if let currentDragCellID = self.currentUserInteractionCellID, currentDragCellID != self.id && self.openSideLock != nil {
47
- // if this cell has an open side area and is not the cell being dragged, close the cell
48
- self.setOffsetX(value: 0)
49
- // reset the drag cell id to nil
50
- self.currentUserInteractionCellID = nil
51
- }
52
- }
53
- }
54
-
55
- internal func swipeToRevealArea(swipeItemGroup: [SwipeCellActionItem], side: SwipeGroupSide)->some View {
56
- HStack {
57
- if side == .trailing {
58
- Spacer()
59
- }
60
- ZStack {
61
- HStack(spacing: 0) {
62
- ForEach(swipeItemGroup) { item in
63
- SwiftUI.Button {
64
- self.setOffsetX(value: 0)
65
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
66
- item.actionCallback()
67
- }
68
- } label: {
69
- self.buttonContentView(item: item, group: swipeItemGroup, side: side)
70
- }.buttonStyle(BorderlessButtonStyle())
71
- }
72
- }
73
- }.opacity(self.swipeRevealAreaOpacity(side: side))
74
-
75
- if side == .leading {
76
- Spacer()
77
- }
78
- }
79
- }
80
-
81
- internal func buttonContentView(item: SwipeCellActionItem, group: [SwipeCellActionItem], side: SwipeGroupSide)->some View {
82
- ZStack {
83
- item.backgroundColor
84
-
85
- HStack {
86
- if self.warnSwipeOutCondition(side: side, hasSwipeOut: item.swipeOutAction) && item.swipeOutButtonView != nil {
87
- item.swipeOutButtonView!()
88
- } else {
89
- item.buttonView()
90
- }
91
- }
92
-
93
- }.frame(width: self.itemButtonWidth(item: item, itemGroup: group, side: side))
94
- }
95
-
96
- internal func menuWidth(side: SwipeGroupSide)->CGFloat {
97
- switch side {
98
- case .leading:
99
- return self.leadingSideGroup.map { $0.buttonWidth }.reduce(0, +)
100
-
101
- case .trailing:
102
- return self.trailingSideGroup.map { $0.buttonWidth }.reduce(0, +)
103
- }
104
- }
105
-
106
- // MARK: drag gesture
107
-
108
- internal func dragOnChanged(value: DragGesture.Value) {
109
- let horizontalTranslation = value.translation.width
110
- if self.nonDraggableCondition(horizontalTranslation: horizontalTranslation) {
111
- return
112
- }
113
-
114
- if self.openSideLock != nil {
115
- // if one side is open, we need to add the menu width!
116
- let menuWidth = self.openSideLock == .leading ? self.menuWidth(side: .leading) : self.menuWidth(side: .trailing)
117
- self.offsetX = menuWidth * openSideLock!.sideFactor + horizontalTranslation
118
- self.triggerHapticFeedbackIfNeeded(horizontalTranslation: horizontalTranslation)
119
- return
120
- }
121
-
122
- self.triggerHapticFeedbackIfNeeded(horizontalTranslation: horizontalTranslation)
123
-
124
- if horizontalTranslation > 8 || horizontalTranslation < -8 { // makes sure the swipe cell doesn't open too easily
125
- self.currentUserInteractionCellID = self.id
126
- self.offsetX = horizontalTranslation
127
- } else {
128
- self.offsetX = 0
129
- }
130
- }
131
-
132
- internal func nonDraggableCondition(horizontalTranslation: CGFloat)->Bool {
133
- return self.offsetX == 0 && (self.leadingSideGroup.isEmpty && horizontalTranslation > 0 || self.trailingSideGroup.isEmpty && horizontalTranslation < 0)
134
- }
135
-
136
- internal func dragOnEnded(value: DragGesture.Value) {
137
- let swipeOutTriggerValue = self.cellWidth * self.settings.swipeOutTriggerRatio
138
-
139
- if self.offsetX == 0 {
140
- self.openSideLock = nil
141
- } else if self.offsetX > 0 {
142
- if self.leadingSideGroup.isEmpty == false {
143
- if self.offsetX < settings.openTriggerValue || (self.openSideLock == .leading && self.offsetX < self.menuWidth(side: .leading) * 0.8) {
144
- self.setOffsetX(value: 0)
145
- } else if let leftItem = self.leadingSideGroup.filter({ $0.swipeOutAction == true }).first, self.offsetX.magnitude > swipeOutTriggerValue {
146
- self.swipeOutAction(item: leftItem, sideFactor: 1)
147
- } else {
148
- self.lockSideMenu(side: .leading)
149
- }
150
-
151
- } else {
152
- // leading group emtpy
153
- self.setOffsetX(value: 0)
154
- }
155
- } else if self.offsetX < 0 {
156
- if self.trailingSideGroup.isEmpty == false {
157
- if self.offsetX.magnitude < settings.openTriggerValue || (self.openSideLock == .trailing && self.offsetX > -self.menuWidth(side: .trailing) * 0.8) {
158
- self.setOffsetX(value: 0)
159
- } else if let rightItem = self.trailingSideGroup.filter({ $0.swipeOutAction == true }).first, self.offsetX.magnitude > swipeOutTriggerValue {
160
- self.swipeOutAction(item: rightItem, sideFactor: -1)
161
- } else {
162
- self.lockSideMenu(side: .trailing)
163
- }
164
-
165
- } else {
166
- // trailing group emtpy
167
- self.setOffsetX(value: 0)
168
- }
169
- }
170
- }
171
-
172
- internal func triggerHapticFeedbackIfNeeded(horizontalTranslation: CGFloat) {
173
- let side: SwipeGroupSide = horizontalTranslation > 0 ? .leading : .trailing
174
- let group = side == .leading ? self.leadingSideGroup : self.trailingSideGroup
175
- let swipeOutActionCondition = self.warnSwipeOutCondition(side: side, hasSwipeOut: true)
176
- if let item = self.swipeOutItemWithHapticFeedback(group: group), self.hapticFeedbackOccurred == false, swipeOutActionCondition == true {
177
- self.generator.notificationOccurred(item.swipeOutHapticFeedbackType!)
178
- self.hapticFeedbackOccurred = true
179
- }
180
- }
181
-
182
- internal func swipeOutItemWithHapticFeedback(group: [SwipeCellActionItem])->SwipeCellActionItem? {
183
- if let item = group.filter({ $0.swipeOutAction == true }).first {
184
- if item.swipeOutHapticFeedbackType != nil {
185
- return item
186
- }
187
- }
188
- return nil
189
- }
190
-
191
- internal func swipeOutAction(item: SwipeCellActionItem, sideFactor: CGFloat) {
192
- if item.swipeOutIsDestructive {
193
- let swipeOutWidth = cellWidth + 10
194
- self.setOffsetX(value: swipeOutWidth * sideFactor)
195
- self.openSideLock = nil
196
- } else {
197
- self.setOffsetX(value: 0) // open side lock set in function!
198
- }
199
-
200
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
201
- item.actionCallback()
202
- }
203
- }
204
-
205
- internal func lockSideMenu(side: SwipeGroupSide) {
206
- self.setOffsetX(value: side.sideFactor * self.menuWidth(side: side))
207
- self.openSideLock = side
208
- self.hapticFeedbackOccurred = false
209
- }
210
-
211
- internal func setOffsetX(value: CGFloat) {
212
- withAnimation(.spring()) {
213
- self.offsetX = value
214
- }
215
- if self.offsetX == 0 {
216
- self.openSideLock = nil
217
- self.hapticFeedbackOccurred = false
218
- }
219
- }
220
-
221
- internal func itemButtonWidth(item: SwipeCellActionItem, itemGroup: [SwipeCellActionItem], side: SwipeGroupSide)->CGFloat {
222
- let dynamicButtonWidth = self.dynamicButtonWidth(item: item, itemCount: itemGroup.count, side: side)
223
- let triggerValue = self.cellWidth * settings.swipeOutTriggerRatio
224
- let swipeOutActionCondition = side == .leading ? self.offsetX > triggerValue : self.offsetX < -triggerValue
225
-
226
- if item.swipeOutAction && swipeOutActionCondition {
227
- return self.offsetX.magnitude + settings.addWidthMargin
228
- } else if swipeOutActionCondition && item.swipeOutAction == false && itemGroup.contains(where: { $0.swipeOutAction == true }) {
229
- return 0
230
- } else {
231
- return dynamicButtonWidth
232
- }
233
- }
234
-
235
- internal func dynamicButtonWidth(item: SwipeCellActionItem, itemCount: Int, side: SwipeGroupSide)->CGFloat {
236
- let menuWidth = self.menuWidth(side: side)
237
- return (self.offsetX.magnitude + settings.addWidthMargin) * (item.buttonWidth / menuWidth)
238
- }
239
-
240
- internal func warnSwipeOutCondition(side: SwipeGroupSide, hasSwipeOut: Bool)->Bool {
241
- if hasSwipeOut == false {
242
- return false
243
- }
244
- let triggerValue = self.cellWidth * settings.swipeOutTriggerRatio
245
- return (side == .trailing && self.offsetX < -triggerValue) || (side == .leading && self.offsetX > triggerValue)
246
- }
247
-
248
- internal func swipeRevealAreaOpacity(side: SwipeGroupSide)->Double {
249
- switch side {
250
- case .leading:
251
-
252
- return self.offsetX > 5 ? 1 : 0
253
- case .trailing:
254
- return self.offsetX < -5 ? 1 : 0
255
- }
256
- }
257
- }
258
-
259
- public extension View {
260
- /// swipe cell modifier
261
- /// - Parameters:
262
- /// - id: the string id of this cell. The default value is a uuid string. If you want to set the currentUserInteractionCellID yourself, e.g. for tap to close functionality, you need to override this id value with your own cell id.
263
- /// - cellWidth: the width of the content view - typically a cell or row in a list under which the swipe to reveal menu should appear.
264
- /// - leadingSideGroup: the button group on the leading side that shall appear when the user swipes the cell to the right
265
- /// - trailingSideGroup: the button group on the trailing side that shall appear when the user swipes the cell to the left
266
- /// - currentUserInteractionCellID: a Binding of an optional UUID that should be set either in the view model of the parent view in which the cells appear or as a State variable into the parent view itself. Don't assign it a value!
267
- /// - settings: settings. can be omitted in which case the settings struct default values apply.
268
- /// - Returns: the modified view of the view that can be swiped.
269
- func swipeCell(id: String = UUID().uuidString, cellWidth: CGFloat = .infinity, leadingSideGroup: [SwipeCellActionItem], trailingSideGroup: [SwipeCellActionItem], currentUserInteractionCellID: Binding<String?> = .constant(""), settings: SwipeCellSettings = SwipeCellSettings())->some View {
270
- self.modifier(SwipeCellModifier(id: id, cellWidth: cellWidth, leadingSideGroup: leadingSideGroup, trailingSideGroup: trailingSideGroup, currentUserInteractionCellID: currentUserInteractionCellID, settings: settings))
271
- }
272
- }
273
-
274
- public extension View {
275
- func castToAnyView()->AnyView {
276
- return AnyView(self)
277
- }
278
- }