feat(backup): add automatic weekly encrypted backups
A silent safety net against in-app data loss or DB corruption, with zero friction. Driven off the app lifecycle (startup + when hidden), it writes a sealed .tanemaki copy into the app's private storage once a week, keeping the newest 3 (rotating). Reuses the manual backup's encryption via the extracted ExportImportService.buildSealedBackup(); never throws, so a failed copy can't crash startup or backgrounding. Not registered on web (no file storage). Settings shows a quiet reassurance line with the date of the last automatic copy (localised via intl), hidden where no automatic backup exists.
This commit is contained in:
parent
cf6975b748
commit
9c449964fd
19 changed files with 559 additions and 29 deletions
|
|
@ -1,10 +1,12 @@
|
|||
import 'package:commons_core/commons_core.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../data/export_import/inventory_snapshot.dart';
|
||||
import '../di/injector.dart';
|
||||
import '../i18n/strings.g.dart';
|
||||
import '../services/auto_backup_service.dart';
|
||||
import '../services/export_import_service.dart';
|
||||
import '../services/recovery_sheet_service.dart';
|
||||
|
||||
|
|
@ -15,21 +17,32 @@ import '../services/recovery_sheet_service.dart';
|
|||
/// for confirmation first (it merges into the live inventory), then reports
|
||||
/// what happened in a SnackBar.
|
||||
class BackupSection extends StatelessWidget {
|
||||
const BackupSection({this.service, this.sheet, super.key});
|
||||
const BackupSection({this.service, this.sheet, this.autoBackup, super.key});
|
||||
|
||||
/// Injectable for widget tests; the app resolves them lazily (on tap) from
|
||||
/// the service locator so merely mounting Settings needs no DI setup.
|
||||
final ExportImportService? service;
|
||||
final RecoverySheetService? sheet;
|
||||
|
||||
/// Drives the reassurance line about automatic copies. When null (and none is
|
||||
/// registered — e.g. web) the line is simply hidden.
|
||||
final AutoBackupService? autoBackup;
|
||||
|
||||
ExportImportService get _service => service ?? getIt<ExportImportService>();
|
||||
RecoverySheetService get _sheet => sheet ?? getIt<RecoverySheetService>();
|
||||
|
||||
AutoBackupService? get _autoBackup =>
|
||||
autoBackup ??
|
||||
(getIt.isRegistered<AutoBackupService>()
|
||||
? getIt<AutoBackupService>()
|
||||
: null);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = context.t;
|
||||
return Column(
|
||||
children: [
|
||||
_AutoBackupTile(service: _autoBackup),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.save_alt_outlined),
|
||||
title: Text(t.backup.exportJson),
|
||||
|
|
@ -299,3 +312,37 @@ class BackupSection extends StatelessWidget {
|
|||
messenger.showSnackBar(SnackBar(content: Text(message)));
|
||||
}
|
||||
}
|
||||
|
||||
/// A quiet reassurance line: the app already keeps copies on its own, and this
|
||||
/// says when the last one was made. Hidden entirely when there is no automatic
|
||||
/// backup on this platform.
|
||||
class _AutoBackupTile extends StatelessWidget {
|
||||
const _AutoBackupTile({required this.service});
|
||||
|
||||
final AutoBackupService? service;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final service = this.service;
|
||||
if (service == null) return const SizedBox.shrink();
|
||||
final t = context.t;
|
||||
return FutureBuilder<DateTime?>(
|
||||
future: service.lastBackupAt(),
|
||||
builder: (context, snapshot) {
|
||||
final last = snapshot.data;
|
||||
final subtitle = last == null
|
||||
? t.backup.autoBackupNone
|
||||
: t.backup.autoBackupLast(
|
||||
date: DateFormat.yMMMd(
|
||||
LocaleSettings.currentLocale.languageCode,
|
||||
).format(last),
|
||||
);
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.shield_outlined),
|
||||
title: Text(t.backup.autoBackupTitle),
|
||||
subtitle: Text(subtitle),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue