app mobile allarmi prima

This commit is contained in:
2025-10-20 19:17:45 +02:00
commit 300912ee02
159 changed files with 11755 additions and 0 deletions

View File

@@ -0,0 +1,494 @@
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:intl/intl.dart';
import '../models/sito.dart';
import '../models/allarme.dart';
import '../services/api_service.dart';
import '../widgets/allarme_card.dart';
import '../utils/constants.dart';
class SitoDetailScreen extends StatefulWidget {
final Sito sito;
const SitoDetailScreen({super.key, required this.sito});
@override
State<SitoDetailScreen> createState() => _SitoDetailScreenState();
}
class _SitoDetailScreenState extends State<SitoDetailScreen>
with SingleTickerProviderStateMixin {
final _apiService = ApiService();
List<Allarme> _allarmi = [];
bool _isLoadingAllarmi = true;
String? _errorMessage;
late TabController _tabController;
GoogleMapController? _mapController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
_loadAllarmi();
}
@override
void dispose() {
_tabController.dispose();
_mapController?.dispose();
super.dispose();
}
Future<void> _loadAllarmi() async {
try {
final response = await _apiService.getAllarmiBySito(widget.sito.id);
setState(() {
_allarmi = response.items;
_isLoadingAllarmi = false;
});
} catch (e) {
setState(() {
_errorMessage = 'Errore caricamento allarmi: $e';
_isLoadingAllarmi = false;
});
}
}
Color _getTipoColor() {
switch (widget.sito.tipo) {
case 'ponte':
return Colors.blue;
case 'galleria':
return Colors.brown;
case 'diga':
return Colors.cyan;
case 'frana':
return Colors.orange;
case 'versante':
return Colors.green;
case 'edificio':
return Colors.purple;
default:
return AppColors.primary;
}
}
IconData _getTipoIcon() {
switch (widget.sito.tipo) {
case 'ponte':
return Icons.architecture;
case 'galleria':
return Icons.south_west;
case 'diga':
return Icons.water_damage;
case 'frana':
return Icons.landscape;
case 'versante':
return Icons.terrain;
case 'edificio':
return Icons.business;
default:
return Icons.location_on;
}
}
int get _allarmiCritici =>
_allarmi.where((a) => a.severita == 'critical').length;
int get _allarmiAperti =>
_allarmi.where((a) => a.stato != 'risolto').length;
@override
Widget build(BuildContext context) {
final dateFormat = DateFormat('dd/MM/yyyy');
return Scaffold(
body: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverAppBar(
expandedHeight: 180,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
background: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
_getTipoColor(),
_getTipoColor().withOpacity(0.7),
],
),
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.fromLTRB(
AppSizes.paddingL,
AppSizes.paddingL,
AppSizes.paddingL,
70, // Spazio per le tab (altezza standard tab bar)
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Icon(
_getTipoIcon(),
size: 32,
color: _getTipoColor(),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.sito.tipoReadable,
style: TextStyle(
color: Colors.white.withOpacity(0.9),
fontSize: 14,
),
),
Text(
widget.sito.nome,
style: const TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
],
),
),
),
),
),
bottom: TabBar(
controller: _tabController,
tabs: const [
Tab(text: 'Informazioni', icon: Icon(Icons.info_outline)),
Tab(text: 'Allarmi', icon: Icon(Icons.notifications_active)),
],
),
),
];
},
body: TabBarView(
controller: _tabController,
children: [
// Tab Informazioni
_buildInfoTab(dateFormat),
// Tab Allarmi
_buildAllarmiTab(),
],
),
),
);
}
Widget _buildInfoTab(DateFormat dateFormat) {
return SingleChildScrollView(
padding: const EdgeInsets.all(AppSizes.paddingL),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Statistiche rapide
Row(
children: [
Expanded(
child: _buildStatCard(
'Allarmi Totali',
_allarmi.length.toString(),
Icons.notifications,
AppColors.primary,
),
),
const SizedBox(width: AppSizes.paddingM),
Expanded(
child: _buildStatCard(
'Critici',
_allarmiCritici.toString(),
Icons.warning,
AppColors.critical,
),
),
const SizedBox(width: AppSizes.paddingM),
Expanded(
child: _buildStatCard(
'Aperti',
_allarmiAperti.toString(),
Icons.pending,
AppColors.warning,
),
),
],
),
const SizedBox(height: 24),
// Descrizione
if (widget.sito.descrizione != null) ...[
const Text('Descrizione', style: AppTextStyles.h3),
const SizedBox(height: 8),
Text(
widget.sito.descrizione!,
style: AppTextStyles.bodyLarge,
),
const SizedBox(height: 24),
],
// Mappa
if (widget.sito.hasCoordinates) ...[
const Text('Posizione', style: AppTextStyles.h3),
const SizedBox(height: 12),
_buildMapView(),
const SizedBox(height: 24),
],
// Dettagli tecnici
const Text('Dettagli', style: AppTextStyles.h3),
const SizedBox(height: 12),
_buildInfoCard(
icon: Icons.place,
title: 'Località',
value: widget.sito.localita,
),
if (widget.sito.regione != null) ...[
const SizedBox(height: 8),
_buildInfoCard(
icon: Icons.map,
title: 'Regione',
value: widget.sito.regione!,
),
],
if (widget.sito.codiceIdentificativo != null) ...[
const SizedBox(height: 8),
_buildInfoCard(
icon: Icons.qr_code,
title: 'Codice Identificativo',
value: widget.sito.codiceIdentificativo!,
),
],
if (widget.sito.hasCoordinates) ...[
const SizedBox(height: 8),
_buildInfoCard(
icon: Icons.my_location,
title: 'Coordinate',
value:
'${widget.sito.latitudine!.toStringAsFixed(6)}, ${widget.sito.longitudine!.toStringAsFixed(6)}',
),
],
if (widget.sito.altitudine != null) ...[
const SizedBox(height: 8),
_buildInfoCard(
icon: Icons.height,
title: 'Altitudine',
value: '${widget.sito.altitudine!.toStringAsFixed(0)} m s.l.m.',
),
],
const SizedBox(height: 8),
_buildInfoCard(
icon: Icons.calendar_today,
title: 'Data creazione',
value: dateFormat.format(widget.sito.createdAt),
),
],
),
);
}
Widget _buildAllarmiTab() {
if (_isLoadingAllarmi) {
return const Center(child: CircularProgressIndicator());
}
if (_errorMessage != null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error_outline, size: 64, color: AppColors.critical),
const SizedBox(height: 16),
Text(_errorMessage!),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: _loadAllarmi,
icon: const Icon(Icons.refresh),
label: const Text('Riprova'),
),
],
),
);
}
if (_allarmi.isEmpty) {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.check_circle_outline, size: 64, color: AppColors.success),
SizedBox(height: 16),
Text('Nessun allarme registrato', style: AppTextStyles.h3),
SizedBox(height: 8),
Text(
'Questo sito non ha allarmi nello storico',
style: AppTextStyles.bodyMedium,
textAlign: TextAlign.center,
),
],
),
);
}
return RefreshIndicator(
onRefresh: _loadAllarmi,
child: ListView.builder(
padding: const EdgeInsets.all(AppSizes.paddingM),
itemCount: _allarmi.length,
itemBuilder: (context, index) {
return AllarmeCard(allarme: _allarmi[index]);
},
),
);
}
Widget _buildStatCard(String label, String value, IconData icon, Color color) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: color.withOpacity(0.3)),
),
child: Column(
children: [
Icon(icon, color: color, size: 24),
const SizedBox(height: 8),
Text(
value,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: color,
),
),
const SizedBox(height: 4),
Text(
label,
style: TextStyle(
fontSize: 11,
color: color,
),
textAlign: TextAlign.center,
),
],
),
);
}
Widget _buildInfoCard({
required IconData icon,
required String title,
required String value,
}) {
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: _getTipoColor(), 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 _buildMapView() {
if (!widget.sito.hasCoordinates) {
return const SizedBox.shrink();
}
final position =
LatLng(widget.sito.latitudine!, widget.sito.longitudine!);
return Container(
height: 250,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey[300]!),
),
clipBehavior: Clip.hardEdge,
child: GoogleMap(
initialCameraPosition: CameraPosition(
target: position,
zoom: 14,
),
markers: {
Marker(
markerId: MarkerId('sito_${widget.sito.id}'),
position: position,
infoWindow: InfoWindow(
title: widget.sito.nome,
snippet: widget.sito.tipoReadable,
),
icon: BitmapDescriptor.defaultMarkerWithHue(
_getTipoColor() == Colors.blue
? BitmapDescriptor.hueBlue
: _getTipoColor() == Colors.green
? BitmapDescriptor.hueGreen
: _getTipoColor() == Colors.orange
? BitmapDescriptor.hueOrange
: BitmapDescriptor.hueRed,
),
),
},
myLocationButtonEnabled: true,
zoomControlsEnabled: true,
mapToolbarEnabled: false,
onMapCreated: (controller) {
_mapController = controller;
},
),
);
}
}