@lodev09/react-native-true-sheet 3.7.0-beta.0 → 3.7.0-beta.2
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.
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerViewManager.kt +7 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentViewManager.kt +7 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterViewManager.kt +7 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetHeaderViewManager.kt +7 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +5 -27
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +86 -0
- package/android/src/main/java/com/lodev09/truesheet/core/RNScreensFragmentObserver.kt +181 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +4 -15
- package/package.json +1 -1
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
package com.lodev09.truesheet
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.module.annotations.ReactModule
|
|
4
|
+
import com.facebook.react.uimanager.PointerEvents
|
|
4
5
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
5
6
|
import com.facebook.react.uimanager.ViewGroupManager
|
|
7
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* ViewManager for TrueSheetContainerView
|
|
@@ -15,6 +17,11 @@ class TrueSheetContainerViewManager : ViewGroupManager<TrueSheetContainerView>()
|
|
|
15
17
|
|
|
16
18
|
override fun createViewInstance(reactContext: ThemedReactContext): TrueSheetContainerView = TrueSheetContainerView(reactContext)
|
|
17
19
|
|
|
20
|
+
@ReactProp(name = "pointerEvents")
|
|
21
|
+
fun setPointerEvents(view: TrueSheetContainerView, pointerEventsStr: String?) {
|
|
22
|
+
view.pointerEvents = PointerEvents.parsePointerEvents(pointerEventsStr)
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
companion object {
|
|
19
26
|
const val REACT_CLASS = "TrueSheetContainerView"
|
|
20
27
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
package com.lodev09.truesheet
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.module.annotations.ReactModule
|
|
4
|
+
import com.facebook.react.uimanager.PointerEvents
|
|
4
5
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
5
6
|
import com.facebook.react.uimanager.ViewGroupManager
|
|
7
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* ViewManager for TrueSheetContentView
|
|
@@ -15,6 +17,11 @@ class TrueSheetContentViewManager : ViewGroupManager<TrueSheetContentView>() {
|
|
|
15
17
|
|
|
16
18
|
override fun createViewInstance(reactContext: ThemedReactContext): TrueSheetContentView = TrueSheetContentView(reactContext)
|
|
17
19
|
|
|
20
|
+
@ReactProp(name = "pointerEvents")
|
|
21
|
+
fun setPointerEvents(view: TrueSheetContentView, pointerEventsStr: String?) {
|
|
22
|
+
view.pointerEvents = PointerEvents.parsePointerEvents(pointerEventsStr)
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
companion object {
|
|
19
26
|
const val REACT_CLASS = "TrueSheetContentView"
|
|
20
27
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
package com.lodev09.truesheet
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.module.annotations.ReactModule
|
|
4
|
+
import com.facebook.react.uimanager.PointerEvents
|
|
4
5
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
5
6
|
import com.facebook.react.uimanager.ViewGroupManager
|
|
7
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* ViewManager for TrueSheetFooterView
|
|
@@ -15,6 +17,11 @@ class TrueSheetFooterViewManager : ViewGroupManager<TrueSheetFooterView>() {
|
|
|
15
17
|
|
|
16
18
|
override fun createViewInstance(reactContext: ThemedReactContext): TrueSheetFooterView = TrueSheetFooterView(reactContext)
|
|
17
19
|
|
|
20
|
+
@ReactProp(name = "pointerEvents")
|
|
21
|
+
fun setPointerEvents(view: TrueSheetFooterView, pointerEventsStr: String?) {
|
|
22
|
+
view.pointerEvents = PointerEvents.parsePointerEvents(pointerEventsStr)
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
companion object {
|
|
19
26
|
const val REACT_CLASS = "TrueSheetFooterView"
|
|
20
27
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
package com.lodev09.truesheet
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.module.annotations.ReactModule
|
|
4
|
+
import com.facebook.react.uimanager.PointerEvents
|
|
4
5
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
5
6
|
import com.facebook.react.uimanager.ViewGroupManager
|
|
7
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* ViewManager for TrueSheetHeaderView
|
|
@@ -15,6 +17,11 @@ class TrueSheetHeaderViewManager : ViewGroupManager<TrueSheetHeaderView>() {
|
|
|
15
17
|
|
|
16
18
|
override fun createViewInstance(reactContext: ThemedReactContext): TrueSheetHeaderView = TrueSheetHeaderView(reactContext)
|
|
17
19
|
|
|
20
|
+
@ReactProp(name = "pointerEvents")
|
|
21
|
+
fun setPointerEvents(view: TrueSheetHeaderView, pointerEventsStr: String?) {
|
|
22
|
+
view.pointerEvents = PointerEvents.parsePointerEvents(pointerEventsStr)
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
companion object {
|
|
19
26
|
const val REACT_CLASS = "TrueSheetHeaderView"
|
|
20
27
|
}
|
|
@@ -151,6 +151,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
151
151
|
// ==================== Lifecycle ====================
|
|
152
152
|
|
|
153
153
|
override fun onHostResume() {
|
|
154
|
+
viewController.reapplyHiddenState()
|
|
154
155
|
finalizeUpdates()
|
|
155
156
|
}
|
|
156
157
|
|
|
@@ -277,10 +278,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
277
278
|
if (!viewController.isPresented) {
|
|
278
279
|
// Attach coordinator to the root container
|
|
279
280
|
rootContainerView = findRootContainerView()
|
|
280
|
-
viewController.coordinatorLayout?.let {
|
|
281
|
-
rootContainerView?.addView(coordinator)
|
|
282
|
-
coordinator.post { measureCoordinatorLayout() }
|
|
283
|
-
}
|
|
281
|
+
viewController.coordinatorLayout?.let { rootContainerView?.addView(it) }
|
|
284
282
|
|
|
285
283
|
// Register with observer to track sheet stack hierarchy
|
|
286
284
|
viewController.parentSheetView = TrueSheetStackManager.onSheetWillPresent(this, detentIndex)
|
|
@@ -436,7 +434,6 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
436
434
|
|
|
437
435
|
override fun viewControllerDidChangeSize(width: Int, height: Int) {
|
|
438
436
|
updateState(width, height)
|
|
439
|
-
measureCoordinatorLayout()
|
|
440
437
|
}
|
|
441
438
|
|
|
442
439
|
override fun viewControllerWillFocus() {
|
|
@@ -481,19 +478,6 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
481
478
|
|
|
482
479
|
// ==================== Private Helpers ====================
|
|
483
480
|
|
|
484
|
-
private fun measureCoordinatorLayout() {
|
|
485
|
-
val coordinator = viewController.coordinatorLayout ?: return
|
|
486
|
-
val width = viewController.screenWidth
|
|
487
|
-
val height = viewController.realScreenHeight
|
|
488
|
-
|
|
489
|
-
if (width > 0 && height > 0) {
|
|
490
|
-
val widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY)
|
|
491
|
-
val heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
|
|
492
|
-
coordinator.measure(widthSpec, heightSpec)
|
|
493
|
-
coordinator.layout(0, 0, width, height)
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
481
|
/**
|
|
498
482
|
* Find the root container view for presenting the sheet.
|
|
499
483
|
* This traverses up the view hierarchy to find the content view (android.R.id.content)
|
|
@@ -502,20 +486,14 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
502
486
|
*/
|
|
503
487
|
private fun findRootContainerView(): ViewGroup? {
|
|
504
488
|
var current: android.view.ViewParent? = parent
|
|
505
|
-
var contentView: ViewGroup? = null
|
|
506
489
|
|
|
507
490
|
while (current != null) {
|
|
508
|
-
if (current is ViewGroup) {
|
|
509
|
-
|
|
510
|
-
return current
|
|
511
|
-
}
|
|
512
|
-
if (contentView == null && current.id == android.R.id.content) {
|
|
513
|
-
contentView = current
|
|
514
|
-
}
|
|
491
|
+
if (current is ViewGroup && current.id == android.R.id.content) {
|
|
492
|
+
return current
|
|
515
493
|
}
|
|
516
494
|
current = current.parent
|
|
517
495
|
}
|
|
518
496
|
|
|
519
|
-
return
|
|
497
|
+
return reactContext.currentActivity?.findViewById(android.R.id.content)
|
|
520
498
|
}
|
|
521
499
|
}
|
|
@@ -23,6 +23,7 @@ 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
25
|
import com.lodev09.truesheet.core.GrabberOptions
|
|
26
|
+
import com.lodev09.truesheet.core.RNScreensFragmentObserver
|
|
26
27
|
import com.lodev09.truesheet.core.TrueSheetBottomSheetView
|
|
27
28
|
import com.lodev09.truesheet.core.TrueSheetBottomSheetViewDelegate
|
|
28
29
|
import com.lodev09.truesheet.core.TrueSheetCoordinatorLayout
|
|
@@ -89,6 +90,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
89
90
|
private const val DEFAULT_CORNER_RADIUS = 16 // dp
|
|
90
91
|
private const val TRANSLATE_ANIMATION_DURATION = 200L
|
|
91
92
|
private const val DISMISS_DURATION = 200L
|
|
93
|
+
private const val MODAL_FADE_DURATION = 150L
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
// =============================================================================
|
|
@@ -128,6 +130,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
128
130
|
|
|
129
131
|
private var interactionState: InteractionState = InteractionState.Idle
|
|
130
132
|
private var isDismissing = false
|
|
133
|
+
private var wasHiddenByModal = false
|
|
131
134
|
private var shouldAnimatePresent = false
|
|
132
135
|
private var isPresentAnimating = false
|
|
133
136
|
|
|
@@ -147,6 +150,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
147
150
|
|
|
148
151
|
// Helper Objects
|
|
149
152
|
private var keyboardObserver: TrueSheetKeyboardObserver? = null
|
|
153
|
+
private var rnScreensObserver: RNScreensFragmentObserver? = null
|
|
150
154
|
internal val detentCalculator = TrueSheetDetentCalculator(reactContext).apply {
|
|
151
155
|
delegate = this@TrueSheetViewController
|
|
152
156
|
}
|
|
@@ -303,6 +307,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
303
307
|
|
|
304
308
|
private fun cleanupSheet() {
|
|
305
309
|
cleanupKeyboardObserver()
|
|
310
|
+
cleanupModalObserver()
|
|
306
311
|
cleanupBackCallback()
|
|
307
312
|
sheetView?.animate()?.cancel()
|
|
308
313
|
|
|
@@ -322,6 +327,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
322
327
|
isDismissing = false
|
|
323
328
|
isPresented = false
|
|
324
329
|
isSheetVisible = false
|
|
330
|
+
wasHiddenByModal = false
|
|
325
331
|
isPresentAnimating = false
|
|
326
332
|
lastEmittedPositionPx = -1
|
|
327
333
|
detentIndexBeforeKeyboard = -1
|
|
@@ -498,6 +504,84 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
498
504
|
}
|
|
499
505
|
}
|
|
500
506
|
|
|
507
|
+
// =============================================================================
|
|
508
|
+
// MARK: - Modal Observer (react-native-screens)
|
|
509
|
+
// =============================================================================
|
|
510
|
+
|
|
511
|
+
private fun setupModalObserver() {
|
|
512
|
+
rnScreensObserver = RNScreensFragmentObserver(
|
|
513
|
+
reactContext = reactContext,
|
|
514
|
+
onModalPresented = {
|
|
515
|
+
if (isPresented && isSheetVisible && isTopmostSheet) {
|
|
516
|
+
hideForModal()
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
onModalWillDismiss = {
|
|
520
|
+
if (isPresented && wasHiddenByModal && isTopmostSheet) {
|
|
521
|
+
showAfterModal()
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
onModalDidDismiss = {
|
|
525
|
+
if (isPresented && wasHiddenByModal) {
|
|
526
|
+
wasHiddenByModal = false
|
|
527
|
+
// Restore parent sheet after this sheet is restored
|
|
528
|
+
parentSheetView?.viewController?.let { parent ->
|
|
529
|
+
post { parent.showAfterModal() }
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
)
|
|
534
|
+
rnScreensObserver?.start()
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
private fun cleanupModalObserver() {
|
|
538
|
+
rnScreensObserver?.stop()
|
|
539
|
+
rnScreensObserver = null
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
private fun setSheetVisibility(visible: Boolean) {
|
|
543
|
+
coordinatorLayout?.visibility = if (visible) VISIBLE else GONE
|
|
544
|
+
dimViews.forEach { it.visibility = if (visible) VISIBLE else INVISIBLE }
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
private fun hideForModal() {
|
|
548
|
+
val sheet = sheetView ?: run {
|
|
549
|
+
RNLog.e(reactContext, "TrueSheet: sheetView is null in hideForModal")
|
|
550
|
+
return
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
isSheetVisible = false
|
|
554
|
+
wasHiddenByModal = true
|
|
555
|
+
|
|
556
|
+
dimViews.forEach { it.animate().alpha(0f).setDuration(MODAL_FADE_DURATION).start() }
|
|
557
|
+
sheet.animate()
|
|
558
|
+
.alpha(0f)
|
|
559
|
+
.setDuration(MODAL_FADE_DURATION)
|
|
560
|
+
.withEndAction {
|
|
561
|
+
setSheetVisibility(false)
|
|
562
|
+
}
|
|
563
|
+
.start()
|
|
564
|
+
|
|
565
|
+
// This will hide parent sheets first
|
|
566
|
+
parentSheetView?.viewController?.hideForModal()
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
private fun showAfterModal() {
|
|
570
|
+
isSheetVisible = true
|
|
571
|
+
setSheetVisibility(true)
|
|
572
|
+
sheetView?.alpha = 1f
|
|
573
|
+
updateDimAmount(animated = true)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Re-applies hidden state after returning from background.
|
|
578
|
+
* Android may restore visibility on activity resume, so we need to hide it again.
|
|
579
|
+
*/
|
|
580
|
+
fun reapplyHiddenState() {
|
|
581
|
+
if (!wasHiddenByModal) return
|
|
582
|
+
setSheetVisibility(false)
|
|
583
|
+
}
|
|
584
|
+
|
|
501
585
|
// =============================================================================
|
|
502
586
|
// MARK: - Presentation
|
|
503
587
|
// =============================================================================
|
|
@@ -529,6 +613,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
529
613
|
setupSheetDetents()
|
|
530
614
|
setupDimmedBackground(currentDetentIndex)
|
|
531
615
|
setupKeyboardObserver()
|
|
616
|
+
setupModalObserver()
|
|
532
617
|
setupBackCallback()
|
|
533
618
|
|
|
534
619
|
sheet.setupBackground()
|
|
@@ -794,6 +879,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
794
879
|
// =============================================================================
|
|
795
880
|
|
|
796
881
|
private fun shouldHandleKeyboard(checkFocus: Boolean = true): Boolean {
|
|
882
|
+
if (wasHiddenByModal) return false
|
|
797
883
|
if (!isTopmostSheet) return false
|
|
798
884
|
if (checkFocus && !isFocusedViewWithinSheet()) return false
|
|
799
885
|
return true
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
package com.lodev09.truesheet.core
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import androidx.appcompat.app.AppCompatActivity
|
|
5
|
+
import androidx.fragment.app.Fragment
|
|
6
|
+
import androidx.fragment.app.FragmentManager
|
|
7
|
+
import androidx.lifecycle.DefaultLifecycleObserver
|
|
8
|
+
import androidx.lifecycle.LifecycleOwner
|
|
9
|
+
import com.facebook.react.bridge.ReactContext
|
|
10
|
+
|
|
11
|
+
private const val RN_SCREENS_PACKAGE = "com.swmansion.rnscreens"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Observes fragment lifecycle to detect react-native-screens modal presentation.
|
|
15
|
+
* Automatically notifies when modals are presented/dismissed.
|
|
16
|
+
*/
|
|
17
|
+
class RNScreensFragmentObserver(
|
|
18
|
+
private val reactContext: ReactContext,
|
|
19
|
+
private val onModalPresented: () -> Unit,
|
|
20
|
+
private val onModalWillDismiss: () -> Unit,
|
|
21
|
+
private val onModalDidDismiss: () -> Unit
|
|
22
|
+
) {
|
|
23
|
+
private var fragmentLifecycleCallback: FragmentManager.FragmentLifecycleCallbacks? = null
|
|
24
|
+
private var activityLifecycleObserver: DefaultLifecycleObserver? = null
|
|
25
|
+
private val activeModalFragments: MutableSet<Fragment> = mutableSetOf()
|
|
26
|
+
private var isActivityInForeground = true
|
|
27
|
+
private var pendingDismissRunnable: Runnable? = null
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Start observing fragment lifecycle events.
|
|
31
|
+
*/
|
|
32
|
+
fun start() {
|
|
33
|
+
val activity = reactContext.currentActivity as? AppCompatActivity ?: return
|
|
34
|
+
val fragmentManager = activity.supportFragmentManager
|
|
35
|
+
|
|
36
|
+
// Track activity foreground state to ignore fragment lifecycle events during background/foreground transitions
|
|
37
|
+
activityLifecycleObserver = object : DefaultLifecycleObserver {
|
|
38
|
+
override fun onResume(owner: LifecycleOwner) {
|
|
39
|
+
isActivityInForeground = true
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
override fun onPause(owner: LifecycleOwner) {
|
|
43
|
+
isActivityInForeground = false
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
activity.lifecycle.addObserver(activityLifecycleObserver!!)
|
|
47
|
+
|
|
48
|
+
fragmentLifecycleCallback = object : FragmentManager.FragmentLifecycleCallbacks() {
|
|
49
|
+
override fun onFragmentAttached(fm: FragmentManager, f: Fragment, context: Context) {
|
|
50
|
+
super.onFragmentAttached(fm, f, context)
|
|
51
|
+
|
|
52
|
+
// Ignore if app is resuming from background
|
|
53
|
+
if (!isActivityInForeground) return
|
|
54
|
+
|
|
55
|
+
if (isModalFragment(f) && !activeModalFragments.contains(f)) {
|
|
56
|
+
// Cancel any pending dismiss since a modal is being presented
|
|
57
|
+
cancelPendingDismiss()
|
|
58
|
+
|
|
59
|
+
activeModalFragments.add(f)
|
|
60
|
+
|
|
61
|
+
if (activeModalFragments.size == 1) {
|
|
62
|
+
onModalPresented()
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
override fun onFragmentStopped(fm: FragmentManager, f: Fragment) {
|
|
68
|
+
super.onFragmentStopped(fm, f)
|
|
69
|
+
|
|
70
|
+
// Ignore if app is going to background (fragments stop with activity)
|
|
71
|
+
if (!isActivityInForeground) return
|
|
72
|
+
|
|
73
|
+
// Only trigger when fragment is being removed (not just stopped for navigation)
|
|
74
|
+
if (activeModalFragments.contains(f) && f.isRemoving) {
|
|
75
|
+
activeModalFragments.remove(f)
|
|
76
|
+
|
|
77
|
+
if (activeModalFragments.isEmpty()) {
|
|
78
|
+
// Post dismiss to allow fragment attach to cancel if navigation is happening
|
|
79
|
+
schedulePendingDismiss()
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
override fun onFragmentDestroyed(fm: FragmentManager, f: Fragment) {
|
|
85
|
+
super.onFragmentDestroyed(fm, f)
|
|
86
|
+
|
|
87
|
+
if (activeModalFragments.isEmpty() && pendingDismissRunnable == null) {
|
|
88
|
+
onModalDidDismiss()
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallback!!, true)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Stop observing and cleanup.
|
|
98
|
+
*/
|
|
99
|
+
fun stop() {
|
|
100
|
+
val activity = reactContext.currentActivity as? AppCompatActivity
|
|
101
|
+
|
|
102
|
+
cancelPendingDismiss()
|
|
103
|
+
|
|
104
|
+
fragmentLifecycleCallback?.let { callback ->
|
|
105
|
+
activity?.supportFragmentManager?.unregisterFragmentLifecycleCallbacks(callback)
|
|
106
|
+
}
|
|
107
|
+
fragmentLifecycleCallback = null
|
|
108
|
+
|
|
109
|
+
activityLifecycleObserver?.let { observer ->
|
|
110
|
+
activity?.lifecycle?.removeObserver(observer)
|
|
111
|
+
}
|
|
112
|
+
activityLifecycleObserver = null
|
|
113
|
+
|
|
114
|
+
activeModalFragments.clear()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private fun schedulePendingDismiss() {
|
|
118
|
+
val activity = reactContext.currentActivity ?: return
|
|
119
|
+
val decorView = activity.window?.decorView ?: return
|
|
120
|
+
|
|
121
|
+
cancelPendingDismiss()
|
|
122
|
+
|
|
123
|
+
pendingDismissRunnable = Runnable {
|
|
124
|
+
pendingDismissRunnable = null
|
|
125
|
+
if (activeModalFragments.isEmpty()) {
|
|
126
|
+
onModalWillDismiss()
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
decorView.post(pendingDismissRunnable)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private fun cancelPendingDismiss() {
|
|
133
|
+
val activity = reactContext.currentActivity ?: return
|
|
134
|
+
val decorView = activity.window?.decorView ?: return
|
|
135
|
+
|
|
136
|
+
pendingDismissRunnable?.let {
|
|
137
|
+
decorView.removeCallbacks(it)
|
|
138
|
+
pendingDismissRunnable = null
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
companion object {
|
|
143
|
+
/**
|
|
144
|
+
* Check if fragment is from react-native-screens.
|
|
145
|
+
*/
|
|
146
|
+
private fun isScreensFragment(fragment: Fragment): Boolean = fragment.javaClass.name.startsWith(RN_SCREENS_PACKAGE)
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Check if fragment is a react-native-screens modal (fullScreenModal, transparentModal, or formSheet).
|
|
150
|
+
* Uses reflection to check the fragment's screen.stackPresentation property.
|
|
151
|
+
*/
|
|
152
|
+
private fun isModalFragment(fragment: Fragment): Boolean {
|
|
153
|
+
val className = fragment.javaClass.name
|
|
154
|
+
|
|
155
|
+
if (!isScreensFragment(fragment)) {
|
|
156
|
+
return false
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ScreenModalFragment is always a modal (used for formSheet with BottomSheetDialog)
|
|
160
|
+
if (className.contains("ScreenModalFragment")) {
|
|
161
|
+
return true
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// For ScreenStackFragment, check the screen's stackPresentation via reflection
|
|
165
|
+
try {
|
|
166
|
+
val getScreenMethod = fragment.javaClass.getMethod("getScreen")
|
|
167
|
+
val screen = getScreenMethod.invoke(fragment) ?: return false
|
|
168
|
+
|
|
169
|
+
val getStackPresentationMethod = screen.javaClass.getMethod("getStackPresentation")
|
|
170
|
+
val stackPresentation = getStackPresentationMethod.invoke(screen) ?: return false
|
|
171
|
+
|
|
172
|
+
val presentationName = stackPresentation.toString()
|
|
173
|
+
return presentationName == "MODAL" ||
|
|
174
|
+
presentationName == "TRANSPARENT_MODAL" ||
|
|
175
|
+
presentationName == "FORM_SHEET"
|
|
176
|
+
} catch (e: Exception) {
|
|
177
|
+
return false
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -68,11 +68,12 @@ class TrueSheetDimView(private val reactContext: ThemedReactContext) :
|
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
70
|
* Attaches this dim view to a target view group.
|
|
71
|
+
* For CoordinatorLayout usage, pass null to use the default (activity's decor view).
|
|
71
72
|
* For stacked sheets, pass the parent sheet's bottom sheet view with corner radius.
|
|
72
73
|
*/
|
|
73
|
-
fun attach(view: ViewGroup, cornerRadius: Float = 0f) {
|
|
74
|
+
fun attach(view: ViewGroup? = null, cornerRadius: Float = 0f) {
|
|
74
75
|
if (parent != null) return
|
|
75
|
-
targetView = view
|
|
76
|
+
targetView = view ?: reactContext.currentActivity?.window?.decorView as? ViewGroup
|
|
76
77
|
|
|
77
78
|
if (cornerRadius > 0f) {
|
|
78
79
|
outlineProvider = object : ViewOutlineProvider() {
|
|
@@ -86,19 +87,7 @@ class TrueSheetDimView(private val reactContext: ThemedReactContext) :
|
|
|
86
87
|
clipToOutline = false
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// Manually measure and layout for React Native views (they don't layout native children)
|
|
92
|
-
view.post {
|
|
93
|
-
val width = view.width
|
|
94
|
-
val height = view.height
|
|
95
|
-
if (width > 0 && height > 0) {
|
|
96
|
-
val widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY)
|
|
97
|
-
val heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
|
|
98
|
-
measure(widthSpec, heightSpec)
|
|
99
|
-
layout(0, 0, width, height)
|
|
100
|
-
}
|
|
101
|
-
}
|
|
90
|
+
targetView?.addView(this)
|
|
102
91
|
}
|
|
103
92
|
|
|
104
93
|
/**
|
package/package.json
CHANGED