Move the flash control into the top bar

This commit is contained in:
2026-09-16 18:21:14 +07:00
parent c40b5565d3
commit 13ad52a975
3 changed files with 52 additions and 57 deletions
+9 -9
View File
@@ -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')}
+1 -47
View File
@@ -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 (
<View>
{/* 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 ? (
<View className="px-7 pb-3">
<TouchableOpacity
onPress={onCycleFlash}
accessibilityRole="button"
accessibilityLabel={`Flash ${flashMode}`}
accessibilityHint="Cycles flash off, auto and on"
className="w-12 h-12 rounded-full bg-black/40 border border-zinc-700/70 flex items-center justify-center"
activeOpacity={0.7}
>
{flashMode === 'off' ? (
<ZapOff size={20} color="#e4e4e7" strokeWidth={1.75} />
) : (
// AUTO keeps the bolt dim and adds the badge below — a lit bolt
// alone would read as ON.
<Zap size={20} color={flashMode === 'on' ? '#fbbf24' : '#e4e4e7'} strokeWidth={1.75} />
)}
{flashMode === 'auto' ? (
<Text style={FLASH_A_BADGE}>A</Text>
) : null}
</TouchableOpacity>
</View>
) : null}
<View
style={{ paddingBottom: insets.bottom + 10 }}
className="flex-row items-center px-7 pt-2"
+42 -1
View File
@@ -1,9 +1,21 @@
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, TextInput, Modal, KeyboardAvoidingView, Platform, Alert } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Plus, Pencil, Undo2 } from 'lucide-react-native';
import { Plus, Pencil, Undo2, Zap, ZapOff } from 'lucide-react-native';
import * as Haptics from 'expo-haptics';
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: 3,
right: 5,
fontSize: 8,
fontWeight: '700' as const,
color: '#f4f4f5',
};
interface TopBarProps {
mode: 'camera' | 'library';
recipeName: string;
@@ -21,6 +33,11 @@ interface TopBarProps {
// drawn: the icon turns amber while there is a step to go back to.
onUndo: () => 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({
</View>
<View className="flex-row items-center space-x-2">
{/* 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 ? (
<TouchableOpacity
onPress={onCycleFlash}
accessibilityRole="button"
accessibilityLabel={`Flash ${flashMode}`}
accessibilityHint="Cycles flash off, auto and on"
className="rounded-full border border-zinc-700/80 bg-black/45 p-2.5"
activeOpacity={0.7}
>
{flashMode === 'off' ? (
<ZapOff size={13} color="#e4e4e7" />
) : (
// AUTO keeps the bolt dim and adds the badge below — a lit
// bolt alone would read as ON.
<Zap size={13} color={flashMode === 'on' ? '#fbbf24' : '#e4e4e7'} />
)}
{flashMode === 'auto' ? <Text style={FLASH_A_BADGE}>A</Text> : null}
</TouchableOpacity>
) : 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. */}