import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../models/user_model.dart'; import '../../services/ocr_service.dart'; import '../../services/gemini_service.dart'; import '../../services/firebase_service.dart'; import '../../models/recommendation_model.dart'; import '../recommendation/recommendation_screen.dart'; class CameraScreen extends ConsumerStatefulWidget { /// Satu atau lebih path foto menu (maks 3). final List imagePaths; final RecommendationModel? previousScan; const CameraScreen({super.key, required this.imagePaths, this.previousScan}); @override ConsumerState createState() => _CameraScreenState(); } class _CameraScreenState extends ConsumerState { bool _isProcessing = false; double _progress = 0.0; // Cycling loading text int _cycleIndex = 0; Timer? _cycleTimer; static const _cycleMessages = [ ( title: 'Tunggu Sebentar', subtitle: 'Biarkan AI Memberikanmu Rekomendasi Terbaiknya', ), (title: 'Sebentar Lagi', subtitle: 'Ini Membutuhkan Waktu Sebentar Lagi'), ]; @override void initState() { super.initState(); _processImage(); _cycleTimer = Timer.periodic(const Duration(seconds: 3), (_) { if (mounted) { setState(() { _cycleIndex = (_cycleIndex + 1) % _cycleMessages.length; }); } }); } @override void dispose() { _cycleTimer?.cancel(); super.dispose(); } Future _processImage() async { setState(() { _isProcessing = true; _progress = 0.1; }); try { // Step 1: OCR – gabungkan teks dari semua foto (Atau ambil dari riwayat) setState(() => _progress = 0.3); final allMenuItems = []; String combinedText = ''; if (widget.previousScan != null) { // Lewati OCR, gunakan data dari riwayat sebelumnya combinedText = widget.previousScan!.originalMenuText; for (var rec in widget.previousScan!.recommendations) { allMenuItems.add(MenuItemOCR( name: rec.menuName, price: rec.price, description: rec.description, category: rec.category, )); } } else { final ocrService = OCRService(); for (final path in widget.imagePaths) { final ocrResult = await ocrService.extractTextFromImage(path); if (ocrResult.isSuccess) { allMenuItems.addAll(ocrResult.menuItems); if (combinedText.isNotEmpty) combinedText += '\n'; combinedText += ocrResult.processedText; } } // Validasi setidaknya ada 1 item menu if (allMenuItems.isEmpty) { // Fallback: gunakan hasil OCR foto pertama walaupun tidak ideal final firstResult = await ocrService.extractTextFromImage( widget.imagePaths.first, ); if (!firstResult.isSuccess || !ocrService.validateOCRResult(firstResult)) { throw Exception( 'Gagal membaca menu. Pastikan gambar jelas dan berisi teks menu.', ); } allMenuItems.addAll(firstResult.menuItems); combinedText = firstResult.processedText; } } // Step 2: Get user data (preferences + daily check-in) setState(() => _progress = 0.5); final firebaseService = FirebaseService(); final currentUser = firebaseService.currentUser; if (currentUser == null) { throw Exception('Pengguna tidak terautentikasi'); } final userData = await firebaseService.getUserData(currentUser.uid); final userPreferences = userData?.preferences ?? UserPreferences(); // Step 3: Generate recommendations — semua data (profil + daily) dikirim sekaligus setState(() => _progress = 0.7); final geminiService = GeminiService(); final recommendation = await geminiService.generateRecommendations( menuItems: allMenuItems, userPreferences: userPreferences, userId: currentUser.uid, menuImageUrl: widget.previousScan?.menuImageUrl ?? (widget.imagePaths.isNotEmpty ? widget.imagePaths.first : null), originalMenuText: combinedText, userModel: userData, menuImagePaths: widget.previousScan?.menuImagePaths ?? widget.imagePaths, ); // Step 4: Save as Draft setState(() => _progress = 0.9); await firebaseService.saveRecommendation(recommendation); // Step 5: Navigate setState(() => _progress = 1.0); await Future.delayed(const Duration(milliseconds: 500)); if (mounted) { Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (context) => RecommendationScreen( recommendation: recommendation, ocrResult: null, ), ), ); } } catch (e) { debugPrint('Image processing error: $e'); if (mounted) { _showErrorDialog(e.toString()); } } } void _showErrorDialog(String error) { showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), title: Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.red.shade50, borderRadius: BorderRadius.circular(10), ), child: Icon( Icons.error_outline, color: Colors.red.shade600, size: 24, ), ), const SizedBox(width: 12), const Expanded( child: Text( 'Gagal Memproses', style: TextStyle( fontFamily: 'Poppins', fontSize: 18, fontWeight: FontWeight.bold, ), ), ), ], ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Terjadi kesalahan saat memproses gambar menu:', style: TextStyle( fontFamily: 'Poppins', fontSize: 14, color: Colors.grey, ), ), const SizedBox(height: 8), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.red.shade50, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.red.shade200), ), child: Text( error.length > 200 ? '${error.substring(0, 200)}...' : error, style: TextStyle( fontFamily: 'Poppins', fontSize: 12, color: Colors.red.shade700, ), ), ), const SizedBox(height: 16), const Text( 'Tips:', style: TextStyle( fontFamily: 'Poppins', fontSize: 14, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), _buildTipItem('Pastikan gambar menu terlihat jelas'), _buildTipItem('Foto dari berbagai sudut bisa membantu'), _buildTipItem('Pastikan koneksi internet aktif'), ], ), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); Navigator.of(context).pop(); }, child: Text( 'Kembali', style: TextStyle( fontFamily: 'Poppins', color: Colors.red.shade600, ), ), ), ElevatedButton( onPressed: () { Navigator.of(context).pop(); setState(() { _isProcessing = true; _progress = 0.0; }); _processImage(); }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF8B5E3C), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), child: const Text( 'Coba Lagi', style: TextStyle(fontFamily: 'Poppins', color: Colors.white), ), ), ], ), ); } Widget _buildTipItem(String text) { return Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon( Icons.check_circle_outline, size: 16, color: Colors.green.shade600, ), const SizedBox(width: 8), Expanded( child: Text( text, style: const TextStyle( fontFamily: 'Poppins', fontSize: 12, color: Colors.grey, ), ), ), ], ), ); } @override Widget build(BuildContext context) { final msg = _cycleMessages[_cycleIndex]; return Scaffold( backgroundColor: const Color(0xFFFAF6F1), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( 'assets/images/Loading Scan.gif', width: 220, height: 220, fit: BoxFit.contain, ), const SizedBox(height: 32), AnimatedSwitcher( duration: const Duration(milliseconds: 400), child: Column( key: ValueKey(_cycleIndex), children: [ Text( msg.title, style: const TextStyle( fontFamily: 'Poppins', fontSize: 22, fontWeight: FontWeight.bold, color: Color(0xFF4A3C3C), ), ), const SizedBox(height: 8), Padding( padding: const EdgeInsets.symmetric(horizontal: 32), child: Text( msg.subtitle, textAlign: TextAlign.center, style: const TextStyle( fontFamily: 'Poppins', fontSize: 13, color: Color(0xFF8B6F6F), ), ), ), ], ), ), if (widget.imagePaths.length > 1) ...[ const SizedBox(height: 16), Text( '${widget.imagePaths.length} foto sedang dianalisis', style: const TextStyle( fontFamily: 'Poppins', fontSize: 12, color: Color(0xFFB0A0A0), ), ), ], ], ), ), ); } }