From 38d827799c5e687326db975c3b988b5f731eba1f Mon Sep 17 00:00:00 2001 From: Florian Schindler <florianschndlr@gmail.com> Date: Tue, 27 Jun 2023 22:07:42 +0200 Subject: [PATCH] tried adding functionality to top of home_view TRIED bcs it not working, fixi dixi tomorrow, wenn ich nicht vergessen hab wie das alles funktioniert und was ich mir dabei gedacht hab --- .../frontend/utils/transaction_listtile.dart | 4 +- trackeroo/lib/frontend/views/home_view.dart | 339 ++++++++++-------- trackeroo/lib/logic/services/locator.dart | 16 +- .../services/transactions_controller.dart | 26 +- 4 files changed, 231 insertions(+), 154 deletions(-) diff --git a/trackeroo/lib/frontend/utils/transaction_listtile.dart b/trackeroo/lib/frontend/utils/transaction_listtile.dart index c5f403b..2c23f18 100644 --- a/trackeroo/lib/frontend/utils/transaction_listtile.dart +++ b/trackeroo/lib/frontend/utils/transaction_listtile.dart @@ -29,7 +29,9 @@ class _TransactionListtileState extends State<TransactionListtile> { key: UniqueKey(), direction: DismissDirection.startToEnd, onDismissed: (_) => { - locator.get<TransactionsController>().deleteTransaction(widget.transaction), + setState(() { + locator.get<TransactionsController>().deleteTransaction(widget.transaction); + }), ScaffoldMessenger.of(context).removeCurrentSnackBar(), ScaffoldMessenger.of(context).showSnackBar( SnackBar( diff --git a/trackeroo/lib/frontend/views/home_view.dart b/trackeroo/lib/frontend/views/home_view.dart index 7e09852..ce71ad1 100644 --- a/trackeroo/lib/frontend/views/home_view.dart +++ b/trackeroo/lib/frontend/views/home_view.dart @@ -3,7 +3,9 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:trackeroo/frontend/utils/transaction_listtile.dart'; import 'package:trackeroo/frontend/views/onboarding_view.dart'; +import 'package:trackeroo/logic/models/category.dart'; import 'package:trackeroo/logic/models/transaction.dart'; +import 'package:trackeroo/logic/services/categories_controller.dart'; import 'package:trackeroo/logic/services/locator.dart'; import 'package:trackeroo/logic/services/transactions_controller.dart'; @@ -28,176 +30,213 @@ class _HomeViewState extends State<HomeView> { return SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(16.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: <Widget>[ - const Text( - 'Balance', - style: TextStyle( - fontSize: 14.0, - fontWeight: FontWeight.bold - ), - ), - const SizedBox(height: 5.0), - const Text( - '4932.32 €', - style: TextStyle( - fontSize: 40.0, - fontWeight: FontWeight.w300 - ), - ), - const SizedBox(height: 10.0), - const Text( - 'Overview', - style: TextStyle( - fontSize: 14.0, - fontWeight: FontWeight.bold - ), - ), - const SizedBox(height: 5.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: ValueListenableBuilder( + valueListenable: transContr.transactionsBox.listenable(), + builder: (context, Box<Transaction> box, widget) { + transContr.transactionsList = box.values.toList(); + transContr.transactionsList.sort((b, a) => a.createdAt.compareTo(b.createdAt)); + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: <Widget>[ + const Text( + 'Balance', + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.bold + ), + ), + const SizedBox(height: 5.0), + Text( + transContr.balance.toStringAsFixed(2), + style: const TextStyle( + fontSize: 40.0, + fontWeight: FontWeight.w300 + ), + ), + const SizedBox(height: 10.0), + const Text( + 'Overview', + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.bold + ), + ), + const SizedBox(height: 5.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Icon(Icons.arrow_downward, color: Colors.green, size: 16.0), - Text('Income'), + const Row( + children: [ + Icon(Icons.arrow_downward, color: Colors.green, size: 16.0), + Text('Income'), + ], + ), + Text('${transContr.income.toStringAsFixed(2)} €') ], ), - Text('1234.56€') - ], - ), - Center( - child: SizedBox( - height: 200.0, - width: 50.0, - child: PieChart( - PieChartData( - centerSpaceRadius: 50.0, - sections: [ - buildPieChartSection(context, 93.302), - buildPieChartSection(context, 43.302), - buildPieChartSection(context, 104.302), - buildPieChartSection(context, 120.302), - buildPieChartSection(context, 110.302) - ] - ) + Center( + child: SizedBox( + height: 200.0, + width: 50.0, + child: PieChart( + PieChartData( + centerSpaceRadius: 0, + sections: transContr.transactionsList.isNotEmpty ? buildPieChartSectionList(context) : null, + ), + swapAnimationDuration: const Duration(milliseconds: 500), + swapAnimationCurve: Curves.easeInOut, + ), + ), ), - ), - ), - const Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Icon(Icons.arrow_upward, color: Colors.red, size: 16.0), - Text('Expenses'), + const Row( + children: [ + Icon(Icons.arrow_upward, color: Colors.red, size: 16.0), + Text('Expenses'), + ], + ), + Text('${transContr.expenses.toStringAsFixed(2)} €') ], ), - Text('1234.56€') ], ), - ], - ), - const SizedBox(height: 10.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - FilterChip( - onSelected: (value) => setState(() { - timespanView = Timespan.daily; - }), - label: const Text('Daily'), - selected: timespanView == Timespan.daily, - showCheckmark: false - ), - FilterChip( - onSelected: (value) => setState(() { - timespanView = Timespan.weekly; - }), - label: const Text('Weekly'), - selected: timespanView == Timespan.weekly, - showCheckmark: false - ), - FilterChip( - onSelected: (value) => setState(() { - timespanView = Timespan.monthly; - }), - label: const Text('Montly'), - selected: timespanView == Timespan.monthly, - showCheckmark: false + const SizedBox(height: 10.0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + FilterChip( + onSelected: (value) => setState(() { + timespanView = Timespan.daily; + }), + label: const Text('Daily'), + selected: timespanView == Timespan.daily, + showCheckmark: false + ), + FilterChip( + onSelected: (value) => setState(() { + timespanView = Timespan.weekly; + }), + label: const Text('Weekly'), + selected: timespanView == Timespan.weekly, + showCheckmark: false + ), + FilterChip( + onSelected: (value) => setState(() { + timespanView = Timespan.monthly; + }), + label: const Text('Montly'), + selected: timespanView == Timespan.monthly, + showCheckmark: false + ), + FilterChip( + onSelected: (value) => setState(() { + timespanView = Timespan.yearly; + }), + label: const Text('Yearly'), + selected: timespanView == Timespan.yearly, + showCheckmark: false + ), + FilterChip( + onSelected: (value) => setState(() { + timespanView = Timespan.all; + }), + label: const Text('All'), + selected: timespanView == Timespan.all, + showCheckmark: false + ) + ], ), - FilterChip( - onSelected: (value) => setState(() { - timespanView = Timespan.yearly; - }), - label: const Text('Yearly'), - selected: timespanView == Timespan.yearly, - showCheckmark: false + const SizedBox(height: 20.0), + const Text( + 'Transactions', + style: TextStyle( + fontSize: 14.0, + fontWeight: FontWeight.bold + ), ), - FilterChip( - onSelected: (value) => setState(() { - timespanView = Timespan.all; - }), - label: const Text('All'), - selected: timespanView == Timespan.all, - showCheckmark: false - ) - ], - ), - const SizedBox(height: 20.0), - const Text( - 'Transactions', - style: TextStyle( - fontSize: 14.0, - fontWeight: FontWeight.bold - ), - ), - const SizedBox(height: 5.0), - ValueListenableBuilder( - valueListenable: transContr.transactionsBox.listenable(), - builder: (context, Box<Transaction> box, widget) { - transContr.transactionsList = box.values.toList(); - transContr.transactionsList.sort((b, a) => a.createdAt.compareTo(b.createdAt)); - if(box.isEmpty) { - return const SizedBox( - height: 200.0, - child: Center( - child: Text('no existing transactions yet'), - ), - ); - } - return ListView.builder( + const SizedBox(height: 5.0), + box.isEmpty ? const SizedBox( + height: 200.0, + child: Center( + child: Text('no existing transactions yet'), + ), + ) : ListView.builder( physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, itemCount: transContr.transactionsList.length <= 25 ? transContr.transactionsList.length : 25, itemBuilder: (context, index) => TransactionListtile(transaction: transContr.transactionsList[index]) - ); - }, - ), - FilledButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => const OnboardingView()) - ); - }, - child: const Text('onboarding') - ), - ], + ), + FilledButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const OnboardingView()) + ); + }, + child: const Text('onboarding') + ), + ], + ); + } ), ), ); } - PieChartSectionData buildPieChartSection(BuildContext context, double value) { - return PieChartSectionData( - value: value, - color: Theme.of(context).colorScheme.secondaryContainer - ); + List<PieChartSectionData> buildPieChartSectionList(BuildContext context) { + List<PieChartSectionData> sectionList = []; + + int elapsedDays = transContr.transactionsList.last.createdAt.difference(DateTime.now()).inDays; + if(elapsedDays <= 0) elapsedDays = 1; + + double other = 0; + + for (Category cat in locator.get<CategoriesController>().categories.values) { + double categorySum = 0; + double avg = 0; + for(Transaction tr in transContr.transactionsList) { + if(tr.categoryId == cat.id) { + categorySum += tr.amount; + } + } + + if(timespanView == Timespan.daily) { + avg = categorySum / elapsedDays; + } else if(timespanView == Timespan.weekly) { + avg = categorySum / (elapsedDays / 7); + } else if(timespanView == Timespan.monthly) { + avg = categorySum / (elapsedDays / 30); + } else if(timespanView == Timespan.yearly) { + avg = categorySum / (elapsedDays / 365); + } else { + avg = categorySum; + } + + bool meetsThreshold = (categorySum / transContr.balance) > 0.2; + + if(!meetsThreshold) { + other += avg; + } + + if(!avg.isNaN && meetsThreshold) { + sectionList.add(PieChartSectionData( + value: double.parse(avg.toStringAsFixed(2)), + color: Color(cat.colorValue), + radius: 100.0 + )); + } + } + sectionList.add(PieChartSectionData( + value: double.parse(other.toStringAsFixed(2)), + color: Colors.grey, + radius: 100.0 + )); + return sectionList; } } diff --git a/trackeroo/lib/logic/services/locator.dart b/trackeroo/lib/logic/services/locator.dart index 5936372..756ebb9 100644 --- a/trackeroo/lib/logic/services/locator.dart +++ b/trackeroo/lib/logic/services/locator.dart @@ -49,9 +49,23 @@ Future<void> setupLocatorService() async { locator.registerLazySingleton<CategoriesController>(() => categoriesController); Box<Transaction> transactionsBox = await Hive.openBox<Transaction>('transactions_box'); + double balance = 0; + double income = 0; + double expenses = 0; + for(Transaction tr in transactionsBox.values.toList()) { + balance += tr.amount; + if(tr.amount.isNegative) { + expenses += tr.amount; + } else { + income += tr.amount; + } + } TransactionsController transactionsController = TransactionsController( transactionsBox: transactionsBox, - transactionsList: transactionsBox.values.toList() + transactionsList: transactionsBox.values.toList(), + balance: balance, + income: income, + expenses: expenses ); locator.registerLazySingleton<TransactionsController>(() => transactionsController); } diff --git a/trackeroo/lib/logic/services/transactions_controller.dart b/trackeroo/lib/logic/services/transactions_controller.dart index 88a7cfc..5f631a8 100644 --- a/trackeroo/lib/logic/services/transactions_controller.dart +++ b/trackeroo/lib/logic/services/transactions_controller.dart @@ -4,11 +4,14 @@ import 'package:trackeroo/logic/models/transaction.dart'; import 'package:trackeroo/logic/services/categories_controller.dart'; import 'package:trackeroo/logic/services/locator.dart'; -class TransactionsController { - TransactionsController({required this.transactionsBox, required this.transactionsList}); +class TransactionsController extends ChangeNotifier { + TransactionsController({required this.transactionsBox, required this.transactionsList, required this.balance, required this.income, required this.expenses}); Box<Transaction> transactionsBox; List<Transaction> transactionsList; + double balance; + double income; + double expenses; late Transaction lastDeletedTransaction; Future<bool> saveTransaction(Transaction transaction) async { @@ -18,6 +21,12 @@ class TransactionsController { transaction.id = id; await transactionsBox.put(transaction.id, transaction); transactionsList = transactionsBox.values.toList(); + balance += transaction.amount; + if(transaction.amount.isNegative) { + expenses += transaction.amount; + } else { + income += transaction.amount; + } } catch (e) { return false; } @@ -39,6 +48,12 @@ class TransactionsController { lastDeletedTransaction = transaction; await transactionsBox.delete(transaction.id); transactionsList = transactionsBox.values.toList(); + balance -= transaction.amount; + if(transaction.amount.isNegative) { + expenses -= transaction.amount; + } else { + income -= transaction.amount; + } } catch (e) { return false; } @@ -49,6 +64,13 @@ class TransactionsController { try { await transactionsBox.put(lastDeletedTransaction.id, lastDeletedTransaction); transactionsList = transactionsBox.values.toList(); + balance += lastDeletedTransaction.amount; + + if(lastDeletedTransaction.amount.isNegative) { + expenses += lastDeletedTransaction.amount; + } else { + income += lastDeletedTransaction.amount; + } } catch (e) { return false; } -- GitLab