import 'dart:convert'; import 'dart:io'; import 'package:archive/archive_io.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_file_dialog/flutter_file_dialog.dart'; import 'package:intl/intl.dart'; import 'package:path_provider/path_provider.dart'; import 'package:prasule/api/wallet.dart'; import 'package:prasule/main.dart'; /// Used for [Wallet]-managing operations class WalletManager { /// Currently selected wallet static Wallet? selectedWallet; /// Path to the directory with wallet files /// /// Saved beforehand so we don't have to use async everywhere static late String walletPath; /// Returns a list of all [Wallet]s static List listWallets() { final path = Directory(walletPath); if (!path.existsSync()) { path.createSync(); } final wallets = []; for (final w in path .listSync() .whereType() .where((e) => e.path.endsWith(".json")) .map((e) => e.path.split("/").last) .toList()) { try { wallets.add(loadWallet(w)); } catch (e) { logger.e(e); // TODO: do something with unreadable wallets } } return wallets; } /// Deletes all [Wallet]s static void deleteAllData() { final path = Directory(walletPath); if (!path.existsSync()) { return; } for (final entry in path.listSync()) { logger.d("Deleting ${entry.path}"); entry.deleteSync(); } } /// Creates a ZIP archive from all wallets static Future exportAllWallets() async { if (kIsWeb) { // TODO return; } final archive = Archive(); for (final w in Directory( "${(await getApplicationDocumentsDirectory()).path}/wallets", ).listSync()) { if (w is! File) continue; logger.i("Zipping ${w.path.split("/").last}"); final wf = w; archive.addFile( ArchiveFile.stream( wf.path.split("/").last, wf.lengthSync(), InputFileStream(wf.path), ), ); } if (!await FlutterFileDialog.isPickDirectorySupported()) { File( "${(await getApplicationDocumentsDirectory()).path}/export_${DateFormat("dd_MM_yyyy").format(DateTime.now())}.zip", ).writeAsBytesSync(ZipEncoder().encode(archive) ?? []); return; } final dir = await FlutterFileDialog.pickDirectory(); if (dir == null) return; await FlutterFileDialog.saveFileToDirectory( directory: dir, data: Uint8List.fromList(ZipEncoder().encode(archive) ?? []), fileName: "export_${DateFormat("dd_MM_yyyy").format(DateTime.now())}.zip", mimeType: "application/zip", ); } /// Exports a single [Wallet] static Future exportWallet({Wallet? wallet, String? name}) async { if (wallet == null && name == null) { throw Exception("You need to specify either a wallet or a name"); } final n = name ?? wallet!.name; if (!await FlutterFileDialog.isPickDirectorySupported()) { File("${(await getApplicationDocumentsDirectory()).path}/wallets/$n") .copySync( "${await getApplicationDocumentsDirectory()}/export_${n.replaceAll(RegExp('[|\\?*<":>+[]/\' ]+'), '_')}_${DateFormat("dd_MM_yyyy").format(DateTime.now())}.json", ); return; } final dir = await FlutterFileDialog.pickDirectory(); if (dir == null) return; await FlutterFileDialog.saveFileToDirectory( directory: dir, data: File("${(await getApplicationDocumentsDirectory()).path}/wallets/$n") .readAsBytesSync(), fileName: "export_${n.replaceAll(RegExp('[|\\?*<":>+[]/\' ]+'), '_')}_${DateFormat("dd_MM_yyyy").format(DateTime.now())}.json", mimeType: "application/json", ); } /// Import a single wallet static Future importWallet({String? data}) async { var d = data ?? ""; if (data == null) { final filePath = await FlutterFileDialog.pickFile( params: const OpenFileDialogParams( mimeTypesFilter: ["application/json"], fileExtensionsFilter: ["json"], ), ); if (filePath == null) return; d = File(filePath).readAsStringSync(); } final w = Wallet.fromJson(jsonDecode(d) as Map); if (WalletManager.exists(w.name)) { throw Exception("Wallet already exists!"); } WalletManager.saveWallet( w, ); } /// Imports wallets from a ZIP archive static Future importArchive() async { final filePath = await FlutterFileDialog.pickFile( params: const OpenFileDialogParams( mimeTypesFilter: ["application/zip"], fileExtensionsFilter: ["zip"], ), ); if (filePath == null) return; if (kIsWeb) { // TODO return; } final temp = Directory("${(await getTemporaryDirectory()).path}/data"); if (temp.existsSync()) { temp.deleteSync(); } temp.createSync(recursive: true); final archive = ZipDecoder().decodeBuffer(InputFileStream(filePath)); for (final file in archive.files) { if (!file.isFile) { logger.d(file.name); continue; } file.writeContent(OutputFileStream("${temp.path}/${file.name}")); } for (final e in temp.listSync()) { logger.d(e.path); if (e is! File) continue; await importWallet(data: e.readAsStringSync()); } } /// Loads and returns a single [Wallet] by name static Wallet loadWallet(String name) { final path = Directory(walletPath); final wallet = File("${path.path}/$name"); if (!path.existsSync()) { path.createSync(); } if (!wallet.existsSync()) { throw Exception("Wallet does not exist"); } return Wallet.fromJson( jsonDecode(wallet.readAsStringSync()) as Map, ); } /// Converts [Wallet] to JSON and saves it to AppData static void saveWallet(Wallet w) { final path = Directory(walletPath); final wallet = File("${path.path}/${w.name}"); if (!path.existsSync()) { path.createSync(); } // if (!wallet.existsSync()) return false; wallet.writeAsStringSync(jsonEncode(w.toJson())); } /// Deletes the corresponding [Wallet] file static void deleteWallet(Wallet w) { final path = Directory(walletPath); File("${path.path}/${w.name}").deleteSync(); } /// Checks if the wallet exists static bool exists(String name) { return File( "$walletPath/$name", ).existsSync(); } }