diff --git a/lib/app.dart b/lib/app.dart index d4a7c8c..8191bdc 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -3,7 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:tunas/clients/storage/storage_client.dart'; import 'package:tunas/pages/home/home_page.dart'; -import 'package:tunas/repositories/account/account_repository.dart'; +import 'package:tunas/repositories/json/json_repository.dart'; +import 'package:tunas/repositories/metadata/metadata_repository.dart'; +import 'package:tunas/repositories/transactions/transactions_repository.dart'; import 'package:tunas/theme.dart'; class App extends StatefulWidget { @@ -29,20 +31,31 @@ class AppView extends StatefulWidget { class _AppViewState extends State { late final StorageClient _storageClient; - late final AccountRepository _accountRepository; + late final JsonRepository _jsonRepository; + late final TransactionsRepository _transactionsRepository; + late final MetadataRepository _metadataRepository; @override void initState() { super.initState(); _storageClient = StorageClient(); - _accountRepository = AccountRepository(storageClient: _storageClient); + _jsonRepository = JsonRepository(storageClient: _storageClient); + _transactionsRepository = TransactionsRepository(jsonRepository: _jsonRepository); + _metadataRepository = MetadataRepository(jsonRepository: _jsonRepository); + + _transactionsRepository.loadTransactions(); + _metadataRepository.loadMetadata(); } @override Widget build(BuildContext context) { return MultiRepositoryProvider( - providers: [RepositoryProvider.value(value: _accountRepository)], + providers: [ + RepositoryProvider.value(value: _jsonRepository), + RepositoryProvider.value(value: _transactionsRepository), + RepositoryProvider.value(value: _metadataRepository), + ], child: MaterialApp( title: 'Tunas', theme: ThemeData(useMaterial3: true, colorScheme: lightColorScheme), diff --git a/lib/clients/storage/storage_client.dart b/lib/clients/storage/storage_client.dart index e138599..1539a49 100644 --- a/lib/clients/storage/storage_client.dart +++ b/lib/clients/storage/storage_client.dart @@ -3,27 +3,27 @@ import 'dart:io'; import 'package:path_provider/path_provider.dart'; class StorageClient { - save(String filename, String data) async { - File file = await _getJson(filename); - await file.writeAsString(data); - } + save(String filename, String data) async { + File file = await _getJson(filename); + await file.writeAsString(data); + } - Future load(String filename) async { - File file = await _getJson(filename); - return file.readAsString(); - } + Future load(String filename) async { + File file = await _getJson(filename); + return file.readAsString(); + } - delete(String filename) async { - File file = await _getJson(filename); - await file.delete(); - } + 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'); - if (!file.existsSync()) { - file.createSync(); - } - return file; + Future _getJson(String filename) async { + final dir = await getApplicationDocumentsDirectory(); + final file = File('${dir.path}/$filename'); + if (!file.existsSync()) { + file.createSync(); } + return file; + } } \ No newline at end of file diff --git a/lib/domains/account/account_bloc.dart b/lib/domains/account/account_bloc.dart index 3436307..d7dbac0 100644 --- a/lib/domains/account/account_bloc.dart +++ b/lib/domains/account/account_bloc.dart @@ -1,14 +1,14 @@ -import 'dart:convert'; import 'dart:io'; 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:tunas/repositories/account/account_repository.dart'; -import 'package:tunas/repositories/account/models/account.dart'; -import 'package:tunas/repositories/account/models/category.dart'; -import 'package:tunas/repositories/account/models/transaction.dart'; +import 'package:tunas/repositories/metadata/models/category.dart'; +import 'package:tunas/repositories/metadata/metadata_repository.dart'; +import 'package:tunas/repositories/metadata/models/account.dart'; +import 'package:tunas/repositories/transactions/models/transaction.dart'; +import 'package:tunas/repositories/transactions/transactions_repository.dart'; import 'package:uuid/uuid.dart'; part 'account_event.dart'; @@ -34,20 +34,25 @@ final colors = [ ]; class AccountBloc extends Bloc { - final AccountRepository _accountRepository; + final TransactionsRepository _transactionsRepository; + final MetadataRepository _metadataRepository; - AccountBloc({required AccountRepository accountRepository}) - : _accountRepository = accountRepository, - super(const AccountState()) { - on(_onAccountImportJSON); + AccountBloc({ + required MetadataRepository metadataRepository, + required TransactionsRepository transactionsRepository, + }) + : _metadataRepository = metadataRepository, + _transactionsRepository = transactionsRepository, + super(const AccountState() + ) { on(_onAccountImportCSV); - on(_onSubAccountLoad); + on(_onAccountLoad); // on(_onAccountImportJSON); // on(_onAccountImportJSON); - _accountRepository - .getSubAccountsStream() - .listen((subAccounts) => add(SubAccountLoad(subAccounts))); + _metadataRepository + .getAccountsStream() + .listen((subAccounts) => add(AccountLoad(subAccounts))); } double _universalConvertToDouble(dynamic value) { @@ -62,8 +67,7 @@ class AccountBloc extends Bloc { } } - _onAccountImportCSV( - AccountImportCSV event, Emitter emit) async { + _onAccountImportCSV(AccountImportCSV event, Emitter emit) async { int colorIndex = 0; FilePickerResult? result = await FilePicker.platform.pickFiles(); @@ -74,27 +78,33 @@ class AccountBloc extends Bloc { final List> csvList = const CsvToListConverter(fieldDelimiter: '|', eol: '\n').convert(csvFileContent); final Map categoriesMap = {}; - final Set subAccounts = {}; + final Map accounts = {}; final transactions = csvList .map((line) { + double value = _universalConvertToDouble(line[4]); String? categoryLabel = line[1]; if (categoryLabel == null || categoryLabel == '') { categoryLabel = 'N/A'; } if (categoriesMap[categoryLabel] == null) { - if (categoryLabel == 'N/A') { - categoriesMap[categoryLabel] = Category(label: 'N/A', color: 'FFFF0000' ); - } else { - String color = colorIndex >= colors.length ? 'FF0000FF' : colors[colorIndex]; - colorIndex++; - categoriesMap[categoryLabel] = Category(label: categoryLabel, color: color ); - } + // if (categoryLabel == 'N/A') { + // categoriesMap[categoryLabel] = Category(label: 'N/A', color: 'FFFF0000' ); + // } else { + // String color = colorIndex >= colors.length ? 'FF0000FF' : colors[colorIndex]; + // colorIndex++; + // categoriesMap[categoryLabel] = Category(label: categoryLabel, color: color ); + // } + + categoriesMap[categoryLabel] = Category(label: categoryLabel, color: value > 0 ? 'FF21E297' : 'FFFFB4AB' ); } - subAccounts.add(line[3]); + String accountLabel = line[3]; + if (accounts[accountLabel] == null) { + accounts[accountLabel] = Account(label: accountLabel); + } return Transaction( uuid: const Uuid().v8(), @@ -102,34 +112,23 @@ class AccountBloc extends Bloc { category: categoryLabel, description: line[2], account: line[3], - value: _universalConvertToDouble(line[4])); - }) + value: value + ); + }) .toList(); - await _accountRepository.deleteAccount(); - final account = Account(transactions: transactions, categories: categoriesMap.values.toList(), subAccounts: subAccounts); - await _accountRepository.saveAccount(account); + await _metadataRepository.deleteMetadata(); + await _transactionsRepository.deleteTransactions(); + + await _metadataRepository.saveAccounts(accounts.values.toList()); + await _metadataRepository.saveBudgets([]); + await _metadataRepository.saveCategories(categoriesMap.values.toList()); + await _transactionsRepository.saveTransactions(transactions); } } - _onAccountImportJSON( - AccountImportJSON event, Emitter emit) async { - FilePickerResult? result = await FilePicker.platform.pickFiles(); - - final jsonPath = result?.files.first.path; - if (jsonPath != null) { - final File json = File(jsonPath); - final String jsonString = await json.readAsString(); - final List jsonList = jsonDecode(jsonString); - final List transactions = jsonList.map((transaction) => Transaction.fromJson(transaction)).toList(); - - await _accountRepository.deleteAccount(); - await _accountRepository.saveTransactions(transactions); - } - } - - _onSubAccountLoad( - SubAccountLoad event, Emitter emit + _onAccountLoad( + AccountLoad event, Emitter emit ) { emit( state.copyWith(event.subAccounts) diff --git a/lib/domains/account/account_event.dart b/lib/domains/account/account_event.dart index f871cf3..f209579 100644 --- a/lib/domains/account/account_event.dart +++ b/lib/domains/account/account_event.dart @@ -23,9 +23,9 @@ final class AccountExportCSV extends AccountEvent { const AccountExportCSV(); } -final class SubAccountLoad extends AccountEvent { - final Set subAccounts; - const SubAccountLoad(this.subAccounts); +final class AccountLoad extends AccountEvent { + final List subAccounts; + const AccountLoad(this.subAccounts); @override List get props => [subAccounts]; diff --git a/lib/domains/account/account_state.dart b/lib/domains/account/account_state.dart index 11fcd29..0fe5e46 100644 --- a/lib/domains/account/account_state.dart +++ b/lib/domains/account/account_state.dart @@ -1,18 +1,18 @@ part of 'account_bloc.dart'; final class AccountState extends Equatable { - final Set subAccounts; + final List accounts; const AccountState({ - this.subAccounts = const {}, + this.accounts = const [], }); - AccountState copyWith(Set? subAccounts) { + AccountState copyWith(List? accounts) { return AccountState( - subAccounts: subAccounts ?? this.subAccounts, + accounts: accounts ?? this.accounts, ); } @override - List get props => [subAccounts]; + List get props => [accounts]; } diff --git a/lib/domains/budget/budget_bloc.dart b/lib/domains/budget/budget_bloc.dart index 0e3b6db..0bf74d5 100644 --- a/lib/domains/budget/budget_bloc.dart +++ b/lib/domains/budget/budget_bloc.dart @@ -1,18 +1,18 @@ 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'; +import 'package:tunas/repositories/metadata/models/budget.dart'; +import 'package:tunas/repositories/metadata/metadata_repository.dart'; part 'budget_event.dart'; part 'budget_state.dart'; class BudgetBloc extends Bloc { - final AccountRepository _accountRepository; + final MetadataRepository _metadataRepository; - BudgetBloc({required AccountRepository accountRepository}) : _accountRepository = accountRepository, super(const BudgetState()) { + BudgetBloc({required MetadataRepository metadataRepository}) : _metadataRepository = metadataRepository, super(const BudgetState()) { on(_onBudgetsLoad); - _accountRepository + _metadataRepository .getBudgetsStream() .listen((budgets) => add(BudgetsLoad(budgets))); } diff --git a/lib/domains/category/category_bloc.dart b/lib/domains/category/category_bloc.dart index d3db74a..ee1b73f 100644 --- a/lib/domains/category/category_bloc.dart +++ b/lib/domains/category/category_bloc.dart @@ -2,19 +2,19 @@ import 'dart:ui'; 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'; +import 'package:tunas/repositories/metadata/metadata_repository.dart'; +import 'package:tunas/repositories/metadata/models/category.dart'; part 'category_event.dart'; part 'category_state.dart'; class CategoryBloc extends Bloc { - final AccountRepository _accountRepository; + final MetadataRepository _metadataRepository; - CategoryBloc({required AccountRepository accountRepository}) : _accountRepository = accountRepository, super(const CategoryState()) { + CategoryBloc({required MetadataRepository metadataRepository}) : _metadataRepository = metadataRepository, super(const CategoryState()) { on(_onCategoriesLoad); - _accountRepository + _metadataRepository .getCategoriesStream() .listen((categories) => add(CategoriesLoad(categories))); } diff --git a/lib/domains/charts/chart_bloc.dart b/lib/domains/charts/chart_bloc.dart index 1d4d58d..f3053ee 100644 --- a/lib/domains/charts/chart_bloc.dart +++ b/lib/domains/charts/chart_bloc.dart @@ -4,29 +4,32 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:tunas/domains/charts/models/month_totals.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/category.dart'; -import 'package:tunas/repositories/account/models/transaction.dart'; +import 'package:tunas/repositories/metadata/metadata_repository.dart'; +import 'package:tunas/repositories/metadata/models/category.dart'; +import 'package:tunas/repositories/transactions/models/transaction.dart'; +import 'package:tunas/repositories/transactions/models/transactions.dart'; +import 'package:tunas/repositories/transactions/transactions_repository.dart'; part 'chart_event.dart'; part 'chart_state.dart'; class ChartBloc extends Bloc { - final AccountRepository _accountRepository; + final MetadataRepository _metadataRepository; + final TransactionsRepository _transactionsRepository; - ChartBloc({required AccountRepository accountRepository}) : - _accountRepository = accountRepository, super(const ChartState()) { + ChartBloc({required MetadataRepository metadataRepository, required TransactionsRepository transactionsRepository}) : + _metadataRepository = metadataRepository, _transactionsRepository = transactionsRepository, super(const ChartState()) { on(_onChartTransactionsLoad); on(_onChartCategoriesLoad); on(_onNextYear); on(_onPreviousYear); - _accountRepository + _transactionsRepository .getTransactionsStream() .listen((transactions) => add(ChartTransactionsLoad(transactions))); - _accountRepository + _metadataRepository .getCategoriesStream() .listen((categories) => add(ChartCategoriesLoad(categories))); } diff --git a/lib/domains/transaction/models/transaction_line.dart b/lib/domains/transaction/models/transaction_line.dart index 3f41f54..8fe53bf 100644 --- a/lib/domains/transaction/models/transaction_line.dart +++ b/lib/domains/transaction/models/transaction_line.dart @@ -1,4 +1,4 @@ -import 'package:tunas/repositories/account/models/transaction.dart'; +import 'package:tunas/repositories/transactions/models/transaction.dart'; class TransactionLine { Transaction transaction; diff --git a/lib/domains/transaction/transaction_bloc.dart b/lib/domains/transaction/transaction_bloc.dart index fbd08da..153fe65 100644 --- a/lib/domains/transaction/transaction_bloc.dart +++ b/lib/domains/transaction/transaction_bloc.dart @@ -8,18 +8,18 @@ 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:tunas/repositories/transactions/models/transaction.dart'; +import 'package:tunas/repositories/transactions/transactions_repository.dart'; import 'package:uuid/uuid.dart'; part 'transaction_event.dart'; part 'transaction_state.dart'; class TransactionBloc extends Bloc { - final AccountRepository _accountRepository; + final TransactionsRepository _transactionsRepository; - TransactionBloc({required AccountRepository accountRepository}) - : _accountRepository = accountRepository, + TransactionBloc({required TransactionsRepository transactionsRepository}) + : _transactionsRepository = transactionsRepository, super(const TransactionState()) { on(_onAccountLoad); on(_onTransactionDateChange); @@ -33,7 +33,7 @@ class TransactionBloc extends Bloc { on(_onTransactionSetCurrent); on(_onTransactionDeleteCurrent); - _accountRepository + _transactionsRepository .getTransactionsStream() .listen((transactions) => add(TransactionsLoad(transactions))); } @@ -161,7 +161,7 @@ class TransactionBloc extends Bloc { )); final computeResult = _computeTransactionLine(transactions); - await _accountRepository.saveTransactions(transactions); + await _transactionsRepository.saveTransactions(transactions); emit(state.copyWith( currentTransaction: null, @@ -211,7 +211,7 @@ class TransactionBloc extends Bloc { List transactions = state.transactions; transactions.removeWhere((transaction) => transaction.uuid == currentTransaction.uuid); final computeResult = _computeTransactionLine(transactions); - await _accountRepository.saveTransactions(transactions); + await _transactionsRepository.saveTransactions(transactions); emit(state.copyWith( currentTransaction: null, transactionDate: const TransactionDate.pure(), diff --git a/lib/pages/data/widgets/account_settings.dart b/lib/pages/data/widgets/account_settings.dart index 57ffe40..70f8242 100644 --- a/lib/pages/data/widgets/account_settings.dart +++ b/lib/pages/data/widgets/account_settings.dart @@ -6,7 +6,7 @@ import 'package:tunas/pages/common/titled_container.dart'; class AccountSettings extends StatelessWidget { const AccountSettings({super.key}); - List _computeCategoryList(Set subAccounts) { + List _computeCategoryList(List subAccounts) { return subAccounts.map((subAccount) => Row( children: [ Text(subAccount), @@ -23,7 +23,7 @@ class AccountSettings extends StatelessWidget { scrollDirection: Axis.vertical, child: Column( crossAxisAlignment: CrossAxisAlignment.start, - children: _computeCategoryList(state.subAccounts), + children: _computeCategoryList(state.accounts.map((account) => account.label).toList()), ), ), ), diff --git a/lib/pages/data/widgets/categories_settings.dart b/lib/pages/data/widgets/categories_settings.dart index 5c6ff89..8c2fc7e 100644 --- a/lib/pages/data/widgets/categories_settings.dart +++ b/lib/pages/data/widgets/categories_settings.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:tunas/domains/category/category_bloc.dart'; import 'package:tunas/pages/common/titled_container.dart'; -import 'package:tunas/repositories/account/models/category.dart'; +import 'package:tunas/repositories/metadata/models/category.dart'; class CategoriesSettings extends StatelessWidget { const CategoriesSettings({super.key}); diff --git a/lib/pages/home/home_page.dart b/lib/pages/home/home_page.dart index 2218f35..6f7b30f 100644 --- a/lib/pages/home/home_page.dart +++ b/lib/pages/home/home_page.dart @@ -8,7 +8,8 @@ import 'package:tunas/pages/budgets/budgets_page.dart'; import 'package:tunas/pages/data/data_page.dart'; import 'package:tunas/pages/stats/stats_page.dart'; import 'package:tunas/pages/transactions/transactions_page.dart'; -import 'package:tunas/repositories/account/account_repository.dart'; +import 'package:tunas/repositories/metadata/metadata_repository.dart'; +import 'package:tunas/repositories/transactions/transactions_repository.dart'; class HomePage extends StatelessWidget { const HomePage({super.key}); @@ -17,10 +18,10 @@ class HomePage extends StatelessWidget { Widget build(BuildContext 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))), + BlocProvider(create: (context) => AccountBloc(transactionsRepository: RepositoryProvider.of(context), metadataRepository: RepositoryProvider.of(context))), + BlocProvider(create: (context) => TransactionBloc(transactionsRepository: RepositoryProvider.of(context))), + BlocProvider(create: (context) => CategoryBloc(metadataRepository: RepositoryProvider.of(context))), + BlocProvider(create: (context) => BudgetBloc(metadataRepository: RepositoryProvider.of(context))), ], child: DefaultTabController( length: 4, diff --git a/lib/pages/stats/stats_page.dart b/lib/pages/stats/stats_page.dart index 4ec208b..0f1c9db 100644 --- a/lib/pages/stats/stats_page.dart +++ b/lib/pages/stats/stats_page.dart @@ -8,7 +8,8 @@ import 'package:tunas/pages/stats/widgets/monthly_categories_total_chart.dart'; import 'package:tunas/pages/stats/widgets/global_total_chart.dart'; import 'package:tunas/pages/stats/widgets/profit_indicator.dart'; import 'package:tunas/pages/stats/widgets/year_selector.dart'; -import 'package:tunas/repositories/account/account_repository.dart'; +import 'package:tunas/repositories/metadata/metadata_repository.dart'; +import 'package:tunas/repositories/transactions/transactions_repository.dart'; class StatsPage extends StatelessWidget { const StatsPage({super.key}); @@ -16,7 +17,10 @@ class StatsPage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => ChartBloc(accountRepository: RepositoryProvider.of(context)), + create: (context) => ChartBloc( + metadataRepository: RepositoryProvider.of(context), + transactionsRepository: RepositoryProvider.of(context), + ), child: BlocBuilder( builder: (context, state) => ListView( children: [ diff --git a/lib/pages/transactions/widgets/transaction_add_dialog.dart b/lib/pages/transactions/widgets/transaction_add_dialog.dart index f7e25bf..2ef90c2 100644 --- a/lib/pages/transactions/widgets/transaction_add_dialog.dart +++ b/lib/pages/transactions/widgets/transaction_add_dialog.dart @@ -4,7 +4,7 @@ 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'; +import 'package:tunas/repositories/transactions/models/transaction.dart'; class TransactionAddDialog extends StatelessWidget { const TransactionAddDialog({super.key}); diff --git a/lib/pages/transactions/widgets/transaction_form.dart b/lib/pages/transactions/widgets/transaction_form.dart index 7311ce9..1252580 100644 --- a/lib/pages/transactions/widgets/transaction_form.dart +++ b/lib/pages/transactions/widgets/transaction_form.dart @@ -126,7 +126,7 @@ class _TransactionAccountInput extends StatelessWidget { child: DropdownButtonFormField( value: state.transactionAccount.value.toString() == '' ? null : state.transactionAccount.value.toString(), onChanged: (value) => context.read().add(TransactionAccountChange(value!)), - items: accountState.subAccounts.map((e) => DropdownMenuItem(value: e, child: Text(e))).toList(), + items: accountState.accounts.map((e) => DropdownMenuItem(value: e.label, child: Text(e.label))).toList(), decoration: InputDecoration( icon: const Icon(Icons.account_box), hintText: 'Account', diff --git a/lib/pages/transactions/widgets/transaction_line.dart b/lib/pages/transactions/widgets/transaction_line.dart index 101fe3e..19dd076 100644 --- a/lib/pages/transactions/widgets/transaction_line.dart +++ b/lib/pages/transactions/widgets/transaction_line.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:tunas/pages/transactions/widgets/transaction_add_dialog.dart'; -import 'package:tunas/repositories/account/models/transaction.dart'; +import 'package:tunas/repositories/transactions/models/transaction.dart'; class TransactionLine extends StatelessWidget { final Transaction transaction; diff --git a/lib/repositories/account/account_repository.dart b/lib/repositories/account/account_repository.dart deleted file mode 100644 index 9c54df4..0000000 --- a/lib/repositories/account/account_repository.dart +++ /dev/null @@ -1,80 +0,0 @@ -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 = 'tunas_main_account.json'; - Account? currentAccount; - - final StorageClient _storageClient; - - final _transactionsController = BehaviorSubject>.seeded(const []); - final _categoriesController = BehaviorSubject>.seeded(const []); - final _budgetController = BehaviorSubject>.seeded(const []); - final _subAccountController = BehaviorSubject>.seeded(const {}); - - AccountRepository({ - required storageClient, - }) : _storageClient = storageClient { - init(); - } - - init() async { - final account = await getAccount(); - _broadcastAccountData(account); - } - - Stream> getTransactionsStream() { - return _transactionsController.asBroadcastStream(); - } - - Stream> getCategoriesStream() { - return _categoriesController.asBroadcastStream(); - } - - Stream> getBudgetsStream() { - return _budgetController.asBroadcastStream(); - } - - Stream> getSubAccountsStream() { - return _subAccountController.asBroadcastStream(); - } - - Future getAccount() async { - String json = await _storageClient.load(accountFile); - Map accountJson = jsonDecode(json); - return Account.fromJson(accountJson); - } - - saveAccount(Account account) async { - await _storageClient.save(accountFile, jsonEncode(account.toJson())); - _broadcastAccountData(account); - } - - saveTransactions(List transactions) async { - Account? account = currentAccount; - if (account == null) { - throw Error(); - } else {account.transactions = transactions; - await saveAccount(account); - } - } - - deleteAccount() async { - await _storageClient.delete(accountFile); - _broadcastAccountData(Account()); - } - - _broadcastAccountData(Account account) { - currentAccount = account; - _transactionsController.add(account.transactions); - _categoriesController.add(account.categories); - _budgetController.add(account.budgets); - _subAccountController.add(account.subAccounts); - } -} diff --git a/lib/repositories/account/models/account.dart b/lib/repositories/account/models/account.dart deleted file mode 100644 index 69f84b4..0000000 --- a/lib/repositories/account/models/account.dart +++ /dev/null @@ -1,36 +0,0 @@ -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; - Set subAccounts; - - Account({ - this.transactions = const [], - this.budgets = const [], - this.categories = const [], - this.subAccounts = 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(), - subAccounts: Set.from(jsonDecode(json['subAccounts'])), - ); - } - - 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()), - 'subAccounts': jsonEncode(subAccounts.toList()), - }; -} diff --git a/lib/repositories/json/json_repository.dart b/lib/repositories/json/json_repository.dart new file mode 100644 index 0000000..0876bc6 --- /dev/null +++ b/lib/repositories/json/json_repository.dart @@ -0,0 +1,32 @@ +import 'dart:convert'; + +import 'package:tunas/clients/storage/storage_client.dart'; +import 'package:tunas/repositories/json/models/json.dart'; + +class JsonRepository { + String accountFile = 'tunas_main_account.json'; + + final StorageClient _storageClient; + + JsonRepository({ + required storageClient, + }) : _storageClient = storageClient; + + saveJson(Json json) async { + await _storageClient.save(json.getJsonFileName(), jsonEncode(json.toJson())); + } + + Future loadJson(Json json, JsonFactory jsonFactory) async { + String jsonString = await _storageClient.load(json.getJsonFileName()); + + if (jsonString.isEmpty) { + return jsonFactory.fromJson({}); + } else { + return jsonFactory.fromJson(jsonDecode(jsonString)); + } + } + + deleteJson(Json json) async { + await _storageClient.delete(json.getJsonFileName()); + } +} diff --git a/lib/repositories/json/models/json.dart b/lib/repositories/json/models/json.dart new file mode 100644 index 0000000..e466478 --- /dev/null +++ b/lib/repositories/json/models/json.dart @@ -0,0 +1,8 @@ +abstract class Json { + Map toJson(); + String getJsonFileName(); +} + +abstract class JsonFactory { + T fromJson(Map json); +} \ No newline at end of file diff --git a/lib/repositories/metadata/metadata_repository.dart b/lib/repositories/metadata/metadata_repository.dart new file mode 100644 index 0000000..a608a16 --- /dev/null +++ b/lib/repositories/metadata/metadata_repository.dart @@ -0,0 +1,76 @@ +import 'package:rxdart/subjects.dart'; +import 'package:tunas/repositories/json/json_repository.dart'; +import 'package:tunas/repositories/metadata/models/budget.dart'; +import 'package:tunas/repositories/metadata/models/category.dart'; +import 'package:tunas/repositories/metadata/models/account.dart'; +import 'package:tunas/repositories/metadata/models/metadata.dart'; + +class MetadataRepository { + + final JsonRepository _jsonRepository; + final _categoriesController = BehaviorSubject>.seeded(const []); + final _budgetController = BehaviorSubject>.seeded(const []); + final _accountController = BehaviorSubject>.seeded(const []); + + MetadataRepository({ + required jsonRepository, + }) : _jsonRepository = jsonRepository; + + Stream> getCategoriesStream() { + return _categoriesController.asBroadcastStream(); + } + + Stream> getBudgetsStream() { + return _budgetController.asBroadcastStream(); + } + + Stream> getAccountsStream() { + return _accountController.asBroadcastStream(); + } + + loadMetadata() async { + Metadata metadata = await _jsonRepository.loadJson(Metadata(), MetadataFactory()); + _broadcastMetadata(metadata); + } + + saveCategories(List categories) async { + Metadata metadata = _constructMetadataFromControllers(); + metadata.categories = categories; + await _jsonRepository.saveJson(metadata); + _categoriesController.add(categories); + } + + saveBudgets(List budgets) async { + Metadata metadata = _constructMetadataFromControllers(); + metadata.budgets = budgets; + await _jsonRepository.saveJson(metadata); + _budgetController.add(budgets); + } + + saveAccounts(List accounts) async { + Metadata metadata = _constructMetadataFromControllers(); + metadata.accounts = accounts; + await _jsonRepository.saveJson(metadata); + _accountController.add(accounts); + } + + deleteMetadata() async { + Metadata metadata = Metadata(); + await _jsonRepository.saveJson(metadata); + _broadcastMetadata(metadata); + } + + _broadcastMetadata(Metadata metadata) { + _categoriesController.add(metadata.categories); + _budgetController.add(metadata.budgets); + _accountController.add(metadata.accounts); + } + + Metadata _constructMetadataFromControllers() { + return Metadata( + categories: _categoriesController.value, + budgets: _budgetController.value, + accounts: _accountController.value, + ); + } +} \ No newline at end of file diff --git a/lib/repositories/metadata/models/account.dart b/lib/repositories/metadata/models/account.dart new file mode 100644 index 0000000..69d7437 --- /dev/null +++ b/lib/repositories/metadata/models/account.dart @@ -0,0 +1,31 @@ +import 'dart:ui'; + +class Account { + String label; + String color; + bool saving; + + Account({ + this.label = '', + this.color = '', + this.saving = false, + }); + + factory Account.fromJson(Map json) { + return Account( + label: json['label'], + color: json['color'], + saving: bool.parse(json['saving']), + ); + } + + Map toJson() => { + 'label': label, + 'color': color, + 'saving': saving.toString(), + }; + + Color rgbToColor() { + return Color(int.parse(color.toUpperCase().replaceAll("#", ""), radix: 16)); + } +} diff --git a/lib/repositories/account/models/budget.dart b/lib/repositories/metadata/models/budget.dart similarity index 100% rename from lib/repositories/account/models/budget.dart rename to lib/repositories/metadata/models/budget.dart diff --git a/lib/repositories/account/models/category.dart b/lib/repositories/metadata/models/category.dart similarity index 100% rename from lib/repositories/account/models/category.dart rename to lib/repositories/metadata/models/category.dart diff --git a/lib/repositories/metadata/models/metadata.dart b/lib/repositories/metadata/models/metadata.dart new file mode 100644 index 0000000..c458905 --- /dev/null +++ b/lib/repositories/metadata/models/metadata.dart @@ -0,0 +1,39 @@ +import 'package:tunas/repositories/metadata/models/budget.dart'; +import 'package:tunas/repositories/metadata/models/category.dart'; +import 'package:tunas/repositories/json/models/json.dart'; +import 'package:tunas/repositories/metadata/models/account.dart'; + +class Metadata implements Json { + List budgets; + List categories; + List accounts; + + Metadata({ + this.budgets = const [], + this.categories = const [], + this.accounts = const [], + }); + + @override + Map toJson() => { + 'budgets': budgets.map((budget) => budget.toJson()).toList(), + 'categories': categories.map((category) => category.toJson()).toList(), + 'accounts': accounts.map((account) => account.toJson()).toList(), + }; + + @override + String getJsonFileName() { + return 'metadata.json'; + } +} + +class MetadataFactory implements JsonFactory { + @override + Metadata fromJson(Map json) { + return Metadata( + budgets: List.from(json['budgets']?.map((budget) => Budget.fromJson(budget))), + categories: List.from(json['categories']?.map((category) => Category.fromJson(category))), + accounts: List.from(json['accounts']?.map((account) => Account.fromJson(account))), + ); + } +} \ No newline at end of file diff --git a/lib/repositories/account/models/transaction.dart b/lib/repositories/transactions/models/transaction.dart similarity index 100% rename from lib/repositories/account/models/transaction.dart rename to lib/repositories/transactions/models/transaction.dart diff --git a/lib/repositories/transactions/models/transactions.dart b/lib/repositories/transactions/models/transactions.dart new file mode 100644 index 0000000..fb55b80 --- /dev/null +++ b/lib/repositories/transactions/models/transactions.dart @@ -0,0 +1,29 @@ +import 'package:tunas/repositories/json/models/json.dart'; +import 'package:tunas/repositories/transactions/models/transaction.dart'; + +class Transactions implements Json { + List transactions; + + Transactions({ + this.transactions = const [], + }); + + @override + Map toJson() => { + 'transactions': transactions.map((transaction) => transaction.toJson()).toList(), + }; + + @override + String getJsonFileName() { + return 'transactions.json'; + } +} + +class TransactionsFactory implements JsonFactory { + @override + Transactions fromJson(Map json) { + return Transactions( + transactions: List.from(json['transactions']?.map((transaction) => Transaction.fromJson(transaction))), + ); + } +} \ No newline at end of file diff --git a/lib/repositories/transactions/transactions_repository.dart b/lib/repositories/transactions/transactions_repository.dart new file mode 100644 index 0000000..ccdf864 --- /dev/null +++ b/lib/repositories/transactions/transactions_repository.dart @@ -0,0 +1,35 @@ +import 'package:rxdart/subjects.dart'; +import 'package:tunas/repositories/json/json_repository.dart'; +import 'package:tunas/repositories/transactions/models/transaction.dart'; +import 'package:tunas/repositories/transactions/models/transactions.dart'; + +class TransactionsRepository { + + final JsonRepository _jsonRepository; + final _transactionsController = BehaviorSubject>.seeded(const []); + + TransactionsRepository({ + required jsonRepository, + }) : _jsonRepository = jsonRepository; + + Stream> getTransactionsStream() { + return _transactionsController.asBroadcastStream(); + } + + loadTransactions() async { + Transactions transactions = await _jsonRepository.loadJson(Transactions(), TransactionsFactory()); + _transactionsController.add(transactions.transactions); + } + + saveTransactions(List transactionsList) async { + Transactions transactions = Transactions(transactions: transactionsList); + await _jsonRepository.saveJson(transactions); + _transactionsController.add(transactionsList); + } + + deleteTransactions() async { + Transactions transactions = Transactions(); + await _jsonRepository.saveJson(transactions); + _transactionsController.add([]); + } +} \ No newline at end of file