diff --git a/src/lib/__tests__/files-back.test.ts b/src/lib/__tests__/files-back.test.ts new file mode 100644 index 00000000..07eaa993 --- /dev/null +++ b/src/lib/__tests__/files-back.test.ts @@ -0,0 +1,23 @@ +import { describe, it, expect } from 'vitest'; +import { filesBackStep } from '../files-back'; + +describe('filesBackStep', () => { + it('leaves the tab only at the root with nothing open', () => { + expect(filesBackStep({ previewOpen: false, selecting: false, folderDepth: 0 })).toBe('leave'); + }); + + it('goes up one folder inside a subfolder', () => { + expect(filesBackStep({ previewOpen: false, selecting: false, folderDepth: 1 })).toBe('folder-up'); + expect(filesBackStep({ previewOpen: false, selecting: false, folderDepth: 3 })).toBe('folder-up'); + }); + + it('ends selection mode before changing folders', () => { + expect(filesBackStep({ previewOpen: false, selecting: true, folderDepth: 2 })).toBe('clear-selection'); + expect(filesBackStep({ previewOpen: false, selecting: true, folderDepth: 0 })).toBe('clear-selection'); + }); + + it('closes an open preview before anything else', () => { + expect(filesBackStep({ previewOpen: true, selecting: true, folderDepth: 2 })).toBe('close-preview'); + expect(filesBackStep({ previewOpen: true, selecting: false, folderDepth: 0 })).toBe('close-preview'); + }); +}); diff --git a/src/lib/files-back.ts b/src/lib/files-back.ts new file mode 100644 index 00000000..0730e452 --- /dev/null +++ b/src/lib/files-back.ts @@ -0,0 +1,19 @@ +/** What Android back does in the Files tab. */ +export type FilesBackStep = 'close-preview' | 'clear-selection' | 'folder-up' | 'leave'; + +/** + * Back works through the tab's own layers, innermost first: an open preview + * closes, then selection mode ends (like the header's X), then the folder + * goes up one level. Only at the root with nothing open does it `leave`, + * i.e. fall through to navigation, which switches to the Mail tab. + */ +export function filesBackStep(state: { + previewOpen: boolean; + selecting: boolean; + folderDepth: number; +}): FilesBackStep { + if (state.previewOpen) return 'close-preview'; + if (state.selecting) return 'clear-selection'; + if (state.folderDepth > 0) return 'folder-up'; + return 'leave'; +} diff --git a/src/screens/FilesScreen.tsx b/src/screens/FilesScreen.tsx index 5ecce550..afb47fdd 100644 --- a/src/screens/FilesScreen.tsx +++ b/src/screens/FilesScreen.tsx @@ -53,6 +53,7 @@ import { downloadAttachment, shareAttachment } from '../lib/email-export'; import { secureFetch } from '../lib/client-cert'; import { getUniqueName } from '../lib/filenode-name'; import { useBackWhileFocused } from '../lib/use-back-while-focused'; +import { filesBackStep } from '../lib/files-back'; import type { FileNode } from '../api/types'; import { spacing, radius, typography, type ThemePalette } from '../theme/tokens'; import { useColors } from '../theme/colors'; @@ -325,10 +326,6 @@ export default function FilesScreen() { const clearSelection = useCallback(() => setSelection(new Set()), []); - // Android back leaves selection mode first, like the header's X, instead - // of switching to the Mail tab. - useBackWhileFocused(selectionMode, clearSelection); - // Shared-with-me rows stay out of multi-select: batch delete routes to our // own account and would fail (or worse, mismatch) on namespaced ids. const toggleSelect = useCallback((row: FileRow) => { @@ -459,6 +456,20 @@ export default function FilesScreen() { clearSelection(); }, [clearSelection]); + // Android back closes the preview, then leaves selection mode, then goes + // up one folder; only at the root does it switch to the Mail tab. + const backStep = filesBackStep({ + previewOpen: previewTarget != null, + selecting: selectionMode, + folderDepth: path.length, + }); + const handleBack = useCallback(() => { + if (backStep === 'close-preview') setPreviewTarget(null); + else if (backStep === 'clear-selection') clearSelection(); + else if (backStep === 'folder-up') goBack(); + }, [backStep, clearSelection, goBack]); + useBackWhileFocused(backStep !== 'leave', handleBack); + const toggleView = useCallback(() => { const next: FilesViewMode = viewMode === 'list' ? 'grid' : 'list'; setViewOverride(next);