import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../data/disease_data.dart'; import '../models/disease.dart'; import '../models/symptom.dart'; class DiseaseDetailPage extends StatelessWidget { final Disease disease; final Map result; const DiseaseDetailPage({ Key? key, required this.disease, required this.result, }) : super(key: key); static const Map diseaseImageMap = { 'P1': 'serkospora.jpg', 'P2': 'Alternaria.PNG', 'P3': 'Fitoftora.jpg', 'P4': 'antraknosa.jpg', 'P5': 'pusarium.jpg', 'P6': 'rebah_kecamba.jpeg', 'P7': 'virus kuning.jpg', }; String _getSeverity(double confidence, double matchPercentage, double totalWeight, int matchedSymptomsCount) { final averageWeight = matchedSymptomsCount > 0 ? totalWeight / matchedSymptomsCount : 0.0; if (confidence >= 70 && (matchPercentage >= 0.5 || averageWeight >= 0.6)) { return 'Kritis'; } else if (confidence >= 40 || (averageWeight >= 0.5 && matchPercentage >= 0.25)) { return 'Perhatian'; } else if (confidence >= 20 || averageWeight >= 0.3) { return 'Ringan'; } else { return 'Minimal'; } } void _showDiagnosisReason(BuildContext context, double confidence, int matchedSymptomsCount, int totalSymptoms) { showDialog( context: context, builder: (context) => AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15.0), ), backgroundColor: Colors.white, elevation: 10, title: Row( children: const [ Icon( Icons.info_outline, color: Color(0xFFAC2B36), size: 28.0, ), SizedBox(width: 10), Text( 'Alasan Diagnosis', style: TextStyle( fontSize: 20.0, fontWeight: FontWeight.bold, color: Color(0xFFAC2B36), ), ), ], ), content: Text( confidence >= 70 ? 'Sistem mendeteksi banyak gejala yang cocok ($matchedSymptomsCount dari $totalSymptoms) dengan tingkat kemungkinan tinggi (${confidence.toStringAsFixed(1)}%).' : confidence >= 40 ? 'Sistem mendeteksi beberapa gejala yang cocok ($matchedSymptomsCount dari $totalSymptoms) dengan kemungkinan sedang (${confidence.toStringAsFixed(1)}%).' : 'Sistem mendeteksi sedikit gejala yang cocok ($matchedSymptomsCount dari $totalSymptoms) dengan kemungkinan rendah (${confidence.toStringAsFixed(1)}%).', style: const TextStyle( fontSize: 16.0, color: Colors.black87, height: 1.5, ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text( 'Tutup', style: TextStyle(fontSize: 16.0, color: Color(0xFF4CAF50)), ), ), ], ), ); } @override Widget build(BuildContext context) { final confidence = result['confidence'] as double; final matchPercentage = result['matchPercentage'] as double; final matchedSymptoms = (result['matchedSymptoms'] as List?)?.cast() ?? []; final totalWeight = result['totalWeight'] as double; final matchedSymptomsCount = matchedSymptoms.length; final totalSymptoms = disease.symptomIds.length; final severity = _getSeverity( confidence, matchPercentage, totalWeight, matchedSymptomsCount); var solutionText = disease.solutions.join('\n'); if (severity == 'Kritis') { solutionText += '\n(Tindakan mendesak diperlukan karena gejala kritis)'; } else if (severity == 'Perhatian') { solutionText += '\n(Tindakan segera diperlukan)'; } final recommendation = result['recommendation'] ?? ''; final prevention = severity == 'Kritis' ? 'Segera konsultasi dengan ahli pertanian dan lakukan pencegahan intensif.' : 'Jaga kebersihan tanaman dan hindari kelembapan berlebih.'; final actionGuidance = severity == 'Kritis' ? 'Segera lakukan tindakan yang disarankan dan konsultasikan dengan ahli pertanian.' : severity == 'Perhatian' ? 'Lakukan tindakan yang disarankan sesegera mungkin untuk mencegah penyebaran.' : 'Amati tanaman secara rutin dan lakukan pencegahan dasar.'; final imageFileName = diseaseImageMap[disease.id] ?? 'placeholder.jpg'; return PopScope( canPop: false, onPopInvoked: (didPop) async { if (didPop) return; Navigator.pop(context, true); }, child: Scaffold( appBar: AppBar( title: Text(disease.name), backgroundColor: Colors.white, elevation: 0, iconTheme: const IconThemeData(color: Colors.black87), titleTextStyle: const TextStyle( color: Colors.black87, fontSize: 20, fontWeight: FontWeight.w600, ), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () { Navigator.pop(context, true); }, ), ), body: Padding( padding: const EdgeInsets.all(16.0), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Hero( tag: 'disease_image_${disease.id}', child: ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.asset( 'assets/$imageFileName', height: 200, width: double.infinity, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( height: 200, color: Colors.grey[200], child: const Center( child: Text('Gambar tidak tersedia')), ); }, ), ), ), const SizedBox(height: 20), Row( children: [ Text( 'Kemungkinan: ${confidence.toStringAsFixed(1)}%', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.black87, ), ), const SizedBox(width: 12), Expanded( child: LinearProgressIndicator( value: confidence / 100, backgroundColor: Colors.grey[200], valueColor: AlwaysStoppedAnimation( confidence >= 70 ? const Color(0xFFAC2B36) : confidence >= 40 ? Colors.amber : const Color(0xFF4CAF50), ), minHeight: 6, borderRadius: BorderRadius.circular(4), ), ), ], ), const SizedBox(height: 16), Row( children: [ Text( 'Tingkat Bahaya: $severity', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: severity == 'Kritis' ? const Color(0xFFAC2B36) : severity == 'Perhatian' ? Colors.amber : const Color(0xFF4CAF50), ), ), IconButton( icon: Icon( Icons.info_outline, size: 20, color: Colors.grey[600], ), onPressed: () => _showDiagnosisReason(context, confidence, matchedSymptomsCount, totalSymptoms), ), ], ), const SizedBox(height: 12), Text( severity == 'Kritis' ? 'Penyakit ini sangat mungkin terjadi dan memerlukan tindakan segera.' : severity == 'Perhatian' ? 'Penyakit ini kemungkinan ada. Segera ambil tindakan yang disarankan.' : 'Penyakit ini mungkin ada, tetapi tidak terlalu serius. Lakukan pencegahan.', style: TextStyle(fontSize: 13, color: Colors.grey[600]), ), if (matchedSymptoms.length < 2) ...[ const SizedBox(height: 12), Text( 'Peringatan: Diagnosis berdasarkan sedikit gejala. Periksa gejala lain untuk hasil lebih akurat.', style: TextStyle( fontSize: 13, color: const Color(0xFFAC2B36), fontStyle: FontStyle.italic, ), ), ], const SizedBox(height: 20), Row( children: [ Icon(Icons.sick_outlined, size: 20, color: Colors.grey[700]), const SizedBox(width: 8), const Text( 'Gejala yang Cocok:', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), ], ), const SizedBox(height: 12), Wrap( spacing: 8, runSpacing: 8, children: disease.symptomIds.map((symptomId) { final symptom = symptoms.firstWhere( (s) => s.id == symptomId, orElse: () => Symptom( id: '', description: 'Tidak Diketahui', weight: 0.0), ); if (matchedSymptoms.contains(symptomId)) { return Chip( label: Text( symptom.description, style: const TextStyle(fontSize: 12), ), backgroundColor: const Color(0xFF4CAF50).withOpacity(0.1), labelStyle: const TextStyle(color: Color(0xFF4CAF50)), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: const BorderSide( color: Color(0xFF4CAF50), width: 1), ), padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4), ); } return const SizedBox.shrink(); }).toList(), ), if (disease.symptomIds.length > matchedSymptoms.length) ...[ const SizedBox(height: 20), Row( children: [ Icon(Icons.search_outlined, size: 20, color: Colors.grey[700]), const SizedBox(width: 8), const Text( 'Gejala yang Perlu Diperiksa:', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), ], ), const SizedBox(height: 12), Wrap( spacing: 8, runSpacing: 8, children: disease.symptomIds.map((symptomId) { final symptom = symptoms.firstWhere( (s) => s.id == symptomId, orElse: () => Symptom( id: '', description: 'Tidak Diketahui', weight: 0.0), ); if (!matchedSymptoms.contains(symptomId)) { return Chip( label: Text( symptom.description, style: const TextStyle(fontSize: 12), ), backgroundColor: Colors.grey[100], labelStyle: TextStyle(color: Colors.grey[600]), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: BorderSide(color: Colors.grey[400]!, width: 1), ), padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4), ); } return const SizedBox.shrink(); }).toList(), ), ], const SizedBox(height: 20), Row( children: [ Icon(Icons.healing_outlined, size: 20, color: Colors.grey[700]), const SizedBox(width: 8), const Text( 'Solusi:', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), ], ), const SizedBox(height: 12), Text( solutionText, style: const TextStyle(fontSize: 13, color: Colors.black87), ), if (recommendation.isNotEmpty) ...[ const SizedBox(height: 20), Row( children: [ Icon(Icons.lightbulb_outline, size: 20, color: Colors.grey[700]), const SizedBox(width: 8), const Text( 'Rekomendasi Tambahan:', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), ], ), const SizedBox(height: 12), Text( recommendation, style: const TextStyle(fontSize: 13, color: Colors.black87), ), ], const SizedBox(height: 20), Row( children: [ Icon(Icons.shield_outlined, size: 20, color: Colors.grey[700]), const SizedBox(width: 8), const Text( 'Pencegahan:', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), ], ), const SizedBox(height: 12), Text( prevention, style: const TextStyle(fontSize: 13, color: Colors.black87), ), const SizedBox(height: 20), Row( children: [ Icon(Icons.next_plan_outlined, size: 20, color: Colors.grey[700]), const SizedBox(width: 8), const Text( 'Langkah Selanjutnya:', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), ], ), const SizedBox(height: 12), Text( actionGuidance, style: const TextStyle(fontSize: 13, color: Colors.black87), ), ], ), ), ), ), ); } } class ResultPage extends StatefulWidget { final Map> results; const ResultPage({Key? key, required this.results}) : super(key: key); @override State createState() => _ResultPageState(); } class _ResultPageState extends State with TickerProviderStateMixin { late List> validResults; late Map diseaseMap; late Map symptomMap; late List _controllers; late List> _fadeAnimations; late TabController _tabController; bool _isLoading = true; int _selectedTabIndex = 0; static const Map diseaseImageMap = { 'P1': 'serkospora.jpg', 'P2': 'Alternaria.PNG', 'P3': 'Fitoftora.jpg', 'P4': 'antraknosa.jpg', 'P5': 'pusarium.jpg', 'P6': 'rebah_kecamba.jpeg', 'P7': 'virus kuning.jpg', }; @override void initState() { super.initState(); if (kDebugMode) { print('ResultPage initState: Memulai dengan results: ${widget.results}'); print('Apakah results kosong? ${widget.results.isEmpty}'); } _initializeData(); } Future _initializeData() async { try { if (kDebugMode) { print('Memulai _initializeData...'); print('Input results: ${widget.results}'); } diseaseMap = {for (var disease in diseases) disease.id: disease}; symptomMap = {for (var symptom in symptoms) symptom.id: symptom}; validResults = []; _controllers = []; _fadeAnimations = []; final tempResults = widget.results; if (tempResults.isEmpty) { if (kDebugMode) { print('Results kosong, mengatur validResults sebagai list kosong.'); } if (mounted) { setState(() => _isLoading = false); } return; } for (var entry in tempResults.entries) { if (kDebugMode) { print('Memproses entry: ${entry.key} dengan value: ${entry.value}'); } if (!entry.value.containsKey('matchedSymptoms') || entry.value['matchedSymptoms'] == null) { if (kDebugMode) { print( 'matchedSymptoms tidak ada untuk ${entry.key}, menambahkan default: []'); } entry.value['matchedSymptoms'] = []; } entry.value['confidence'] = (entry.value['confidence'] as num?)?.toDouble() ?? 0.0; entry.value['matchPercentage'] = (entry.value['matchPercentage'] as num?)?.toDouble() ?? 0.0; entry.value['recommendation'] = entry.value['recommendation']?.toString() ?? ''; entry.value['totalWeight'] = (entry.value['totalWeight'] as num?)?.toDouble() ?? 0.0; } validResults = tempResults.entries .where((entry) { final isValid = diseaseMap.containsKey(entry.key) && entry.value['confidence'] is double && entry.value['matchPercentage'] is double && entry.value['totalWeight'] is double && entry.value['matchedSymptoms'] is List; if (!isValid && kDebugMode) { print('Entry tidak valid untuk ${entry.key}: ${entry.value}'); } return isValid; }) .map((entry) => { 'entry': entry, 'matchedSymptomsCount': (entry.value['matchedSymptoms'] as List?) ?.length ?? 0, }) .toList() ..sort((a, b) { final bConfidence = (b['entry'] as MapEntry).value['confidence'] as double? ?? 0.0; final aConfidence = (a['entry'] as MapEntry).value['confidence'] as double? ?? 0.0; final compareByScore = bConfidence.compareTo(aConfidence); if (compareByScore != 0) return compareByScore; final bMatched = b['matchedSymptomsCount'] as int? ?? 0; final aMatched = a['matchedSymptomsCount'] as int? ?? 0; return bMatched.compareTo(aMatched); }); if (kDebugMode) { print('Jumlah validResults: ${validResults.length}'); for (var result in validResults) { print( 'Penyakit: ${result['entry'].key}, Confidence: ${result['entry'].value['confidence']}, Matched: ${result['matchedSymptomsCount']}'); } } _controllers = List.generate( validResults.isEmpty ? 0 : validResults.length, (index) => AnimationController( duration: const Duration(milliseconds: 600), vsync: this, ), ); _fadeAnimations = _controllers .map((controller) => Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: controller, curve: Curves.easeInOut), )) .toList(); if (validResults.length > 3) { _tabController = TabController( length: validResults.length, vsync: this, initialIndex: _selectedTabIndex, ); _tabController.addListener(() { if (_tabController.index != _selectedTabIndex) { setState(() { _selectedTabIndex = _tabController.index; }); } }); } await Future.delayed(const Duration(milliseconds: 500)); if (mounted) { setState(() => _isLoading = false); if (kDebugMode) { print('Inisialisasi selesai, _isLoading = false'); } } if (validResults.isNotEmpty) { WidgetsBinding.instance.addPostFrameCallback((_) { for (var i = 0; i < _controllers.length; i++) { Future.delayed(Duration(milliseconds: i * 150), () { if (mounted && !_controllers[i].isAnimating) { _controllers[i].forward(); } }); } }); } } catch (e, stackTrace) { if (kDebugMode) { print('Error di _initializeData: $e'); print('Stack trace: $stackTrace'); } if (mounted) { setState(() => _isLoading = false); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Gagal memuat hasil diagnosis: $e'), backgroundColor: const Color(0xFFAC2B36), ), ); } } } @override void dispose() { for (var controller in _controllers) { controller.dispose(); } _tabController?.dispose(); super.dispose(); } String _getSeverity(double confidence, double matchPercentage, double totalWeight, int matchedSymptomsCount) { final averageWeight = matchedSymptomsCount > 0 ? totalWeight / matchedSymptomsCount : 0.0; if (confidence >= 70 && (matchPercentage >= 0.5 || averageWeight >= 0.6)) { return 'Kritis'; } else if (confidence >= 40 || (averageWeight >= 0.5 && matchPercentage >= 0.25)) { return 'Perhatian'; } else if (confidence >= 20 || averageWeight >= 0.3) { return 'Ringan'; } else { return 'Minimal'; } } BoxDecoration _getCardDecoration(double totalWeight, double confidence, double matchPercentage, int matchedSymptomsCount) { final severity = _getSeverity( confidence, matchPercentage, totalWeight, matchedSymptomsCount); final borderColor = severity == 'Kritis' ? const Color(0xFFAC2B36).withOpacity(0.3) : severity == 'Perhatian' ? Colors.amber.withOpacity(0.3) : const Color(0xFF4CAF50).withOpacity(0.3); return BoxDecoration( color: Colors.white, border: Border.all(color: borderColor, width: 1), borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 6, offset: const Offset(0, 3), ), ], ); } void _showDiagnosisReason(BuildContext context, double confidence, int matchedSymptomsCount, int totalSymptoms) { showDialog( context: context, builder: (context) => AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15.0), ), backgroundColor: Colors.white, elevation: 10, title: Row( children: const [ Icon( Icons.info_outline, color: Color(0xFFAC2B36), size: 28.0, ), SizedBox(width: 10), Text( 'Alasan Diagnosis', style: TextStyle( fontSize: 20.0, fontWeight: FontWeight.bold, color: Color(0xFFAC2B36), ), ), ], ), content: Text( confidence >= 70 ? 'Sistem mendeteksi banyak gejala yang cocok ($matchedSymptomsCount dari $totalSymptoms) dengan tingkat kemungkinan tinggi (${confidence.toStringAsFixed(1)}%).' : confidence >= 40 ? 'Sistem mendeteksi beberapa gejala yang cocok ($matchedSymptomsCount dari $totalSymptoms) dengan kemungkinan sedang (${confidence.toStringAsFixed(1)}%).' : 'Sistem mendeteksi sedikit gejala yang cocok ($matchedSymptomsCount dari $totalSymptoms) dengan kemungkinan rendah (${confidence.toStringAsFixed(1)}%).', style: const TextStyle( fontSize: 16.0, color: Colors.black87, height: 1.5, ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text( 'Tutup', style: TextStyle(fontSize: 16.0, color: Color(0xFF4CAF50)), ), ), ], ), ); } Future _showBackConfirmation(BuildContext context) async { final result = await showDialog( context: context, builder: (context) => AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15.0), ), backgroundColor: Colors.white, elevation: 10, title: Row( children: const [ Icon( Icons.warning_rounded, color: Color(0xFFAC2B36), size: 28.0, ), SizedBox(width: 10), Text( 'Konfirmasi Kembali', style: TextStyle( fontSize: 20.0, fontWeight: FontWeight.bold, color: Color(0xFFAC2B36), ), ), ], ), content: const Text( 'Kembali ke halaman diagnosis akan menghapus gejala yang dipilih. Apakah Anda yakin ingin melanjutkan?', style: TextStyle( fontSize: 16.0, color: Colors.black87, height: 1.5, ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text( 'Batal', style: TextStyle(fontSize: 16.0, color: Colors.grey), ), ), ElevatedButton( onPressed: () => Navigator.of(context).pop(true), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFAC2B36), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10.0), ), ), child: const Text( 'Kembali', style: TextStyle(fontSize: 16.0, color: Colors.white), ), ), ], ), ); return result ?? false; } Widget _buildDiseaseCard(int index) { if (index >= _fadeAnimations.length) { return const SizedBox.shrink(); } final result = validResults[index]['entry']; final disease = diseaseMap[result.key]; if (disease == null || result.value['confidence'] < 10) { return const SizedBox.shrink(); } final totalWeight = result.value['totalWeight'] as double; final confidence = result.value['confidence'] as double; final matchPercentage = result.value['matchPercentage'] as double; final matchedSymptomsCount = validResults[index]['matchedSymptomsCount'] as int; final totalSymptoms = disease.symptomIds.length; final matchedSymptomsList = (result.value['matchedSymptoms'] as List?)?.cast() ?? []; final severity = _getSeverity( confidence, matchPercentage, totalWeight, matchedSymptomsCount); final matchedSymptoms = []; for (var symptomId in disease.symptomIds) { if (matchedSymptomsList.contains(symptomId)) { final symptom = symptomMap[symptomId] ?? Symptom(id: '', description: 'Tidak Diketahui', weight: 0.0); matchedSymptoms.add(symptom.description); } } final unmatchedSymptoms = []; for (var symptomId in disease.symptomIds) { if (!matchedSymptomsList.contains(symptomId)) { final symptom = symptomMap[symptomId] ?? Symptom(id: '', description: 'Tidak Diketahui', weight: 0.0); unmatchedSymptoms.add(symptom.description); } } final imageFileName = diseaseImageMap[disease.id] ?? 'placeholder.jpg'; var solutionText = disease.solutions.join('\n'); if (severity == 'Kritis') { solutionText += '\n(Tindakan mendesak diperlukan karena gejala kritis)'; } else if (severity == 'Perhatian') { solutionText += '\n(Tindakan segera diperlukan)'; } final recommendation = result.value['recommendation'] ?? ''; final prevention = severity == 'Kritis' ? 'Segera konsultasi dengan ahli pertanian dan lakukan pencegahan intensif.' : 'Jaga kebersihan tanaman dan hindari kelembapan berlebih.'; final actionGuidance = severity == 'Kritis' ? 'Segera lakukan tindakan yang disarankan dan konsultasikan dengan ahli pertanian.' : severity == 'Perhatian' ? 'Lakukan tindakan yang disarankan sesegera mungkin untuk mencegah penyebaran.' : 'Amati tanaman secara rutin dan lakukan pencegahan dasar.'; return FadeTransition( opacity: _fadeAnimations[index], child: SlideTransition( position: Tween( begin: const Offset(0.1, 0), end: Offset.zero, ).animate(_fadeAnimations[index]), child: Container( margin: const EdgeInsets.symmetric(vertical: 8), constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width > 600 ? 600 : double.infinity, ), decoration: _getCardDecoration( totalWeight, confidence, matchPercentage, matchedSymptomsCount), padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( disease.name, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Colors.black87, ), ), ), Icon( severity == 'Kritis' ? Icons.warning_amber_rounded : severity == 'Perhatian' ? Icons.notifications_active : Icons.check_circle_outline, color: severity == 'Kritis' ? const Color(0xFFAC2B36) : severity == 'Perhatian' ? Colors.amber : const Color(0xFF4CAF50), size: 24, ), ], ), const SizedBox(height: 12), Row( children: [ Text( 'Kemungkinan: ${confidence.toStringAsFixed(1)}%', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), const SizedBox(width: 12), Expanded( child: TweenAnimationBuilder( tween: Tween(begin: 0.0, end: confidence / 100), duration: const Duration(milliseconds: 600), builder: (context, value, child) { return LinearProgressIndicator( value: value, backgroundColor: Colors.grey[200], valueColor: AlwaysStoppedAnimation( confidence >= 70 ? const Color(0xFFAC2B36) : confidence >= 40 ? Colors.amber : const Color(0xFF4CAF50), ), minHeight: 5, borderRadius: BorderRadius.circular(4), ); }, ), ), IconButton( icon: Icon( Icons.info_outline, size: 20, color: Colors.grey[600], ), onPressed: () => _showDiagnosisReason(context, confidence, matchedSymptomsCount, totalSymptoms), ), ], ), const SizedBox(height: 12), Text( 'Tingkat Bahaya: $severity', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: severity == 'Kritis' ? const Color(0xFFAC2B36) : severity == 'Perhatian' ? Colors.amber : const Color(0xFF4CAF50), ), ), const SizedBox(height: 8), Text( severity == 'Kritis' ? 'Penyakit ini sangat mungkin terjadi dan memerlukan tindakan segera.' : severity == 'Perhatian' ? 'Penyakit ini kemungkinan ada. Segera ambil tindakan yang disarankan.' : 'Penyakit ini mungkin ada, tetapi tidak terlalu serius. Lakukan pencegahan.', style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), if (matchedSymptoms.length < 2) ...[ const SizedBox(height: 12), Text( 'Peringatan: Diagnosis berdasarkan sedikit gejala. Periksa gejala lain untuk hasil lebih akurat.', style: TextStyle( fontSize: 12, color: const Color(0xFFAC2B36), fontStyle: FontStyle.italic, ), ), ], const SizedBox(height: 20), Hero( tag: 'disease_image_${disease.id}', child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.asset( 'assets/$imageFileName', height: 80, width: double.infinity, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( height: 80, color: Colors.grey[200], child: const Center( child: Icon(Icons.broken_image, color: Colors.grey)), ); }, ), ), ), const SizedBox(height: 20), Row( children: [ Icon(Icons.sick_outlined, size: 20, color: Colors.grey[700]), const SizedBox(width: 8), const Text( 'Gejala yang Cocok:', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), ], ), const SizedBox(height: 12), Wrap( spacing: 8, runSpacing: 8, alignment: WrapAlignment.start, children: matchedSymptoms .map((symptom) => Chip( label: ConstrainedBox( constraints: const BoxConstraints(minWidth: 60), child: Text( symptom, style: const TextStyle(fontSize: 12), textAlign: TextAlign.center, ), ), backgroundColor: const Color(0xFF4CAF50).withOpacity(0.1), labelStyle: const TextStyle(color: Color(0xFF4CAF50)), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: const BorderSide( color: Color(0xFF4CAF50), width: 1), ), padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4), )) .toList(), ), if (unmatchedSymptoms.isNotEmpty) ...[ const SizedBox(height: 20), Row( children: [ Icon(Icons.search_outlined, size: 20, color: Colors.grey[700]), const SizedBox(width: 8), const Text( 'Gejala yang Perlu Diperiksa:', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), ], ), const SizedBox(height: 12), Wrap( spacing: 8, runSpacing: 8, alignment: WrapAlignment.start, children: unmatchedSymptoms .map((symptom) => Chip( label: ConstrainedBox( constraints: const BoxConstraints(minWidth: 60), child: Text( symptom, style: const TextStyle(fontSize: 12), textAlign: TextAlign.center, ), ), backgroundColor: Colors.grey[100], labelStyle: TextStyle(color: Colors.grey[600]), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: BorderSide( color: Colors.grey[400]!, width: 1), ), padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4), )) .toList(), ), ], const SizedBox(height: 20), Row( children: [ Icon(Icons.healing, size: 20, color: Colors.grey[700]), const SizedBox(width: 8), const Text( 'Solusi:', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), ], ), const SizedBox(height: 12), Text( solutionText, style: const TextStyle(fontSize: 13, color: Colors.black87), ), if (recommendation.isNotEmpty) ...[ const SizedBox(height: 20), Row( children: [ Icon(Icons.lightbulb_outline, size: 20, color: Colors.grey[700]), const SizedBox(width: 8), const Text( 'Rekomendasi Tambahan:', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), ], ), const SizedBox(height: 12), Text( recommendation, style: const TextStyle(fontSize: 13, color: Colors.black87), ), ], const SizedBox(height: 20), Row( children: [ Icon(Icons.shield_outlined, size: 20, color: Colors.grey[700]), const SizedBox(width: 8), const Text( 'Pencegahan:', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), ], ), const SizedBox(height: 12), Text( prevention, style: const TextStyle(fontSize: 13, color: Colors.black87), ), const SizedBox(height: 20), Row( children: [ Icon(Icons.next_plan_outlined, size: 20, color: Colors.grey[700]), const SizedBox(width: 8), const Text( 'Langkah Selanjutnya:', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), ], ), const SizedBox(height: 12), Text( actionGuidance, style: const TextStyle(fontSize: 13, color: Colors.black87), ), const SizedBox(height: 20), Align( alignment: Alignment.centerRight, child: TextButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => DiseaseDetailPage( disease: disease, result: result.value, ), ), ); }, child: const Text( 'Lihat Detail Lengkap', style: TextStyle( fontSize: 14, color: Color(0xFF4CAF50), fontWeight: FontWeight.w600, ), ), ), ), ], ), ), ), ); } @override Widget build(BuildContext context) { return PopScope( canPop: false, onPopInvoked: (didPop) async { if (didPop) return; final confirmed = await _showBackConfirmation(context); if (confirmed) { Navigator.pop(context, true); } }, child: Scaffold( backgroundColor: Colors.grey[50], appBar: AppBar( title: const Text('Hasil Diagnosa'), backgroundColor: Colors.white, elevation: 0, iconTheme: const IconThemeData(color: Colors.black87), titleTextStyle: const TextStyle( color: Colors.black87, fontSize: 20, fontWeight: FontWeight.w600, ), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () async { final confirmed = await _showBackConfirmation(context); if (confirmed) { Navigator.pop(context, true); } }, ), ), body: SafeArea( child: _isLoading ? const Center( child: CircularProgressIndicator( color: Color(0xFFAC2B36), strokeWidth: 3, ), ) : validResults.isEmpty ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.search_off, size: 80, color: Colors.grey[300], ), const SizedBox(height: 20), Text( 'Tidak ada penyakit yang cocok dengan gejala yang dipilih.', style: TextStyle( fontSize: 16, color: Colors.grey[600], fontWeight: FontWeight.w500, ), textAlign: TextAlign.center, ), const SizedBox(height: 20), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFAC2B36), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), elevation: 2, ), onPressed: () async { final confirmed = await _showBackConfirmation(context); if (confirmed) { Navigator.pop(context, true); } }, child: const Text( 'Coba Diagnosa Lagi', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, ), ), ), ], ), ) : validResults.length <= 3 ? ListView.builder( padding: EdgeInsets.symmetric( horizontal: MediaQuery.of(context).size.width > 600 ? (MediaQuery.of(context).size.width - 600) / 2 : 16, vertical: 16), itemCount: validResults.length, itemBuilder: (context, index) => _buildDiseaseCard(index), ) : Column( children: [ Container( color: Colors.white, padding: const EdgeInsets.symmetric(vertical: 8), margin: const EdgeInsets.only(bottom: 8), child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: TabBar( controller: _tabController, isScrollable: true, labelColor: const Color(0xFFAC2B36), unselectedLabelColor: Colors.grey[600], indicator: const UnderlineTabIndicator( borderSide: BorderSide( color: Color(0xFFAC2B36), width: 2, ), insets: EdgeInsets.symmetric(horizontal: 16), ), labelStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, ), unselectedLabelStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.w400, ), padding: const EdgeInsets.symmetric( horizontal: 16), tabs: validResults.asMap().entries.map((entry) { final disease = diseaseMap[entry.value['entry'].key]!; return Tab( text: disease.name, ); }).toList(), ), ), ), Expanded( child: TabBarView( controller: _tabController, children: validResults.asMap().entries.map((entry) { final controller = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); final animation = Tween(begin: 0.0, end: 1.0) .animate(CurvedAnimation( parent: controller, curve: Curves.easeInOut, )); controller.forward(); return FadeTransition( opacity: animation, child: SingleChildScrollView( padding: EdgeInsets.symmetric( horizontal: MediaQuery.of(context) .size .width > 600 ? (MediaQuery.of(context) .size .width - 600) / 2 : 16, vertical: 16), child: _buildDiseaseCard(entry.key), ), ); }).toList(), ), ), ], ), ), bottomNavigationBar: Padding( padding: const EdgeInsets.all(16.0), child: SizedBox( height: 56, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFAC2B36), foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), elevation: 2, ), onPressed: () async { final confirmed = await _showBackConfirmation(context); if (confirmed) { Navigator.pop(context, true); } }, child: const Text( 'Kembali ke Diagnosis', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, ), ), ), ), ), ), ); } }