Move the flash control into the top bar
This commit is contained in:
@@ -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,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"
|
||||
|
||||
@@ -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. */}
|
||||
|
||||
Reference in New Issue
Block a user