Files
orca/mobile/src/components/BottomDrawer.tsx
T
Brennan Benson 78771646af fix(mobile): show one review sheet at a time so the review screen never freezes (#22951)
* fix(mobile): close the review sheet before opening Send Notes

On iPhone, Review Actions > Send Unsent Notes and Review Complete > Send Notes
opened the Send Notes sheet while their own sheet was still on screen. iOS
cannot present a second native sheet until the first has unmounted, so Send
Notes never appeared and every later tap on the review screen was swallowed
until the app restarted. Send Unsent Notes now uses the action sheet's
closeBeforePress, and the Review Complete drawer opens Send Notes from its
onAfterClose, the same sequencing the action sheet already uses.

* test(mobile): drive Send Unsent Notes through the real action sheet

The overflow test only checked the closeBeforePress flag, and nothing tested that the
action sheet actually defers such an action until it has closed. Press the real row and
assert Send Notes opens only from the sheet's after-close callback.

* fix(mobile): ignore a close request on a drawer that is already hiding

Android Back during a drawer's close animation restarted the hide, which
cancelled it, so the drawer never unmounted and its invisible Modal kept
swallowing every tap. Send Notes, which now opens after the review sheet
closes, never appeared either.

* refactor(mobile): give the review screen one sheet state so sheets cannot stack

The review screen kept five independent sheet flags that any caller could
set at any time. iOS cannot present a sheet while another is still on
screen (even mid-close), so any overlap froze every tap. Two openers could
still produce one: Review Complete appearing after the mark-reviewed save
landed on a sheet opened meanwhile, and the Send Notes list reopening a
sheet the user had already dismissed.

The five flags become one reducer that mounts at most one sheet. Switching
sheets closes the current one and shows the next only after its drawer
reports it has finished closing; Review Complete waits for the user's
sheet instead of closing it; a late send list only fills a Send Notes that
is still shown or queued. The two per-call-site sequencers this branch
added are removed in favour of it.

* test(mobile): repin the RPC goldens for the review sheet state

The review-actions recording adapter now drives the screen's sheet reducer
instead of the removed Send Notes setter, which moves adapterSha256 on the
14 goldens recorded through it. baseline is repinned to the refactor commit
so the recorder's product fence matches; no recorded body changed (only the
baseline and adapterSha256 header fields move).

* fix(mobile): settle a review sheet that closed before it was ever shown

A drawer mounts only once a commit shows it, so a sheet closed or displaced in
the same batch it opened in (e.g. Review Complete landing in the same frame as
a tap that opens another sheet) never sends onAfterClose. The sheet state then
waited on it forever and every later sheet on the screen stayed queued.

Track which sheet's drawer a commit actually showed and settle a closing sheet
that never reached the screen instead of waiting for a close it cannot send.

* fix(mobile): keep a queued Send Notes when a late Review Complete lands

A second Mark Reviewed save resolving while Review Complete was closing
toward Send Notes reopened Review Complete and dropped the Send Notes the
user had just asked for. A background opener now yields to any queued
user sheet, including behind a closing sheet of its own kind.

* refactor(mobile): present review sheets through one keyed drawer

iOS cannot present a native Modal while another is still presented, even
during its close animation. The review screen now renders all five sheets
through one KeyedBottomDrawer that alone decides what is presented: a
request for a different sheet hides the current one, and the next is
mounted only after its hide finished and a commit without any Modal has
landed. A request replaced before it was shown is never mounted.

The screen's sheet state now records only what the user asked for
(`requested`, plus a background Review Complete in `deferred`), so the
presented-sheet bookkeeping, the per-drawer close callbacks and the
screen-side mounted-sheet inference are gone.

BottomDrawer becomes a constant-key adapter over the same drawer, so the
app has one mount/close lifecycle. A hide that finished just before a
reopen is now ignored instead of latching, which used to swallow the next
close and leave an invisible Modal eating taps.

* test(mobile): repin the RPC goldens for the keyed review drawer

The review action adapter now drives the requested-sheet state, which
moves that family's adapterSha256 on its 14 goldens; the repin to the
keyed-drawer commit moves `baseline` on all 787. No golden body moved.
2026-09-27 21:38:29 -07:00

39 lines
1.3 KiB
TypeScript

import type { ReactNode } from 'react'
import { KeyedBottomDrawer } from './keyed-bottom-drawer'
type Props = {
visible: boolean
onClose: () => void
onAfterClose?: () => void
children: ReactNode
dragContentToDismiss?: boolean
contentScrollable?: boolean
// Why: smart-source (and similar) need a stable outer frame so a docked
// TextInput can sit above the keyboard while results reflow in flex space
// above it — content-sized sheets make that field ride every list change.
fillAvailable?: boolean
// Why: pin an outer content-sized sheet under an inner fill picker without
// letting it take touches, draw a second backdrop, or keyboard-lift.
interactive?: boolean
zIndex?: number
}
const SHOWN = 'shown'
const sheetKey = () => SHOWN
export function BottomDrawer({ visible, onClose, onAfterClose, children, ...drawerProps }: Props) {
// Why: hidden drawers are rendered by parent screens even while closed; the keyed drawer
// renders nothing until shown, which keeps Reanimated/Gesture setup out of hot paths.
return (
<KeyedBottomDrawer
{...drawerProps}
sheet={visible ? SHOWN : null}
sheetKey={sheetKey}
onClose={onClose}
onAfterClose={onAfterClose}
>
{() => children}
</KeyedBottomDrawer>
)
}