import 'package:flutter/material.dart';
import 'master_data_sync.dart';
import 'master_data_sync_repository.dart';
import 'report_asset_sync.dart';
import 'report_sync.dart';
class MasterDataSyncCard extends StatelessWidget {
const MasterDataSyncCard({super.key, this.controller});
final MasterDataSyncController? controller;
Future<void> _sync(
BuildContext context,
MasterDataSyncController sync,
) async {
if (sync.requiresInitialDecision) {
final decision = await showDialog<InitialSyncDecision>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Erste Synchronisation'),
content: const Text(
'Auf diesem Gerät liegen lokale Stammdaten. Entscheide ausdrücklich, ob sie zum Server importiert werden sollen. Ohne Import bleiben sie lokal und werden nicht hochgeladen oder überschrieben.',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Abbrechen'),
),
TextButton(
onPressed: () =>
Navigator.pop(context, InitialSyncDecision.serverOnly),
child: const Text('Nur Serverdaten ergänzen'),
),
FilledButton(
onPressed: () =>
Navigator.pop(context, InitialSyncDecision.importLocal),
child: const Text('Lokale Daten importieren'),
),
],
),
);
if (decision == null) return;
await sync.chooseInitialSync(decision);
}
await sync.synchronize();
await ReportSyncController.instance.synchronize(force: true);
await ReportAssetSyncController.instance.synchronize(force: true);
}
@override
Widget build(BuildContext context) {
final sync = controller ?? MasterDataSyncController.instance;
return ListenableBuilder(
listenable: sync,
builder: (context, _) => Card(
child: LayoutBuilder(
builder: (context, constraints) {
final icon = sync.isSyncing
? const SizedBox.square(
dimension: 24,
child: CircularProgressIndicator(strokeWidth: 2.5),
)
: Icon(_icon(sync.status), color: _color(sync.status));
final description = Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Synchronisation',
style: TextStyle(fontWeight: FontWeight.w700),
),
const SizedBox(height: 4),
Text(_description(sync)),
if (sync.conflictCount > 0)
TextButton(
onPressed: sync.isSyncing
? null
: () => sync.discardConflicts(),
child: const Text('Lokale Konfliktänderung verwerfen'),
),
],
),
);
final button = FilledButton.icon(
onPressed:
sync.isSyncing ||
sync.status == MasterDataSyncStatus.unavailable
? null
: () => _sync(context, sync),
icon: const Icon(Icons.sync_rounded),
label: const Text('Alles synchronisieren'),
);
return Padding(
padding: const EdgeInsets.all(20),
child: constraints.maxWidth < 520
? Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
icon,
const SizedBox(width: 16),
description,
],
),
const SizedBox(height: 16),
button,
],
)
: Row(
children: [
icon,
const SizedBox(width: 16),
description,
const SizedBox(width: 16),
button,
],
),
);
},
),
),
);
}
String _description(MasterDataSyncController sync) {
if (sync.requiresInitialDecision) {
return 'Entscheidung zum vorhandenen lokalen Datenbestand erforderlich.';
}
if (sync.message != null) return sync.message!;
if (sync.pendingCount > 0) {
return '${sync.pendingCount} Stammdatenänderung(en) warten auf den nächsten Versuch.';
}
if (sync.lastSyncAt != null) {
return 'Zuletzt synchronisiert: ${sync.lastSyncAt!.toLocal()}';
}
return sync.status == MasterDataSyncStatus.unavailable
? 'Nur nach mobiler Anmeldung verfügbar.'
: 'Berichte und Dateien werden beim manuellen Start ebenfalls sofort übertragen.';
}
IconData _icon(MasterDataSyncStatus status) => switch (status) {
MasterDataSyncStatus.conflict => Icons.sync_problem_rounded,
MasterDataSyncStatus.offline => Icons.cloud_off_rounded,
MasterDataSyncStatus.error => Icons.error_outline_rounded,
MasterDataSyncStatus.success => Icons.cloud_done_outlined,
_ => Icons.sync_rounded,
};
Color _color(MasterDataSyncStatus status) => switch (status) {
MasterDataSyncStatus.conflict || MasterDataSyncStatus.error => Colors.red,
MasterDataSyncStatus.offline => Colors.orange,
_ => Colors.green,
};
}
