import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; import '../../../database/database.dart'; import '../../../providers/auth_provider.dart'; import '../../../providers/sync_provider.dart'; class SyncPage extends ConsumerStatefulWidget { const SyncPage({super.key}); @override ConsumerState createState() => _SyncPageState(); } class _SyncPageState extends ConsumerState with SingleTickerProviderStateMixin { bool _forceFullSync = false; late AnimationController _syncIconController; @override void initState() { super.initState(); _syncIconController = AnimationController( vsync: this, duration: const Duration(seconds: 2), ); } @override void dispose() { _syncIconController.dispose(); super.dispose(); } String _formatLastSyncTime(DateTime? time) { if (time == null) return 'Mai'; final now = DateTime.now(); final today = DateTime(now.year, now.month, now.day); final syncDate = DateTime(time.year, time.month, time.day); final timeStr = DateFormat('HH:mm').format(time); if (syncDate == today) { return 'Oggi, $timeStr'; } else if (syncDate == today.subtract(const Duration(days: 1))) { return 'Ieri, $timeStr'; } else { return DateFormat('dd/MM/yyyy, HH:mm').format(time); } } @override Widget build(BuildContext context) { final authState = ref.watch(authProvider); final syncState = ref.watch(syncProvider); final theme = Theme.of(context); final agente = authState.agente; final colorScheme = theme.colorScheme; ref.listen(syncProvider, (prev, next) { if (next.isSyncing && !(prev?.isSyncing ?? false)) { _syncIconController.repeat(); } else if (!next.isSyncing && (prev?.isSyncing ?? false)) { _syncIconController.stop(); _syncIconController.reset(); if (next.error == null && next.progress >= 1.0) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Trasmissione terminata')), ); } } }); return Scaffold( appBar: AppBar( title: const Text('Sincronizzazione'), actions: [ IconButton( icon: const Icon(Icons.settings), onPressed: () => context.push('/dashboard/sync/settings'), ), ], ), body: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Hero section: Toggle + Start button LayoutBuilder( builder: (context, constraints) { final isWide = constraints.maxWidth > 600; if (isWide) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(flex: 1, child: _buildToggleCard(context, syncState)), const SizedBox(width: 16), Expanded( flex: 2, child: _buildActionCard( context, agente, syncState, colorScheme, ), ), ], ); } return Column( children: [ _buildToggleCard(context, syncState), const SizedBox(height: 16), _buildActionCard(context, agente, syncState, colorScheme), ], ); }, ), const SizedBox(height: 16), // Progress section if (syncState.phases.isNotEmpty || syncState.isSyncing || syncState.progress >= 1.0) _buildProgressSection(context, syncState, colorScheme), // Error section if (syncState.error != null) ...[ const SizedBox(height: 16), _buildErrorCard(context, syncState, colorScheme), ], ], ), ), ); } Widget _buildToggleCard(BuildContext context, SyncState syncState) { final theme = Theme.of(context); return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Tipo Sincronizzazione', style: theme.textTheme.titleLarge, ), const SizedBox(height: 8), Text( 'Scegli tra sincronizzazione parziale delle variazioni o scaricamento integrale di tutti i dati dal server.', style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 16), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: theme.colorScheme.surface, borderRadius: BorderRadius.circular(8), border: Border.all( color: theme.colorScheme.outlineVariant.withValues(alpha: 0.3), ), ), child: Row( children: [ Switch( value: _forceFullSync, onChanged: syncState.isSyncing ? null : (v) => setState(() => _forceFullSync = v), ), const SizedBox(width: 12), Expanded( child: Text( 'Sincronizzazione completa', style: theme.textTheme.labelLarge, ), ), ], ), ), ], ), ), ); } Widget _buildActionCard( BuildContext context, Agente? agente, SyncState syncState, ColorScheme colorScheme, ) { final theme = Theme.of(context); return Card( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ FilledButton.icon( onPressed: syncState.isSyncing || agente == null ? null : () => ref.read(syncProvider.notifier).startFullSync( agente.idAgente, agente.codAgente!, forceFullSync: _forceFullSync, ), icon: syncState.isSyncing ? RotationTransition( turns: _syncIconController, child: const Icon(Icons.sync), ) : const Icon(Icons.sync), label: Text( syncState.isSyncing ? 'Sincronizzazione in corso...' : 'Avvia Sincronizzazione', ), style: FilledButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 20), textStyle: theme.textTheme.titleLarge, ), ), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.schedule, size: 16, color: colorScheme.onSurfaceVariant, ), const SizedBox(width: 6), Text( 'Ultimo aggiornamento: ${_formatLastSyncTime(syncState.lastSyncTime)}', style: theme.textTheme.bodyMedium?.copyWith( color: colorScheme.onSurfaceVariant, ), ), ], ), ], ), ), ); } Widget _buildProgressSection( BuildContext context, SyncState syncState, ColorScheme colorScheme, ) { final theme = Theme.of(context); final percent = syncState.phases.isNotEmpty ? (syncState.progress * 100).toInt() : 0; return Card( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Header Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: theme.colorScheme.surface, border: Border( bottom: BorderSide(color: theme.colorScheme.outlineVariant), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( syncState.currentAgentCode != null ? 'Sincronizzazione dati agente (${syncState.currentAgentCode})' : 'Sincronizzazione dati agente', style: theme.textTheme.titleLarge, ), if (syncState.agentiCodici.isNotEmpty) RichText( text: TextSpan( style: theme.textTheme.bodyMedium?.copyWith( color: colorScheme.onSurfaceVariant, ), children: [ const TextSpan( text: 'Stato complessivo del processo (Agenti: '), for (int i = 0; i < syncState.agentiCodici.length; i++) ...[ if (i > 0) const TextSpan(text: ', '), TextSpan( text: syncState.agentiCodici[i], style: syncState.agentiCodici[i] == syncState.currentAgentCode ? TextStyle( fontWeight: FontWeight.bold, color: colorScheme.onSurfaceVariant, ) : null, ), ], const TextSpan(text: ')'), ], ), ) else Text( 'Stato complessivo del processo', style: theme.textTheme.bodyMedium?.copyWith( color: colorScheme.onSurfaceVariant, ), ), ], ), Text( '$percent%', style: theme.textTheme.headlineMedium?.copyWith( color: colorScheme.secondary, ), ), ], ), ), // Progress bar + phases Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Main progress bar ClipRRect( borderRadius: BorderRadius.circular(4), child: LinearProgressIndicator( value: syncState.progress, minHeight: 8, backgroundColor: theme.colorScheme.surfaceContainerHighest, valueColor: AlwaysStoppedAnimation( colorScheme.secondary, ), ), ), const SizedBox(height: 16), // Phases grid ...syncState.phases.asMap().entries.map( (entry) => _buildPhaseRow( context, entry.key, entry.value, colorScheme, ), ), ], ), ), ], ), ); } Widget _buildPhaseRow( BuildContext context, int index, SyncPhaseInfo phase, ColorScheme colorScheme, ) { final theme = Theme.of(context); final isCompleted = phase.status == SyncPhaseStatus.completed; return Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( color: isCompleted ? Colors.green.shade50 : theme.colorScheme.surface, borderRadius: BorderRadius.circular(8), border: Border.all( color: theme.colorScheme.outlineVariant.withValues(alpha: 0.2), ), ), child: Row( children: [ // Status dot _buildStatusDot(phase.status, colorScheme), const SizedBox(width: 12), // Phase name Expanded( child: Text( phase.name, style: theme.textTheme.bodyMedium?.copyWith( color: isCompleted ? Colors.green.shade700 : theme.colorScheme.onSurface, ), ), ), // Status text _buildStatusText(phase, colorScheme), ], ), ); } Widget _buildStatusDot(SyncPhaseStatus status, ColorScheme colorScheme) { Color dotColor; List? shadow; switch (status) { case SyncPhaseStatus.waiting: dotColor = colorScheme.outlineVariant; break; case SyncPhaseStatus.downloading: case SyncPhaseStatus.processing: dotColor = colorScheme.secondary; shadow = [ BoxShadow( color: colorScheme.secondary.withValues(alpha: 0.5), blurRadius: 6, ), ]; break; case SyncPhaseStatus.completed: dotColor = Colors.green; shadow = [ BoxShadow( color: Colors.green.withValues(alpha: 0.5), blurRadius: 6, ), ]; } return AnimatedContainer( duration: const Duration(milliseconds: 300), width: 10, height: 10, decoration: BoxDecoration( shape: BoxShape.circle, color: dotColor, boxShadow: shadow, ), ); } Widget _buildStatusText(SyncPhaseInfo phase, ColorScheme colorScheme) { Color textColor; switch (phase.status) { case SyncPhaseStatus.waiting: textColor = colorScheme.onSurfaceVariant; break; case SyncPhaseStatus.downloading: case SyncPhaseStatus.processing: textColor = colorScheme.secondary; break; case SyncPhaseStatus.completed: textColor = Colors.green.shade600; break; } return Container( constraints: const BoxConstraints(minWidth: 140), child: Text( phase.statusText, textAlign: TextAlign.end, style: GoogleFonts.jetBrainsMono( fontSize: 11, fontWeight: FontWeight.w500, color: textColor, letterSpacing: -0.3, ), ), ); } Widget _buildErrorCard( BuildContext context, SyncState syncState, ColorScheme colorScheme, ) { return Card( color: colorScheme.errorContainer, child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ Icon( Icons.error_outline, color: colorScheme.onErrorContainer, ), const SizedBox(width: 12), Expanded( child: Text( syncState.error!, style: TextStyle(color: colorScheme.onErrorContainer), ), ), ], ), ), ); } }