@lodev09/react-native-true-sheet 3.9.9 → 3.10.0-beta.1
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 +1 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +1 -2
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +41 -14
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +10 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +10 -12
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +49 -43
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +11 -2
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetBehavior.kt +36 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +14 -3
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +3 -3
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +3 -1
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetGrabberView.kt +51 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +8 -5
- package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +17 -0
- package/ios/TrueSheetContainerView.h +8 -1
- package/ios/TrueSheetContainerView.mm +14 -7
- package/ios/TrueSheetContentView.mm +10 -7
- package/ios/TrueSheetModule.mm +5 -0
- package/ios/TrueSheetView.mm +40 -40
- package/ios/TrueSheetViewController.h +3 -1
- package/ios/TrueSheetViewController.mm +67 -18
- package/ios/core/TrueSheetGrabberView.h +20 -0
- package/ios/core/TrueSheetGrabberView.mm +58 -12
- package/lib/module/TrueSheet.js +27 -7
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +2 -1
- package/lib/module/specs/NativeTrueSheetModule.js.map +1 -1
- package/lib/typescript/src/TrueSheet.d.ts +5 -1
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +9 -3
- package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +4 -1
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/specs/NativeTrueSheetModule.d.ts +6 -0
- package/lib/typescript/src/specs/NativeTrueSheetModule.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/TrueSheet.tsx +40 -8
- package/src/TrueSheet.types.ts +10 -3
- package/src/fabric/TrueSheetViewNativeComponent.ts +2 -1
- package/src/specs/NativeTrueSheetModule.ts +7 -0
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ The true native bottom sheet experience for your React Native Apps. 💩
|
|
|
15
15
|
* 🚀 **Fully Native** - Implemented in the native realm, zero JS hacks
|
|
16
16
|
* ♿ **Accessible** - Native accessibility and screen reader support out of the box
|
|
17
17
|
* 🔄 **Flexible API** - Use [imperative methods](https://sheet.lodev09.com/reference/methods#ref-methods) or [lifecycle events](https://sheet.lodev09.com/reference/events)
|
|
18
|
+
* ⌨️ **Keyboard Handling** - Built-in [keyboard handling](https://sheet.lodev09.com/guides/keyboard) with automatic adjustment
|
|
18
19
|
* 📐 **Side Sheets** - Native [side sheet](https://sheet.lodev09.com/guides/side-sheets) support for iPad and Android tablets
|
|
19
20
|
* 🪟 **Liquid Glass** - [iOS 26+ Liquid Glass](https://sheet.lodev09.com/guides/liquid-glass) support out of the box, featured in [Expo Blog](https://expo.dev/blog/how-to-create-apple-maps-style-liquid-glass-sheets)
|
|
20
21
|
* 🐎 **Reanimated** - First-class support for [react-native-reanimated](https://sheet.lodev09.com/guides/reanimated)
|
|
@@ -2,7 +2,6 @@ package com.lodev09.truesheet
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.view.View
|
|
5
|
-
import com.facebook.react.bridge.ReadableMap
|
|
6
5
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
7
6
|
import com.facebook.react.uimanager.events.EventDispatcher
|
|
8
7
|
import com.facebook.react.views.view.ReactViewGroup
|
|
@@ -40,7 +39,7 @@ class TrueSheetContainerView(reactContext: ThemedReactContext) :
|
|
|
40
39
|
var insetAdjustment: TrueSheetInsetAdjustment = TrueSheetInsetAdjustment.AUTOMATIC
|
|
41
40
|
var scrollViewBottomInset: Int = 0
|
|
42
41
|
var scrollableEnabled: Boolean = false
|
|
43
|
-
var scrollableOptions:
|
|
42
|
+
var scrollableOptions: ScrollableOptions? = null
|
|
44
43
|
set(value) {
|
|
45
44
|
field = value
|
|
46
45
|
contentView?.scrollableOptions = value
|
|
@@ -4,13 +4,18 @@ import android.annotation.SuppressLint
|
|
|
4
4
|
import android.view.View
|
|
5
5
|
import android.view.ViewGroup
|
|
6
6
|
import android.widget.ScrollView
|
|
7
|
-
import
|
|
7
|
+
import androidx.core.widget.NestedScrollView
|
|
8
|
+
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
|
8
9
|
import com.facebook.react.uimanager.PixelUtil.dpToPx
|
|
9
10
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
10
11
|
import com.facebook.react.views.view.ReactViewGroup
|
|
11
12
|
import com.lodev09.truesheet.core.TrueSheetKeyboardObserver
|
|
12
13
|
import com.lodev09.truesheet.core.TrueSheetKeyboardObserverDelegate
|
|
13
14
|
import com.lodev09.truesheet.utils.isDescendantOf
|
|
15
|
+
import com.lodev09.truesheet.utils.smoothScrollBy
|
|
16
|
+
import com.lodev09.truesheet.utils.smoothScrollTo
|
|
17
|
+
|
|
18
|
+
data class ScrollableOptions(val keyboardScrollOffset: Float = 0f, val scrollingExpandsSheet: Boolean = true)
|
|
14
19
|
|
|
15
20
|
/**
|
|
16
21
|
* Delegate interface for content view size changes
|
|
@@ -32,17 +37,18 @@ class TrueSheetContentView(private val reactContext: ThemedReactContext) : React
|
|
|
32
37
|
private var lastWidth = 0
|
|
33
38
|
private var lastHeight = 0
|
|
34
39
|
|
|
35
|
-
private var pinnedScrollView:
|
|
40
|
+
private var pinnedScrollView: ViewGroup? = null
|
|
36
41
|
private var originalScrollViewPaddingBottom: Int = 0
|
|
37
42
|
private var bottomInset: Int = 0
|
|
43
|
+
private var scrollExpansionPadding: Int = 0
|
|
38
44
|
|
|
39
45
|
private var keyboardScrollOffset: Float = 0f
|
|
40
46
|
private var keyboardObserver: TrueSheetKeyboardObserver? = null
|
|
41
47
|
|
|
42
|
-
var scrollableOptions:
|
|
48
|
+
var scrollableOptions: ScrollableOptions? = null
|
|
43
49
|
set(value) {
|
|
44
50
|
field = value
|
|
45
|
-
keyboardScrollOffset = value?.
|
|
51
|
+
keyboardScrollOffset = value?.keyboardScrollOffset?.dpToPx() ?: 0f
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
override fun addView(child: View?, index: Int) {
|
|
@@ -94,6 +100,9 @@ class TrueSheetContentView(private val reactContext: ThemedReactContext) : React
|
|
|
94
100
|
originalScrollViewPaddingBottom = scrollView.paddingBottom
|
|
95
101
|
pinnedScrollView = scrollView
|
|
96
102
|
|
|
103
|
+
scrollView.isNestedScrollingEnabled = true
|
|
104
|
+
(scrollView.parent as? SwipeRefreshLayout)?.isNestedScrollingEnabled = false
|
|
105
|
+
|
|
97
106
|
scrollView.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
|
98
107
|
if (scrollY != oldScrollY) {
|
|
99
108
|
delegate?.contentViewDidScroll()
|
|
@@ -112,6 +121,19 @@ class TrueSheetContentView(private val reactContext: ThemedReactContext) : React
|
|
|
112
121
|
}
|
|
113
122
|
}
|
|
114
123
|
|
|
124
|
+
// TODO: Replace this workaround with synchronous state layout updates on every sheet resize.
|
|
125
|
+
// The container is currently sized to the largest detent, so at smaller detents the ScrollView
|
|
126
|
+
// viewport extends beyond the visible area, reducing the effective scroll range. This padding
|
|
127
|
+
// compensates for that difference until we can resize the container per-detent synchronously.
|
|
128
|
+
fun updateScrollExpansionPadding(padding: Int) {
|
|
129
|
+
if (scrollExpansionPadding == padding) return
|
|
130
|
+
scrollExpansionPadding = padding
|
|
131
|
+
val keyboardHeight = keyboardObserver?.currentHeight ?: 0
|
|
132
|
+
val basePadding = if (keyboardHeight > 0) keyboardHeight else bottomInset
|
|
133
|
+
setScrollViewPaddingBottom(originalScrollViewPaddingBottom + basePadding)
|
|
134
|
+
nudgeScrollView()
|
|
135
|
+
}
|
|
136
|
+
|
|
115
137
|
private fun setScrollViewPaddingBottom(paddingBottom: Int) {
|
|
116
138
|
val scrollView = pinnedScrollView ?: return
|
|
117
139
|
scrollView.clipToPadding = false
|
|
@@ -119,26 +141,29 @@ class TrueSheetContentView(private val reactContext: ThemedReactContext) : React
|
|
|
119
141
|
scrollView.paddingLeft,
|
|
120
142
|
scrollView.paddingTop,
|
|
121
143
|
scrollView.paddingRight,
|
|
122
|
-
paddingBottom
|
|
144
|
+
paddingBottom + scrollExpansionPadding
|
|
123
145
|
)
|
|
124
146
|
}
|
|
125
147
|
|
|
126
148
|
fun clearScrollable() {
|
|
127
149
|
pinnedScrollView?.setOnScrollChangeListener(null as View.OnScrollChangeListener?)
|
|
150
|
+
pinnedScrollView?.isNestedScrollingEnabled = false
|
|
151
|
+
(pinnedScrollView?.parent as? SwipeRefreshLayout)?.isNestedScrollingEnabled = true
|
|
152
|
+
scrollExpansionPadding = 0
|
|
128
153
|
setScrollViewPaddingBottom(originalScrollViewPaddingBottom)
|
|
129
154
|
pinnedScrollView = null
|
|
130
155
|
originalScrollViewPaddingBottom = 0
|
|
131
156
|
bottomInset = 0
|
|
132
157
|
}
|
|
133
158
|
|
|
134
|
-
fun findScrollView():
|
|
159
|
+
fun findScrollView(): ViewGroup? {
|
|
135
160
|
if (pinnedScrollView != null) return pinnedScrollView
|
|
136
161
|
return findScrollView(this as View)
|
|
137
162
|
}
|
|
138
163
|
|
|
139
|
-
private fun findScrollView(view: View):
|
|
140
|
-
if (view is ScrollView) {
|
|
141
|
-
return view
|
|
164
|
+
private fun findScrollView(view: View): ViewGroup? {
|
|
165
|
+
if (view is ScrollView || view is NestedScrollView) {
|
|
166
|
+
return view as ViewGroup
|
|
142
167
|
}
|
|
143
168
|
|
|
144
169
|
if (view is ViewGroup) {
|
|
@@ -191,11 +216,13 @@ class TrueSheetContentView(private val reactContext: ThemedReactContext) : React
|
|
|
191
216
|
val totalBottomInset = if (keyboardHeight > 0) keyboardHeight else bottomInset
|
|
192
217
|
setScrollViewPaddingBottom(originalScrollViewPaddingBottom + totalBottomInset)
|
|
193
218
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
219
|
+
scrollView.post { nudgeScrollView() }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private fun nudgeScrollView() {
|
|
223
|
+
val scrollView = pinnedScrollView ?: return
|
|
224
|
+
scrollView.smoothScrollBy(0, 1)
|
|
225
|
+
scrollView.smoothScrollBy(0, -1)
|
|
199
226
|
}
|
|
200
227
|
|
|
201
228
|
private fun scrollToFocusedInput() {
|
|
@@ -140,6 +140,16 @@ class TrueSheetModule(reactContext: ReactApplicationContext) :
|
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
@ReactMethod
|
|
144
|
+
fun handleBackPress(viewTag: Double, promise: Promise) {
|
|
145
|
+
val tag = viewTag.toInt()
|
|
146
|
+
|
|
147
|
+
withTrueSheetView(tag, promise) { view ->
|
|
148
|
+
view.handleBackPress()
|
|
149
|
+
promise.resolve(null)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
143
153
|
/**
|
|
144
154
|
* Helper method to get TrueSheetView by tag and execute closure
|
|
145
155
|
*/
|
|
@@ -6,7 +6,6 @@ import android.view.ViewGroup
|
|
|
6
6
|
import android.view.accessibility.AccessibilityEvent
|
|
7
7
|
import androidx.annotation.UiThread
|
|
8
8
|
import com.facebook.react.bridge.LifecycleEventListener
|
|
9
|
-
import com.facebook.react.bridge.ReadableMap
|
|
10
9
|
import com.facebook.react.bridge.WritableNativeMap
|
|
11
10
|
import com.facebook.react.uimanager.PixelUtil.pxToDp
|
|
12
11
|
import com.facebook.react.uimanager.StateWrapper
|
|
@@ -120,9 +119,6 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
120
119
|
|
|
121
120
|
if (child is TrueSheetContainerView) {
|
|
122
121
|
child.delegate = this
|
|
123
|
-
viewController.createSheet()
|
|
124
|
-
setupScrollable()
|
|
125
|
-
|
|
126
122
|
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
127
123
|
eventDispatcher?.dispatchEvent(MountEvent(surfaceId, id))
|
|
128
124
|
}
|
|
@@ -282,7 +278,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
282
278
|
setupScrollable()
|
|
283
279
|
}
|
|
284
280
|
|
|
285
|
-
fun setScrollableOptions(options:
|
|
281
|
+
fun setScrollableOptions(options: ScrollableOptions?) {
|
|
286
282
|
viewController.scrollableOptions = options
|
|
287
283
|
setupScrollable()
|
|
288
284
|
}
|
|
@@ -350,11 +346,8 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
350
346
|
return
|
|
351
347
|
}
|
|
352
348
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
promiseCallback()
|
|
356
|
-
return
|
|
357
|
-
}
|
|
349
|
+
viewController.createSheet()
|
|
350
|
+
setupScrollable()
|
|
358
351
|
|
|
359
352
|
// Dismiss keyboard if focused view is within a sheet or if target detent will be dimmed
|
|
360
353
|
val parentSheet = TrueSheetStackManager.getTopmostSheet()
|
|
@@ -379,6 +372,11 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
379
372
|
viewController.present(detentIndex, animated)
|
|
380
373
|
}
|
|
381
374
|
|
|
375
|
+
@UiThread
|
|
376
|
+
fun handleBackPress() {
|
|
377
|
+
viewController.handleBackPress()
|
|
378
|
+
}
|
|
379
|
+
|
|
382
380
|
@UiThread
|
|
383
381
|
fun dismiss(animated: Boolean = true, promiseCallback: () -> Unit) {
|
|
384
382
|
if (viewController.isBeingDismissed || !viewController.isPresented) {
|
|
@@ -577,9 +575,9 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
577
575
|
eventDispatcher?.dispatchEvent(BlurEvent(surfaceId, id))
|
|
578
576
|
}
|
|
579
577
|
|
|
580
|
-
override fun
|
|
578
|
+
override fun viewControllerDidChangeVisibility(visible: Boolean) {
|
|
581
579
|
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
582
|
-
eventDispatcher?.dispatchEvent(
|
|
580
|
+
eventDispatcher?.dispatchEvent(VisibilityChangeEvent(surfaceId, id, visible))
|
|
583
581
|
}
|
|
584
582
|
|
|
585
583
|
// ==================== TrueSheetContainerViewDelegate ====================
|
|
@@ -8,14 +8,10 @@ import android.view.View
|
|
|
8
8
|
import android.view.ViewGroup
|
|
9
9
|
import android.view.accessibility.AccessibilityNodeInfo
|
|
10
10
|
import android.widget.ImageView
|
|
11
|
-
import android.widget.ScrollView
|
|
12
|
-
import androidx.activity.OnBackPressedCallback
|
|
13
|
-
import androidx.appcompat.app.AppCompatActivity
|
|
14
11
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
15
12
|
import androidx.core.graphics.createBitmap
|
|
16
13
|
import androidx.core.view.isNotEmpty
|
|
17
14
|
import com.facebook.react.R
|
|
18
|
-
import com.facebook.react.bridge.ReadableMap
|
|
19
15
|
import com.facebook.react.uimanager.JSPointerDispatcher
|
|
20
16
|
import com.facebook.react.uimanager.JSTouchDispatcher
|
|
21
17
|
import com.facebook.react.uimanager.PixelUtil.dpToPx
|
|
@@ -27,6 +23,7 @@ import com.facebook.react.util.RNLog
|
|
|
27
23
|
import com.facebook.react.views.view.ReactViewGroup
|
|
28
24
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
29
25
|
import com.lodev09.truesheet.core.GrabberOptions
|
|
26
|
+
import com.lodev09.truesheet.core.TrueSheetBottomSheetBehavior
|
|
30
27
|
import com.lodev09.truesheet.core.TrueSheetBottomSheetView
|
|
31
28
|
import com.lodev09.truesheet.core.TrueSheetBottomSheetViewDelegate
|
|
32
29
|
import com.lodev09.truesheet.core.TrueSheetCoordinatorLayout
|
|
@@ -64,7 +61,7 @@ interface TrueSheetViewControllerDelegate {
|
|
|
64
61
|
fun viewControllerDidFocus()
|
|
65
62
|
fun viewControllerWillBlur()
|
|
66
63
|
fun viewControllerDidBlur()
|
|
67
|
-
fun
|
|
64
|
+
fun viewControllerDidChangeVisibility(visible: Boolean)
|
|
68
65
|
}
|
|
69
66
|
|
|
70
67
|
// =============================================================================
|
|
@@ -146,9 +143,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
146
143
|
private var dimView: TrueSheetDimView? = null
|
|
147
144
|
private var parentDimView: TrueSheetDimView? = null
|
|
148
145
|
|
|
149
|
-
// Back button handling
|
|
150
|
-
private var backCallback: OnBackPressedCallback? = null
|
|
151
|
-
|
|
152
146
|
// Presentation State
|
|
153
147
|
var isPresented = false
|
|
154
148
|
private set
|
|
@@ -213,7 +207,12 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
213
207
|
|
|
214
208
|
var scrollable: Boolean = false
|
|
215
209
|
|
|
216
|
-
var scrollableOptions:
|
|
210
|
+
var scrollableOptions: ScrollableOptions? = null
|
|
211
|
+
set(value) {
|
|
212
|
+
field = value
|
|
213
|
+
behavior?.scrollingExpandsSheet = value?.scrollingExpandsSheet ?: true
|
|
214
|
+
if (isPresented) sheetView?.let { updateScrollExpansionPadding(it.top) }
|
|
215
|
+
}
|
|
217
216
|
|
|
218
217
|
override var sheetCornerRadius: Float = DEFAULT_CORNER_RADIUS.dpToPx()
|
|
219
218
|
set(value) {
|
|
@@ -245,7 +244,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
245
244
|
// =============================================================================
|
|
246
245
|
|
|
247
246
|
// Behavior
|
|
248
|
-
private val behavior:
|
|
247
|
+
private val behavior: TrueSheetBottomSheetBehavior<TrueSheetBottomSheetView>?
|
|
249
248
|
get() = sheetView?.behavior
|
|
250
249
|
|
|
251
250
|
internal val containerView: TrueSheetContainerView?
|
|
@@ -349,7 +348,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
349
348
|
|
|
350
349
|
private fun cleanupSheet() {
|
|
351
350
|
cleanupKeyboardObserver()
|
|
352
|
-
cleanupBackCallback()
|
|
353
351
|
sheetView?.animate()?.cancel()
|
|
354
352
|
|
|
355
353
|
// Cleanup dim views
|
|
@@ -411,28 +409,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
411
409
|
snapshotView = null
|
|
412
410
|
}
|
|
413
411
|
|
|
414
|
-
// =============================================================================
|
|
415
|
-
// MARK: - Back Button Handling
|
|
416
|
-
// =============================================================================
|
|
417
|
-
|
|
418
|
-
private fun setupBackCallback() {
|
|
419
|
-
val activity = reactContext.currentActivity as? AppCompatActivity ?: return
|
|
420
|
-
|
|
421
|
-
backCallback = object : OnBackPressedCallback(true) {
|
|
422
|
-
override fun handleOnBackPressed() {
|
|
423
|
-
delegate?.viewControllerDidBackPress()
|
|
424
|
-
dismissOrCollapseToLowest()
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
activity.onBackPressedDispatcher.addCallback(backCallback!!)
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
private fun cleanupBackCallback() {
|
|
432
|
-
backCallback?.remove()
|
|
433
|
-
backCallback = null
|
|
434
|
-
}
|
|
435
|
-
|
|
436
412
|
// =============================================================================
|
|
437
413
|
// MARK: - TrueSheetCoordinatorLayout.Delegate
|
|
438
414
|
// =============================================================================
|
|
@@ -453,7 +429,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
453
429
|
sheetView?.let { emitChangePositionDelegate(it.top, realtime = false) }
|
|
454
430
|
}
|
|
455
431
|
|
|
456
|
-
override fun findScrollView():
|
|
432
|
+
override fun findScrollView(): ViewGroup? = containerView?.contentView?.findScrollView()
|
|
457
433
|
override fun findSheetView(): TrueSheetBottomSheetView? = sheetView
|
|
458
434
|
|
|
459
435
|
// =============================================================================
|
|
@@ -480,7 +456,11 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
480
456
|
children.forEach { it.viewController.dismiss(animated = true) }
|
|
481
457
|
}
|
|
482
458
|
|
|
483
|
-
|
|
459
|
+
if (dismissible) {
|
|
460
|
+
dismiss(animated = true)
|
|
461
|
+
} else if (parentSheetView == null && isDimmedAtCurrentDetent && dimmedDetentIndex > 0) {
|
|
462
|
+
setStateForDetentIndex(dimmedDetentIndex - 1)
|
|
463
|
+
}
|
|
484
464
|
}
|
|
485
465
|
|
|
486
466
|
// =============================================================================
|
|
@@ -496,6 +476,20 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
496
476
|
}
|
|
497
477
|
}
|
|
498
478
|
|
|
479
|
+
override fun bottomSheetViewDidAccessibilityIncrement() {
|
|
480
|
+
if (currentDetentIndex < detents.size - 1) {
|
|
481
|
+
setStateForDetentIndex(currentDetentIndex + 1)
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
override fun bottomSheetViewDidAccessibilityDecrement() {
|
|
486
|
+
if (currentDetentIndex > 0) {
|
|
487
|
+
setStateForDetentIndex(currentDetentIndex - 1)
|
|
488
|
+
} else if (dismissible) {
|
|
489
|
+
dismiss(animated = true)
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
499
493
|
// =============================================================================
|
|
500
494
|
// MARK: - BottomSheetCallback
|
|
501
495
|
// =============================================================================
|
|
@@ -548,6 +542,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
548
542
|
else -> { }
|
|
549
543
|
}
|
|
550
544
|
|
|
545
|
+
updateScrollExpansionPadding(sheetView.top)
|
|
551
546
|
emitChangePositionDelegate(sheetView.top)
|
|
552
547
|
|
|
553
548
|
// On older APIs, use onSlide for footer positioning during keyboard transitions
|
|
@@ -561,6 +556,15 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
561
556
|
}
|
|
562
557
|
}
|
|
563
558
|
|
|
559
|
+
private fun updateScrollExpansionPadding(sheetTop: Int) {
|
|
560
|
+
if (!scrollable) {
|
|
561
|
+
containerView?.contentView?.updateScrollExpansionPadding(0)
|
|
562
|
+
return
|
|
563
|
+
}
|
|
564
|
+
val expandedOffset = behavior?.expandedOffset ?: return
|
|
565
|
+
containerView?.contentView?.updateScrollExpansionPadding(maxOf(0, sheetTop - expandedOffset))
|
|
566
|
+
}
|
|
567
|
+
|
|
564
568
|
private fun handleStateSettled(sheetView: View, newState: Int) {
|
|
565
569
|
if (interactionState is InteractionState.Reconfiguring) return
|
|
566
570
|
|
|
@@ -586,6 +590,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
586
590
|
currentDetentIndex = detentInfo.index
|
|
587
591
|
setupDimmedBackground()
|
|
588
592
|
delegate?.viewControllerDidChangeDetent(detentInfo.index, detentInfo.position, detent)
|
|
593
|
+
this@TrueSheetViewController.sheetView?.updateGrabberAccessibilityValue(detentInfo.index, detents.size)
|
|
589
594
|
}
|
|
590
595
|
|
|
591
596
|
interactionState = InteractionState.Idle
|
|
@@ -598,6 +603,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
598
603
|
val detent = detentCalculator.getDetentValueForIndex(detentInfo.index)
|
|
599
604
|
delegate?.viewControllerDidChangeDetent(detentInfo.index, detentInfo.position, detent)
|
|
600
605
|
}
|
|
606
|
+
this@TrueSheetViewController.sheetView?.updateGrabberAccessibilityValue(detentInfo.index, detents.size)
|
|
601
607
|
}
|
|
602
608
|
}
|
|
603
609
|
}
|
|
@@ -624,8 +630,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
624
630
|
|
|
625
631
|
isSheetVisible = false
|
|
626
632
|
wasHiddenByScreen = true
|
|
627
|
-
|
|
628
|
-
|
|
633
|
+
delegate?.viewControllerDidChangeVisibility(false)
|
|
629
634
|
dimViews.forEach { it.animate().alpha(0f).setDuration(SCREEN_FADE_DURATION).start() }
|
|
630
635
|
sheet.animate()
|
|
631
636
|
.alpha(0f)
|
|
@@ -641,10 +646,10 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
641
646
|
|
|
642
647
|
internal fun showAfterScreen() {
|
|
643
648
|
isSheetVisible = true
|
|
649
|
+
delegate?.viewControllerDidChangeVisibility(true)
|
|
644
650
|
setSheetVisibility(true)
|
|
645
651
|
sheetView?.alpha = 1f
|
|
646
652
|
updateDimAmount(animated = true)
|
|
647
|
-
backCallback?.isEnabled = true
|
|
648
653
|
}
|
|
649
654
|
|
|
650
655
|
/**
|
|
@@ -677,7 +682,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
677
682
|
setupSheetDetents()
|
|
678
683
|
setupDimmedBackground()
|
|
679
684
|
setupKeyboardObserver()
|
|
680
|
-
setupBackCallback()
|
|
681
685
|
|
|
682
686
|
sheet.setupBackground()
|
|
683
687
|
sheet.setupElevation()
|
|
@@ -722,11 +726,12 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
722
726
|
val params = sheet.createLayoutParams()
|
|
723
727
|
|
|
724
728
|
@Suppress("UNCHECKED_CAST")
|
|
725
|
-
val behavior = params.behavior as
|
|
729
|
+
val behavior = params.behavior as TrueSheetBottomSheetBehavior<TrueSheetBottomSheetView>
|
|
726
730
|
|
|
727
731
|
// Configure behavior
|
|
728
732
|
behavior.isHideable = true
|
|
729
733
|
behavior.isDraggable = draggable
|
|
734
|
+
behavior.scrollingExpandsSheet = scrollableOptions?.scrollingExpandsSheet ?: true
|
|
730
735
|
behavior.state = BottomSheetBehavior.STATE_HIDDEN
|
|
731
736
|
behavior.addBottomSheetCallback(sheetCallback)
|
|
732
737
|
|
|
@@ -754,11 +759,11 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
754
759
|
KeyboardUtils.dismiss(reactContext)
|
|
755
760
|
}
|
|
756
761
|
|
|
757
|
-
|
|
762
|
+
fun handleBackPress() {
|
|
758
763
|
if (dismissible) {
|
|
759
764
|
dismiss(animated = true)
|
|
760
|
-
} else if (
|
|
761
|
-
setStateForDetentIndex(
|
|
765
|
+
} else if (currentDetentIndex > 0) {
|
|
766
|
+
setStateForDetentIndex(0)
|
|
762
767
|
}
|
|
763
768
|
}
|
|
764
769
|
|
|
@@ -787,6 +792,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
787
792
|
delegate?.viewControllerDidPresent(index, position, detent)
|
|
788
793
|
parentSheetView?.viewControllerDidBlur()
|
|
789
794
|
delegate?.viewControllerDidFocus()
|
|
795
|
+
sheetView?.updateGrabberAccessibilityValue(index, detents.size)
|
|
790
796
|
|
|
791
797
|
presentPromise?.invoke()
|
|
792
798
|
presentPromise = null
|
|
@@ -74,7 +74,7 @@ class TrueSheetViewManager :
|
|
|
74
74
|
FocusEvent.EVENT_NAME to hashMapOf("registrationName" to FocusEvent.REGISTRATION_NAME),
|
|
75
75
|
WillBlurEvent.EVENT_NAME to hashMapOf("registrationName" to WillBlurEvent.REGISTRATION_NAME),
|
|
76
76
|
BlurEvent.EVENT_NAME to hashMapOf("registrationName" to BlurEvent.REGISTRATION_NAME),
|
|
77
|
-
|
|
77
|
+
VisibilityChangeEvent.EVENT_NAME to hashMapOf("registrationName" to VisibilityChangeEvent.REGISTRATION_NAME)
|
|
78
78
|
)
|
|
79
79
|
|
|
80
80
|
// ==================== Props ====================
|
|
@@ -218,7 +218,16 @@ class TrueSheetViewManager :
|
|
|
218
218
|
|
|
219
219
|
@ReactProp(name = "scrollableOptions")
|
|
220
220
|
override fun setScrollableOptions(view: TrueSheetView, options: ReadableMap?) {
|
|
221
|
-
|
|
221
|
+
if (options == null) {
|
|
222
|
+
view.setScrollableOptions(null)
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
val scrollableOptions = ScrollableOptions(
|
|
227
|
+
keyboardScrollOffset = if (options.hasKey("keyboardScrollOffset")) options.getDouble("keyboardScrollOffset").toFloat() else 0f,
|
|
228
|
+
scrollingExpandsSheet = if (options.hasKey("scrollingExpandsSheet")) options.getBoolean("scrollingExpandsSheet") else true
|
|
229
|
+
)
|
|
230
|
+
view.setScrollableOptions(scrollableOptions)
|
|
222
231
|
}
|
|
223
232
|
|
|
224
233
|
companion object {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
package com.lodev09.truesheet.core
|
|
2
|
+
|
|
3
|
+
import android.view.View
|
|
4
|
+
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
5
|
+
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
6
|
+
|
|
7
|
+
class TrueSheetBottomSheetBehavior<V : View> : BottomSheetBehavior<V>() {
|
|
8
|
+
var scrollingExpandsSheet: Boolean = true
|
|
9
|
+
|
|
10
|
+
override fun onNestedPreScroll(
|
|
11
|
+
coordinatorLayout: CoordinatorLayout,
|
|
12
|
+
child: V,
|
|
13
|
+
target: View,
|
|
14
|
+
dx: Int,
|
|
15
|
+
dy: Int,
|
|
16
|
+
consumed: IntArray,
|
|
17
|
+
type: Int
|
|
18
|
+
) {
|
|
19
|
+
// dy > 0 = user swiping up = sheet expanding
|
|
20
|
+
// Block expansion from scroll, but allow if sheet is already being dragged
|
|
21
|
+
if (!scrollingExpandsSheet && dy > 0 && state != STATE_DRAGGING) return
|
|
22
|
+
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
override fun onNestedPreFling(
|
|
26
|
+
coordinatorLayout: CoordinatorLayout,
|
|
27
|
+
child: V,
|
|
28
|
+
target: View,
|
|
29
|
+
velocityX: Float,
|
|
30
|
+
velocityY: Float
|
|
31
|
+
): Boolean {
|
|
32
|
+
// Don't consume flings — let the ScrollView decelerate naturally
|
|
33
|
+
if (!scrollingExpandsSheet) return false
|
|
34
|
+
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -14,6 +14,7 @@ import android.view.View
|
|
|
14
14
|
import android.view.ViewOutlineProvider
|
|
15
15
|
import android.widget.FrameLayout
|
|
16
16
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
17
|
+
import androidx.core.view.ViewCompat
|
|
17
18
|
import com.facebook.react.uimanager.PixelUtil.dpToPx
|
|
18
19
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
19
20
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
@@ -32,6 +33,8 @@ interface TrueSheetBottomSheetViewDelegate {
|
|
|
32
33
|
val grabberOptions: GrabberOptions?
|
|
33
34
|
val draggable: Boolean
|
|
34
35
|
fun bottomSheetViewDidTapGrabber()
|
|
36
|
+
fun bottomSheetViewDidAccessibilityIncrement()
|
|
37
|
+
fun bottomSheetViewDidAccessibilityDecrement()
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
/**
|
|
@@ -63,9 +66,9 @@ class TrueSheetBottomSheetView(private val reactContext: ThemedReactContext) : F
|
|
|
63
66
|
|
|
64
67
|
// Behavior reference (set after adding to CoordinatorLayout)
|
|
65
68
|
@Suppress("UNCHECKED_CAST")
|
|
66
|
-
val behavior:
|
|
69
|
+
val behavior: TrueSheetBottomSheetBehavior<TrueSheetBottomSheetView>?
|
|
67
70
|
get() = (layoutParams as? CoordinatorLayout.LayoutParams)
|
|
68
|
-
?.behavior as?
|
|
71
|
+
?.behavior as? TrueSheetBottomSheetBehavior<TrueSheetBottomSheetView>
|
|
69
72
|
|
|
70
73
|
// =============================================================================
|
|
71
74
|
// MARK: - Initialization
|
|
@@ -75,6 +78,8 @@ class TrueSheetBottomSheetView(private val reactContext: ThemedReactContext) : F
|
|
|
75
78
|
// Allow content to extend beyond bounds (for footer positioning)
|
|
76
79
|
clipChildren = false
|
|
77
80
|
clipToPadding = false
|
|
81
|
+
|
|
82
|
+
ViewCompat.setAccessibilityPaneTitle(this, "Bottom sheet")
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
override fun setTranslationY(translationY: Float) {
|
|
@@ -106,7 +111,7 @@ class TrueSheetBottomSheetView(private val reactContext: ThemedReactContext) : F
|
|
|
106
111
|
fun createLayoutParams(): CoordinatorLayout.LayoutParams {
|
|
107
112
|
val applyMaxWidth = delegate?.maxContentWidth != null && !ScreenUtils.isPortraitPhone(reactContext)
|
|
108
113
|
val effectiveMaxWidth = if (applyMaxWidth) delegate!!.maxContentWidth!! else DEFAULT_MAX_WIDTH.dpToPx().toInt()
|
|
109
|
-
val behavior =
|
|
114
|
+
val behavior = TrueSheetBottomSheetBehavior<TrueSheetBottomSheetView>().apply {
|
|
110
115
|
isHideable = true
|
|
111
116
|
maxWidth = effectiveMaxWidth
|
|
112
117
|
}
|
|
@@ -196,11 +201,17 @@ class TrueSheetBottomSheetView(private val reactContext: ThemedReactContext) : F
|
|
|
196
201
|
|
|
197
202
|
val grabberView = TrueSheetGrabberView(reactContext, delegate?.grabberOptions).apply {
|
|
198
203
|
tag = GRABBER_TAG
|
|
204
|
+
onAccessibilityIncrement = { delegate?.bottomSheetViewDidAccessibilityIncrement() }
|
|
205
|
+
onAccessibilityDecrement = { delegate?.bottomSheetViewDidAccessibilityDecrement() }
|
|
199
206
|
}
|
|
200
207
|
|
|
201
208
|
addView(grabberView)
|
|
202
209
|
}
|
|
203
210
|
|
|
211
|
+
fun updateGrabberAccessibilityValue(index: Int, detentCount: Int) {
|
|
212
|
+
findViewWithTag<TrueSheetGrabberView>(GRABBER_TAG)?.updateAccessibilityValue(index, detentCount)
|
|
213
|
+
}
|
|
214
|
+
|
|
204
215
|
// =============================================================================
|
|
205
216
|
// MARK: - Grabber Tap Detection
|
|
206
217
|
// =============================================================================
|
|
@@ -5,7 +5,7 @@ import android.content.Context
|
|
|
5
5
|
import android.content.res.Configuration
|
|
6
6
|
import android.view.MotionEvent
|
|
7
7
|
import android.view.ViewConfiguration
|
|
8
|
-
import android.
|
|
8
|
+
import android.view.ViewGroup
|
|
9
9
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
10
10
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
|
11
11
|
import com.facebook.react.uimanager.PointerEvents
|
|
@@ -15,7 +15,7 @@ import com.lodev09.truesheet.utils.isDescendantOf
|
|
|
15
15
|
interface TrueSheetCoordinatorLayoutDelegate {
|
|
16
16
|
fun coordinatorLayoutDidLayout(changed: Boolean)
|
|
17
17
|
fun coordinatorLayoutDidChangeConfiguration()
|
|
18
|
-
fun findScrollView():
|
|
18
|
+
fun findScrollView(): ViewGroup?
|
|
19
19
|
fun findSheetView(): TrueSheetBottomSheetView?
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -75,7 +75,7 @@ class TrueSheetCoordinatorLayout(context: Context) :
|
|
|
75
75
|
val sheet = delegate?.findSheetView() ?: return
|
|
76
76
|
val behavior = sheet.behavior ?: return
|
|
77
77
|
try {
|
|
78
|
-
val field = behavior.javaClass.getDeclaredField("nestedScrollingChildRef")
|
|
78
|
+
val field = behavior.javaClass.superclass.getDeclaredField("nestedScrollingChildRef")
|
|
79
79
|
field.isAccessible = true
|
|
80
80
|
@Suppress("UNCHECKED_CAST")
|
|
81
81
|
val ref = field.get(behavior) as? java.lang.ref.WeakReference<android.view.View> ?: return
|
|
@@ -29,7 +29,7 @@ interface TrueSheetDimViewDelegate {
|
|
|
29
29
|
* This implements the "dimmedDetentIndex" equivalent functionality:
|
|
30
30
|
* the view only becomes interactive when the sheet is at or above the dimmed detent.
|
|
31
31
|
*/
|
|
32
|
-
@SuppressLint("ViewConstructor"
|
|
32
|
+
@SuppressLint("ViewConstructor")
|
|
33
33
|
class TrueSheetDimView(private val reactContext: ThemedReactContext) :
|
|
34
34
|
View(reactContext),
|
|
35
35
|
ReactPointerEventsView {
|
|
@@ -60,6 +60,8 @@ class TrueSheetDimView(private val reactContext: ThemedReactContext) :
|
|
|
60
60
|
setOnClickListener {
|
|
61
61
|
delegate?.dimViewDidTap()
|
|
62
62
|
}
|
|
63
|
+
|
|
64
|
+
importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
// =============================================================================
|