@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.
@@ -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
+ }
@@ -0,0 +1,11 @@
1
+ #ifdef RCT_NEW_ARCH_ENABLED
2
+ #import <React/RCTViewComponentView.h>
3
+ #import <UIKit/UIKit.h>
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ @interface RCTNativeSheetComponentView : RCTViewComponentView
8
+ @end
9
+
10
+ NS_ASSUME_NONNULL_END
11
+ #endif