@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.
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +2 -2
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +31 -28
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +358 -271
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +0 -5
- package/android/src/main/java/com/lodev09/truesheet/core/RNScreensFragmentObserver.kt +7 -8
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +158 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +55 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +18 -12
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +91 -2
- package/android/src/main/java/com/lodev09/truesheet/core/{TrueSheetDialogObserver.kt → TrueSheetStackManager.kt} +7 -5
- package/ios/TrueSheetViewController.h +2 -3
- package/ios/TrueSheetViewController.mm +11 -4
- package/ios/core/TrueSheetDetentCalculator.h +2 -3
- package/ios/core/TrueSheetDetentCalculator.mm +7 -9
- package/lib/module/TrueSheet.js +0 -2
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +0 -1
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +0 -8
- package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +0 -1
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/navigation/types.d.ts +1 -1
- package/lib/typescript/src/navigation/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/TrueSheet.tsx +0 -2
- package/src/TrueSheet.types.ts +0 -9
- package/src/fabric/TrueSheetViewNativeComponent.ts +0 -1
- package/src/navigation/types.ts +0 -1
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetAnimator.kt +0 -144
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDialogFragment.kt +0 -317
- package/android/src/main/res/anim/fast_fade_out.xml +0 -6
- package/android/src/main/res/values/styles.xml +0 -21
|
@@ -183,11 +183,6 @@ class TrueSheetViewManager :
|
|
|
183
183
|
// iOS-specific prop - no-op on Android
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
@ReactProp(name = "edgeToEdgeFullScreen", defaultBoolean = false)
|
|
187
|
-
override fun setEdgeToEdgeFullScreen(view: TrueSheetView, edgeToEdgeFullScreen: Boolean) {
|
|
188
|
-
view.setEdgeToEdgeFullScreen(edgeToEdgeFullScreen)
|
|
189
|
-
}
|
|
190
|
-
|
|
191
186
|
@ReactProp(name = "insetAdjustment")
|
|
192
187
|
override fun setInsetAdjustment(view: TrueSheetView, insetAdjustment: String?) {
|
|
193
188
|
view.setInsetAdjustment(insetAdjustment ?: "automatic")
|
|
@@ -66,8 +66,11 @@ class RNScreensFragmentObserver(
|
|
|
66
66
|
// Ignore if app is going to background (fragments stop with activity)
|
|
67
67
|
if (!isActivityInForeground) return
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
// Only trigger when fragment is being removed (not just stopped for navigation)
|
|
70
|
+
if (activeModalFragments.contains(f) && f.isRemoving) {
|
|
71
|
+
activeModalFragments.remove(f)
|
|
72
|
+
|
|
73
|
+
if (activeModalFragments.isEmpty()) {
|
|
71
74
|
onModalWillDismiss()
|
|
72
75
|
}
|
|
73
76
|
}
|
|
@@ -76,12 +79,8 @@ class RNScreensFragmentObserver(
|
|
|
76
79
|
override fun onFragmentDestroyed(fm: FragmentManager, f: Fragment) {
|
|
77
80
|
super.onFragmentDestroyed(fm, f)
|
|
78
81
|
|
|
79
|
-
if (activeModalFragments.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (activeModalFragments.isEmpty()) {
|
|
83
|
-
onModalDidDismiss()
|
|
84
|
-
}
|
|
82
|
+
if (activeModalFragments.isEmpty()) {
|
|
83
|
+
onModalDidDismiss()
|
|
85
84
|
}
|
|
86
85
|
}
|
|
87
86
|
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
package com.lodev09.truesheet.core
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.graphics.Color
|
|
5
|
+
import android.graphics.Outline
|
|
6
|
+
import android.graphics.drawable.ShapeDrawable
|
|
7
|
+
import android.graphics.drawable.shapes.RoundRectShape
|
|
8
|
+
import android.util.TypedValue
|
|
9
|
+
import android.view.Gravity
|
|
10
|
+
import android.view.View
|
|
11
|
+
import android.view.ViewOutlineProvider
|
|
12
|
+
import android.widget.FrameLayout
|
|
13
|
+
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
14
|
+
import com.facebook.react.uimanager.PixelUtil.dpToPx
|
|
15
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
16
|
+
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
17
|
+
|
|
18
|
+
interface TrueSheetBottomSheetViewDelegate {
|
|
19
|
+
val isTopmostSheet: Boolean
|
|
20
|
+
val sheetCornerRadius: Float
|
|
21
|
+
val sheetBackgroundColor: Int?
|
|
22
|
+
val grabber: Boolean
|
|
23
|
+
val grabberOptions: GrabberOptions?
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The bottom sheet view that holds the content.
|
|
28
|
+
* This view has BottomSheetBehavior attached via CoordinatorLayout.LayoutParams.
|
|
29
|
+
*
|
|
30
|
+
* Touch dispatching to React Native is handled by TrueSheetViewController,
|
|
31
|
+
* which is the actual RootView containing the React content.
|
|
32
|
+
*/
|
|
33
|
+
@SuppressLint("ViewConstructor")
|
|
34
|
+
class TrueSheetBottomSheetView(private val reactContext: ThemedReactContext) : FrameLayout(reactContext) {
|
|
35
|
+
|
|
36
|
+
companion object {
|
|
37
|
+
private const val GRABBER_TAG = "TrueSheetGrabber"
|
|
38
|
+
private const val DEFAULT_CORNER_RADIUS = 16f // dp
|
|
39
|
+
private const val DEFAULT_MAX_WIDTH = 640 // dp
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// =============================================================================
|
|
43
|
+
// MARK: - Properties
|
|
44
|
+
// =============================================================================
|
|
45
|
+
|
|
46
|
+
var delegate: TrueSheetBottomSheetViewDelegate? = null
|
|
47
|
+
|
|
48
|
+
// Behavior reference (set after adding to CoordinatorLayout)
|
|
49
|
+
val behavior: BottomSheetBehavior<TrueSheetBottomSheetView>?
|
|
50
|
+
get() = (layoutParams as? CoordinatorLayout.LayoutParams)
|
|
51
|
+
?.behavior as? BottomSheetBehavior<TrueSheetBottomSheetView>
|
|
52
|
+
|
|
53
|
+
// =============================================================================
|
|
54
|
+
// MARK: - Initialization
|
|
55
|
+
// =============================================================================
|
|
56
|
+
|
|
57
|
+
init {
|
|
58
|
+
// Allow content to extend beyond bounds (for footer positioning)
|
|
59
|
+
clipChildren = false
|
|
60
|
+
clipToPadding = false
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
override fun setTranslationY(translationY: Float) {
|
|
64
|
+
// Skip resetting translation to 0 for parent sheets (non-topmost)
|
|
65
|
+
// This prevents keyboard inset animations from resetting parent sheet translation
|
|
66
|
+
if (delegate?.isTopmostSheet == false && translationY == 0f && this.translationY != 0f) {
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
super.setTranslationY(translationY)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// =============================================================================
|
|
73
|
+
// MARK: - Layout
|
|
74
|
+
// =============================================================================
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Creates layout params with BottomSheetBehavior attached.
|
|
78
|
+
*/
|
|
79
|
+
fun createLayoutParams(): CoordinatorLayout.LayoutParams {
|
|
80
|
+
val behavior = BottomSheetBehavior<TrueSheetBottomSheetView>().apply {
|
|
81
|
+
isHideable = true
|
|
82
|
+
maxWidth = DEFAULT_MAX_WIDTH.dpToPx().toInt()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return CoordinatorLayout.LayoutParams(
|
|
86
|
+
CoordinatorLayout.LayoutParams.MATCH_PARENT,
|
|
87
|
+
CoordinatorLayout.LayoutParams.MATCH_PARENT
|
|
88
|
+
).apply {
|
|
89
|
+
this.behavior = behavior
|
|
90
|
+
this.gravity = Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// =============================================================================
|
|
95
|
+
// MARK: - Background & Styling
|
|
96
|
+
// =============================================================================
|
|
97
|
+
|
|
98
|
+
fun setupBackground() {
|
|
99
|
+
val radius = delegate?.sheetCornerRadius ?: DEFAULT_CORNER_RADIUS.dpToPx()
|
|
100
|
+
val effectiveRadius = if (radius < 0) DEFAULT_CORNER_RADIUS.dpToPx() else radius
|
|
101
|
+
|
|
102
|
+
val outerRadii = floatArrayOf(
|
|
103
|
+
effectiveRadius,
|
|
104
|
+
effectiveRadius, // top-left
|
|
105
|
+
effectiveRadius,
|
|
106
|
+
effectiveRadius, // top-right
|
|
107
|
+
0f,
|
|
108
|
+
0f, // bottom-right
|
|
109
|
+
0f,
|
|
110
|
+
0f // bottom-left
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
val color = delegate?.sheetBackgroundColor ?: getDefaultBackgroundColor()
|
|
114
|
+
|
|
115
|
+
background = ShapeDrawable(RoundRectShape(outerRadii, null, null)).apply {
|
|
116
|
+
paint.color = color
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
outlineProvider = object : ViewOutlineProvider() {
|
|
120
|
+
override fun getOutline(view: View, outline: Outline) {
|
|
121
|
+
outline.setRoundRect(0, 0, view.width, view.height, effectiveRadius)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
clipToOutline = true
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private fun getDefaultBackgroundColor(): Int {
|
|
128
|
+
val typedValue = TypedValue()
|
|
129
|
+
return if (reactContext.theme.resolveAttribute(
|
|
130
|
+
com.google.android.material.R.attr.colorSurfaceContainerLow,
|
|
131
|
+
typedValue,
|
|
132
|
+
true
|
|
133
|
+
)
|
|
134
|
+
) {
|
|
135
|
+
typedValue.data
|
|
136
|
+
} else {
|
|
137
|
+
Color.WHITE
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// =============================================================================
|
|
142
|
+
// MARK: - Grabber
|
|
143
|
+
// =============================================================================
|
|
144
|
+
|
|
145
|
+
fun setupGrabber() {
|
|
146
|
+
findViewWithTag<View>(GRABBER_TAG)?.let { removeView(it) }
|
|
147
|
+
|
|
148
|
+
val isEnabled = delegate?.grabber ?: true
|
|
149
|
+
val isDraggable = behavior?.isDraggable ?: true
|
|
150
|
+
if (!isEnabled || !isDraggable) return
|
|
151
|
+
|
|
152
|
+
val grabberView = TrueSheetGrabberView(reactContext, delegate?.grabberOptions).apply {
|
|
153
|
+
tag = GRABBER_TAG
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
addView(grabberView, 0)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
package com.lodev09.truesheet.core
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.view.View
|
|
6
|
+
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
7
|
+
import com.facebook.react.uimanager.PointerEvents
|
|
8
|
+
import com.facebook.react.uimanager.ReactPointerEventsView
|
|
9
|
+
|
|
10
|
+
interface TrueSheetCoordinatorLayoutDelegate {
|
|
11
|
+
fun coordinatorLayoutDidLayout(changed: Boolean)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Custom CoordinatorLayout that hosts the bottom sheet and dim view.
|
|
16
|
+
* Implements ReactPointerEventsView to allow touch events to pass through
|
|
17
|
+
* to underlying React Native views when appropriate.
|
|
18
|
+
*/
|
|
19
|
+
@SuppressLint("ViewConstructor")
|
|
20
|
+
class TrueSheetCoordinatorLayout(context: Context) :
|
|
21
|
+
CoordinatorLayout(context),
|
|
22
|
+
ReactPointerEventsView {
|
|
23
|
+
|
|
24
|
+
var delegate: TrueSheetCoordinatorLayoutDelegate? = null
|
|
25
|
+
|
|
26
|
+
init {
|
|
27
|
+
// Fill the entire screen
|
|
28
|
+
layoutParams = LayoutParams(
|
|
29
|
+
LayoutParams.MATCH_PARENT,
|
|
30
|
+
LayoutParams.MATCH_PARENT
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
// Ensure we don't clip the sheet during animations
|
|
34
|
+
clipChildren = false
|
|
35
|
+
clipToPadding = false
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
override fun onLayout(
|
|
39
|
+
changed: Boolean,
|
|
40
|
+
l: Int,
|
|
41
|
+
t: Int,
|
|
42
|
+
r: Int,
|
|
43
|
+
b: Int
|
|
44
|
+
) {
|
|
45
|
+
super.onLayout(changed, l, t, r, b)
|
|
46
|
+
delegate?.coordinatorLayoutDidLayout(changed)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Allow pointer events to pass through to underlying views.
|
|
51
|
+
* The DimView and BottomSheetView handle their own touch interception.
|
|
52
|
+
*/
|
|
53
|
+
override val pointerEvents: PointerEvents
|
|
54
|
+
get() = PointerEvents.BOX_NONE
|
|
55
|
+
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
package com.lodev09.truesheet.core
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.uimanager.PixelUtil.pxToDp
|
|
4
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
5
|
+
import com.facebook.react.util.RNLog
|
|
4
6
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Provides screen dimensions and content measurements for detent calculations.
|
|
8
10
|
*/
|
|
9
|
-
interface
|
|
11
|
+
interface TrueSheetDetentCalculatorDelegate {
|
|
10
12
|
val screenHeight: Int
|
|
11
13
|
val realScreenHeight: Int
|
|
12
14
|
val detents: MutableList<Double>
|
|
@@ -19,18 +21,19 @@ interface TrueSheetDetentMeasurements {
|
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* Handles all detent-related calculations for the bottom sheet.
|
|
22
|
-
* Takes a measurements provider to always read current values.
|
|
23
24
|
*/
|
|
24
|
-
class TrueSheetDetentCalculator(private val
|
|
25
|
+
class TrueSheetDetentCalculator(private val reactContext: ThemedReactContext) {
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
private val
|
|
29
|
-
private val
|
|
30
|
-
private val
|
|
31
|
-
private val
|
|
32
|
-
private val
|
|
33
|
-
private val
|
|
27
|
+
var delegate: TrueSheetDetentCalculatorDelegate? = null
|
|
28
|
+
|
|
29
|
+
private val screenHeight: Int get() = delegate?.screenHeight ?: 0
|
|
30
|
+
private val realScreenHeight: Int get() = delegate?.realScreenHeight ?: 0
|
|
31
|
+
private val detents: List<Double> get() = delegate?.detents ?: emptyList()
|
|
32
|
+
private val contentHeight: Int get() = delegate?.contentHeight ?: 0
|
|
33
|
+
private val headerHeight: Int get() = delegate?.headerHeight ?: 0
|
|
34
|
+
private val contentBottomInset: Int get() = delegate?.contentBottomInset ?: 0
|
|
35
|
+
private val maxSheetHeight: Int? get() = delegate?.maxSheetHeight
|
|
36
|
+
private val keyboardInset: Int get() = delegate?.keyboardInset ?: 0
|
|
34
37
|
|
|
35
38
|
/**
|
|
36
39
|
* Calculate the height in pixels for a given detent value.
|
|
@@ -55,7 +58,10 @@ class TrueSheetDetentCalculator(private val measurements: TrueSheetDetentMeasure
|
|
|
55
58
|
* Get the expected sheet top position for a detent index.
|
|
56
59
|
*/
|
|
57
60
|
fun getSheetTopForDetentIndex(index: Int): Int {
|
|
58
|
-
if (index < 0 || index >= detents.size)
|
|
61
|
+
if (index < 0 || index >= detents.size) {
|
|
62
|
+
RNLog.w(reactContext, "TrueSheet: Detent index ($index) is out of bounds (0..${detents.size - 1})")
|
|
63
|
+
return realScreenHeight
|
|
64
|
+
}
|
|
59
65
|
return realScreenHeight - getDetentHeight(detents[index])
|
|
60
66
|
}
|
|
61
67
|
|
|
@@ -3,21 +3,51 @@ package com.lodev09.truesheet.core
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.graphics.Color
|
|
5
5
|
import android.graphics.Outline
|
|
6
|
+
import android.view.MotionEvent
|
|
6
7
|
import android.view.View
|
|
7
8
|
import android.view.ViewGroup
|
|
8
9
|
import android.view.ViewOutlineProvider
|
|
10
|
+
import com.facebook.react.uimanager.PointerEvents
|
|
11
|
+
import com.facebook.react.uimanager.ReactPointerEventsView
|
|
9
12
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
10
13
|
import com.lodev09.truesheet.utils.ScreenUtils
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Delegate for handling dim view interactions.
|
|
17
|
+
*/
|
|
18
|
+
interface TrueSheetDimViewDelegate {
|
|
19
|
+
fun dimViewDidTap()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Dim view that sits behind the bottom sheet in the CoordinatorLayout.
|
|
24
|
+
*
|
|
25
|
+
* Key behaviors:
|
|
26
|
+
* - When alpha > 0 (dimmed): blocks touches and calls delegate on tap
|
|
27
|
+
* - When alpha == 0 (not dimmed): passes touches through to views below
|
|
28
|
+
*
|
|
29
|
+
* This implements the "dimmedDetentIndex" equivalent functionality:
|
|
30
|
+
* the view only becomes interactive when the sheet is at or above the dimmed detent.
|
|
31
|
+
*/
|
|
32
|
+
@SuppressLint("ViewConstructor", "ClickableViewAccessibility")
|
|
33
|
+
class TrueSheetDimView(private val reactContext: ThemedReactContext) :
|
|
34
|
+
View(reactContext),
|
|
35
|
+
ReactPointerEventsView {
|
|
14
36
|
|
|
15
37
|
companion object {
|
|
16
38
|
private const val MAX_ALPHA = 0.5f
|
|
17
39
|
}
|
|
18
40
|
|
|
41
|
+
var delegate: TrueSheetDimViewDelegate? = null
|
|
42
|
+
|
|
19
43
|
private var targetView: ViewGroup? = null
|
|
20
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Whether this view should block gestures (when dimmed).
|
|
47
|
+
*/
|
|
48
|
+
private val blockGestures: Boolean
|
|
49
|
+
get() = alpha > 0f
|
|
50
|
+
|
|
21
51
|
init {
|
|
22
52
|
layoutParams = ViewGroup.LayoutParams(
|
|
23
53
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
@@ -25,8 +55,22 @@ class TrueSheetDimView(private val reactContext: ThemedReactContext) : View(reac
|
|
|
25
55
|
)
|
|
26
56
|
setBackgroundColor(Color.BLACK)
|
|
27
57
|
alpha = 0f
|
|
58
|
+
|
|
59
|
+
// Handle taps on the dim view
|
|
60
|
+
setOnClickListener {
|
|
61
|
+
delegate?.dimViewDidTap()
|
|
62
|
+
}
|
|
28
63
|
}
|
|
29
64
|
|
|
65
|
+
// =============================================================================
|
|
66
|
+
// MARK: - Attachment
|
|
67
|
+
// =============================================================================
|
|
68
|
+
|
|
69
|
+
/**
|
|
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
|
+
* For stacked sheets, pass the parent sheet's bottom sheet view with corner radius.
|
|
73
|
+
*/
|
|
30
74
|
fun attach(view: ViewGroup? = null, cornerRadius: Float = 0f) {
|
|
31
75
|
if (parent != null) return
|
|
32
76
|
targetView = view ?: reactContext.currentActivity?.window?.decorView as? ViewGroup
|
|
@@ -38,16 +82,34 @@ class TrueSheetDimView(private val reactContext: ThemedReactContext) : View(reac
|
|
|
38
82
|
}
|
|
39
83
|
}
|
|
40
84
|
clipToOutline = true
|
|
85
|
+
} else {
|
|
86
|
+
outlineProvider = null
|
|
87
|
+
clipToOutline = false
|
|
41
88
|
}
|
|
42
89
|
|
|
43
90
|
targetView?.addView(this)
|
|
44
91
|
}
|
|
45
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Attaches this dim view to a CoordinatorLayout at index 0 (behind the sheet).
|
|
95
|
+
*/
|
|
96
|
+
fun attachToCoordinator(coordinator: TrueSheetCoordinatorLayout) {
|
|
97
|
+
if (parent != null) return
|
|
98
|
+
targetView = coordinator
|
|
99
|
+
outlineProvider = null
|
|
100
|
+
clipToOutline = false
|
|
101
|
+
coordinator.addView(this, 0)
|
|
102
|
+
}
|
|
103
|
+
|
|
46
104
|
fun detach() {
|
|
47
105
|
targetView?.removeView(this)
|
|
48
106
|
targetView = null
|
|
49
107
|
}
|
|
50
108
|
|
|
109
|
+
// =============================================================================
|
|
110
|
+
// MARK: - Alpha Calculation
|
|
111
|
+
// =============================================================================
|
|
112
|
+
|
|
51
113
|
fun calculateAlpha(sheetTop: Int, dimmedDetentIndex: Int, getSheetTopForDetentIndex: (Int) -> Int): Float {
|
|
52
114
|
val realHeight = ScreenUtils.getRealScreenHeight(reactContext)
|
|
53
115
|
val dimmedDetentTop = getSheetTopForDetentIndex(dimmedDetentIndex)
|
|
@@ -68,4 +130,31 @@ class TrueSheetDimView(private val reactContext: ThemedReactContext) : View(reac
|
|
|
68
130
|
fun interpolateAlpha(sheetTop: Int, dimmedDetentIndex: Int, getSheetTopForDetentIndex: (Int) -> Int) {
|
|
69
131
|
alpha = calculateAlpha(sheetTop, dimmedDetentIndex, getSheetTopForDetentIndex)
|
|
70
132
|
}
|
|
133
|
+
|
|
134
|
+
// =============================================================================
|
|
135
|
+
// MARK: - Touch Handling
|
|
136
|
+
// =============================================================================
|
|
137
|
+
|
|
138
|
+
override fun onTouchEvent(event: MotionEvent): Boolean {
|
|
139
|
+
if (blockGestures) {
|
|
140
|
+
// When dimmed, consume touch and trigger click on ACTION_UP
|
|
141
|
+
if (event.action == MotionEvent.ACTION_UP) {
|
|
142
|
+
performClick()
|
|
143
|
+
}
|
|
144
|
+
return true
|
|
145
|
+
}
|
|
146
|
+
// When not dimmed, let touches pass through
|
|
147
|
+
return false
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// =============================================================================
|
|
151
|
+
// MARK: - ReactPointerEventsView
|
|
152
|
+
// =============================================================================
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* When dimmed (alpha > 0), intercept touches (AUTO).
|
|
156
|
+
* When not dimmed (alpha == 0), pass through (NONE).
|
|
157
|
+
*/
|
|
158
|
+
override val pointerEvents: PointerEvents
|
|
159
|
+
get() = if (blockGestures) PointerEvents.AUTO else PointerEvents.NONE
|
|
71
160
|
}
|
|
@@ -6,7 +6,7 @@ import com.lodev09.truesheet.TrueSheetView
|
|
|
6
6
|
* Manages TrueSheet stacking behavior.
|
|
7
7
|
* Tracks presented sheets and handles visibility when sheets stack on top of each other.
|
|
8
8
|
*/
|
|
9
|
-
object
|
|
9
|
+
object TrueSheetStackManager {
|
|
10
10
|
|
|
11
11
|
private val presentedSheetStack = mutableListOf<TrueSheetView>()
|
|
12
12
|
|
|
@@ -18,9 +18,9 @@ object TrueSheetDialogObserver {
|
|
|
18
18
|
fun onSheetWillPresent(sheetView: TrueSheetView, detentIndex: Int): TrueSheetView? {
|
|
19
19
|
synchronized(presentedSheetStack) {
|
|
20
20
|
val parentSheet = presentedSheetStack.lastOrNull()
|
|
21
|
-
?.takeIf { it.viewController.isPresented && it.viewController.
|
|
21
|
+
?.takeIf { it.viewController.isPresented && it.viewController.isSheetVisible }
|
|
22
22
|
|
|
23
|
-
val childSheetTop = sheetView.viewController.
|
|
23
|
+
val childSheetTop = sheetView.viewController.detentCalculator.getSheetTopForDetentIndex(detentIndex)
|
|
24
24
|
parentSheet?.updateTranslationForChild(childSheetTop)
|
|
25
25
|
|
|
26
26
|
if (!presentedSheetStack.contains(sheetView)) {
|
|
@@ -59,8 +59,10 @@ object TrueSheetDialogObserver {
|
|
|
59
59
|
|
|
60
60
|
// Post to ensure layout is complete before reading position
|
|
61
61
|
sheetView.viewController.post {
|
|
62
|
-
val childMinSheetTop = sheetView.viewController.
|
|
63
|
-
val childCurrentSheetTop = sheetView.viewController.
|
|
62
|
+
val childMinSheetTop = sheetView.viewController.detentCalculator.getSheetTopForDetentIndex(0)
|
|
63
|
+
val childCurrentSheetTop = sheetView.viewController.detentCalculator.getSheetTopForDetentIndex(
|
|
64
|
+
sheetView.viewController.currentDetentIndex
|
|
65
|
+
)
|
|
64
66
|
// Cap to minimum detent position
|
|
65
67
|
val childSheetTop = maxOf(childMinSheetTop, childCurrentSheetTop)
|
|
66
68
|
parentSheet.updateTranslationForChild(childSheetTop)
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
//
|
|
8
8
|
|
|
9
9
|
#import <UIKit/UIKit.h>
|
|
10
|
+
#import "core/TrueSheetDetentCalculator.h"
|
|
10
11
|
|
|
11
12
|
#if __has_include(<RNScreens/RNSDismissibleModalProtocol.h>)
|
|
12
13
|
#import <RNScreens/RNSDismissibleModalProtocol.h>
|
|
@@ -40,10 +41,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
40
41
|
|
|
41
42
|
@end
|
|
42
43
|
|
|
43
|
-
@protocol TrueSheetDetentMeasurements;
|
|
44
|
-
|
|
45
44
|
@interface TrueSheetViewController : UIViewController <UISheetPresentationControllerDelegate,
|
|
46
|
-
|
|
45
|
+
TrueSheetDetentCalculatorDelegate
|
|
47
46
|
#if RNS_DISMISSIBLE_MODAL_PROTOCOL_AVAILABLE
|
|
48
47
|
,
|
|
49
48
|
RNSDismissibleModalProtocol
|
|
@@ -65,7 +65,8 @@
|
|
|
65
65
|
|
|
66
66
|
_blurInteraction = YES;
|
|
67
67
|
_insetAdjustment = @"automatic";
|
|
68
|
-
_detentCalculator = [[TrueSheetDetentCalculator alloc]
|
|
68
|
+
_detentCalculator = [[TrueSheetDetentCalculator alloc] init];
|
|
69
|
+
_detentCalculator.delegate = self;
|
|
69
70
|
}
|
|
70
71
|
return self;
|
|
71
72
|
}
|
|
@@ -503,8 +504,10 @@
|
|
|
503
504
|
|
|
504
505
|
- (void)setupSheetDetents {
|
|
505
506
|
UISheetPresentationController *sheet = self.sheetPresentationController;
|
|
506
|
-
if (!sheet)
|
|
507
|
+
if (!sheet) {
|
|
508
|
+
RCTLogError(@"TrueSheet: sheetPresentationController is nil in setupSheetDetents");
|
|
507
509
|
return;
|
|
510
|
+
}
|
|
508
511
|
|
|
509
512
|
NSMutableArray<UISheetPresentationControllerDetent *> *detents = [NSMutableArray array];
|
|
510
513
|
[_detentCalculator clearResolvedPositions];
|
|
@@ -591,8 +594,10 @@
|
|
|
591
594
|
|
|
592
595
|
- (UISheetPresentationControllerDetentIdentifier)detentIdentifierForIndex:(NSInteger)index {
|
|
593
596
|
UISheetPresentationController *sheet = self.sheetPresentationController;
|
|
594
|
-
if (!sheet)
|
|
597
|
+
if (!sheet) {
|
|
598
|
+
RCTLogError(@"TrueSheet: sheetPresentationController is nil in detentIdentifierForIndex");
|
|
595
599
|
return UISheetPresentationControllerDetentIdentifierMedium;
|
|
600
|
+
}
|
|
596
601
|
|
|
597
602
|
UISheetPresentationControllerDetentIdentifier identifier = UISheetPresentationControllerDetentIdentifierMedium;
|
|
598
603
|
if (index >= 0 && index < (NSInteger)sheet.detents.count) {
|
|
@@ -611,8 +616,10 @@
|
|
|
611
616
|
|
|
612
617
|
- (void)applyActiveDetent {
|
|
613
618
|
UISheetPresentationController *sheet = self.sheetPresentationController;
|
|
614
|
-
if (!sheet)
|
|
619
|
+
if (!sheet) {
|
|
620
|
+
RCTLogError(@"TrueSheet: sheetPresentationController is nil in applyActiveDetent");
|
|
615
621
|
return;
|
|
622
|
+
}
|
|
616
623
|
|
|
617
624
|
NSInteger detentCount = _detents.count;
|
|
618
625
|
if (detentCount == 0)
|
|
@@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
14
14
|
Protocol that provides dynamic measurements for detent calculations.
|
|
15
15
|
Implemented by TrueSheetViewController to supply real-time values.
|
|
16
16
|
*/
|
|
17
|
-
@protocol
|
|
17
|
+
@protocol TrueSheetDetentCalculatorDelegate <NSObject>
|
|
18
18
|
|
|
19
19
|
@property (nonatomic, readonly) CGFloat screenHeight;
|
|
20
20
|
@property (nonatomic, readonly) CGFloat currentPosition;
|
|
@@ -26,11 +26,10 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
Encapsulates all detent-related calculations for the sheet.
|
|
29
|
-
Uses the TrueSheetDetentMeasurements protocol to read dynamic values.
|
|
30
29
|
*/
|
|
31
30
|
@interface TrueSheetDetentCalculator : NSObject
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
@property (nonatomic, weak, nullable) id<TrueSheetDetentCalculatorDelegate> delegate;
|
|
34
33
|
|
|
35
34
|
/**
|
|
36
35
|
Returns the detent value (0-1 fraction) for a given index.
|
|
@@ -9,13 +9,11 @@
|
|
|
9
9
|
#import "TrueSheetDetentCalculator.h"
|
|
10
10
|
|
|
11
11
|
@implementation TrueSheetDetentCalculator {
|
|
12
|
-
__weak id<TrueSheetDetentMeasurements> _measurements;
|
|
13
12
|
NSMutableArray<NSNumber *> *_resolvedDetentPositions;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
- (instancetype)
|
|
15
|
+
- (instancetype)init {
|
|
17
16
|
if (self = [super init]) {
|
|
18
|
-
_measurements = measurements;
|
|
19
17
|
_resolvedDetentPositions = [NSMutableArray array];
|
|
20
18
|
}
|
|
21
19
|
return self;
|
|
@@ -24,12 +22,12 @@
|
|
|
24
22
|
#pragma mark - Public Methods
|
|
25
23
|
|
|
26
24
|
- (CGFloat)detentValueForIndex:(NSInteger)index {
|
|
27
|
-
NSArray<NSNumber *> *detents =
|
|
25
|
+
NSArray<NSNumber *> *detents = self.delegate.detents;
|
|
28
26
|
if (index >= 0 && index < (NSInteger)detents.count) {
|
|
29
27
|
CGFloat value = [detents[index] doubleValue];
|
|
30
28
|
if (value == -1) {
|
|
31
|
-
CGFloat autoHeight = [
|
|
32
|
-
return autoHeight /
|
|
29
|
+
CGFloat autoHeight = [self.delegate.contentHeight floatValue] + [self.delegate.headerHeight floatValue];
|
|
30
|
+
return autoHeight / self.delegate.screenHeight;
|
|
33
31
|
}
|
|
34
32
|
return value;
|
|
35
33
|
}
|
|
@@ -46,7 +44,7 @@
|
|
|
46
44
|
return storedPos;
|
|
47
45
|
}
|
|
48
46
|
|
|
49
|
-
CGFloat screenHeight =
|
|
47
|
+
CGFloat screenHeight = self.delegate.screenHeight;
|
|
50
48
|
CGFloat detentValue = [self detentValueForIndex:index];
|
|
51
49
|
CGFloat basePosition = screenHeight - (detentValue * screenHeight);
|
|
52
50
|
|
|
@@ -66,7 +64,7 @@
|
|
|
66
64
|
|
|
67
65
|
- (void)storeResolvedPositionForIndex:(NSInteger)index {
|
|
68
66
|
if (index >= 0 && index < (NSInteger)_resolvedDetentPositions.count) {
|
|
69
|
-
_resolvedDetentPositions[index] = @(
|
|
67
|
+
_resolvedDetentPositions[index] = @(self.delegate.currentPosition);
|
|
70
68
|
}
|
|
71
69
|
}
|
|
72
70
|
|
|
@@ -78,7 +76,7 @@
|
|
|
78
76
|
return NO;
|
|
79
77
|
}
|
|
80
78
|
|
|
81
|
-
CGFloat screenHeight =
|
|
79
|
+
CGFloat screenHeight = self.delegate.screenHeight;
|
|
82
80
|
CGFloat firstPos = [self estimatedPositionForIndex:0];
|
|
83
81
|
|
|
84
82
|
// Above first detent - interpolating toward closed
|
package/lib/module/TrueSheet.js
CHANGED
|
@@ -264,7 +264,6 @@ export class TrueSheet extends PureComponent {
|
|
|
264
264
|
blurOptions,
|
|
265
265
|
cornerRadius,
|
|
266
266
|
maxHeight,
|
|
267
|
-
edgeToEdgeFullScreen,
|
|
268
267
|
scrollable = false,
|
|
269
268
|
pageSizing = true,
|
|
270
269
|
children,
|
|
@@ -312,7 +311,6 @@ export class TrueSheet extends PureComponent {
|
|
|
312
311
|
dismissible: dismissible,
|
|
313
312
|
draggable: draggable,
|
|
314
313
|
maxHeight: maxHeight,
|
|
315
|
-
edgeToEdgeFullScreen: edgeToEdgeFullScreen,
|
|
316
314
|
scrollable: scrollable,
|
|
317
315
|
pageSizing: pageSizing,
|
|
318
316
|
insetAdjustment: insetAdjustment,
|