Add local backup/restore: encrypted .enc export to Download/loginvault, restore from list (no file_picker to avoid SDK conflict)
This commit is contained in:
@@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:login_vault_app/auth_service.dart';
|
||||
import 'package:login_vault_app/auth_screen.dart';
|
||||
import 'package:login_vault_app/theme_provider.dart';
|
||||
import 'package:login_vault_app/backup_service.dart';
|
||||
|
||||
/// Settings: change app password, switch theme (dark/system), session timeout,
|
||||
/// fingerprint toggle, and Exit.
|
||||
@@ -72,6 +73,73 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
_newCtrl.clear();
|
||||
}
|
||||
|
||||
Future<void> _backup() async {
|
||||
try {
|
||||
final path = await BackupService.exportEncrypted(widget.auth);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_msg = 'Backup tersimpan: $path';
|
||||
_msgColor = Colors.green;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_msg = 'Backup gagal: $e';
|
||||
_msgColor = Colors.red;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _restore() async {
|
||||
try {
|
||||
final backups = await BackupService.listBackups();
|
||||
if (backups.isEmpty) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_msg = 'Tidak ada file backup di folder Download/loginvault';
|
||||
_msgColor = Colors.red;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!mounted) return;
|
||||
final chosen = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('Pilih file backup'),
|
||||
content: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: backups.length,
|
||||
itemBuilder: (_, i) => ListTile(
|
||||
title: Text(backups[i].path.split('/').last),
|
||||
onTap: () => Navigator.of(ctx).pop(backups[i].path),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
if (chosen == null) return;
|
||||
final count = await BackupService.importEncrypted(widget.auth, chosen);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_msg = 'Restore berhasil: $count entri';
|
||||
_msgColor = Colors.green;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_msg = 'Restore gagal: $e';
|
||||
_msgColor = Colors.red;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _toggleBiometric(bool value) async {
|
||||
if (value && !_biometricAvailable) {
|
||||
setState(() {
|
||||
@@ -209,6 +277,22 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
child: Text(_msg!, style: TextStyle(color: _msgColor)),
|
||||
),
|
||||
const Divider(),
|
||||
const Text('Backup & Restore',
|
||||
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.backup),
|
||||
title: const Text('Backup ke file (lokal)'),
|
||||
subtitle: const Text(
|
||||
'Simpan vault terenkripsi ke folder Download. Bisa dipindah ke Drive manual.'),
|
||||
onTap: _backup,
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.restore),
|
||||
title: const Text('Restore dari file'),
|
||||
subtitle: const Text('Pilih file backup .enc untuk dikembalikan.'),
|
||||
onTap: _restore,
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.exit_to_app),
|
||||
title: const Text('Keluar (Exit)'),
|
||||
|
||||
Reference in New Issue
Block a user