import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import '../models/pending_operation.dart'; import '../services/sync_service.dart'; class SyncScreen extends StatefulWidget { const SyncScreen({ super.key, required this.syncService, }); final SyncService syncService; @override State createState() => _SyncScreenState(); } class _SyncScreenState extends State { final _date = DateFormat('dd/MM/yyyy HH:mm', 'fr_FR'); final _money = NumberFormat.currency(locale: 'fr_FR', symbol: 'MAD ', decimalDigits: 2); bool _loading = true; bool _syncing = false; bool _isOnline = false; String? _apiUrl; String? _message; DateTime? _lastSyncAt; List _pending = []; @override void initState() { super.initState(); _load(); } Future _load() async { setState(() { _loading = true; _message = null; }); try { final apiUrl = await widget.syncService.getApiUrl(); final isOnline = await widget.syncService.testBackend(); final snapshot = await widget.syncService.loadLocalSnapshot( isOnline: isOnline, message: null, ); final pending = await widget.syncService.loadPendingOperations(); if (!mounted) return; setState(() { _apiUrl = apiUrl; _isOnline = isOnline; _lastSyncAt = snapshot.lastSyncAt; _pending = pending; }); } catch (e) { if (!mounted) return; setState(() => _message = 'Erreur locale : $e'); } finally { if (mounted) setState(() => _loading = false); } } Future _syncNow() async { if (_syncing) return; setState(() { _syncing = true; _message = 'Synchronisation en cours...'; }); try { final snapshot = await widget.syncService.synchronize(); final pending = await widget.syncService.loadPendingOperations(); final apiUrl = await widget.syncService.getApiUrl(); if (!mounted) return; setState(() { _apiUrl = apiUrl; _isOnline = snapshot.isOnline; _lastSyncAt = snapshot.lastSyncAt; _pending = pending; _message = snapshot.isOnline ? 'Synchronisation terminée.' : 'Backend indisponible. Les opérations restent en attente.'; }); } catch (e) { if (!mounted) return; setState(() => _message = 'Erreur de synchronisation : $e'); } finally { if (mounted) setState(() => _syncing = false); } } @override Widget build(BuildContext context) { final pendingCount = _pending.length; return Scaffold( appBar: AppBar( title: const Text('Synchronisation'), actions: [ IconButton( onPressed: _loading || _syncing ? null : _load, icon: const Icon(Icons.refresh), ), ], ), bottomNavigationBar: SafeArea( minimum: const EdgeInsets.fromLTRB(16, 8, 16, 12), child: FilledButton.icon( onPressed: _syncing ? null : _syncNow, icon: _syncing ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.sync), label: Text(_syncing ? 'Synchronisation...' : 'Synchroniser maintenant'), ), ), body: _loading ? const Center(child: CircularProgressIndicator()) : ListView( padding: const EdgeInsets.fromLTRB(16, 16, 16, 110), children: [ Card( child: ListTile( leading: Icon(_isOnline ? Icons.cloud_done : Icons.cloud_off), title: Text(_isOnline ? 'Backend joignable' : 'Backend indisponible'), subtitle: Text(_apiUrl ?? 'URL API inconnue'), ), ), Card( child: ListTile( leading: const Icon(Icons.schedule), title: const Text('Dernière synchronisation réussie'), subtitle: Text( _lastSyncAt == null ? 'Aucune synchronisation réussie' : _date.format(_lastSyncAt!.toLocal()), ), ), ), Card( child: ListTile( leading: Icon(pendingCount == 0 ? Icons.check_circle : Icons.pending_actions), title: Text('$pendingCount opération(s) en attente'), subtitle: Text( pendingCount == 0 ? 'Aucune opération locale à envoyer.' : 'Ces opérations seront envoyées quand le backend sera disponible.', ), ), ), if (_message != null && _message!.isNotEmpty) ...[ const SizedBox(height: 8), Card( child: Padding( padding: const EdgeInsets.all(12), child: Text(_message!), ), ), ], const SizedBox(height: 16), Text('File d’attente', style: Theme.of(context).textTheme.titleLarge), const SizedBox(height: 8), if (_pending.isEmpty) const Card( child: ListTile( leading: Icon(Icons.inbox), title: Text('Aucune opération en attente'), ), ) else ..._pending.map((operation) => _PendingOperationTile( operation: operation, date: _date, money: _money, )), ], ), ); } } class _PendingOperationTile extends StatelessWidget { const _PendingOperationTile({ required this.operation, required this.date, required this.money, }); final PendingOperation operation; final DateFormat date; final NumberFormat money; @override Widget build(BuildContext context) { final payload = operation.payload; final label = payload['label']?.toString() ?? 'Opération mobile'; final amount = (payload['amount'] as num? ?? 0).toDouble(); final type = payload['type']?.toString() ?? operation.operationType; final accountId = payload['account_id']?.toString() ?? '?'; final targetAccountId = payload['target_account_id']?.toString(); final typeLabel = switch (type) { 'expense' => 'Dépense', 'income' => 'Recette', 'transfer' => 'Transfert', _ => type, }; final accountLabel = type == 'transfer' && targetAccountId != null ? 'Compte $accountId → compte $targetAccountId' : 'Compte $accountId'; return Card( child: ListTile( leading: const Icon(Icons.schedule_send), title: Text(label), subtitle: Text( '$typeLabel • $accountLabel\nCréée le ${date.format(operation.createdAt.toLocal())}' '${operation.error == null ? '' : '\nDernière erreur : ${operation.error}'}', ), isThreeLine: true, trailing: Text( money.format(amount), style: const TextStyle(fontWeight: FontWeight.bold), ), ), ); } }