Add backup and restore functionality for rental data in settings

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-20 09:32:02 +01:00
parent df7705a224
commit 477ca0ee07
5 changed files with 155 additions and 12 deletions
+87 -5
View File
@@ -1,3 +1,4 @@
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:rental_income_tracker/state/rent_controller.dart';
@@ -12,6 +13,32 @@ class SettingsScreen extends StatefulWidget {
class _SettingsScreenState extends State<SettingsScreen> {
final TextEditingController _rentController = TextEditingController();
Future<bool> _confirmImport() async {
final result = await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Import backup file?'),
content: const Text(
'This will overwrite current local settings and logged entries with the contents of the selected backup file.',
),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Cancel'),
),
FilledButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('Import'),
),
],
);
},
);
return result ?? false;
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
@@ -103,16 +130,71 @@ class _SettingsScreenState extends State<SettingsScreen> {
const SizedBox(height: 12),
FilledButton.icon(
onPressed: () async {
final path = await controller.exportJson();
final directoryPath = await FilePicker.platform
.getDirectoryPath(dialogTitle: 'Choose backup folder');
if (directoryPath == null || !context.mounted) {
return;
}
final path = await controller.exportBackupToDirectory(
directoryPath,
);
if (!context.mounted) {
return;
}
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Exported to $path')));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Backup saved to $path')),
);
},
icon: const Icon(Icons.download),
label: const Text('Export JSON'),
label: const Text('Create backup'),
),
const SizedBox(height: 12),
FilledButton.icon(
onPressed: () async {
final shouldImport = await _confirmImport();
if (!shouldImport || !context.mounted) {
return;
}
final result = await FilePicker.platform.pickFiles(
dialogTitle: 'Choose backup file',
type: FileType.custom,
allowedExtensions: <String>['json'],
withData: false,
);
final filePath = result?.files.single.path;
if (filePath == null || !context.mounted) {
return;
}
try {
final path = await controller.importBackupFromFile(
filePath,
);
if (!context.mounted) {
return;
}
_rentController.text = controller.settings.rentUsd
.toStringAsFixed(2);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Imported backup from $path')),
);
} catch (_) {
if (!context.mounted) {
return;
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
controller.errorMessage ?? 'Restore failed.',
),
),
);
}
},
icon: const Icon(Icons.restore),
label: const Text('Import backup file'),
),
const SizedBox(height: 16),
const Text(