@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,15 +1,13 @@
1
1
  package com.lodev09.truesheet
2
2
 
3
3
  import android.annotation.SuppressLint
4
- import android.graphics.Color
5
- import android.graphics.drawable.ShapeDrawable
6
- import android.graphics.drawable.shapes.RoundRectShape
7
- import android.util.TypedValue
4
+ import android.util.Log
8
5
  import android.view.MotionEvent
9
6
  import android.view.View
10
7
  import android.view.WindowManager
11
8
  import android.view.accessibility.AccessibilityNodeInfo
12
9
  import android.widget.FrameLayout
10
+ import androidx.appcompat.app.AppCompatActivity
13
11
  import androidx.core.view.isNotEmpty
14
12
  import androidx.core.view.isVisible
15
13
  import com.facebook.react.R
@@ -30,9 +28,10 @@ import com.lodev09.truesheet.core.TrueSheetAnimator
30
28
  import com.lodev09.truesheet.core.TrueSheetAnimatorProvider
31
29
  import com.lodev09.truesheet.core.TrueSheetDetentCalculator
32
30
  import com.lodev09.truesheet.core.TrueSheetDetentMeasurements
31
+ import com.lodev09.truesheet.core.TrueSheetDialogFragment
32
+ import com.lodev09.truesheet.core.TrueSheetDialogFragmentDelegate
33
33
  import com.lodev09.truesheet.core.TrueSheetDialogObserver
34
34
  import com.lodev09.truesheet.core.TrueSheetDimView
35
- import com.lodev09.truesheet.core.TrueSheetGrabberView
36
35
  import com.lodev09.truesheet.core.TrueSheetKeyboardObserver
37
36
  import com.lodev09.truesheet.core.TrueSheetKeyboardObserverDelegate
38
37
  import com.lodev09.truesheet.utils.ScreenUtils
@@ -66,7 +65,7 @@ interface TrueSheetViewControllerDelegate {
66
65
  // =============================================================================
67
66
 
68
67
  /**
69
- * Manages the bottom sheet dialog and its presentation lifecycle.
68
+ * Manages the bottom sheet dialog fragment and its presentation lifecycle.
70
69
  * Acts as a RootView to properly dispatch touch events to React Native.
71
70
  */
72
71
  @SuppressLint("ClickableViewAccessibility", "ViewConstructor")
@@ -74,14 +73,13 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
74
73
  ReactViewGroup(reactContext),
75
74
  RootView,
76
75
  TrueSheetDetentMeasurements,
77
- TrueSheetAnimatorProvider {
76
+ TrueSheetAnimatorProvider,
77
+ TrueSheetDialogFragmentDelegate {
78
78
 
79
79
  companion object {
80
80
  const val TAG_NAME = "TrueSheet"
81
81
 
82
- // Prevents fully expanded ratio which causes behavior issues
83
- private const val MAX_HALF_EXPANDED_RATIO = 0.999f
84
- private const val GRABBER_TAG = "TrueSheetGrabber"
82
+ private const val FRAGMENT_TAG = "TrueSheetDialogFragment"
85
83
  private const val DEFAULT_MAX_WIDTH = 640 // dp
86
84
  private const val DEFAULT_CORNER_RADIUS = 16 // dp
87
85
  private const val TRANSLATE_ANIMATION_DURATION = 200L
@@ -103,8 +101,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
103
101
 
104
102
  var delegate: TrueSheetViewControllerDelegate? = null
105
103
 
106
- // Dialog & Views
107
- private var dialog: BottomSheetDialog? = null
104
+ // Dialog Fragment
105
+ private var dialogFragment: TrueSheetDialogFragment? = null
108
106
  private var dimView: TrueSheetDimView? = null
109
107
  private var parentDimView: TrueSheetDimView? = null
110
108
 
@@ -165,23 +163,20 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
165
163
  var sheetCornerRadius: Float = DEFAULT_CORNER_RADIUS.dpToPx()
166
164
  set(value) {
167
165
  field = if (value < 0) DEFAULT_CORNER_RADIUS.dpToPx() else value
168
- if (isPresented) setupBackground()
166
+ dialogFragment?.sheetCornerRadius = field
167
+ if (isPresented) dialogFragment?.setupBackground()
169
168
  }
170
169
 
171
170
  var dismissible: Boolean = true
172
171
  set(value) {
173
172
  field = value
174
- dialog?.apply {
175
- setCanceledOnTouchOutside(value)
176
- setCancelable(value)
177
- behavior.isHideable = value
178
- }
173
+ dialogFragment?.dismissible = value
179
174
  }
180
175
 
181
176
  var draggable: Boolean = true
182
177
  set(value) {
183
178
  field = value
184
- behavior?.isDraggable = value
179
+ dialogFragment?.updateDraggable(value)
185
180
  }
186
181
 
187
182
  // =============================================================================
@@ -189,14 +184,17 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
189
184
  // =============================================================================
190
185
 
191
186
  // Dialog
187
+ private val dialog: BottomSheetDialog?
188
+ get() = dialogFragment?.bottomSheetDialog
189
+
192
190
  private val behavior: BottomSheetBehavior<FrameLayout>?
193
- get() = dialog?.behavior
191
+ get() = dialogFragment?.behavior
194
192
 
195
193
  private val sheetContainer: FrameLayout?
196
194
  get() = this.parent as? FrameLayout
197
195
 
198
196
  override val bottomSheetView: FrameLayout?
199
- get() = dialog?.findViewById(com.google.android.material.R.id.design_bottom_sheet)
197
+ get() = dialogFragment?.bottomSheetView
200
198
 
201
199
  private val containerView: TrueSheetContainerView?
202
200
  get() = if (this.isNotEmpty()) getChildAt(0) as? TrueSheetContainerView else null
@@ -268,53 +266,29 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
268
266
  }
269
267
 
270
268
  // =============================================================================
271
- // MARK: - Dialog Creation & Cleanup
269
+ // MARK: - Fragment Creation & Cleanup
272
270
  // =============================================================================
273
271
 
274
272
  fun createDialog() {
275
- if (dialog != null) return
276
-
277
- val style = if (edgeToEdgeEnabled) {
278
- com.lodev09.truesheet.R.style.TrueSheetEdgeToEdgeEnabledDialog
279
- } else {
280
- com.lodev09.truesheet.R.style.TrueSheetDialog
273
+ if (dialogFragment != null) return
274
+
275
+ dialogFragment = TrueSheetDialogFragment.newInstance().apply {
276
+ this.delegate = this@TrueSheetViewController
277
+ this.contentView = this@TrueSheetViewController
278
+ this.reactContext = this@TrueSheetViewController.reactContext
279
+ this.sheetCornerRadius = this@TrueSheetViewController.sheetCornerRadius
280
+ this.sheetBackgroundColor = this@TrueSheetViewController.sheetBackgroundColor
281
+ this.edgeToEdgeFullScreen = this@TrueSheetViewController.edgeToEdgeFullScreen
282
+ this.grabberEnabled = this@TrueSheetViewController.grabber
283
+ this.grabberOptions = this@TrueSheetViewController.grabberOptions
284
+ this.dismissible = this@TrueSheetViewController.dismissible
285
+ this.draggable = this@TrueSheetViewController.draggable
281
286
  }
282
287
 
283
- dialog = BottomSheetDialog(reactContext, style).apply {
284
- setContentView(this@TrueSheetViewController)
285
-
286
- window?.apply {
287
- setWindowAnimations(0)
288
- setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
289
- }
290
-
291
- setupModalObserver()
292
- setupDialogListeners(this)
293
- setupBottomSheetBehavior(this)
294
-
295
- setCanceledOnTouchOutside(dismissible)
296
- setCancelable(dismissible)
297
- behavior.isHideable = dismissible
298
- behavior.isDraggable = draggable
299
-
300
- onBackPressedDispatcher.addCallback(object : androidx.activity.OnBackPressedCallback(true) {
301
- override fun handleOnBackPressed() {
302
- this@TrueSheetViewController.delegate?.viewControllerDidBackPress()
303
- if (dismissible) {
304
- dismiss()
305
- }
306
- }
307
- })
308
- }
288
+ setupModalObserver()
309
289
  }
310
290
 
311
291
  private fun cleanupDialog() {
312
- dialog?.apply {
313
- setOnShowListener(null)
314
- setOnCancelListener(null)
315
- setOnDismissListener(null)
316
- }
317
-
318
292
  cleanupKeyboardObserver()
319
293
  cleanupModalObserver()
320
294
  sheetAnimator.cancel()
@@ -324,7 +298,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
324
298
  parentDimView = null
325
299
  sheetContainer?.removeView(this)
326
300
 
327
- dialog = null
301
+ dialogFragment = null
328
302
  interactionState = InteractionState.Idle
329
303
  isDismissing = false
330
304
  isPresented = false
@@ -335,86 +309,94 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
335
309
  }
336
310
 
337
311
  // =============================================================================
338
- // MARK: - Dialog Listeners
312
+ // MARK: - TrueSheetDialogFragmentDelegate
339
313
  // =============================================================================
340
314
 
341
- private fun setupDialogListeners(dialog: BottomSheetDialog) {
342
- dialog.setOnShowListener {
343
- bottomSheetView?.visibility = VISIBLE
344
- isPresented = true
345
- isDialogVisible = true
346
-
347
- setupKeyboardObserver()
348
-
349
- if (shouldAnimatePresent) {
350
- val toTop = getExpectedSheetTop(currentDetentIndex)
351
- sheetAnimator.animatePresent(
352
- toTop = toTop,
353
- onUpdate = { effectiveTop ->
354
- emitChangePositionDelegate(effectiveTop)
355
- positionFooter()
356
- updateDimAmount(effectiveTop)
357
- },
358
- onEnd = { finishPresent() }
359
- )
360
- } else {
361
- val toTop = getExpectedSheetTop(currentDetentIndex)
362
- emitChangePositionDelegate(toTop)
363
- positionFooter()
364
- finishPresent()
365
- }
366
- }
315
+ override fun onDialogShow() {
316
+ bottomSheetView?.visibility = VISIBLE
317
+
318
+ isPresented = true
319
+ isDialogVisible = true
320
+
321
+ emitWillPresentEvents()
322
+
323
+ setupSheetDetents()
324
+ setupDimmedBackground(currentDetentIndex)
325
+ setupKeyboardObserver()
367
326
 
368
- dialog.setOnDismissListener {
369
- emitDidDismissEvents()
370
- cleanupDialog()
327
+ if (shouldAnimatePresent) {
328
+ val toTop = getExpectedSheetTop(currentDetentIndex)
329
+ sheetAnimator.animatePresent(
330
+ toTop = toTop,
331
+ onUpdate = { effectiveTop ->
332
+ emitChangePositionDelegate(effectiveTop)
333
+ positionFooter()
334
+ updateDimAmount(effectiveTop)
335
+ },
336
+ onEnd = { finishPresent() }
337
+ )
338
+ } else {
339
+ val toTop = getExpectedSheetTop(currentDetentIndex)
340
+ emitChangePositionDelegate(toTop)
341
+ positionFooter()
342
+ finishPresent()
371
343
  }
372
344
  }
373
345
 
374
- private fun setupBottomSheetBehavior(dialog: BottomSheetDialog) {
375
- dialog.behavior.addBottomSheetCallback(
376
- object : BottomSheetBehavior.BottomSheetCallback() {
377
- override fun onSlide(sheetView: View, slideOffset: Float) {
378
- val behavior = behavior ?: return
346
+ override fun onDialogDismiss() {
347
+ emitDidDismissEvents()
348
+ cleanupDialog()
349
+ }
379
350
 
380
- emitChangePositionDelegate(sheetView.top)
351
+ override fun onDialogCancel() {
352
+ // Cancel is called before dismiss for user-initiated cancellation
353
+ }
381
354
 
382
- when (behavior.state) {
383
- BottomSheetBehavior.STATE_DRAGGING,
384
- BottomSheetBehavior.STATE_SETTLING -> handleDragChange(sheetView)
355
+ override fun onStateChanged(sheetView: View, newState: Int) {
356
+ if (newState == BottomSheetBehavior.STATE_HIDDEN) {
357
+ if (isDismissing) return
358
+ isDismissing = true
359
+ emitWillDismissEvents()
360
+ dialogFragment?.dismiss()
361
+ return
362
+ }
385
363
 
386
- else -> { }
387
- }
364
+ if (!isPresented) return
388
365
 
389
- if (!isKeyboardTransitioning) {
390
- positionFooter(slideOffset)
391
- updateDimAmount(sheetView.top)
392
- }
393
- }
366
+ when (newState) {
367
+ BottomSheetBehavior.STATE_DRAGGING -> handleDragBegin(sheetView)
394
368
 
395
- override fun onStateChanged(sheetView: View, newState: Int) {
396
- if (newState == BottomSheetBehavior.STATE_HIDDEN) {
397
- if (isDismissing) return
398
- isDismissing = true
399
- emitWillDismissEvents()
400
- dialog.dismiss()
401
- return
402
- }
369
+ BottomSheetBehavior.STATE_EXPANDED,
370
+ BottomSheetBehavior.STATE_COLLAPSED,
371
+ BottomSheetBehavior.STATE_HALF_EXPANDED -> handleStateSettled(sheetView, newState)
403
372
 
404
- if (!isPresented) return
373
+ else -> {}
374
+ }
375
+ }
405
376
 
406
- when (newState) {
407
- BottomSheetBehavior.STATE_DRAGGING -> handleDragBegin(sheetView)
377
+ override fun onSlide(sheetView: View, slideOffset: Float) {
378
+ // Skip if our custom animator is handling the animation
379
+ if (sheetAnimator.isAnimating) return
408
380
 
409
- BottomSheetBehavior.STATE_EXPANDED,
410
- BottomSheetBehavior.STATE_COLLAPSED,
411
- BottomSheetBehavior.STATE_HALF_EXPANDED -> handleStateSettled(sheetView, newState)
381
+ val behavior = behavior ?: return
412
382
 
413
- else -> {}
414
- }
415
- }
416
- }
417
- )
383
+ when (behavior.state) {
384
+ BottomSheetBehavior.STATE_DRAGGING,
385
+ BottomSheetBehavior.STATE_SETTLING -> handleDragChange(sheetView)
386
+
387
+ else -> { }
388
+ }
389
+
390
+ emitChangePositionDelegate(sheetView.top)
391
+
392
+ if (!isKeyboardTransitioning) {
393
+ positionFooter(slideOffset)
394
+ updateDimAmount(sheetView.top)
395
+ }
396
+ }
397
+
398
+ override fun onBackPressed() {
399
+ delegate?.viewControllerDidBackPress()
418
400
  }
419
401
 
420
402
  private fun handleStateSettled(sheetView: View, newState: Int) {
@@ -519,31 +501,33 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
519
501
  // =============================================================================
520
502
 
521
503
  fun present(detentIndex: Int, animated: Boolean = true) {
522
- val dialog = this.dialog ?: run {
523
- RNLog.w(reactContext, "TrueSheet: No dialog available. Ensure the sheet is mounted before presenting.")
504
+ val fragment = this.dialogFragment ?: run {
505
+ RNLog.w(reactContext, "TrueSheet: No dialog fragment available. Ensure the sheet is mounted before presenting.")
524
506
  return
525
507
  }
526
508
 
527
- setupDimmedBackground(detentIndex)
509
+ val activity = reactContext.currentActivity as? AppCompatActivity ?: run {
510
+ RNLog.w(reactContext, "TrueSheet: No AppCompatActivity available for fragment transaction.")
511
+ return
512
+ }
528
513
 
529
514
  if (isPresented) {
515
+ setupDimmedBackground(detentIndex)
530
516
  setStateForDetentIndex(detentIndex)
531
517
  } else {
532
518
  shouldAnimatePresent = animated
533
519
  currentDetentIndex = detentIndex
534
520
  interactionState = InteractionState.Idle
535
521
 
536
- emitWillPresentEvents()
537
-
538
- setupSheetDetents()
539
- setStateForDetentIndex(detentIndex)
540
- setupBackground()
541
- setupGrabber()
522
+ // Show the fragment - detents are configured in onDialogShow
523
+ if (!fragment.isAdded) {
524
+ fragment.show(activity.supportFragmentManager, FRAGMENT_TAG)
525
+ }
542
526
 
543
- // Hide until animation starts
527
+ // Execute pending transactions to ensure fragment is added
528
+ activity.supportFragmentManager.executePendingTransactions()
529
+ bottomSheetView?.translationY = realScreenHeight.toFloat()
544
530
  bottomSheetView?.visibility = INVISIBLE
545
-
546
- dialog.show()
547
531
  }
548
532
  }
549
533
 
@@ -560,11 +544,11 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
560
544
  positionFooter()
561
545
  updateDimAmount(effectiveTop)
562
546
  },
563
- onEnd = { dialog?.dismiss() }
547
+ onEnd = { dialogFragment?.dismiss() }
564
548
  )
565
549
  } else {
566
550
  emitChangePositionDelegate(realScreenHeight)
567
- dialog?.dismiss()
551
+ dialogFragment?.dismiss()
568
552
  }
569
553
  }
570
554
 
@@ -583,53 +567,57 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
583
567
  // =============================================================================
584
568
 
585
569
  fun setupSheetDetents() {
570
+ val fragment = this.dialogFragment ?: return
586
571
  val behavior = this.behavior ?: return
587
572
 
588
573
  interactionState = InteractionState.Reconfiguring
589
574
  val edgeToEdgeTopInset: Int = if (!edgeToEdgeFullScreen) topInset else 0
590
575
 
591
- behavior.apply {
592
- isFitToContents = false
593
- maxWidth = DEFAULT_MAX_WIDTH.dpToPx().toInt()
576
+ behavior.isFitToContents = false
594
577
 
595
- val maxAvailableHeight = realScreenHeight - edgeToEdgeTopInset
578
+ val maxAvailableHeight = realScreenHeight - edgeToEdgeTopInset
596
579
 
597
- setPeekHeight(detentCalculator.getDetentHeight(detents[0]), isPresented)
580
+ val peekHeight = detentCalculator.getDetentHeight(detents[0])
598
581
 
599
- val halfExpandedDetentHeight = when (detents.size) {
600
- 1 -> peekHeight
601
- else -> detentCalculator.getDetentHeight(detents[1])
602
- }
582
+ val halfExpandedDetentHeight = when (detents.size) {
583
+ 1 -> peekHeight
584
+ else -> detentCalculator.getDetentHeight(detents[1])
585
+ }
603
586
 
604
- val maxDetentHeight = detentCalculator.getDetentHeight(detents.last())
587
+ val maxDetentHeight = detentCalculator.getDetentHeight(detents.last())
605
588
 
606
- val adjustedHalfExpandedHeight = minOf(halfExpandedDetentHeight, maxAvailableHeight)
607
- halfExpandedRatio = minOf(
608
- adjustedHalfExpandedHeight.toFloat() / realScreenHeight.toFloat(),
609
- MAX_HALF_EXPANDED_RATIO
610
- )
589
+ val adjustedHalfExpandedHeight = minOf(halfExpandedDetentHeight, maxAvailableHeight)
590
+ val halfExpandedRatio = (adjustedHalfExpandedHeight.toFloat() / realScreenHeight.toFloat())
591
+ .coerceIn(0f, 0.999f)
611
592
 
612
- expandedOffset = maxOf(edgeToEdgeTopInset, realScreenHeight - maxDetentHeight)
593
+ val expandedOffset = maxOf(edgeToEdgeTopInset, realScreenHeight - maxDetentHeight)
613
594
 
614
- // fitToContents works better with <= 2 detents when no expanded offset
615
- isFitToContents = detents.size < 3 && expandedOffset == 0
595
+ // fitToContents works better with <= 2 detents when no expanded offset
596
+ val fitToContents = detents.size < 3 && expandedOffset == 0
616
597
 
617
- val offset = if (expandedOffset == 0) topInset else 0
618
- val newHeight = realScreenHeight - expandedOffset - offset
619
- val newWidth = minOf(screenWidth, maxWidth)
598
+ fragment.configureDetents(
599
+ peekHeight = peekHeight,
600
+ halfExpandedRatio = halfExpandedRatio,
601
+ expandedOffset = expandedOffset,
602
+ fitToContents = fitToContents,
603
+ animate = isPresented
604
+ )
620
605
 
621
- if (lastStateWidth != newWidth || lastStateHeight != newHeight) {
622
- lastStateWidth = newWidth
623
- lastStateHeight = newHeight
624
- delegate?.viewControllerDidChangeSize(newWidth, newHeight)
625
- }
606
+ val offset = if (expandedOffset == 0) topInset else 0
607
+ val newHeight = realScreenHeight - expandedOffset - offset
608
+ val newWidth = minOf(screenWidth, DEFAULT_MAX_WIDTH.dpToPx().toInt())
626
609
 
627
- if (isPresented) {
628
- setStateForDetentIndex(currentDetentIndex)
629
- }
610
+ if (lastStateWidth != newWidth || lastStateHeight != newHeight) {
611
+ lastStateWidth = newWidth
612
+ lastStateHeight = newHeight
613
+ delegate?.viewControllerDidChangeSize(newWidth, newHeight)
614
+ }
630
615
 
631
- interactionState = InteractionState.Idle
616
+ if (isPresented) {
617
+ setStateForDetentIndex(currentDetentIndex)
632
618
  }
619
+
620
+ interactionState = InteractionState.Idle
633
621
  }
634
622
 
635
623
  fun setupSheetDetentsForSizeChange() {
@@ -638,7 +626,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
638
626
  }
639
627
 
640
628
  fun setStateForDetentIndex(index: Int) {
641
- behavior?.state = detentCalculator.getStateForDetentIndex(index)
629
+ dialogFragment?.setState(detentCalculator.getStateForDetentIndex(index))
642
630
  }
643
631
 
644
632
  // =============================================================================
@@ -646,19 +634,11 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
646
634
  // =============================================================================
647
635
 
648
636
  fun setupGrabber() {
649
- val bottomSheet = bottomSheetView ?: return
650
-
651
- bottomSheet.findViewWithTag<View>(GRABBER_TAG)?.let {
652
- bottomSheet.removeView(it)
653
- }
654
-
655
- if (!grabber || !draggable) return
656
-
657
- val grabberView = TrueSheetGrabberView(reactContext, grabberOptions).apply {
658
- tag = GRABBER_TAG
637
+ dialogFragment?.apply {
638
+ grabberEnabled = this@TrueSheetViewController.grabber
639
+ grabberOptions = this@TrueSheetViewController.grabberOptions
640
+ setupGrabber()
659
641
  }
660
-
661
- bottomSheet.addView(grabberView)
662
642
  }
663
643
 
664
644
  // =============================================================================
@@ -666,25 +646,11 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
666
646
  // =============================================================================
667
647
 
668
648
  fun setupBackground() {
669
- val bottomSheet = bottomSheetView ?: return
670
-
671
- // Rounded corners only on top
672
- val outerRadii = floatArrayOf(
673
- sheetCornerRadius,
674
- sheetCornerRadius,
675
- sheetCornerRadius,
676
- sheetCornerRadius,
677
- 0f,
678
- 0f,
679
- 0f,
680
- 0f
681
- )
682
- val backgroundColor = sheetBackgroundColor ?: getDefaultBackgroundColor()
683
-
684
- bottomSheet.background = ShapeDrawable(RoundRectShape(outerRadii, null, null)).apply {
685
- paint.color = backgroundColor
649
+ dialogFragment?.apply {
650
+ sheetCornerRadius = this@TrueSheetViewController.sheetCornerRadius
651
+ sheetBackgroundColor = this@TrueSheetViewController.sheetBackgroundColor
652
+ setupBackground()
686
653
  }
687
- bottomSheet.clipToOutline = true
688
654
  }
689
655
 
690
656
  fun setupDimmedBackground(detentIndex: Int) {
@@ -756,25 +722,12 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
756
722
  }
757
723
  }
758
724
 
759
- fun getDefaultBackgroundColor(): Int {
760
- val typedValue = TypedValue()
761
- return if (reactContext.theme.resolveAttribute(
762
- com.google.android.material.R.attr.colorSurfaceContainerLow,
763
- typedValue,
764
- true
765
- )
766
- ) {
767
- typedValue.data
768
- } else {
769
- Color.WHITE
770
- }
771
- }
772
-
773
725
  // =============================================================================
774
726
  // MARK: - Footer Positioning
775
727
  // =============================================================================
776
728
 
777
729
  fun positionFooter(slideOffset: Float? = null) {
730
+ if (!isPresented) return
778
731
  val footerView = containerView?.footerView ?: return
779
732
  val bottomSheet = bottomSheetView ?: return
780
733
 
@@ -829,7 +782,10 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
829
782
  isKeyboardTransitioning = false
830
783
  }
831
784
 
832
- override fun keyboardDidChangeHeight(height: Int) {}
785
+ override fun keyboardDidChangeHeight(height: Int) {
786
+ if (!shouldHandleKeyboard()) return
787
+ positionFooter()
788
+ }
833
789
  }
834
790
  start()
835
791
  }
@@ -961,7 +917,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
961
917
  // Skip reconfiguration if expanded and only height changed (e.g., keyboard)
962
918
  if (h + topInset >= screenHeight && isExpanded && oldw == w) return
963
919
 
964
- this.post {
920
+ post {
965
921
  setupSheetDetents()
966
922
  positionFooter()
967
923
  bottomSheetView?.let { emitChangePositionDelegate(it.top, realtime = false) }