@lodev09/react-native-true-sheet 3.5.0 → 3.5.1-beta.1

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.
@@ -1,7 +1,6 @@
1
1
  package com.lodev09.truesheet
2
2
 
3
3
  import android.annotation.SuppressLint
4
- import android.util.Log
5
4
  import android.view.View
6
5
  import android.view.ViewStructure
7
6
  import android.view.accessibility.AccessibilityEvent
@@ -30,6 +29,12 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
30
29
  TrueSheetViewControllerDelegate,
31
30
  TrueSheetContainerViewDelegate {
32
31
 
32
+ companion object {
33
+ const val TAG_NAME = "TrueSheet"
34
+ }
35
+
36
+ // ==================== Properties ====================
37
+
33
38
  internal val viewController: TrueSheetViewController = TrueSheetViewController(reactContext)
34
39
 
35
40
  private val containerView: TrueSheetContainerView?
@@ -37,26 +42,28 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
37
42
 
38
43
  var eventDispatcher: EventDispatcher? = null
39
44
 
45
+ // Initial present configuration (set by ViewManager before mount)
40
46
  var initialDetentIndex: Int = -1
41
47
  var initialDetentAnimated: Boolean = true
42
48
 
43
49
  var stateWrapper: StateWrapper? = null
44
50
  set(value) {
45
- // Immediately update state with screen width during first state update
46
- // This ensures we have initial width for content layout before presenting
51
+ // On first state wrapper assignment, immediately update state with screen dimensions.
52
+ // This ensures Yoga has initial width/height for content layout before presenting.
47
53
  if (field == null && value != null) {
48
54
  updateState(viewController.screenWidth, viewController.screenHeight)
49
55
  }
50
56
  field = value
51
57
  }
52
58
 
53
- // Track last dimensions to avoid unnecessary state updates
54
59
  private var lastContainerWidth: Int = 0
55
60
  private var lastContainerHeight: Int = 0
56
61
 
57
- // Flag to prevent multiple pending sheet updates
62
+ // Debounce flag to coalesce rapid layout changes into a single sheet update
58
63
  private var isSheetUpdatePending: Boolean = false
59
64
 
65
+ // ==================== Initialization ====================
66
+
60
67
  init {
61
68
  reactContext.addLifecycleEventListener(this)
62
69
  viewController.delegate = this
@@ -65,6 +72,8 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
65
72
  visibility = GONE
66
73
  }
67
74
 
75
+ // ==================== ReactViewGroup Overrides ====================
76
+
68
77
  override fun dispatchProvideStructure(structure: ViewStructure) {
69
78
  super.dispatchProvideStructure(structure)
70
79
  }
@@ -76,7 +85,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
76
85
  right: Int,
77
86
  bottom: Int
78
87
  ) {
79
- // Do nothing as we are laid out by UIManager
88
+ // No-op: layout is managed by React Native's UIManager
80
89
  }
81
90
 
82
91
  override fun setId(id: Int) {
@@ -85,19 +94,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
85
94
  TrueSheetModule.registerView(this, id)
86
95
  }
87
96
 
88
- /**
89
- * Called by the manager after all properties are set.
90
- * Reconfigures the sheet if it's currently presented.
91
- */
92
- fun finalizeUpdates() {
93
- if (viewController.isPresented) {
94
- viewController.setupBackground()
95
- viewController.setupGrabber()
96
- updateSheetIfNeeded()
97
- }
98
- }
99
-
100
- // ==================== View Management ====================
97
+ // ==================== View Hierarchy Management ====================
101
98
 
102
99
  override fun addView(child: View?, index: Int) {
103
100
  viewController.addView(child, index)
@@ -106,7 +103,6 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
106
103
  child.delegate = this
107
104
  viewController.createDialog()
108
105
 
109
- // Present at initial detent after layout pass when content height is available
110
106
  if (initialDetentIndex >= 0) {
111
107
  post { present(initialDetentIndex, initialDetentAnimated) { } }
112
108
  }
@@ -117,6 +113,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
117
113
  }
118
114
 
119
115
  override fun getChildCount(): Int = viewController.childCount
116
+
120
117
  override fun getChildAt(index: Int): View? = viewController.getChildAt(index)
121
118
 
122
119
  override fun removeViewAt(index: Int) {
@@ -127,119 +124,46 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
127
124
  viewController.removeView(child)
128
125
  }
129
126
 
130
- // Accessibility events are handled by the dialog's host view
127
+ // Accessibility: delegate to dialog's host view since this view is hidden
131
128
  override fun addChildrenForAccessibility(outChildren: ArrayList<View>) {}
132
129
  override fun dispatchPopulateAccessibilityEvent(event: AccessibilityEvent): Boolean = false
133
130
 
134
- fun onDropInstance() {
135
- reactContext.removeLifecycleEventListener(this)
136
- TrueSheetModule.unregisterView(id)
137
- TrueSheetDialogObserver.removeSheet(this)
138
-
139
- if (viewController.isPresented) {
140
- viewController.dismiss()
141
- }
142
- viewController.delegate = null
143
- }
131
+ // ==================== Lifecycle ====================
144
132
 
145
133
  override fun onHostResume() {
146
134
  finalizeUpdates()
147
135
  }
148
136
 
149
- override fun onHostPause() {
150
- }
137
+ override fun onHostPause() {}
151
138
 
152
139
  override fun onHostDestroy() {
153
140
  onDropInstance()
154
141
  }
155
142
 
156
- // ==================== TrueSheetViewControllerDelegate Implementation ====================
157
-
158
- override fun viewControllerWillPresent(index: Int, position: Float, detent: Float) {
159
- val surfaceId = UIManagerHelper.getSurfaceId(this)
160
- eventDispatcher?.dispatchEvent(WillPresentEvent(surfaceId, id, index, position, detent))
161
-
162
- // Enable touch event dispatching to React Native
163
- viewController.eventDispatcher = eventDispatcher
164
- containerView?.footerView?.eventDispatcher = eventDispatcher
165
- }
166
-
167
- override fun viewControllerDidPresent(index: Int, position: Float, detent: Float) {
168
- val surfaceId = UIManagerHelper.getSurfaceId(this)
169
- eventDispatcher?.dispatchEvent(DidPresentEvent(surfaceId, id, index, position, detent))
170
- }
171
-
172
- override fun viewControllerWillDismiss() {
173
- val surfaceId = UIManagerHelper.getSurfaceId(this)
174
- eventDispatcher?.dispatchEvent(WillDismissEvent(surfaceId, id))
175
-
176
- // Disable touch event dispatching
177
- viewController.eventDispatcher = null
178
- containerView?.footerView?.eventDispatcher = null
179
- }
180
-
181
- override fun viewControllerDidDismiss(hadParent: Boolean) {
182
- val surfaceId = UIManagerHelper.getSurfaceId(this)
183
- eventDispatcher?.dispatchEvent(DidDismissEvent(surfaceId, id))
184
-
185
- TrueSheetDialogObserver.onSheetDidDismiss(this, hadParent)
186
- }
187
-
188
- override fun viewControllerDidChangeDetent(index: Int, position: Float, detent: Float) {
189
- val surfaceId = UIManagerHelper.getSurfaceId(this)
190
- eventDispatcher?.dispatchEvent(DetentChangeEvent(surfaceId, id, index, position, detent))
191
- }
192
-
193
- override fun viewControllerDidDragBegin(index: Int, position: Float, detent: Float) {
194
- val surfaceId = UIManagerHelper.getSurfaceId(this)
195
- eventDispatcher?.dispatchEvent(DragBeginEvent(surfaceId, id, index, position, detent))
196
- }
197
-
198
- override fun viewControllerDidDragChange(index: Int, position: Float, detent: Float) {
199
- val surfaceId = UIManagerHelper.getSurfaceId(this)
200
- eventDispatcher?.dispatchEvent(DragChangeEvent(surfaceId, id, index, position, detent))
201
- }
202
-
203
- override fun viewControllerDidDragEnd(index: Int, position: Float, detent: Float) {
204
- val surfaceId = UIManagerHelper.getSurfaceId(this)
205
- eventDispatcher?.dispatchEvent(DragEndEvent(surfaceId, id, index, position, detent))
206
- }
207
-
208
- override fun viewControllerDidChangePosition(index: Float, position: Float, detent: Float, realtime: Boolean) {
209
- val surfaceId = UIManagerHelper.getSurfaceId(this)
210
- eventDispatcher?.dispatchEvent(PositionChangeEvent(surfaceId, id, index, position, detent, realtime))
211
- }
212
-
213
- override fun viewControllerDidChangeSize(width: Int, height: Int) {
214
- updateState(width, height)
215
- }
216
-
217
- override fun viewControllerWillFocus() {
218
- val surfaceId = UIManagerHelper.getSurfaceId(this)
219
- eventDispatcher?.dispatchEvent(WillFocusEvent(surfaceId, id))
220
- }
221
-
222
- override fun viewControllerDidFocus() {
223
- val surfaceId = UIManagerHelper.getSurfaceId(this)
224
- eventDispatcher?.dispatchEvent(FocusEvent(surfaceId, id))
225
- }
226
-
227
- override fun viewControllerWillBlur() {
228
- val surfaceId = UIManagerHelper.getSurfaceId(this)
229
- eventDispatcher?.dispatchEvent(WillBlurEvent(surfaceId, id))
230
- }
143
+ fun onDropInstance() {
144
+ reactContext.removeLifecycleEventListener(this)
145
+ TrueSheetModule.unregisterView(id)
146
+ TrueSheetDialogObserver.removeSheet(this)
231
147
 
232
- override fun viewControllerDidBlur() {
233
- val surfaceId = UIManagerHelper.getSurfaceId(this)
234
- eventDispatcher?.dispatchEvent(BlurEvent(surfaceId, id))
148
+ if (viewController.isPresented) {
149
+ viewController.dismiss()
150
+ }
151
+ viewController.delegate = null
235
152
  }
236
153
 
237
- override fun viewControllerDidBackPress() {
238
- val surfaceId = UIManagerHelper.getSurfaceId(this)
239
- eventDispatcher?.dispatchEvent(BackPressEvent(surfaceId, id))
154
+ /**
155
+ * Called by the ViewManager after all properties are set.
156
+ * Reconfigures the sheet if it's currently presented.
157
+ */
158
+ fun finalizeUpdates() {
159
+ if (viewController.isPresented) {
160
+ viewController.setupBackground()
161
+ viewController.setupGrabber()
162
+ updateSheetIfNeeded()
163
+ }
240
164
  }
241
165
 
242
- // ==================== Property Setters (forward to controller) ====================
166
+ // ==================== Property Setters ====================
243
167
 
244
168
  fun setMaxHeight(height: Int) {
245
169
  if (viewController.maxSheetHeight == height) return
@@ -293,6 +217,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
293
217
  fun setDetents(newDetents: MutableList<Double>) {
294
218
  viewController.detents = newDetents
295
219
  }
220
+
296
221
  fun setEdgeToEdgeFullScreen(edgeToEdgeFullScreen: Boolean) {
297
222
  viewController.edgeToEdgeFullScreen = edgeToEdgeFullScreen
298
223
  }
@@ -305,6 +230,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
305
230
 
306
231
  /**
307
232
  * Updates the Fabric state with container dimensions for Yoga layout.
233
+ * Converts pixel values to density-independent pixels (dp).
308
234
  */
309
235
  fun updateState(width: Int, height: Int) {
310
236
  if (width == lastContainerWidth && height == lastContainerHeight) return
@@ -319,9 +245,12 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
319
245
  sw.updateState(newStateData)
320
246
  }
321
247
 
248
+ // ==================== Sheet Actions ====================
249
+
322
250
  @UiThread
323
251
  fun present(detentIndex: Int, animated: Boolean = true, promiseCallback: () -> Unit) {
324
252
  if (!viewController.isPresented) {
253
+ // Register with observer to track sheet stack hierarchy
325
254
  viewController.parentSheetView = TrueSheetDialogObserver.onSheetWillPresent(this, detentIndex)
326
255
  }
327
256
  viewController.presentPromise = promiseCallback
@@ -359,7 +288,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
359
288
 
360
289
  /**
361
290
  * Debounced sheet update to handle rapid content/header size changes.
362
- * Uses post to ensure all layout passes complete before reconfiguring.
291
+ * Uses post() to ensure all layout passes complete before reconfiguring.
363
292
  */
364
293
  fun updateSheetIfNeeded() {
365
294
  if (!viewController.isPresented) return
@@ -373,25 +302,12 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
373
302
  }
374
303
  }
375
304
 
376
- // ==================== TrueSheetContainerViewDelegate Implementation ====================
377
-
378
- override fun containerViewContentDidChangeSize(width: Int, height: Int) {
379
- updateSheetIfNeeded()
380
- }
381
-
382
- override fun containerViewHeaderDidChangeSize(width: Int, height: Int) {
383
- updateSheetIfNeeded()
384
- }
385
-
386
- override fun containerViewFooterDidChangeSize(width: Int, height: Int) {
387
- viewController.positionFooter()
388
- }
389
-
390
- // ==================== Stack Translation ====================
305
+ // ==================== Sheet Stack Translation ====================
391
306
 
392
307
  /**
393
- * Updates this sheet's translation based on its direct child's position.
394
- * Propagates additional translation to parent so it stays behind this sheet.
308
+ * Updates this sheet's translation based on its child sheet's position.
309
+ * When a child sheet is presented, parent sheets slide down to create a stacked appearance.
310
+ * Propagates additional translation to parent so the entire stack stays visually consistent.
395
311
  */
396
312
  fun updateTranslationForChild(childSheetTop: Int) {
397
313
  if (!viewController.isDialogVisible || viewController.isExpanded) return
@@ -402,13 +318,14 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
402
318
 
403
319
  viewController.translateDialog(newTranslation)
404
320
 
321
+ // Propagate any additional translation up the stack
405
322
  if (additionalTranslation > 0) {
406
323
  TrueSheetDialogObserver.getParentSheet(this)?.addTranslation(additionalTranslation)
407
324
  }
408
325
  }
409
326
 
410
327
  /**
411
- * Adds translation to this sheet and propagates to parent.
328
+ * Recursively adds translation to this sheet and all parent sheets.
412
329
  */
413
330
  private fun addTranslation(amount: Int) {
414
331
  if (!viewController.isDialogVisible || viewController.isExpanded) return
@@ -417,7 +334,104 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
417
334
  TrueSheetDialogObserver.getParentSheet(this)?.addTranslation(amount)
418
335
  }
419
336
 
420
- companion object {
421
- const val TAG_NAME = "TrueSheet"
337
+ // ==================== TrueSheetViewControllerDelegate ====================
338
+
339
+ override fun viewControllerWillPresent(index: Int, position: Float, detent: Float) {
340
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
341
+ eventDispatcher?.dispatchEvent(WillPresentEvent(surfaceId, id, index, position, detent))
342
+
343
+ // Enable touch event dispatching to React Native while sheet is visible
344
+ viewController.eventDispatcher = eventDispatcher
345
+ containerView?.footerView?.eventDispatcher = eventDispatcher
346
+ }
347
+
348
+ override fun viewControllerDidPresent(index: Int, position: Float, detent: Float) {
349
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
350
+ eventDispatcher?.dispatchEvent(DidPresentEvent(surfaceId, id, index, position, detent))
351
+ }
352
+
353
+ override fun viewControllerWillDismiss() {
354
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
355
+ eventDispatcher?.dispatchEvent(WillDismissEvent(surfaceId, id))
356
+
357
+ // Disable touch event dispatching when sheet is dismissing
358
+ viewController.eventDispatcher = null
359
+ containerView?.footerView?.eventDispatcher = null
360
+ }
361
+
362
+ override fun viewControllerDidDismiss(hadParent: Boolean) {
363
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
364
+ eventDispatcher?.dispatchEvent(DidDismissEvent(surfaceId, id))
365
+
366
+ TrueSheetDialogObserver.onSheetDidDismiss(this, hadParent)
367
+ }
368
+
369
+ override fun viewControllerDidChangeDetent(index: Int, position: Float, detent: Float) {
370
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
371
+ eventDispatcher?.dispatchEvent(DetentChangeEvent(surfaceId, id, index, position, detent))
372
+ }
373
+
374
+ override fun viewControllerDidDragBegin(index: Int, position: Float, detent: Float) {
375
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
376
+ eventDispatcher?.dispatchEvent(DragBeginEvent(surfaceId, id, index, position, detent))
377
+ }
378
+
379
+ override fun viewControllerDidDragChange(index: Int, position: Float, detent: Float) {
380
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
381
+ eventDispatcher?.dispatchEvent(DragChangeEvent(surfaceId, id, index, position, detent))
382
+ }
383
+
384
+ override fun viewControllerDidDragEnd(index: Int, position: Float, detent: Float) {
385
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
386
+ eventDispatcher?.dispatchEvent(DragEndEvent(surfaceId, id, index, position, detent))
387
+ }
388
+
389
+ override fun viewControllerDidChangePosition(index: Float, position: Float, detent: Float, realtime: Boolean) {
390
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
391
+ eventDispatcher?.dispatchEvent(PositionChangeEvent(surfaceId, id, index, position, detent, realtime))
392
+ }
393
+
394
+ override fun viewControllerDidChangeSize(width: Int, height: Int) {
395
+ updateState(width, height)
396
+ }
397
+
398
+ override fun viewControllerWillFocus() {
399
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
400
+ eventDispatcher?.dispatchEvent(WillFocusEvent(surfaceId, id))
401
+ }
402
+
403
+ override fun viewControllerDidFocus() {
404
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
405
+ eventDispatcher?.dispatchEvent(FocusEvent(surfaceId, id))
406
+ }
407
+
408
+ override fun viewControllerWillBlur() {
409
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
410
+ eventDispatcher?.dispatchEvent(WillBlurEvent(surfaceId, id))
411
+ }
412
+
413
+ override fun viewControllerDidBlur() {
414
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
415
+ eventDispatcher?.dispatchEvent(BlurEvent(surfaceId, id))
416
+ }
417
+
418
+ override fun viewControllerDidBackPress() {
419
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
420
+ eventDispatcher?.dispatchEvent(BackPressEvent(surfaceId, id))
421
+ }
422
+
423
+ // ==================== TrueSheetContainerViewDelegate ====================
424
+
425
+ override fun containerViewContentDidChangeSize(width: Int, height: Int) {
426
+ updateSheetIfNeeded()
427
+ }
428
+
429
+ override fun containerViewHeaderDidChangeSize(width: Int, height: Int) {
430
+ updateSheetIfNeeded()
431
+ }
432
+
433
+ override fun containerViewFooterDidChangeSize(width: Int, height: Int) {
434
+ // Footer changes don't affect detents, only reposition it
435
+ viewController.positionFooter()
422
436
  }
423
437
  }