diff --git a/lib/clients/storage/storage_client.dart b/lib/clients/storage/storage_client.dart index 541eb6c..e138599 100644 --- a/lib/clients/storage/storage_client.dart +++ b/lib/clients/storage/storage_client.dart @@ -13,6 +13,11 @@ class StorageClient { return file.readAsString(); } + delete(String filename) async { + File file = await _getJson(filename); + await file.delete(); + } + Future _getJson(String filename) async { final dir = await getApplicationDocumentsDirectory(); final file = File('${dir.path}/$filename'); diff --git a/lib/domains/account/account_bloc.dart b/lib/domains/account/account_bloc.dart index ca63039..1523af2 100644 --- a/lib/domains/account/account_bloc.dart +++ b/lib/domains/account/account_bloc.dart @@ -5,13 +5,6 @@ import 'package:csv/csv.dart'; import 'package:equatable/equatable.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:formz/formz.dart'; -import 'package:tunas/domains/account/models/transaction_account.dart'; -import 'package:tunas/domains/account/models/transaction_category.dart'; -import 'package:tunas/domains/account/models/transaction_date.dart'; -import 'package:tunas/domains/account/models/transaction_description.dart'; -import 'package:tunas/domains/account/models/transaction_line.dart'; -import 'package:tunas/domains/account/models/transaction_value.dart'; import 'package:tunas/repositories/account/account_repository.dart'; import 'package:tunas/repositories/account/models/transaction.dart'; import 'package:uuid/uuid.dart'; @@ -25,36 +18,22 @@ class AccountBloc extends Bloc { AccountBloc({required AccountRepository accountRepository}) : _accountRepository = accountRepository, super(const AccountState()) { - on(_onAccountLoad); on(_onAccountImportJSON); on(_onAccountImportCSV); // on(_onAccountImportJSON); // on(_onAccountImportJSON); - on(_onTransactionDateChange); - on(_onTransactionCategoryChange); - on(_onTransactionDescriptionChange); - on(_onTransactionAccountChange); - on(_onTransactionValueChange); - on(_onTransactionOpenAddDialog); - on(_onTransactionHideAddDialog); - on(_onTransactionAddDialog); - on(_onTransactionSetCurrent); - on(_onTransactionDeleteCurrent); - - _accountRepository - .getTransactionsStream() - .listen((transactions) => add(AccountLoad(transactions))); } - _onAccountLoad(AccountLoad event, Emitter emit) { - var computeResult = _computeTransactionLine(event.transactions); - emit(state.copyWith( - transactions: event.transactions, - transactionsLines: computeResult.list, - globalTotal: computeResult.globalTotal, - accountsTotals: computeResult.accountsTotals, - categories: computeResult.categories - )); + double _universalConvertToDouble(dynamic value) { + if (value is String) { + return double.parse(value); + } else if (value is int) { + return value.toDouble(); + } else if (value is double) { + return value; + } else { + throw Error(); + } } _onAccountImportCSV( @@ -65,7 +44,7 @@ class AccountBloc extends Bloc { if (csvPath != null) { final File csv = File(csvPath); final String csvFileContent = await csv.readAsString(); - final List> csvList = const CsvToListConverter(fieldDelimiter: '|').convert(csvFileContent); + final List> csvList = const CsvToListConverter(fieldDelimiter: '|', eol: '\n').convert(csvFileContent); final transactions = csvList .map((line) => Transaction( @@ -74,10 +53,11 @@ class AccountBloc extends Bloc { category: line[1], description: line[2], account: line[3], - value: double.parse(line[4])) + value: _universalConvertToDouble(line[4])) ) .toList(); - + + await _accountRepository.deleteAccount(); await _accountRepository.saveTransactions(transactions); } } @@ -93,186 +73,8 @@ class AccountBloc extends Bloc { final List jsonList = jsonDecode(jsonString); final List transactions = jsonList.map((transaction) => Transaction.fromJson(transaction)).toList(); + await _accountRepository.deleteAccount(); await _accountRepository.saveTransactions(transactions); } } - - ({List list, double globalTotal, Map accountsTotals, List categories}) _computeTransactionLine(List transactions) { - double globalTotal = 0; - Map accountsTotals = {}; - List output = []; - Set categories = {}; - - for(var transaction in transactions) { - double subTotal = globalTotal + transaction.value; - globalTotal = subTotal; - - double accountTotal = accountsTotals[transaction.account] ?? 0; - accountTotal += transaction.value; - accountsTotals[transaction.account] = accountTotal; - categories.add(transaction.category); - - output.add(TransactionLine(transaction: transaction, subTotal: subTotal)); - } - - output.sort((a, b) => b.transaction.date.compareTo(a.transaction.date)); - - return (list: output, globalTotal: globalTotal, accountsTotals: accountsTotals, categories: categories.toList()); - } - - _onTransactionDateChange( - TransactionDateChange event, Emitter emit - ) { - final transactionDate = TransactionDate.dirty(event.date); - emit(state.copyWith( - transactionDate: transactionDate, - isValid: Formz.validate([transactionDate, state.transactionCategory, state.transactionDescription, state.transactionAccount, state.transactionValue]), - )); - } - - _onTransactionCategoryChange( - TransactionCategoryChange event, Emitter emit - ) { - final transactionCategory = TransactionCategory.dirty(event.category); - emit(state.copyWith( - transactionCategory: transactionCategory, - isValid: Formz.validate([state.transactionDate, transactionCategory, state.transactionDescription, state.transactionAccount, state.transactionValue]), - )); - } - - _onTransactionDescriptionChange( - TransactionDescriptionChange event, Emitter emit - ) { - final transactionDescription = TransactionDescription.dirty(event.description); - emit(state.copyWith( - transactionDescription: transactionDescription, - isValid: Formz.validate([state.transactionDate, state.transactionCategory, transactionDescription, state.transactionAccount, state.transactionValue]), - )); - } - - _onTransactionAccountChange( - TransactionAccountChange event, Emitter emit - ) { - final transactionAccount = TransactionAccount.dirty(event.account); - emit(state.copyWith( - transactionAccount: transactionAccount, - isValid: Formz.validate([state.transactionDate, state.transactionCategory, state.transactionDescription, transactionAccount, state.transactionValue]), - )); - } - - _onTransactionValueChange( - TransactionValueChange event, Emitter emit - ) { - try { - final transactionValue = TransactionValue.dirty(double.parse(event.value)); - emit(state.copyWith( - transactionValue: transactionValue, - isValid: Formz.validate([state.transactionDate, state.transactionCategory, state.transactionDescription, state.transactionAccount, transactionValue]), - )); - } catch (e) { - const transactionValue = TransactionValue.dirty(double.infinity); - emit(state.copyWith( - transactionValue: transactionValue, - isValid: Formz.validate([state.transactionDate, state.transactionCategory, state.transactionDescription, state.transactionAccount, transactionValue]), - )); - } - } - - _onTransactionOpenAddDialog( - TransactionOpenAddDialog event, Emitter emit - ) { - emit(state.copyWith(showAddDialog: true)); - } - - _onTransactionHideAddDialog( - TransactionHideAddDialog event, Emitter emit - ) { - emit(state.copyWith(showAddDialog: false)); - } - - _onTransactionAddDialog( - TransactionAdd event, Emitter emit - ) async { - if (state.isValid) { - List transactions = state.transactions; - Transaction? currentTransaction = state.currentTransaction; - if (currentTransaction != null) { - transactions.removeWhere((transaction) => transaction.uuid == currentTransaction.uuid); - } - - transactions.add(Transaction( - uuid: const Uuid().v8(), - date: state.transactionDate.value ?? DateTime.now(), - category: state.transactionCategory.value, - description: state.transactionDescription.value, - account: state.transactionAccount.value, - value: state.transactionValue.value - )); - final computeResult = _computeTransactionLine(transactions); - - await _accountRepository.saveTransactions(transactions); - - emit(state.copyWith( - currentTransaction: null, - transactionDate: const TransactionDate.pure(), - transactionCategory: const TransactionCategory.pure(), - transactionDescription: const TransactionDescription.pure(), - transactionAccount: const TransactionAccount.pure(), - transactionValue: const TransactionValue.pure(), - transactions: transactions, - transactionsLines: computeResult.list, - globalTotal: computeResult.globalTotal, - accountsTotals: computeResult.accountsTotals, - )); - } - } - - _onTransactionSetCurrent( - TransactionSetCurrent event, Emitter emit - ) { - Transaction? transaction = event.transaction; - if (transaction == null) { - emit(state.copyWith( - currentTransaction: event.transaction, - transactionDate: const TransactionDate.pure(), - transactionCategory: const TransactionCategory.pure(), - transactionDescription: const TransactionDescription.pure(), - transactionAccount: const TransactionAccount.pure(), - transactionValue: const TransactionValue.pure(), - )); - } else { - emit(state.copyWith( - currentTransaction: transaction, - transactionDate: TransactionDate.dirty(transaction.date), - transactionCategory: TransactionCategory.dirty(transaction.category), - transactionDescription: TransactionDescription.dirty(transaction.description), - transactionAccount: TransactionAccount.dirty(transaction.account), - transactionValue: TransactionValue.dirty(transaction.value), - )); - } - } - - _onTransactionDeleteCurrent( - TransactionDeleteCurrent event, Emitter emit - ) async { - Transaction? currentTransaction = state.currentTransaction; - if (currentTransaction != null) { - List transactions = state.transactions; - transactions.removeWhere((transaction) => transaction.uuid == currentTransaction.uuid); - final computeResult = _computeTransactionLine(transactions); - await _accountRepository.saveTransactions(transactions); - emit(state.copyWith( - currentTransaction: null, - transactionDate: const TransactionDate.pure(), - transactionCategory: const TransactionCategory.pure(), - transactionDescription: const TransactionDescription.pure(), - transactionAccount: const TransactionAccount.pure(), - transactionValue: const TransactionValue.pure(), - transactions: transactions, - transactionsLines: computeResult.list, - globalTotal: computeResult.globalTotal, - accountsTotals: computeResult.accountsTotals, - )); - } - } } diff --git a/lib/domains/account/account_event.dart b/lib/domains/account/account_event.dart index 5402d80..f1d0327 100644 --- a/lib/domains/account/account_event.dart +++ b/lib/domains/account/account_event.dart @@ -7,14 +7,6 @@ sealed class AccountEvent extends Equatable { List get props => []; } -final class AccountLoad extends AccountEvent { - final List transactions; - const AccountLoad(this.transactions); - - @override - List get props => [transactions]; -} - final class AccountImportCSV extends AccountEvent { const AccountImportCSV(); } @@ -30,61 +22,3 @@ final class AccountExportJSON extends AccountEvent { final class AccountExportCSV extends AccountEvent { const AccountExportCSV(); } - -final class TransactionDateChange extends AccountEvent { - final DateTime? date; - const TransactionDateChange(this.date); -} - -final class TransactionCategoryChange extends AccountEvent { - final String category; - const TransactionCategoryChange(this.category); - - @override - List get props => [category]; -} - -final class TransactionDescriptionChange extends AccountEvent { - final String description; - const TransactionDescriptionChange(this.description); - - @override - List get props => [description]; -} - -final class TransactionAccountChange extends AccountEvent { - final String account; - const TransactionAccountChange(this.account); - - @override - List get props => [account]; -} - -final class TransactionValueChange extends AccountEvent { - final String value; - const TransactionValueChange(this.value); - - @override - List get props => [value]; -} - -final class TransactionAdd extends AccountEvent { - const TransactionAdd(); -} - -final class TransactionOpenAddDialog extends AccountEvent { - const TransactionOpenAddDialog(); -} - -final class TransactionHideAddDialog extends AccountEvent { - const TransactionHideAddDialog(); -} - -final class TransactionSetCurrent extends AccountEvent { - final Transaction? transaction; - const TransactionSetCurrent(this.transaction); -} - -final class TransactionDeleteCurrent extends AccountEvent { - const TransactionDeleteCurrent(); -} \ No newline at end of file diff --git a/lib/domains/account/account_state.dart b/lib/domains/account/account_state.dart index 4bff110..3eebdbe 100644 --- a/lib/domains/account/account_state.dart +++ b/lib/domains/account/account_state.dart @@ -1,85 +1,12 @@ part of 'account_bloc.dart'; final class AccountState extends Equatable { - final List transactions; - final List transactionsLines; - final double globalTotal; - final Map accountsTotals; + const AccountState(); - final List categories; - - final TransactionDate transactionDate; - final TransactionCategory transactionCategory; - final TransactionDescription transactionDescription; - final TransactionAccount transactionAccount; - final TransactionValue transactionValue; - final bool isValid; - final bool showAddDialog; - - final Transaction? currentTransaction; - - const AccountState({ - this.transactions = const [], - this.transactionsLines = const [], - this.globalTotal = 0, - this.accountsTotals = const {}, - this.categories = const [], - this.transactionDate = const TransactionDate.pure(), - this.transactionCategory = const TransactionCategory.pure(), - this.transactionDescription = const TransactionDescription.pure(), - this.transactionAccount = const TransactionAccount.pure(), - this.transactionValue = const TransactionValue.pure(), - this.isValid = false, - this.showAddDialog = false, - this.currentTransaction - }); - - AccountState copyWith({ - List? transactions, - List? transactionsLines, - double? globalTotal, - Map? accountsTotals, - List? categories, - TransactionDate? transactionDate, - TransactionCategory? transactionCategory, - TransactionDescription? transactionDescription, - TransactionAccount? transactionAccount, - TransactionValue? transactionValue, - bool? isValid, - bool? showAddDialog, - Transaction? currentTransaction, - }) { - return AccountState( - transactions: transactions ?? this.transactions, - transactionsLines: transactionsLines ?? this.transactionsLines, - globalTotal: globalTotal ?? this.globalTotal, - accountsTotals: accountsTotals ?? this.accountsTotals, - categories: categories ?? this.categories, - transactionDate: transactionDate ?? this.transactionDate, - transactionCategory: transactionCategory ?? this.transactionCategory, - transactionDescription: transactionDescription ?? this.transactionDescription, - transactionAccount: transactionAccount ?? this.transactionAccount, - transactionValue: transactionValue ?? this.transactionValue, - isValid: isValid ?? this.isValid, - showAddDialog: showAddDialog ?? this.showAddDialog, - currentTransaction: currentTransaction ?? this.currentTransaction, - ); + AccountState copyWith() { + return const AccountState(); } @override - List get props => [ - transactions, - transactionsLines, - globalTotal, - accountsTotals, - categories, - transactionDate, - transactionCategory, - transactionDescription, - transactionAccount, - transactionValue, - isValid, - showAddDialog, - currentTransaction, - ]; + List get props => []; } diff --git a/lib/domains/budget/budget_bloc.dart b/lib/domains/budget/budget_bloc.dart index 1590c3a..0e3b6db 100644 --- a/lib/domains/budget/budget_bloc.dart +++ b/lib/domains/budget/budget_bloc.dart @@ -1,10 +1,27 @@ import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:tunas/repositories/account/account_repository.dart'; +import 'package:tunas/repositories/account/models/budget.dart'; part 'budget_event.dart'; part 'budget_state.dart'; class BudgetBloc extends Bloc { - BudgetBloc(super.initialState); + final AccountRepository _accountRepository; + BudgetBloc({required AccountRepository accountRepository}) : _accountRepository = accountRepository, super(const BudgetState()) { + on(_onBudgetsLoad); + + _accountRepository + .getBudgetsStream() + .listen((budgets) => add(BudgetsLoad(budgets))); + } + + _onBudgetsLoad( + BudgetsLoad event, Emitter emit + ) { + emit(state.copyWith( + budgets: event.budgets, + )); + } } \ No newline at end of file diff --git a/lib/domains/budget/budget_event.dart b/lib/domains/budget/budget_event.dart index bbf2a4c..83f15fe 100644 --- a/lib/domains/budget/budget_event.dart +++ b/lib/domains/budget/budget_event.dart @@ -5,4 +5,12 @@ sealed class BudgetEvent extends Equatable { @override List get props => []; +} + +final class BudgetsLoad extends BudgetEvent { + final List budgets; + const BudgetsLoad(this.budgets); + + @override + List get props => [budgets]; } \ No newline at end of file diff --git a/lib/domains/budget/budget_state.dart b/lib/domains/budget/budget_state.dart index 5cf1355..ce4bbde 100644 --- a/lib/domains/budget/budget_state.dart +++ b/lib/domains/budget/budget_state.dart @@ -1,6 +1,20 @@ part of 'budget_bloc.dart'; final class BudgetState extends Equatable { + final List budgets; + + const BudgetState({ + this.budgets = const [], + }); + + BudgetState copyWith({ + List? budgets, + }) { + return BudgetState( + budgets: budgets ?? this.budgets, + ); + } + @override - List get props => []; -} \ No newline at end of file + List get props => [budgets]; +} diff --git a/lib/domains/category/category_bloc.dart b/lib/domains/category/category_bloc.dart new file mode 100644 index 0000000..4c971ea --- /dev/null +++ b/lib/domains/category/category_bloc.dart @@ -0,0 +1,27 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:tunas/repositories/account/account_repository.dart'; +import 'package:tunas/repositories/account/models/category.dart'; + +part 'category_event.dart'; +part 'category_state.dart'; + +class CategoryBloc extends Bloc { + final AccountRepository _accountRepository; + + CategoryBloc({required AccountRepository accountRepository}) : _accountRepository = accountRepository, super(const CategoryState()) { + on(_onCategoriesLoad); + + _accountRepository + .getCategoriesStream() + .listen((categories) => add(CategoriesLoad(categories))); + } + + _onCategoriesLoad( + CategoriesLoad event, Emitter emit + ) { + emit(state.copyWith( + categories: event.categories, + )); + } +} diff --git a/lib/domains/category/category_event.dart b/lib/domains/category/category_event.dart new file mode 100644 index 0000000..02b270e --- /dev/null +++ b/lib/domains/category/category_event.dart @@ -0,0 +1,16 @@ +part of 'category_bloc.dart'; + +sealed class CategoryEvent extends Equatable { + const CategoryEvent(); + + @override + List get props => []; +} + +final class CategoriesLoad extends CategoryEvent { + final List categories; + const CategoriesLoad(this.categories); + + @override + List get props => [categories]; +} \ No newline at end of file diff --git a/lib/domains/category/category_state.dart b/lib/domains/category/category_state.dart new file mode 100644 index 0000000..9639fc1 --- /dev/null +++ b/lib/domains/category/category_state.dart @@ -0,0 +1,20 @@ +part of 'category_bloc.dart'; + +final class CategoryState extends Equatable { + final List categories; + + const CategoryState({ + this.categories = const [], + }); + + CategoryState copyWith({ + List? categories, + }) { + return CategoryState( + categories: categories ?? this.categories, + ); + } + + @override + List get props => [categories]; +} diff --git a/lib/domains/charts/chart_bloc.dart b/lib/domains/charts/chart_bloc.dart index 1ac42b6..e5ae774 100644 --- a/lib/domains/charts/chart_bloc.dart +++ b/lib/domains/charts/chart_bloc.dart @@ -4,7 +4,7 @@ import 'package:equatable/equatable.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:tunas/domains/account/models/transaction_line.dart'; +import 'package:tunas/domains/transaction/models/transaction_line.dart'; import 'package:tunas/domains/charts/models/chart_item.dart'; import 'package:tunas/repositories/account/account_repository.dart'; import 'package:tunas/repositories/account/models/transaction.dart'; @@ -149,7 +149,11 @@ class ChartBloc extends Bloc { } if (categoriesColors[transaction.category] == null) { - categoriesColors[transaction.category] = colors[colorIndex]; + if (colorIndex >= colors.length) { + categoriesColors[transaction.category] = const Color.fromARGB(255, 234, 0, 255); + } else { + categoriesColors[transaction.category] = colors[colorIndex]; + } colorIndex++; } diff --git a/lib/domains/account/models/transaction_account.dart b/lib/domains/transaction/models/transaction_account.dart similarity index 100% rename from lib/domains/account/models/transaction_account.dart rename to lib/domains/transaction/models/transaction_account.dart diff --git a/lib/domains/account/models/transaction_category.dart b/lib/domains/transaction/models/transaction_category.dart similarity index 100% rename from lib/domains/account/models/transaction_category.dart rename to lib/domains/transaction/models/transaction_category.dart diff --git a/lib/domains/account/models/transaction_date.dart b/lib/domains/transaction/models/transaction_date.dart similarity index 100% rename from lib/domains/account/models/transaction_date.dart rename to lib/domains/transaction/models/transaction_date.dart diff --git a/lib/domains/account/models/transaction_description.dart b/lib/domains/transaction/models/transaction_description.dart similarity index 100% rename from lib/domains/account/models/transaction_description.dart rename to lib/domains/transaction/models/transaction_description.dart diff --git a/lib/domains/account/models/transaction_line.dart b/lib/domains/transaction/models/transaction_line.dart similarity index 100% rename from lib/domains/account/models/transaction_line.dart rename to lib/domains/transaction/models/transaction_line.dart diff --git a/lib/domains/account/models/transaction_value.dart b/lib/domains/transaction/models/transaction_value.dart similarity index 100% rename from lib/domains/account/models/transaction_value.dart rename to lib/domains/transaction/models/transaction_value.dart diff --git a/lib/domains/transaction/transaction_bloc.dart b/lib/domains/transaction/transaction_bloc.dart new file mode 100644 index 0000000..fbd08da --- /dev/null +++ b/lib/domains/transaction/transaction_bloc.dart @@ -0,0 +1,230 @@ + +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:formz/formz.dart'; +import 'package:tunas/domains/transaction/models/transaction_account.dart'; +import 'package:tunas/domains/transaction/models/transaction_category.dart'; +import 'package:tunas/domains/transaction/models/transaction_date.dart'; +import 'package:tunas/domains/transaction/models/transaction_description.dart'; +import 'package:tunas/domains/transaction/models/transaction_line.dart'; +import 'package:tunas/domains/transaction/models/transaction_value.dart'; +import 'package:tunas/repositories/account/account_repository.dart'; +import 'package:tunas/repositories/account/models/transaction.dart'; +import 'package:uuid/uuid.dart'; + +part 'transaction_event.dart'; +part 'transaction_state.dart'; + +class TransactionBloc extends Bloc { + final AccountRepository _accountRepository; + + TransactionBloc({required AccountRepository accountRepository}) + : _accountRepository = accountRepository, + super(const TransactionState()) { + on(_onAccountLoad); + on(_onTransactionDateChange); + on(_onTransactionCategoryChange); + on(_onTransactionDescriptionChange); + on(_onTransactionAccountChange); + on(_onTransactionValueChange); + on(_onTransactionOpenAddDialog); + on(_onTransactionHideAddDialog); + on(_onTransactionAddDialog); + on(_onTransactionSetCurrent); + on(_onTransactionDeleteCurrent); + + _accountRepository + .getTransactionsStream() + .listen((transactions) => add(TransactionsLoad(transactions))); + } + + _onAccountLoad(TransactionsLoad event, Emitter emit) { + var computeResult = _computeTransactionLine(event.transactions); + emit(state.copyWith( + transactions: event.transactions, + transactionsLines: computeResult.list, + globalTotal: computeResult.globalTotal, + accountsTotals: computeResult.accountsTotals, + )); + } + + ({List list, double globalTotal, Map accountsTotals, List categories}) _computeTransactionLine(List transactions) { + double globalTotal = 0; + Map accountsTotals = {}; + List output = []; + Set categories = {}; + + for(var transaction in transactions) { + double subTotal = globalTotal + transaction.value; + globalTotal = subTotal; + + double accountTotal = accountsTotals[transaction.account] ?? 0; + accountTotal += transaction.value; + accountsTotals[transaction.account] = accountTotal; + categories.add(transaction.category); + + output.add(TransactionLine(transaction: transaction, subTotal: subTotal)); + } + + output.sort((a, b) => b.transaction.date.compareTo(a.transaction.date)); + + return (list: output, globalTotal: globalTotal, accountsTotals: accountsTotals, categories: categories.toList()); + } + + _onTransactionDateChange( + TransactionDateChange event, Emitter emit + ) { + final transactionDate = TransactionDate.dirty(event.date); + emit(state.copyWith( + transactionDate: transactionDate, + isValid: Formz.validate([transactionDate, state.transactionCategory, state.transactionDescription, state.transactionAccount, state.transactionValue]), + )); + } + + _onTransactionCategoryChange( + TransactionCategoryChange event, Emitter emit + ) { + final transactionCategory = TransactionCategory.dirty(event.category); + emit(state.copyWith( + transactionCategory: transactionCategory, + isValid: Formz.validate([state.transactionDate, transactionCategory, state.transactionDescription, state.transactionAccount, state.transactionValue]), + )); + } + + _onTransactionDescriptionChange( + TransactionDescriptionChange event, Emitter emit + ) { + final transactionDescription = TransactionDescription.dirty(event.description); + emit(state.copyWith( + transactionDescription: transactionDescription, + isValid: Formz.validate([state.transactionDate, state.transactionCategory, transactionDescription, state.transactionAccount, state.transactionValue]), + )); + } + + _onTransactionAccountChange( + TransactionAccountChange event, Emitter emit + ) { + final transactionAccount = TransactionAccount.dirty(event.account); + emit(state.copyWith( + transactionAccount: transactionAccount, + isValid: Formz.validate([state.transactionDate, state.transactionCategory, state.transactionDescription, transactionAccount, state.transactionValue]), + )); + } + + _onTransactionValueChange( + TransactionValueChange event, Emitter emit + ) { + try { + final transactionValue = TransactionValue.dirty(double.parse(event.value)); + emit(state.copyWith( + transactionValue: transactionValue, + isValid: Formz.validate([state.transactionDate, state.transactionCategory, state.transactionDescription, state.transactionAccount, transactionValue]), + )); + } catch (e) { + const transactionValue = TransactionValue.dirty(double.infinity); + emit(state.copyWith( + transactionValue: transactionValue, + isValid: Formz.validate([state.transactionDate, state.transactionCategory, state.transactionDescription, state.transactionAccount, transactionValue]), + )); + } + } + + _onTransactionOpenAddDialog( + TransactionOpenAddDialog event, Emitter emit + ) { + emit(state.copyWith(showAddDialog: true)); + } + + _onTransactionHideAddDialog( + TransactionHideAddDialog event, Emitter emit + ) { + emit(state.copyWith(showAddDialog: false)); + } + + _onTransactionAddDialog( + TransactionAdd event, Emitter emit + ) async { + if (state.isValid) { + List transactions = state.transactions; + Transaction? currentTransaction = state.currentTransaction; + if (currentTransaction != null) { + transactions.removeWhere((transaction) => transaction.uuid == currentTransaction.uuid); + } + + transactions.add(Transaction( + uuid: const Uuid().v8(), + date: state.transactionDate.value ?? DateTime.now(), + category: state.transactionCategory.value, + description: state.transactionDescription.value, + account: state.transactionAccount.value, + value: state.transactionValue.value + )); + final computeResult = _computeTransactionLine(transactions); + + await _accountRepository.saveTransactions(transactions); + + emit(state.copyWith( + currentTransaction: null, + transactionDate: const TransactionDate.pure(), + transactionCategory: const TransactionCategory.pure(), + transactionDescription: const TransactionDescription.pure(), + transactionAccount: const TransactionAccount.pure(), + transactionValue: const TransactionValue.pure(), + transactions: transactions, + transactionsLines: computeResult.list, + globalTotal: computeResult.globalTotal, + accountsTotals: computeResult.accountsTotals, + )); + } + } + + _onTransactionSetCurrent( + TransactionSetCurrent event, Emitter emit + ) { + Transaction? transaction = event.transaction; + if (transaction == null) { + emit(state.copyWith( + currentTransaction: event.transaction, + transactionDate: const TransactionDate.pure(), + transactionCategory: const TransactionCategory.pure(), + transactionDescription: const TransactionDescription.pure(), + transactionAccount: const TransactionAccount.pure(), + transactionValue: const TransactionValue.pure(), + )); + } else { + emit(state.copyWith( + currentTransaction: transaction, + transactionDate: TransactionDate.dirty(transaction.date), + transactionCategory: TransactionCategory.dirty(transaction.category), + transactionDescription: TransactionDescription.dirty(transaction.description), + transactionAccount: TransactionAccount.dirty(transaction.account), + transactionValue: TransactionValue.dirty(transaction.value), + )); + } + } + + _onTransactionDeleteCurrent( + TransactionDeleteCurrent event, Emitter emit + ) async { + Transaction? currentTransaction = state.currentTransaction; + if (currentTransaction != null) { + List transactions = state.transactions; + transactions.removeWhere((transaction) => transaction.uuid == currentTransaction.uuid); + final computeResult = _computeTransactionLine(transactions); + await _accountRepository.saveTransactions(transactions); + emit(state.copyWith( + currentTransaction: null, + transactionDate: const TransactionDate.pure(), + transactionCategory: const TransactionCategory.pure(), + transactionDescription: const TransactionDescription.pure(), + transactionAccount: const TransactionAccount.pure(), + transactionValue: const TransactionValue.pure(), + transactions: transactions, + transactionsLines: computeResult.list, + globalTotal: computeResult.globalTotal, + accountsTotals: computeResult.accountsTotals, + )); + } + } + +} \ No newline at end of file diff --git a/lib/domains/transaction/transaction_event.dart b/lib/domains/transaction/transaction_event.dart new file mode 100644 index 0000000..2b32175 --- /dev/null +++ b/lib/domains/transaction/transaction_event.dart @@ -0,0 +1,74 @@ +part of 'transaction_bloc.dart'; + +sealed class TransactionEvent extends Equatable { + const TransactionEvent(); + + @override + List get props => []; +} + +final class TransactionsLoad extends TransactionEvent { + final List transactions; + const TransactionsLoad(this.transactions); + + @override + List get props => [transactions]; +} + +final class TransactionDateChange extends TransactionEvent { + final DateTime? date; + const TransactionDateChange(this.date); +} + +final class TransactionCategoryChange extends TransactionEvent { + final String category; + const TransactionCategoryChange(this.category); + + @override + List get props => [category]; +} + +final class TransactionDescriptionChange extends TransactionEvent { + final String description; + const TransactionDescriptionChange(this.description); + + @override + List get props => [description]; +} + +final class TransactionAccountChange extends TransactionEvent { + final String account; + const TransactionAccountChange(this.account); + + @override + List get props => [account]; +} + +final class TransactionValueChange extends TransactionEvent { + final String value; + const TransactionValueChange(this.value); + + @override + List get props => [value]; +} + +final class TransactionAdd extends TransactionEvent { + const TransactionAdd(); +} + +final class TransactionOpenAddDialog extends TransactionEvent { + const TransactionOpenAddDialog(); +} + +final class TransactionHideAddDialog extends TransactionEvent { + const TransactionHideAddDialog(); +} + +final class TransactionSetCurrent extends TransactionEvent { + final Transaction? transaction; + const TransactionSetCurrent(this.transaction); +} + +final class TransactionDeleteCurrent extends TransactionEvent { + const TransactionDeleteCurrent(); +} \ No newline at end of file diff --git a/lib/domains/transaction/transaction_state.dart b/lib/domains/transaction/transaction_state.dart new file mode 100644 index 0000000..63bf93d --- /dev/null +++ b/lib/domains/transaction/transaction_state.dart @@ -0,0 +1,77 @@ +part of 'transaction_bloc.dart'; + +final class TransactionState extends Equatable { + final double globalTotal; + final Map accountsTotals; + + final List transactions; + final List transactionsLines; + + final TransactionDate transactionDate; + final TransactionCategory transactionCategory; + final TransactionDescription transactionDescription; + final TransactionAccount transactionAccount; + final TransactionValue transactionValue; + final bool isValid; + final bool showAddDialog; + + final Transaction? currentTransaction; + + const TransactionState({ + this.globalTotal = 0, + this.accountsTotals = const {}, + this.transactions = const [], + this.transactionsLines = const [], + this.transactionDate = const TransactionDate.pure(), + this.transactionCategory = const TransactionCategory.pure(), + this.transactionDescription = const TransactionDescription.pure(), + this.transactionAccount = const TransactionAccount.pure(), + this.transactionValue = const TransactionValue.pure(), + this.isValid = false, + this.showAddDialog = false, + this.currentTransaction + }); + + TransactionState copyWith({ + double? globalTotal, + Map? accountsTotals, + List? transactions, + List? transactionsLines, + TransactionDate? transactionDate, + TransactionCategory? transactionCategory, + TransactionDescription? transactionDescription, + TransactionAccount? transactionAccount, + TransactionValue? transactionValue, + bool? isValid, + bool? showAddDialog, + Transaction? currentTransaction, + }) { + return TransactionState( + globalTotal: globalTotal ?? this.globalTotal, + accountsTotals: accountsTotals ?? this.accountsTotals, + transactions: transactions ?? this.transactions, + transactionsLines: transactionsLines ?? this.transactionsLines, + transactionDate: transactionDate ?? this.transactionDate, + transactionCategory: transactionCategory ?? this.transactionCategory, + transactionDescription: transactionDescription ?? this.transactionDescription, + transactionAccount: transactionAccount ?? this.transactionAccount, + transactionValue: transactionValue ?? this.transactionValue, + isValid: isValid ?? this.isValid, + showAddDialog: showAddDialog ?? this.showAddDialog, + currentTransaction: currentTransaction ?? this.currentTransaction, + ); + } + + @override + List get props => [ + transactionDate, + transactionCategory, + transactionDescription, + transactionAccount, + transactionValue, + isValid, + showAddDialog, + currentTransaction, + ]; + +} \ No newline at end of file diff --git a/lib/pages/budgets/budgets_page.dart b/lib/pages/budgets/budgets_page.dart index e54e239..2557794 100644 --- a/lib/pages/budgets/budgets_page.dart +++ b/lib/pages/budgets/budgets_page.dart @@ -1,6 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:tunas/domains/account/account_bloc.dart'; import 'package:tunas/pages/budgets/widgets/budgets_actions.dart'; class BudgetsPage extends StatelessWidget { @@ -8,23 +6,16 @@ class BudgetsPage extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocListener( - listener: (context, state) { - if (state.showAddDialog) { - // TransactionAddDialog.show(context); - } - }, - child: const Flex( - direction: Axis.horizontal, - children: [ - Expanded( - child: Column( - children: [ - BudgetsActions(), - ], - )) - ], - ), + return const Flex( + direction: Axis.horizontal, + children: [ + Expanded( + child: Column( + children: [ + BudgetsActions(), + ], + )) + ], ); } } \ No newline at end of file diff --git a/lib/pages/home/home_page.dart b/lib/pages/home/home_page.dart index a66c72f..9a089aa 100644 --- a/lib/pages/home/home_page.dart +++ b/lib/pages/home/home_page.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:tunas/domains/account/account_bloc.dart'; +import 'package:tunas/domains/budget/budget_bloc.dart'; +import 'package:tunas/domains/category/category_bloc.dart'; +import 'package:tunas/domains/transaction/transaction_bloc.dart'; import 'package:tunas/pages/budgets/budgets_page.dart'; import 'package:tunas/pages/data/data_page.dart'; import 'package:tunas/pages/stats/stats_page.dart'; @@ -12,8 +15,13 @@ class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocProvider( - create: (context) => AccountBloc(accountRepository: RepositoryProvider.of(context)), + return MultiBlocProvider( + providers: [ + BlocProvider(create: (context) => AccountBloc(accountRepository: RepositoryProvider.of(context))), + BlocProvider(create: (context) => TransactionBloc(accountRepository: RepositoryProvider.of(context))), + BlocProvider(create: (context) => CategoryBloc(accountRepository: RepositoryProvider.of(context))), + BlocProvider(create: (context) => BudgetBloc(accountRepository: RepositoryProvider.of(context))), + ], child: DefaultTabController( length: 4, child: Scaffold( diff --git a/lib/pages/stats/widgets/monthly_categories_total_chart.dart b/lib/pages/stats/widgets/monthly_categories_total_chart.dart index 416302c..4bf5d13 100644 --- a/lib/pages/stats/widgets/monthly_categories_total_chart.dart +++ b/lib/pages/stats/widgets/monthly_categories_total_chart.dart @@ -42,17 +42,29 @@ class MonthlyCategoriesTotalChart extends StatelessWidget { ); } + double _computeMaxValue() { + double max = 0.0; + categoriesMonthlyTotals.forEach((monthKey, value) { + double localMax = value.values.reduce((value, element) => value + element); + if (localMax > max) { + max = localMax; + } + }); + return max + (max / 10); + } + @override Widget build(BuildContext context) { return AspectRatio( aspectRatio: 1.66, child: LayoutBuilder( builder: (context, constraints) { - final barsSpace = 4.0 * constraints.maxWidth / 400; - final barsWidth = 8.0 * constraints.maxWidth / 100; + final barsSpace = 4.0 * constraints.maxWidth / 100; + final barsWidth = 8.0 * constraints.maxWidth / 130; return BarChart( BarChartData( + maxY: _computeMaxValue(), barGroups: _computeBarGroups(barsSpace, barsWidth), titlesData: FlTitlesData( topTitles: const AxisTitles( diff --git a/lib/pages/transactions/transactions_page.dart b/lib/pages/transactions/transactions_page.dart index 796b5e7..4eafd42 100644 --- a/lib/pages/transactions/transactions_page.dart +++ b/lib/pages/transactions/transactions_page.dart @@ -1,6 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:tunas/domains/account/account_bloc.dart'; import 'package:tunas/pages/transactions/widgets/transactions_actions.dart'; import 'package:tunas/pages/transactions/widgets/transactions_header.dart'; import 'package:tunas/pages/transactions/widgets/transactions_list.dart'; @@ -10,25 +8,18 @@ class TransactionsPage extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocListener( - listener: (context, state) { - if (state.showAddDialog) { - // TransactionAddDialog.show(context); - } - }, - child: const Flex( - direction: Axis.horizontal, - children: [ - Expanded( - child: Column( - children: [ - TransactionsActions(), - TransactionsHeader(), - TransactionsList(), - ], - )) - ], - ), + return const Flex( + direction: Axis.horizontal, + children: [ + Expanded( + child: Column( + children: [ + TransactionsActions(), + TransactionsHeader(), + TransactionsList(), + ], + )) + ], ); } } \ No newline at end of file diff --git a/lib/pages/transactions/widgets/transaction_add_dialog.dart b/lib/pages/transactions/widgets/transaction_add_dialog.dart index fd6ef20..7aca030 100644 --- a/lib/pages/transactions/widgets/transaction_add_dialog.dart +++ b/lib/pages/transactions/widgets/transaction_add_dialog.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:tunas/domains/account/account_bloc.dart'; +import 'package:tunas/domains/category/category_bloc.dart'; +import 'package:tunas/domains/transaction/transaction_bloc.dart'; import 'package:tunas/pages/transactions/widgets/transaction_form.dart'; import 'package:tunas/repositories/account/models/transaction.dart'; @@ -8,13 +9,16 @@ class TransactionAddDialog extends StatelessWidget { const TransactionAddDialog({super.key}); static void show(BuildContext context, Transaction? transaction) { - context.read().add(TransactionSetCurrent(transaction)); + context.read().add(TransactionSetCurrent(transaction)); showDialog( context: context, barrierDismissible: false, useRootNavigator: false, - builder: (_) => BlocProvider.value( - value: BlocProvider.of(context), + builder: (_) => MultiBlocProvider( + providers: [ + BlocProvider.value(value: BlocProvider.of(context)), + BlocProvider.value(value: BlocProvider.of(context)), + ], child: const TransactionAddDialog() ) ); @@ -29,14 +33,14 @@ class TransactionAddDialog extends StatelessWidget { icon: const Icon(Icons.close) ), IconButton( - onPressed: () => context.read().add(const TransactionAdd()), + onPressed: () => context.read().add(const TransactionAdd()), icon: const Icon(Icons.save) ), ]; if (currentTransaction != null) { actions.add(IconButton( - onPressed: () => context.read().add(const TransactionDeleteCurrent()), + onPressed: () => context.read().add(const TransactionDeleteCurrent()), icon: const Icon(Icons.delete) )); } @@ -46,7 +50,7 @@ class TransactionAddDialog extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( buildWhen: (previous, current) => previous.currentTransaction != current.currentTransaction, builder: (context, state) => AlertDialog( title: Text(state.currentTransaction == null ? 'Add Transaction' : 'Edit Transaction'), diff --git a/lib/pages/transactions/widgets/transaction_form.dart b/lib/pages/transactions/widgets/transaction_form.dart index 0c2654d..64e1680 100644 --- a/lib/pages/transactions/widgets/transaction_form.dart +++ b/lib/pages/transactions/widgets/transaction_form.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; -import 'package:tunas/domains/account/account_bloc.dart'; +import 'package:tunas/domains/category/category_bloc.dart'; +import 'package:tunas/domains/transaction/transaction_bloc.dart'; import 'autocomplete_input.dart'; @@ -27,7 +28,7 @@ class TransactionForm extends StatelessWidget { class _TransactionDateInput extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( buildWhen: (previous, current) => previous.transactionDate != current.transactionDate, builder: (context, state) => SizedBox( width: 500, @@ -40,7 +41,7 @@ class _TransactionDateInput extends StatelessWidget { context: context, firstDate: DateTime.fromMicrosecondsSinceEpoch(0), lastDate: DateTime.now() - ).then((value) => context.read().add(TransactionDateChange(value))); + ).then((value) => context.read().add(TransactionDateChange(value))); }, decoration: InputDecoration( hintText: 'Date', @@ -55,16 +56,17 @@ class _TransactionDateInput extends StatelessWidget { class _TransactionCategoryInput extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder( + final categoryState = context.watch().state; + return BlocBuilder( buildWhen: (previous, current) => previous.transactionCategory != current.transactionCategory, builder: (context, state) => SizedBox( width: 500, child: AutocompleteInput( - options: state.categories, + options: categoryState.categories.map((e) => e.label).toList(), hintText: 'Category', initialValue: state.transactionCategory.value, errorText: state.transactionCategory.isNotValid ? state.transactionCategory.error?.message : null, - onChanged: (value) => context.read().add(TransactionCategoryChange(value)), + onChanged: (value) => context.read().add(TransactionCategoryChange(value)), ), ), ); @@ -74,7 +76,7 @@ class _TransactionCategoryInput extends StatelessWidget { class _TransactionDescriptionInput extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( buildWhen: (previous, current) => previous.transactionDescription != current.transactionDescription, builder: (context, state) => SizedBox( width: 500, @@ -83,7 +85,7 @@ class _TransactionDescriptionInput extends StatelessWidget { hintText: 'Description', errorText: state.transactionDescription.isNotValid ? state.transactionDescription.error?.message : null ), - onChanged: (value) => context.read().add(TransactionDescriptionChange(value)) + onChanged: (value) => context.read().add(TransactionDescriptionChange(value)) ) ), ); @@ -93,7 +95,7 @@ class _TransactionDescriptionInput extends StatelessWidget { class _TransactionAccountInput extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( buildWhen: (previous, current) => previous.transactionAccount != current.transactionAccount, builder: (context, state) => SizedBox( width: 500, @@ -102,7 +104,7 @@ class _TransactionAccountInput extends StatelessWidget { hintText: 'Account', initialValue: state.transactionAccount.value, errorText: state.transactionAccount.isNotValid ? state.transactionAccount.error?.message : null, - onChanged: (value) => context.read().add(TransactionAccountChange(value)), + onChanged: (value) => context.read().add(TransactionAccountChange(value)), ), ), ); @@ -112,7 +114,7 @@ class _TransactionAccountInput extends StatelessWidget { class _TransactionValueInput extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( buildWhen: (previous, current) => previous.transactionValue != current.transactionValue, builder: (context, state) => SizedBox( width: 500, @@ -122,7 +124,7 @@ class _TransactionValueInput extends StatelessWidget { hintText: '\$\$\$', errorText: state.transactionValue.isNotValid ? state.transactionValue.error?.message : null ), - onChanged: (value) => context.read().add(TransactionValueChange(value)) + onChanged: (value) => context.read().add(TransactionValueChange(value)) ) ), ); diff --git a/lib/pages/transactions/widgets/transactions_actions.dart b/lib/pages/transactions/widgets/transactions_actions.dart index c8bf7aa..3aadfc2 100644 --- a/lib/pages/transactions/widgets/transactions_actions.dart +++ b/lib/pages/transactions/widgets/transactions_actions.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:tunas/domains/account/account_bloc.dart'; +import 'package:tunas/domains/transaction/transaction_bloc.dart'; import 'package:tunas/pages/transactions/widgets/transaction_add_dialog.dart'; class TransactionsActions extends StatelessWidget { @@ -8,7 +8,7 @@ class TransactionsActions extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( builder: (context, state) => Container( padding: const EdgeInsets.symmetric(vertical: 9, horizontal: 10), margin: const EdgeInsets.symmetric(vertical: 2, horizontal: 10), diff --git a/lib/pages/transactions/widgets/transactions_list.dart b/lib/pages/transactions/widgets/transactions_list.dart index 6b7faa6..c3b7f42 100644 --- a/lib/pages/transactions/widgets/transactions_list.dart +++ b/lib/pages/transactions/widgets/transactions_list.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:tunas/domains/account/account_bloc.dart'; +import 'package:tunas/domains/transaction/transaction_bloc.dart'; import 'package:tunas/pages/transactions/widgets/transaction_line.dart'; class TransactionsList extends StatelessWidget { @@ -8,7 +8,7 @@ class TransactionsList extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( buildWhen: (previous, current) => previous.transactionsLines != current.transactionsLines, builder: (context, state) => Expanded( child: ListView.builder( diff --git a/lib/repositories/account/account_repository.dart b/lib/repositories/account/account_repository.dart index 0747153..229d8fa 100644 --- a/lib/repositories/account/account_repository.dart +++ b/lib/repositories/account/account_repository.dart @@ -3,14 +3,18 @@ import 'dart:convert'; import 'package:rxdart/subjects.dart'; import 'package:tunas/clients/storage/storage_client.dart'; import 'package:tunas/repositories/account/models/account.dart'; +import 'package:tunas/repositories/account/models/budget.dart'; +import 'package:tunas/repositories/account/models/category.dart'; import 'package:tunas/repositories/account/models/transaction.dart'; class AccountRepository { - String accountFile = 'main_account.json'; + String accountFile = 'tunas_main_account.json'; final StorageClient _storageClient; final _transactionsController = BehaviorSubject>.seeded(const []); + final _categoriesController = BehaviorSubject>.seeded(const []); + final _budgetController = BehaviorSubject>.seeded(const []); AccountRepository({ required storageClient, @@ -21,12 +25,22 @@ class AccountRepository { init() async { final account = await getAccount(); _transactionsController.add(account.transactions); + _categoriesController.add(account.categories); + _budgetController.add(account.budgets); } Stream> getTransactionsStream() { return _transactionsController.asBroadcastStream(); } + Stream> getCategoriesStream() { + return _categoriesController.asBroadcastStream(); + } + + Stream> getBudgetsStream() { + return _budgetController.asBroadcastStream(); + } + Future getAccount() async { String json = await _storageClient.load(accountFile); Map accountJson = jsonDecode(json); @@ -41,5 +55,14 @@ class AccountRepository { final account = Account(transactions: transactions); await saveAccount(account); _transactionsController.add(account.transactions); + _categoriesController.add(account.categories); + _budgetController.add(account.budgets); + } + + deleteAccount() async { + await _storageClient.delete(accountFile); + _transactionsController.add(const []); + _categoriesController.add(const []); + _budgetController.add(const []); } } diff --git a/lib/repositories/account/models/account.dart b/lib/repositories/account/models/account.dart index cdadf07..c892106 100644 --- a/lib/repositories/account/models/account.dart +++ b/lib/repositories/account/models/account.dart @@ -1,21 +1,32 @@ import 'dart:convert'; +import 'package:tunas/repositories/account/models/budget.dart'; +import 'package:tunas/repositories/account/models/category.dart'; + import 'transaction.dart'; class Account { List transactions; + List budgets; + List categories; Account({ this.transactions = const [], + this.budgets = const [], + this.categories = const [], }); factory Account.fromJson(Map json) { return Account( transactions: (jsonDecode(json['transactions']) as List).map((transaction) => Transaction.fromJson(transaction)).toList(), + budgets: (jsonDecode(json['budgets']) as List).map((budget) => Budget.fromJson(budget)).toList(), + categories: (jsonDecode(json['categories']) as List).map((category) => Category.fromJson(category)).toList(), ); } Map toJson() => { 'transactions': jsonEncode(transactions.map((transaction) => transaction.toJson()).toList()), + 'budgets': jsonEncode(budgets.map((budget) => budget.toJson()).toList()), + 'categories': jsonEncode(categories.map((category) => category.toJson()).toList()), }; } diff --git a/lib/repositories/account/models/budget.dart b/lib/repositories/account/models/budget.dart new file mode 100644 index 0000000..85e0bb9 --- /dev/null +++ b/lib/repositories/account/models/budget.dart @@ -0,0 +1,21 @@ +class Budget { + bool monthly; + double value; + + Budget({ + this.monthly = false, + this.value = 0.0, + }); + + factory Budget.fromJson(Map json) { + return Budget( + monthly: json['monthly'], + value: double.parse(json['value']), + ); + } + + Map toJson() => { + 'monthly': monthly.toString(), + 'value': value.toString(), + }; +} diff --git a/lib/repositories/account/models/category.dart b/lib/repositories/account/models/category.dart new file mode 100644 index 0000000..7360d29 --- /dev/null +++ b/lib/repositories/account/models/category.dart @@ -0,0 +1,21 @@ +class Category { + String label; + String color; + + Category({ + this.label = '', + this.color = '', + }); + + factory Category.fromJson(Map json) { + return Category( + label: json['label'], + color: json['color'] + ); + } + + Map toJson() => { + 'label': label, + 'color': color, + }; +}