Files
terrain_monitor_app/lib/screens/allarme_detail_screen.dart
2025-10-20 19:17:45 +02:00

574 lines
19 KiB
Dart

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'dart:convert';
import '../models/allarme.dart';
import '../models/sito.dart';
import '../services/api_service.dart';
import '../utils/constants.dart';
class AllarmeDetailScreen extends StatefulWidget {
final Allarme allarme;
const AllarmeDetailScreen({super.key, required this.allarme});
@override
State<AllarmeDetailScreen> createState() => _AllarmeDetailScreenState();
}
class _AllarmeDetailScreenState extends State<AllarmeDetailScreen> {
final _apiService = ApiService();
late Allarme _allarme;
bool _isUpdating = false;
bool _isLoadingSito = true;
Sito? _sito;
GoogleMapController? _mapController;
@override
void initState() {
super.initState();
_allarme = widget.allarme;
_loadSito();
}
Future<void> _loadSito() async {
try {
final sito = await _apiService.getSito(_allarme.sitoId);
setState(() {
_sito = sito;
_isLoadingSito = false;
});
} catch (e) {
setState(() => _isLoadingSito = false);
}
}
@override
void dispose() {
_mapController?.dispose();
super.dispose();
}
Color _getSeverityColor() {
switch (_allarme.severita) {
case 'critical':
return AppColors.critical;
case 'warning':
return AppColors.warning;
case 'info':
return AppColors.info;
default:
return AppColors.textSecondary;
}
}
Future<void> _updateStato(String nuovoStato) async {
setState(() => _isUpdating = true);
try {
final updatedAllarme = await _apiService.updateAllarme(
_allarme.id,
stato: nuovoStato,
);
setState(() {
_allarme = updatedAllarme;
_isUpdating = false;
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Stato aggiornato a: ${_allarme.statoReadable}'),
backgroundColor: AppColors.success,
),
);
}
} catch (e) {
setState(() => _isUpdating = false);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Errore aggiornamento: $e'),
backgroundColor: AppColors.critical,
),
);
}
}
}
void _showStatoDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Cambia Stato'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildStatoOption('nuovo', 'Nuovo', Icons.fiber_new),
_buildStatoOption('in_gestione', 'In Gestione', Icons.engineering),
_buildStatoOption('risolto', 'Risolto', Icons.check_circle),
],
),
),
);
}
Widget _buildStatoOption(String stato, String label, IconData icon) {
final isCurrentStato = _allarme.stato == stato;
return ListTile(
leading: Icon(
icon,
color: isCurrentStato ? AppColors.primary : AppColors.textSecondary,
),
title: Text(
label,
style: TextStyle(
fontWeight: isCurrentStato ? FontWeight.bold : FontWeight.normal,
color: isCurrentStato ? AppColors.primary : null,
),
),
trailing: isCurrentStato
? const Icon(Icons.check, color: AppColors.primary)
: null,
onTap: isCurrentStato
? null
: () {
Navigator.pop(context);
_updateStato(stato);
},
);
}
@override
Widget build(BuildContext context) {
final dateFormat = DateFormat('dd/MM/yyyy HH:mm');
return Scaffold(
appBar: AppBar(
title: const Text('Dettaglio Allarme'),
actions: [
if (!_isUpdating)
IconButton(
icon: const Icon(Icons.edit),
onPressed: _showStatoDialog,
tooltip: 'Cambia stato',
),
],
),
body: _isUpdating
? const Center(child: CircularProgressIndicator())
: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header con severità
Container(
width: double.infinity,
padding: const EdgeInsets.all(AppSizes.paddingL),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
_getSeverityColor(),
_getSeverityColor().withOpacity(0.7),
],
),
),
child: SafeArea(
bottom: false,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: Text(
_allarme.severitaReadable,
style: TextStyle(
color: _getSeverityColor(),
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.9),
borderRadius: BorderRadius.circular(20),
),
child: Row(
children: [
Icon(
_getStatoIcon(),
size: 16,
color: _getStatoColor(),
),
const SizedBox(width: 4),
Text(
_allarme.statoReadable,
style: TextStyle(
color: _getStatoColor(),
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
],
),
),
],
),
const SizedBox(height: 16),
Text(
_allarme.titolo,
style: const TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Row(
children: [
Icon(
AlarmTypeIcons.getIcon(_allarme.tipo),
color: Colors.white.withOpacity(0.9),
size: 18,
),
const SizedBox(width: 8),
Text(
_allarme.tipoReadable,
style: TextStyle(
color: Colors.white.withOpacity(0.9),
fontSize: 16,
),
),
],
),
],
),
),
),
// Informazioni principali
Padding(
padding: const EdgeInsets.all(AppSizes.paddingL),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Descrizione
if (_allarme.descrizione != null) ...[
const Text(
'Descrizione',
style: AppTextStyles.h3,
),
const SizedBox(height: 8),
Text(
_allarme.descrizione!,
style: AppTextStyles.bodyLarge,
),
const SizedBox(height: 24),
],
// Valori rilevati
if (_allarme.valoreRilevato != null &&
_allarme.valoreSoglia != null) ...[
const Text(
'Valori Rilevati',
style: AppTextStyles.h3,
),
const SizedBox(height: 12),
_buildInfoCard(
icon: Icons.analytics,
title: 'Valore Rilevato',
value:
'${_allarme.valoreRilevato!.toStringAsFixed(2)} ${_allarme.unitaMisura ?? ''}',
color: _getSeverityColor(),
),
const SizedBox(height: 8),
_buildInfoCard(
icon: Icons.straighten,
title: 'Soglia Impostata',
value:
'${_allarme.valoreSoglia!.toStringAsFixed(2)} ${_allarme.unitaMisura ?? ''}',
color: AppColors.textSecondary,
),
const SizedBox(height: 24),
],
// Dati sensori
if (_allarme.datiSensori != null &&
_allarme.datiSensori!.isNotEmpty) ...[
const Text(
'Dati Sensori',
style: AppTextStyles.h3,
),
const SizedBox(height: 12),
_buildSensorsData(),
const SizedBox(height: 24),
],
// Informazioni temporali
const Text(
'Informazioni Temporali',
style: AppTextStyles.h3,
),
const SizedBox(height: 12),
_buildInfoCard(
icon: Icons.access_time,
title: 'Rilevato il',
value: dateFormat.format(_allarme.timestampRilevamento),
),
const SizedBox(height: 8),
_buildInfoCard(
icon: Icons.schedule,
title: 'Creato il',
value: dateFormat.format(_allarme.createdAt),
),
const SizedBox(height: 24),
// Mappa con posizione sito
if (_sito != null && _sito!.hasCoordinates) ...[
const Text(
'Posizione Sito',
style: AppTextStyles.h3,
),
const SizedBox(height: 12),
_buildMapView(),
const SizedBox(height: 8),
_buildInfoCard(
icon: Icons.place,
title: 'Sito',
value: '${_sito!.nome} - ${_sito!.localita}',
),
const SizedBox(height: 24),
] else if (_isLoadingSito) ...[
const Center(
child: Padding(
padding: EdgeInsets.all(20.0),
child: CircularProgressIndicator(),
),
),
const SizedBox(height: 24),
],
// Dettagli tecnici
const Text(
'Dettagli Tecnici',
style: AppTextStyles.h3,
),
const SizedBox(height: 12),
_buildInfoCard(
icon: Icons.tag,
title: 'ID Allarme',
value: '#${_allarme.id}',
),
const SizedBox(height: 8),
_buildInfoCard(
icon: Icons.location_on,
title: 'ID Sito',
value: '#${_allarme.sitoId}',
),
],
),
),
],
),
),
bottomNavigationBar: !_isUpdating && _allarme.stato != 'risolto'
? SafeArea(
child: Padding(
padding: const EdgeInsets.all(AppSizes.paddingL),
child: ElevatedButton.icon(
onPressed: () => _updateStato('risolto'),
icon: const Icon(Icons.check_circle),
label: const Text('Segna come Risolto'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.success,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
),
),
),
)
: null,
);
}
Widget _buildInfoCard({
required IconData icon,
required String title,
required String value,
Color? color,
}) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey[300]!),
),
child: Row(
children: [
Icon(icon, color: color ?? AppColors.primary, size: 20),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: AppTextStyles.bodySmall.copyWith(
color: AppColors.textSecondary,
),
),
const SizedBox(height: 2),
Text(
value,
style: AppTextStyles.bodyMedium.copyWith(
fontWeight: FontWeight.w600,
),
),
],
),
),
],
),
);
}
Widget _buildSensorsData() {
final jsonEncoder = const JsonEncoder.withIndent(' ');
final prettyJson = jsonEncoder.convert(_allarme.datiSensori);
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey[900],
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(
Icons.sensors,
color: Colors.greenAccent,
size: 20,
),
const SizedBox(width: 8),
const Text(
'JSON Data',
style: TextStyle(
color: Colors.greenAccent,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 12),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Text(
prettyJson,
style: const TextStyle(
fontFamily: 'monospace',
color: Colors.white,
fontSize: 12,
),
),
),
],
),
);
}
IconData _getStatoIcon() {
switch (_allarme.stato) {
case 'nuovo':
return Icons.fiber_new;
case 'in_gestione':
return Icons.engineering;
case 'risolto':
return Icons.check_circle;
default:
return Icons.info;
}
}
Color _getStatoColor() {
switch (_allarme.stato) {
case 'nuovo':
return AppColors.critical;
case 'in_gestione':
return AppColors.warning;
case 'risolto':
return AppColors.success;
default:
return AppColors.textSecondary;
}
}
Widget _buildMapView() {
if (_sito == null || !_sito!.hasCoordinates) {
return const SizedBox.shrink();
}
final position = LatLng(_sito!.latitudine!, _sito!.longitudine!);
return Container(
height: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey[300]!),
),
clipBehavior: Clip.hardEdge,
child: GoogleMap(
initialCameraPosition: CameraPosition(
target: position,
zoom: 15,
),
markers: {
Marker(
markerId: MarkerId('sito_${_sito!.id}'),
position: position,
infoWindow: InfoWindow(
title: _sito!.nome,
snippet: _sito!.tipoReadable,
),
icon: BitmapDescriptor.defaultMarkerWithHue(
_getSeverityColor() == AppColors.critical
? BitmapDescriptor.hueRed
: _getSeverityColor() == AppColors.warning
? BitmapDescriptor.hueOrange
: BitmapDescriptor.hueBlue,
),
),
},
myLocationButtonEnabled: false,
zoomControlsEnabled: true,
mapToolbarEnabled: false,
onMapCreated: (controller) {
_mapController = controller;
},
),
);
}
}