From 91fc0bb6073f1c7f2180cca5799fb552b6e0ef51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maty=C3=A1=C5=A1=20Caras?= Date: Mon, 22 Jan 2024 15:15:10 +0100 Subject: [PATCH] fix: show current balance on homepage --- lib/api/wallet.dart | 22 +++ lib/l10n/app_cs.arb | 5 +- lib/l10n/app_en.arb | 5 +- lib/util/graphs.dart | 16 +- lib/views/home.dart | 400 ++++++++++++++++++++++++++----------------- 5 files changed, 285 insertions(+), 163 deletions(-) diff --git a/lib/api/wallet.dart b/lib/api/wallet.dart index 8440b51..e2dcd1f 100644 --- a/lib/api/wallet.dart +++ b/lib/api/wallet.dart @@ -160,6 +160,28 @@ class Wallet { await WalletManager.saveWallet(this); } + /// Returns the current balance + /// + /// Basically just takes *starterBalance* and adds all the entries to it + double calculateCurrentBalance() { + var toAdd = 0.0; + for (final e in entries) { + toAdd += (e.type == EntryType.income) ? e.data.amount : -e.data.amount; + } + return starterBalance + toAdd; + } + + /// Returns the amount that was made/lost during a month + double calculateMonthStatus(int month, int year) { + var f = 0.0; + for (final e in entries.where( + (element) => element.date.year == year && element.date.month == month, + )) { + f += (e.type == EntryType.income) ? e.data.amount : -e.data.amount; + } + return f; + } + /// Empty wallet used for placeholders static final Wallet empty = Wallet( name: "Empty", diff --git a/lib/l10n/app_cs.arb b/lib/l10n/app_cs.arb index d1794ff..4558880 100644 --- a/lib/l10n/app_cs.arb +++ b/lib/l10n/app_cs.arb @@ -87,5 +87,8 @@ "dayCounter":"{count, plural, =1{den} few{dny} many{dnů} other{dnů} }", "yearCounter":"{count, plural, =1{rok} few{rok} many{let} other{let} }", "recurEvery":"{count, plural, =1{Opakovat každý} few{Opakovat každé} many{Opakovat každých} other{Opakovat každých}}", - "startingWithDate": "počínaje datem" + "startingWithDate": "počínaje datem", + "evenMoney":"Váš stav je stejný jako minulý měsíc.", + "balanceStatusA": "Váš stav je ", + "balanceStatusB": " oproti minulému měsíci." } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index c2fe305..f6b06cd 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -203,5 +203,8 @@ "startingWithDate": "starting", "@startingWithDate":{ "description": "Shown after 'Recur every X Y', e.g. 'Recur every 2 month starting 20th June 2023'" - } + }, + "evenMoney":"You're on the same balance as last month.", + "balanceStatusA": "Your balance is ", + "balanceStatusB": " compared to last month." } \ No newline at end of file diff --git a/lib/util/graphs.dart b/lib/util/graphs.dart index 211ee90..1fba13e 100644 --- a/lib/util/graphs.dart +++ b/lib/util/graphs.dart @@ -150,11 +150,11 @@ class ExpensesLineChart extends StatelessWidget { isStrokeCapRound: true, dotData: const FlDotData(show: false), belowBarData: BarAreaData(), - color: - (MediaQuery.of(context).platformBrightness == Brightness.dark) + color: ((MediaQuery.of(context).platformBrightness == + Brightness.dark) ? Colors.green.shade300 - : Colors.green - .harmonizeWith(Theme.of(context).colorScheme.primary), + : Colors.green) + .harmonizeWith(Theme.of(context).colorScheme.primary), spots: List.generate( yearly ? 12 : date.lastDay, (index) => FlSpot(index.toDouble(), incomeData[index]), @@ -167,11 +167,11 @@ class ExpensesLineChart extends StatelessWidget { isStrokeCapRound: true, dotData: const FlDotData(show: false), belowBarData: BarAreaData(), - color: - (MediaQuery.of(context).platformBrightness == Brightness.dark) + color: ((MediaQuery.of(context).platformBrightness == + Brightness.dark) ? Colors.red.shade300 - : Colors.red - .harmonizeWith(Theme.of(context).colorScheme.primary), + : Colors.red) + .harmonizeWith(Theme.of(context).colorScheme.primary), spots: List.generate( yearly ? 12 diff --git a/lib/views/home.dart b/lib/views/home.dart index a7606a4..20f4f65 100644 --- a/lib/views/home.dart +++ b/lib/views/home.dart @@ -222,173 +222,267 @@ class _HomeViewState extends State { ), ], ) - : GroupedListView( - groupHeaderBuilder: (element) => Text( - DateFormat.yMMMM(locale).format(element.date), - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - ), - ), - elements: selectedWallet!.entries, - itemComparator: (a, b) => b.date.compareTo(a.date), - groupBy: (e) => DateFormat.yMMMM(locale).format(e.date), - groupComparator: (a, b) { - // TODO: better sorting algorithm lol - final yearA = RegExp(r'\d+').firstMatch(a); - if (yearA == null) return 0; - final yearB = RegExp(r'\d+').firstMatch(b); - if (yearB == null) return 0; - final compareYears = int.parse(yearB.group(0)!) - .compareTo(int.parse(yearA.group(0)!)); - if (compareYears != 0) return compareYears; - final months = List.generate( - 12, - (index) => DateFormat.MMMM(locale).format( - DateTime(2023, index + 1), + : Column( + children: [ + Text( + NumberFormat.compactCurrency( + locale: locale, + symbol: selectedWallet!.currency.symbol, + ).format(selectedWallet!.calculateCurrentBalance()), + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 22, ), - ); - final monthA = RegExp('[^0-9 ]+').firstMatch(a); - if (monthA == null) return 0; - final monthB = RegExp('[^0-9 ]+').firstMatch(b); - if (monthB == null) return 0; - return months.indexOf(monthB.group(0)!).compareTo( - months.indexOf(monthA.group(0)!), - ); - }, - itemBuilder: (context, element) => Slidable( - endActionPane: ActionPane( - motion: const ScrollMotion(), - children: [ - SlidableAction( - onPressed: (c) { - Navigator.of(context) - .push( - MaterialPageRoute( - builder: (c) => CreateSingleEntryView( - w: selectedWallet!, - editEntry: element, - ), - ), - ) - .then( - (editedEntry) { - if (editedEntry == null) return; - selectedWallet!.entries.remove(element); - selectedWallet!.entries.add(editedEntry); - WalletManager.saveWallet(selectedWallet!); - setState(() {}); - }, - ); - }, - backgroundColor: - Theme.of(context).colorScheme.secondary, - foregroundColor: - Theme.of(context).colorScheme.onSecondary, - icon: Icons.edit, - ), - SlidableAction( - backgroundColor: - Theme.of(context).colorScheme.error, - foregroundColor: - Theme.of(context).colorScheme.onError, - icon: Icons.delete, - onPressed: (c) { - showDialog( - context: context, - builder: (cx) => PlatformDialog( - title: - AppLocalizations.of(context).sureDialog, - content: Text( - AppLocalizations.of(context).deleteSure, - ), - actions: [ - PlatformButton( - text: AppLocalizations.of(context).yes, - onPressed: () { - selectedWallet?.entries.removeWhere( - (e) => e.id == element.id, - ); - - WalletManager.saveWallet( - selectedWallet!, - ); - Navigator.of(cx).pop(); - setState(() {}); - }, - ), - PlatformButton( - text: AppLocalizations.of(context).no, - onPressed: () { - Navigator.of(cx).pop(); - }, - ), - ], - ), - ); - }, - ), - ], ), - child: ListTile( - leading: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - color: element.category.color, - ), - child: Padding( - padding: const EdgeInsets.all(8), - child: Icon( - element.category.icon, - color: - element.category.color.calculateTextColor(), - ), - ), - ), - title: Text(element.data.name), - subtitle: RichText( + const SizedBox( + height: 5, + ), + if (selectedWallet!.calculateMonthStatus( + DateTime.now().month, + DateTime.now().year, + ) == + 0) + Text(AppLocalizations.of(context).evenMoney) + else + RichText( text: TextSpan( children: [ TextSpan( - text: NumberFormat.currency( - symbol: selectedWallet!.currency.symbol, - ).format(element.data.amount), + text: AppLocalizations.of(context) + .balanceStatusA, + ), + TextSpan( style: TextStyle( - color: (element.type == EntryType.income) - ? (MediaQuery.of(context) - .platformBrightness == - Brightness.dark) - ? Colors.green.shade300 - : Colors.green.harmonizeWith( - Theme.of(context) - .colorScheme - .primary, - ) - : (MediaQuery.of(context) - .platformBrightness == - Brightness.dark) - ? Colors.red.shade300 - : Colors.red.harmonizeWith( - Theme.of(context) - .colorScheme - .primary, - ), + color: (selectedWallet! + .calculateMonthStatus( + DateTime.now().month, + DateTime.now().year, + ) > + 0 + ? ((MediaQuery.of(context) + .platformBrightness == + Brightness.dark) + ? Colors.green.shade300 + : Colors.green) + .harmonizeWith( + Theme.of(context) + .colorScheme + .primary, + ) + : ((MediaQuery.of(context) + .platformBrightness == + Brightness.dark) + ? Colors.red.shade300 + : Colors.red) + .harmonizeWith( + Theme.of(context) + .colorScheme + .primary, + )), + ), + text: NumberFormat.compactCurrency( + locale: locale, + symbol: selectedWallet!.currency.symbol, + ).format( + selectedWallet!.calculateMonthStatus( + DateTime.now().month, + DateTime.now().year, + ), ), ), TextSpan( - text: - " | ${DateFormat.MMMd(locale).format(element.date)}", - style: TextStyle( - color: Theme.of(context) - .colorScheme - .background - .calculateTextColor(), - ), + text: AppLocalizations.of(context) + .balanceStatusB, ), ], ), ), + const SizedBox( + height: 10, ), - ), + Expanded( + child: GroupedListView( + groupHeaderBuilder: (element) => Text( + DateFormat.yMMMM(locale).format(element.date), + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + ), + ), + elements: selectedWallet!.entries, + itemComparator: (a, b) => b.date.compareTo(a.date), + groupBy: (e) => + DateFormat.yMMMM(locale).format(e.date), + groupComparator: (a, b) { + // TODO: better sorting algorithm lol + final yearA = RegExp(r'\d+').firstMatch(a); + if (yearA == null) return 0; + final yearB = RegExp(r'\d+').firstMatch(b); + if (yearB == null) return 0; + final compareYears = int.parse(yearB.group(0)!) + .compareTo(int.parse(yearA.group(0)!)); + if (compareYears != 0) return compareYears; + final months = List.generate( + 12, + (index) => DateFormat.MMMM(locale).format( + DateTime(2023, index + 1), + ), + ); + final monthA = RegExp('[^0-9 ]+').firstMatch(a); + if (monthA == null) return 0; + final monthB = RegExp('[^0-9 ]+').firstMatch(b); + if (monthB == null) return 0; + return months.indexOf(monthB.group(0)!).compareTo( + months.indexOf(monthA.group(0)!), + ); + }, + itemBuilder: (context, element) => Slidable( + endActionPane: ActionPane( + motion: const ScrollMotion(), + children: [ + SlidableAction( + onPressed: (c) { + Navigator.of(context) + .push( + MaterialPageRoute( + builder: (c) => CreateSingleEntryView( + w: selectedWallet!, + editEntry: element, + ), + ), + ) + .then( + (editedEntry) { + if (editedEntry == null) return; + selectedWallet!.entries + .remove(element); + selectedWallet!.entries + .add(editedEntry); + WalletManager.saveWallet( + selectedWallet!, + ); + setState(() {}); + }, + ); + }, + backgroundColor: + Theme.of(context).colorScheme.secondary, + foregroundColor: Theme.of(context) + .colorScheme + .onSecondary, + icon: Icons.edit, + ), + SlidableAction( + backgroundColor: + Theme.of(context).colorScheme.error, + foregroundColor: + Theme.of(context).colorScheme.onError, + icon: Icons.delete, + onPressed: (c) { + showDialog( + context: context, + builder: (cx) => PlatformDialog( + title: AppLocalizations.of(context) + .sureDialog, + content: Text( + AppLocalizations.of(context) + .deleteSure, + ), + actions: [ + PlatformButton( + text: AppLocalizations.of(context) + .yes, + onPressed: () { + selectedWallet?.entries + .removeWhere( + (e) => e.id == element.id, + ); + + WalletManager.saveWallet( + selectedWallet!, + ); + Navigator.of(cx).pop(); + setState(() {}); + }, + ), + PlatformButton( + text: AppLocalizations.of(context) + .no, + onPressed: () { + Navigator.of(cx).pop(); + }, + ), + ], + ), + ); + }, + ), + ], + ), + child: ListTile( + leading: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: element.category.color, + ), + child: Padding( + padding: const EdgeInsets.all(8), + child: Icon( + element.category.icon, + color: element.category.color + .calculateTextColor(), + ), + ), + ), + title: Text(element.data.name), + subtitle: RichText( + text: TextSpan( + children: [ + TextSpan( + text: NumberFormat.currency( + symbol: + selectedWallet!.currency.symbol, + ).format(element.data.amount), + style: TextStyle( + color: (element.type == + EntryType.income) + ? (MediaQuery.of(context) + .platformBrightness == + Brightness.dark) + ? Colors.green.shade300 + : Colors.green.harmonizeWith( + Theme.of(context) + .colorScheme + .primary, + ) + : (MediaQuery.of(context) + .platformBrightness == + Brightness.dark) + ? Colors.red.shade300 + : Colors.red.harmonizeWith( + Theme.of(context) + .colorScheme + .primary, + ), + ), + ), + TextSpan( + text: + " | ${DateFormat.MMMd(locale).format(element.date)}", + style: TextStyle( + color: Theme.of(context) + .colorScheme + .background + .calculateTextColor(), + ), + ), + ], + ), + ), + ), + ), + ), + ), + ], ), ), ),