@lodev09/react-native-true-sheet 3.7.4-beta.2 → 3.8.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -48
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +24 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +162 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetPackage.kt +2 -3
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +76 -8
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +70 -74
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +5 -0
- package/android/src/main/java/com/lodev09/truesheet/core/RNScreensEventObserver.kt +63 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +1 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +2 -19
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetKeyboardObserver.kt +34 -6
- package/android/src/main/java/com/lodev09/truesheet/utils/ViewUtils.kt +12 -0
- package/ios/TrueSheetContainerView.h +15 -4
- package/ios/TrueSheetContainerView.mm +45 -6
- package/ios/TrueSheetContentView.h +6 -2
- package/ios/TrueSheetContentView.mm +88 -2
- package/ios/TrueSheetFooterView.h +4 -3
- package/ios/TrueSheetFooterView.mm +16 -63
- package/ios/TrueSheetView.mm +31 -7
- package/ios/core/TrueSheetKeyboardObserver.h +38 -0
- package/ios/core/TrueSheetKeyboardObserver.mm +90 -0
- package/lib/module/TrueSheet.js +3 -1
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +5 -0
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +15 -0
- package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +4 -0
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/TrueSheet.tsx +3 -1
- package/src/TrueSheet.types.ts +17 -0
- package/src/fabric/TrueSheetViewNativeComponent.ts +5 -0
- package/android/src/main/java/com/lodev09/truesheet/core/RNScreensFragmentObserver.kt +0 -266
|
@@ -8,11 +8,13 @@ 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
|
|
11
12
|
import androidx.activity.OnBackPressedCallback
|
|
12
13
|
import androidx.appcompat.app.AppCompatActivity
|
|
13
14
|
import androidx.core.graphics.createBitmap
|
|
14
15
|
import androidx.core.view.isNotEmpty
|
|
15
16
|
import com.facebook.react.R
|
|
17
|
+
import com.facebook.react.bridge.ReadableMap
|
|
16
18
|
import com.facebook.react.uimanager.JSPointerDispatcher
|
|
17
19
|
import com.facebook.react.uimanager.JSTouchDispatcher
|
|
18
20
|
import com.facebook.react.uimanager.PixelUtil.dpToPx
|
|
@@ -24,7 +26,6 @@ import com.facebook.react.util.RNLog
|
|
|
24
26
|
import com.facebook.react.views.view.ReactViewGroup
|
|
25
27
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
26
28
|
import com.lodev09.truesheet.core.GrabberOptions
|
|
27
|
-
import com.lodev09.truesheet.core.RNScreensFragmentObserver
|
|
28
29
|
import com.lodev09.truesheet.core.TrueSheetBottomSheetView
|
|
29
30
|
import com.lodev09.truesheet.core.TrueSheetBottomSheetViewDelegate
|
|
30
31
|
import com.lodev09.truesheet.core.TrueSheetCoordinatorLayout
|
|
@@ -131,7 +132,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
131
132
|
|
|
132
133
|
private var interactionState: InteractionState = InteractionState.Idle
|
|
133
134
|
private var isDismissing = false
|
|
134
|
-
|
|
135
|
+
var wasHiddenByScreen = false
|
|
135
136
|
private var shouldAnimatePresent = false
|
|
136
137
|
private var isPresentAnimating = false
|
|
137
138
|
|
|
@@ -145,6 +146,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
145
146
|
|
|
146
147
|
// Promises
|
|
147
148
|
var presentPromise: (() -> Unit)? = null
|
|
149
|
+
var resizePromise: (() -> Unit)? = null
|
|
148
150
|
var dismissPromise: (() -> Unit)? = null
|
|
149
151
|
|
|
150
152
|
// For stacked sheets
|
|
@@ -152,7 +154,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
152
154
|
|
|
153
155
|
// Helper Objects
|
|
154
156
|
private var keyboardObserver: TrueSheetKeyboardObserver? = null
|
|
155
|
-
private var rnScreensObserver: RNScreensFragmentObserver? = null
|
|
156
157
|
internal val detentCalculator = TrueSheetDetentCalculator(reactContext).apply {
|
|
157
158
|
delegate = this@TrueSheetViewController
|
|
158
159
|
}
|
|
@@ -175,12 +176,15 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
175
176
|
override var grabberOptions: GrabberOptions? = null
|
|
176
177
|
override var sheetBackgroundColor: Int? = null
|
|
177
178
|
var insetAdjustment: String = "automatic"
|
|
179
|
+
|
|
178
180
|
var scrollable: Boolean = false
|
|
179
181
|
set(value) {
|
|
180
182
|
field = value
|
|
181
183
|
coordinatorLayout?.scrollable = value
|
|
182
184
|
}
|
|
183
185
|
|
|
186
|
+
var scrollableOptions: ReadableMap? = null
|
|
187
|
+
|
|
184
188
|
override var sheetCornerRadius: Float = DEFAULT_CORNER_RADIUS.dpToPx()
|
|
185
189
|
set(value) {
|
|
186
190
|
field = if (value < 0) DEFAULT_CORNER_RADIUS.dpToPx() else value
|
|
@@ -316,7 +320,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
316
320
|
|
|
317
321
|
private fun cleanupSheet() {
|
|
318
322
|
cleanupKeyboardObserver()
|
|
319
|
-
cleanupModalObserver()
|
|
320
323
|
cleanupBackCallback()
|
|
321
324
|
sheetView?.animate()?.cancel()
|
|
322
325
|
|
|
@@ -332,6 +335,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
332
335
|
// Detach content from sheet
|
|
333
336
|
sheetView?.removeView(this)
|
|
334
337
|
|
|
338
|
+
containerView?.cleanupKeyboardHandler()
|
|
335
339
|
coordinatorLayout = null
|
|
336
340
|
sheetView = null
|
|
337
341
|
|
|
@@ -417,6 +421,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
417
421
|
sheetView?.let { emitChangePositionDelegate(it.top, realtime = false) }
|
|
418
422
|
}
|
|
419
423
|
|
|
424
|
+
override fun findScrollView(): ScrollView? = containerView?.contentView?.findScrollView()
|
|
425
|
+
|
|
420
426
|
// =============================================================================
|
|
421
427
|
// MARK: - TrueSheetDimViewDelegate
|
|
422
428
|
// =============================================================================
|
|
@@ -561,54 +567,15 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
561
567
|
}
|
|
562
568
|
|
|
563
569
|
// =============================================================================
|
|
564
|
-
// MARK: -
|
|
570
|
+
// MARK: - Screen Visibility
|
|
565
571
|
// =============================================================================
|
|
566
572
|
|
|
567
|
-
private fun setupModalObserver() {
|
|
568
|
-
rnScreensObserver = RNScreensFragmentObserver(
|
|
569
|
-
reactContext = reactContext,
|
|
570
|
-
onScreenPresented = {
|
|
571
|
-
if (isPresented && isTopmostSheet) {
|
|
572
|
-
if (isSheetVisible) {
|
|
573
|
-
dismissKeyboard()
|
|
574
|
-
post { hideForScreen() }
|
|
575
|
-
} else {
|
|
576
|
-
// Sheet is already hidden, just mark it
|
|
577
|
-
wasHiddenByScreen = true
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
},
|
|
581
|
-
onScreenWillDismiss = {
|
|
582
|
-
val hasPushedScreens = rnScreensObserver?.hasPushedScreens == true
|
|
583
|
-
if (isPresented && wasHiddenByScreen && isTopmostSheet && !hasPushedScreens) {
|
|
584
|
-
showAfterScreen()
|
|
585
|
-
delegate?.viewControllerDidDetectScreenDismiss()
|
|
586
|
-
}
|
|
587
|
-
},
|
|
588
|
-
onScreenDidDismiss = {
|
|
589
|
-
if (isPresented && wasHiddenByScreen) {
|
|
590
|
-
wasHiddenByScreen = false
|
|
591
|
-
// Restore parent sheet after this sheet is restored
|
|
592
|
-
parentSheetView?.viewController?.let { parent ->
|
|
593
|
-
post { parent.showAfterScreen() }
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
)
|
|
598
|
-
rnScreensObserver?.start()
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
private fun cleanupModalObserver() {
|
|
602
|
-
rnScreensObserver?.stop()
|
|
603
|
-
rnScreensObserver = null
|
|
604
|
-
}
|
|
605
|
-
|
|
606
573
|
private fun setSheetVisibility(visible: Boolean) {
|
|
607
574
|
coordinatorLayout?.visibility = if (visible) VISIBLE else GONE
|
|
608
575
|
dimViews.forEach { it.visibility = if (visible) VISIBLE else INVISIBLE }
|
|
609
576
|
}
|
|
610
577
|
|
|
611
|
-
|
|
578
|
+
internal fun hideForScreen() {
|
|
612
579
|
val sheet = sheetView ?: run {
|
|
613
580
|
RNLog.e(reactContext, "TrueSheet: sheetView is null in hideForScreen")
|
|
614
581
|
return
|
|
@@ -630,7 +597,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
630
597
|
parentSheetView?.viewController?.hideForScreen()
|
|
631
598
|
}
|
|
632
599
|
|
|
633
|
-
|
|
600
|
+
internal fun showAfterScreen() {
|
|
634
601
|
isSheetVisible = true
|
|
635
602
|
setSheetVisibility(true)
|
|
636
603
|
sheetView?.alpha = 1f
|
|
@@ -662,41 +629,58 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
662
629
|
}
|
|
663
630
|
|
|
664
631
|
if (isPresented) {
|
|
665
|
-
|
|
666
|
-
|
|
632
|
+
RNLog.w(reactContext, "TrueSheet: sheet is already presented. Use resize() to change detent.")
|
|
633
|
+
presentPromise?.invoke()
|
|
634
|
+
presentPromise = null
|
|
635
|
+
return
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
shouldAnimatePresent = animated
|
|
639
|
+
currentDetentIndex = detentIndex
|
|
640
|
+
interactionState = InteractionState.Idle
|
|
641
|
+
|
|
642
|
+
// Setup sheet in coordinator layout
|
|
643
|
+
setupSheetInCoordinator(coordinator, sheet)
|
|
644
|
+
|
|
645
|
+
emitWillPresentEvents()
|
|
646
|
+
|
|
647
|
+
setupSheetDetents()
|
|
648
|
+
setupDimmedBackground()
|
|
649
|
+
setupKeyboardObserver()
|
|
650
|
+
setupBackCallback()
|
|
651
|
+
|
|
652
|
+
sheet.setupBackground()
|
|
653
|
+
sheet.setupElevation()
|
|
654
|
+
sheet.setupGrabber()
|
|
655
|
+
|
|
656
|
+
if (shouldAnimatePresent) {
|
|
657
|
+
isPresentAnimating = true
|
|
658
|
+
post { setStateForDetentIndex(currentDetentIndex) }
|
|
667
659
|
} else {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
interactionState = InteractionState.Idle
|
|
671
|
-
|
|
672
|
-
// Setup sheet in coordinator layout
|
|
673
|
-
setupSheetInCoordinator(coordinator, sheet)
|
|
674
|
-
|
|
675
|
-
emitWillPresentEvents()
|
|
676
|
-
|
|
677
|
-
setupSheetDetents()
|
|
678
|
-
setupDimmedBackground()
|
|
679
|
-
setupKeyboardObserver()
|
|
680
|
-
setupModalObserver()
|
|
681
|
-
setupBackCallback()
|
|
682
|
-
|
|
683
|
-
sheet.setupBackground()
|
|
684
|
-
sheet.setupElevation()
|
|
685
|
-
sheet.setupGrabber()
|
|
686
|
-
|
|
687
|
-
if (shouldAnimatePresent) {
|
|
688
|
-
isPresentAnimating = true
|
|
689
|
-
post { setStateForDetentIndex(currentDetentIndex) }
|
|
690
|
-
} else {
|
|
691
|
-
setStateForDetentIndex(currentDetentIndex)
|
|
660
|
+
setStateForDetentIndex(currentDetentIndex)
|
|
661
|
+
post {
|
|
692
662
|
emitChangePositionDelegate(detentCalculator.getSheetTopForDetentIndex(currentDetentIndex))
|
|
693
663
|
updateDimAmount()
|
|
694
664
|
finishPresent()
|
|
695
665
|
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
isPresented = true
|
|
669
|
+
isSheetVisible = true
|
|
670
|
+
}
|
|
696
671
|
|
|
697
|
-
|
|
698
|
-
|
|
672
|
+
fun resize(detentIndex: Int) {
|
|
673
|
+
if (!isPresented) {
|
|
674
|
+
RNLog.w(reactContext, "TrueSheet: Cannot resize. Sheet is not presented.")
|
|
675
|
+
resizePromise?.invoke()
|
|
676
|
+
resizePromise = null
|
|
677
|
+
return
|
|
699
678
|
}
|
|
679
|
+
|
|
680
|
+
setupDimmedBackground()
|
|
681
|
+
setStateForDetentIndex(detentIndex)
|
|
682
|
+
resizePromise?.invoke()
|
|
683
|
+
resizePromise = null
|
|
700
684
|
}
|
|
701
685
|
|
|
702
686
|
private fun setupSheetInCoordinator(coordinator: TrueSheetCoordinatorLayout, sheet: TrueSheetBottomSheetView) {
|
|
@@ -766,6 +750,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
766
750
|
// Restore isHideable to actual value after present animation
|
|
767
751
|
behavior?.isHideable = dismissible
|
|
768
752
|
|
|
753
|
+
containerView?.setupKeyboardHandler()
|
|
754
|
+
|
|
769
755
|
val (index, position, detent) = getDetentInfoWithValue(currentDetentIndex)
|
|
770
756
|
delegate?.viewControllerDidPresent(index, position, detent)
|
|
771
757
|
parentSheetView?.viewControllerDidBlur()
|
|
@@ -1012,6 +998,16 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
1012
998
|
if (!shouldHandleKeyboard(checkFocus = !isHandlingKeyboard)) return
|
|
1013
999
|
positionFooter()
|
|
1014
1000
|
}
|
|
1001
|
+
|
|
1002
|
+
override fun focusDidChange(newFocus: View) {
|
|
1003
|
+
// Handle case where keyboard is already visible and focus moves into the sheet
|
|
1004
|
+
if (!shouldHandleKeyboard()) return
|
|
1005
|
+
if (detentIndexBeforeKeyboard < 0 && (keyboardObserver?.currentHeight ?: 0) > 0) {
|
|
1006
|
+
detentIndexBeforeKeyboard = currentDetentIndex
|
|
1007
|
+
currentDetentIndex = detents.size - 1
|
|
1008
|
+
setupSheetDetents()
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1015
1011
|
}
|
|
1016
1012
|
start()
|
|
1017
1013
|
}
|
|
@@ -203,6 +203,11 @@ class TrueSheetViewManager :
|
|
|
203
203
|
view.setSheetElevation(elevation.toFloat())
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
@ReactProp(name = "scrollableOptions")
|
|
207
|
+
override fun setScrollableOptions(view: TrueSheetView, options: ReadableMap?) {
|
|
208
|
+
view.setScrollableOptions(options)
|
|
209
|
+
}
|
|
210
|
+
|
|
206
211
|
companion object {
|
|
207
212
|
const val REACT_CLASS = "TrueSheetView"
|
|
208
213
|
const val TAG_NAME = "TrueSheet"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
package com.lodev09.truesheet.core
|
|
2
|
+
|
|
3
|
+
import android.view.View
|
|
4
|
+
import com.facebook.react.uimanager.events.Event
|
|
5
|
+
import com.facebook.react.uimanager.events.EventDispatcher
|
|
6
|
+
import com.facebook.react.uimanager.events.EventDispatcherListener
|
|
7
|
+
|
|
8
|
+
private const val RN_SCREENS_VIEW_CLASS = "com.swmansion.rnscreens.Screen"
|
|
9
|
+
|
|
10
|
+
interface RNScreensEventObserverDelegate {
|
|
11
|
+
fun presenterScreenWillDisappear()
|
|
12
|
+
fun presenterScreenWillAppear()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Observes react-native-screens lifecycle events via EventDispatcherListener.
|
|
17
|
+
* Detects when the presenting screen unmounts while sheet is presented.
|
|
18
|
+
*/
|
|
19
|
+
class RNScreensEventObserver : EventDispatcherListener {
|
|
20
|
+
var delegate: RNScreensEventObserverDelegate? = null
|
|
21
|
+
|
|
22
|
+
private var eventDispatcher: EventDispatcher? = null
|
|
23
|
+
var presenterScreenTag: Int = 0
|
|
24
|
+
|
|
25
|
+
fun startObserving(dispatcher: EventDispatcher?) {
|
|
26
|
+
if (eventDispatcher != null || dispatcher == null) return
|
|
27
|
+
|
|
28
|
+
eventDispatcher = dispatcher
|
|
29
|
+
dispatcher.addListener(this)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fun stopObserving() {
|
|
33
|
+
eventDispatcher?.removeListener(this)
|
|
34
|
+
eventDispatcher = null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fun capturePresenterScreenFromView(view: View?) {
|
|
38
|
+
presenterScreenTag = 0
|
|
39
|
+
|
|
40
|
+
var current: View? = view
|
|
41
|
+
while (current != null) {
|
|
42
|
+
if (isScreenView(current)) {
|
|
43
|
+
presenterScreenTag = current.id
|
|
44
|
+
break
|
|
45
|
+
}
|
|
46
|
+
current = (current.parent as? View)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
override fun onEventDispatch(event: Event<*>) {
|
|
51
|
+
// Only process events for the presenter screen
|
|
52
|
+
if (presenterScreenTag == 0 || event.viewTag != presenterScreenTag) return
|
|
53
|
+
|
|
54
|
+
when (event.eventName) {
|
|
55
|
+
"topWillDisappear" -> delegate?.presenterScreenWillDisappear()
|
|
56
|
+
"topWillAppear" -> delegate?.presenterScreenWillAppear()
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
companion object {
|
|
61
|
+
private fun isScreenView(view: View): Boolean = view.javaClass.name == RN_SCREENS_VIEW_CLASS
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -52,6 +52,7 @@ class TrueSheetBottomSheetView(private val reactContext: ThemedReactContext) : F
|
|
|
52
52
|
var delegate: TrueSheetBottomSheetViewDelegate? = null
|
|
53
53
|
|
|
54
54
|
// Behavior reference (set after adding to CoordinatorLayout)
|
|
55
|
+
@Suppress("UNCHECKED_CAST")
|
|
55
56
|
val behavior: BottomSheetBehavior<TrueSheetBottomSheetView>?
|
|
56
57
|
get() = (layoutParams as? CoordinatorLayout.LayoutParams)
|
|
57
58
|
?.behavior as? BottomSheetBehavior<TrueSheetBottomSheetView>
|
|
@@ -5,7 +5,6 @@ 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.view.ViewGroup
|
|
9
8
|
import android.widget.ScrollView
|
|
10
9
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
11
10
|
import com.facebook.react.uimanager.PointerEvents
|
|
@@ -14,6 +13,7 @@ import com.facebook.react.uimanager.ReactPointerEventsView
|
|
|
14
13
|
interface TrueSheetCoordinatorLayoutDelegate {
|
|
15
14
|
fun coordinatorLayoutDidLayout(changed: Boolean)
|
|
16
15
|
fun coordinatorLayoutDidChangeConfiguration()
|
|
16
|
+
fun findScrollView(): ScrollView?
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -75,7 +75,7 @@ class TrueSheetCoordinatorLayout(context: Context) :
|
|
|
75
75
|
return super.onInterceptTouchEvent(ev)
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
val scrollView = findScrollView(
|
|
78
|
+
val scrollView = delegate?.findScrollView()
|
|
79
79
|
val cannotScroll = scrollView != null &&
|
|
80
80
|
scrollView.scrollY == 0 &&
|
|
81
81
|
!scrollView.canScrollVertically(1)
|
|
@@ -126,21 +126,4 @@ class TrueSheetCoordinatorLayout(context: Context) :
|
|
|
126
126
|
}
|
|
127
127
|
return super.onTouchEvent(ev)
|
|
128
128
|
}
|
|
129
|
-
|
|
130
|
-
private fun findScrollView(view: android.view.View): ScrollView? {
|
|
131
|
-
if (view is ScrollView) {
|
|
132
|
-
return view
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (view is ViewGroup) {
|
|
136
|
-
for (i in 0 until view.childCount) {
|
|
137
|
-
val scrollView = findScrollView(view.getChildAt(i))
|
|
138
|
-
if (scrollView != null) {
|
|
139
|
-
return scrollView
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return null
|
|
145
|
-
}
|
|
146
129
|
}
|
|
@@ -9,12 +9,15 @@ import androidx.core.view.WindowInsetsAnimationCompat
|
|
|
9
9
|
import androidx.core.view.WindowInsetsCompat
|
|
10
10
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
11
11
|
import com.lodev09.truesheet.utils.KeyboardUtils
|
|
12
|
+
import com.lodev09.truesheet.utils.isDescendantOf
|
|
12
13
|
|
|
13
14
|
interface TrueSheetKeyboardObserverDelegate {
|
|
14
|
-
fun keyboardWillShow(height: Int)
|
|
15
|
-
fun
|
|
16
|
-
fun
|
|
17
|
-
fun
|
|
15
|
+
fun keyboardWillShow(height: Int) {}
|
|
16
|
+
fun keyboardDidShow(height: Int) {}
|
|
17
|
+
fun keyboardWillHide() {}
|
|
18
|
+
fun keyboardDidHide() {}
|
|
19
|
+
fun keyboardDidChangeHeight(height: Int) {}
|
|
20
|
+
fun focusDidChange(newFocus: View) {}
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
/**
|
|
@@ -45,8 +48,10 @@ class TrueSheetKeyboardObserver(private val targetView: View, private val reactC
|
|
|
45
48
|
return false
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
|
|
51
|
+
var isHiding: Boolean = false
|
|
52
|
+
private set
|
|
49
53
|
private var globalLayoutListener: ViewTreeObserver.OnGlobalLayoutListener? = null
|
|
54
|
+
private var focusChangeListener: ViewTreeObserver.OnGlobalFocusChangeListener? = null
|
|
50
55
|
private var activityRootView: View? = null
|
|
51
56
|
|
|
52
57
|
fun start() {
|
|
@@ -55,14 +60,19 @@ class TrueSheetKeyboardObserver(private val targetView: View, private val reactC
|
|
|
55
60
|
} else {
|
|
56
61
|
setupLegacyListener()
|
|
57
62
|
}
|
|
63
|
+
setupFocusChangeListener()
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
fun stop() {
|
|
61
67
|
globalLayoutListener?.let { listener ->
|
|
62
68
|
activityRootView?.viewTreeObserver?.removeOnGlobalLayoutListener(listener)
|
|
63
69
|
globalLayoutListener = null
|
|
64
|
-
activityRootView = null
|
|
65
70
|
}
|
|
71
|
+
focusChangeListener?.let { listener ->
|
|
72
|
+
activityRootView?.viewTreeObserver?.removeOnGlobalFocusChangeListener(listener)
|
|
73
|
+
focusChangeListener = null
|
|
74
|
+
}
|
|
75
|
+
activityRootView = null
|
|
66
76
|
ViewCompat.setWindowInsetsAnimationCallback(targetView, null)
|
|
67
77
|
}
|
|
68
78
|
|
|
@@ -121,6 +131,8 @@ class TrueSheetKeyboardObserver(private val targetView: View, private val reactC
|
|
|
121
131
|
if (isHiding) {
|
|
122
132
|
delegate?.keyboardDidHide()
|
|
123
133
|
isHiding = false
|
|
134
|
+
} else if (finalHeight > 0) {
|
|
135
|
+
delegate?.keyboardDidShow(finalHeight)
|
|
124
136
|
}
|
|
125
137
|
}
|
|
126
138
|
}
|
|
@@ -164,9 +176,25 @@ class TrueSheetKeyboardObserver(private val targetView: View, private val reactC
|
|
|
164
176
|
if (isHiding && newHeight == 0) {
|
|
165
177
|
delegate?.keyboardDidHide()
|
|
166
178
|
isHiding = false
|
|
179
|
+
} else if (newHeight > 0) {
|
|
180
|
+
delegate?.keyboardDidShow(newHeight)
|
|
167
181
|
}
|
|
168
182
|
}
|
|
169
183
|
|
|
170
184
|
rootView.viewTreeObserver.addOnGlobalLayoutListener(globalLayoutListener)
|
|
171
185
|
}
|
|
186
|
+
|
|
187
|
+
private fun setupFocusChangeListener() {
|
|
188
|
+
if (focusChangeListener != null) return
|
|
189
|
+
|
|
190
|
+
val rootView = targetView.rootView ?: return
|
|
191
|
+
activityRootView = rootView
|
|
192
|
+
|
|
193
|
+
focusChangeListener = ViewTreeObserver.OnGlobalFocusChangeListener { _, newFocus ->
|
|
194
|
+
if (currentHeight > 0 && newFocus != null && newFocus.isDescendantOf(targetView)) {
|
|
195
|
+
delegate?.focusDidChange(newFocus)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
rootView.viewTreeObserver.addOnGlobalFocusChangeListener(focusChangeListener)
|
|
199
|
+
}
|
|
172
200
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
package com.lodev09.truesheet.utils
|
|
2
|
+
|
|
3
|
+
import android.view.View
|
|
4
|
+
|
|
5
|
+
fun View.isDescendantOf(ancestor: View): Boolean {
|
|
6
|
+
var current: View? = this
|
|
7
|
+
while (current != null) {
|
|
8
|
+
if (current === ancestor) return true
|
|
9
|
+
current = (current.parent as? View)
|
|
10
|
+
}
|
|
11
|
+
return false
|
|
12
|
+
}
|
|
@@ -43,6 +43,16 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
43
43
|
*/
|
|
44
44
|
@property (nonatomic, assign) BOOL scrollViewPinningEnabled;
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Inset adjustment mode for pinned ScrollView
|
|
48
|
+
*/
|
|
49
|
+
@property (nonatomic, copy, nullable) NSString *insetAdjustment;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Options for scrollable behavior
|
|
53
|
+
*/
|
|
54
|
+
@property (nonatomic, strong, nullable) NSDictionary *scrollableOptions;
|
|
55
|
+
|
|
46
56
|
/**
|
|
47
57
|
* Returns the current content height
|
|
48
58
|
*/
|
|
@@ -64,14 +74,15 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
64
74
|
- (void)setupContentScrollViewPinning;
|
|
65
75
|
|
|
66
76
|
/**
|
|
67
|
-
* Setup keyboard
|
|
77
|
+
* Setup keyboard observer for content and footer
|
|
78
|
+
* @param viewController The sheet view controller to observe keyboard events for
|
|
68
79
|
*/
|
|
69
|
-
- (void)
|
|
80
|
+
- (void)setupKeyboardObserverWithViewController:(UIViewController *)viewController;
|
|
70
81
|
|
|
71
82
|
/**
|
|
72
|
-
* Cleanup keyboard
|
|
83
|
+
* Cleanup keyboard observer
|
|
73
84
|
*/
|
|
74
|
-
- (void)
|
|
85
|
+
- (void)cleanupKeyboardObserver;
|
|
75
86
|
|
|
76
87
|
@end
|
|
77
88
|
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
#import "TrueSheetContentView.h"
|
|
13
13
|
#import "TrueSheetFooterView.h"
|
|
14
14
|
#import "TrueSheetHeaderView.h"
|
|
15
|
+
#import "TrueSheetViewController.h"
|
|
16
|
+
#import "core/TrueSheetKeyboardObserver.h"
|
|
17
|
+
#import "utils/WindowUtil.h"
|
|
15
18
|
|
|
16
19
|
#import <react/renderer/components/TrueSheetSpec/ComponentDescriptors.h>
|
|
17
20
|
#import <react/renderer/components/TrueSheetSpec/EventEmitters.h>
|
|
@@ -30,6 +33,7 @@ using namespace facebook::react;
|
|
|
30
33
|
TrueSheetContentView *_contentView;
|
|
31
34
|
TrueSheetHeaderView *_headerView;
|
|
32
35
|
TrueSheetFooterView *_footerView;
|
|
36
|
+
TrueSheetKeyboardObserver *_keyboardObserver;
|
|
33
37
|
BOOL _scrollViewPinningSet;
|
|
34
38
|
}
|
|
35
39
|
|
|
@@ -77,9 +81,23 @@ using namespace facebook::react;
|
|
|
77
81
|
_scrollViewPinningSet = YES;
|
|
78
82
|
}
|
|
79
83
|
|
|
84
|
+
- (void)setScrollableOptions:(NSDictionary *)scrollableOptions {
|
|
85
|
+
_scrollableOptions = scrollableOptions;
|
|
86
|
+
if (scrollableOptions) {
|
|
87
|
+
NSNumber *offset = scrollableOptions[@"keyboardScrollOffset"];
|
|
88
|
+
_contentView.keyboardScrollOffset = offset ? [offset floatValue] : 0;
|
|
89
|
+
} else {
|
|
90
|
+
_contentView.keyboardScrollOffset = 0;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
80
94
|
- (void)setupContentScrollViewPinning {
|
|
81
95
|
if (_scrollViewPinningSet && _contentView) {
|
|
82
|
-
|
|
96
|
+
CGFloat bottomInset = 0;
|
|
97
|
+
if ([_insetAdjustment isEqualToString:@"automatic"]) {
|
|
98
|
+
bottomInset = [WindowUtil keyWindow].safeAreaInsets.bottom;
|
|
99
|
+
}
|
|
100
|
+
[_contentView setupScrollViewPinning:_scrollViewPinningEnabled bottomInset:bottomInset];
|
|
83
101
|
}
|
|
84
102
|
}
|
|
85
103
|
|
|
@@ -159,14 +177,35 @@ using namespace facebook::react;
|
|
|
159
177
|
[self.delegate containerViewHeaderDidChangeSize:newSize];
|
|
160
178
|
}
|
|
161
179
|
|
|
162
|
-
#pragma mark - Keyboard
|
|
180
|
+
#pragma mark - Keyboard Observer
|
|
163
181
|
|
|
164
|
-
- (void)
|
|
165
|
-
[
|
|
182
|
+
- (void)setupKeyboardObserverWithViewController:(UIViewController *)viewController {
|
|
183
|
+
[self cleanupKeyboardObserver];
|
|
184
|
+
|
|
185
|
+
_keyboardObserver = [[TrueSheetKeyboardObserver alloc] init];
|
|
186
|
+
_keyboardObserver.viewController = (TrueSheetViewController *)viewController;
|
|
187
|
+
|
|
188
|
+
if (_contentView) {
|
|
189
|
+
_contentView.keyboardObserver = _keyboardObserver;
|
|
190
|
+
[_keyboardObserver addDelegate:_contentView];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (_footerView) {
|
|
194
|
+
_footerView.keyboardObserver = _keyboardObserver;
|
|
195
|
+
[_keyboardObserver addDelegate:_footerView];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
[_keyboardObserver start];
|
|
166
199
|
}
|
|
167
200
|
|
|
168
|
-
- (void)
|
|
169
|
-
|
|
201
|
+
- (void)cleanupKeyboardObserver {
|
|
202
|
+
if (_keyboardObserver) {
|
|
203
|
+
[_keyboardObserver stop];
|
|
204
|
+
_keyboardObserver = nil;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
_contentView.keyboardObserver = nil;
|
|
208
|
+
_footerView.keyboardObserver = nil;
|
|
170
209
|
}
|
|
171
210
|
|
|
172
211
|
@end
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
#import <React/RCTViewComponentView.h>
|
|
13
13
|
#import <UIKit/UIKit.h>
|
|
14
14
|
#import <react/renderer/core/LayoutMetrics.h>
|
|
15
|
+
#import "core/TrueSheetKeyboardObserver.h"
|
|
15
16
|
|
|
16
17
|
@class TrueSheetViewController;
|
|
17
18
|
@class RCTScrollViewComponentView;
|
|
@@ -26,17 +27,20 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
26
27
|
|
|
27
28
|
@end
|
|
28
29
|
|
|
29
|
-
@interface TrueSheetContentView : RCTViewComponentView
|
|
30
|
+
@interface TrueSheetContentView : RCTViewComponentView <TrueSheetKeyboardObserverDelegate>
|
|
30
31
|
|
|
31
32
|
@property (nonatomic, weak, nullable) id<TrueSheetContentViewDelegate> delegate;
|
|
33
|
+
@property (nonatomic, assign) CGFloat keyboardScrollOffset;
|
|
34
|
+
@property (nonatomic, weak, nullable) TrueSheetKeyboardObserver *keyboardObserver;
|
|
32
35
|
|
|
33
36
|
- (RCTScrollViewComponentView *_Nullable)findScrollView:(UIView *_Nullable *_Nullable)outTopSibling;
|
|
34
37
|
|
|
35
38
|
/**
|
|
36
39
|
* Setup ScrollView pinning
|
|
37
40
|
* @param pinned Whether to pin the scroll view
|
|
41
|
+
* @param bottomInset Bottom content inset for the scroll view
|
|
38
42
|
*/
|
|
39
|
-
- (void)setupScrollViewPinning:(BOOL)pinned;
|
|
43
|
+
- (void)setupScrollViewPinning:(BOOL)pinned bottomInset:(CGFloat)bottomInset;
|
|
40
44
|
|
|
41
45
|
@end
|
|
42
46
|
|