import React, { useState, useEffect } from 'react'; import { SafeAreaView, View, StatusBar, Alert, ActivityIndicator, Text } from 'react-native'; import { useCameraPermissions } from 'expo-camera'; import * as ImagePicker from 'expo-image-picker'; import * as Haptics from 'expo-haptics'; import Header from './src/components/Header'; import Viewfinder from './src/components/Viewfinder'; import TabSelector, { TabId } from './src/components/TabSelector'; import AdjustmentPanel from './src/components/AdjustmentPanel'; import CameraControls from './src/components/CameraControls'; import PreviewModal from './src/components/PreviewModal'; import { Recipe, ColorAdjustments, FrameId, GPSInfo } from './src/types'; import { getAllRecipes, saveCustomRecipe, deleteCustomRecipe } from './src/utils/storageUtils'; import { getCurrentGPS, requestLocationPermissions } from './src/utils/locationUtils'; import { processAndExportPhoto } from './src/utils/exportEngine'; // @ts-ignore import './global.css'; export default function App() { const [mode, setMode] = useState<'camera' | 'library'>('camera'); const [recipes, setRecipes] = useState([]); const [selectedRecipe, setSelectedRecipe] = useState(null); // Custom temporary overrides for the sliders const [adjustments, setAdjustments] = useState({ exposure: 0, contrast: 0, saturation: 0, temperature: 5500, tint: 0, highlight: 0, shadow: 0, denoise: 0, clarity: 0, grain: 0, colorChrome: 'none', }); const [selectedFrame, setSelectedFrame] = useState('none'); const [useGeotag, setUseGeotag] = useState(true); const [gpsInfo, setGpsInfo] = useState(null); const [activeTab, setActiveTab] = useState('recipes'); const [libraryImageUri, setLibraryImageUri] = useState(null); const [lastPhotoUri, setLastPhotoUri] = useState(null); const [previewVisible, setPreviewVisible] = useState(false); const [isProcessing, setIsProcessing] = useState(false); // Hardware Permissions const [cameraPermission, requestCameraPermission] = useCameraPermissions(); useEffect(() => { loadRecipes(); loadGPS(); }, []); const loadRecipes = async () => { const list = await getAllRecipes(); setRecipes(list); if (list.length > 0) { applyRecipe(list[0]); } }; const loadGPS = async () => { const info = await getCurrentGPS(); if (info) { setGpsInfo(info); } }; const applyRecipe = (recipe: Recipe) => { setSelectedRecipe(recipe); setAdjustments(recipe.adjustments); setSelectedFrame(recipe.frameId || 'none'); setUseGeotag(recipe.useGeotag); }; const handleUpdateAdjustments = (updates: Partial) => { setAdjustments((prev) => { const next = { ...prev, ...updates }; if (selectedRecipe) { setSelectedRecipe({ ...selectedRecipe, adjustments: next, }); } return next; }); }; const handleUpdateFrame = (frameId: FrameId) => { setSelectedFrame(frameId); if (selectedRecipe) { setSelectedRecipe({ ...selectedRecipe, frameId, }); } }; const handleToggleGeotag = async (enabled: boolean) => { setUseGeotag(enabled); if (selectedRecipe) { setSelectedRecipe({ ...selectedRecipe, useGeotag: enabled, }); } if (enabled && !gpsInfo) { const locationGranted = await requestLocationPermissions(); if (locationGranted) { loadGPS(); } else { Alert.alert('Permission Denied', 'Enable location services to use GPS Watermark.'); setUseGeotag(false); } } }; const handleSaveRecipe = async (name: string) => { if (!selectedRecipe) return; const newRecipe = await saveCustomRecipe({ name, baseFilter: selectedRecipe.baseFilter, adjustments, frameId: selectedFrame, useGeotag, }); const list = await getAllRecipes(); setRecipes(list); applyRecipe(newRecipe); Alert.alert('Recipe Saved', `"${name}" has been created.`); }; const handleDeleteRecipe = async (id: string) => { await deleteCustomRecipe(id); const list = await getAllRecipes(); setRecipes(list); if (selectedRecipe?.id === id && list.length > 0) { applyRecipe(list[0]); } }; const handlePickImage = async () => { const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, allowsEditing: false, quality: 1, }); if (!result.canceled && result.assets && result.assets.length > 0) { setLibraryImageUri(result.assets[0].uri); setMode('library'); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); } }; const handleCapture = async () => { if (mode === 'camera') { // For simulator & simplicity, if camera view isn't fully mocked, we use a placeholder or trigger camera picture. // In this setup, we simulate taking a photo or request library image if running in simulator. // Let's create a beautiful simulation that pulls a mock asset or lets you select one if in simulator. Alert.alert( 'Capture Mode', 'Camera capture requires native device hardware. For testing on emulators, please use the LIBRARY mode to load a photo and apply filters.', [{ text: 'OK' }] ); } else { // Library mode capture/export: processes selected image with recipe, overlays, text if (!libraryImageUri || !selectedRecipe) { Alert.alert('Error', 'Please select an image from the library first.'); return; } setIsProcessing(true); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); const exportedUri = await processAndExportPhoto( libraryImageUri, { ...selectedRecipe, adjustments, frameId: selectedFrame, useGeotag, }, selectedFrame, useGeotag, gpsInfo ); setIsProcessing(false); if (exportedUri) { setLastPhotoUri(exportedUri); setPreviewVisible(true); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); } else { Alert.alert('Failed', 'Error occurred while processing image.'); } } }; return ( {/* Top Header */}
{/* Main Viewfinder Frame */} {/* Selector Tabs */} {/* Adjustments Panel */} {/* Bottom Camera Buttons */} setPreviewVisible(true)} isLibraryMode={mode === 'library' && !libraryImageUri} /> {/* Preview Modal */} setPreviewVisible(false)} photoUri={lastPhotoUri} recipeName={selectedRecipe?.name || 'Custom'} /> {/* Global Processing Loader Overlay */} {isProcessing && ( RENDERING PHOTO... )} ); }