@lodev09/react-native-true-sheet 3.0.0-beta.8 → 3.0.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/README.md +13 -6
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +29 -33
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +3 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +48 -43
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +387 -88
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +22 -4
- package/android/src/main/java/com/lodev09/truesheet/core/RNScreensFragmentObserver.kt +0 -5
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDialogObserver.kt +67 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetGrabberView.kt +44 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetDragEvents.kt +71 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetFocusEvents.kt +65 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +94 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetStateEvents.kt +56 -0
- package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +37 -33
- package/android/src/main/res/anim/true_sheet_slide_in.xml +13 -0
- package/android/src/main/res/anim/true_sheet_slide_out.xml +13 -0
- package/android/src/main/res/values/styles.xml +13 -1
- package/ios/TrueSheetContainerView.mm +4 -0
- package/ios/TrueSheetContentView.h +2 -1
- package/ios/TrueSheetContentView.mm +91 -11
- package/ios/TrueSheetView.mm +65 -41
- package/ios/TrueSheetViewController.h +21 -10
- package/ios/TrueSheetViewController.mm +330 -165
- package/ios/core/TrueSheetBlurView.h +24 -0
- package/ios/{utils/ConversionUtil.mm → core/TrueSheetBlurView.mm} +65 -3
- package/ios/events/TrueSheetDragEvents.h +39 -0
- package/ios/events/TrueSheetDragEvents.mm +62 -0
- package/ios/events/{OnPositionChangeEvent.h → TrueSheetFocusEvents.h} +8 -5
- package/ios/events/TrueSheetFocusEvents.mm +49 -0
- package/ios/events/TrueSheetLifecycleEvents.h +40 -0
- package/ios/events/TrueSheetLifecycleEvents.mm +71 -0
- package/ios/events/TrueSheetStateEvents.h +35 -0
- package/ios/events/TrueSheetStateEvents.mm +49 -0
- package/ios/utils/GestureUtil.h +7 -0
- package/ios/utils/GestureUtil.mm +12 -0
- package/lib/module/TrueSheet.js +65 -12
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +15 -5
- package/lib/module/index.js +0 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/reanimated/ReanimatedTrueSheet.js +13 -7
- package/lib/module/reanimated/ReanimatedTrueSheet.js.map +1 -1
- package/lib/module/reanimated/ReanimatedTrueSheetProvider.js +4 -2
- package/lib/module/reanimated/ReanimatedTrueSheetProvider.js.map +1 -1
- package/lib/typescript/src/TrueSheet.d.ts +4 -0
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +58 -6
- package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +14 -5
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +0 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/reanimated/ReanimatedTrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/reanimated/ReanimatedTrueSheetProvider.d.ts +8 -2
- package/lib/typescript/src/reanimated/ReanimatedTrueSheetProvider.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/TrueSheet.tsx +80 -10
- package/src/TrueSheet.types.ts +65 -6
- package/src/__mocks__/index.js +0 -5
- package/src/fabric/TrueSheetViewNativeComponent.ts +15 -5
- package/src/index.ts +0 -1
- package/src/reanimated/ReanimatedTrueSheet.tsx +12 -7
- package/src/reanimated/ReanimatedTrueSheetProvider.tsx +11 -3
- package/android/src/main/java/com/lodev09/truesheet/events/DetentChangeEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DidDismissEvent.kt +0 -20
- package/android/src/main/java/com/lodev09/truesheet/events/DidPresentEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DragBeginEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DragChangeEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DragEndEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/MountEvent.kt +0 -20
- package/android/src/main/java/com/lodev09/truesheet/events/PositionChangeEvent.kt +0 -32
- package/android/src/main/java/com/lodev09/truesheet/events/WillDismissEvent.kt +0 -20
- package/android/src/main/java/com/lodev09/truesheet/events/WillPresentEvent.kt +0 -26
- package/ios/events/OnDetentChangeEvent.h +0 -28
- package/ios/events/OnDetentChangeEvent.mm +0 -30
- package/ios/events/OnDidDismissEvent.h +0 -26
- package/ios/events/OnDidDismissEvent.mm +0 -25
- package/ios/events/OnDidPresentEvent.h +0 -28
- package/ios/events/OnDidPresentEvent.mm +0 -30
- package/ios/events/OnDragBeginEvent.h +0 -28
- package/ios/events/OnDragBeginEvent.mm +0 -30
- package/ios/events/OnDragChangeEvent.h +0 -28
- package/ios/events/OnDragChangeEvent.mm +0 -30
- package/ios/events/OnDragEndEvent.h +0 -28
- package/ios/events/OnDragEndEvent.mm +0 -30
- package/ios/events/OnMountEvent.h +0 -26
- package/ios/events/OnMountEvent.mm +0 -25
- package/ios/events/OnPositionChangeEvent.mm +0 -32
- package/ios/events/OnWillDismissEvent.h +0 -26
- package/ios/events/OnWillDismissEvent.mm +0 -25
- package/ios/events/OnWillPresentEvent.h +0 -28
- package/ios/events/OnWillPresentEvent.mm +0 -30
- package/ios/utils/ConversionUtil.h +0 -24
- package/lib/module/TrueSheetGrabber.js +0 -51
- package/lib/module/TrueSheetGrabber.js.map +0 -1
- package/lib/typescript/src/TrueSheetGrabber.d.ts +0 -39
- package/lib/typescript/src/TrueSheetGrabber.d.ts.map +0 -1
- package/src/TrueSheetGrabber.tsx +0 -82
|
@@ -67,7 +67,11 @@ class TrueSheetViewManager :
|
|
|
67
67
|
DragBeginEvent.EVENT_NAME to hashMapOf("registrationName" to DragBeginEvent.REGISTRATION_NAME),
|
|
68
68
|
DragChangeEvent.EVENT_NAME to hashMapOf("registrationName" to DragChangeEvent.REGISTRATION_NAME),
|
|
69
69
|
DragEndEvent.EVENT_NAME to hashMapOf("registrationName" to DragEndEvent.REGISTRATION_NAME),
|
|
70
|
-
PositionChangeEvent.EVENT_NAME to hashMapOf("registrationName" to PositionChangeEvent.REGISTRATION_NAME)
|
|
70
|
+
PositionChangeEvent.EVENT_NAME to hashMapOf("registrationName" to PositionChangeEvent.REGISTRATION_NAME),
|
|
71
|
+
WillFocusEvent.EVENT_NAME to hashMapOf("registrationName" to WillFocusEvent.REGISTRATION_NAME),
|
|
72
|
+
FocusEvent.EVENT_NAME to hashMapOf("registrationName" to FocusEvent.REGISTRATION_NAME),
|
|
73
|
+
WillBlurEvent.EVENT_NAME to hashMapOf("registrationName" to WillBlurEvent.REGISTRATION_NAME),
|
|
74
|
+
BlurEvent.EVENT_NAME to hashMapOf("registrationName" to BlurEvent.REGISTRATION_NAME)
|
|
71
75
|
)
|
|
72
76
|
|
|
73
77
|
// ==================== Props ====================
|
|
@@ -110,6 +114,11 @@ class TrueSheetViewManager :
|
|
|
110
114
|
view.setDismissible(dismissible)
|
|
111
115
|
}
|
|
112
116
|
|
|
117
|
+
@ReactProp(name = "draggable", defaultBoolean = true)
|
|
118
|
+
override fun setDraggable(view: TrueSheetView, draggable: Boolean) {
|
|
119
|
+
view.setDraggable(draggable)
|
|
120
|
+
}
|
|
121
|
+
|
|
113
122
|
@ReactProp(name = "dimmed", defaultBoolean = true)
|
|
114
123
|
override fun setDimmed(view: TrueSheetView, dimmed: Boolean) {
|
|
115
124
|
view.setDimmed(dimmed)
|
|
@@ -149,7 +158,16 @@ class TrueSheetViewManager :
|
|
|
149
158
|
@ReactProp(name = "blurTint")
|
|
150
159
|
override fun setBlurTint(view: TrueSheetView, tint: String?) {
|
|
151
160
|
// iOS-specific prop - no-op on Android
|
|
152
|
-
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@ReactProp(name = "blurIntensity", defaultDouble = 1.0)
|
|
164
|
+
override fun setBlurIntensity(view: TrueSheetView, value: Double) {
|
|
165
|
+
// iOS-specific prop - no-op on Android
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@ReactProp(name = "blurInteraction", defaultBoolean = false)
|
|
169
|
+
override fun setBlurInteraction(view: TrueSheetView, value: Boolean) {
|
|
170
|
+
// iOS-specific prop - no-op on Android
|
|
153
171
|
}
|
|
154
172
|
|
|
155
173
|
@ReactProp(name = "edgeToEdgeFullScreen", defaultBoolean = false)
|
|
@@ -157,8 +175,8 @@ class TrueSheetViewManager :
|
|
|
157
175
|
view.setEdgeToEdgeFullScreen(edgeToEdgeFullScreen)
|
|
158
176
|
}
|
|
159
177
|
|
|
160
|
-
@ReactProp(name = "
|
|
161
|
-
override fun
|
|
178
|
+
@ReactProp(name = "scrollable", defaultBoolean = false)
|
|
179
|
+
override fun setScrollable(view: TrueSheetView, value: Boolean) {
|
|
162
180
|
// iOS-specific prop - no-op on Android
|
|
163
181
|
}
|
|
164
182
|
|
|
@@ -19,11 +19,6 @@ class RNScreensFragmentObserver(
|
|
|
19
19
|
private var fragmentLifecycleCallback: FragmentManager.FragmentLifecycleCallbacks? = null
|
|
20
20
|
private val activeModalFragments: MutableSet<Fragment> = mutableSetOf()
|
|
21
21
|
|
|
22
|
-
/**
|
|
23
|
-
* Check if there are active modal fragments being tracked.
|
|
24
|
-
*/
|
|
25
|
-
fun hasActiveModals(): Boolean = activeModalFragments.isNotEmpty()
|
|
26
|
-
|
|
27
22
|
/**
|
|
28
23
|
* Start observing fragment lifecycle events.
|
|
29
24
|
*/
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
package com.lodev09.truesheet.core
|
|
2
|
+
|
|
3
|
+
import com.lodev09.truesheet.TrueSheetView
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Manages TrueSheet stacking behavior.
|
|
7
|
+
* Tracks presented sheets and handles visibility when sheets stack on top of each other.
|
|
8
|
+
*/
|
|
9
|
+
object TrueSheetDialogObserver {
|
|
10
|
+
|
|
11
|
+
private val presentedSheetStack = mutableListOf<TrueSheetView>()
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Called when a sheet is about to be presented.
|
|
15
|
+
* Returns the visible parent sheet to stack on, or null if none.
|
|
16
|
+
*/
|
|
17
|
+
@JvmStatic
|
|
18
|
+
fun onSheetWillPresent(sheetView: TrueSheetView, detentIndex: Int): TrueSheetView? {
|
|
19
|
+
synchronized(presentedSheetStack) {
|
|
20
|
+
val parentSheet = presentedSheetStack.lastOrNull()
|
|
21
|
+
?.takeIf { it.viewController.isPresented && it.viewController.isDialogVisible }
|
|
22
|
+
|
|
23
|
+
// Hide parent if the new sheet would cover it
|
|
24
|
+
parentSheet?.let {
|
|
25
|
+
val parentTop = it.viewController.currentSheetTop
|
|
26
|
+
val newSheetTop = sheetView.viewController.getExpectedSheetTop(detentIndex)
|
|
27
|
+
if (!it.viewController.isExpanded && parentTop <= newSheetTop) {
|
|
28
|
+
it.viewController.hideDialog()
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!presentedSheetStack.contains(sheetView)) {
|
|
33
|
+
presentedSheetStack.add(sheetView)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return parentSheet
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Called when a sheet has been dismissed.
|
|
42
|
+
* Shows the parent sheet if this sheet was stacked on it.
|
|
43
|
+
*/
|
|
44
|
+
@JvmStatic
|
|
45
|
+
fun onSheetDidDismiss(sheetView: TrueSheetView, hadParent: Boolean) {
|
|
46
|
+
synchronized(presentedSheetStack) {
|
|
47
|
+
presentedSheetStack.remove(sheetView)
|
|
48
|
+
if (hadParent) {
|
|
49
|
+
presentedSheetStack.lastOrNull()?.viewController?.showDialog()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@JvmStatic
|
|
55
|
+
fun removeSheet(sheetView: TrueSheetView) {
|
|
56
|
+
synchronized(presentedSheetStack) {
|
|
57
|
+
presentedSheetStack.remove(sheetView)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@JvmStatic
|
|
62
|
+
fun clear() {
|
|
63
|
+
synchronized(presentedSheetStack) {
|
|
64
|
+
presentedSheetStack.clear()
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
package com.lodev09.truesheet.core
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.graphics.Color
|
|
6
|
+
import android.graphics.drawable.GradientDrawable
|
|
7
|
+
import android.view.Gravity
|
|
8
|
+
import android.view.View
|
|
9
|
+
import android.widget.FrameLayout
|
|
10
|
+
import com.facebook.react.uimanager.PixelUtil.dpToPx
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Native grabber (drag handle) view for the bottom sheet.
|
|
14
|
+
* Displays a small pill-shaped indicator at the top of the sheet.
|
|
15
|
+
*/
|
|
16
|
+
@SuppressLint("ViewConstructor")
|
|
17
|
+
class TrueSheetGrabberView(context: Context) : View(context) {
|
|
18
|
+
|
|
19
|
+
companion object {
|
|
20
|
+
private const val GRABBER_WIDTH = 32f // dp
|
|
21
|
+
private const val GRABBER_HEIGHT = 4f // dp
|
|
22
|
+
private const val GRABBER_TOP_MARGIN = 16f // dp
|
|
23
|
+
private val GRABBER_COLOR = Color.argb((0.4 * 255).toInt(), 73, 69, 79) // #49454F @ 40%
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
init {
|
|
27
|
+
layoutParams = FrameLayout.LayoutParams(
|
|
28
|
+
GRABBER_WIDTH.dpToPx().toInt(),
|
|
29
|
+
GRABBER_HEIGHT.dpToPx().toInt()
|
|
30
|
+
).apply {
|
|
31
|
+
gravity = Gravity.CENTER_HORIZONTAL or Gravity.TOP
|
|
32
|
+
topMargin = GRABBER_TOP_MARGIN.dpToPx().toInt()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
background = GradientDrawable().apply {
|
|
36
|
+
shape = GradientDrawable.RECTANGLE
|
|
37
|
+
cornerRadius = (GRABBER_HEIGHT / 2).dpToPx()
|
|
38
|
+
setColor(GRABBER_COLOR)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// High elevation to ensure grabber appears above content views
|
|
42
|
+
elevation = 100f
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
package com.lodev09.truesheet.events
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.WritableMap
|
|
5
|
+
import com.facebook.react.uimanager.events.Event
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Fired when dragging begins
|
|
9
|
+
* Payload: { index: number, position: number, detent: number }
|
|
10
|
+
*/
|
|
11
|
+
class DragBeginEvent(surfaceId: Int, viewId: Int, private val index: Int, private val position: Float, private val detent: Float) :
|
|
12
|
+
Event<DragBeginEvent>(surfaceId, viewId) {
|
|
13
|
+
|
|
14
|
+
override fun getEventName(): String = EVENT_NAME
|
|
15
|
+
|
|
16
|
+
override fun getEventData(): WritableMap =
|
|
17
|
+
Arguments.createMap().apply {
|
|
18
|
+
putInt("index", index)
|
|
19
|
+
putDouble("position", position.toDouble())
|
|
20
|
+
putDouble("detent", detent.toDouble())
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
companion object {
|
|
24
|
+
const val EVENT_NAME = "topDragBegin"
|
|
25
|
+
const val REGISTRATION_NAME = "onDragBegin"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Fired continuously during dragging
|
|
31
|
+
* Payload: { index: number, position: number, detent: number }
|
|
32
|
+
*/
|
|
33
|
+
class DragChangeEvent(surfaceId: Int, viewId: Int, private val index: Int, private val position: Float, private val detent: Float) :
|
|
34
|
+
Event<DragChangeEvent>(surfaceId, viewId) {
|
|
35
|
+
|
|
36
|
+
override fun getEventName(): String = EVENT_NAME
|
|
37
|
+
|
|
38
|
+
override fun getEventData(): WritableMap =
|
|
39
|
+
Arguments.createMap().apply {
|
|
40
|
+
putInt("index", index)
|
|
41
|
+
putDouble("position", position.toDouble())
|
|
42
|
+
putDouble("detent", detent.toDouble())
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
companion object {
|
|
46
|
+
const val EVENT_NAME = "topDragChange"
|
|
47
|
+
const val REGISTRATION_NAME = "onDragChange"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Fired when dragging ends
|
|
53
|
+
* Payload: { index: number, position: number, detent: number }
|
|
54
|
+
*/
|
|
55
|
+
class DragEndEvent(surfaceId: Int, viewId: Int, private val index: Int, private val position: Float, private val detent: Float) :
|
|
56
|
+
Event<DragEndEvent>(surfaceId, viewId) {
|
|
57
|
+
|
|
58
|
+
override fun getEventName(): String = EVENT_NAME
|
|
59
|
+
|
|
60
|
+
override fun getEventData(): WritableMap =
|
|
61
|
+
Arguments.createMap().apply {
|
|
62
|
+
putInt("index", index)
|
|
63
|
+
putDouble("position", position.toDouble())
|
|
64
|
+
putDouble("detent", detent.toDouble())
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
companion object {
|
|
68
|
+
const val EVENT_NAME = "topDragEnd"
|
|
69
|
+
const val REGISTRATION_NAME = "onDragEnd"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
package com.lodev09.truesheet.events
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.WritableMap
|
|
5
|
+
import com.facebook.react.uimanager.events.Event
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Fired when the sheet is about to regain focus because a sheet on top of it is being dismissed
|
|
9
|
+
*/
|
|
10
|
+
class WillFocusEvent(surfaceId: Int, viewId: Int) : Event<WillFocusEvent>(surfaceId, viewId) {
|
|
11
|
+
|
|
12
|
+
override fun getEventName(): String = EVENT_NAME
|
|
13
|
+
|
|
14
|
+
override fun getEventData(): WritableMap = Arguments.createMap()
|
|
15
|
+
|
|
16
|
+
companion object {
|
|
17
|
+
const val EVENT_NAME = "topWillFocus"
|
|
18
|
+
const val REGISTRATION_NAME = "onWillFocus"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Fired when the sheet regains focus after a sheet on top of it is dismissed
|
|
24
|
+
*/
|
|
25
|
+
class FocusEvent(surfaceId: Int, viewId: Int) : Event<FocusEvent>(surfaceId, viewId) {
|
|
26
|
+
|
|
27
|
+
override fun getEventName(): String = EVENT_NAME
|
|
28
|
+
|
|
29
|
+
override fun getEventData(): WritableMap = Arguments.createMap()
|
|
30
|
+
|
|
31
|
+
companion object {
|
|
32
|
+
const val EVENT_NAME = "topDidFocus"
|
|
33
|
+
const val REGISTRATION_NAME = "onDidFocus"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Fired when the sheet is about to lose focus because another sheet is being presented on top of it
|
|
39
|
+
*/
|
|
40
|
+
class WillBlurEvent(surfaceId: Int, viewId: Int) : Event<WillBlurEvent>(surfaceId, viewId) {
|
|
41
|
+
|
|
42
|
+
override fun getEventName(): String = EVENT_NAME
|
|
43
|
+
|
|
44
|
+
override fun getEventData(): WritableMap = Arguments.createMap()
|
|
45
|
+
|
|
46
|
+
companion object {
|
|
47
|
+
const val EVENT_NAME = "topWillBlur"
|
|
48
|
+
const val REGISTRATION_NAME = "onWillBlur"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Fired when the sheet loses focus because another sheet is presented on top of it
|
|
54
|
+
*/
|
|
55
|
+
class BlurEvent(surfaceId: Int, viewId: Int) : Event<BlurEvent>(surfaceId, viewId) {
|
|
56
|
+
|
|
57
|
+
override fun getEventName(): String = EVENT_NAME
|
|
58
|
+
|
|
59
|
+
override fun getEventData(): WritableMap = Arguments.createMap()
|
|
60
|
+
|
|
61
|
+
companion object {
|
|
62
|
+
const val EVENT_NAME = "topDidBlur"
|
|
63
|
+
const val REGISTRATION_NAME = "onDidBlur"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
package com.lodev09.truesheet.events
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.WritableMap
|
|
5
|
+
import com.facebook.react.uimanager.events.Event
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Fired when the sheet component is mounted and ready
|
|
9
|
+
*/
|
|
10
|
+
class MountEvent(surfaceId: Int, viewId: Int) : Event<MountEvent>(surfaceId, viewId) {
|
|
11
|
+
|
|
12
|
+
override fun getEventName(): String = EVENT_NAME
|
|
13
|
+
|
|
14
|
+
override fun getEventData(): WritableMap = Arguments.createMap()
|
|
15
|
+
|
|
16
|
+
companion object {
|
|
17
|
+
const val EVENT_NAME = "topMount"
|
|
18
|
+
const val REGISTRATION_NAME = "onMount"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Fired before the sheet is presented
|
|
24
|
+
* Payload: { index: number, position: number, detent: number }
|
|
25
|
+
*/
|
|
26
|
+
class WillPresentEvent(surfaceId: Int, viewId: Int, private val index: Int, private val position: Float, private val detent: Float) :
|
|
27
|
+
Event<WillPresentEvent>(surfaceId, viewId) {
|
|
28
|
+
|
|
29
|
+
override fun getEventName(): String = EVENT_NAME
|
|
30
|
+
|
|
31
|
+
override fun getEventData(): WritableMap =
|
|
32
|
+
Arguments.createMap().apply {
|
|
33
|
+
putInt("index", index)
|
|
34
|
+
putDouble("position", position.toDouble())
|
|
35
|
+
putDouble("detent", detent.toDouble())
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
companion object {
|
|
39
|
+
const val EVENT_NAME = "topWillPresent"
|
|
40
|
+
const val REGISTRATION_NAME = "onWillPresent"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Fired after the sheet is presented
|
|
46
|
+
* Payload: { index: number, position: number, detent: number }
|
|
47
|
+
*/
|
|
48
|
+
class DidPresentEvent(surfaceId: Int, viewId: Int, private val index: Int, private val position: Float, private val detent: Float) :
|
|
49
|
+
Event<DidPresentEvent>(surfaceId, viewId) {
|
|
50
|
+
|
|
51
|
+
override fun getEventName(): String = EVENT_NAME
|
|
52
|
+
|
|
53
|
+
override fun getEventData(): WritableMap =
|
|
54
|
+
Arguments.createMap().apply {
|
|
55
|
+
putInt("index", index)
|
|
56
|
+
putDouble("position", position.toDouble())
|
|
57
|
+
putDouble("detent", detent.toDouble())
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
companion object {
|
|
61
|
+
const val EVENT_NAME = "topDidPresent"
|
|
62
|
+
const val REGISTRATION_NAME = "onDidPresent"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Fired before the sheet is dismissed
|
|
68
|
+
*/
|
|
69
|
+
class WillDismissEvent(surfaceId: Int, viewId: Int) : Event<WillDismissEvent>(surfaceId, viewId) {
|
|
70
|
+
|
|
71
|
+
override fun getEventName(): String = EVENT_NAME
|
|
72
|
+
|
|
73
|
+
override fun getEventData(): WritableMap = Arguments.createMap()
|
|
74
|
+
|
|
75
|
+
companion object {
|
|
76
|
+
const val EVENT_NAME = "topWillDismiss"
|
|
77
|
+
const val REGISTRATION_NAME = "onWillDismiss"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Fired after the sheet is dismissed
|
|
83
|
+
*/
|
|
84
|
+
class DidDismissEvent(surfaceId: Int, viewId: Int) : Event<DidDismissEvent>(surfaceId, viewId) {
|
|
85
|
+
|
|
86
|
+
override fun getEventName(): String = EVENT_NAME
|
|
87
|
+
|
|
88
|
+
override fun getEventData(): WritableMap = Arguments.createMap()
|
|
89
|
+
|
|
90
|
+
companion object {
|
|
91
|
+
const val EVENT_NAME = "topDidDismiss"
|
|
92
|
+
const val REGISTRATION_NAME = "onDidDismiss"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
package com.lodev09.truesheet.events
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.WritableMap
|
|
5
|
+
import com.facebook.react.uimanager.events.Event
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Fired when the detent changes
|
|
9
|
+
* Payload: { index: number, position: number, detent: number }
|
|
10
|
+
*/
|
|
11
|
+
class DetentChangeEvent(surfaceId: Int, viewId: Int, private val index: Int, private val position: Float, private val detent: Float) :
|
|
12
|
+
Event<DetentChangeEvent>(surfaceId, viewId) {
|
|
13
|
+
|
|
14
|
+
override fun getEventName(): String = EVENT_NAME
|
|
15
|
+
|
|
16
|
+
override fun getEventData(): WritableMap =
|
|
17
|
+
Arguments.createMap().apply {
|
|
18
|
+
putInt("index", index)
|
|
19
|
+
putDouble("position", position.toDouble())
|
|
20
|
+
putDouble("detent", detent.toDouble())
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
companion object {
|
|
24
|
+
const val EVENT_NAME = "topDetentChange"
|
|
25
|
+
const val REGISTRATION_NAME = "onDetentChange"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Fired continuously for position updates during drag and animation
|
|
31
|
+
* Payload: { index: number, position: number, detent: number, realtime: boolean }
|
|
32
|
+
*/
|
|
33
|
+
class PositionChangeEvent(
|
|
34
|
+
surfaceId: Int,
|
|
35
|
+
viewId: Int,
|
|
36
|
+
private val index: Float,
|
|
37
|
+
private val position: Float,
|
|
38
|
+
private val detent: Float,
|
|
39
|
+
private val realtime: Boolean = false
|
|
40
|
+
) : Event<PositionChangeEvent>(surfaceId, viewId) {
|
|
41
|
+
|
|
42
|
+
override fun getEventName(): String = EVENT_NAME
|
|
43
|
+
|
|
44
|
+
override fun getEventData(): WritableMap =
|
|
45
|
+
Arguments.createMap().apply {
|
|
46
|
+
putDouble("index", index.toDouble())
|
|
47
|
+
putDouble("position", position.toDouble())
|
|
48
|
+
putDouble("detent", detent.toDouble())
|
|
49
|
+
putBoolean("realtime", realtime)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
companion object {
|
|
53
|
+
const val EVENT_NAME = "topPositionChange"
|
|
54
|
+
const val REGISTRATION_NAME = "onPositionChange"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -19,6 +19,18 @@ object ScreenUtils {
|
|
|
19
19
|
context.resources.getIdentifier(name, "dimen", "android")
|
|
20
20
|
).takeIf { it > 0 } ?: 0
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Get the WindowInsets for API 30+, or null for older APIs
|
|
24
|
+
*/
|
|
25
|
+
private fun getWindowInsets(context: ReactContext): WindowInsets? {
|
|
26
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
27
|
+
return context.getSystemService(WindowManager::class.java)
|
|
28
|
+
?.currentWindowMetrics
|
|
29
|
+
?.windowInsets
|
|
30
|
+
}
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
|
|
22
34
|
/**
|
|
23
35
|
* Get the status bar height
|
|
24
36
|
*
|
|
@@ -26,20 +38,29 @@ object ScreenUtils {
|
|
|
26
38
|
* @return Status bar height in pixels
|
|
27
39
|
*/
|
|
28
40
|
fun getStatusBarHeight(context: ReactContext): Int {
|
|
29
|
-
// Modern approach using WindowInsets (API 30+)
|
|
30
41
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (insets != null) {
|
|
35
|
-
return insets.top
|
|
36
|
-
}
|
|
42
|
+
getWindowInsets(context)
|
|
43
|
+
?.getInsetsIgnoringVisibility(WindowInsets.Type.statusBars())
|
|
44
|
+
?.let { return it.top }
|
|
37
45
|
}
|
|
38
|
-
|
|
39
|
-
// Fallback to legacy approach for older APIs
|
|
40
46
|
return getIdentifierHeight(context, "status_bar_height")
|
|
41
47
|
}
|
|
42
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Get the navigation bar height (bottom inset)
|
|
51
|
+
*
|
|
52
|
+
* @param context React context
|
|
53
|
+
* @return Navigation bar height in pixels
|
|
54
|
+
*/
|
|
55
|
+
fun getNavigationBarHeight(context: ReactContext): Int {
|
|
56
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
57
|
+
getWindowInsets(context)
|
|
58
|
+
?.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars())
|
|
59
|
+
?.let { return it.bottom }
|
|
60
|
+
}
|
|
61
|
+
return getIdentifierHeight(context, "navigation_bar_height")
|
|
62
|
+
}
|
|
63
|
+
|
|
43
64
|
/**
|
|
44
65
|
* Calculate the screen height
|
|
45
66
|
*
|
|
@@ -55,36 +76,20 @@ object ScreenUtils {
|
|
|
55
76
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
56
77
|
context.display?.getRealMetrics(displayMetrics)
|
|
57
78
|
} else {
|
|
79
|
+
@Suppress("DEPRECATION")
|
|
58
80
|
windowManager.defaultDisplay.getMetrics(displayMetrics)
|
|
59
81
|
}
|
|
60
82
|
|
|
61
83
|
val screenHeight = displayMetrics.heightPixels
|
|
62
|
-
val statusBarHeight = getStatusBarHeight(context)
|
|
63
|
-
|
|
64
|
-
val hasNavigationBar = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
65
|
-
context.getSystemService(WindowManager::class.java)
|
|
66
|
-
?.currentWindowMetrics
|
|
67
|
-
?.windowInsets
|
|
68
|
-
?.isVisible(WindowInsets.Type.navigationBars()) ?: false
|
|
69
|
-
} else {
|
|
70
|
-
context.resources.getIdentifier("navigation_bar_height", "dimen", "android") > 0
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
val navigationBarHeight = if (hasNavigationBar) {
|
|
74
|
-
getIdentifierHeight(context, "navigation_bar_height")
|
|
75
|
-
} else {
|
|
76
|
-
0
|
|
77
|
-
}
|
|
78
84
|
|
|
79
85
|
return if (edgeToEdge) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
false -> screenHeight + navigationBarHeight
|
|
86
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
87
|
+
screenHeight
|
|
88
|
+
} else {
|
|
89
|
+
screenHeight + getNavigationBarHeight(context)
|
|
85
90
|
}
|
|
86
91
|
} else {
|
|
87
|
-
screenHeight -
|
|
92
|
+
screenHeight - getStatusBarHeight(context) - getNavigationBarHeight(context)
|
|
88
93
|
}
|
|
89
94
|
}
|
|
90
95
|
|
|
@@ -98,8 +103,7 @@ object ScreenUtils {
|
|
|
98
103
|
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
|
99
104
|
|
|
100
105
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
101
|
-
|
|
102
|
-
windowMetrics.bounds.width()
|
|
106
|
+
windowManager.currentWindowMetrics.bounds.width()
|
|
103
107
|
} else {
|
|
104
108
|
val displayMetrics = DisplayMetrics()
|
|
105
109
|
@Suppress("DEPRECATION")
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
|
3
|
+
<translate
|
|
4
|
+
android:duration="200"
|
|
5
|
+
android:fromYDelta="20%p"
|
|
6
|
+
android:toYDelta="0"
|
|
7
|
+
android:interpolator="@android:interpolator/fast_out_slow_in" />
|
|
8
|
+
<alpha
|
|
9
|
+
android:duration="200"
|
|
10
|
+
android:fromAlpha="0.0"
|
|
11
|
+
android:toAlpha="1.0"
|
|
12
|
+
android:interpolator="@android:interpolator/fast_out_slow_in" />
|
|
13
|
+
</set>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
|
3
|
+
<translate
|
|
4
|
+
android:duration="150"
|
|
5
|
+
android:fromYDelta="0"
|
|
6
|
+
android:toYDelta="20%p"
|
|
7
|
+
android:interpolator="@android:interpolator/fast_out_slow_in" />
|
|
8
|
+
<alpha
|
|
9
|
+
android:duration="150"
|
|
10
|
+
android:fromAlpha="1.0"
|
|
11
|
+
android:toAlpha="0.0"
|
|
12
|
+
android:interpolator="@android:interpolator/fast_out_slow_in" />
|
|
13
|
+
</set>
|
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
2
2
|
<resources>
|
|
3
|
-
<!--
|
|
3
|
+
<!-- Smooth slide animation for BottomSheetDialog -->
|
|
4
|
+
<style name="TrueSheetAnimation" parent="Animation.AppCompat.Dialog">
|
|
5
|
+
<item name="android:windowEnterAnimation">@anim/true_sheet_slide_in</item>
|
|
6
|
+
<item name="android:windowExitAnimation">@anim/true_sheet_slide_out</item>
|
|
7
|
+
</style>
|
|
8
|
+
|
|
9
|
+
<!-- Default BottomSheetDialog style with smooth animations -->
|
|
10
|
+
<style name="TrueSheetDialog" parent="Theme.Design.Light.BottomSheetDialog">
|
|
11
|
+
<item name="android:windowAnimationStyle">@style/TrueSheetAnimation</item>
|
|
12
|
+
</style>
|
|
13
|
+
|
|
14
|
+
<!-- BottomSheetDialog style with edge-to-edge and smooth animations -->
|
|
4
15
|
<style name="TrueSheetEdgeToEdgeEnabledDialog" parent="Theme.Design.Light.BottomSheetDialog">
|
|
5
16
|
<item name="android:windowIsFloating">false</item>
|
|
6
17
|
<item name="enableEdgeToEdge">true</item>
|
|
18
|
+
<item name="android:windowAnimationStyle">@style/TrueSheetAnimation</item>
|
|
7
19
|
</style>
|
|
8
20
|
</resources>
|
|
@@ -167,6 +167,10 @@ using namespace facebook::react;
|
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
- (void)contentViewDidChangeChildren {
|
|
171
|
+
[self setupContentScrollViewPinning];
|
|
172
|
+
}
|
|
173
|
+
|
|
170
174
|
#pragma mark - TrueSheetHeaderViewDelegate
|
|
171
175
|
|
|
172
176
|
- (void)headerViewDidChangeSize:(CGSize)newSize {
|
|
@@ -21,6 +21,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
21
21
|
@protocol TrueSheetContentViewDelegate <NSObject>
|
|
22
22
|
|
|
23
23
|
- (void)contentViewDidChangeSize:(CGSize)newSize;
|
|
24
|
+
- (void)contentViewDidChangeChildren;
|
|
24
25
|
|
|
25
26
|
@end
|
|
26
27
|
|
|
@@ -28,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
28
29
|
|
|
29
30
|
@property (nonatomic, weak, nullable) id<TrueSheetContentViewDelegate> delegate;
|
|
30
31
|
|
|
31
|
-
- (RCTScrollViewComponentView *_Nullable)findScrollView;
|
|
32
|
+
- (RCTScrollViewComponentView *_Nullable)findScrollView:(UIView *_Nullable *_Nullable)outTopSibling;
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* Setup ScrollView pinning
|