118 lines
3.2 KiB
Dart
118 lines
3.2 KiB
Dart
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<Expense>;
|
|
|
|
@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<String, dynamic> 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<ExpensesManager> expenses(Ref ref) async => ExpensesManager.instance();
|
|
|
|
class ExpensesManager {
|
|
final String storagePath;
|
|
|
|
ExpensesManager._({required this.storagePath});
|
|
|
|
/// Get an instance of this manager
|
|
static Future<ExpensesManager> 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<ExpensesList> getList() async {
|
|
final jsonDec = jsonDecode(await expenseFile.readAsString());
|
|
return List<Expense>.from(jsonDec.map((m) => Expense.fromJson(m)));
|
|
}
|
|
|
|
/// Save the list of expenses
|
|
Future<void> saveList(ExpensesList list) async {
|
|
final jsonDoc = jsonEncode(list.map((t) => t.toJson()));
|
|
await expenseFile.writeAsString(jsonDoc);
|
|
}
|
|
|
|
/// Add a new expense to the list
|
|
Future<void> add({
|
|
required String? label,
|
|
required double cost,
|
|
required int time,
|
|
required List<int> 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);
|
|
}
|
|
}
|