@lodev09/react-native-true-sheet 3.5.1-beta.3 → 3.5.1-beta.5

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.
Files changed (33) hide show
  1. package/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +2 -2
  2. package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +31 -28
  3. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +358 -271
  4. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +0 -5
  5. package/android/src/main/java/com/lodev09/truesheet/core/RNScreensFragmentObserver.kt +7 -8
  6. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +158 -0
  7. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +55 -0
  8. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +18 -12
  9. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +91 -2
  10. package/android/src/main/java/com/lodev09/truesheet/core/{TrueSheetDialogObserver.kt → TrueSheetStackManager.kt} +7 -5
  11. package/ios/TrueSheetViewController.h +2 -3
  12. package/ios/TrueSheetViewController.mm +11 -4
  13. package/ios/core/TrueSheetDetentCalculator.h +2 -3
  14. package/ios/core/TrueSheetDetentCalculator.mm +7 -9
  15. package/lib/module/TrueSheet.js +0 -2
  16. package/lib/module/TrueSheet.js.map +1 -1
  17. package/lib/module/fabric/TrueSheetViewNativeComponent.ts +0 -1
  18. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  19. package/lib/typescript/src/TrueSheet.types.d.ts +0 -8
  20. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  21. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +0 -1
  22. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
  23. package/lib/typescript/src/navigation/types.d.ts +1 -1
  24. package/lib/typescript/src/navigation/types.d.ts.map +1 -1
  25. package/package.json +1 -1
  26. package/src/TrueSheet.tsx +0 -2
  27. package/src/TrueSheet.types.ts +0 -9
  28. package/src/fabric/TrueSheetViewNativeComponent.ts +0 -1
  29. package/src/navigation/types.ts +0 -1
  30. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetAnimator.kt +0 -144
  31. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDialogFragment.kt +0 -317
  32. package/android/src/main/res/anim/fast_fade_out.xml +0 -6
  33. package/android/src/main/res/values/styles.xml +0 -21
@@ -4,13 +4,13 @@ import android.annotation.SuppressLint
4
4
  import android.os.Build
5
5
  import android.view.MotionEvent
6
6
  import android.view.View
7
- import android.view.WindowManager
7
+ import android.view.ViewGroup
8
8
  import android.view.accessibility.AccessibilityNodeInfo
9
- import android.widget.FrameLayout
9
+ import androidx.activity.OnBackPressedCallback
10
10
  import androidx.appcompat.app.AppCompatActivity
11
+ import androidx.coordinatorlayout.widget.CoordinatorLayout
11
12
  import androidx.core.view.isNotEmpty
12
13
  import androidx.core.view.isVisible
13
- import androidx.core.view.postDelayed
14
14
  import com.facebook.react.R
15
15
  import com.facebook.react.uimanager.JSPointerDispatcher
16
16
  import com.facebook.react.uimanager.JSTouchDispatcher
@@ -22,19 +22,19 @@ import com.facebook.react.uimanager.events.EventDispatcher
22
22
  import com.facebook.react.util.RNLog
23
23
  import com.facebook.react.views.view.ReactViewGroup
24
24
  import com.google.android.material.bottomsheet.BottomSheetBehavior
25
- import com.google.android.material.bottomsheet.BottomSheetDialog
26
25
  import com.lodev09.truesheet.core.GrabberOptions
27
26
  import com.lodev09.truesheet.core.RNScreensFragmentObserver
28
- import com.lodev09.truesheet.core.TrueSheetAnimator
29
- import com.lodev09.truesheet.core.TrueSheetAnimatorProvider
27
+ import com.lodev09.truesheet.core.TrueSheetBottomSheetView
28
+ import com.lodev09.truesheet.core.TrueSheetBottomSheetViewDelegate
29
+ import com.lodev09.truesheet.core.TrueSheetCoordinatorLayout
30
+ import com.lodev09.truesheet.core.TrueSheetCoordinatorLayoutDelegate
30
31
  import com.lodev09.truesheet.core.TrueSheetDetentCalculator
31
- import com.lodev09.truesheet.core.TrueSheetDetentMeasurements
32
- import com.lodev09.truesheet.core.TrueSheetDialogFragment
33
- import com.lodev09.truesheet.core.TrueSheetDialogFragmentDelegate
34
- import com.lodev09.truesheet.core.TrueSheetDialogObserver
32
+ import com.lodev09.truesheet.core.TrueSheetDetentCalculatorDelegate
35
33
  import com.lodev09.truesheet.core.TrueSheetDimView
34
+ import com.lodev09.truesheet.core.TrueSheetDimViewDelegate
36
35
  import com.lodev09.truesheet.core.TrueSheetKeyboardObserver
37
36
  import com.lodev09.truesheet.core.TrueSheetKeyboardObserverDelegate
37
+ import com.lodev09.truesheet.core.TrueSheetStackManager
38
38
  import com.lodev09.truesheet.utils.ScreenUtils
39
39
 
40
40
  // =============================================================================
@@ -66,24 +66,29 @@ interface TrueSheetViewControllerDelegate {
66
66
  // =============================================================================
67
67
 
68
68
  /**
69
- * Manages the bottom sheet dialog fragment and its presentation lifecycle.
70
- * Acts as a RootView to properly dispatch touch events to React Native.
69
+ * Controls the presentation and behavior of a bottom sheet.
70
+ *
71
+ * Uses CoordinatorLayout with BottomSheetBehavior to manage the sheet within the activity window,
72
+ * enabling touch pass-through to underlying views. Handles detent configuration, drag interactions,
73
+ * keyboard avoidance, dimmed backgrounds, back button, and lifecycle events for stacked sheets.
71
74
  */
72
75
  @SuppressLint("ClickableViewAccessibility", "ViewConstructor")
73
76
  class TrueSheetViewController(private val reactContext: ThemedReactContext) :
74
77
  ReactViewGroup(reactContext),
75
78
  RootView,
76
- TrueSheetDetentMeasurements,
77
- TrueSheetAnimatorProvider,
78
- TrueSheetDialogFragmentDelegate {
79
+ TrueSheetDetentCalculatorDelegate,
80
+ TrueSheetDimViewDelegate,
81
+ TrueSheetCoordinatorLayoutDelegate,
82
+ TrueSheetBottomSheetViewDelegate {
79
83
 
80
84
  companion object {
81
85
  const val TAG_NAME = "TrueSheet"
82
86
 
83
- private const val FRAGMENT_TAG = "TrueSheetDialogFragment"
84
87
  private const val DEFAULT_MAX_WIDTH = 640 // dp
85
88
  private const val DEFAULT_CORNER_RADIUS = 16 // dp
86
89
  private const val TRANSLATE_ANIMATION_DURATION = 200L
90
+ private const val DISMISS_DURATION = 200L
91
+ private const val MODAL_FADE_DURATION = 150L
87
92
  }
88
93
 
89
94
  // =============================================================================
@@ -102,16 +107,20 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
102
107
 
103
108
  var delegate: TrueSheetViewControllerDelegate? = null
104
109
 
105
- // Dialog Fragment
106
- private var dialogFragment: TrueSheetDialogFragment? = null
110
+ // CoordinatorLayout components (replaces DialogFragment)
111
+ internal var sheetView: TrueSheetBottomSheetView? = null
112
+ private var coordinatorLayout: TrueSheetCoordinatorLayout? = null
107
113
  private var dimView: TrueSheetDimView? = null
108
114
  private var parentDimView: TrueSheetDimView? = null
109
115
 
116
+ // Back button handling
117
+ private var backCallback: OnBackPressedCallback? = null
118
+
110
119
  // Presentation State
111
120
  var isPresented = false
112
121
  private set
113
122
 
114
- var isDialogVisible = false
123
+ var isSheetVisible = false
115
124
  private set
116
125
 
117
126
  var currentDetentIndex: Int = -1
@@ -121,8 +130,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
121
130
  private var isDismissing = false
122
131
  private var wasHiddenByModal = false
123
132
  private var shouldAnimatePresent = false
124
- private var wasPresentingWithAnimation = false
125
- private var isPresentingWithoutAnimation = false
133
+ private var isPresentAnimating = false
126
134
 
127
135
  private var lastStateWidth: Int = 0
128
136
  private var lastStateHeight: Int = 0
@@ -140,15 +148,16 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
140
148
  var parentSheetView: TrueSheetView? = null
141
149
 
142
150
  // Helper Objects
143
- private val sheetAnimator = TrueSheetAnimator(this)
144
151
  private var keyboardObserver: TrueSheetKeyboardObserver? = null
145
152
  private var rnScreensObserver: RNScreensFragmentObserver? = null
146
- private val detentCalculator = TrueSheetDetentCalculator(this)
153
+ internal val detentCalculator = TrueSheetDetentCalculator(reactContext).apply {
154
+ delegate = this@TrueSheetViewController
155
+ }
147
156
 
148
157
  // Touch Dispatchers
149
158
  internal var eventDispatcher: EventDispatcher? = null
150
- private val jSTouchDispatcher = JSTouchDispatcher(this)
151
- private var jSPointerDispatcher: JSPointerDispatcher? = null
159
+ private val jsTouchDispatcher = JSTouchDispatcher(this)
160
+ private var jsPointerDispatcher: JSPointerDispatcher? = null
152
161
 
153
162
  // Detent Configuration
154
163
  override var maxSheetHeight: Int? = null
@@ -157,47 +166,40 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
157
166
  // Appearance Configuration
158
167
  var dimmed = true
159
168
  var dimmedDetentIndex = 0
160
- var grabber: Boolean = true
161
- var grabberOptions: GrabberOptions? = null
162
- var sheetBackgroundColor: Int? = null
163
- var edgeToEdgeFullScreen: Boolean = false
169
+ override var grabber: Boolean = true
170
+ override var grabberOptions: GrabberOptions? = null
171
+ override var sheetBackgroundColor: Int? = null
164
172
  var insetAdjustment: String = "automatic"
165
173
 
166
- var sheetCornerRadius: Float = DEFAULT_CORNER_RADIUS.dpToPx()
174
+ override var sheetCornerRadius: Float = DEFAULT_CORNER_RADIUS.dpToPx()
167
175
  set(value) {
168
176
  field = if (value < 0) DEFAULT_CORNER_RADIUS.dpToPx() else value
169
- dialogFragment?.sheetCornerRadius = field
170
- if (isPresented) dialogFragment?.setupBackground()
177
+ if (isPresented) sheetView?.setupBackground()
171
178
  }
172
179
 
173
180
  var dismissible: Boolean = true
174
181
  set(value) {
175
182
  field = value
176
- dialogFragment?.dismissible = value
183
+ behavior?.isHideable = value
177
184
  }
178
185
 
179
186
  var draggable: Boolean = true
180
187
  set(value) {
181
188
  field = value
182
- dialogFragment?.updateDraggable(value)
189
+ behavior?.isDraggable = value
190
+ if (isPresented) sheetView?.setupGrabber()
183
191
  }
184
192
 
193
+ val isDimmedAtCurrentDetent: Boolean
194
+ get() = dimmed && currentDetentIndex >= dimmedDetentIndex
195
+
185
196
  // =============================================================================
186
197
  // MARK: - Computed Properties
187
198
  // =============================================================================
188
199
 
189
- // Dialog
190
- private val dialog: BottomSheetDialog?
191
- get() = dialogFragment?.bottomSheetDialog
192
-
193
- private val behavior: BottomSheetBehavior<FrameLayout>?
194
- get() = dialogFragment?.behavior
195
-
196
- private val sheetContainer: FrameLayout?
197
- get() = this.parent as? FrameLayout
198
-
199
- override val bottomSheetView: FrameLayout?
200
- get() = dialogFragment?.bottomSheetView
200
+ // Behavior
201
+ private val behavior: BottomSheetBehavior<TrueSheetBottomSheetView>?
202
+ get() = sheetView?.behavior
201
203
 
202
204
  private val containerView: TrueSheetContainerView?
203
205
  get() = if (this.isNotEmpty()) getChildAt(0) as? TrueSheetContainerView else null
@@ -241,23 +243,23 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
241
243
  private val edgeToEdgeEnabled: Boolean
242
244
  get() {
243
245
  val defaultEnabled = android.os.Build.VERSION.SDK_INT >= 36
244
- return BuildConfig.EDGE_TO_EDGE_ENABLED || dialog?.edgeToEdgeEnabled == true || defaultEnabled
246
+ return BuildConfig.EDGE_TO_EDGE_ENABLED || defaultEnabled
245
247
  }
246
248
 
247
249
  // Sheet State
248
250
  val isExpanded: Boolean
249
251
  get() {
250
- val sheetTop = bottomSheetView?.top ?: return false
252
+ val sheetTop = sheetView?.top ?: return false
251
253
  return sheetTop <= topInset
252
254
  }
253
255
 
254
256
  val currentTranslationY: Int
255
- get() = bottomSheetView?.translationY?.toInt() ?: 0
257
+ get() = sheetView?.translationY?.toInt() ?: 0
256
258
 
257
- private val isTopmostSheet: Boolean
259
+ override val isTopmostSheet: Boolean
258
260
  get() {
259
261
  val hostView = delegate as? TrueSheetView ?: return true
260
- return TrueSheetDialogObserver.isTopmostSheet(hostView)
262
+ return TrueSheetStackManager.isTopmostSheet(hostView)
261
263
  }
262
264
 
263
265
  private val dimViews: List<TrueSheetDimView>
@@ -268,120 +270,145 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
268
270
  // =============================================================================
269
271
 
270
272
  init {
271
- jSPointerDispatcher = JSPointerDispatcher(this)
273
+ jsPointerDispatcher = JSPointerDispatcher(this)
272
274
  }
273
275
 
274
276
  // =============================================================================
275
- // MARK: - Fragment Creation & Cleanup
277
+ // MARK: - Sheet Creation & Cleanup
276
278
  // =============================================================================
277
279
 
278
- fun createDialog() {
279
- if (dialogFragment != null) return
280
+ fun createSheet() {
281
+ if (coordinatorLayout != null) return
280
282
 
281
- dialogFragment = TrueSheetDialogFragment.newInstance().apply {
283
+ // Create coordinator layout
284
+ coordinatorLayout = TrueSheetCoordinatorLayout(reactContext).apply {
282
285
  delegate = this@TrueSheetViewController
283
- contentView = this@TrueSheetViewController
284
- syncFragmentProperties(this)
285
286
  }
286
287
 
287
- setupModalObserver()
288
- }
289
-
290
- private fun syncFragmentProperties(fragment: TrueSheetDialogFragment) {
291
- fragment.apply {
292
- reactContext = this@TrueSheetViewController.reactContext
293
- sheetCornerRadius = this@TrueSheetViewController.sheetCornerRadius
294
- sheetBackgroundColor = this@TrueSheetViewController.sheetBackgroundColor
295
- edgeToEdgeFullScreen = this@TrueSheetViewController.edgeToEdgeFullScreen
296
- grabberEnabled = this@TrueSheetViewController.grabber
297
- grabberOptions = this@TrueSheetViewController.grabberOptions
298
- dismissible = this@TrueSheetViewController.dismissible
299
- draggable = this@TrueSheetViewController.draggable
288
+ sheetView = TrueSheetBottomSheetView(reactContext).apply {
289
+ delegate = this@TrueSheetViewController
300
290
  }
291
+
292
+ setupModalObserver()
301
293
  }
302
294
 
303
- private fun cleanupDialog() {
295
+ private fun cleanupSheet() {
304
296
  cleanupKeyboardObserver()
305
297
  cleanupModalObserver()
306
- sheetAnimator.cancel()
298
+ cleanupBackCallback()
299
+ sheetView?.animate()?.cancel()
300
+
301
+ // Remove from activity
302
+ removeFromActivity()
303
+
304
+ // Cleanup dim views
307
305
  dimView?.detach()
308
306
  dimView = null
309
307
  parentDimView?.detach()
310
308
  parentDimView = null
311
- sheetContainer?.removeView(this)
312
309
 
313
- dialogFragment = null
310
+ // Detach content from sheet
311
+ sheetView?.removeView(this)
312
+
313
+ coordinatorLayout = null
314
+ sheetView = null
315
+
314
316
  interactionState = InteractionState.Idle
315
317
  isDismissing = false
316
318
  isPresented = false
317
- isDialogVisible = false
319
+ isSheetVisible = false
318
320
  wasHiddenByModal = false
321
+ isPresentAnimating = false
319
322
  lastEmittedPositionPx = -1
320
323
  shouldAnimatePresent = true
321
324
  }
322
325
 
326
+ private fun removeFromActivity() {
327
+ val coordinator = coordinatorLayout ?: return
328
+ val contentView = reactContext.currentActivity?.findViewById<ViewGroup>(android.R.id.content)
329
+ contentView?.removeView(coordinator)
330
+ }
331
+
323
332
  // =============================================================================
324
- // MARK: - TrueSheetDialogFragmentDelegate
333
+ // MARK: - Back Button Handling
325
334
  // =============================================================================
326
335
 
327
- override fun onDialogCreated() {
328
- bottomSheetView?.visibility = INVISIBLE
336
+ private fun setupBackCallback() {
337
+ val activity = reactContext.currentActivity as? AppCompatActivity ?: return
329
338
 
330
- // Ensure sheet starts off-screen to prevent flicker
331
- bottomSheetView?.y = realScreenHeight.toFloat()
339
+ backCallback = object : OnBackPressedCallback(true) {
340
+ override fun handleOnBackPressed() {
341
+ delegate?.viewControllerDidBackPress()
342
+ dismissOrCollapseToLowest()
343
+ }
344
+ }
345
+
346
+ activity.onBackPressedDispatcher.addCallback(backCallback!!)
332
347
  }
333
348
 
334
- override fun onDialogShow() {
335
- bottomSheetView?.visibility = VISIBLE
349
+ private fun cleanupBackCallback() {
350
+ backCallback?.remove()
351
+ backCallback = null
352
+ }
336
353
 
337
- isPresented = true
338
- isDialogVisible = true
354
+ // =============================================================================
355
+ // MARK: - TrueSheetCoordinatorLayout.Delegate
356
+ // =============================================================================
339
357
 
340
- emitWillPresentEvents()
358
+ override fun coordinatorLayoutDidLayout(changed: Boolean) {
359
+ // Reposition footer when layout changes
360
+ if (isPresented && changed) {
361
+ positionFooter()
362
+ }
363
+ }
341
364
 
342
- setupSheetDetents()
343
- setupDimmedBackground(currentDetentIndex)
344
- setupKeyboardObserver()
365
+ // =============================================================================
366
+ // MARK: - TrueSheetDimViewDelegate
367
+ // =============================================================================
345
368
 
346
- if (shouldAnimatePresent) {
347
- wasPresentingWithAnimation = true
348
- post {
349
- val toTop = getExpectedSheetTop(currentDetentIndex)
350
- sheetAnimator.animatePresent(
351
- toTop = toTop,
352
- onUpdate = { effectiveTop -> updateSheetVisuals(effectiveTop) },
353
- onStart = { wasPresentingWithAnimation = false },
354
- onEnd = { finishPresent() }
355
- )
356
- }
357
- } else {
358
- isPresentingWithoutAnimation = true
369
+ override fun dimViewDidTap() {
370
+ val hostView = delegate as? TrueSheetView ?: return
359
371
 
360
- post {
361
- val toTop = getExpectedSheetTop(currentDetentIndex)
362
- bottomSheetView?.y = toTop.toFloat()
372
+ val children = TrueSheetStackManager.getSheetsAbove(hostView)
373
+ val topmostChild = children.firstOrNull()?.viewController
363
374
 
364
- updateSheetVisuals(toTop)
365
- finishPresent()
375
+ // If topmost child is dimmed, only handle that child
376
+ if (topmostChild?.isDimmedAtCurrentDetent == true) {
377
+ if (topmostChild.dismissible) {
378
+ topmostChild.dismiss(animated = true)
366
379
  }
380
+ return
367
381
  }
368
- }
369
382
 
370
- override fun onDialogDismiss() {
371
- emitDidDismissEvents()
372
- cleanupDialog()
383
+ // Pass through to parent - dismiss all if possible
384
+ val allDismissible = dismissible && children.all { it.viewController.dismissible }
385
+ if (allDismissible) {
386
+ children.forEach { it.viewController.dismiss(animated = true) }
387
+ }
388
+
389
+ dismissOrCollapseToLowest()
373
390
  }
374
391
 
375
- override fun onDialogCancel() {
376
- // Cancel is called before dismiss for user-initiated cancellation
392
+ // =============================================================================
393
+ // MARK: - BottomSheetCallback
394
+ // =============================================================================
395
+
396
+ private val sheetCallback = object : BottomSheetBehavior.BottomSheetCallback() {
397
+ override fun onStateChanged(sheetView: View, newState: Int) {
398
+ handleStateChanged(sheetView, newState)
399
+ }
400
+
401
+ override fun onSlide(sheetView: View, slideOffset: Float) {
402
+ handleSlide(sheetView, slideOffset)
403
+ }
377
404
  }
378
405
 
379
- override fun onStateChanged(sheetView: View, newState: Int) {
406
+ private fun handleStateChanged(sheetView: View, newState: Int) {
380
407
  if (newState == BottomSheetBehavior.STATE_HIDDEN) {
381
408
  if (isDismissing) return
382
409
  isDismissing = true
383
410
  emitWillDismissEvents()
384
- dialogFragment?.dismiss()
411
+ finishDismiss()
385
412
  return
386
413
  }
387
414
 
@@ -398,21 +425,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
398
425
  }
399
426
  }
400
427
 
401
- override fun onSlide(sheetView: View, slideOffset: Float) {
402
- // Skip if our custom animator is handling the animation
403
- if (sheetAnimator.isAnimating) return
404
-
405
- // Keep it off screen to prevent flicker
406
- if (wasPresentingWithAnimation) {
407
- sheetView.y = realScreenHeight.toFloat()
408
- return
409
- }
410
-
411
- // When presenting without animation, keep the sheet at the target position
412
- if (isPresentingWithoutAnimation) {
413
- sheetView.y = getExpectedSheetTop(currentDetentIndex).toFloat()
414
- return
415
- }
428
+ private fun handleSlide(sheetView: View, slideOffset: Float) {
429
+ // Skip during dismiss animation
430
+ if (isDismissing) return
416
431
 
417
432
  val behavior = behavior ?: return
418
433
 
@@ -436,28 +451,20 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
436
451
  }
437
452
  }
438
453
 
439
- override fun onBackPressed() {
440
- delegate?.viewControllerDidBackPress()
441
- if (dismissible) {
442
- dismiss(animated = true)
443
- }
444
- }
445
-
446
454
  private fun handleStateSettled(sheetView: View, newState: Int) {
447
455
  if (interactionState is InteractionState.Reconfiguring) return
448
456
 
449
- // Reset non-animated presentation flag once behavior has settled at the target
450
- if (isPresentingWithoutAnimation) {
451
- val targetTop = getExpectedSheetTop(currentDetentIndex)
452
- sheetView.y = targetTop.toFloat()
453
-
454
- isPresentingWithoutAnimation = false
455
- }
456
-
457
457
  val index = detentCalculator.getDetentIndexForState(newState) ?: return
458
458
  val position = getPositionDpForView(sheetView)
459
459
  val detentInfo = DetentInfo(index, position)
460
460
 
461
+ // Handle present animation completion
462
+ if (isPresentAnimating) {
463
+ isPresentAnimating = false
464
+ finishPresent()
465
+ return
466
+ }
467
+
461
468
  when (interactionState) {
462
469
  is InteractionState.Dragging -> {
463
470
  val detent = detentCalculator.getDetentValueForIndex(detentInfo.index)
@@ -494,7 +501,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
494
501
  rnScreensObserver = RNScreensFragmentObserver(
495
502
  reactContext = reactContext,
496
503
  onModalPresented = {
497
- if (isPresented && isDialogVisible && isTopmostSheet) {
504
+ if (isPresented && isSheetVisible && isTopmostSheet) {
498
505
  hideForModal()
499
506
  }
500
507
  },
@@ -521,39 +528,46 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
521
528
  rnScreensObserver = null
522
529
  }
523
530
 
531
+ private fun setSheetVisibility(visible: Boolean) {
532
+ coordinatorLayout?.visibility = if (visible) VISIBLE else GONE
533
+ dimViews.forEach { it.visibility = if (visible) VISIBLE else INVISIBLE }
534
+ }
535
+
524
536
  private fun hideForModal() {
525
- isDialogVisible = false
537
+ isSheetVisible = false
526
538
  wasHiddenByModal = true
527
539
 
528
- // Prepare for fast fade out
529
- dimViews.forEach { it.alpha = 0f }
530
-
531
- dialog?.window?.setWindowAnimations(com.lodev09.truesheet.R.style.TrueSheetFastFadeOut)
532
- dialog?.window?.decorView?.visibility = GONE
533
- dimViews.forEach { it.visibility = INVISIBLE }
540
+ val sheet = sheetView ?: run {
541
+ setSheetVisibility(false)
542
+ parentSheetView?.viewController?.hideForModal()
543
+ return
544
+ }
534
545
 
535
- parentSheetView?.viewController?.hideForModal()
546
+ dimViews.forEach { it.animate().alpha(0f).setDuration(MODAL_FADE_DURATION).start() }
547
+ sheet.animate()
548
+ .alpha(0f)
549
+ .setDuration(MODAL_FADE_DURATION)
550
+ .withEndAction {
551
+ setSheetVisibility(false)
552
+ parentSheetView?.viewController?.hideForModal()
553
+ }
554
+ .start()
536
555
  }
537
556
 
538
557
  private fun showAfterModal() {
539
- isDialogVisible = true
540
-
541
- dialog?.window?.setWindowAnimations(0)
542
- dialog?.window?.decorView?.visibility = VISIBLE
543
- dimViews.forEach { it.visibility = VISIBLE }
544
-
558
+ isSheetVisible = true
559
+ setSheetVisibility(true)
560
+ sheetView?.alpha = 1f
545
561
  updateDimAmount(animated = true)
546
562
  }
547
563
 
548
564
  /**
549
565
  * Re-applies hidden state after returning from background.
550
- * Android may restore dialog visibility on activity resume, so we need to hide it again.
566
+ * Android may restore visibility on activity resume, so we need to hide it again.
551
567
  */
552
568
  fun reapplyHiddenState() {
553
569
  if (!wasHiddenByModal) return
554
-
555
- dialog?.window?.decorView?.visibility = GONE
556
- dimViews.forEach { it.visibility = INVISIBLE }
570
+ setSheetVisibility(false)
557
571
  }
558
572
 
559
573
  // =============================================================================
@@ -561,13 +575,18 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
561
575
  // =============================================================================
562
576
 
563
577
  fun present(detentIndex: Int, animated: Boolean = true) {
564
- val fragment = this.dialogFragment ?: run {
565
- RNLog.w(reactContext, "TrueSheet: No dialog fragment available. Ensure the sheet is mounted before presenting.")
578
+ val coordinator = this.coordinatorLayout ?: run {
579
+ RNLog.w(reactContext, "TrueSheet: No coordinator layout available. Ensure the sheet is mounted before presenting.")
580
+ return
581
+ }
582
+
583
+ val sheet = this.sheetView ?: run {
584
+ RNLog.w(reactContext, "TrueSheet: No sheet view available.")
566
585
  return
567
586
  }
568
587
 
569
- val activity = reactContext.currentActivity as? AppCompatActivity ?: run {
570
- RNLog.w(reactContext, "TrueSheet: No AppCompatActivity available for fragment transaction.")
588
+ val activity = reactContext.currentActivity ?: run {
589
+ RNLog.w(reactContext, "TrueSheet: No activity available for presentation.")
571
590
  return
572
591
  }
573
592
 
@@ -579,16 +598,68 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
579
598
  currentDetentIndex = detentIndex
580
599
  interactionState = InteractionState.Idle
581
600
 
582
- // Show the fragment - detents are configured in onDialogShow
583
- if (!fragment.isAdded) {
584
- fragment.show(activity.supportFragmentManager, FRAGMENT_TAG)
585
- }
601
+ // Setup sheet in coordinator layout
602
+ setupSheetInCoordinator(coordinator, sheet)
603
+
604
+ // Add coordinator to activity
605
+ val contentView = activity.findViewById<ViewGroup>(android.R.id.content)
606
+ contentView?.addView(coordinator)
607
+
608
+ // Setup back button handling
609
+ setupBackCallback()
586
610
 
587
- // Execute pending transactions to ensure fragment is added
588
- activity.supportFragmentManager.executePendingTransactions()
611
+ // Start presentation
612
+ onSheetShow()
589
613
  }
590
614
  }
591
615
 
616
+ private fun setupSheetInCoordinator(coordinator: TrueSheetCoordinatorLayout, sheet: TrueSheetBottomSheetView) {
617
+ // Add this controller as content to the sheet
618
+ (parent as? ViewGroup)?.removeView(this)
619
+ sheet.addView(this)
620
+
621
+ // Create layout params with behavior
622
+ val params = sheet.createLayoutParams()
623
+ val behavior = params.behavior as BottomSheetBehavior<TrueSheetBottomSheetView>
624
+
625
+ // Configure behavior
626
+ behavior.isHideable = true
627
+ behavior.isDraggable = draggable
628
+ behavior.state = BottomSheetBehavior.STATE_HIDDEN
629
+ behavior.addBottomSheetCallback(sheetCallback)
630
+
631
+ // Add sheet to coordinator
632
+ coordinator.addView(sheet, params)
633
+ }
634
+
635
+ private fun onSheetShow() {
636
+ val sheet = sheetView ?: run {
637
+ RNLog.e(reactContext, "TrueSheet: sheetView is null in onSheetShow")
638
+ return
639
+ }
640
+
641
+ emitWillPresentEvents()
642
+
643
+ setupSheetDetents()
644
+ setupDimmedBackground(currentDetentIndex)
645
+ setupKeyboardObserver()
646
+ sheet.setupBackground()
647
+ sheet.setupGrabber()
648
+
649
+ if (shouldAnimatePresent) {
650
+ isPresentAnimating = true
651
+ post { setStateForDetentIndex(currentDetentIndex) }
652
+ } else {
653
+ setStateForDetentIndex(currentDetentIndex)
654
+ emitChangePositionDelegate(detentCalculator.getSheetTopForDetentIndex(currentDetentIndex))
655
+ updateDimAmount()
656
+ finishPresent()
657
+ }
658
+
659
+ isPresented = true
660
+ isSheetVisible = true
661
+ }
662
+
592
663
  fun dismiss(animated: Boolean = true) {
593
664
  if (isDismissing) return
594
665
 
@@ -596,17 +667,40 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
596
667
  emitWillDismissEvents()
597
668
 
598
669
  if (animated) {
599
- sheetAnimator.animateDismiss(
600
- onUpdate = { effectiveTop -> updateSheetVisuals(effectiveTop) },
601
- onEnd = { dialogFragment?.dismiss() }
602
- )
670
+ animateDismiss()
603
671
  } else {
604
672
  emitChangePositionDelegate(realScreenHeight)
605
- dialogFragment?.dismiss()
673
+ finishDismiss()
674
+ }
675
+ }
676
+
677
+ private fun dismissOrCollapseToLowest() {
678
+ if (dismissible) {
679
+ dismiss(animated = true)
680
+ } else if (parentSheetView == null && isDimmedAtCurrentDetent && dimmedDetentIndex > 0) {
681
+ setStateForDetentIndex(dimmedDetentIndex - 1)
606
682
  }
607
683
  }
608
684
 
685
+ private fun animateDismiss() {
686
+ val sheet = sheetView ?: run {
687
+ finishDismiss()
688
+ return
689
+ }
690
+
691
+ sheet.animate()
692
+ .y(realScreenHeight.toFloat())
693
+ .setDuration(DISMISS_DURATION)
694
+ .setInterpolator(android.view.animation.AccelerateInterpolator())
695
+ .setUpdateListener { updateSheetVisuals(sheet.y.toInt()) }
696
+ .withEndAction { finishDismiss() }
697
+ .start()
698
+ }
699
+
609
700
  private fun finishPresent() {
701
+ // Restore isHideable to actual value after present animation
702
+ behavior?.isHideable = dismissible
703
+
610
704
  val (index, position, detent) = getDetentInfoWithValue(currentDetentIndex)
611
705
  delegate?.viewControllerDidPresent(index, position, detent)
612
706
  parentSheetView?.viewControllerDidBlur()
@@ -616,40 +710,47 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
616
710
  presentPromise = null
617
711
  }
618
712
 
713
+ private fun finishDismiss() {
714
+ emitDidDismissEvents()
715
+ cleanupSheet()
716
+ }
717
+
619
718
  // =============================================================================
620
719
  // MARK: - Sheet Configuration
621
720
  // =============================================================================
622
721
 
623
722
  fun setupSheetDetents() {
624
- val fragment = this.dialogFragment ?: return
625
- val behavior = this.behavior ?: return
723
+ val behavior = this.behavior ?: run {
724
+ RNLog.e(reactContext, "TrueSheet: behavior is null in setupSheetDetents")
725
+ return
726
+ }
626
727
 
627
728
  interactionState = InteractionState.Reconfiguring
628
- val edgeToEdgeTopInset: Int = if (!edgeToEdgeFullScreen) topInset else 0
629
729
 
630
730
  behavior.isFitToContents = false
631
731
 
632
- val maxAvailableHeight = realScreenHeight - edgeToEdgeTopInset
732
+ val maxAvailableHeight = realScreenHeight - topInset
633
733
 
634
- val peekHeight = detentCalculator.getDetentHeight(detents[0])
734
+ val peekHeight = minOf(detentCalculator.getDetentHeight(detents[0]), maxAvailableHeight)
635
735
 
636
736
  val halfExpandedDetentHeight = when (detents.size) {
637
737
  1 -> peekHeight
638
738
  else -> detentCalculator.getDetentHeight(detents[1])
639
739
  }
640
740
 
641
- val maxDetentHeight = detentCalculator.getDetentHeight(detents.last())
741
+ val maxDetentHeight = minOf(detentCalculator.getDetentHeight(detents.last()), maxAvailableHeight)
642
742
 
643
743
  val adjustedHalfExpandedHeight = minOf(halfExpandedDetentHeight, maxAvailableHeight)
644
744
  val halfExpandedRatio = (adjustedHalfExpandedHeight.toFloat() / realScreenHeight.toFloat())
645
745
  .coerceIn(0f, 0.999f)
646
746
 
647
- val expandedOffset = maxOf(edgeToEdgeTopInset, realScreenHeight - maxDetentHeight)
747
+ val expandedOffset = realScreenHeight - maxDetentHeight
648
748
 
649
749
  // fitToContents works better with <= 2 detents when no expanded offset
650
750
  val fitToContents = detents.size < 3 && expandedOffset == 0
651
751
 
652
- fragment.configureDetents(
752
+ configureDetents(
753
+ behavior = behavior,
653
754
  peekHeight = peekHeight,
654
755
  halfExpandedRatio = halfExpandedRatio,
655
756
  expandedOffset = expandedOffset,
@@ -674,87 +775,76 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
674
775
  interactionState = InteractionState.Idle
675
776
  }
676
777
 
778
+ private fun configureDetents(
779
+ behavior: BottomSheetBehavior<TrueSheetBottomSheetView>,
780
+ peekHeight: Int,
781
+ halfExpandedRatio: Float,
782
+ expandedOffset: Int,
783
+ fitToContents: Boolean,
784
+ animate: Boolean
785
+ ) {
786
+ behavior.apply {
787
+ isFitToContents = fitToContents
788
+ skipCollapsed = false
789
+ setPeekHeight(peekHeight, animate)
790
+ this.halfExpandedRatio = halfExpandedRatio.coerceIn(0f, 0.999f)
791
+ this.expandedOffset = expandedOffset
792
+ }
793
+ }
794
+
677
795
  fun setupSheetDetentsForSizeChange() {
678
796
  setupSheetDetents()
679
797
  positionFooter()
680
798
  }
681
799
 
682
800
  fun setStateForDetentIndex(index: Int) {
683
- dialogFragment?.setState(detentCalculator.getStateForDetentIndex(index))
801
+ behavior?.state = detentCalculator.getStateForDetentIndex(index)
684
802
  }
685
803
 
686
804
  // =============================================================================
687
- // MARK: - Grabber & Background
805
+ // MARK: - Dimmed Background
688
806
  // =============================================================================
689
807
 
690
- fun setupGrabber() {
691
- dialogFragment?.let {
692
- syncFragmentProperties(it)
693
- it.setupGrabber()
694
- }
695
- }
696
-
697
- fun setupBackground() {
698
- dialogFragment?.let {
699
- syncFragmentProperties(it)
700
- it.setupBackground()
701
- }
702
- }
703
-
704
808
  fun setupDimmedBackground(detentIndex: Int) {
705
- val dialog = this.dialog ?: return
706
-
707
- dialog.window?.apply {
708
- val touchOutside = findViewById<View>(com.google.android.material.R.id.touch_outside)
709
- clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
710
-
711
- val shouldDimAtDetent = dimmed && detentIndex >= dimmedDetentIndex
712
-
713
- if (dimmed) {
714
- val parentDimVisible = (parentSheetView?.viewController?.dimView?.alpha ?: 0f) > 0f
809
+ val coordinator = this.coordinatorLayout ?: run {
810
+ RNLog.e(reactContext, "TrueSheet: coordinatorLayout is null in setupDimmedBackground")
811
+ return
812
+ }
715
813
 
716
- if (dimView == null) dimView = TrueSheetDimView(reactContext)
717
- if (!parentDimVisible) dimView?.attach(null)
814
+ if (dimmed) {
815
+ val parentDimVisible = (parentSheetView?.viewController?.dimView?.alpha ?: 0f) > 0f
718
816
 
719
- // Attach dim view to parent sheet if stacked
720
- val parentController = parentSheetView?.viewController
721
- val parentBottomSheet = parentController?.bottomSheetView
722
- if (parentBottomSheet != null) {
723
- if (parentDimView == null) parentDimView = TrueSheetDimView(reactContext)
724
- parentDimView?.attach(parentBottomSheet, parentController.sheetCornerRadius)
817
+ if (dimView == null) {
818
+ dimView = TrueSheetDimView(reactContext).apply {
819
+ delegate = this@TrueSheetViewController
725
820
  }
726
- } else {
727
- dimView?.detach()
728
- dimView = null
729
- parentDimView?.detach()
730
- parentDimView = null
821
+ }
822
+ if (!parentDimVisible) {
823
+ dimView?.attachToCoordinator(coordinator)
731
824
  }
732
825
 
733
- if (shouldDimAtDetent) {
734
- touchOutside.setOnTouchListener { _, event ->
735
- if (event.action == MotionEvent.ACTION_UP && dismissible) {
736
- dismiss()
826
+ // Attach dim view to parent sheet if stacked
827
+ val parentController = parentSheetView?.viewController
828
+ val parentBottomSheet = parentController?.sheetView
829
+ if (parentBottomSheet != null) {
830
+ if (parentDimView == null) {
831
+ parentDimView = TrueSheetDimView(reactContext).apply {
832
+ delegate = this@TrueSheetViewController
737
833
  }
738
- true
739
834
  }
740
- } else {
741
- // Pass through touches to parent or activity when not dimmed
742
- touchOutside.setOnTouchListener { v, event ->
743
- event.setLocation(event.rawX - v.x, event.rawY - v.y)
744
- (
745
- parentSheetView?.viewController?.dialog?.window?.decorView
746
- ?: reactContext.currentActivity?.window?.decorView
747
- )?.dispatchTouchEvent(event)
748
- false
749
- }
750
- dialog.setCanceledOnTouchOutside(false)
835
+ parentDimView?.attach(parentBottomSheet, parentController.sheetCornerRadius)
751
836
  }
837
+ } else {
838
+ dimView?.detach()
839
+ dimView = null
840
+ parentDimView?.detach()
841
+ parentDimView = null
752
842
  }
753
843
  }
754
844
 
755
845
  fun updateDimAmount(sheetTop: Int? = null, animated: Boolean = false) {
756
846
  if (!dimmed) return
757
- val top = (sheetTop ?: bottomSheetView?.top ?: return) + currentKeyboardInset
847
+ val top = (sheetTop ?: sheetView?.top ?: return) + currentKeyboardInset
758
848
 
759
849
  if (animated) {
760
850
  val targetAlpha = dimView?.calculateAlpha(
@@ -775,11 +865,11 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
775
865
  fun positionFooter(slideOffset: Float? = null) {
776
866
  if (!isPresented) return
777
867
  val footerView = containerView?.footerView ?: return
778
- val bottomSheet = bottomSheetView ?: return
868
+ val sheet = sheetView ?: return
779
869
 
780
870
  val footerHeight = footerView.height
781
- val sheetHeight = bottomSheet.height
782
- val sheetTop = bottomSheet.top
871
+ val sheetHeight = sheet.height
872
+ val sheetTop = sheet.top
783
873
 
784
874
  var footerY = (sheetHeight - sheetTop - footerHeight - currentKeyboardInset).toFloat()
785
875
 
@@ -803,14 +893,17 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
803
893
  }
804
894
 
805
895
  fun setupKeyboardObserver() {
806
- val bottomSheet = bottomSheetView ?: return
896
+ val coordinator = coordinatorLayout ?: run {
897
+ RNLog.e(reactContext, "TrueSheet: coordinatorLayout is null in setupKeyboardObserver")
898
+ return
899
+ }
807
900
  cleanupKeyboardObserver()
808
- keyboardObserver = TrueSheetKeyboardObserver(bottomSheet, reactContext).apply {
901
+ keyboardObserver = TrueSheetKeyboardObserver(coordinator, reactContext).apply {
809
902
  delegate = object : TrueSheetKeyboardObserverDelegate {
810
903
  override fun keyboardWillShow(height: Int) {
904
+ isKeyboardTransitioning = true
811
905
  if (!shouldHandleKeyboard()) return
812
906
  detentIndexBeforeKeyboard = currentDetentIndex
813
- isKeyboardTransitioning = true
814
907
  setupSheetDetents()
815
908
  setStateForDetentIndex(detents.size - 1)
816
909
  }
@@ -825,7 +918,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
825
918
  }
826
919
 
827
920
  override fun keyboardDidHide() {
828
- if (!shouldHandleKeyboard()) return
829
921
  isKeyboardTransitioning = false
830
922
  }
831
923
 
@@ -919,19 +1011,14 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
919
1011
  // MARK: - Detent Helpers
920
1012
  // =============================================================================
921
1013
 
922
- fun getExpectedSheetTop(detentIndex: Int): Int {
923
- if (detentIndex < 0 || detentIndex >= detents.size) return screenHeight
924
- return realScreenHeight - detentCalculator.getDetentHeight(detents[detentIndex])
925
- }
926
-
927
- fun translateDialog(translationY: Int) {
928
- val bottomSheet = bottomSheetView ?: return
1014
+ fun translateSheet(translationY: Int) {
1015
+ val sheet = sheetView ?: return
929
1016
 
930
- bottomSheet.animate()
1017
+ sheet.animate()
931
1018
  .translationY(translationY.toFloat())
932
1019
  .setDuration(TRANSLATE_ANIMATION_DURATION)
933
1020
  .setUpdateListener {
934
- val effectiveTop = bottomSheet.top + bottomSheet.translationY.toInt()
1021
+ val effectiveTop = sheet.top + sheet.translationY.toInt()
935
1022
  emitChangePositionDelegate(effectiveTop)
936
1023
  }
937
1024
  .start()
@@ -948,7 +1035,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
948
1035
  private fun getPositionForDetentIndex(index: Int): Float {
949
1036
  if (index < 0 || index >= detents.size) return screenHeight.pxToDp()
950
1037
 
951
- bottomSheetView?.let {
1038
+ sheetView?.let {
952
1039
  val visibleSheetHeight = detentCalculator.getVisibleSheetHeight(it.top)
953
1040
  if (visibleSheetHeight in 1..<realScreenHeight) {
954
1041
  return detentCalculator.getPositionDp(visibleSheetHeight)
@@ -980,7 +1067,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
980
1067
  post {
981
1068
  setupSheetDetents()
982
1069
  positionFooter()
983
- bottomSheetView?.let { emitChangePositionDelegate(it.top, realtime = false) }
1070
+ sheetView?.let { emitChangePositionDelegate(it.top, realtime = false) }
984
1071
  }
985
1072
  }
986
1073
 
@@ -1021,41 +1108,41 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
1021
1108
 
1022
1109
  override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
1023
1110
  eventDispatcher?.let {
1024
- jSTouchDispatcher.handleTouchEvent(event, it, reactContext)
1025
- jSPointerDispatcher?.handleMotionEvent(event, it, true)
1111
+ jsTouchDispatcher.handleTouchEvent(event, it, reactContext)
1112
+ jsPointerDispatcher?.handleMotionEvent(event, it, true)
1026
1113
  }
1027
1114
  return super.onInterceptTouchEvent(event)
1028
1115
  }
1029
1116
 
1030
1117
  override fun onTouchEvent(event: MotionEvent): Boolean {
1031
1118
  eventDispatcher?.let {
1032
- jSTouchDispatcher.handleTouchEvent(event, it, reactContext)
1033
- jSPointerDispatcher?.handleMotionEvent(event, it, false)
1119
+ jsTouchDispatcher.handleTouchEvent(event, it, reactContext)
1120
+ jsPointerDispatcher?.handleMotionEvent(event, it, false)
1034
1121
  }
1035
1122
  super.onTouchEvent(event)
1036
1123
  return true
1037
1124
  }
1038
1125
 
1039
1126
  override fun onInterceptHoverEvent(event: MotionEvent): Boolean {
1040
- eventDispatcher?.let { jSPointerDispatcher?.handleMotionEvent(event, it, true) }
1127
+ eventDispatcher?.let { jsPointerDispatcher?.handleMotionEvent(event, it, true) }
1041
1128
  return super.onHoverEvent(event)
1042
1129
  }
1043
1130
 
1044
1131
  override fun onHoverEvent(event: MotionEvent): Boolean {
1045
- eventDispatcher?.let { jSPointerDispatcher?.handleMotionEvent(event, it, false) }
1132
+ eventDispatcher?.let { jsPointerDispatcher?.handleMotionEvent(event, it, false) }
1046
1133
  return super.onHoverEvent(event)
1047
1134
  }
1048
1135
 
1049
1136
  override fun onChildStartedNativeGesture(childView: View?, ev: MotionEvent) {
1050
1137
  eventDispatcher?.let {
1051
- jSTouchDispatcher.onChildStartedNativeGesture(ev, it)
1052
- jSPointerDispatcher?.onChildStartedNativeGesture(childView, ev, it)
1138
+ jsTouchDispatcher.onChildStartedNativeGesture(ev, it)
1139
+ jsPointerDispatcher?.onChildStartedNativeGesture(childView, ev, it)
1053
1140
  }
1054
1141
  }
1055
1142
 
1056
1143
  override fun onChildEndedNativeGesture(childView: View, ev: MotionEvent) {
1057
- eventDispatcher?.let { jSTouchDispatcher.onChildEndedNativeGesture(ev, it) }
1058
- jSPointerDispatcher?.onChildEndedNativeGesture()
1144
+ eventDispatcher?.let { jsTouchDispatcher.onChildEndedNativeGesture(ev, it) }
1145
+ jsPointerDispatcher?.onChildEndedNativeGesture()
1059
1146
  }
1060
1147
 
1061
1148
  override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {