|
|
@@ -1,6 +1,9 @@
|
|
|
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';
|
|
|
|
|
|
@@ -11,8 +14,41 @@ class SyncPage extends ConsumerStatefulWidget {
|
|
|
ConsumerState<SyncPage> createState() => _SyncPageState();
|
|
|
}
|
|
|
|
|
|
-class _SyncPageState extends ConsumerState<SyncPage> {
|
|
|
+class _SyncPageState extends ConsumerState<SyncPage>
|
|
|
+ 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) {
|
|
|
@@ -20,6 +56,21 @@ class _SyncPageState extends ConsumerState<SyncPage> {
|
|
|
final syncState = ref.watch(syncProvider);
|
|
|
final theme = Theme.of(context);
|
|
|
final agente = authState.agente;
|
|
|
+ final colorScheme = theme.colorScheme;
|
|
|
+
|
|
|
+ ref.listen<SyncState>(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(
|
|
|
@@ -31,84 +82,430 @@ class _SyncPageState extends ConsumerState<SyncPage> {
|
|
|
),
|
|
|
],
|
|
|
),
|
|
|
- body: SafeArea(
|
|
|
- child: Center(
|
|
|
- child: Padding(
|
|
|
- padding: const EdgeInsets.all(32),
|
|
|
- child: Column(
|
|
|
- mainAxisSize: MainAxisSize.min,
|
|
|
+ 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(
|
|
|
- syncState.isSyncing ? Icons.sync : Icons.sync_outlined,
|
|
|
- size: 64,
|
|
|
- color: syncState.isSyncing
|
|
|
- ? theme.colorScheme.primary
|
|
|
- : theme.colorScheme.outline,
|
|
|
+ Icons.schedule,
|
|
|
+ size: 16,
|
|
|
+ color: colorScheme.onSurfaceVariant,
|
|
|
),
|
|
|
- const SizedBox(height: 24),
|
|
|
+ const SizedBox(width: 6),
|
|
|
Text(
|
|
|
- syncState.isSyncing
|
|
|
- ? 'Sincronizzazione in corso...'
|
|
|
- : 'Sincronizzazione dati',
|
|
|
- style: theme.textTheme.titleLarge,
|
|
|
+ 'Ultimo aggiornamento: ${_formatLastSyncTime(syncState.lastSyncTime)}',
|
|
|
+ style: theme.textTheme.bodyMedium?.copyWith(
|
|
|
+ color: colorScheme.onSurfaceVariant,
|
|
|
+ ),
|
|
|
),
|
|
|
- const SizedBox(height: 16),
|
|
|
- if (syncState.isSyncing) ...[
|
|
|
- LinearProgressIndicator(value: syncState.progress),
|
|
|
- const SizedBox(height: 12),
|
|
|
- Text(syncState.message, style: theme.textTheme.bodyMedium),
|
|
|
- const SizedBox(height: 8),
|
|
|
- Text('${(syncState.progress * 100).toInt()}%',
|
|
|
- style: theme.textTheme.bodySmall),
|
|
|
- ] else if (syncState.error != null) ...[
|
|
|
- Card(
|
|
|
- color: theme.colorScheme.errorContainer,
|
|
|
- child: Padding(
|
|
|
- padding: const EdgeInsets.all(12),
|
|
|
- child: Text(syncState.error!,
|
|
|
- style: TextStyle(
|
|
|
- color: theme.colorScheme.onErrorContainer)),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ 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,
|
|
|
),
|
|
|
- ] else if (syncState.progress >= 1.0) ...[
|
|
|
- Icon(Icons.check_circle, size: 48, color: Colors.green),
|
|
|
- const SizedBox(height: 8),
|
|
|
- const Text('Completato!'),
|
|
|
- ],
|
|
|
- const SizedBox(height: 12),
|
|
|
- CheckboxListTile(
|
|
|
- value: _forceFullSync,
|
|
|
- onChanged: syncState.isSyncing
|
|
|
- ? null
|
|
|
- : (v) => setState(() => _forceFullSync = v!),
|
|
|
- title: const Text('Sincronizzazione completa'),
|
|
|
- subtitle:
|
|
|
- const Text('Forza il download completo di tutti i dati'),
|
|
|
- controlAffinity: ListTileControlAffinity.leading,
|
|
|
- enabled: !syncState.isSyncing,
|
|
|
),
|
|
|
- const SizedBox(height: 12),
|
|
|
- FilledButton.icon(
|
|
|
- onPressed: syncState.isSyncing || agente == null
|
|
|
- ? null
|
|
|
- : () => ref.read(syncProvider.notifier).startFullSync(
|
|
|
- agente.idAgente,
|
|
|
- agente.codAgente!,
|
|
|
- forceFullSync: _forceFullSync,
|
|
|
- ),
|
|
|
- icon: syncState.isSyncing
|
|
|
- ? const SizedBox(
|
|
|
- width: 20,
|
|
|
- height: 20,
|
|
|
- child: CircularProgressIndicator(strokeWidth: 2))
|
|
|
- : const Icon(Icons.sync),
|
|
|
- label: Text(syncState.isSyncing
|
|
|
- ? 'In corso...'
|
|
|
- : 'Avvia Sincronizzazione'),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+
|
|
|
+ // 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<Color>(
|
|
|
+ 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<BoxShadow>? 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),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
),
|
|
|
),
|
|
|
);
|