From 13ad52a975d50ad7c194fb0cf9a16c48d33870d3 Mon Sep 17 00:00:00 2001 From: locphamtran Date: Wed, 16 Sep 2026 18:21:14 +0700 Subject: [PATCH] Move the flash control into the top bar --- App.tsx | 18 ++++++------ src/components/CameraControls.tsx | 48 +------------------------------ src/components/TopBar.tsx | 43 ++++++++++++++++++++++++++- 3 files changed, 52 insertions(+), 57 deletions(-) diff --git a/App.tsx b/App.tsx index 52cc9de..5b6506c 100644 --- a/App.tsx +++ b/App.tsx @@ -1807,6 +1807,15 @@ export default function App() { onExportShare={handleExportShare} onUndo={handleUndo} undoDepth={undoDepth} + // Capture-time LED flash: offered only on a lens that carries a flash + // unit, and only in camera mode (the library save never fires the + // camera). Tap cycles OFF -> AUTO -> ON -> OFF. + flashMode={mode === 'camera' && flashAvailable ? flashMode : undefined} + onCycleFlash={ + mode === 'camera' && flashAvailable + ? () => setFlashMode((m) => (m === 'off' ? 'auto' : m === 'auto' ? 'on' : 'off')) + : undefined + } onHeight={setTopInset} /> @@ -1901,15 +1910,6 @@ export default function App() { onPickImage={handlePickImage} onOpenPreview={openLatestViewer} onOpenSettings={() => setSettingsVisible(true)} - // Capture-time LED flash: offered only on a lens that carries a flash - // unit, and only in camera mode (the library save never fires the - // camera). Tap cycles OFF -> AUTO -> ON -> OFF. - flashMode={mode === 'camera' && flashAvailable ? flashMode : undefined} - onCycleFlash={ - mode === 'camera' && flashAvailable - ? () => setFlashMode((m) => (m === 'off' ? 'auto' : m === 'auto' ? 'on' : 'off')) - : undefined - } onFlipCamera={() => setCameraFacing((f) => (f === 'back' ? 'front' : 'back'))} isLibraryMode={mode === 'library'} onSwitchToCamera={() => handleSetMode('camera')} diff --git a/src/components/CameraControls.tsx b/src/components/CameraControls.tsx index 390b4f0..cfe1fde 100644 --- a/src/components/CameraControls.tsx +++ b/src/components/CameraControls.tsx @@ -1,18 +1,7 @@ import React from 'react'; import { View, Text, TouchableOpacity, Image, StyleSheet } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { Image as ImageIcon, Eye, Download, Camera, Settings, RefreshCw, Zap, ZapOff } from 'lucide-react-native'; -import type { FlashMode } from '../types'; - -// "A" badge tucked into the corner of the flash button's AUTO state. -const FLASH_A_BADGE = { - position: 'absolute' as const, - bottom: 6, - right: 12, - fontSize: 9, - fontWeight: '700' as const, - color: '#f4f4f5', -}; +import { Image as ImageIcon, Eye, Download, Camera, Settings, RefreshCw } from 'lucide-react-native'; interface CameraControlsProps { lastPhotoUri: string | null; @@ -24,10 +13,6 @@ interface CameraControlsProps { onPickImage: () => void; onOpenPreview: () => void; onOpenSettings: () => void; - // LED flash state for the next still. Absent = no flash control to show - // (library mode, or a lens with no flash unit). - flashMode?: FlashMode; - onCycleFlash?: () => void; onFlipCamera?: () => void; isLibraryMode: boolean; onSwitchToCamera?: () => void; @@ -41,8 +26,6 @@ export default function CameraControls({ onPickImage, onOpenPreview, onOpenSettings, - flashMode, - onCycleFlash, onFlipCamera, isLibraryMode, onSwitchToCamera, @@ -51,35 +34,6 @@ export default function CameraControls({ return ( - {/* Capture-time LED flash: a button of its own above the control row - rather than a third button inside it. The two side zones are sized to - hold two 48dp buttons next to the shutter — a third would slide under - the shutter's hit area on a 360dp screen. Absent on a lens with no - flash unit, so the front camera shows no dead control. */} - {flashMode && onCycleFlash ? ( - - - {flashMode === 'off' ? ( - - ) : ( - // AUTO keeps the bolt dim and adds the badge below — a lit bolt - // alone would read as ON. - - )} - {flashMode === 'auto' ? ( - A - ) : null} - - - ) : null} - void; undoDepth: number; + // LED flash state for the next still, cycled off -> auto -> on. Absent = + // no flash control to show (library mode, or a lens with no flash unit), + // so the front camera shows no dead control. + flashMode?: FlashMode; + onCycleFlash?: () => void; // Height of the floating top strip (its own onLayout): App keeps the library // photo — and with it the ratio band — out from under it. onHeight?: (height: number) => void; @@ -35,6 +52,8 @@ export default function TopBar({ onExportShare, onUndo, undoDepth, + flashMode, + onCycleFlash, onHeight, }: TopBarProps) { const insets = useSafeAreaInsets(); @@ -94,6 +113,28 @@ export default function TopBar({ + {/* Capture-time LED flash, left of BACK: a still taken on the 0.5x + native lens goes through its own Camera2 request, so the state + has to be reachable while the chip row is off screen. */} + {flashMode && onCycleFlash ? ( + + {flashMode === 'off' ? ( + + ) : ( + // AUTO keeps the bolt dim and adds the badge below — a lit + // bolt alone would read as ON. + + )} + {flashMode === 'auto' ? A : null} + + ) : null} {/* BACK: the history control the chip row used to carry, sitting between the status block and SAVE & SHARE. Lucide (ISC) vector — no icon font to ship or license. */}