import React, { useState, useEffect, useRef } from 'react'; import { Tab, Plant, IdentificationResult, Language } from './types'; import { StorageService } from './services/storageService'; import { PlantRecognitionService } from './services/plantRecognitionService'; import { PlantDatabaseService } from './services/plantDatabaseService'; import { getTranslation } from './utils/translations'; import { TabBar } from './components/TabBar'; import { PlantCard } from './components/PlantCard'; import { PlantSkeleton } from './components/PlantSkeleton'; import { ResultCard } from './components/ResultCard'; import { PlantDetail } from './components/PlantDetail'; import { Toast } from './components/Toast'; import { Camera, Image as ImageIcon, HelpCircle, X, Settings as SettingsIcon, ScanLine, Leaf, Plus, Zap, Search, ArrowRight, ArrowLeft, Globe, ChevronDown, ChevronUp, Check, Cpu, BookOpen } from 'lucide-react'; const generateId = () => Math.random().toString(36).substr(2, 9); const App: React.FC = () => { const [activeTab, setActiveTab] = useState(Tab.HOME); const [plants, setPlants] = useState([]); const [isDarkMode, setIsDarkMode] = useState(false); const [language, setLanguage] = useState('de'); const [isLoadingPlants, setIsLoadingPlants] = useState(true); // Search State const [searchQuery, setSearchQuery] = useState(''); // Lexicon State const [isLexiconOpen, setIsLexiconOpen] = useState(false); const [lexiconSearchQuery, setLexiconSearchQuery] = useState(''); // Settings State const [isLanguageDropdownOpen, setIsLanguageDropdownOpen] = useState(false); // Scanner Modal State const [isScannerOpen, setIsScannerOpen] = useState(false); const [selectedImage, setSelectedImage] = useState(null); // Analysis State const [isAnalyzing, setIsAnalyzing] = useState(false); const [analysisProgress, setAnalysisProgress] = useState(0); const [analysisResult, setAnalysisResult] = useState(null); // Detail State const [selectedPlant, setSelectedPlant] = useState(null); // Toast State const [toast, setToast] = useState({ message: '', visible: false }); // Refs const fileInputRef = useRef(null); // Derived state for translations const t = getTranslation(language); useEffect(() => { const loadData = async () => { setIsLoadingPlants(true); await new Promise(resolve => setTimeout(resolve, 800)); setPlants(StorageService.getPlants()); setLanguage(StorageService.getLanguage()); setIsLoadingPlants(false); }; loadData(); if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { setIsDarkMode(true); document.documentElement.classList.add('dark'); } }, []); const toggleDarkMode = () => { setIsDarkMode(!isDarkMode); if (!isDarkMode) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } }; const changeLanguage = (lang: Language) => { setLanguage(lang); StorageService.saveLanguage(lang); setIsLanguageDropdownOpen(false); }; const handleImageSelect = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (file) { const reader = new FileReader(); reader.onloadend = () => { const base64String = reader.result as string; setSelectedImage(base64String); analyzeImage(base64String); }; reader.readAsDataURL(file); } }; const analyzeImage = async (imageUri: string) => { setIsAnalyzing(true); setAnalysisProgress(0); setAnalysisResult(null); // Simulate realistic progress const progressInterval = setInterval(() => { setAnalysisProgress(prev => { // Fast start if (prev < 30) return prev + Math.random() * 8; // Slower middle (processing) if (prev < 70) return prev + Math.random() * 2; // Stall at end (waiting for API) if (prev < 90) return prev + 0.5; return prev; }); }, 150); try { // Pass the current language to the service const result = await PlantRecognitionService.identify(imageUri, language); clearInterval(progressInterval); setAnalysisProgress(100); // Short delay to allow user to see 100% completion setTimeout(() => { setAnalysisResult(result); setIsAnalyzing(false); }, 500); } catch (error) { clearInterval(progressInterval); console.error("Analysis failed", error); alert("Fehler bei der Analyse."); setSelectedImage(null); setIsAnalyzing(false); } }; const showToast = (message: string) => { setToast({ message, visible: true }); }; const hideToast = () => { setToast(prev => ({ ...prev, visible: false })); }; const savePlant = () => { if (analysisResult && selectedImage) { const now = new Date().toISOString(); const newPlant: Plant = { id: generateId(), name: analysisResult.name, botanicalName: analysisResult.botanicalName, imageUri: selectedImage, dateAdded: now, careInfo: analysisResult.careInfo, lastWatered: now, wateringHistory: [now], // Initialize history with the creation date/first watering description: analysisResult.description, notificationsEnabled: false // Default off }; StorageService.savePlant(newPlant); setPlants(StorageService.getPlants()); closeScanner(); // Also close lexicon if open setIsLexiconOpen(false); showToast(t.plantAddedSuccess); } }; const closeScanner = () => { setIsScannerOpen(false); setSelectedImage(null); setAnalysisResult(null); setIsAnalyzing(false); setAnalysisProgress(0); if (fileInputRef.current) fileInputRef.current.value = ''; }; const openScanner = () => { setIsScannerOpen(true); }; const handlePlantClick = (plant: Plant) => { setSelectedPlant(plant); }; const closeDetail = () => { setSelectedPlant(null); }; const handleDeletePlant = (id: string) => { StorageService.deletePlant(id); setPlants(prev => prev.filter(p => p.id !== id)); closeDetail(); showToast(t.plantDeleted); }; const handleUpdatePlant = (updatedPlant: Plant) => { StorageService.updatePlant(updatedPlant); setPlants(prev => prev.map(p => p.id === updatedPlant.id ? updatedPlant : p)); setSelectedPlant(updatedPlant); showToast(t.wateredSuccess); }; // Lexicon Handling const handleLexiconItemClick = (item: any) => { // We treat this like a "Scan Result" for simplicity, reusing the ResultCard setAnalysisResult(item); setSelectedImage(item.imageUri); // Since ResultCard is rendered conditionally based on analysisResult && selectedImage, // we need to make sure the view is visible. // We will render ResultCard inside the Lexicon view if selected. }; const closeLexiconResult = () => { setAnalysisResult(null); setSelectedImage(null); }; // --- SCREENS --- const renderHome = () => (

{t.myPlants}

{/* Filters */}
{isLoadingPlants ? (
{[1, 2, 3, 4].map((i) => ( ))}
) : plants.length === 0 ? (

{t.noPlants}

) : (
{plants.map(plant => ( handlePlantClick(plant)} t={t} /> ))}
)} {/* FAB */}
); const renderSearch = () => { const filteredPlants = plants.filter(p => p.name.toLowerCase().includes(searchQuery.toLowerCase()) || p.botanicalName.toLowerCase().includes(searchQuery.toLowerCase()) ); const categories = [ { name: t.catCareEasy, color: "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" }, { name: t.catSucculents, color: "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400" }, { name: t.catLowLight, color: "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-400" }, { name: t.catPetFriendly, color: "bg-rose-100 text-rose-700 dark:bg-rose-900/30 dark:text-rose-400" }, { name: t.catAirPurifier, color: "bg-teal-100 text-teal-700 dark:bg-teal-900/30 dark:text-teal-400" }, { name: t.catFlowering, color: "bg-fuchsia-100 text-fuchsia-700 dark:bg-fuchsia-900/30 dark:text-fuchsia-400" }, ]; return (

{t.searchTitle}

setSearchQuery(e.target.value)} className="w-full bg-white dark:bg-stone-900 border border-stone-200 dark:border-stone-800 rounded-xl py-3 pl-12 pr-4 text-stone-900 dark:text-stone-100 focus:outline-none focus:ring-2 focus:ring-primary-500/50 placeholder:text-stone-400" /> {searchQuery && ( )}
{searchQuery ? (

{filteredPlants.length} {t.resultsInPlants}

{filteredPlants.length > 0 ? (
{filteredPlants.map(plant => ( handlePlantClick(plant)} t={t} /> ))}
) : (

{t.noResults}

)}
) : (

{t.categories}

{categories.map((cat) => ( ))}
setIsLexiconOpen(true)} className="mt-8 p-6 bg-gradient-to-br from-primary-600 to-primary-800 rounded-2xl text-white shadow-lg relative overflow-hidden cursor-pointer active:scale-[0.98] transition-transform" >

{t.lexiconTitle}

{t.lexiconDesc}

{t.browseLexicon}
)}
); }; const renderLexicon = () => { if (!isLexiconOpen) return null; // If we have a selected item from Lexicon, show ResultCard (Detail View) if (analysisResult && selectedImage) { return (
); } const lexiconPlants = PlantDatabaseService.searchPlants(lexiconSearchQuery, language); return (
{/* Header */}

{t.lexiconTitle}

{/* Content */}
{/* Search */}
setLexiconSearchQuery(e.target.value)} className="w-full bg-white dark:bg-stone-900 border border-stone-200 dark:border-stone-800 rounded-xl py-3 pl-12 pr-4 text-stone-900 dark:text-stone-100 focus:outline-none focus:ring-2 focus:ring-primary-500/50 placeholder:text-stone-400" />
{/* Grid - NOW 3 COLUMNS */}
{lexiconPlants.map((plant, index) => ( ))} {lexiconPlants.length === 0 && (
{t.noResults}
)}
); }; const renderSettings = () => { const languages: { code: Language; label: string }[] = [ { code: 'de', label: 'Deutsch' }, { code: 'en', label: 'English' }, { code: 'es', label: 'EspaƱol' } ]; const currentLangLabel = languages.find(l => l.code === language)?.label; return (

{t.settingsTitle}

{/* Dark Mode Settings */}
{t.darkMode}
{/* Language Settings (Dropdown Style) */}
setIsLanguageDropdownOpen(!isLanguageDropdownOpen)} >
{t.language}
{currentLangLabel} {isLanguageDropdownOpen ? : }
{/* Dropdown Content */} {isLanguageDropdownOpen && (
{languages.map((lang) => ( ))}
)}
); }; const renderScannerModal = () => { if (!isScannerOpen) return null; // 1. Result View if (analysisResult && selectedImage) { return (
); } // 2. Scanner View return (
{/* Header */}
{t.scanner}
{/* Main Camera Area */}
{selectedImage ? ( ) : (
)} {/* Background Grid */}
{/* Scan Frame */}
{/* SHOW SELECTED IMAGE IN FRAME */} {selectedImage && ( Scan preview )}
{/* Laser Line */} {isAnalyzing || !selectedImage ? (
) : null}
{/* Analyzing Sheet Overlay - Loading Animation */} {isAnalyzing && (
{analysisProgress < 100 ? t.analyzing : t.result} {Math.round(analysisProgress)}%
{/* Progress Bar */}
{/* Stage Indicators */}
{t.localProcessing}
{analysisProgress < 30 ? t.scanStage1 : analysisProgress < 75 ? t.scanStage2 : t.scanStage3}
)} {/* Bottom Controls */}
{t.gallery}
{t.help}
); }; return (
{activeTab === Tab.HOME && renderHome()} {activeTab === Tab.SETTINGS && renderSettings()} {activeTab === Tab.SEARCH && renderSearch()}
{/* Modal Layer for Detail View */} {selectedPlant && ( )} {/* Modal Layer for Scanner */} {renderScannerModal()} {/* Lexicon Overlay */} {renderLexicon()} {/* Toast Notification */}
); }; export default App;