diff --git a/CHANGELOG.md b/CHANGELOG.md index 69664f97..496fb7d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ - Sheet navigator: a sheet pushed while another is still dismissing is no longer dropped. ([#898](https://github.com/lodev09/react-native-true-sheet/pull/898) by [@lodev09](https://github.com/lodev09)) - Dismissing a sheet before its present finishes now always dismisses it and settles `present()`, instead of leaving the sheet open or `present()` hanging. On web, `present()`/`dismiss()` now resolve when the animation ends and ignore calls on an already presented or dismissed sheet, like native. ([#900](https://github.com/lodev09/react-native-true-sheet/pull/900) by [@lodev09](https://github.com/lodev09)) - `scrollableRef` now resolves scrollables that expose their scroll node through an imperative handle, such as LegendList. ([#914](https://github.com/lodev09/react-native-true-sheet/pull/914) by [@cpruijsen](https://github.com/cpruijsen)) +- A sheet hosted in a bottom-tab screen is now hidden when the stack enclosing the tabs pushes a screen, and restored on return, instead of covering the pushed screen. ([#910](https://github.com/lodev09/react-native-true-sheet/pull/910) by [@IjzerenHein](https://github.com/IjzerenHein)) ### 💥 Breaking changes diff --git a/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt b/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt index ce4fd604..a78803fe 100644 --- a/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +++ b/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt @@ -399,10 +399,12 @@ class TrueSheetView(private val reactContext: ThemedReactContext) : // For stacked sheets on the same screen, inherit parent's presenter screen tag. // If parent was hidden by screen navigation, this sheet is on a different screen. - val parentScreenTag = viewController.parentSheetView?.screensEventObserver?.presenterScreenTag ?: 0 + val parentObserver = viewController.parentSheetView?.screensEventObserver + val parentScreenTag = parentObserver?.presenterScreenTag ?: 0 val parentHiddenByScreen = viewController.parentSheetView?.viewController?.wasHiddenByScreen == true if (parentScreenTag != 0 && !parentHiddenByScreen) { presenterScreenTag = parentScreenTag + ancestorScreenTags = parentObserver?.ancestorScreenTags ?: emptySet() } else { capturePresenterScreenFromView(this@TrueSheetView) } diff --git a/android/src/main/java/com/lodev09/truesheet/core/RNScreensEventObserver.kt b/android/src/main/java/com/lodev09/truesheet/core/RNScreensEventObserver.kt index 0c9f6651..da290613 100644 --- a/android/src/main/java/com/lodev09/truesheet/core/RNScreensEventObserver.kt +++ b/android/src/main/java/com/lodev09/truesheet/core/RNScreensEventObserver.kt @@ -22,6 +22,15 @@ class RNScreensEventObserver : EventDispatcherListener { private var eventDispatcher: EventDispatcher? = null var presenterScreenTag: Int = 0 + // Screens enclosing the presenter. react-native-screens only dispatches lifecycle + // events for stack screens, so a presenter inside a plain screen container (e.g. a + // bottom tab) never receives them and an enclosing stack screen has to stand in. + var ancestorScreenTags: Set = emptySet() + + // Only the screen that hid the sheet brings it back. An enclosing screen reappearing + // must not reveal a sheet whose own screen is still covered inside it. + private var disappearedScreenTag: Int = 0 + fun startObserving(dispatcher: EventDispatcher?) { if (eventDispatcher != null || dispatcher == null) return @@ -36,24 +45,40 @@ class RNScreensEventObserver : EventDispatcherListener { fun capturePresenterScreenFromView(view: View?) { presenterScreenTag = 0 + val ancestors = mutableSetOf() var current: View? = view while (current != null) { if (isScreenView(current)) { - presenterScreenTag = current.id - break + if (presenterScreenTag == 0) { + presenterScreenTag = current.id + } else { + ancestors.add(current.id) + } } current = (current.parent as? View) } + + ancestorScreenTags = ancestors } override fun onEventDispatch(event: Event<*>) { - // Only process events for the presenter screen - if (presenterScreenTag == 0 || event.viewTag != presenterScreenTag) return + // Only process events for the presenter screen and the screens enclosing it + if (presenterScreenTag == 0) return + if (event.viewTag != presenterScreenTag && event.viewTag !in ancestorScreenTags) return when (event.eventName) { - "topWillDisappear" -> delegate?.presenterScreenWillDisappear() - "topWillAppear" -> delegate?.presenterScreenWillAppear() + "topWillDisappear" -> { + if (disappearedScreenTag != 0) return + disappearedScreenTag = event.viewTag + delegate?.presenterScreenWillDisappear() + } + + "topWillAppear" -> { + if (event.viewTag != disappearedScreenTag) return + disappearedScreenTag = 0 + delegate?.presenterScreenWillAppear() + } } } diff --git a/example/expo/app/repro/_layout.tsx b/example/expo/app/repro/_layout.tsx index 433752ed..3f6cd3b0 100644 --- a/example/expo/app/repro/_layout.tsx +++ b/example/expo/app/repro/_layout.tsx @@ -23,6 +23,9 @@ export default function ReproLayout() { /> + + + ); } diff --git a/example/expo/app/repro/nested/_layout.tsx b/example/expo/app/repro/nested/_layout.tsx new file mode 100644 index 00000000..5850eb0f --- /dev/null +++ b/example/expo/app/repro/nested/_layout.tsx @@ -0,0 +1,17 @@ +import { Stack } from 'expo-router'; + +import { DARK_BLUE } from '@example/shared/utils'; + +export default function ReproNestedLayout() { + return ( + + + + + ); +} diff --git a/example/expo/app/repro/nested/details.tsx b/example/expo/app/repro/nested/details.tsx new file mode 100644 index 00000000..47d7e55f --- /dev/null +++ b/example/expo/app/repro/nested/details.tsx @@ -0,0 +1,19 @@ +import { useRouter } from 'expo-router'; + +import { Button } from '@example/shared/components'; + +import { ReproScreen } from '../../../components/ReproScreen'; + +export default function ReproNestedDetails() { + const router = useRouter(); + + return ( + +