import 'dart:io'; import 'package:flutter/material.dart'; import 'package:camera/camera.dart'; import 'package:tflite_flutter/tflite_flutter.dart'; import 'package:image/image.dart' as img; import '../../core/app_colors.dart'; List _cameras = []; class AiCameraScreen extends StatefulWidget { const AiCameraScreen({super.key}); @override State createState() => _AiCameraScreenState(); } class _AiCameraScreenState extends State { CameraController? _cam; Interpreter? _interpreter; bool _processing = false; String _result = 'Apunta a un residuo y toca el botón'; String _confidence = ''; bool _modelLoaded = false; // 0=Orgánico, 1=Inorgánico (según waste_classification_model) final _labels = ['Residuo Organico', 'Residuo Inorganico']; final _labelColors = [AppColors.verdeExito, AppColors.naranjaAlerta]; @override void initState() { super.initState(); _init(); } Future _init() async { try { _cameras = await availableCameras(); } catch (_) {} await _initCamera(); await _loadModel(); } Future _initCamera() async { if (_cameras.isEmpty) return; _cam = CameraController(_cameras[0], ResolutionPreset.medium, enableAudio: false); try { await _cam!.initialize(); if (mounted) setState(() {}); } catch (_) {} } Future _loadModel() async { try { _interpreter = await Interpreter.fromAsset('assets/models/waste_model.tflite'); setState(() => _modelLoaded = true); } catch (e) { setState(() => _result = 'Modelo no encontrado.\nAgrega waste_model.tflite a assets/models/'); } } Future _classify() async { if (_cam == null || !_cam!.value.isInitialized || _processing || !_modelLoaded) return; setState(() { _processing = true; _result = 'Analizando...'; _confidence = ''; }); try { final pic = await _cam!.takePicture(); final raw = await File(pic.path).readAsBytes(); img.Image? decoded = img.decodeImage(raw); if (decoded == null) throw Exception('No se pudo decodificar'); final resized = img.copyResize(decoded, width: 150, height: 150); var input = List.generate(1, (_) => List.generate(150, (_) => List.generate(150, (_) => List.generate(3, (_) => 0.0)))); for (int y = 0; y < 150; y++) { for (int x = 0; x < 150; x++) { final px = resized.getPixel(x, y); input[0][y][x][0] = px.r / 255.0; input[0][y][x][1] = px.g / 255.0; input[0][y][x][2] = px.b / 255.0; } } var output = List.filled(2, 0.0).reshape([1, 2]); _interpreter!.run(input, output); final pred = List.from(output[0]); final maxIdx = pred[0] > pred[1] ? 0 : 1; final conf = pred[maxIdx] * 100; await File(pic.path).delete(); setState(() { _result = _labels[maxIdx]; _confidence = 'Confianza: ${conf.toStringAsFixed(1)}%'; }); } catch (e) { setState(() => _result = 'Error en análisis'); } finally { setState(() => _processing = false); } } @override void dispose() { _cam?.dispose(); _interpreter?.close(); super.dispose(); } @override Widget build(BuildContext context) { final resultColor = _result.contains('Orgánico') ? AppColors.verdeExito : _result.contains('Inorgánico') ? AppColors.naranjaAlerta : AppColors.guindaPrimary; return Scaffold( backgroundColor: Colors.black, appBar: AppBar( backgroundColor: AppColors.guindaPrimary, foregroundColor: Colors.white, title: const Text('Clasificador IA de Residuos'), bottom: PreferredSize(preferredSize: const Size.fromHeight(4), child: Container(height: 4, color: AppColors.dorado)), ), body: Column(children: [ // Visor cámara Expanded(flex: 4, child: Container(margin: const EdgeInsets.all(14), clipBehavior: Clip.antiAlias, decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), border: Border.all(color: AppColors.guindaPrimary, width: 3)), child: _cam != null && _cam!.value.isInitialized ? CameraPreview(_cam!) : const Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.camera_alt, color: Colors.white54, size: 48), SizedBox(height: 8), Text('Iniciando cámara...', style: TextStyle(color: Colors.white54)), ])), ), ), // Panel resultado Expanded(flex: 2, child: Container(width: double.infinity, decoration: BoxDecoration(color: AppColors.guindaPrimary.withOpacity(0.06), borderRadius: const BorderRadius.vertical(top: Radius.circular(28))), padding: const EdgeInsets.all(20), child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ Text(_result, textAlign: TextAlign.center, style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: resultColor)), if (_confidence.isNotEmpty) ...[ const SizedBox(height: 6), Text(_confidence, style: const TextStyle(fontSize: 16, color: Colors.black54, fontWeight: FontWeight.w500)), ], const SizedBox(height: 16), if (!_modelLoaded) Container(padding: const EdgeInsets.all(10), decoration: BoxDecoration(color: Colors.orange.shade50, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.orange.shade300)), child: const Text('ℹ️ Para usar la IA, coloca waste_model.tflite en assets/models/', textAlign: TextAlign.center, style: TextStyle(fontSize: 11))), if (_modelLoaded) SizedBox(width: double.infinity, height: 50, child: ElevatedButton.icon( onPressed: _processing ? null : _classify, style: ElevatedButton.styleFrom( backgroundColor: AppColors.guindaPrimary, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14))), icon: _processing ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2)) : const Icon(Icons.center_focus_strong), label: Text(_processing ? 'Procesando...' : 'Escanear Residuo', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), )), ]), ), ), ]), ); } }