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

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,29 @@ 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)
46
+ var pendingInitialPresent: Boolean = false
40
47
  var initialDetentIndex: Int = -1
41
48
  var initialDetentAnimated: Boolean = true
42
49
 
43
50
  var stateWrapper: StateWrapper? = null
44
51
  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
52
+ // On first state wrapper assignment, immediately update state with screen dimensions.
53
+ // This ensures Yoga has initial width/height for content layout before presenting.
47
54
  if (field == null && value != null) {
48
55
  updateState(viewController.screenWidth, viewController.screenHeight)
49
56
  }
50
57
  field = value
51
58
  }
52
59
 
53
- // Track last dimensions to avoid unnecessary state updates
54
60
  private var lastContainerWidth: Int = 0
55
61
  private var lastContainerHeight: Int = 0
56
62
 
57
- // Flag to prevent multiple pending sheet updates
63
+ // Debounce flag to coalesce rapid layout changes into a single sheet update
58
64
  private var isSheetUpdatePending: Boolean = false
59
65
 
66
+ // ==================== Initialization ====================
67
+
60
68
  init {
61
69
  reactContext.addLifecycleEventListener(this)
62
70
  viewController.delegate = this
@@ -65,6 +73,8 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
65
73
  visibility = GONE
66
74
  }
67
75
 
76
+ // ==================== ReactViewGroup Overrides ====================
77
+
68
78
  override fun dispatchProvideStructure(structure: ViewStructure) {
69
79
  super.dispatchProvideStructure(structure)
70
80
  }
@@ -76,7 +86,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
76
86
  right: Int,
77
87
  bottom: Int
78
88
  ) {
79
- // Do nothing as we are laid out by UIManager
89
+ // No-op: layout is managed by React Native's UIManager
80
90
  }
81
91
 
82
92
  override fun setId(id: Int) {
@@ -85,19 +95,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
85
95
  TrueSheetModule.registerView(this, id)
86
96
  }
87
97
 
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 ====================
98
+ // ==================== View Hierarchy Management ====================
101
99
 
102
100
  override fun addView(child: View?, index: Int) {
103
101
  viewController.addView(child, index)
@@ -106,9 +104,8 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
106
104
  child.delegate = this
107
105
  viewController.createDialog()
108
106
 
109
- // Present at initial detent after layout pass when content height is available
110
107
  if (initialDetentIndex >= 0) {
111
- post { present(initialDetentIndex, initialDetentAnimated) { } }
108
+ pendingInitialPresent = true
112
109
  }
113
110
 
114
111
  val surfaceId = UIManagerHelper.getSurfaceId(this)
@@ -117,6 +114,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
117
114
  }
118
115
 
119
116
  override fun getChildCount(): Int = viewController.childCount
117
+
120
118
  override fun getChildAt(index: Int): View? = viewController.getChildAt(index)
121
119
 
122
120
  override fun removeViewAt(index: Int) {
@@ -127,119 +125,46 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
127
125
  viewController.removeView(child)
128
126
  }
129
127
 
130
- // Accessibility events are handled by the dialog's host view
128
+ // Accessibility: delegate to dialog's host view since this view is hidden
131
129
  override fun addChildrenForAccessibility(outChildren: ArrayList<View>) {}
132
130
  override fun dispatchPopulateAccessibilityEvent(event: AccessibilityEvent): Boolean = false
133
131
 
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
- }
132
+ // ==================== Lifecycle ====================
144
133
 
145
134
  override fun onHostResume() {
146
135
  finalizeUpdates()
147
136
  }
148
137
 
149
- override fun onHostPause() {
150
- }
138
+ override fun onHostPause() {}
151
139
 
152
140
  override fun onHostDestroy() {
153
141
  onDropInstance()
154
142
  }
155
143
 
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
- }
144
+ fun onDropInstance() {
145
+ reactContext.removeLifecycleEventListener(this)
146
+ TrueSheetModule.unregisterView(id)
147
+ TrueSheetDialogObserver.removeSheet(this)
231
148
 
232
- override fun viewControllerDidBlur() {
233
- val surfaceId = UIManagerHelper.getSurfaceId(this)
234
- eventDispatcher?.dispatchEvent(BlurEvent(surfaceId, id))
149
+ if (viewController.isPresented) {
150
+ viewController.dismiss()
151
+ }
152
+ viewController.delegate = null
235
153
  }
236
154
 
237
- override fun viewControllerDidBackPress() {
238
- val surfaceId = UIManagerHelper.getSurfaceId(this)
239
- eventDispatcher?.dispatchEvent(BackPressEvent(surfaceId, id))
155
+ /**
156
+ * Called by the ViewManager after all properties are set.
157
+ * Reconfigures the sheet if it's currently presented.
158
+ */
159
+ fun finalizeUpdates() {
160
+ if (viewController.isPresented) {
161
+ viewController.setupBackground()
162
+ viewController.setupGrabber()
163
+ updateSheetIfNeeded()
164
+ }
240
165
  }
241
166
 
242
- // ==================== Property Setters (forward to controller) ====================
167
+ // ==================== Property Setters ====================
243
168
 
244
169
  fun setMaxHeight(height: Int) {
245
170
  if (viewController.maxSheetHeight == height) return
@@ -293,6 +218,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
293
218
  fun setDetents(newDetents: MutableList<Double>) {
294
219
  viewController.detents = newDetents
295
220
  }
221
+
296
222
  fun setEdgeToEdgeFullScreen(edgeToEdgeFullScreen: Boolean) {
297
223
  viewController.edgeToEdgeFullScreen = edgeToEdgeFullScreen
298
224
  }
@@ -305,6 +231,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
305
231
 
306
232
  /**
307
233
  * Updates the Fabric state with container dimensions for Yoga layout.
234
+ * Converts pixel values to density-independent pixels (dp).
308
235
  */
309
236
  fun updateState(width: Int, height: Int) {
310
237
  if (width == lastContainerWidth && height == lastContainerHeight) return
@@ -319,9 +246,12 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
319
246
  sw.updateState(newStateData)
320
247
  }
321
248
 
249
+ // ==================== Sheet Actions ====================
250
+
322
251
  @UiThread
323
252
  fun present(detentIndex: Int, animated: Boolean = true, promiseCallback: () -> Unit) {
324
253
  if (!viewController.isPresented) {
254
+ // Register with observer to track sheet stack hierarchy
325
255
  viewController.parentSheetView = TrueSheetDialogObserver.onSheetWillPresent(this, detentIndex)
326
256
  }
327
257
  viewController.presentPromise = promiseCallback
@@ -359,10 +289,19 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
359
289
 
360
290
  /**
361
291
  * Debounced sheet update to handle rapid content/header size changes.
362
- * Uses post to ensure all layout passes complete before reconfiguring.
292
+ * Uses post() to ensure all layout passes complete before reconfiguring.
363
293
  */
364
294
  fun updateSheetIfNeeded() {
365
- if (!viewController.isPresented) return
295
+ if (!viewController.isPresented) {
296
+ // Handle initial present if pending
297
+ if (pendingInitialPresent) {
298
+ viewController.setupSheetDetents()
299
+ present(initialDetentIndex, initialDetentAnimated) { }
300
+ pendingInitialPresent = false
301
+ }
302
+ return
303
+ }
304
+
366
305
  if (isSheetUpdatePending) return
367
306
 
368
307
  isSheetUpdatePending = true
@@ -373,25 +312,12 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
373
312
  }
374
313
  }
375
314
 
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 ====================
315
+ // ==================== Sheet Stack Translation ====================
391
316
 
392
317
  /**
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.
318
+ * Updates this sheet's translation based on its child sheet's position.
319
+ * When a child sheet is presented, parent sheets slide down to create a stacked appearance.
320
+ * Propagates additional translation to parent so the entire stack stays visually consistent.
395
321
  */
396
322
  fun updateTranslationForChild(childSheetTop: Int) {
397
323
  if (!viewController.isDialogVisible || viewController.isExpanded) return
@@ -402,13 +328,14 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
402
328
 
403
329
  viewController.translateDialog(newTranslation)
404
330
 
331
+ // Propagate any additional translation up the stack
405
332
  if (additionalTranslation > 0) {
406
333
  TrueSheetDialogObserver.getParentSheet(this)?.addTranslation(additionalTranslation)
407
334
  }
408
335
  }
409
336
 
410
337
  /**
411
- * Adds translation to this sheet and propagates to parent.
338
+ * Recursively adds translation to this sheet and all parent sheets.
412
339
  */
413
340
  private fun addTranslation(amount: Int) {
414
341
  if (!viewController.isDialogVisible || viewController.isExpanded) return
@@ -417,7 +344,104 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
417
344
  TrueSheetDialogObserver.getParentSheet(this)?.addTranslation(amount)
418
345
  }
419
346
 
420
- companion object {
421
- const val TAG_NAME = "TrueSheet"
347
+ // ==================== TrueSheetViewControllerDelegate ====================
348
+
349
+ override fun viewControllerWillPresent(index: Int, position: Float, detent: Float) {
350
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
351
+ eventDispatcher?.dispatchEvent(WillPresentEvent(surfaceId, id, index, position, detent))
352
+
353
+ // Enable touch event dispatching to React Native while sheet is visible
354
+ viewController.eventDispatcher = eventDispatcher
355
+ containerView?.footerView?.eventDispatcher = eventDispatcher
356
+ }
357
+
358
+ override fun viewControllerDidPresent(index: Int, position: Float, detent: Float) {
359
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
360
+ eventDispatcher?.dispatchEvent(DidPresentEvent(surfaceId, id, index, position, detent))
361
+ }
362
+
363
+ override fun viewControllerWillDismiss() {
364
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
365
+ eventDispatcher?.dispatchEvent(WillDismissEvent(surfaceId, id))
366
+
367
+ // Disable touch event dispatching when sheet is dismissing
368
+ viewController.eventDispatcher = null
369
+ containerView?.footerView?.eventDispatcher = null
370
+ }
371
+
372
+ override fun viewControllerDidDismiss(hadParent: Boolean) {
373
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
374
+ eventDispatcher?.dispatchEvent(DidDismissEvent(surfaceId, id))
375
+
376
+ TrueSheetDialogObserver.onSheetDidDismiss(this, hadParent)
377
+ }
378
+
379
+ override fun viewControllerDidChangeDetent(index: Int, position: Float, detent: Float) {
380
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
381
+ eventDispatcher?.dispatchEvent(DetentChangeEvent(surfaceId, id, index, position, detent))
382
+ }
383
+
384
+ override fun viewControllerDidDragBegin(index: Int, position: Float, detent: Float) {
385
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
386
+ eventDispatcher?.dispatchEvent(DragBeginEvent(surfaceId, id, index, position, detent))
387
+ }
388
+
389
+ override fun viewControllerDidDragChange(index: Int, position: Float, detent: Float) {
390
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
391
+ eventDispatcher?.dispatchEvent(DragChangeEvent(surfaceId, id, index, position, detent))
392
+ }
393
+
394
+ override fun viewControllerDidDragEnd(index: Int, position: Float, detent: Float) {
395
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
396
+ eventDispatcher?.dispatchEvent(DragEndEvent(surfaceId, id, index, position, detent))
397
+ }
398
+
399
+ override fun viewControllerDidChangePosition(index: Float, position: Float, detent: Float, realtime: Boolean) {
400
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
401
+ eventDispatcher?.dispatchEvent(PositionChangeEvent(surfaceId, id, index, position, detent, realtime))
402
+ }
403
+
404
+ override fun viewControllerDidChangeSize(width: Int, height: Int) {
405
+ updateState(width, height)
406
+ }
407
+
408
+ override fun viewControllerWillFocus() {
409
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
410
+ eventDispatcher?.dispatchEvent(WillFocusEvent(surfaceId, id))
411
+ }
412
+
413
+ override fun viewControllerDidFocus() {
414
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
415
+ eventDispatcher?.dispatchEvent(FocusEvent(surfaceId, id))
416
+ }
417
+
418
+ override fun viewControllerWillBlur() {
419
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
420
+ eventDispatcher?.dispatchEvent(WillBlurEvent(surfaceId, id))
421
+ }
422
+
423
+ override fun viewControllerDidBlur() {
424
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
425
+ eventDispatcher?.dispatchEvent(BlurEvent(surfaceId, id))
426
+ }
427
+
428
+ override fun viewControllerDidBackPress() {
429
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
430
+ eventDispatcher?.dispatchEvent(BackPressEvent(surfaceId, id))
431
+ }
432
+
433
+ // ==================== TrueSheetContainerViewDelegate ====================
434
+
435
+ override fun containerViewContentDidChangeSize(width: Int, height: Int) {
436
+ updateSheetIfNeeded()
437
+ }
438
+
439
+ override fun containerViewHeaderDidChangeSize(width: Int, height: Int) {
440
+ updateSheetIfNeeded()
441
+ }
442
+
443
+ override fun containerViewFooterDidChangeSize(width: Int, height: Int) {
444
+ // Footer changes don't affect detents, only reposition it
445
+ viewController.positionFooter()
422
446
  }
423
447
  }
@@ -80,7 +80,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
80
80
  const val TAG_NAME = "TrueSheet"
81
81
 
82
82
  // Prevents fully expanded ratio which causes behavior issues
83
- private const val MAX_HALF_EXPANDED_RATIO = 0.999f
84
83
  private const val GRABBER_TAG = "TrueSheetGrabber"
85
84
  private const val DEFAULT_MAX_WIDTH = 640 // dp
86
85
  private const val DEFAULT_CORNER_RADIUS = 16 // dp
@@ -341,9 +340,15 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
341
340
  private fun setupDialogListeners(dialog: BottomSheetDialog) {
342
341
  dialog.setOnShowListener {
343
342
  bottomSheetView?.visibility = VISIBLE
343
+
344
344
  isPresented = true
345
345
  isDialogVisible = true
346
346
 
347
+ emitWillPresentEvents()
348
+
349
+ setupSheetDetents()
350
+ setupBackground()
351
+ setupGrabber()
347
352
  setupKeyboardObserver()
348
353
 
349
354
  if (shouldAnimatePresent) {
@@ -377,8 +382,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
377
382
  override fun onSlide(sheetView: View, slideOffset: Float) {
378
383
  val behavior = behavior ?: return
379
384
 
380
- emitChangePositionDelegate(sheetView.top)
381
-
382
385
  when (behavior.state) {
383
386
  BottomSheetBehavior.STATE_DRAGGING,
384
387
  BottomSheetBehavior.STATE_SETTLING -> handleDragChange(sheetView)
@@ -386,9 +389,13 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
386
389
  else -> { }
387
390
  }
388
391
 
389
- if (!isKeyboardTransitioning) {
390
- positionFooter(slideOffset)
391
- updateDimAmount(sheetView.top)
392
+ if (!sheetAnimator.isAnimating) {
393
+ emitChangePositionDelegate(sheetView.top)
394
+
395
+ if (!isKeyboardTransitioning) {
396
+ positionFooter(slideOffset)
397
+ updateDimAmount(sheetView.top)
398
+ }
392
399
  }
393
400
  }
394
401
 
@@ -525,22 +532,15 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
525
532
  }
526
533
 
527
534
  setupDimmedBackground(detentIndex)
535
+ setStateForDetentIndex(detentIndex)
528
536
 
529
- if (isPresented) {
530
- setStateForDetentIndex(detentIndex)
531
- } else {
537
+ if (!isPresented) {
532
538
  shouldAnimatePresent = animated
533
539
  currentDetentIndex = detentIndex
534
540
  interactionState = InteractionState.Idle
535
541
 
536
- emitWillPresentEvents()
537
-
538
- setupSheetDetents()
539
- setStateForDetentIndex(detentIndex)
540
- setupBackground()
541
- setupGrabber()
542
-
543
- // Hide until animation starts
542
+ // Position off-screen until animation starts
543
+ bottomSheetView?.translationY = realScreenHeight.toFloat()
544
544
  bottomSheetView?.visibility = INVISIBLE
545
545
 
546
546
  dialog.show()
@@ -604,10 +604,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
604
604
  val maxDetentHeight = detentCalculator.getDetentHeight(detents.last())
605
605
 
606
606
  val adjustedHalfExpandedHeight = minOf(halfExpandedDetentHeight, maxAvailableHeight)
607
- halfExpandedRatio = minOf(
608
- adjustedHalfExpandedHeight.toFloat() / realScreenHeight.toFloat(),
609
- MAX_HALF_EXPANDED_RATIO
610
- )
607
+ halfExpandedRatio = (adjustedHalfExpandedHeight.toFloat() / realScreenHeight.toFloat())
608
+ .coerceIn(0f, 0.999f)
611
609
 
612
610
  expandedOffset = maxOf(edgeToEdgeTopInset, realScreenHeight - maxDetentHeight)
613
611
 
@@ -961,7 +959,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
961
959
  // Skip reconfiguration if expanded and only height changed (e.g., keyboard)
962
960
  if (h + topInset >= screenHeight && isExpanded && oldw == w) return
963
961
 
964
- this.post {
962
+ post {
965
963
  setupSheetDetents()
966
964
  positionFooter()
967
965
  bottomSheetView?.let { emitChangePositionDelegate(it.top, realtime = false) }
@@ -3,6 +3,7 @@ package com.lodev09.truesheet.core
3
3
  import android.animation.Animator
4
4
  import android.animation.AnimatorListenerAdapter
5
5
  import android.animation.ValueAnimator
6
+ import android.util.Log
6
7
  import android.view.animation.AccelerateInterpolator
7
8
  import android.view.animation.DecelerateInterpolator
8
9
  import android.widget.FrameLayout
@@ -29,6 +30,8 @@ class TrueSheetAnimator(private val provider: TrueSheetAnimatorProvider) {
29
30
  private var presentAnimator: ValueAnimator? = null
30
31
  private var dismissAnimator: ValueAnimator? = null
31
32
 
33
+ var isAnimating: Boolean = false
34
+
32
35
  /**
33
36
  * Animate the sheet presenting from bottom of screen to target position.
34
37
  * @param toTop The target top position of the sheet
@@ -57,14 +60,20 @@ class TrueSheetAnimator(private val provider: TrueSheetAnimatorProvider) {
57
60
  }
58
61
 
59
62
  addListener(object : AnimatorListenerAdapter() {
63
+ override fun onAnimationStart(animation: Animator) {
64
+ isAnimating = true
65
+ }
66
+
60
67
  override fun onAnimationEnd(animation: Animator) {
61
68
  bottomSheet.translationY = 0f
62
69
  presentAnimator = null
70
+ isAnimating = false
63
71
  onEnd()
64
72
  }
65
73
 
66
74
  override fun onAnimationCancel(animation: Animator) {
67
75
  presentAnimator = null
76
+ isAnimating = false
68
77
  onEnd()
69
78
  }
70
79
  })
@@ -101,13 +110,19 @@ class TrueSheetAnimator(private val provider: TrueSheetAnimatorProvider) {
101
110
  }
102
111
 
103
112
  addListener(object : AnimatorListenerAdapter() {
113
+ override fun onAnimationStart(animation: Animator) {
114
+ isAnimating = true
115
+ }
116
+
104
117
  override fun onAnimationEnd(animation: Animator) {
105
118
  dismissAnimator = null
119
+ isAnimating = false
106
120
  onEnd()
107
121
  }
108
122
 
109
123
  override fun onAnimationCancel(animation: Animator) {
110
124
  dismissAnimator = null
125
+ isAnimating = false
111
126
  onEnd()
112
127
  }
113
128
  })
@@ -125,10 +140,4 @@ class TrueSheetAnimator(private val provider: TrueSheetAnimatorProvider) {
125
140
  dismissAnimator?.cancel()
126
141
  dismissAnimator = null
127
142
  }
128
-
129
- /**
130
- * Check if any animation is currently running.
131
- */
132
- val isAnimating: Boolean
133
- get() = presentAnimator?.isRunning == true || dismissAnimator?.isRunning == true
134
143
  }
@@ -1,11 +1,5 @@
1
1
  <?xml version="1.0" encoding="utf-8"?>
2
2
  <resources>
3
- <!-- No animation style - animations handled programmatically -->
4
- <style name="TrueSheetNoAnimation" parent="Animation.AppCompat.Dialog">
5
- <item name="android:windowEnterAnimation">@null</item>
6
- <item name="android:windowExitAnimation">@null</item>
7
- </style>
8
-
9
3
  <!-- Fast fade out animation for hiding sheet when rn-screen is presented -->
10
4
  <style name="TrueSheetFastFadeOut" parent="Animation.AppCompat.Dialog">
11
5
  <item name="android:windowEnterAnimation">@null</item>
@@ -14,7 +8,7 @@
14
8
 
15
9
  <!-- Default BottomSheetDialog style with programmatic animations -->
16
10
  <style name="TrueSheetDialog" parent="Theme.Design.Light.BottomSheetDialog">
17
- <item name="android:windowAnimationStyle">@style/TrueSheetNoAnimation</item>
11
+ <item name="android:windowAnimationStyle">@null</item>
18
12
  </style>
19
13
 
20
14
  <!-- BottomSheetDialog style with edge-to-edge and programmatic animations -->
@@ -22,6 +16,6 @@
22
16
  <item name="android:windowIsFloating">false</item>
23
17
  <item name="enableEdgeToEdge">true</item>
24
18
  <item name="android:navigationBarColor">@android:color/transparent</item>
25
- <item name="android:windowAnimationStyle">@style/TrueSheetNoAnimation</item>
19
+ <item name="android:windowAnimationStyle">@null</item>
26
20
  </style>
27
21
  </resources>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lodev09/react-native-true-sheet",
3
- "version": "3.5.0",
3
+ "version": "3.5.1-beta.0",
4
4
  "description": "The true native bottom sheet experience for your React Native Apps.",
5
5
  "source": "./src/index.ts",
6
6
  "main": "./lib/module/index.js",