@onekeyfe/react-native-native-sheet 3.0.125
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/LICENSE +21 -0
- package/NativeSheet.podspec +22 -0
- package/README.md +56 -0
- package/android/build.gradle +82 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/onekey/nativesheet/NativeSheetEvents.kt +37 -0
- package/android/src/main/java/com/onekey/nativesheet/NativeSheetPackage.kt +16 -0
- package/android/src/main/java/com/onekey/nativesheet/NativeSheetView.kt +354 -0
- package/android/src/main/java/com/onekey/nativesheet/NativeSheetViewManager.kt +118 -0
- package/ios/NativeSheetContainerView.swift +441 -0
- package/ios/RCTNativeSheetComponentView.h +11 -0
- package/ios/RCTNativeSheetComponentView.mm +156 -0
- package/lib/module/NativeSheetNativeComponent.ts +35 -0
- package/lib/module/codegen-types.d.js +2 -0
- package/lib/module/codegen-types.d.js.map +1 -0
- package/lib/module/index.js +92 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeSheetNativeComponent.d.ts +25 -0
- package/lib/typescript/src/NativeSheetNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +40 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +171 -0
- package/react-native.config.js +12 -0
- package/src/NativeSheetNativeComponent.ts +35 -0
- package/src/codegen-types.d.ts +9 -0
- package/src/index.tsx +145 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
package com.onekey.nativesheet
|
|
2
|
+
|
|
3
|
+
import android.view.View
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
6
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
7
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
8
|
+
import com.facebook.react.uimanager.ViewGroupManager
|
|
9
|
+
import com.facebook.react.uimanager.ViewManagerDelegate
|
|
10
|
+
import com.facebook.react.viewmanagers.RNCNativeSheetManagerDelegate
|
|
11
|
+
import com.facebook.react.viewmanagers.RNCNativeSheetManagerInterface
|
|
12
|
+
|
|
13
|
+
@ReactModule(name = NativeSheetViewManager.NAME)
|
|
14
|
+
class NativeSheetViewManager(
|
|
15
|
+
context: ReactApplicationContext,
|
|
16
|
+
) : ViewGroupManager<NativeSheetView>(), RNCNativeSheetManagerInterface<NativeSheetView> {
|
|
17
|
+
private val delegate = RNCNativeSheetManagerDelegate<NativeSheetView, NativeSheetViewManager>(this)
|
|
18
|
+
|
|
19
|
+
override fun getName(): String = NAME
|
|
20
|
+
|
|
21
|
+
override fun getDelegate(): ViewManagerDelegate<NativeSheetView> = delegate
|
|
22
|
+
|
|
23
|
+
override fun createViewInstance(context: ThemedReactContext): NativeSheetView {
|
|
24
|
+
val view = NativeSheetView(context)
|
|
25
|
+
view.onDismiss = { reason ->
|
|
26
|
+
val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id)
|
|
27
|
+
val surfaceId = UIManagerHelper.getSurfaceId(context)
|
|
28
|
+
dispatcher?.dispatchEvent(NativeSheetDismissEvent(surfaceId, view.id, reason))
|
|
29
|
+
}
|
|
30
|
+
view.onPresented = { height ->
|
|
31
|
+
val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id)
|
|
32
|
+
val surfaceId = UIManagerHelper.getSurfaceId(context)
|
|
33
|
+
dispatcher?.dispatchEvent(NativeSheetPresentedEvent(surfaceId, view.id, height))
|
|
34
|
+
}
|
|
35
|
+
return view
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
override fun addEventEmitters(context: ThemedReactContext, view: NativeSheetView) {
|
|
39
|
+
view.eventDispatcher = UIManagerHelper.getEventDispatcher(context)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
override fun onAfterUpdateTransaction(view: NativeSheetView) {
|
|
43
|
+
super.onAfterUpdateTransaction(view)
|
|
44
|
+
view.commitConfiguration()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
override fun onDropViewInstance(view: NativeSheetView) {
|
|
48
|
+
view.onDropViewInstance()
|
|
49
|
+
super.onDropViewInstance(view)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
override fun addView(parent: NativeSheetView, child: View, index: Int) {
|
|
53
|
+
parent.addReactChild(child, index)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
override fun getChildCount(parent: NativeSheetView): Int = parent.getReactChildCount()
|
|
57
|
+
|
|
58
|
+
override fun getChildAt(parent: NativeSheetView, index: Int): View? =
|
|
59
|
+
parent.getReactChildAt(index)
|
|
60
|
+
|
|
61
|
+
override fun removeView(parent: NativeSheetView, view: View) {
|
|
62
|
+
parent.removeReactChild(view)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
override fun removeAllViews(parent: NativeSheetView) {
|
|
66
|
+
parent.removeAllReactChildren()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
override fun removeViewAt(parent: NativeSheetView, index: Int) {
|
|
70
|
+
parent.getReactChildAt(index)?.let(parent::removeReactChild)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
override fun needsCustomLayoutForChildren(): Boolean = true
|
|
74
|
+
|
|
75
|
+
override fun setOpen(view: NativeSheetView?, value: Boolean) {
|
|
76
|
+
view?.open = value
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
override fun setSheetHeight(view: NativeSheetView?, value: Double) {
|
|
80
|
+
view?.sheetHeight = value
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
override fun setSecurityBlocked(view: NativeSheetView?, value: Boolean) {
|
|
84
|
+
view?.securityBlocked = value
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
override fun setDismissOnPanDown(view: NativeSheetView?, value: Boolean) {
|
|
88
|
+
view?.dismissOnPanDown = value
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
override fun setDismissOnBackdropPress(view: NativeSheetView?, value: Boolean) {
|
|
92
|
+
view?.dismissOnBackdropPress = value
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
override fun setDismissOnBackPress(view: NativeSheetView?, value: Boolean) {
|
|
96
|
+
view?.dismissOnBackPress = value
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
override fun setShowHandle(view: NativeSheetView?, value: Boolean) {
|
|
100
|
+
view?.showHandle = value
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
override fun setCornerRadius(view: NativeSheetView?, value: Double) {
|
|
104
|
+
view?.cornerRadius = value
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
override fun setDimAmount(view: NativeSheetView?, value: Double) {
|
|
108
|
+
view?.dimAmount = value
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
override fun setSheetBackgroundColor(view: NativeSheetView?, value: Int?) {
|
|
112
|
+
view?.sheetBackgroundColor = value
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
companion object {
|
|
116
|
+
const val NAME = "RNCNativeSheet"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import React
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
private final class WeakSheetHost {
|
|
5
|
+
weak var value: NativeSheetContainerView?
|
|
6
|
+
|
|
7
|
+
init(_ value: NativeSheetContainerView) {
|
|
8
|
+
self.value = value
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
private final class NativeSheetPresentationCoordinator {
|
|
13
|
+
static let shared = NativeSheetPresentationCoordinator()
|
|
14
|
+
|
|
15
|
+
private var active: [WeakSheetHost] = []
|
|
16
|
+
private var pending: [WeakSheetHost] = []
|
|
17
|
+
private var deferredDismissals: [(host: WeakSheetHost, reason: String, animated: Bool)] = []
|
|
18
|
+
private var transitioning = false
|
|
19
|
+
|
|
20
|
+
func present(_ host: NativeSheetContainerView) {
|
|
21
|
+
compact()
|
|
22
|
+
guard !active.contains(where: { $0.value === host }),
|
|
23
|
+
!pending.contains(where: { $0.value === host }) else {
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
pending.append(WeakSheetHost(host))
|
|
27
|
+
processPending()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func dismiss(_ host: NativeSheetContainerView, reason: String, animated: Bool) {
|
|
31
|
+
pending.removeAll { $0.value == nil || $0.value === host }
|
|
32
|
+
guard !transitioning else {
|
|
33
|
+
queueDismiss(host, reason: reason, animated: animated)
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
compact()
|
|
38
|
+
guard let index = active.firstIndex(where: { $0.value === host }) else {
|
|
39
|
+
host.finishDismiss(reason: reason)
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
let targets = active[index...].compactMap(\.value).reversed()
|
|
43
|
+
active.removeSubrange(index...)
|
|
44
|
+
dismiss(Array(targets), requestedHost: host, reason: reason, animated: animated)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
func didDismissInteractively(_ host: NativeSheetContainerView, reason: String) {
|
|
48
|
+
active.removeAll { $0.value == nil || $0.value === host }
|
|
49
|
+
transitioning = false
|
|
50
|
+
host.finishDismiss(reason: reason)
|
|
51
|
+
processDeferredDismissalOrPending()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private func dismiss(
|
|
55
|
+
_ targets: [NativeSheetContainerView],
|
|
56
|
+
requestedHost: NativeSheetContainerView,
|
|
57
|
+
reason: String,
|
|
58
|
+
animated: Bool
|
|
59
|
+
) {
|
|
60
|
+
guard let target = targets.first else {
|
|
61
|
+
transitioning = false
|
|
62
|
+
processDeferredDismissalOrPending()
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
let remaining = Array(targets.dropFirst())
|
|
66
|
+
let targetReason = target === requestedHost
|
|
67
|
+
? reason
|
|
68
|
+
: (reason == "security" ? reason : "system")
|
|
69
|
+
guard let controller = target.presentedController else {
|
|
70
|
+
target.finishDismiss(reason: targetReason)
|
|
71
|
+
dismiss(
|
|
72
|
+
remaining,
|
|
73
|
+
requestedHost: requestedHost,
|
|
74
|
+
reason: reason,
|
|
75
|
+
animated: animated
|
|
76
|
+
)
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
transitioning = true
|
|
81
|
+
controller.dismiss(animated: animated) { [weak self, weak target, weak requestedHost] in
|
|
82
|
+
guard let self else { return }
|
|
83
|
+
target?.finishDismiss(reason: targetReason)
|
|
84
|
+
guard let requestedHost else {
|
|
85
|
+
self.transitioning = false
|
|
86
|
+
self.processDeferredDismissalOrPending()
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
self.transitioning = false
|
|
90
|
+
self.dismiss(
|
|
91
|
+
remaining,
|
|
92
|
+
requestedHost: requestedHost,
|
|
93
|
+
reason: reason,
|
|
94
|
+
animated: animated
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private func processPending() {
|
|
100
|
+
compact()
|
|
101
|
+
guard !transitioning else { return }
|
|
102
|
+
guard let host = pending.first?.value else {
|
|
103
|
+
pending.removeAll { $0.value == nil }
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
pending.removeFirst()
|
|
107
|
+
guard host.shouldPresent else {
|
|
108
|
+
processPending()
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
guard let presenter = topViewController() else {
|
|
112
|
+
processPending()
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let controller = host.makeController()
|
|
117
|
+
transitioning = true
|
|
118
|
+
presenter.present(controller, animated: true) { [weak self, weak host, weak controller] in
|
|
119
|
+
guard let self else { return }
|
|
120
|
+
self.transitioning = false
|
|
121
|
+
if let host, controller?.presentingViewController != nil {
|
|
122
|
+
self.active.append(WeakSheetHost(host))
|
|
123
|
+
if host.shouldPresent {
|
|
124
|
+
host.finishPresent()
|
|
125
|
+
} else {
|
|
126
|
+
self.queueDismiss(
|
|
127
|
+
host,
|
|
128
|
+
reason: host.securityBlocked ? "security" : "system",
|
|
129
|
+
animated: false
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
host?.finishDismiss(reason: "system")
|
|
134
|
+
}
|
|
135
|
+
self.processDeferredDismissalOrPending()
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private func queueDismiss(
|
|
140
|
+
_ host: NativeSheetContainerView,
|
|
141
|
+
reason: String,
|
|
142
|
+
animated: Bool
|
|
143
|
+
) {
|
|
144
|
+
deferredDismissals.removeAll { $0.host.value == nil || $0.host.value === host }
|
|
145
|
+
deferredDismissals.append((WeakSheetHost(host), reason, animated))
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private func processDeferredDismissalOrPending() {
|
|
149
|
+
compact()
|
|
150
|
+
guard !transitioning else { return }
|
|
151
|
+
while !deferredDismissals.isEmpty {
|
|
152
|
+
let request = deferredDismissals.removeFirst()
|
|
153
|
+
if let host = request.host.value {
|
|
154
|
+
dismiss(host, reason: request.reason, animated: request.animated)
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
processPending()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private func compact() {
|
|
162
|
+
active.removeAll { $0.value == nil }
|
|
163
|
+
pending.removeAll { $0.value == nil }
|
|
164
|
+
deferredDismissals.removeAll { $0.host.value == nil }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private func topViewController() -> UIViewController? {
|
|
168
|
+
let foregroundScenes = UIApplication.shared.connectedScenes
|
|
169
|
+
.compactMap { $0 as? UIWindowScene }
|
|
170
|
+
.filter { $0.activationState == .foregroundActive }
|
|
171
|
+
let window = foregroundScenes
|
|
172
|
+
.flatMap(\.windows)
|
|
173
|
+
.first(where: \.isKeyWindow)
|
|
174
|
+
?? foregroundScenes.flatMap(\.windows).first(where: { !$0.isHidden })
|
|
175
|
+
return topViewController(from: window?.rootViewController)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private func topViewController(from root: UIViewController?) -> UIViewController? {
|
|
179
|
+
if let presented = root?.presentedViewController, !presented.isBeingDismissed {
|
|
180
|
+
return topViewController(from: presented)
|
|
181
|
+
}
|
|
182
|
+
if let navigation = root as? UINavigationController {
|
|
183
|
+
return topViewController(from: navigation.visibleViewController)
|
|
184
|
+
}
|
|
185
|
+
if let tab = root as? UITabBarController {
|
|
186
|
+
return topViewController(from: tab.selectedViewController)
|
|
187
|
+
}
|
|
188
|
+
return root
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private final class NativeSheetViewController: UIViewController,
|
|
193
|
+
UIAdaptivePresentationControllerDelegate,
|
|
194
|
+
UIGestureRecognizerDelegate {
|
|
195
|
+
let host: NativeSheetContainerView
|
|
196
|
+
private let lockedHeight: CGFloat
|
|
197
|
+
private let backgroundColorValue: UIColor
|
|
198
|
+
private let cornerRadiusValue: CGFloat
|
|
199
|
+
private let showHandleValue: Bool
|
|
200
|
+
private let dismissOnBackdropPressValue: Bool
|
|
201
|
+
private var backdropTap: UITapGestureRecognizer?
|
|
202
|
+
|
|
203
|
+
init(
|
|
204
|
+
host: NativeSheetContainerView,
|
|
205
|
+
height: CGFloat,
|
|
206
|
+
backgroundColor: UIColor,
|
|
207
|
+
cornerRadius: CGFloat,
|
|
208
|
+
showHandle: Bool,
|
|
209
|
+
dismissOnBackdropPress: Bool
|
|
210
|
+
) {
|
|
211
|
+
self.host = host
|
|
212
|
+
self.lockedHeight = height
|
|
213
|
+
self.backgroundColorValue = backgroundColor
|
|
214
|
+
self.cornerRadiusValue = cornerRadius
|
|
215
|
+
self.showHandleValue = showHandle
|
|
216
|
+
self.dismissOnBackdropPressValue = dismissOnBackdropPress
|
|
217
|
+
super.init(nibName: nil, bundle: nil)
|
|
218
|
+
modalPresentationStyle = .pageSheet
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@available(*, unavailable)
|
|
222
|
+
required init?(coder: NSCoder) {
|
|
223
|
+
fatalError("init(coder:) has not been implemented")
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
override func loadView() {
|
|
227
|
+
view = UIView()
|
|
228
|
+
view.backgroundColor = backgroundColorValue
|
|
229
|
+
view.clipsToBounds = true
|
|
230
|
+
host.attachTouchHandler(to: view)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
deinit {
|
|
234
|
+
if isViewLoaded {
|
|
235
|
+
host.detachTouchHandler(from: view)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
override func viewDidLoad() {
|
|
240
|
+
super.viewDidLoad()
|
|
241
|
+
host.moveContent(to: view)
|
|
242
|
+
guard let sheet = sheetPresentationController else { return }
|
|
243
|
+
let identifier = UISheetPresentationController.Detent.Identifier("onekey.nativeSheet.fixed")
|
|
244
|
+
sheet.detents = [
|
|
245
|
+
.custom(identifier: identifier) { [lockedHeight] context in
|
|
246
|
+
min(max(lockedHeight, 1), context.maximumDetentValue)
|
|
247
|
+
},
|
|
248
|
+
]
|
|
249
|
+
sheet.selectedDetentIdentifier = identifier
|
|
250
|
+
sheet.prefersGrabberVisible = showHandleValue
|
|
251
|
+
sheet.preferredCornerRadius = cornerRadiusValue
|
|
252
|
+
sheet.prefersScrollingExpandsWhenScrolledToEdge = false
|
|
253
|
+
sheet.prefersEdgeAttachedInCompactHeight = true
|
|
254
|
+
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = false
|
|
255
|
+
presentationController?.delegate = self
|
|
256
|
+
isModalInPresentation = !host.dismissOnPanDown
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
override func viewDidAppear(_ animated: Bool) {
|
|
260
|
+
super.viewDidAppear(animated)
|
|
261
|
+
guard dismissOnBackdropPressValue,
|
|
262
|
+
backdropTap == nil,
|
|
263
|
+
let container = presentationController?.containerView else {
|
|
264
|
+
return
|
|
265
|
+
}
|
|
266
|
+
let recognizer = UITapGestureRecognizer(target: self, action: #selector(handleBackdropTap(_:)))
|
|
267
|
+
recognizer.cancelsTouchesInView = false
|
|
268
|
+
recognizer.delegate = self
|
|
269
|
+
container.addGestureRecognizer(recognizer)
|
|
270
|
+
backdropTap = recognizer
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
override func viewDidLayoutSubviews() {
|
|
274
|
+
super.viewDidLayoutSubviews()
|
|
275
|
+
host.layoutPresentedContent(in: view.bounds)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
|
|
279
|
+
host.dismissOnPanDown
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
|
|
283
|
+
NativeSheetPresentationCoordinator.shared.didDismissInteractively(host, reason: "pan")
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
|
|
287
|
+
guard let container = presentationController?.containerView else { return false }
|
|
288
|
+
let point = touch.location(in: container)
|
|
289
|
+
return !view.frame.contains(point)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
@objc private func handleBackdropTap(_ recognizer: UITapGestureRecognizer) {
|
|
293
|
+
guard recognizer.state == .ended else { return }
|
|
294
|
+
NativeSheetPresentationCoordinator.shared.dismiss(host, reason: "backdrop", animated: true)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
@objc final class NativeSheetContainerView: UIView {
|
|
299
|
+
@objc var open = false
|
|
300
|
+
@objc var sheetHeight: CGFloat = 0
|
|
301
|
+
@objc var securityBlocked = false
|
|
302
|
+
@objc var dismissOnPanDown = true
|
|
303
|
+
@objc var dismissOnBackdropPress = false
|
|
304
|
+
@objc var dismissOnBackPress = true
|
|
305
|
+
@objc var showHandle = true
|
|
306
|
+
@objc var cornerRadius: CGFloat = 32
|
|
307
|
+
@objc var dimAmount: CGFloat = 0.4
|
|
308
|
+
@objc var sheetBackgroundColor: UIColor?
|
|
309
|
+
@objc var touchHandler: UIGestureRecognizer?
|
|
310
|
+
@objc var onDismiss: RCTDirectEventBlock?
|
|
311
|
+
@objc var onPresented: RCTDirectEventBlock?
|
|
312
|
+
|
|
313
|
+
fileprivate var presentedController: NativeSheetViewController?
|
|
314
|
+
fileprivate var shouldPresent: Bool {
|
|
315
|
+
open && !securityBlocked && !dismissedForCurrentOpen && window != nil
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private weak var contentChild: UIView?
|
|
319
|
+
private var committedOpen = false
|
|
320
|
+
private var dismissedForCurrentOpen = false
|
|
321
|
+
private var dismissNotified = false
|
|
322
|
+
|
|
323
|
+
override func didMoveToWindow() {
|
|
324
|
+
super.didMoveToWindow()
|
|
325
|
+
if window != nil {
|
|
326
|
+
commitConfiguration()
|
|
327
|
+
} else if presentedController != nil {
|
|
328
|
+
NativeSheetPresentationCoordinator.shared.dismiss(self, reason: "system", animated: false)
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
override func layoutSubviews() {
|
|
333
|
+
super.layoutSubviews()
|
|
334
|
+
if presentedController == nil {
|
|
335
|
+
contentChild?.frame = bounds
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
@objc func insertChild(_ child: UIView, atIndex index: Int) {
|
|
340
|
+
if contentChild !== child {
|
|
341
|
+
contentChild?.removeFromSuperview()
|
|
342
|
+
}
|
|
343
|
+
contentChild = child
|
|
344
|
+
if let controller = presentedController {
|
|
345
|
+
moveContent(to: controller.view)
|
|
346
|
+
} else {
|
|
347
|
+
child.removeFromSuperview()
|
|
348
|
+
addSubview(child)
|
|
349
|
+
child.frame = bounds
|
|
350
|
+
child.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
@objc func removeChild(_ child: UIView) {
|
|
355
|
+
guard contentChild === child else { return }
|
|
356
|
+
child.removeFromSuperview()
|
|
357
|
+
contentChild = nil
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
@objc func commitConfiguration() {
|
|
361
|
+
let openChanged = committedOpen != open
|
|
362
|
+
committedOpen = open
|
|
363
|
+
if !open {
|
|
364
|
+
dismissedForCurrentOpen = false
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if securityBlocked {
|
|
368
|
+
dismissedForCurrentOpen = open
|
|
369
|
+
NativeSheetPresentationCoordinator.shared.dismiss(self, reason: "security", animated: false)
|
|
370
|
+
} else if !open {
|
|
371
|
+
NativeSheetPresentationCoordinator.shared.dismiss(self, reason: "programmatic", animated: true)
|
|
372
|
+
} else if window != nil,
|
|
373
|
+
(openChanged || presentedController == nil),
|
|
374
|
+
!dismissedForCurrentOpen {
|
|
375
|
+
NativeSheetPresentationCoordinator.shared.present(self)
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
@objc func invalidate() {
|
|
380
|
+
NativeSheetPresentationCoordinator.shared.dismiss(self, reason: "system", animated: false)
|
|
381
|
+
onDismiss = nil
|
|
382
|
+
onPresented = nil
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
fileprivate func makeController() -> NativeSheetViewController {
|
|
386
|
+
dismissNotified = false
|
|
387
|
+
let controller = NativeSheetViewController(
|
|
388
|
+
host: self,
|
|
389
|
+
height: max(sheetHeight, 1),
|
|
390
|
+
backgroundColor: sheetBackgroundColor ?? .systemBackground,
|
|
391
|
+
cornerRadius: max(cornerRadius, 0),
|
|
392
|
+
showHandle: showHandle,
|
|
393
|
+
dismissOnBackdropPress: dismissOnBackdropPress
|
|
394
|
+
)
|
|
395
|
+
presentedController = controller
|
|
396
|
+
return controller
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
fileprivate func finishPresent() {
|
|
400
|
+
guard let controller = presentedController else { return }
|
|
401
|
+
onPresented?(["height": min(sheetHeight, controller.view.bounds.height)])
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
fileprivate func finishDismiss(reason: String) {
|
|
405
|
+
if let child = contentChild {
|
|
406
|
+
child.removeFromSuperview()
|
|
407
|
+
addSubview(child)
|
|
408
|
+
child.frame = bounds
|
|
409
|
+
child.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
410
|
+
}
|
|
411
|
+
let hadController = presentedController != nil
|
|
412
|
+
presentedController = nil
|
|
413
|
+
dismissedForCurrentOpen = committedOpen
|
|
414
|
+
if hadController && !dismissNotified {
|
|
415
|
+
dismissNotified = true
|
|
416
|
+
onDismiss?(["reason": reason])
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
fileprivate func moveContent(to parent: UIView) {
|
|
421
|
+
guard let child = contentChild else { return }
|
|
422
|
+
child.removeFromSuperview()
|
|
423
|
+
parent.addSubview(child)
|
|
424
|
+
child.frame = parent.bounds
|
|
425
|
+
child.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
fileprivate func layoutPresentedContent(in bounds: CGRect) {
|
|
429
|
+
contentChild?.frame = bounds
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
fileprivate func attachTouchHandler(to view: UIView) {
|
|
433
|
+
guard let touchHandler, touchHandler.view == nil else { return }
|
|
434
|
+
touchHandler.perform(NSSelectorFromString("attachToView:"), with: view)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
fileprivate func detachTouchHandler(from view: UIView) {
|
|
438
|
+
guard let touchHandler, touchHandler.view === view else { return }
|
|
439
|
+
touchHandler.perform(NSSelectorFromString("detachFromView:"), with: view)
|
|
440
|
+
}
|
|
441
|
+
}
|