import { useState } from 'react'; import { Platform, StyleSheet, Text, View } from 'react-native'; import { useCameraPermissions } from 'expo-camera'; import PrimaryButton from '../components/PrimaryButton'; import SecondaryButton from '../components/SecondaryButton'; import ManualSearchInput from '../components/form/ManualSearchInput'; import AppHeader from '../components/layout/AppHeader'; import ScreenContainer from '../components/layout/ScreenContainer'; import SectionTitle from '../components/layout/SectionTitle'; import ProductCard from '../components/product/ProductCard'; import BarcodeScannerView from '../components/scan/BarcodeScannerView'; import ScanErrorModal from '../components/scan/ScanErrorModal'; import { searchProducts } from '../services/productApi'; import { calculateProductRating } from '../services/ratingService'; import { colors } from '../utils/constants'; export default function ScanScreen({ preferences, productCache, onScanBarcode, onOpenProduct }) { const [permission, requestPermission] = useCameraPermissions(); const [isScanning, setIsScanning] = useState(true); const [isLoadingProduct, setIsLoadingProduct] = useState(false); const [scanError, setScanError] = useState(''); const [isErrorModalVisible, setIsErrorModalVisible] = useState(false); const [manualSearchQuery, setManualSearchQuery] = useState(''); const [manualResults, setManualResults] = useState([]); const [isSearching, setIsSearching] = useState(false); async function handleBarcodeScanned(event) { if (!isScanning) return; setIsScanning(false); setIsLoadingProduct(true); let result; try { result = await onScanBarcode(event.data); } catch (error) { result = { status: 'error', errorMessage: 'Das Produkt konnte gerade nicht geladen werden.' }; } finally { setIsLoadingProduct(false); } if (result?.status !== 'found') { setScanError(result?.errorMessage || 'Der Barcode konnte nicht verarbeitet werden.'); setIsErrorModalVisible(true); } } async function handleManualSearch() { if (!manualSearchQuery.trim() || isSearching) return; setIsSearching(true); try { const results = await searchProducts(manualSearchQuery, productCache, preferences.offlineModeEnabled); setManualResults(results); } finally { setIsSearching(false); } } function retryScan() { setIsErrorModalVisible(false); setScanError(''); setIsScanning(true); } const hasCameraAccess = permission?.granted && Platform.OS !== 'web'; return ( {!permission?.granted && Platform.OS !== 'web' ? ( Kamera-Berechtigung benötigt Shoppy braucht die Kamera, um Barcodes direkt im Laden zu erfassen. ) : null} {hasCameraAccess ? ( <> setIsScanning((current) => !current)} /> ) : ( Scanner-Simulation Auf Web oder ohne Kamera kannst du Open-Food-Facts-Barcodes testen. {isLoadingProduct ? Produkt wird geladen... : null} handleBarcodeScanned({ data: '3017620422003' })} /> handleBarcodeScanned({ data: '737628064502' })} /> )} Manuelle Suche {isSearching ? Suche läuft... : null} {!isSearching && manualSearchQuery && manualResults.length === 0 ? ( Keine Treffer gefunden. ) : null} {manualResults.map((product) => ( onOpenProduct(product)} /> ))} setIsErrorModalVisible(false)} onRetry={retryScan} visible={isErrorModalVisible} /> ); } const styles = StyleSheet.create({ permissionBox: { backgroundColor: colors.surface, borderColor: colors.border, borderRadius: 8, borderWidth: 1, gap: 12, padding: 16 }, permissionTitle: { color: colors.text, fontSize: 18, fontWeight: '800' }, permissionCopy: { color: colors.muted, fontSize: 15, lineHeight: 21 }, demoRow: { gap: 10 }, note: { color: colors.muted, fontSize: 14 } });