Merge branch 'main' into spike/block2-derisking
# Conflicts: # apps/app_seeds/lib/app.dart # apps/app_seeds/lib/i18n/strings.g.dart # apps/app_seeds/lib/main.dart
This commit is contained in:
commit
cf99b7ff87
23 changed files with 607 additions and 25 deletions
42
apps/app_seeds/lib/ui/auto_backup_gate.dart
Normal file
42
apps/app_seeds/lib/ui/auto_backup_gate.dart
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../services/auto_backup_service.dart';
|
||||
|
||||
/// Drives the automatic backup off the app lifecycle: once on startup and again
|
||||
/// each time the app becomes hidden (backgrounded on mobile, window hidden on
|
||||
/// desktop). The [AutoBackupService] decides whether a copy is actually due, so
|
||||
/// these are cheap no-ops most of the time.
|
||||
///
|
||||
/// When [service] is null (widget tests, or platforms without file storage such
|
||||
/// as web) it wraps [child] untouched and does nothing.
|
||||
class AutoBackupGate extends StatefulWidget {
|
||||
const AutoBackupGate({required this.child, this.service, super.key});
|
||||
|
||||
final Widget child;
|
||||
final AutoBackupService? service;
|
||||
|
||||
@override
|
||||
State<AutoBackupGate> createState() => _AutoBackupGateState();
|
||||
}
|
||||
|
||||
class _AutoBackupGateState extends State<AutoBackupGate> {
|
||||
AppLifecycleListener? _listener;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final service = widget.service;
|
||||
if (service == null) return;
|
||||
_listener = AppLifecycleListener(onHide: () => service.runIfDue());
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => service.runIfDue());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_listener?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => widget.child;
|
||||
}
|
||||
|
|
@ -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