From 070fc1bc0816b1b9eb1177724f741165ebca9d5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maty=C3=A1=C5=A1=20Caras?= Date: Sat, 10 Feb 2024 13:11:22 +0100 Subject: [PATCH] feat: remake graph view --- CHANGELOG.md | 4 + lib/l10n/app_cs.arb | 16 +- lib/l10n/app_en.arb | 69 ++- lib/util/graphs.dart | 33 +- lib/views/graph_view.dart | 866 ++++++++++++++++++++++++++------------ pubspec.lock | 8 + pubspec.yaml | 1 + 7 files changed, 719 insertions(+), 278 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 869aa46..202b4d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ - Fix starting balance not saving - Fix overlay disabling tappig on edit/delete buttons on home view - Allow changing dates on entries +- Graph view now uses tabs +- Graphs now display correct tooltips when displaying only income or only expenses +- Change graph container style when using light mode +- Make pie chart values more visible by adding the category's corresponding color as background # 1.0.0-alpha+5 - Add tests - Add searching through entries to homepage diff --git a/lib/l10n/app_cs.arb b/lib/l10n/app_cs.arb index 5be270f..5b6a09c 100644 --- a/lib/l10n/app_cs.arb +++ b/lib/l10n/app_cs.arb @@ -111,8 +111,16 @@ "sortOldest":"Nejstarší první", "sort":"Seřadit", "search":"Prohledat", - "expensesPerYear":"Měsíční výdaje v roce {year}", - "expensesPerMonth":"Denní výdaje během měsíce {monthYear}", - "expensesPerCategory":"Dohromady výdaje za kategorii", - "date":"Datum" + "expensesPerYear":"Výdaje za měsíc v roce {year}", + "expensesPerMonth":"Výdaje za den během měsíce {monthYear}", + "date":"Datum", + "incomePlural":"Příjmy", + "incomePerYear":"Příjmy za měsíc v roce {year}", + "incomePerMonth":"Příjmy za den během měsíce {monthYear}", + "expensesPerMonthCategory":"Výdaje podle kategorie během měsíce {monthYear}", + "expensesPerYearCategory":"Výdaje podle kategorie za rok {year}", + "incomePerYearCategory":"Příjmy podle kategorie za rok {year}", + "incomePerMonthCategory":"Příjmy podle kategorie za měsíc {monthYear}", + "selectYear":"Zvolte rok", + "selectMonth":"Zvolte měsíc a rok" } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index f479516..4281354 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -247,6 +247,71 @@ } } }, - "expensesPerCategory":"Total expenses per category", - "date":"Date" + "incomePerYear":"Income per month in {year}", + "@incomePerYear":{ + "placeholders": { + "year":{ + "description": "The year of the monthly expense sum", + "example": "2024", + "type": "int" + } + } + }, + "incomePerMonth":"Income per day during {monthYear}", + "@incomePerMonth":{ + "placeholders": { + "monthYear":{ + "description": "Month and year formatted through DateFormat class", + "example": "June, 2024", + "type": "String" + } + } + }, + "date":"Date", + "incomePlural":"Income", + "@incomePlural":{ + "description": "Plural form of 'Income'" + }, + "expensesPerMonthCategory":"Expenses per category during {monthYear}", + "@expensesPerMonthCategory":{ + "placeholders": { + "monthYear":{ + "description": "Month and year formatted through DateFormat class", + "example": "June, 2024", + "type": "String" + } + } + }, + "expensesPerYearCategory":"Expenses per category in {year}", + "@expensesPerYearCategory":{ + "placeholders": { + "year":{ + "description": "The year", + "example": "2024", + "type": "int" + } + } + }, + "incomePerMonthCategory":"Income per category during {monthYear}", + "@incomePerMonthCategory":{ + "placeholders": { + "monthYear":{ + "description": "Month and year formatted through DateFormat class", + "example": "June, 2024", + "type": "String" + } + } + }, + "incomePerYearCategory":"Income per category in {year}", + "@incomePerYearCategory":{ + "placeholders": { + "year":{ + "description": "The year", + "example": "2024", + "type": "int" + } + } + }, + "selectYear":"Select a year", + "selectMonth":"Select a month and year" } \ No newline at end of file diff --git a/lib/util/graphs.dart b/lib/util/graphs.dart index 77e5c27..6ad292e 100644 --- a/lib/util/graphs.dart +++ b/lib/util/graphs.dart @@ -77,7 +77,8 @@ class ExpensesLineChart extends StatelessWidget { (index) => LineTooltipItem( // Changes what's rendered on the tooltip // when clicked in the chart - (spots[index].barIndex == 0) // income chart + (spots[index].barIndex == 0 && + incomeData.isNotEmpty) // income chart ? (yearly ? AppLocalizations.of(context).incomeForMonth( DateFormat.MMMM(locale).format( @@ -122,14 +123,11 @@ class ExpensesLineChart extends StatelessWidget { )), TextStyle(color: spots[index].bar.color), children: [ - TextSpan( - text: "\n${yearly ? DateFormat.MMMM(locale).format( - DateTime( - date.year, - index + 1, - ), - ) : DateFormat.yMMMMd(locale).format(DateTime(date.year, date.month, spots[index].spotIndex + 1))}", - ), + if (!yearly) + TextSpan( + text: + "\n${DateFormat.yMMMMd(locale).format(DateTime(date.year, date.month, spots[index].spotIndex + 1))}", + ), ], ), ), @@ -137,12 +135,11 @@ class ExpensesLineChart extends StatelessWidget { ), maxY: maxY, maxX: yearly - ? 12 + ? 11 : date.lastDay.toDouble() - 1, // remove 1 because we are indexing from 0 minY: 0, minX: 0, - backgroundColor: Theme.of(context).colorScheme.background, lineBarsData: [ if (incomeData.isNotEmpty) LineChartBarData( @@ -292,7 +289,7 @@ class ExpensesBarChart extends StatelessWidget { getTooltipItem: (group, groupIndex, rod, rodIndex) => yearly // create custom tooltips for graph bars ? BarTooltipItem( - (rodIndex == 1) + (rodIndex == 1 || incomeData.isEmpty) // expense ? AppLocalizations.of(context).expensesForMonth( DateFormat.MMMM(locale).format( DateTime(date.year, groupIndex + 1), @@ -304,6 +301,7 @@ class ExpensesBarChart extends StatelessWidget { ).format(rod.toY), ) : AppLocalizations.of(context).incomeForMonth( + // income DateFormat.MMMM(locale).format( DateTime(date.year, groupIndex + 1), ), @@ -394,6 +392,7 @@ class CategoriesPieChart extends StatefulWidget { required this.entries, required this.categories, required this.symbol, + required this.locale, super.key, }); @@ -406,6 +405,9 @@ class CategoriesPieChart extends StatefulWidget { /// Currency symbol displayed on the chart final String symbol; + /// User locale + final String locale; + @override State createState() => _CategoriesPieChartState(); } @@ -467,8 +469,10 @@ class _CategoriesPieChartState extends State { sections: List.generate( widget.categories.length, (index) => PieChartSectionData( - title: NumberFormat.compactCurrency(symbol: widget.symbol) - .format( + title: NumberFormat.compactCurrency( + symbol: widget.symbol, + locale: widget.locale, + ).format( widget.entries .where( (element) => @@ -485,6 +489,7 @@ class _CategoriesPieChartState extends State { color: widget.categories[index].color.calculateTextColor(), fontWeight: FontWeight.bold, + backgroundColor: widget.categories[index].color, ), color: widget.categories[index].color, value: widget.entries diff --git a/lib/views/graph_view.dart b/lib/views/graph_view.dart index 64c5c8b..f12047e 100644 --- a/lib/views/graph_view.dart +++ b/lib/views/graph_view.dart @@ -1,6 +1,8 @@ import 'dart:async'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:intl/intl.dart'; import 'package:prasule/api/category.dart'; @@ -14,6 +16,7 @@ import 'package:prasule/util/utils.dart'; import 'package:prasule/views/settings/settings.dart'; import 'package:prasule/views/setup.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:wheel_chooser/wheel_chooser.dart'; /// Shows data from a [Wallet] in graphs class GraphView extends StatefulWidget { @@ -29,9 +32,7 @@ class _GraphViewState extends State { Wallet? selectedWallet; List wallets = []; String? locale; - Set yearlyBtnSet = {"monthly"}; - Set graphTypeSet = {"expense", "income"}; - bool get yearly => yearlyBtnSet.contains("yearly"); + bool yearly = true; @override void didChangeDependencies() { @@ -66,6 +67,8 @@ class _GraphViewState extends State { return data; } + final availableYears = >[]; + Future loadWallet() async { wallets = await WalletManager.listWallets(); if (wallets.isEmpty && mounted) { @@ -76,6 +79,17 @@ class _GraphViewState extends State { return; } selectedWallet = wallets.first; + availableYears.clear(); + for (final entry in selectedWallet!.entries) { + if (!availableYears.any((element) => element.value == entry.date.year)) { + availableYears.add( + WheelChoice( + value: entry.date.year, + title: entry.date.year.toString(), + ), + ); + } + } setState(() {}); } @@ -92,275 +106,611 @@ class _GraphViewState extends State { @override Widget build(BuildContext context) { - return Scaffold( - floatingActionButton: Tooltip( - message: AppLocalizations.of(context).changeDate, - child: FloatingActionButton( - child: const Icon(Icons.calendar_month), - onPressed: () async { - final firstDate = (selectedWallet!.entries - ..sort( - (a, b) => a.date.compareTo(b.date), - )) - .first - .date; - final newDate = await showDatePicker( - context: context, - initialDate: DateTime( - _selectedDate.year, - _selectedDate.month, - ), - firstDate: firstDate, - lastDate: DateTime.now(), - initialEntryMode: yearly - ? DatePickerEntryMode.input - : DatePickerEntryMode.calendar, - initialDatePickerMode: - yearly ? DatePickerMode.year : DatePickerMode.day, - ); - if (newDate == null) return; - _selectedDate = newDate; - setState(() {}); - }, - ), - ), - appBar: AppBar( - title: DropdownButton( - value: - (selectedWallet == null) ? -1 : wallets.indexOf(selectedWallet!), - items: [ - ...wallets.map( - (e) => DropdownMenuItem( - value: wallets.indexOf( - e, - ), - child: Text(e.name), - ), - ), - DropdownMenuItem( - value: -1, - child: Text(AppLocalizations.of(context).newWallet), - ), - ], - onChanged: (v) async { - if (v == null || v == -1) { - await Navigator.of(context).push( - platformRoute( - (c) => const SetupView( - newWallet: true, + return DefaultTabController( + length: 2, + child: Scaffold( + floatingActionButton: Tooltip( + message: AppLocalizations.of(context).changeDate, + child: FloatingActionButton( + child: const Icon(Icons.calendar_month), + onPressed: () async { + var selectedYear = _selectedDate.year; + var selectedMonth = _selectedDate.month; + await showAdaptiveDialog( + context: context, + builder: (c) => AlertDialog.adaptive( + title: Text( + yearly + ? AppLocalizations.of(context).selectYear + : AppLocalizations.of(context).selectMonth, ), - ), - ); - wallets = await WalletManager.listWallets(); - logger.i(wallets.length); - selectedWallet = wallets.last; - setState(() {}); - return; - } - selectedWallet = wallets[v]; - setState(() {}); - }, - ), - actions: [ - PopupMenuButton( - itemBuilder: (context) => [ - AppLocalizations.of(context).settings, - AppLocalizations.of(context).about, - ].map((e) => PopupMenuItem(value: e, child: Text(e))).toList(), - onSelected: (value) { - if (value == AppLocalizations.of(context).settings) { - Navigator.of(context) - .push( - platformRoute( - (context) => const SettingsView(), - ), - ) - .then((value) async { - selectedWallet = - await WalletManager.loadWallet(selectedWallet!.name); - final s = await SharedPreferences.getInstance(); - chartType = s.getInt("monthlygraph") ?? 2; - setState(() {}); - }); - } else if (value == AppLocalizations.of(context).about) { - showAbout(context); - } - }, - ), - ], - ), - drawer: makeDrawer(context, 2), - body: SingleChildScrollView( - child: Center( - child: (selectedWallet == null) - ? const CircularProgressIndicator( - strokeWidth: 5, - ) - : SizedBox( - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SegmentedButton( - segments: [ - ButtonSegment( - value: "expense", - label: Text(AppLocalizations.of(context).expenses), - ), - ButtonSegment( - value: "income", - label: Text(AppLocalizations.of(context).income), - ), - ], - selected: graphTypeSet, - multiSelectionEnabled: true, - onSelectionChanged: (selection) { - graphTypeSet = selection; - setState(() {}); - }, - ), - const SizedBox( - height: 5, - ), - SegmentedButton( - segments: [ - ButtonSegment( - value: "yearly", - label: Text(AppLocalizations.of(context).yearly), - ), - ButtonSegment( - value: "monthly", - label: Text(AppLocalizations.of(context).monthly), - ), - ], - selected: yearlyBtnSet, - onSelectionChanged: (selection) async { - yearlyBtnSet = selection; - final s = await SharedPreferences.getInstance(); - chartType = yearly - ? (s.getInt("yearlygraph") ?? 1) - : (s.getInt("monthlygraph") ?? 2); - setState(() {}); - }, - ), - const SizedBox(height: 5), - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: - Theme.of(context).colorScheme.secondaryContainer, - ), - child: Padding( - padding: const EdgeInsets.all(8), - child: Column( - children: [ - Text( - yearly - ? AppLocalizations.of(context) - .expensesPerYear(_selectedDate.year) - : AppLocalizations.of(context) - .expensesPerMonth( - DateFormat.yMMMM(locale) - .format(_selectedDate), - ), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, + content: LimitedBox( + maxHeight: MediaQuery.of(context).size.width * 0.7, + maxWidth: MediaQuery.of(context).size.width * 0.8, + child: Wrap( + alignment: WrapAlignment.center, + spacing: 5, + children: [ + if (!yearly) + SizedBox( + width: 120, + height: 100, + child: WheelChooser.choices( + onChoiceChanged: (v) { + selectedMonth = v as int; + }, + startPosition: _selectedDate.month - 1, + choices: List>.generate( + 12, + (index) => WheelChoice( + value: index + 1, + title: DateFormat.MMMM(locale ?? "en").format( + DateTime( + _selectedDate.year, + index + 1, + ), + ), ), ), - const SizedBox( - height: 15, - ), - SizedBox( - width: MediaQuery.of(context).size.width * 0.9, - height: - MediaQuery.of(context).size.height * 0.35, - child: (chartType == null) - ? const CircularProgressIndicator() - : (chartType == 1) - ? ExpensesBarChart( - currency: selectedWallet!.currency, - date: _selectedDate, - locale: locale ?? "en", - yearly: yearly, - expenseData: (graphTypeSet - .contains("expense")) - ? generateChartData( - EntryType.expense, - ) - : [], - incomeData: (graphTypeSet - .contains("income")) - ? generateChartData( - EntryType.income, - ) - : [], - ) - : Padding( - padding: const EdgeInsets.all(8), - child: ExpensesLineChart( - currency: - selectedWallet!.currency, - date: _selectedDate, - locale: locale ?? "en", - yearly: yearly, - expenseData: (graphTypeSet - .contains("expense")) - ? generateChartData( - EntryType.expense, - ) - : [], - incomeData: (graphTypeSet - .contains("income")) - ? generateChartData( - EntryType.income, - ) - : [], - ), - ), - ), - ], + ), + ), + SizedBox( + height: 100, + width: 80, + child: WheelChooser.choices( + startPosition: availableYears.indexWhere( + (element) => element.value == _selectedDate.year, + ), + onChoiceChanged: (v) { + selectedYear = v as int; + }, + choices: availableYears, ), ), - ), - const SizedBox( - height: 25, - ), - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: - Theme.of(context).colorScheme.secondaryContainer, - ), - width: MediaQuery.of(context).size.width * 0.95, - height: MediaQuery.of(context).size.height * 0.4, + ], + ), + ), + actions: [ + TextButton( + onPressed: () { + _selectedDate = DateTime(selectedYear, selectedMonth); + Navigator.of(c).pop(); + }, + child: Text(AppLocalizations.of(context).ok), + ), + ], + ), + ); + setState(() {}); + }, + ), + ), + appBar: AppBar( + bottom: TabBar( + tabs: [ + Tab( + child: Text(AppLocalizations.of(context).expenses), + ), + Tab( + child: Text(AppLocalizations.of(context).incomePlural), + ), + ], + ), + title: DropdownButton( + value: (selectedWallet == null) + ? -1 + : wallets.indexOf(selectedWallet!), + items: [ + ...wallets.map( + (e) => DropdownMenuItem( + value: wallets.indexOf( + e, + ), + child: Text(e.name), + ), + ), + DropdownMenuItem( + value: -1, + child: Text(AppLocalizations.of(context).newWallet), + ), + ], + onChanged: (v) async { + if (v == null || v == -1) { + await Navigator.of(context).push( + platformRoute( + (c) => const SetupView( + newWallet: true, + ), + ), + ); + wallets = await WalletManager.listWallets(); + logger.i(wallets.length); + selectedWallet = wallets.last; + setState(() {}); + return; + } + selectedWallet = wallets[v]; + setState(() {}); + }, + ), + actions: [ + PopupMenuButton( + itemBuilder: (context) => [ + AppLocalizations.of(context).settings, + AppLocalizations.of(context).about, + ].map((e) => PopupMenuItem(value: e, child: Text(e))).toList(), + onSelected: (value) { + if (value == AppLocalizations.of(context).settings) { + Navigator.of(context) + .push( + platformRoute( + (context) => const SettingsView(), + ), + ) + .then((value) async { + selectedWallet = + await WalletManager.loadWallet(selectedWallet!.name); + final s = await SharedPreferences.getInstance(); + chartType = s.getInt("monthlygraph") ?? 2; + setState(() {}); + }); + } else if (value == AppLocalizations.of(context).about) { + showAbout(context); + } + }, + ), + ], + ), + drawer: makeDrawer(context, 2), + body: TabBarView( + children: [ + // EXPENSE TAB + SingleChildScrollView( + child: Center( + child: (selectedWallet == null) + ? const CircularProgressIndicator( + strokeWidth: 5, + ) + : SizedBox( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height, child: Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ - const SizedBox( - height: 10, - ), - Text( - AppLocalizations.of(context).expensesPerCategory, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, + SizedBox( + width: 200, + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context).monthly, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + Switch.adaptive( + value: yearly, + onChanged: (v) async { + yearly = v; + final s = + await SharedPreferences.getInstance(); + chartType = yearly + ? (s.getInt("yearlygraph") ?? 1) + : (s.getInt("monthlygraph") ?? 2); + + setState(() {}); + }, + ), + Text( + AppLocalizations.of(context).yearly, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ], ), ), - Padding( - padding: const EdgeInsets.all(8), - child: CategoriesPieChart( - symbol: selectedWallet!.currency.symbol, - entries: selectedWallet!.entries, - categories: selectedWallet!.categories, + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + boxShadow: (MediaQuery.of(context) + .platformBrightness == + Brightness.light) + ? [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 3, + blurRadius: 7, + offset: const Offset( + 0, + 3, + ), + ), + ] + : null, + color: (MediaQuery.of(context) + .platformBrightness == + Brightness.dark) + ? Theme.of(context) + .colorScheme + .secondaryContainer + : Theme.of(context).colorScheme.background, + ), + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + children: [ + Text( + yearly + ? AppLocalizations.of(context) + .expensesPerYear( + _selectedDate.year, + ) + : AppLocalizations.of(context) + .expensesPerMonth( + DateFormat.yMMMM(locale) + .format(_selectedDate), + ), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox( + height: 15, + ), + SizedBox( + width: MediaQuery.of(context).size.width * + 0.9, + height: + MediaQuery.of(context).size.height * + 0.35, + child: (chartType == null) + ? const CircularProgressIndicator() + : (chartType == 1) + ? ExpensesBarChart( + currency: + selectedWallet!.currency, + date: _selectedDate, + locale: locale ?? "en", + yearly: yearly, + expenseData: + generateChartData( + EntryType.expense, + ), + incomeData: const [], + ) + : Padding( + padding: + const EdgeInsets.all(8), + child: ExpensesLineChart( + currency: selectedWallet! + .currency, + date: _selectedDate, + locale: locale ?? "en", + yearly: yearly, + expenseData: + generateChartData( + EntryType.expense, + ), + incomeData: const [], + ), + ), + ), + ], + ), + ), + ), + const SizedBox( + height: 25, + ), + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + boxShadow: (MediaQuery.of(context) + .platformBrightness == + Brightness.light) + ? [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 3, + blurRadius: 7, + offset: const Offset( + 0, + 3, + ), + ), + ] + : null, + color: (MediaQuery.of(context) + .platformBrightness == + Brightness.dark) + ? Theme.of(context) + .colorScheme + .secondaryContainer + : Theme.of(context).colorScheme.background, + ), + width: MediaQuery.of(context).size.width * 0.95, + height: MediaQuery.of(context).size.height * 0.4, + child: Column( + children: [ + const SizedBox( + height: 10, + ), + Flexible( + child: Text( + textAlign: TextAlign.center, + yearly + ? AppLocalizations.of(context) + .expensesPerYearCategory( + _selectedDate.year, + ) + : AppLocalizations.of(context) + .expensesPerMonthCategory( + DateFormat.yMMMM(locale) + .format(_selectedDate), + ), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + Padding( + padding: const EdgeInsets.all(6), + child: CategoriesPieChart( + // TODO: better size adaptivity without overflow + locale: locale ?? "en", + symbol: selectedWallet!.currency.symbol, + entries: selectedWallet!.entries + .where( + (element) => + ((!yearly) + ? element.date.month == + _selectedDate + .month && + element.date.year == + _selectedDate.year + : element.date.year == + _selectedDate.year) && + element.type == + EntryType.expense, + ) + .toList(), + categories: selectedWallet!.categories, + ), + ), + ], ), ), ], ), ), - ], - ), - ), + ), + ), // Expense Tab END + SingleChildScrollView( + child: Center( + child: (selectedWallet == null) + ? const CircularProgressIndicator( + strokeWidth: 5, + ) + : SizedBox( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 200, + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context).monthly, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + Switch.adaptive( + value: yearly, + onChanged: (v) async { + yearly = v; + final s = + await SharedPreferences.getInstance(); + chartType = yearly + ? (s.getInt("yearlygraph") ?? 1) + : (s.getInt("monthlygraph") ?? 2); + + setState(() {}); + }, + ), + Text( + AppLocalizations.of(context).yearly, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + boxShadow: (MediaQuery.of(context) + .platformBrightness == + Brightness.light) + ? [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 3, + blurRadius: 7, + offset: const Offset( + 0, + 3, + ), + ), + ] + : null, + color: (MediaQuery.of(context) + .platformBrightness == + Brightness.dark) + ? Theme.of(context) + .colorScheme + .secondaryContainer + : Theme.of(context).colorScheme.background, + ), + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + children: [ + Text( + yearly + ? AppLocalizations.of(context) + .incomePerYear( + _selectedDate.year, + ) + : AppLocalizations.of(context) + .incomePerMonth( + DateFormat.yMMMM(locale) + .format(_selectedDate), + ), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox( + height: 15, + ), + SizedBox( + width: MediaQuery.of(context).size.width * + 0.9, + height: + MediaQuery.of(context).size.height * + 0.35, + child: (chartType == null) + ? const CircularProgressIndicator() + : (chartType == 1) + ? ExpensesBarChart( + currency: + selectedWallet!.currency, + date: _selectedDate, + locale: locale ?? "en", + yearly: yearly, + expenseData: const [], + incomeData: generateChartData( + EntryType.income, + ), + ) + : Padding( + padding: + const EdgeInsets.all(8), + child: ExpensesLineChart( + currency: selectedWallet! + .currency, + date: _selectedDate, + locale: locale ?? "en", + yearly: yearly, + expenseData: const [], + incomeData: + generateChartData( + EntryType.income, + ), + ), + ), + ), + ], + ), + ), + ), + const SizedBox( + height: 25, + ), + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + boxShadow: (MediaQuery.of(context) + .platformBrightness == + Brightness.light) + ? [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 3, + blurRadius: 7, + offset: const Offset( + 0, + 3, + ), + ), + ] + : null, + color: (MediaQuery.of(context) + .platformBrightness == + Brightness.dark) + ? Theme.of(context) + .colorScheme + .secondaryContainer + : Theme.of(context).colorScheme.background, + ), + width: MediaQuery.of(context).size.width * 0.95, + height: MediaQuery.of(context).size.height * 0.4, + child: Column( + children: [ + const SizedBox( + height: 10, + ), + Flexible( + child: Text( + yearly + ? AppLocalizations.of(context) + .incomePerYearCategory( + _selectedDate.year, + ) + : AppLocalizations.of(context) + .incomePerMonthCategory( + DateFormat.yMMMM(locale) + .format(_selectedDate), + ), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + Padding( + padding: const EdgeInsets.all(6), + child: CategoriesPieChart( + locale: locale ?? "en", + symbol: selectedWallet!.currency.symbol, + entries: selectedWallet!.entries + .where( + (element) => + ((!yearly) + ? element.date.month == + _selectedDate + .month && + element.date.year == + _selectedDate.year + : element.date.year == + _selectedDate.year) && + element.type == + EntryType.income, + ) + .toList(), + categories: selectedWallet!.categories, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), // Income Tab END + ], ), ), ); diff --git a/pubspec.lock b/pubspec.lock index 7c7c4f5..d5f3b26 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1145,6 +1145,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + wheel_chooser: + dependency: "direct main" + description: + name: wheel_chooser + sha256: "3fee36f081f321c58a0b7b4afcdd92599f2ca520b3a1420084774e6b19cca1d8" + url: "https://pub.dev" + source: hosted + version: "1.1.2" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e58da6e..b4873e0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -40,6 +40,7 @@ dependencies: settings_ui: ^2.0.2 shared_preferences: ^2.2.2 url_launcher: ^6.2.4 + wheel_chooser: ^1.1.2 dev_dependencies: build_runner: ^2.4.6