import 'dart:convert'; import 'dart:io'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import '../utils/log.dart'; /// Eén automatisch bewaard herstelbestand voor een (nog) niet-opgeslagen deck. class RecoverySnapshot { final String id; final DateTime savedAt; final String? filePath; final String label; final String markdown; const RecoverySnapshot({ required this.id, required this.savedAt, required this.filePath, required this.label, required this.markdown, }); Map toJson() => { 'id': id, 'savedAt': savedAt.toIso8601String(), 'filePath': filePath, 'label': label, 'markdown': markdown, }; static RecoverySnapshot fromJson(Map json) { return RecoverySnapshot( id: json['id'] as String, savedAt: DateTime.tryParse(json['savedAt'] as String? ?? '') ?? DateTime.now(), filePath: json['filePath'] as String?, label: (json['label'] as String?) ?? 'Presentatie', markdown: (json['markdown'] as String?) ?? '', ); } } /// Schrijft en leest autosave-herstelbestanden. Elk dirty tabblad krijgt een /// eigen `.json` in de herstelmap; bij opslaan/sluiten wordt het gewist, /// zodat resterende bestanden bij de volgende start op niet-opgeslagen werk /// (een crash) wijzen. class RecoveryService { /// Tests injecteren een tijdelijke map; in productie wordt de app-support-map /// gebruikt (path_provider). final Future Function() _resolveDir; RecoveryService({Directory? baseDir}) : _resolveDir = baseDir != null ? (() async => baseDir) : _defaultDir; static Future _defaultDir() async { final support = await getApplicationSupportDirectory(); return Directory(p.join(support.path, 'recovery')); } Future _dir() async { final dir = await _resolveDir(); if (!dir.existsSync()) await dir.create(recursive: true); return dir; } File _file(Directory dir, String id) => File(p.join(dir.path, '$id.json')); Future save(RecoverySnapshot snapshot) async { try { final dir = await _dir(); await _file( dir, snapshot.id, ).writeAsString(jsonEncode(snapshot.toJson()), flush: true); } catch (e) { logWarning('RecoveryService.save: write recovery snapshot', e); // Autosave mag nooit de app verstoren. } } Future discard(String id) async { try { final file = _file(await _dir(), id); if (file.existsSync()) await file.delete(); } catch (e) { logWarning('RecoveryService.discard: delete recovery file', e); } } Future> loadAll() async { try { final dir = await _dir(); final out = []; for (final entry in dir.listSync()) { if (entry is File && entry.path.endsWith('.json')) { try { final data = jsonDecode(await entry.readAsString()); out.add(RecoverySnapshot.fromJson(Map.from(data))); } catch (e, s) { logError('RecoveryService.loadAll: decode recovery snapshot', e, s); } } } out.sort((a, b) => b.savedAt.compareTo(a.savedAt)); return out; } catch (e) { logWarning('RecoveryService.loadAll: list recovery dir', e); return const []; } } Future clearAll() async { try { final dir = await _dir(); for (final entry in dir.listSync()) { if (entry is File && entry.path.endsWith('.json')) { try { await entry.delete(); } catch (e) { logWarning('RecoveryService.clearAll: delete recovery file', e); } } } } catch (e) { logWarning('RecoveryService.clearAll: list recovery dir', e); } } } final recoveryServiceProvider = Provider( (_) => RecoveryService(), );