mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import ExpoModulesCore
2import UIKit
3
4class SheetView: ExpoView, UISheetPresentationControllerDelegate {
5 // Views
6 private var sheetVc: SheetViewController?
7 private var innerView: UIView?
8 private var touchHandler: RCTTouchHandler?
9
10 // Events
11 private let onAttemptDismiss = EventDispatcher()
12 private let onSnapPointChange = EventDispatcher()
13 private let onStateChange = EventDispatcher()
14
15 // Open event firing
16 private var isOpen: Bool = false {
17 didSet {
18 onStateChange([
19 "state": isOpen ? "open" : "closed"
20 ])
21 }
22 }
23
24 // React view props
25 var preventDismiss = false
26 var preventExpansion = false
27 var cornerRadius: CGFloat?
28 var minHeight = 0.0
29 var maxHeight: CGFloat! {
30 didSet {
31 let screenHeight = Util.getScreenHeight() ?? 0
32 if maxHeight > screenHeight {
33 maxHeight = screenHeight
34 }
35 }
36 }
37
38 private var isOpening = false {
39 didSet {
40 if isOpening {
41 onStateChange([
42 "state": "opening"
43 ])
44 }
45 }
46 }
47 private var isClosing = false {
48 didSet {
49 if isClosing {
50 onStateChange([
51 "state": "closing"
52 ])
53 }
54 }
55 }
56 private var selectedDetentIdentifier: UISheetPresentationController.Detent.Identifier? {
57 didSet {
58 if selectedDetentIdentifier == .large {
59 onSnapPointChange([
60 "snapPoint": 2
61 ])
62 } else {
63 onSnapPointChange([
64 "snapPoint": 1
65 ])
66 }
67 }
68 }
69 private var prevLayoutDetentIdentifier: UISheetPresentationController.Detent.Identifier?
70
71 // MARK: - Lifecycle
72
73 required init (appContext: AppContext? = nil) {
74 super.init(appContext: appContext)
75 self.maxHeight = Util.getScreenHeight()
76 self.touchHandler = RCTTouchHandler(bridge: appContext?.reactBridge)
77 SheetManager.shared.add(self)
78 }
79
80 deinit {
81 self.destroy()
82 }
83
84 // We don't want this view to actually get added to the tree, so we'll simply store it for adding
85 // to the SheetViewController
86 override func insertReactSubview(_ subview: UIView!, at atIndex: Int) {
87 self.touchHandler?.attach(to: subview)
88 self.innerView = subview
89 }
90
91 // We'll grab the content height from here so we know the initial detent to set
92 override func layoutSubviews() {
93 super.layoutSubviews()
94
95 guard let innerView = self.innerView else {
96 return
97 }
98
99 if innerView.subviews.count != 1 {
100 return
101 }
102
103 self.present()
104 }
105
106 private func destroy() {
107 self.isClosing = false
108 self.isOpen = false
109 self.sheetVc = nil
110 self.touchHandler?.detach(from: self.innerView)
111 self.touchHandler = nil
112 self.innerView = nil
113 SheetManager.shared.remove(self)
114 }
115
116 // MARK: - Presentation
117
118 func present() {
119 guard !self.isOpen,
120 !self.isOpening,
121 !self.isClosing,
122 let innerView = self.innerView,
123 let contentHeight = innerView.subviews.first?.frame.height,
124 let rvc = self.reactViewController() else {
125 return
126 }
127
128 let sheetVc = SheetViewController()
129 sheetVc.setDetents(contentHeight: self.clampHeight(contentHeight), preventExpansion: self.preventExpansion)
130 if let sheet = sheetVc.sheetPresentationController {
131 sheet.delegate = self
132 sheet.preferredCornerRadius = self.cornerRadius
133 self.selectedDetentIdentifier = sheet.selectedDetentIdentifier
134 }
135 sheetVc.view.addSubview(innerView)
136
137 self.sheetVc = sheetVc
138 self.isOpening = true
139
140 rvc.present(sheetVc, animated: true) { [weak self] in
141 self?.isOpening = false
142 self?.isOpen = true
143 }
144 }
145
146 func updateLayout() {
147 if self.prevLayoutDetentIdentifier == self.selectedDetentIdentifier,
148 let contentHeight = self.innerView?.subviews.first?.frame.size.height {
149 self.sheetVc?.updateDetents(contentHeight: self.clampHeight(contentHeight),
150 preventExpansion: self.preventExpansion)
151 self.selectedDetentIdentifier = self.sheetVc?.getCurrentDetentIdentifier()
152 }
153 self.prevLayoutDetentIdentifier = self.selectedDetentIdentifier
154 }
155
156 func dismiss() {
157 self.isClosing = true
158 self.sheetVc?.dismiss(animated: true) { [weak self] in
159 self?.destroy()
160 }
161 }
162
163 // MARK: - Utils
164
165 private func clampHeight(_ height: CGFloat) -> CGFloat {
166 if height < self.minHeight {
167 return self.minHeight
168 } else if height > self.maxHeight {
169 return self.maxHeight
170 }
171 return height
172 }
173
174 // MARK: - UISheetPresentationControllerDelegate
175
176 func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
177 self.onAttemptDismiss()
178 return !self.preventDismiss
179 }
180
181 func presentationControllerWillDismiss(_ presentationController: UIPresentationController) {
182 self.isClosing = true
183 }
184
185 func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
186 self.destroy()
187 }
188
189 func sheetPresentationControllerDidChangeSelectedDetentIdentifier(_ sheetPresentationController: UISheetPresentationController) {
190 self.selectedDetentIdentifier = sheetPresentationController.selectedDetentIdentifier
191 }
192}