@lodev09/react-native-true-sheet 3.6.11 → 3.7.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +28 -6
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +29 -114
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +7 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +15 -4
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetStackManager.kt +22 -7
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.h +6 -0
- package/package.json +1 -1
- package/android/src/main/java/com/lodev09/truesheet/core/RNScreensFragmentObserver.kt +0 -181
|
@@ -66,7 +66,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
66
66
|
private var isSheetUpdatePending: Boolean = false
|
|
67
67
|
|
|
68
68
|
// Root container for the coordinator layout (activity or Modal dialog content view)
|
|
69
|
-
|
|
69
|
+
internal var rootContainerView: ViewGroup? = null
|
|
70
70
|
|
|
71
71
|
// ==================== Initialization ====================
|
|
72
72
|
|
|
@@ -151,7 +151,6 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
151
151
|
// ==================== Lifecycle ====================
|
|
152
152
|
|
|
153
153
|
override fun onHostResume() {
|
|
154
|
-
viewController.reapplyHiddenState()
|
|
155
154
|
finalizeUpdates()
|
|
156
155
|
}
|
|
157
156
|
|
|
@@ -278,7 +277,10 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
278
277
|
if (!viewController.isPresented) {
|
|
279
278
|
// Attach coordinator to the root container
|
|
280
279
|
rootContainerView = findRootContainerView()
|
|
281
|
-
viewController.coordinatorLayout?.let {
|
|
280
|
+
viewController.coordinatorLayout?.let { coordinator ->
|
|
281
|
+
rootContainerView?.addView(coordinator)
|
|
282
|
+
coordinator.post { measureCoordinatorLayout() }
|
|
283
|
+
}
|
|
282
284
|
|
|
283
285
|
// Register with observer to track sheet stack hierarchy
|
|
284
286
|
viewController.parentSheetView = TrueSheetStackManager.onSheetWillPresent(this, detentIndex)
|
|
@@ -434,6 +436,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
434
436
|
|
|
435
437
|
override fun viewControllerDidChangeSize(width: Int, height: Int) {
|
|
436
438
|
updateState(width, height)
|
|
439
|
+
measureCoordinatorLayout()
|
|
437
440
|
}
|
|
438
441
|
|
|
439
442
|
override fun viewControllerWillFocus() {
|
|
@@ -478,6 +481,19 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
478
481
|
|
|
479
482
|
// ==================== Private Helpers ====================
|
|
480
483
|
|
|
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
|
+
|
|
481
497
|
/**
|
|
482
498
|
* Find the root container view for presenting the sheet.
|
|
483
499
|
* This traverses up the view hierarchy to find the content view (android.R.id.content)
|
|
@@ -486,14 +502,20 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
486
502
|
*/
|
|
487
503
|
private fun findRootContainerView(): ViewGroup? {
|
|
488
504
|
var current: android.view.ViewParent? = parent
|
|
505
|
+
var contentView: ViewGroup? = null
|
|
489
506
|
|
|
490
507
|
while (current != null) {
|
|
491
|
-
if (current is ViewGroup
|
|
492
|
-
|
|
508
|
+
if (current is ViewGroup) {
|
|
509
|
+
if (current.javaClass.name == "com.swmansion.rnscreens.Screen") {
|
|
510
|
+
return current
|
|
511
|
+
}
|
|
512
|
+
if (contentView == null && current.id == android.R.id.content) {
|
|
513
|
+
contentView = current
|
|
514
|
+
}
|
|
493
515
|
}
|
|
494
516
|
current = current.parent
|
|
495
517
|
}
|
|
496
518
|
|
|
497
|
-
return reactContext.currentActivity?.findViewById(android.R.id.content)
|
|
519
|
+
return contentView ?: reactContext.currentActivity?.findViewById(android.R.id.content)
|
|
498
520
|
}
|
|
499
521
|
}
|
|
@@ -23,7 +23,6 @@ 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
|
|
27
26
|
import com.lodev09.truesheet.core.TrueSheetBottomSheetView
|
|
28
27
|
import com.lodev09.truesheet.core.TrueSheetBottomSheetViewDelegate
|
|
29
28
|
import com.lodev09.truesheet.core.TrueSheetCoordinatorLayout
|
|
@@ -90,7 +89,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
90
89
|
private const val DEFAULT_CORNER_RADIUS = 16 // dp
|
|
91
90
|
private const val TRANSLATE_ANIMATION_DURATION = 200L
|
|
92
91
|
private const val DISMISS_DURATION = 200L
|
|
93
|
-
private const val MODAL_FADE_DURATION = 150L
|
|
94
92
|
}
|
|
95
93
|
|
|
96
94
|
// =============================================================================
|
|
@@ -130,7 +128,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
130
128
|
|
|
131
129
|
private var interactionState: InteractionState = InteractionState.Idle
|
|
132
130
|
private var isDismissing = false
|
|
133
|
-
private var wasHiddenByModal = false
|
|
134
131
|
private var shouldAnimatePresent = false
|
|
135
132
|
private var isPresentAnimating = false
|
|
136
133
|
|
|
@@ -150,7 +147,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
150
147
|
|
|
151
148
|
// Helper Objects
|
|
152
149
|
private var keyboardObserver: TrueSheetKeyboardObserver? = null
|
|
153
|
-
private var rnScreensObserver: RNScreensFragmentObserver? = null
|
|
154
150
|
internal val detentCalculator = TrueSheetDetentCalculator(reactContext).apply {
|
|
155
151
|
delegate = this@TrueSheetViewController
|
|
156
152
|
}
|
|
@@ -307,7 +303,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
307
303
|
|
|
308
304
|
private fun cleanupSheet() {
|
|
309
305
|
cleanupKeyboardObserver()
|
|
310
|
-
cleanupModalObserver()
|
|
311
306
|
cleanupBackCallback()
|
|
312
307
|
sheetView?.animate()?.cancel()
|
|
313
308
|
|
|
@@ -327,7 +322,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
327
322
|
isDismissing = false
|
|
328
323
|
isPresented = false
|
|
329
324
|
isSheetVisible = false
|
|
330
|
-
wasHiddenByModal = false
|
|
331
325
|
isPresentAnimating = false
|
|
332
326
|
lastEmittedPositionPx = -1
|
|
333
327
|
detentIndexBeforeKeyboard = -1
|
|
@@ -367,6 +361,13 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
367
361
|
}
|
|
368
362
|
}
|
|
369
363
|
|
|
364
|
+
override fun coordinatorLayoutDidChangeConfiguration() {
|
|
365
|
+
if (!isPresented) return
|
|
366
|
+
|
|
367
|
+
updateStateDimensions()
|
|
368
|
+
sheetView?.let { emitChangePositionDelegate(it.top, realtime = false) }
|
|
369
|
+
}
|
|
370
|
+
|
|
370
371
|
// =============================================================================
|
|
371
372
|
// MARK: - TrueSheetDimViewDelegate
|
|
372
373
|
// =============================================================================
|
|
@@ -497,84 +498,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
497
498
|
}
|
|
498
499
|
}
|
|
499
500
|
|
|
500
|
-
// =============================================================================
|
|
501
|
-
// MARK: - Modal Observer (react-native-screens)
|
|
502
|
-
// =============================================================================
|
|
503
|
-
|
|
504
|
-
private fun setupModalObserver() {
|
|
505
|
-
rnScreensObserver = RNScreensFragmentObserver(
|
|
506
|
-
reactContext = reactContext,
|
|
507
|
-
onModalPresented = {
|
|
508
|
-
if (isPresented && isSheetVisible && isTopmostSheet) {
|
|
509
|
-
hideForModal()
|
|
510
|
-
}
|
|
511
|
-
},
|
|
512
|
-
onModalWillDismiss = {
|
|
513
|
-
if (isPresented && wasHiddenByModal && isTopmostSheet) {
|
|
514
|
-
showAfterModal()
|
|
515
|
-
}
|
|
516
|
-
},
|
|
517
|
-
onModalDidDismiss = {
|
|
518
|
-
if (isPresented && wasHiddenByModal) {
|
|
519
|
-
wasHiddenByModal = false
|
|
520
|
-
// Restore parent sheet after this sheet is restored
|
|
521
|
-
parentSheetView?.viewController?.let { parent ->
|
|
522
|
-
post { parent.showAfterModal() }
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
)
|
|
527
|
-
rnScreensObserver?.start()
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
private fun cleanupModalObserver() {
|
|
531
|
-
rnScreensObserver?.stop()
|
|
532
|
-
rnScreensObserver = null
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
private fun setSheetVisibility(visible: Boolean) {
|
|
536
|
-
coordinatorLayout?.visibility = if (visible) VISIBLE else GONE
|
|
537
|
-
dimViews.forEach { it.visibility = if (visible) VISIBLE else INVISIBLE }
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
private fun hideForModal() {
|
|
541
|
-
val sheet = sheetView ?: run {
|
|
542
|
-
RNLog.e(reactContext, "TrueSheet: sheetView is null in hideForModal")
|
|
543
|
-
return
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
isSheetVisible = false
|
|
547
|
-
wasHiddenByModal = true
|
|
548
|
-
|
|
549
|
-
dimViews.forEach { it.animate().alpha(0f).setDuration(MODAL_FADE_DURATION).start() }
|
|
550
|
-
sheet.animate()
|
|
551
|
-
.alpha(0f)
|
|
552
|
-
.setDuration(MODAL_FADE_DURATION)
|
|
553
|
-
.withEndAction {
|
|
554
|
-
setSheetVisibility(false)
|
|
555
|
-
}
|
|
556
|
-
.start()
|
|
557
|
-
|
|
558
|
-
// This will hide parent sheets first
|
|
559
|
-
parentSheetView?.viewController?.hideForModal()
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
private fun showAfterModal() {
|
|
563
|
-
isSheetVisible = true
|
|
564
|
-
setSheetVisibility(true)
|
|
565
|
-
sheetView?.alpha = 1f
|
|
566
|
-
updateDimAmount(animated = true)
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
/**
|
|
570
|
-
* Re-applies hidden state after returning from background.
|
|
571
|
-
* Android may restore visibility on activity resume, so we need to hide it again.
|
|
572
|
-
*/
|
|
573
|
-
fun reapplyHiddenState() {
|
|
574
|
-
if (!wasHiddenByModal) return
|
|
575
|
-
setSheetVisibility(false)
|
|
576
|
-
}
|
|
577
|
-
|
|
578
501
|
// =============================================================================
|
|
579
502
|
// MARK: - Presentation
|
|
580
503
|
// =============================================================================
|
|
@@ -606,7 +529,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
606
529
|
setupSheetDetents()
|
|
607
530
|
setupDimmedBackground(currentDetentIndex)
|
|
608
531
|
setupKeyboardObserver()
|
|
609
|
-
setupModalObserver()
|
|
610
532
|
setupBackCallback()
|
|
611
533
|
|
|
612
534
|
sheet.setupBackground()
|
|
@@ -749,15 +671,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
749
671
|
animate = isPresented
|
|
750
672
|
)
|
|
751
673
|
|
|
752
|
-
|
|
753
|
-
val newHeight = realScreenHeight - expandedOffset - offset
|
|
754
|
-
val newWidth = minOf(screenWidth, DEFAULT_MAX_WIDTH.dpToPx().toInt())
|
|
755
|
-
|
|
756
|
-
if (lastStateWidth != newWidth || lastStateHeight != newHeight) {
|
|
757
|
-
lastStateWidth = newWidth
|
|
758
|
-
lastStateHeight = newHeight
|
|
759
|
-
delegate?.viewControllerDidChangeSize(newWidth, newHeight)
|
|
760
|
-
}
|
|
674
|
+
updateStateDimensions(expandedOffset)
|
|
761
675
|
|
|
762
676
|
if (isPresented) {
|
|
763
677
|
setStateForDetentIndex(currentDetentIndex)
|
|
@@ -880,7 +794,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
880
794
|
// =============================================================================
|
|
881
795
|
|
|
882
796
|
private fun shouldHandleKeyboard(checkFocus: Boolean = true): Boolean {
|
|
883
|
-
if (wasHiddenByModal) return false
|
|
884
797
|
if (!isTopmostSheet) return false
|
|
885
798
|
if (checkFocus && !isFocusedViewWithinSheet()) return false
|
|
886
799
|
return true
|
|
@@ -907,14 +820,19 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
907
820
|
setupSheetDetents()
|
|
908
821
|
if (!isDismissing && detentIndexBeforeKeyboard >= 0) {
|
|
909
822
|
setStateForDetentIndex(detentIndexBeforeKeyboard)
|
|
910
|
-
detentIndexBeforeKeyboard = -1
|
|
911
823
|
}
|
|
912
824
|
}
|
|
913
825
|
|
|
914
|
-
override fun keyboardDidHide() {
|
|
826
|
+
override fun keyboardDidHide() {
|
|
827
|
+
if (!shouldHandleKeyboard(checkFocus = false)) return
|
|
828
|
+
detentIndexBeforeKeyboard = -1
|
|
829
|
+
positionFooter()
|
|
830
|
+
}
|
|
915
831
|
|
|
916
832
|
override fun keyboardDidChangeHeight(height: Int) {
|
|
917
|
-
if (
|
|
833
|
+
// Skip focus check if already handling keyboard (focus may be lost during hide)
|
|
834
|
+
val isHandlingKeyboard = detentIndexBeforeKeyboard >= 0
|
|
835
|
+
if (!shouldHandleKeyboard(checkFocus = !isHandlingKeyboard)) return
|
|
918
836
|
positionFooter()
|
|
919
837
|
}
|
|
920
838
|
}
|
|
@@ -1003,6 +921,19 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1003
921
|
// MARK: - Detent Helpers
|
|
1004
922
|
// =============================================================================
|
|
1005
923
|
|
|
924
|
+
private fun updateStateDimensions(expandedOffset: Int? = null) {
|
|
925
|
+
val offset = expandedOffset ?: (realScreenHeight - detentCalculator.getDetentHeight(detents.last()))
|
|
926
|
+
val topOffset = if (offset == 0) topInset else 0
|
|
927
|
+
val newHeight = realScreenHeight - offset - topOffset
|
|
928
|
+
val newWidth = minOf(screenWidth, DEFAULT_MAX_WIDTH.dpToPx().toInt())
|
|
929
|
+
|
|
930
|
+
if (lastStateWidth != newWidth || lastStateHeight != newHeight) {
|
|
931
|
+
lastStateWidth = newWidth
|
|
932
|
+
lastStateHeight = newHeight
|
|
933
|
+
delegate?.viewControllerDidChangeSize(newWidth, newHeight)
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
1006
937
|
fun translateSheet(translationY: Int) {
|
|
1007
938
|
val sheet = sheetView ?: return
|
|
1008
939
|
|
|
@@ -1047,22 +978,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1047
978
|
(getTag(R.id.react_test_id) as? String)?.let { info.viewIdResourceName = it }
|
|
1048
979
|
}
|
|
1049
980
|
|
|
1050
|
-
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
|
1051
|
-
super.onSizeChanged(w, h, oldw, oldh)
|
|
1052
|
-
|
|
1053
|
-
if (w == oldw && h == oldh) return
|
|
1054
|
-
if (!isPresented) return
|
|
1055
|
-
|
|
1056
|
-
// Skip reconfiguration if expanded and only height changed (e.g., keyboard)
|
|
1057
|
-
if (h + topInset >= screenHeight && isExpanded && oldw == w) return
|
|
1058
|
-
|
|
1059
|
-
post {
|
|
1060
|
-
setupSheetDetents()
|
|
1061
|
-
positionFooter()
|
|
1062
|
-
sheetView?.let { emitChangePositionDelegate(it.top, realtime = false) }
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
981
|
// =============================================================================
|
|
1067
982
|
// MARK: - RootView Touch Handling
|
|
1068
983
|
// =============================================================================
|
|
@@ -2,6 +2,7 @@ package com.lodev09.truesheet.core
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.content.Context
|
|
5
|
+
import android.content.res.Configuration
|
|
5
6
|
import android.view.MotionEvent
|
|
6
7
|
import android.view.ViewConfiguration
|
|
7
8
|
import android.view.ViewGroup
|
|
@@ -12,6 +13,7 @@ import com.facebook.react.uimanager.ReactPointerEventsView
|
|
|
12
13
|
|
|
13
14
|
interface TrueSheetCoordinatorLayoutDelegate {
|
|
14
15
|
fun coordinatorLayoutDidLayout(changed: Boolean)
|
|
16
|
+
fun coordinatorLayoutDidChangeConfiguration()
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
/**
|
|
@@ -53,6 +55,11 @@ class TrueSheetCoordinatorLayout(context: Context) :
|
|
|
53
55
|
delegate?.coordinatorLayoutDidLayout(changed)
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
override fun onConfigurationChanged(newConfig: Configuration?) {
|
|
59
|
+
super.onConfigurationChanged(newConfig)
|
|
60
|
+
delegate?.coordinatorLayoutDidChangeConfiguration()
|
|
61
|
+
}
|
|
62
|
+
|
|
56
63
|
override val pointerEvents: PointerEvents
|
|
57
64
|
get() = PointerEvents.BOX_NONE
|
|
58
65
|
|
|
@@ -68,12 +68,11 @@ 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).
|
|
72
71
|
* For stacked sheets, pass the parent sheet's bottom sheet view with corner radius.
|
|
73
72
|
*/
|
|
74
|
-
fun attach(view: ViewGroup
|
|
73
|
+
fun attach(view: ViewGroup, cornerRadius: Float = 0f) {
|
|
75
74
|
if (parent != null) return
|
|
76
|
-
targetView = view
|
|
75
|
+
targetView = view
|
|
77
76
|
|
|
78
77
|
if (cornerRadius > 0f) {
|
|
79
78
|
outlineProvider = object : ViewOutlineProvider() {
|
|
@@ -87,7 +86,19 @@ class TrueSheetDimView(private val reactContext: ThemedReactContext) :
|
|
|
87
86
|
clipToOutline = false
|
|
88
87
|
}
|
|
89
88
|
|
|
90
|
-
|
|
89
|
+
view.addView(this)
|
|
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
|
+
}
|
|
91
102
|
}
|
|
92
103
|
|
|
93
104
|
/**
|
|
@@ -13,12 +13,18 @@ object TrueSheetStackManager {
|
|
|
13
13
|
/**
|
|
14
14
|
* Called when a sheet is about to be presented.
|
|
15
15
|
* Returns the visible parent sheet to stack on, or null if none.
|
|
16
|
+
* Only returns a parent if it's in the same container (e.g., same Screen).
|
|
16
17
|
*/
|
|
17
18
|
@JvmStatic
|
|
18
19
|
fun onSheetWillPresent(sheetView: TrueSheetView, detentIndex: Int): TrueSheetView? {
|
|
19
20
|
synchronized(presentedSheetStack) {
|
|
21
|
+
val rootContainer = sheetView.rootContainerView
|
|
20
22
|
val parentSheet = presentedSheetStack.lastOrNull()
|
|
21
|
-
?.takeIf {
|
|
23
|
+
?.takeIf {
|
|
24
|
+
it.viewController.isPresented &&
|
|
25
|
+
it.viewController.isSheetVisible &&
|
|
26
|
+
it.rootContainerView == rootContainer
|
|
27
|
+
}
|
|
22
28
|
|
|
23
29
|
val childSheetTop = sheetView.viewController.detentCalculator.getSheetTopForDetentIndex(detentIndex)
|
|
24
30
|
parentSheet?.updateTranslationForChild(childSheetTop)
|
|
@@ -48,6 +54,7 @@ object TrueSheetStackManager {
|
|
|
48
54
|
/**
|
|
49
55
|
* Called when a presented sheet's size changes (e.g., after setupSheetDetents).
|
|
50
56
|
* Updates parent sheet translations to match the new sheet position.
|
|
57
|
+
* Only affects parent sheets in the same container.
|
|
51
58
|
*/
|
|
52
59
|
@JvmStatic
|
|
53
60
|
fun onSheetSizeChanged(sheetView: TrueSheetView) {
|
|
@@ -55,7 +62,9 @@ object TrueSheetStackManager {
|
|
|
55
62
|
val index = presentedSheetStack.indexOf(sheetView)
|
|
56
63
|
if (index <= 0) return
|
|
57
64
|
|
|
65
|
+
val rootContainer = sheetView.rootContainerView
|
|
58
66
|
val parentSheet = presentedSheetStack[index - 1]
|
|
67
|
+
.takeIf { it.rootContainerView == rootContainer } ?: return
|
|
59
68
|
|
|
60
69
|
// Post to ensure layout is complete before reading position
|
|
61
70
|
sheetView.viewController.post {
|
|
@@ -71,15 +80,18 @@ object TrueSheetStackManager {
|
|
|
71
80
|
}
|
|
72
81
|
|
|
73
82
|
/**
|
|
74
|
-
* Returns all sheets presented on top of the given sheet (children/descendants)
|
|
75
|
-
* Returns them in reverse order (top-most first) for proper dismissal.
|
|
83
|
+
* Returns all sheets presented on top of the given sheet (children/descendants)
|
|
84
|
+
* that are in the same container. Returns them in reverse order (top-most first) for proper dismissal.
|
|
76
85
|
*/
|
|
77
86
|
@JvmStatic
|
|
78
87
|
fun getSheetsAbove(sheetView: TrueSheetView): List<TrueSheetView> {
|
|
79
88
|
synchronized(presentedSheetStack) {
|
|
80
89
|
val index = presentedSheetStack.indexOf(sheetView)
|
|
81
90
|
if (index < 0 || index >= presentedSheetStack.size - 1) return emptyList()
|
|
82
|
-
|
|
91
|
+
val rootContainer = sheetView.rootContainerView
|
|
92
|
+
return presentedSheetStack.subList(index + 1, presentedSheetStack.size)
|
|
93
|
+
.filter { it.rootContainerView == rootContainer }
|
|
94
|
+
.reversed()
|
|
83
95
|
}
|
|
84
96
|
}
|
|
85
97
|
|
|
@@ -99,23 +111,26 @@ object TrueSheetStackManager {
|
|
|
99
111
|
|
|
100
112
|
/**
|
|
101
113
|
* Gets the parent sheet of the given sheet, if any.
|
|
114
|
+
* Only returns a parent if it's in the same container.
|
|
102
115
|
*/
|
|
103
116
|
@JvmStatic
|
|
104
117
|
fun getParentSheet(sheetView: TrueSheetView): TrueSheetView? {
|
|
105
118
|
synchronized(presentedSheetStack) {
|
|
106
119
|
val index = presentedSheetStack.indexOf(sheetView)
|
|
107
120
|
if (index <= 0) return null
|
|
108
|
-
|
|
121
|
+
val rootContainer = sheetView.rootContainerView
|
|
122
|
+
return presentedSheetStack[index - 1].takeIf { it.rootContainerView == rootContainer }
|
|
109
123
|
}
|
|
110
124
|
}
|
|
111
125
|
|
|
112
126
|
/**
|
|
113
|
-
* Returns true if the given sheet is the topmost presented sheet.
|
|
127
|
+
* Returns true if the given sheet is the topmost presented sheet in its container.
|
|
114
128
|
*/
|
|
115
129
|
@JvmStatic
|
|
116
130
|
fun isTopmostSheet(sheetView: TrueSheetView): Boolean {
|
|
117
131
|
synchronized(presentedSheetStack) {
|
|
118
|
-
|
|
132
|
+
val rootContainer = sheetView.rootContainerView
|
|
133
|
+
return presentedSheetStack.lastOrNull { it.rootContainerView == rootContainer } == sheetView
|
|
119
134
|
}
|
|
120
135
|
}
|
|
121
136
|
}
|
|
@@ -22,6 +22,12 @@ class JSI_EXPORT TrueSheetViewShadowNode final
|
|
|
22
22
|
using ConcreteViewShadowNode::ConcreteViewShadowNode;
|
|
23
23
|
|
|
24
24
|
public:
|
|
25
|
+
static ShadowNodeTraits BaseTraits() {
|
|
26
|
+
auto traits = ConcreteViewShadowNode::BaseTraits();
|
|
27
|
+
traits.set(ShadowNodeTraits::Trait::RootNodeKind);
|
|
28
|
+
return traits;
|
|
29
|
+
}
|
|
30
|
+
|
|
25
31
|
void adjustLayoutWithState();
|
|
26
32
|
};
|
|
27
33
|
|
package/package.json
CHANGED
|
@@ -1,181 +0,0 @@
|
|
|
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
|
-
}
|