fix: stream actions not working
Multiple sheets compete with each other and cause silent failure on ios.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Dimensions,
|
Dimensions,
|
||||||
KeyboardAvoidingView,
|
KeyboardAvoidingView,
|
||||||
@@ -30,6 +30,13 @@ const ANIMATION_MS = 240;
|
|||||||
interface BottomSheetProps {
|
interface BottomSheetProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
/**
|
||||||
|
* Fires after the close animation completes and the underlying Modal has
|
||||||
|
* unmounted. Use this to chain a follow-up sheet/Alert without stacking
|
||||||
|
* iOS Modals — presenting a second Modal while another is still mounted
|
||||||
|
* silently fails on iOS and leaves the app looking frozen.
|
||||||
|
*/
|
||||||
|
onClosed?: () => void;
|
||||||
/** When true, wrap content in KeyboardAvoidingView so the sheet floats above the keyboard. */
|
/** When true, wrap content in KeyboardAvoidingView so the sheet floats above the keyboard. */
|
||||||
avoidKeyboard?: boolean;
|
avoidKeyboard?: boolean;
|
||||||
/**
|
/**
|
||||||
@@ -49,6 +56,7 @@ interface BottomSheetProps {
|
|||||||
export function BottomSheet({
|
export function BottomSheet({
|
||||||
open,
|
open,
|
||||||
onClose,
|
onClose,
|
||||||
|
onClosed,
|
||||||
avoidKeyboard = false,
|
avoidKeyboard = false,
|
||||||
maxHeight = "85%",
|
maxHeight = "85%",
|
||||||
children,
|
children,
|
||||||
@@ -58,6 +66,18 @@ export function BottomSheet({
|
|||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
const translateY = useSharedValue(SCREEN_HEIGHT);
|
const translateY = useSharedValue(SCREEN_HEIGHT);
|
||||||
|
|
||||||
|
// Latest onClosed in a ref so the worklet→JS bridge always invokes the
|
||||||
|
// current callback even if the parent re-rendered with a new closure.
|
||||||
|
const onClosedRef = useRef(onClosed);
|
||||||
|
useEffect(() => {
|
||||||
|
onClosedRef.current = onClosed;
|
||||||
|
}, [onClosed]);
|
||||||
|
|
||||||
|
const handleClosed = useCallback(() => {
|
||||||
|
setMounted(false);
|
||||||
|
onClosedRef.current?.();
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
@@ -73,7 +93,7 @@ export function BottomSheet({
|
|||||||
SCREEN_HEIGHT,
|
SCREEN_HEIGHT,
|
||||||
{ duration: ANIMATION_MS, easing: Easing.in(Easing.cubic) },
|
{ duration: ANIMATION_MS, easing: Easing.in(Easing.cubic) },
|
||||||
(finished) => {
|
(finished) => {
|
||||||
if (finished) runOnJS(setMounted)(false);
|
if (finished) runOnJS(handleClosed)();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useState } from "react";
|
||||||
import { Pressable, Text, View } from "react-native";
|
import { Pressable, Text, View } from "react-native";
|
||||||
import {
|
import {
|
||||||
CircleCheckBig,
|
CircleCheckBig,
|
||||||
@@ -33,13 +34,28 @@ export function StreamActionsSheet({
|
|||||||
isCreator,
|
isCreator,
|
||||||
canDeleteParticle,
|
canDeleteParticle,
|
||||||
}: StreamActionsSheetProps) {
|
}: StreamActionsSheetProps) {
|
||||||
|
// Hold the picked action until the sheet's Modal has fully unmounted, then
|
||||||
|
// dispatch. Follow-ups like rename/members open another Modal and "delete
|
||||||
|
// particle" shows an Alert — both are iOS Modals, and iOS will not present
|
||||||
|
// a second Modal while another is still on screen. Without this defer, the
|
||||||
|
// tap appears to do nothing and the app feels frozen behind a phantom
|
||||||
|
// overlay until the close animation finishes.
|
||||||
|
const [pending, setPending] = useState<StreamActionId | null>(null);
|
||||||
|
|
||||||
const choose = (id: StreamActionId) => {
|
const choose = (id: StreamActionId) => {
|
||||||
|
setPending(id);
|
||||||
onClose();
|
onClose();
|
||||||
onSelect(id);
|
};
|
||||||
|
|
||||||
|
const handleClosed = () => {
|
||||||
|
if (pending) {
|
||||||
|
onSelect(pending);
|
||||||
|
setPending(null);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomSheet open={open} onClose={onClose}>
|
<BottomSheet open={open} onClose={onClose} onClosed={handleClosed}>
|
||||||
<View className="py-2">
|
<View className="py-2">
|
||||||
<ActionRow
|
<ActionRow
|
||||||
icon={
|
icon={
|
||||||
|
|||||||
Reference in New Issue
Block a user