@momo-kits/native-kits 0.0.1-beta.4 → 0.2.0-kt22
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.
- package/README.md +176 -5
- package/ios/Application/ApplicationEnvironment.swift +50 -0
- package/ios/Application/Components.swift +263 -0
- package/ios/Application/ComposeApi.swift +22 -0
- package/ios/Application/FloatingButton.swift +172 -0
- package/ios/Application/HeaderRight.swift +271 -0
- package/ios/Application/Screen.swift +249 -0
- package/ios/Badge/Badge.swift +85 -0
- package/ios/Badge/BadgeDot.swift +31 -0
- package/ios/Badge/BadgeRibbon.swift +242 -0
- package/ios/Button/Button.swift +169 -89
- package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +126 -0
- package/ios/Checkbox/Checkbox.swift +81 -0
- package/ios/Chip/Chip.swift +96 -0
- package/ios/{Colors+Spacing → Colors+Radius+Spacing}/Colors.swift +21 -19
- package/ios/Colors+Radius+Spacing/Radius.swift +22 -0
- package/ios/Icon/Icon.swift +51 -0
- package/ios/Image/Image.swift +70 -0
- package/ios/Input/ErrorView.swift +55 -0
- package/ios/Input/Input.swift +159 -50
- package/ios/Input/InputPhoneNumber.swift +158 -0
- package/ios/Input/InputSearch.swift +238 -0
- package/ios/Input/InputTextArea.swift +242 -0
- package/ios/Lottie/LottieView.swift +86 -0
- package/ios/OTPKeyboard/KeyboardButton.swift +12 -4
- package/ios/OTPKeyboard/OTPKeyboard.swift +1 -1
- package/ios/Popup/PopupDisplay.swift +287 -0
- package/ios/Popup/PopupInput.swift +96 -0
- package/ios/Popup/PopupPromotion.swift +73 -0
- package/ios/PopupView/FullscreenPopup.swift +251 -0
- package/ios/{Popup → PopupView}/Modifiers.swift +6 -20
- package/ios/{Popup → PopupView}/PopupView.swift +38 -146
- package/ios/PopupView/Utils++.swift +281 -0
- package/ios/ScrollIndicator/ScrollIndicator.swift +110 -0
- package/ios/Swipeable/SwipeCell.swift +39 -31
- package/ios/Switch/Switch.swift +44 -0
- package/ios/Template/Logo/Logo.swift +75 -0
- package/ios/Template/TrustBanner/TrustBanner.swift +120 -0
- package/ios/Typography/Text.swift +140 -0
- package/ios/Typography/Typography.swift +70 -5
- package/ios/native-kits.podspec +20 -15
- package/package.json +6 -6
- package/CODE_OF_CONDUCT.md +0 -133
- package/CONTRIBUTING.md +0 -114
- package/LICENSE +0 -20
- package/android/build.gradle +0 -82
- package/android/gradle.properties +0 -5
- package/android/src/main/AndroidManifest.xml +0 -4
- package/android/src/main/java/com/momoplatform/nativekits/NativeKitsPackage.kt +0 -17
- package/android/src/main/java/com/momoplatform/nativekits/NativeKitsViewManager.kt +0 -20
- package/example/ios/Example/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -63
- package/example/ios/Example/Assets.xcassets/Background.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/Border.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/Card.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/Contents.json +0 -6
- package/example/ios/Example/Assets.xcassets/Error.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/Primary.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/PrimaryDark.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/PrimaryLight.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/Secondary.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/Success.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/Text.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/TextSecondary.colorset/Contents.json +0 -38
- package/example/ios/Example/Assets.xcassets/Warning.colorset/Contents.json +0 -38
- package/example/ios/Example/ComponentDetail.swift +0 -35
- package/example/ios/Example/ComponentItem.swift +0 -24
- package/example/ios/Example/ComponentModel.swift +0 -33
- package/example/ios/Example/ContentView.swift +0 -53
- package/example/ios/Example/Example.entitlements +0 -10
- package/example/ios/Example/Foundation/ButtonDemo.swift +0 -80
- package/example/ios/Example/Foundation/Popup/ActionSheets.swift +0 -136
- package/example/ios/Example/Foundation/Popup/Floats.swift +0 -127
- package/example/ios/Example/Foundation/Popup/Popups.swift +0 -101
- package/example/ios/Example/Foundation/PopupDemo.swift +0 -366
- package/example/ios/Example/Foundation/TextInputDemo.swift +0 -31
- package/example/ios/Example/Foundation/TypographyDemo.swift +0 -53
- package/example/ios/Example/Foundation.swift +0 -26
- package/example/ios/Example/Info.plist +0 -18
- package/example/ios/Example/Library/SwipeableDemo.swift +0 -105
- package/example/ios/Example/Library.swift +0 -26
- package/example/ios/Example/Main.swift +0 -11
- package/example/ios/Example/Overview.swift +0 -20
- package/example/ios/Example/Preview Content/Preview Assets.xcassets/Contents.json +0 -6
- package/example/ios/Example/Resource/SFProText-Black.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Bold.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Heavy.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Light.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Medium.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Regular.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Semibold.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Thin.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Ultralight.ttf +0 -0
- package/example/ios/Example/Resource/foundation.json +0 -33
- package/example/ios/Example/Resource/library.json +0 -8
- package/example/ios/Example/Utils.swift +0 -30
- package/example/ios/Example/ViewModifier/DarkViewModifier.swift +0 -22
- package/example/ios/Example.xcodeproj/project.pbxproj +0 -570
- package/example/ios/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/example/ios/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/example/ios/Example.xcodeproj/project.xcworkspace/xcuserdata/wem.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/example/ios/Example.xcodeproj/xcuserdata/wem.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- package/example/ios/Example.xcworkspace/contents.xcworkspacedata +0 -10
- package/example/ios/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/example/ios/Example.xcworkspace/xcuserdata/wem.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/example/ios/Example.xcworkspace/xcuserdata/wem.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +0 -6
- package/example/ios/Podfile +0 -6
- package/example/ios/Podfile.lock +0 -16
- package/example/ios/Pods/Local Podspecs/MoMoUIKits.podspec.json +0 -29
- package/example/ios/Pods/Manifest.lock +0 -16
- package/example/ios/Pods/Pods.xcodeproj/project.pbxproj +0 -661
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/wem.xcuserdatad/xcschemes/MoMoUIKits.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/wem.xcuserdatad/xcschemes/Pods-Example.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/wem.xcuserdatad/xcschemes/xcschememanagement.plist +0 -25
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits-dummy.m +0 -5
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits-prefix.pch +0 -12
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits-umbrella.h +0 -16
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits.debug.xcconfig +0 -12
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits.modulemap +0 -6
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits.release.xcconfig +0 -12
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.markdown +0 -3
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.plist +0 -29
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example-dummy.m +0 -5
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example-umbrella.h +0 -16
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig +0 -14
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example.modulemap +0 -6
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig +0 -14
- package/ios/Colors+Spacing/Radius.swift +0 -11
- package/ios/Popup/Utils++.swift +0 -163
- package/ios/Theme.md +0 -18
- package/publish.sh +0 -28
- /package/ios/{Colors+Spacing → Colors+Radius+Spacing}/Spacing.swift +0 -0
|
@@ -0,0 +1,281 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
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
|
+
}
|
|
@@ -10,9 +10,9 @@ public struct SwipeCellModifier: ViewModifier {
|
|
|
10
10
|
var trailingSideGroup: [SwipeCellActionItem] = []
|
|
11
11
|
@Binding var currentUserInteractionCellID: String?
|
|
12
12
|
var settings: SwipeCellSettings = .init()
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
@State private var offsetX: CGFloat = 0
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
let generator = UINotificationFeedbackGenerator()
|
|
17
17
|
@State private var hapticFeedbackOccurred: Bool = false
|
|
18
18
|
@State private var openSideLock: SwipeGroupSide?
|
|
@@ -22,15 +22,23 @@ public struct SwipeCellModifier: ViewModifier {
|
|
|
22
22
|
if self.leadingSideGroup.isEmpty == false && self.offsetX != 0 {
|
|
23
23
|
self.swipeToRevealArea(swipeItemGroup: self.leadingSideGroup, side: .leading)
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
if self.trailingSideGroup.isEmpty == false && self.offsetX != 0 {
|
|
27
27
|
self.swipeToRevealArea(swipeItemGroup: self.trailingSideGroup, side: .trailing)
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
content
|
|
31
31
|
.offset(x: self.offsetX)
|
|
32
|
-
.
|
|
33
|
-
|
|
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
|
+
|
|
34
42
|
}.frame(width: cellWidth)
|
|
35
43
|
.edgesIgnoringSafeArea(.horizontal)
|
|
36
44
|
.clipped()
|
|
@@ -43,7 +51,7 @@ public struct SwipeCellModifier: ViewModifier {
|
|
|
43
51
|
}
|
|
44
52
|
}
|
|
45
53
|
}
|
|
46
|
-
|
|
54
|
+
|
|
47
55
|
internal func swipeToRevealArea(swipeItemGroup: [SwipeCellActionItem], side: SwipeGroupSide)->some View {
|
|
48
56
|
HStack {
|
|
49
57
|
if side == .trailing {
|
|
@@ -63,17 +71,17 @@ public struct SwipeCellModifier: ViewModifier {
|
|
|
63
71
|
}
|
|
64
72
|
}
|
|
65
73
|
}.opacity(self.swipeRevealAreaOpacity(side: side))
|
|
66
|
-
|
|
74
|
+
|
|
67
75
|
if side == .leading {
|
|
68
76
|
Spacer()
|
|
69
77
|
}
|
|
70
78
|
}
|
|
71
79
|
}
|
|
72
|
-
|
|
80
|
+
|
|
73
81
|
internal func buttonContentView(item: SwipeCellActionItem, group: [SwipeCellActionItem], side: SwipeGroupSide)->some View {
|
|
74
82
|
ZStack {
|
|
75
83
|
item.backgroundColor
|
|
76
|
-
|
|
84
|
+
|
|
77
85
|
HStack {
|
|
78
86
|
if self.warnSwipeOutCondition(side: side, hasSwipeOut: item.swipeOutAction) && item.swipeOutButtonView != nil {
|
|
79
87
|
item.swipeOutButtonView!()
|
|
@@ -81,28 +89,28 @@ public struct SwipeCellModifier: ViewModifier {
|
|
|
81
89
|
item.buttonView()
|
|
82
90
|
}
|
|
83
91
|
}
|
|
84
|
-
|
|
92
|
+
|
|
85
93
|
}.frame(width: self.itemButtonWidth(item: item, itemGroup: group, side: side))
|
|
86
94
|
}
|
|
87
|
-
|
|
95
|
+
|
|
88
96
|
internal func menuWidth(side: SwipeGroupSide)->CGFloat {
|
|
89
97
|
switch side {
|
|
90
98
|
case .leading:
|
|
91
99
|
return self.leadingSideGroup.map { $0.buttonWidth }.reduce(0, +)
|
|
92
|
-
|
|
100
|
+
|
|
93
101
|
case .trailing:
|
|
94
102
|
return self.trailingSideGroup.map { $0.buttonWidth }.reduce(0, +)
|
|
95
103
|
}
|
|
96
104
|
}
|
|
97
|
-
|
|
105
|
+
|
|
98
106
|
// MARK: drag gesture
|
|
99
|
-
|
|
107
|
+
|
|
100
108
|
internal func dragOnChanged(value: DragGesture.Value) {
|
|
101
109
|
let horizontalTranslation = value.translation.width
|
|
102
110
|
if self.nonDraggableCondition(horizontalTranslation: horizontalTranslation) {
|
|
103
111
|
return
|
|
104
112
|
}
|
|
105
|
-
|
|
113
|
+
|
|
106
114
|
if self.openSideLock != nil {
|
|
107
115
|
// if one side is open, we need to add the menu width!
|
|
108
116
|
let menuWidth = self.openSideLock == .leading ? self.menuWidth(side: .leading) : self.menuWidth(side: .trailing)
|
|
@@ -110,9 +118,9 @@ public struct SwipeCellModifier: ViewModifier {
|
|
|
110
118
|
self.triggerHapticFeedbackIfNeeded(horizontalTranslation: horizontalTranslation)
|
|
111
119
|
return
|
|
112
120
|
}
|
|
113
|
-
|
|
121
|
+
|
|
114
122
|
self.triggerHapticFeedbackIfNeeded(horizontalTranslation: horizontalTranslation)
|
|
115
|
-
|
|
123
|
+
|
|
116
124
|
if horizontalTranslation > 8 || horizontalTranslation < -8 { // makes sure the swipe cell doesn't open too easily
|
|
117
125
|
self.currentUserInteractionCellID = self.id
|
|
118
126
|
self.offsetX = horizontalTranslation
|
|
@@ -120,11 +128,11 @@ public struct SwipeCellModifier: ViewModifier {
|
|
|
120
128
|
self.offsetX = 0
|
|
121
129
|
}
|
|
122
130
|
}
|
|
123
|
-
|
|
131
|
+
|
|
124
132
|
internal func nonDraggableCondition(horizontalTranslation: CGFloat)->Bool {
|
|
125
133
|
return self.offsetX == 0 && (self.leadingSideGroup.isEmpty && horizontalTranslation > 0 || self.trailingSideGroup.isEmpty && horizontalTranslation < 0)
|
|
126
134
|
}
|
|
127
|
-
|
|
135
|
+
|
|
128
136
|
internal func dragOnEnded(value: DragGesture.Value) {
|
|
129
137
|
let swipeOutTriggerValue = self.cellWidth * self.settings.swipeOutTriggerRatio
|
|
130
138
|
|
|
@@ -139,7 +147,7 @@ public struct SwipeCellModifier: ViewModifier {
|
|
|
139
147
|
} else {
|
|
140
148
|
self.lockSideMenu(side: .leading)
|
|
141
149
|
}
|
|
142
|
-
|
|
150
|
+
|
|
143
151
|
} else {
|
|
144
152
|
// leading group emtpy
|
|
145
153
|
self.setOffsetX(value: 0)
|
|
@@ -160,7 +168,7 @@ public struct SwipeCellModifier: ViewModifier {
|
|
|
160
168
|
}
|
|
161
169
|
}
|
|
162
170
|
}
|
|
163
|
-
|
|
171
|
+
|
|
164
172
|
internal func triggerHapticFeedbackIfNeeded(horizontalTranslation: CGFloat) {
|
|
165
173
|
let side: SwipeGroupSide = horizontalTranslation > 0 ? .leading : .trailing
|
|
166
174
|
let group = side == .leading ? self.leadingSideGroup : self.trailingSideGroup
|
|
@@ -170,7 +178,7 @@ public struct SwipeCellModifier: ViewModifier {
|
|
|
170
178
|
self.hapticFeedbackOccurred = true
|
|
171
179
|
}
|
|
172
180
|
}
|
|
173
|
-
|
|
181
|
+
|
|
174
182
|
internal func swipeOutItemWithHapticFeedback(group: [SwipeCellActionItem])->SwipeCellActionItem? {
|
|
175
183
|
if let item = group.filter({ $0.swipeOutAction == true }).first {
|
|
176
184
|
if item.swipeOutHapticFeedbackType != nil {
|
|
@@ -179,7 +187,7 @@ public struct SwipeCellModifier: ViewModifier {
|
|
|
179
187
|
}
|
|
180
188
|
return nil
|
|
181
189
|
}
|
|
182
|
-
|
|
190
|
+
|
|
183
191
|
internal func swipeOutAction(item: SwipeCellActionItem, sideFactor: CGFloat) {
|
|
184
192
|
if item.swipeOutIsDestructive {
|
|
185
193
|
let swipeOutWidth = cellWidth + 10
|
|
@@ -188,12 +196,12 @@ public struct SwipeCellModifier: ViewModifier {
|
|
|
188
196
|
} else {
|
|
189
197
|
self.setOffsetX(value: 0) // open side lock set in function!
|
|
190
198
|
}
|
|
191
|
-
|
|
199
|
+
|
|
192
200
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
|
193
201
|
item.actionCallback()
|
|
194
202
|
}
|
|
195
203
|
}
|
|
196
|
-
|
|
204
|
+
|
|
197
205
|
internal func lockSideMenu(side: SwipeGroupSide) {
|
|
198
206
|
self.setOffsetX(value: side.sideFactor * self.menuWidth(side: side))
|
|
199
207
|
self.openSideLock = side
|
|
@@ -214,7 +222,7 @@ public struct SwipeCellModifier: ViewModifier {
|
|
|
214
222
|
let dynamicButtonWidth = self.dynamicButtonWidth(item: item, itemCount: itemGroup.count, side: side)
|
|
215
223
|
let triggerValue = self.cellWidth * settings.swipeOutTriggerRatio
|
|
216
224
|
let swipeOutActionCondition = side == .leading ? self.offsetX > triggerValue : self.offsetX < -triggerValue
|
|
217
|
-
|
|
225
|
+
|
|
218
226
|
if item.swipeOutAction && swipeOutActionCondition {
|
|
219
227
|
return self.offsetX.magnitude + settings.addWidthMargin
|
|
220
228
|
} else if swipeOutActionCondition && item.swipeOutAction == false && itemGroup.contains(where: { $0.swipeOutAction == true }) {
|
|
@@ -223,12 +231,12 @@ public struct SwipeCellModifier: ViewModifier {
|
|
|
223
231
|
return dynamicButtonWidth
|
|
224
232
|
}
|
|
225
233
|
}
|
|
226
|
-
|
|
234
|
+
|
|
227
235
|
internal func dynamicButtonWidth(item: SwipeCellActionItem, itemCount: Int, side: SwipeGroupSide)->CGFloat {
|
|
228
236
|
let menuWidth = self.menuWidth(side: side)
|
|
229
237
|
return (self.offsetX.magnitude + settings.addWidthMargin) * (item.buttonWidth / menuWidth)
|
|
230
238
|
}
|
|
231
|
-
|
|
239
|
+
|
|
232
240
|
internal func warnSwipeOutCondition(side: SwipeGroupSide, hasSwipeOut: Bool)->Bool {
|
|
233
241
|
if hasSwipeOut == false {
|
|
234
242
|
return false
|
|
@@ -236,11 +244,11 @@ public struct SwipeCellModifier: ViewModifier {
|
|
|
236
244
|
let triggerValue = self.cellWidth * settings.swipeOutTriggerRatio
|
|
237
245
|
return (side == .trailing && self.offsetX < -triggerValue) || (side == .leading && self.offsetX > triggerValue)
|
|
238
246
|
}
|
|
239
|
-
|
|
247
|
+
|
|
240
248
|
internal func swipeRevealAreaOpacity(side: SwipeGroupSide)->Double {
|
|
241
249
|
switch side {
|
|
242
250
|
case .leading:
|
|
243
|
-
|
|
251
|
+
|
|
244
252
|
return self.offsetX > 5 ? 1 : 0
|
|
245
253
|
case .trailing:
|
|
246
254
|
return self.offsetX < -5 ? 1 : 0
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import SwiftUI
|
|
3
|
+
|
|
4
|
+
public struct Switch: View{
|
|
5
|
+
@Binding var value: Bool
|
|
6
|
+
var disabled: Bool
|
|
7
|
+
var onChange: ((Bool)->Void)?
|
|
8
|
+
|
|
9
|
+
public init(_ value: Binding<Bool>,disabled: Bool = false, onChange: ((Bool)->Void)? = nil){
|
|
10
|
+
self._value = value
|
|
11
|
+
self.disabled = disabled
|
|
12
|
+
self.onChange = onChange
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
public var body: some View {
|
|
17
|
+
SwiftUI.Button(action: {
|
|
18
|
+
withAnimation{
|
|
19
|
+
value.toggle()
|
|
20
|
+
onChange?(value)
|
|
21
|
+
}
|
|
22
|
+
}){
|
|
23
|
+
HStack{
|
|
24
|
+
HStack {
|
|
25
|
+
}
|
|
26
|
+
.frame(width: 6, height: 6)
|
|
27
|
+
.background(value ? Colors.pink03 : Colors.black07)
|
|
28
|
+
.clipShape(Circle())
|
|
29
|
+
.opacity(disabled ? 0.4 : 1)
|
|
30
|
+
}
|
|
31
|
+
.frame(width: 14, height: 14)
|
|
32
|
+
.background(Colors.black01)
|
|
33
|
+
.clipShape(Circle())
|
|
34
|
+
.padding(.leading,5)
|
|
35
|
+
.padding(.trailing,5)
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
.disabled(disabled)
|
|
39
|
+
.frame(width: 40, height: 24, alignment: value == true ? .trailing : .leading)
|
|
40
|
+
.background(value ? Colors.pink03 : Colors.black07)
|
|
41
|
+
.clipShape(Capsule())
|
|
42
|
+
.opacity(disabled ? 0.4 : 1)
|
|
43
|
+
}
|
|
44
|
+
}
|