import 'dart:convert'; import 'dart:io'; import 'package:flutter/services.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:login_vault_app/database_helper.dart'; import 'package:login_vault_app/encryptor.dart'; import 'package:login_vault_app/auth_service.dart'; import 'package:login_vault_app/constants.dart'; /// Backup & restore the local vault to a file on device storage. /// The file is encrypted with the app password (not the constant key), so it /// stays safe if copied to Drive / shared. User can later move the .enc file /// to Google Drive manually. class BackupService { /// Export all vault entries into an encrypted JSON file. /// Returns the saved file path, or throws on error. static Future exportEncrypted(AuthService auth) async { final pw = await auth.getAppPassword(); if (pw == null) throw Exception('App password belum diset'); final all = await DatabaseHelper().getAll(); final plain = jsonEncode({ 'app': 'login_vault', 'version': 1, 'entries': all.map((e) => {'id': e.id, 'content': e.content}).toList(), }); // encrypt with app password-derived key final enc = Encryptor.encryptWithPassword(plain, pw); final dir = await _targetDir(); final ts = DateTime.now().toIso8601String().replaceAll(':', '-').split('.')[0]; final file = File('${dir.path}/loginvault_backup_$ts.enc'); await file.writeAsString(enc); return file.path; } /// Import an encrypted backup file into the DB (merge by id, overwrite). /// [path] is the file the user picked (or default backup file). static Future importEncrypted(AuthService auth, String path) async { final pw = await auth.getAppPassword(); if (pw == null) throw Exception('App password belum diset'); final file = File(path); if (!await file.exists()) throw Exception('File backup tidak ditemukan'); final enc = await file.readAsString(); final plain = Encryptor.decryptWithPassword(enc, pw); final data = jsonDecode(plain) as Map; final entries = (data['entries'] as List).cast>(); final db = DatabaseHelper(); int count = 0; for (final e in entries) { final id = e['id'] as String; final content = e['content'] as String; // insert or update try { await db.insert(VaultEntry(id: id, content: content)); } catch (_) { await db.update(id, content); } count++; } return count; } /// Default backup directory: /// - Android: Download/loginvault (so user can move to Drive) /// - others: app documents dir static Future _targetDir() async { if (Platform.isAndroid) { // request storage permission for Android < 33 (scoped) if (await Permission.storage.request().isGranted || await Permission.manageExternalStorage.request().isGranted) { final download = Directory('/storage/emulated/0/Download/loginvault'); if (!await download.exists()) await download.create(recursive: true); return download; } } final docs = await getExternalStorageDirectory(); final dir = Directory('${docs?.path ?? (await getApplicationDocumentsDirectory()).path}/loginvault'); if (!await dir.exists()) await dir.create(recursive: true); return dir; } /// List existing backup files in the default folder. static Future> listBackups() async { try { final dir = await _targetDir(); return dir .listSync() .where((f) => f.path.endsWith('.enc')) .toList(); } catch (_) { return []; } } }