From 8c47831ef137bf1cf7b058274c07e229959e4b8e Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 4 Mar 2024 14:01:19 -0600 Subject: [PATCH] Fix android --- src/components/Dialog/index.tsx | 14 +++++++----- src/components/Dialog/index.web.tsx | 12 +++++----- src/state/dialogs/index.tsx | 34 +++++++++++++++++++++-------- src/view/shell/index.tsx | 16 +++++++++++++- 4 files changed, 54 insertions(+), 22 deletions(-) diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx index 88a5de3a4c..fa375b0f4c 100644 --- a/src/components/Dialog/index.tsx +++ b/src/components/Dialog/index.tsx @@ -15,7 +15,7 @@ import {useTheme, atoms as a, flatten} from '#/alf' import {Portal} from '#/components/Portal' import {createInput} from '#/components/forms/TextField' import {logger} from '#/logger' -import {useDialogStateContext} from '#/state/dialogs' +import {useDialogStateControlContext} from '#/state/dialogs' import { DialogOuterProps, @@ -82,7 +82,7 @@ export function Outer({ const hasSnapPoints = !!sheetOptions.snapPoints const insets = useSafeAreaInsets() const closeCallback = React.useRef<() => void>() - const {openDialogs} = useDialogStateContext() + const {setDialogIsOpen} = useDialogStateControlContext() /* * Used to manage open/closed, but index is otherwise handled internally by `BottomSheet` @@ -96,11 +96,11 @@ export function Outer({ const open = React.useCallback( ({index} = {}) => { - openDialogs.current.add(control.id) + setDialogIsOpen(control.id, true) // can be set to any index of `snapPoints`, but `0` is the first i.e. "open" setOpenIndex(index || 0) }, - [setOpenIndex, openDialogs, control.id], + [setOpenIndex, setDialogIsOpen, control.id], ) const close = React.useCallback(cb => { @@ -133,12 +133,12 @@ export function Outer({ closeCallback.current = undefined } - openDialogs.current.delete(control.id) + setDialogIsOpen(control.id, false) onClose?.() setOpenIndex(-1) } }, - [onClose, setOpenIndex, openDialogs, control.id], + [onClose, setOpenIndex, setDialogIsOpen, control.id], ) const context = React.useMemo(() => ({close}), [close]) @@ -149,6 +149,8 @@ export function Outer({ { setIsOpen(true) - openDialogs.current.add(control.id) - }, [setIsOpen, openDialogs, control.id]) + setDialogIsOpen(control.id, true) + }, [setIsOpen, setDialogIsOpen, control.id]) const close = React.useCallback(async () => { setIsVisible(false) await new Promise(resolve => setTimeout(resolve, 150)) setIsOpen(false) setIsVisible(true) - openDialogs.current.delete(control.id) + setDialogIsOpen(control.id, false) onClose?.() - }, [onClose, setIsOpen, openDialogs, control.id]) + }, [onClose, setIsOpen, setDialogIsOpen, control.id]) useImperativeHandle( control.ref, diff --git a/src/state/dialogs/index.tsx b/src/state/dialogs/index.tsx index 9fc70c1787..90aaca4f8b 100644 --- a/src/state/dialogs/index.tsx +++ b/src/state/dialogs/index.tsx @@ -13,20 +13,20 @@ const DialogContext = React.createContext<{ * The currently open dialogs, referenced by their IDs, generated from * `useId`. */ - openDialogs: React.MutableRefObject> + openDialogs: string[] }>({ activeDialogs: { current: new Map(), }, - openDialogs: { - current: new Set(), - }, + openDialogs: [], }) const DialogControlContext = React.createContext<{ closeAllDialogs(): boolean + setDialogIsOpen(id: string, isOpen: boolean): void }>({ closeAllDialogs: () => false, + setDialogIsOpen: () => {}, }) export function useDialogStateContext() { @@ -41,15 +41,31 @@ export function Provider({children}: React.PropsWithChildren<{}>) { const activeDialogs = React.useRef< Map> >(new Map()) - const openDialogs = React.useRef>(new Set()) + const [openDialogs, setOpenDialogs] = React.useState([]) const closeAllDialogs = React.useCallback(() => { activeDialogs.current.forEach(dialog => dialog.current.close()) - return openDialogs.current.size > 0 - }, []) + return openDialogs.length > 0 + }, [openDialogs]) - const context = React.useMemo(() => ({activeDialogs, openDialogs}), []) - const controls = React.useMemo(() => ({closeAllDialogs}), [closeAllDialogs]) + const setDialogIsOpen = React.useCallback( + (id: string, isOpen: boolean) => { + setOpenDialogs(prev => { + const filtered = prev.filter(dialogId => dialogId !== id) as string[] + return isOpen ? [...filtered, id] : filtered + }) + }, + [setOpenDialogs], + ) + + const context = React.useMemo( + () => ({activeDialogs, openDialogs}), + [openDialogs], + ) + const controls = React.useMemo( + () => ({closeAllDialogs, setDialogIsOpen}), + [closeAllDialogs, setDialogIsOpen], + ) return ( diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index d895d88517..bdba791741 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -30,6 +30,7 @@ import {useCloseAnyActiveElement} from '#/state/util' import * as notifications from 'lib/notifications/notifications' import {Outlet as PortalOutlet} from '#/components/Portal' import {MutedWordsDialog} from '#/components/dialogs/MutedWords' +import {useDialogStateContext} from '#/state/dialogs' function ShellInner() { const isDrawerOpen = useIsDrawerOpen() @@ -55,6 +56,7 @@ function ShellInner() { const closeAnyActiveElement = useCloseAnyActiveElement() // start undefined const currentAccountDid = React.useRef(undefined) + const {openDialogs} = useDialogStateContext() React.useEffect(() => { let listener = {remove() {}} @@ -78,9 +80,21 @@ function ShellInner() { } }, [currentAccount]) + /** + * The counterpart to `accessibilityViewIsModal` for Android. This property + * applies to the parent of all non-modal views, and prevents TalkBack from + * navigating within content beneath an open dialog. + * + * @see https://reactnative.dev/docs/accessibility#importantforaccessibility-android + */ + const importantForAccessibility = + openDialogs.length > 0 ? 'no-hide-descendants' : undefined + return ( <> - +