From 3b9061264f64a7644f7acabd863a80abdd75a995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor?= Date: Mon, 20 Jan 2025 18:39:25 +0100 Subject: [PATCH 1/4] Allow choosing export directory for local backups --- lib/app/settings/export_page.dart | 13 ++++++++-- .../backup/backup_database_service.dart | 24 +++++++++---------- lib/core/utils/get_download_path.dart | 24 ------------------- 3 files changed, 22 insertions(+), 39 deletions(-) delete mode 100644 lib/core/utils/get_download_path.dart diff --git a/lib/app/settings/export_page.dart b/lib/app/settings/export_page.dart index 02fd8c19..17fa4433 100644 --- a/lib/app/settings/export_page.dart +++ b/lib/app/settings/export_page.dart @@ -1,3 +1,4 @@ +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:monekin/core/database/services/transaction/transaction_service.dart'; import 'package:monekin/core/presentation/widgets/dates/outlinedButtonStacked.dart'; @@ -54,6 +55,14 @@ class _ExportDataPageState extends State { ); } + Future pickDirectory() async { + String? result = await FilePicker.platform.getDirectoryPath(); + if (result != null) { + return '$result/'; + } + return '/'; + } + @override Widget build(BuildContext context) { final t = Translations.of(context); @@ -71,7 +80,7 @@ class _ExportDataPageState extends State { if (selectedExportFormat == _ExportFormats.db) { await BackupDatabaseService() - .downloadDatabaseFile(context) + .exportDatabaseFile(context, await pickDirectory()) .then((value) { print('EEEEEEEEEEE'); }).catchError((err) { @@ -80,7 +89,7 @@ class _ExportDataPageState extends State { } else { await BackupDatabaseService() .exportSpreadsheet( - context, + context, await pickDirectory(), await TransactionService.instance .getTransactions(filters: filters) .first) diff --git a/lib/core/database/backup/backup_database_service.dart b/lib/core/database/backup/backup_database_service.dart index 38c95051..47ed3f80 100644 --- a/lib/core/database/backup/backup_database_service.dart +++ b/lib/core/database/backup/backup_database_service.dart @@ -7,34 +7,33 @@ import 'package:intl/intl.dart'; import 'package:monekin/core/database/app_db.dart'; import 'package:monekin/core/database/services/app-data/app_data_service.dart'; import 'package:monekin/core/models/transaction/transaction.dart'; -import 'package:monekin/core/utils/get_download_path.dart'; import 'package:path/path.dart' as path; class BackupDatabaseService { AppDB db = AppDB.instance; - Future downloadDatabaseFile(BuildContext context) async { + Future exportDatabaseFile(BuildContext context, String exportPath) async { final messeger = ScaffoldMessenger.of(context); List dbFileInBytes = await File(await db.databasePath).readAsBytes(); - String downloadPath = await getDownloadPath(); - downloadPath = path.join( - downloadPath, + exportPath = path.join( + exportPath, "monekin-${DateFormat('yyyyMMdd-Hms').format(DateTime.now())}.db", ); - File downloadFile = File(downloadPath); + File downloadFile = File(exportPath); await downloadFile.writeAsBytes(dbFileInBytes); messeger.showSnackBar(SnackBar( - content: Text('Base de datos descargada con exito en $downloadPath'), + content: Text('Base de datos descargada con exito en $exportPath'), )); } Future exportSpreadsheet( BuildContext context, + String exportPath, List data, { String format = 'csv', String separator = ',', @@ -93,15 +92,14 @@ class BackupDatabaseService { } } - String downloadPath = await getDownloadPath(); - downloadPath = - '${downloadPath}Transactions-${DateFormat('yyyyMMdd-Hms').format(DateTime.now())}.csv'; + exportPath = + '${exportPath}Transactions-${DateFormat('yyyyMMdd-Hms').format(DateTime.now())}.csv'; - File downloadFile = File(downloadPath); + File exportFile = File(exportPath); - await downloadFile.writeAsString(csvData); + await exportFile.writeAsString(csvData); - return downloadPath; + return exportPath; } Future importDatabase() async { diff --git a/lib/core/utils/get_download_path.dart b/lib/core/utils/get_download_path.dart deleted file mode 100644 index 98dc3d87..00000000 --- a/lib/core/utils/get_download_path.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'dart:io'; - -import 'package:path_provider/path_provider.dart'; - -/// Since the `path_provider` library do not support the download path for android, this functions try to find it and get another directory for the download if fails. See: https://pub.dev/packages/path_provider#supported-platforms-and-paths -Future getDownloadPath() async { - String downloadPath = (await getTemporaryDirectory()).path; - - if (!Platform.isAndroid) { - final newPath = (await getDownloadsDirectory()); - if (newPath != null) { - downloadPath = newPath.path; - } - } else if ((await Directory('/storage/emulated/0/Download').exists())) { - downloadPath = '/storage/emulated/0/Download/'; - } else { - final newPath = (await getExternalStorageDirectory()); - if (newPath != null) { - downloadPath = newPath.path; - } - } - - return downloadPath; -} From 9ce52e8ab62fa53745141193e9e8ca5f6b8957ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor?= Date: Tue, 21 Jan 2025 11:05:22 +0100 Subject: [PATCH 2/4] Fix bug when user cancels file picker dialogue --- lib/app/settings/export_page.dart | 63 ++++++++++++++----------------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/lib/app/settings/export_page.dart b/lib/app/settings/export_page.dart index b965196e..4be8aa44 100644 --- a/lib/app/settings/export_page.dart +++ b/lib/app/settings/export_page.dart @@ -56,14 +56,6 @@ class _ExportDataPageState extends State { ); } - Future pickDirectory() async { - String? result = await FilePicker.platform.getDirectoryPath(); - if (result != null) { - return '$result/'; - } - return '/'; - } - @override Widget build(BuildContext context) { final t = Translations.of(context); @@ -77,32 +69,35 @@ class _ExportDataPageState extends State { child: FilledButton( child: Text(t.backup.export.title), onPressed: () async { - final messeger = ScaffoldMessenger.of(context); - - if (selectedExportFormat == _ExportFormats.db) { - await BackupDatabaseService() - .exportDatabaseFile(context, await pickDirectory()) - .then((value) { - Logger.printDebug('EEEEEEEEEEE'); - }).catchError((err) { - Logger.printDebug(err); - }); - } else { - await BackupDatabaseService() - .exportSpreadsheet( - context, await pickDirectory(), - await TransactionService.instance - .getTransactions(filters: filters) - .first) - .then((value) { - messeger.showSnackBar(SnackBar( - content: Text(t.backup.export.success(x: value)), - )); - }).catchError((err) { - messeger.showSnackBar(SnackBar( - content: Text('$err'), - )); - }); + String? path = await FilePicker.platform.getDirectoryPath(); + if (path != null) { + final messeger = ScaffoldMessenger.of(context); + if (selectedExportFormat == _ExportFormats.db) { + await BackupDatabaseService() + .exportDatabaseFile(context, path) + .then((value) { + print('EEEEEEEEEEE'); + }).catchError((err) { + print(err); + }); + } else { + await BackupDatabaseService() + .exportSpreadsheet( + context, + path, + await TransactionService.instance + .getTransactions(filters: filters) + .first) + .then((value) { + messeger.showSnackBar(SnackBar( + content: Text(t.backup.export.success(x: value)), + )); + }).catchError((err) { + messeger.showSnackBar(SnackBar( + content: Text('$err'), + )); + }); + } } }, )) From 13b58c24a3f39725789fbf3d5757ff02892b4886 Mon Sep 17 00:00:00 2001 From: Enrique Lozano Cebriano <61509169+enrique-lozano@users.noreply.github.com> Date: Tue, 21 Jan 2025 18:02:54 +0100 Subject: [PATCH 3/4] refactor: Remove flutter logic from the export service --- lib/app/settings/export_page.dart | 15 +++++++++------ .../database/backup/backup_database_service.dart | 10 +--------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/lib/app/settings/export_page.dart b/lib/app/settings/export_page.dart index 4be8aa44..13985499 100644 --- a/lib/app/settings/export_page.dart +++ b/lib/app/settings/export_page.dart @@ -4,7 +4,6 @@ import 'package:monekin/core/database/services/transaction/transaction_service.d import 'package:monekin/core/presentation/widgets/dates/outlinedButtonStacked.dart'; import 'package:monekin/core/presentation/widgets/persistent_footer_button.dart'; import 'package:monekin/core/presentation/widgets/transaction_filter/transaction_filters.dart'; -import 'package:monekin/core/utils/logger.dart'; import 'package:monekin/i18n/translations.g.dart'; import '../../core/database/backup/backup_database_service.dart'; @@ -69,21 +68,25 @@ class _ExportDataPageState extends State { child: FilledButton( child: Text(t.backup.export.title), onPressed: () async { + final messeger = ScaffoldMessenger.of(context); String? path = await FilePicker.platform.getDirectoryPath(); + if (path != null) { - final messeger = ScaffoldMessenger.of(context); if (selectedExportFormat == _ExportFormats.db) { await BackupDatabaseService() - .exportDatabaseFile(context, path) + .exportDatabaseFile(path) .then((value) { - print('EEEEEEEEEEE'); + messeger.showSnackBar(SnackBar( + content: Text(t.backup.export.success(x: path)), + )); }).catchError((err) { - print(err); + messeger.showSnackBar(SnackBar( + content: Text('$err'), + )); }); } else { await BackupDatabaseService() .exportSpreadsheet( - context, path, await TransactionService.instance .getTransactions(filters: filters) diff --git a/lib/core/database/backup/backup_database_service.dart b/lib/core/database/backup/backup_database_service.dart index 02f33167..c23c2ac9 100644 --- a/lib/core/database/backup/backup_database_service.dart +++ b/lib/core/database/backup/backup_database_service.dart @@ -2,7 +2,6 @@ import 'dart:io'; import 'package:csv/csv.dart'; import 'package:file_picker/file_picker.dart'; -import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:monekin/core/database/app_db.dart'; import 'package:monekin/core/database/services/app-data/app_data_service.dart'; @@ -13,9 +12,7 @@ import 'package:path/path.dart' as path; class BackupDatabaseService { AppDB db = AppDB.instance; - Future exportDatabaseFile(BuildContext context, String exportPath) async { - final messeger = ScaffoldMessenger.of(context); - + Future exportDatabaseFile(String exportPath) async { List dbFileInBytes = await File(await db.databasePath).readAsBytes(); exportPath = path.join( @@ -26,14 +23,9 @@ class BackupDatabaseService { File downloadFile = File(exportPath); await downloadFile.writeAsBytes(dbFileInBytes); - - messeger.showSnackBar(SnackBar( - content: Text('Base de datos descargada con exito en $exportPath'), - )); } Future exportSpreadsheet( - BuildContext context, String exportPath, List data, { String format = 'csv', From 7f6542f0a65b09d835a14e15533a6575cb265028 Mon Sep 17 00:00:00 2001 From: Enrique Lozano Cebriano <61509169+enrique-lozano@users.noreply.github.com> Date: Tue, 21 Jan 2025 18:05:05 +0100 Subject: [PATCH 4/4] refactor: Null assertion logic --- lib/app/settings/export_page.dart | 63 ++++++++++++++++--------------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/lib/app/settings/export_page.dart b/lib/app/settings/export_page.dart index 13985499..1ec56ffe 100644 --- a/lib/app/settings/export_page.dart +++ b/lib/app/settings/export_page.dart @@ -71,36 +71,39 @@ class _ExportDataPageState extends State { final messeger = ScaffoldMessenger.of(context); String? path = await FilePicker.platform.getDirectoryPath(); - if (path != null) { - if (selectedExportFormat == _ExportFormats.db) { - await BackupDatabaseService() - .exportDatabaseFile(path) - .then((value) { - messeger.showSnackBar(SnackBar( - content: Text(t.backup.export.success(x: path)), - )); - }).catchError((err) { - messeger.showSnackBar(SnackBar( - content: Text('$err'), - )); - }); - } else { - await BackupDatabaseService() - .exportSpreadsheet( - path, - await TransactionService.instance - .getTransactions(filters: filters) - .first) - .then((value) { - messeger.showSnackBar(SnackBar( - content: Text(t.backup.export.success(x: value)), - )); - }).catchError((err) { - messeger.showSnackBar(SnackBar( - content: Text('$err'), - )); - }); - } + if (path == null) { + // TODO: Maybe we should also add a snackbar here + return; + } + + if (selectedExportFormat == _ExportFormats.db) { + await BackupDatabaseService() + .exportDatabaseFile(path) + .then((value) { + messeger.showSnackBar(SnackBar( + content: Text(t.backup.export.success(x: path)), + )); + }).catchError((err) { + messeger.showSnackBar(SnackBar( + content: Text('$err'), + )); + }); + } else { + await BackupDatabaseService() + .exportSpreadsheet( + path, + await TransactionService.instance + .getTransactions(filters: filters) + .first) + .then((value) { + messeger.showSnackBar(SnackBar( + content: Text(t.backup.export.success(x: value)), + )); + }).catchError((err) { + messeger.showSnackBar(SnackBar( + content: Text('$err'), + )); + }); } }, ))