From 51ba649b6eadcfbee139541757eab37a3669c5c0 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Mon, 14 Jul 2025 08:00:35 +0200 Subject: [PATCH] Can save the list of expenses --- .../lib/services/storage/expenses.dart | 117 ++++++++++++++++++ moneymgr_mobile/pubspec.lock | 2 +- moneymgr_mobile/pubspec.yaml | 3 + 3 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 moneymgr_mobile/lib/services/storage/expenses.dart diff --git a/moneymgr_mobile/lib/services/storage/expenses.dart b/moneymgr_mobile/lib/services/storage/expenses.dart new file mode 100644 index 0000000..2013a7c --- /dev/null +++ b/moneymgr_mobile/lib/services/storage/expenses.dart @@ -0,0 +1,117 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:math'; + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'expenses.freezed.dart'; +part 'expenses.g.dart'; + +typedef ExpensesList = List; + +@freezed +abstract class Expense with _$Expense { + const Expense._(); + + const factory Expense({ + /// Internal id used to identify the expense + required int id, + + /// Label of the expense + required String? label, + + /// The cost shall always be a positive value + required double cost, + + /// Time associated with the expense + required int time, + + /// Associated file mime type + required String mimeType, + }) = _Expense; + + factory Expense.fromJson(Map json) => + _$ExpenseFromJson(json); + + /// Get associated expense file name + String get localFileName { + if (mimeType == "application/pdf") return "$id.pdf"; + if (mimeType == "image/jpeg") return "$id.jpeg"; + if (mimeType == "image/png") return "$id.png"; + return id.toString(); + } +} + +@riverpod +Future expenses(Ref ref) async => ExpensesManager.instance(); + +class ExpensesManager { + final String storagePath; + + ExpensesManager._({required this.storagePath}); + + /// Get an instance of this manager + static Future instance() async { + final appDir = await getApplicationDocumentsDirectory(); + final subDir = p.join(appDir.absolute.path, "expenses"); + final result = await Directory(subDir).create(recursive: true); + + return ExpensesManager._(storagePath: result.absolute.path); + } + + /// Get expenses list file path + File get expenseFile => File(p.join(storagePath, "list.json")); + + /// Get the files storage path + Directory get filesStoragePath => Directory(p.join(storagePath, "exp_files")); + + /// Get the current list of expenses + Future getList() async { + final jsonDec = jsonDecode(await expenseFile.readAsString()); + return List.from(jsonDec.map((m) => Expense.fromJson(m))); + } + + /// Save the list of expenses + Future saveList(ExpensesList list) async { + final jsonDoc = jsonEncode(list.map((t) => t.toJson())); + await expenseFile.writeAsString(jsonDoc); + } + + /// Add a new expense to the list + Future add({ + required String? label, + required double cost, + required int time, + required List fileContent, + required String fileMimeType, + }) async { + final list = await getList(); + + final exp = Expense( + id: (list.lastOrNull?.id ?? 0) + Random().nextInt(1000), + label: label, + cost: cost, + time: time, + mimeType: fileMimeType, + ); + + // Create files storage directory if required + if (!await filesStoragePath.exists()) { + await filesStoragePath.create(recursive: true); + } + + // Save associated file + final file = File( + p.join(filesStoragePath.absolute.path, exp.localFileName), + ); + await file.writeAsBytes(fileContent); + + // Save the list of expenses + list.add(exp); + await saveList(list); + } +} diff --git a/moneymgr_mobile/pubspec.lock b/moneymgr_mobile/pubspec.lock index e724eec..5d19604 100644 --- a/moneymgr_mobile/pubspec.lock +++ b/moneymgr_mobile/pubspec.lock @@ -673,7 +673,7 @@ packages: source: hosted version: "1.9.1" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" diff --git a/moneymgr_mobile/pubspec.yaml b/moneymgr_mobile/pubspec.yaml index 6182b34..6ddd402 100644 --- a/moneymgr_mobile/pubspec.yaml +++ b/moneymgr_mobile/pubspec.yaml @@ -85,6 +85,9 @@ dependencies: # https://developers.google.com/ml-kit/tips/installation-paths scanbot_sdk: ^7.0.0 + # Get documents path + path_provider: ^2.1.5 + dev_dependencies: flutter_test: sdk: flutter