Keeps the fix — gesture handlers now read current props — but makes the
class of bug structurally impossible rather than patched case by case.
The PanResponder is created once, so its handlers closed over the props of
the render that built it. Four call sites had to be corrected individually,
and nothing stopped a fifth from being added stale. Now the decisions live
in swipe-gesture.ts as pure functions taking the config as an argument, so
there is nothing left for a handler to capture; the component keeps one
`latest` ref pointing at the current props and hands it over on each event.
Also:
- Replace the placeholder test, which asserted that assigning to a locally
declared `{current}` object works and passed against the unfixed
component, with 20 tests over the real gesture logic: claim/decline,
overshoot clamping, commit thresholds, flick velocity, reveal open/close
from an offset base, and the 'none' cases.
- Build the responder in a useMemo instead of re-running PanResponder.create
on every render of every visible row and discarding the result.
- Fold the four repeated Animated.spring calls into springTo/settle.
Beyond the reported symptom, the same staleness broke two more cases:
entering selection mode passes 'none' for both actions, but a swipe still
fired the old action; and onAction captured a handleSwipeAction from before
the mailbox ids resolved, so on a slow connection swipe silently did nothing
without anyone touching the setting — which matches the deeper problem
Arnaud suspected on the PR.
Follow-up to the direct-view fix. No behaviour change on the happy path,
but the handoff is now easier to reason about and covered by tests.
- Use `File.contentUri` from the expo-file-system 19 API instead of the
legacy `getContentUriAsync`, dropping the `expo-file-system/legacy`
import. Same FileProvider, one less round trip through the bridge.
- Pull the intent launch into `openWithViewer`, which returns false when
the handoff didn't happen, so the fallback reads as a decision rather
than as an empty catch. It also no longer swallows the reason silently.
- Rewrite the expo-sharing comment above `shareAttachment`: it warns
against pre-translating to a content URI, which is still true for every
`Sharing.shareAsync` call but is exactly what the new VIEW path has to
do. Note why `downloadAttachment` keeps the share sheet as well.
- Record why FLAG_ACTIVITY_NEW_TASK is deliberately absent: the module
launches via startActivityForResult, which a new task would cancel.
The dot added in #27 is positioned with `top: '50%'`, so it centres on the
whole row. Rows are three lines tall (sender / subject / preview), so the dot
lands a full line below the checkbox and the avatar it reads as a column
with — the taller the row, the worse it looks.
Anchor it on the first line instead: row padding + half an avatar, following
the density (and the text line height when the avatar is hidden in
extra-compact). The webmail's list carries the same `top-1/2` and the same
drift.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FTRyrFCR2N9qkpKK37GgWb
Fixes#32.
On Android, Sharing.shareAsync triggers Intent.ACTION_SEND (Share sheet). Updated shareAttachment to convert the file URI to a content URI and launch android.intent.action.VIEW via expo-intent-launcher, with fallback to shareAsync if no viewer application is installed for the MIME type.
Fixes#30.
PanResponder in SwipeableRow was created once on mount and captured initial props in a stale closure. Using mutable refs ensures gesture handlers always read and execute the latest leftAction, rightAction, mode, and onAction props.