diff --git a/lib/constants/constants.dart b/lib/constants/constants.dart index 5d43ca1..6509f92 100644 --- a/lib/constants/constants.dart +++ b/lib/constants/constants.dart @@ -40,7 +40,6 @@ const categoryColorList = [ category7, category8, category9, - category10, ]; const darkCategoryColorList = [ diff --git a/lib/constants/style.dart b/lib/constants/style.dart index 2c11995..28116bb 100644 --- a/lib/constants/style.dart +++ b/lib/constants/style.dart @@ -41,10 +41,9 @@ const category3 = Color(0xFFFF4754); const category4 = Color(0xFFD336B6); const category5 = Color(0xFF7236D3); const category6 = Color(0xFF2675E3); -const category7 = Color(0xFF16ADDF);//inesistente sul figma -const category8 = Color(0xFF12BFCE); -const category9 = Color(0xFF12BA95); -const category10 = Color(0xFF0BC11D); +const category7 = Color(0xFF12BFCE); +const category8 = Color(0xFF12BA95); +const category9 = Color(0xFF0BC11D); const account1 = Color(0xFFFFB703); const account2 = Color(0xFFFB8500); @@ -81,7 +80,6 @@ const darkCategory6 = Color(0xFF1B6BD9); const darkCategory7 = Color(0xFF0FB1C0); const darkCategory8 = Color(0xFF0EB38F); const darkCategory9 = Color(0xFF0AAF1A); -// const darkCategory10 = Color(0xFF); const darkAccount1 = Color(0xFFE0A30C); const darkAccount2 = Color(0xFFDC7807); diff --git a/lib/custom_widgets/account_modal.dart b/lib/custom_widgets/account_modal.dart index 4057c89..cd470f5 100644 --- a/lib/custom_widgets/account_modal.dart +++ b/lib/custom_widgets/account_modal.dart @@ -3,6 +3,7 @@ import 'dart:ui'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; + import '../constants/functions.dart'; import '../providers/currency_provider.dart'; import 'line_chart.dart'; @@ -61,7 +62,7 @@ class AccountDialog extends ConsumerWidget with Functions { padding: EdgeInsets.all(8.0), ), LineChartWidget( - line1Data: [ + lineData: [ FlSpot(0, 3), FlSpot(1, 1.3), FlSpot(2, -2), @@ -80,9 +81,9 @@ class AccountDialog extends ConsumerWidget with Functions { FlSpot(15, -4.5), FlSpot(16, 2.5), ], - colorLine1Data: Color(0xffffffff), + lineColor: Color(0xffffffff), line2Data: [], - colorLine2Data: Color(0xffffffff), + line2Color: Color(0xffffffff), colorBackground: Color(0xff356CA3), period: Period.month, ), diff --git a/lib/custom_widgets/line_chart.dart b/lib/custom_widgets/line_chart.dart index 970b55c..7c72e19 100644 --- a/lib/custom_widgets/line_chart.dart +++ b/lib/custom_widgets/line_chart.dart @@ -4,39 +4,36 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -enum Period { - month, - year -} +enum Period { month, year } //This class can be used when we need to draw a line chart with one or two lines class LineChartWidget extends StatefulWidget { - final List line1Data; //this should be a list of Flspot(x,y) - final Color colorLine1Data; + final List lineData; //this should be a list of Flspot(x,y) + final Color? lineColor; - final List line2Data; //this should be a list of Flspot(x,y), if you only need one just put an empty list - final Color colorLine2Data; + final List line2Data; //this should be a list of Flspot(x,y) + final Color? line2Color; // Used to decide the bottom label final Period period; final int currentMonthDays = DateUtils.getDaysInMonth(DateTime.now().year, DateTime.now().month); final int nXLabel = 10; final double minY; - - final Color colorBackground; + + final Color? colorBackground; LineChartWidget({ super.key, - required this.line1Data, - required this.colorLine1Data, - required this.line2Data, - required this.colorLine2Data, - required this.colorBackground, + required this.lineData, + this.lineColor, + this.line2Data = const [], + this.line2Color, + this.colorBackground, this.period = Period.month, int nXLabel = 10, double? minY, - }) : minY = minY ?? calculateMinY(line1Data, line2Data); + }) : minY = minY ?? calculateMinY(lineData, line2Data); - static double calculateMinY(List line1Data, List line2Data){ + static double calculateMinY(List line1Data, List line2Data) { if (line1Data.isEmpty && line2Data.isEmpty) { return 0; } @@ -46,7 +43,6 @@ class LineChartWidget extends StatefulWidget { @override State createState() => _LineChartSample2State(); - } class _LineChartSample2State extends State { @@ -54,37 +50,27 @@ class _LineChartSample2State extends State { @override Widget build(BuildContext context) { + final ThemeData themeData = Theme.of(context); return Stack( children: [ AspectRatio( aspectRatio: 2, child: DecoratedBox( decoration: BoxDecoration( - borderRadius: const BorderRadius.all( - Radius.circular(18), - ), - color: widget.colorBackground, + color: widget.colorBackground ?? themeData.colorScheme.tertiary, ), child: Padding( padding: const EdgeInsets.only(top: 24), child: Builder( builder: (context) { - if(widget.line1Data.length < 2 && widget.line2Data.length < 2){ + if (widget.lineData.length < 2 && widget.line2Data.length < 2) { return Center( - child: RichText( - textAlign: TextAlign.center, - text: TextSpan( - style: DefaultTextStyle.of(context).style, - children: [ - TextSpan( - text: "We are sorry but there are not\nenough data to make the graph", - style: TextStyle(color: Theme.of(context).hintColor), - ) - ] - ) + child: Text( + "We are sorry but there are not\nenough data to make the graph...", + style: TextStyle(color: Theme.of(context).hintColor), ), - ); } - + ); + } return LineChart( mainData(), ); @@ -98,8 +84,10 @@ class _LineChartSample2State extends State { } Widget bottomTitleWidgets(double value, TitleMeta meta) { + final ThemeData themeData = Theme.of(context); + Color lineColor = widget.lineColor ?? themeData.colorScheme.primary; final style = TextStyle( - color: widget.colorLine1Data.withOpacity(1.0), + color: lineColor, fontWeight: FontWeight.normal, fontSize: 8, ); @@ -144,9 +132,12 @@ class _LineChartSample2State extends State { break; case Period.month: int step = (widget.currentMonthDays / widget.nXLabel).round(); - if(value.toInt() % step == 1 && value.toInt() != widget.currentMonthDays){ - text = Text((value + 1).toStringAsFixed(0), style: style,); - }else{ + if (value.toInt() % step == 1 && value.toInt() != widget.currentMonthDays) { + text = Text( + (value + 1).toStringAsFixed(0), + style: style, + ); + } else { text = Text('', style: style); } @@ -162,6 +153,10 @@ class _LineChartSample2State extends State { } LineChartData mainData() { + final ThemeData themeData = Theme.of(context); + Color lineColor = widget.lineColor ?? themeData.colorScheme.primary; + Color line2Color = widget.line2Color ?? themeData.disabledColor; + return LineChartData( titlesData: FlTitlesData( show: true, @@ -183,48 +178,43 @@ class _LineChartSample2State extends State { ), gridData: const FlGridData(show: false), lineTouchData: LineTouchData( - getTouchedSpotIndicator: - (LineChartBarData barData, List spotIndexes) { - bool allSameX = spotIndexes.toSet().length == 1; + getTouchedSpotIndicator: (LineChartBarData barData, List spotIndexes) { + bool allSameX = spotIndexes.toSet().length == 1; - if(!allSameX){ - return []; - } - return spotIndexes.map((spotIndex) { - return TouchedSpotIndicatorData( - const FlLine( - color: Colors.blueGrey, - strokeWidth: 2, - ), - FlDotData( - getDotPainter: (spot, percent, barData, index) { - return FlDotCirclePainter( - radius: 2, - color: Colors.grey, - strokeWidth: 2, - strokeColor: Colors.blueGrey - ); - }, - ), - ); - }).toList(); + if (!allSameX) { + return []; + } + return spotIndexes.map((spotIndex) { + return TouchedSpotIndicatorData( + const FlLine( + color: Colors.blueGrey, + strokeWidth: 2, + ), + FlDotData( + getDotPainter: (spot, percent, barData, index) { + return FlDotCirclePainter( + radius: 2, color: Colors.grey, strokeWidth: 2, strokeColor: Colors.blueGrey); + }, + ), + ); + }).toList(); }, touchTooltipData: LineTouchTooltipData( fitInsideHorizontally: true, getTooltipItems: (List touchedBarSpots) { bool allSameX = touchedBarSpots.map((e) => e.x).toSet().length == 1; - if(!allSameX || touchedBarSpots.isEmpty){ + if (!allSameX || touchedBarSpots.isEmpty) { return []; } double x = touchedBarSpots[0].x; - DateTime date = widget.period == Period.month ? - DateTime(DateTime.now().year, DateTime.now().month, (x+1).toInt()) : - DateTime(DateTime.now().year, (x+1).toInt(), 1); - String dateFormat = widget.period == Period.month ? - DateFormat(DateFormat.ABBR_MONTH_DAY).format(date) : - DateFormat(DateFormat.ABBR_MONTH).format(date); + DateTime date = widget.period == Period.month + ? DateTime(DateTime.now().year, DateTime.now().month, (x + 1).toInt()) + : DateTime(DateTime.now().year, (x + 1).toInt(), 1); + String dateFormat = widget.period == Period.month + ? DateFormat(DateFormat.ABBR_MONTH_DAY).format(date) + : DateFormat(DateFormat.ABBR_MONTH).format(date); LineTooltipItem first = LineTooltipItem( '$dateFormat \n\n', @@ -236,7 +226,7 @@ class _LineChartSample2State extends State { TextSpan( text: touchedBarSpots[0].y.toString(), style: TextStyle( - color: widget.colorLine2Data, + color: line2Color, fontWeight: FontWeight.w900, ), ), @@ -255,7 +245,7 @@ class _LineChartSample2State extends State { TextSpan( text: flSpot.y.toString(), style: TextStyle( - color: widget.colorLine1Data, + color: lineColor, fontWeight: FontWeight.w900, ), ), @@ -267,33 +257,38 @@ class _LineChartSample2State extends State { }, ), ), - + borderData: FlBorderData( border: const Border( bottom: BorderSide(color: Colors.grey, width: 1.0, style: BorderStyle.solid), ), ), minX: 0, - maxX: widget.period == Period.year ? 11 : widget.currentMonthDays - 1, // if year display 12 month, if mont display the number of days in the month + maxX: widget.period == Period.year + ? 11 + : widget.currentMonthDays - + 1, // if year display 12 month, if mont display the number of days in the month minY: widget.minY, lineBarsData: [ LineChartBarData( - spots: widget.line1Data, + spots: widget.lineData, isCurved: true, barWidth: 1.5, isStrokeCapRound: true, - color: widget.colorLine1Data, + color: lineColor, dotData: const FlDotData( show: false, ), - belowBarData: BarAreaData(show: true, color: widget.colorLine1Data.withOpacity(0.3)), + belowBarData: BarAreaData( + show: true, + color: lineColor.withOpacity(0.3)), ), LineChartBarData( spots: widget.line2Data, isCurved: true, barWidth: 1, isStrokeCapRound: true, - color: widget.colorLine2Data, + color: line2Color, dotData: const FlDotData( show: false, ), diff --git a/lib/pages/transactions_page/widgets/transaction_type_button.dart b/lib/custom_widgets/transaction_type_button.dart similarity index 93% rename from lib/pages/transactions_page/widgets/transaction_type_button.dart rename to lib/custom_widgets/transaction_type_button.dart index 13c5b3e..dbe802d 100644 --- a/lib/pages/transactions_page/widgets/transaction_type_button.dart +++ b/lib/custom_widgets/transaction_type_button.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../constants/style.dart'; -import '../../../model/transaction.dart'; -import 'accounts_tab.dart'; -import 'categories_tab.dart'; +import '../constants/style.dart'; +import '../model/transaction.dart'; +import '../pages/transactions_page/widgets/accounts_tab.dart'; +import '../pages/transactions_page/widgets/categories_tab.dart'; final selectedTransactionTypeProvider = StateProvider.autoDispose((ref) => TransactionType.income); diff --git a/lib/model/transaction.dart b/lib/model/transaction.dart index 91823a0..a7ea8b4 100644 --- a/lib/model/transaction.dart +++ b/lib/model/transaction.dart @@ -194,7 +194,9 @@ class TransactionMethods extends SossoldiDatabase { Future selectById(int id) async { final db = await database; - final maps = await db.rawQuery('SELECT t.*, c.${CategoryTransactionFields.name} as ${TransactionFields.categoryName}, c.${CategoryTransactionFields.color} as ${TransactionFields.categoryColor}, c.${CategoryTransactionFields.symbol} as ${TransactionFields.categorySymbol}, b1.${BankAccountFields.name} as ${TransactionFields.bankAccountName}, b2.${BankAccountFields.name} as ${TransactionFields.bankAccountTransferName} FROM $transactionTable as t LEFT JOIN $categoryTransactionTable as c ON t.${TransactionFields.idCategory} = c.${CategoryTransactionFields.id} LEFT JOIN $bankAccountTable as b1 ON t.${TransactionFields.idBankAccount} = b1.${BankAccountFields.id} LEFT JOIN $bankAccountTable as b2 ON t.${TransactionFields.idBankAccountTransfer} = b2.${BankAccountFields.id} WHERE t.${TransactionFields.id} = ?', [id]); + final maps = await db.rawQuery( + 'SELECT t.*, c.${CategoryTransactionFields.name} as ${TransactionFields.categoryName}, c.${CategoryTransactionFields.color} as ${TransactionFields.categoryColor}, c.${CategoryTransactionFields.symbol} as ${TransactionFields.categorySymbol}, b1.${BankAccountFields.name} as ${TransactionFields.bankAccountName}, b2.${BankAccountFields.name} as ${TransactionFields.bankAccountTransferName} FROM $transactionTable as t LEFT JOIN $categoryTransactionTable as c ON t.${TransactionFields.idCategory} = c.${CategoryTransactionFields.id} LEFT JOIN $bankAccountTable as b1 ON t.${TransactionFields.idBankAccount} = b1.${BankAccountFields.id} LEFT JOIN $bankAccountTable as b2 ON t.${TransactionFields.idBankAccountTransfer} = b2.${BankAccountFields.id} WHERE t.${TransactionFields.id} = ?', + [id]); if (maps.isNotEmpty) { return Transaction.fromJson(maps.first); @@ -223,24 +225,26 @@ class TransactionMethods extends SossoldiDatabase { "${where != null ? '$where and ' : ''}strftime('%Y-%m-%d', ${TransactionFields.date}) BETWEEN '${dateRangeStart.toString().substring(0, 10)}' and '${dateRangeEnd.toIso8601String().substring(0, 10)}'"; } - if(label != null && label.isNotEmpty) { - where = "${where != null ? '$where and ' : ''}t.note LIKE '%$label%' "; + if (label != null && label.isNotEmpty) { + where = "${where != null ? '$where and ' : ''}t.note LIKE '%$label%' "; } - if(transactionType != null) { + if (transactionType != null) { final transactionTypeList = transactionType.map((e) => "'$e'").toList(); - where = "${where != null ? '$where and ' : ''}t.type IN (${transactionTypeList.join(',')}) "; + where = "${where != null ? '$where and ' : ''}t.type IN (${transactionTypeList.join(',')}) "; } - if(bankAccounts != null && !bankAccounts.entries.every((element) => element.value == false)) { - final bankAccountIds = bankAccounts.entries.where((bankAccount) => bankAccount.value).map((e) => "'${e.key}'"); - where = "${where != null ? '$where and ' : ''}t.${TransactionFields.idBankAccount} IN (${bankAccountIds.join(',')}) "; + if (bankAccounts != null && !bankAccounts.entries.every((element) => element.value == false)) { + final bankAccountIds = + bankAccounts.entries.where((bankAccount) => bankAccount.value).map((e) => "'${e.key}'"); + where = + "${where != null ? '$where and ' : ''}t.${TransactionFields.idBankAccount} IN (${bankAccountIds.join(',')}) "; } final orderByDESC = '${TransactionFields.date} DESC'; - final result = - await db.rawQuery('SELECT t.*, c.${CategoryTransactionFields.name} as ${TransactionFields.categoryName}, c.${CategoryTransactionFields.color} as ${TransactionFields.categoryColor}, c.${CategoryTransactionFields.symbol} as ${TransactionFields.categorySymbol}, b1.${BankAccountFields.name} as ${TransactionFields.bankAccountName}, b2.${BankAccountFields.name} as ${TransactionFields.bankAccountTransferName} FROM "$transactionTable" as t LEFT JOIN $categoryTransactionTable as c ON t.${TransactionFields.idCategory} = c.${CategoryTransactionFields.id} LEFT JOIN $bankAccountTable as b1 ON t.${TransactionFields.idBankAccount} = b1.${BankAccountFields.id} LEFT JOIN $bankAccountTable as b2 ON t.${TransactionFields.idBankAccountTransfer} = b2.${BankAccountFields.id} ${where != null ? "WHERE $where" : ""} ORDER BY $orderByDESC ${limit != null ? "LIMIT $limit" : ""}'); + final result = await db.rawQuery( + 'SELECT t.*, c.${CategoryTransactionFields.name} as ${TransactionFields.categoryName}, c.${CategoryTransactionFields.color} as ${TransactionFields.categoryColor}, c.${CategoryTransactionFields.symbol} as ${TransactionFields.categorySymbol}, b1.${BankAccountFields.name} as ${TransactionFields.bankAccountName}, b2.${BankAccountFields.name} as ${TransactionFields.bankAccountTransferName} FROM "$transactionTable" as t LEFT JOIN $categoryTransactionTable as c ON t.${TransactionFields.idCategory} = c.${CategoryTransactionFields.id} LEFT JOIN $bankAccountTable as b1 ON t.${TransactionFields.idBankAccount} = b1.${BankAccountFields.id} LEFT JOIN $bankAccountTable as b2 ON t.${TransactionFields.idBankAccountTransfer} = b2.${BankAccountFields.id} ${where != null ? "WHERE $where" : ""} ORDER BY $orderByDESC ${limit != null ? "LIMIT $limit" : ""}'); return result.map((json) => Transaction.fromJson(json)).toList(); } @@ -251,13 +255,13 @@ class TransactionMethods extends SossoldiDatabase { String where = ""; final orderByDESC = '${TransactionFields.date} DESC'; - if(label != null){ + if (label != null) { where = "t.note LIKE '%$label%' "; } - final result = - await db.rawQuery('SELECT DISTINCT LOWER(t.note) as note FROM "$transactionTable" as t LEFT JOIN $bankAccountTable as b1 ON t.${TransactionFields.idBankAccount} = b1.${BankAccountFields.id} LEFT JOIN $bankAccountTable as b2 ON t.${TransactionFields.idBankAccountTransfer} = b2.${BankAccountFields.id} ${where.isNotEmpty ? "WHERE $where" : ""} ORDER BY $orderByDESC'); - + final result = await db.rawQuery( + 'SELECT DISTINCT LOWER(t.note) as note FROM "$transactionTable" as t LEFT JOIN $bankAccountTable as b1 ON t.${TransactionFields.idBankAccount} = b1.${BankAccountFields.id} LEFT JOIN $bankAccountTable as b2 ON t.${TransactionFields.idBankAccountTransfer} = b2.${BankAccountFields.id} ${where.isNotEmpty ? "WHERE $where" : ""} ORDER BY $orderByDESC'); + return (result).map((x) => x["note"] as String).toList(); } @@ -269,11 +273,12 @@ class TransactionMethods extends SossoldiDatabase { final beginningCurrentMonth = DateTime(currentYear, currentMonth, 1); final beginningNextMonth = DateTime(currentYear, currentMonth + 1, 1); - return transactionByFrequencyAndPeriod( - accountId: accountId, - recurrence: Recurrence.daily, - dateRangeStart: beginningCurrentMonth, - dateRangeEnd: beginningNextMonth); + return _transactionByFrequencyAndPeriod( + accountId: accountId, + recurrence: Recurrence.daily, + dateRangeStart: beginningCurrentMonth, + dateRangeEnd: beginningNextMonth, + ); } Future lastMonthDailyTransactions() async { @@ -284,10 +289,11 @@ class TransactionMethods extends SossoldiDatabase { final beginningCurrentMonth = DateTime(currentYear, currentMonth, 1); final beginningLastMonth = DateTime(currentYear, currentMonth - 1, 1); - return transactionByFrequencyAndPeriod( - recurrence: Recurrence.daily, - dateRangeStart: beginningLastMonth, - dateRangeEnd: beginningCurrentMonth); + return _transactionByFrequencyAndPeriod( + recurrence: Recurrence.daily, + dateRangeStart: beginningLastMonth, + dateRangeEnd: beginningCurrentMonth, + ); } Future currentYearMontlyTransactions() async { @@ -297,13 +303,14 @@ class TransactionMethods extends SossoldiDatabase { final beginningCurrentYear = DateTime(currentYear, 1, 1); final beginningNextMonth = DateTime(currentYear + 1, 1, 1); - return transactionByFrequencyAndPeriod( - recurrence: Recurrence.monthly, - dateRangeStart: beginningCurrentYear, - dateRangeEnd: beginningNextMonth); + return _transactionByFrequencyAndPeriod( + recurrence: Recurrence.monthly, + dateRangeStart: beginningCurrentYear, + dateRangeEnd: beginningNextMonth, + ); } - Future transactionByFrequencyAndPeriod({ + Future _transactionByFrequencyAndPeriod({ int? accountId, Recurrence recurrence = Recurrence.daily, DateTime? dateRangeStart, @@ -328,9 +335,8 @@ class TransactionMethods extends SossoldiDatabase { throw ArgumentError("Query not implemented for frequency $recurrence"); } - final accountFilter = accountId != null - ? "${TransactionFields.idBankAccount} = $accountId" - : ""; + final accountFilter = + accountId != null ? "${TransactionFields.idBankAccount} = $accountId" : ""; //var periodDateFormatter = ""; final periodFilterStart = dateRangeStart != null ? "strftime('%Y-%m-%d', ${TransactionFields.date}) >= '${dateRangeStart.toString().substring(0, 10)}'" diff --git a/lib/pages/account_page/account_page.dart b/lib/pages/account_page/account_page.dart index 9aa2610..82a3977 100644 --- a/lib/pages/account_page/account_page.dart +++ b/lib/pages/account_page/account_page.dart @@ -1,4 +1,3 @@ -import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -47,10 +46,7 @@ class _AccountPage extends ConsumerState with Functions { Padding( padding: const EdgeInsets.all(8.0), child: LineChartWidget( - line1Data: accountTransactions, - colorLine1Data: const Color(0xffffffff), - line2Data: const [], - colorLine2Data: const Color(0xffffffff), + lineData: accountTransactions, colorBackground: blue5, period: Period.month, minY: 0, diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 94b580a..5a0418c 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -141,11 +141,8 @@ class _HomePageState extends ConsumerState with Functions { ), const SizedBox(height: 16), LineChartWidget( - line1Data: currentMonthList, - colorLine1Data: const Color(0xff00152D), + lineData: currentMonthList, line2Data: lastMonthList, - colorLine2Data: const Color(0xffB9BABC), //da modificare in darkMode - colorBackground: Theme.of(context).colorScheme.tertiary, ), Row( children: [ diff --git a/lib/pages/statistics_page.dart b/lib/pages/statistics_page.dart index 46a64bd..5479041 100644 --- a/lib/pages/statistics_page.dart +++ b/lib/pages/statistics_page.dart @@ -1,15 +1,16 @@ // Satistics page. -import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:sossoldi/providers/statistics_provider.dart'; import '../constants/functions.dart'; import '../constants/style.dart'; +import '../custom_widgets/default_container.dart'; import '../custom_widgets/line_chart.dart'; +import '../custom_widgets/transaction_type_button.dart'; import '../model/bank_account.dart'; import '../providers/accounts_provider.dart'; import '../providers/currency_provider.dart'; +import '../providers/statistics_provider.dart'; class StatsPage extends ConsumerStatefulWidget { const StatsPage({super.key}); @@ -21,261 +22,201 @@ class StatsPage extends ConsumerStatefulWidget { class _StatsPageState extends ConsumerState with Functions { @override Widget build(BuildContext context) { + final currentYearMonthlyTransactions = ref.watch(currentYearMontlyTransactionsProvider); final accountList = ref.watch(accountsProvider); - final curretYearMontlyTransactions = ref.watch(currentYearMontlyTransactionsProvider); final currencyState = ref.watch(currencyStateNotifier); return ListView( children: [ - Column( - children: [ - const Padding(padding: EdgeInsets.only(top: 24)), - Row( - children: [ - const Padding(padding: EdgeInsets.only(left: 8.0)), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Available liquidity", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - const Padding(padding: EdgeInsets.only(right: 120.0)), - Column( - crossAxisAlignment: CrossAxisAlignment.end, + const SizedBox(height: 16), + ref.watch(statisticsProvider).when( + data: (value) { + double percentGainLoss = 0; + if (currentYearMonthlyTransactions.length > 1) { + percentGainLoss = ((currentYearMonthlyTransactions.last.y - + currentYearMonthlyTransactions[ + currentYearMonthlyTransactions.length - 2] + .y) / + currentYearMonthlyTransactions[currentYearMonthlyTransactions.length - 2] + .y) * + 100; + } + return Column( children: [ - RichText( - textScaleFactor: MediaQuery.of(context).textScaleFactor, - text: TextSpan( + Container( + padding: const EdgeInsets.all(16.0), + color: Theme.of(context).colorScheme.tertiary, + child: Column( children: [ - TextSpan( - text: "10.635", - style: Theme.of(context) - .textTheme - .headlineLarge - ?.copyWith(color: blue4), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + "Available liquidity", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: Theme.of(context).colorScheme.primary, + ), + ), + RichText( + text: TextSpan( + children: [ + TextSpan( + text: numToCurrency(currentYearMonthlyTransactions.last.y), + style: Theme.of(context) + .textTheme + .headlineLarge + ?.copyWith(color: blue4), + ), + TextSpan( + text: currencyState.selectedCurrency.symbol, + style: Theme.of(context) + .textTheme + .labelLarge + ?.copyWith(color: blue4), + ), + ], + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.circular(4), + ), + padding: const EdgeInsets.all(4.0), + child: Text( + "${numToCurrency(percentGainLoss)}%", + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: percentGainLoss < 0 ? red : green, + fontWeight: FontWeight.w600, + ), + ), + ), + Text( + " VS last month", + style: Theme.of(context) + .textTheme + .labelLarge + ?.copyWith(fontWeight: FontWeight.w300), + ), + ], ), - TextSpan( - text: currencyState.selectedCurrency.symbol, - style: Theme.of(context) - .textTheme - .labelLarge - ?.copyWith(color: blue4)), ], ), ), - RichText( - textScaleFactor: MediaQuery.of(context).textScaleFactor, - text: TextSpan( + LineChartWidget(lineData: currentYearMonthlyTransactions), + ], + ); + }, + loading: () => const SizedBox(), + error: (err, stack) => Text('Error: $err'), + ), + const SizedBox(height: 24), + Align( + alignment: Alignment.centerLeft, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: Text( + "Accounts", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: Theme.of(context).colorScheme.primary, + ), + ), + ), + ), + DefaultContainer( + child: accountList.when( + data: (accounts) => ListView.builder( + itemCount: accounts.length, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + scrollDirection: Axis.vertical, + itemBuilder: (context, i) { + BankAccount account = accounts[i]; + return SizedBox( + height: 50.0, + child: Column( + children: [ + const Padding(padding: EdgeInsets.all(2.0)), + Row( children: [ - TextSpan( - text: "+6%", - style: Theme.of(context) - .textTheme - .bodyMedium - ?.copyWith( - color: green, fontWeight: FontWeight.w600), + RichText( + text: TextSpan( + children: [ + TextSpan( + text: account.name, + style: + Theme.of(context).textTheme.bodySmall?.copyWith(color: blue1), + ), + ], + ), ), - TextSpan( - text: " VS last month", - style: Theme.of(context) - .textTheme - .labelLarge - ?.copyWith(fontWeight: FontWeight.w300), + Expanded( + child: Text( + "${numToCurrency(account.total)}${currencyState.selectedCurrency.symbol}", + textAlign: TextAlign.right, + style: Theme.of(context).textTheme.bodySmall?.copyWith(color: blue1), + ), ), ], ), - ), - ], - ), - ], - ), - const Padding( - padding: EdgeInsets.all(8.0), - ), - ref.watch(statisticsProvider).when( - data: (value) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: LineChartWidget( - line1Data: curretYearMontlyTransactions, - colorLine1Data: Color(0xff00152D), - line2Data: [], - colorLine2Data: Color(0xffB9BABC), - colorBackground: Color(0xffF1F5F9), - period: Period.year, + const SizedBox(height: 4.0), + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(16)), + child: LinearProgressIndicator( + value: account.total != 0 ? account.startingValue / account.total! : 0, + minHeight: 16, + backgroundColor: blue3.withOpacity(0.3), + valueColor: const AlwaysStoppedAnimation(blue3), + borderRadius: const BorderRadius.all(Radius.circular(16)), + ), + ), + ], ), ); }, - error: (err, stack) => Text('Error: $err'), - loading: () => const SizedBox()), - Align( - alignment: Alignment.centerLeft, - child: Container( - padding: const EdgeInsets.only(left: 8.0, top: 8.0), - child: Text( - "Accounts", - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.w600, - ), - textAlign: TextAlign.left, - ), - ), ), - Card( - child: SizedBox( - child: accountList.when( - data: (accounts) => Flexible( - child: ListView.builder( - itemCount: accounts.length, - shrinkWrap: true, - padding: const EdgeInsets.only( - left: 16.0, right: 16.0, top: 8.0), - physics: const BouncingScrollPhysics(), - scrollDirection: Axis.vertical, - itemBuilder: (context, i) { - if (i == accounts.length) { - return Padding( - padding: const EdgeInsets.fromLTRB(0, 4, 0, 16), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - boxShadow: [defaultShadow], - ), - ), - ); - } else { - BankAccount account = accounts[i]; - double max = 0; - for (var i = 0; i < accounts.length; i++) { - if (max <= accounts[i].startingValue) { - max = accounts[i].startingValue.toDouble(); - } - } - return SizedBox( - height: 50.0, - child: Column( - children: [ - const Padding(padding: EdgeInsets.all(2.0)), - Row( - children: [ - RichText( - textScaleFactor: MediaQuery.of(context) - .textScaleFactor, - text: TextSpan( - children: [ - TextSpan( - text: account.name, - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: blue1), - ), - ], - ), - ), - Expanded( - child: Text( - "${account.startingValue}${currencyState.selectedCurrency.symbol}", - textAlign: TextAlign.right, - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: blue1), - ), - ), - ], - ), - const Padding( - padding: EdgeInsets.only(top: 4.0)), - SizedBox( - height: 12, - width: double.infinity, - child: Row( - children: [ - Container( - width: MediaQuery.of(context) - .size - .width * - 0.9 * - (account.startingValue - .toDouble() / - max) - - (account.startingValue - .toDouble() / - max > - 0 - ? 1 - : 0), - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - topLeft: - const Radius.circular(4.0), - bottomLeft: - const Radius.circular(4.0), - topRight: Radius.circular(account - .startingValue - .toDouble() / - max == - 1 - ? 4.0 - : 0.0), - bottomRight: Radius.circular( - account.startingValue - .toDouble() / - max == - 1 - ? 4.0 - : 0.0), - ), - color: blue3, - ), - ), - Container( - width: account.startingValue - .toDouble() / - max == - 1 - ? 0 - : MediaQuery.of(context) - .size - .width * - 0.9 * - (1 - - (account.startingValue - .toDouble() / - max)) - - 1, - decoration: const BoxDecoration( - borderRadius: BorderRadius.only( - topRight: Radius.circular(4.0), - bottomRight: Radius.circular(4.0), - ), - color: blue7, - ), - ), - ], - ), - ) - ], - )); - } - }, - ), + loading: () => const SizedBox(), + error: (err, stack) => Text('Error: $err'), + ), + ), + const SizedBox(height: 24), + Align( + alignment: Alignment.centerLeft, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + child: Text( + "Categories", + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: Theme.of(context).colorScheme.primary, + ), + ), + ), + ), + DefaultContainer( + child: Column( + children: [ + const TransactionTypeButton(), + const SizedBox(height: 16), + SizedBox( + height: 200, + child: Center( + child: Text( + "After you add some transactions, some outstanding graphs will appear here... almost by magic!", + style: Theme.of(context).textTheme.bodySmall, + textAlign: TextAlign.center, ), - loading: () => const SizedBox(), - error: (err, stack) => Text('Error: $err'), ), ), - ) - ], + ], + ), ), + const SizedBox(height: 24), ], ); } diff --git a/lib/pages/transactions_page/widgets/accounts_tab.dart b/lib/pages/transactions_page/widgets/accounts_tab.dart index 4a6081f..2e46564 100644 --- a/lib/pages/transactions_page/widgets/accounts_tab.dart +++ b/lib/pages/transactions_page/widgets/accounts_tab.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:sossoldi/pages/transactions_page/widgets/transaction_type_button.dart'; import '../../../constants/constants.dart'; import '../../../constants/functions.dart'; import '../../../custom_widgets/default_container.dart'; +import '../../../custom_widgets/transaction_type_button.dart'; import '../../../model/bank_account.dart'; import '../../../model/transaction.dart'; import '../../../providers/accounts_provider.dart'; diff --git a/lib/pages/transactions_page/widgets/categories_tab.dart b/lib/pages/transactions_page/widgets/categories_tab.dart index e83902a..7d3cf44 100644 --- a/lib/pages/transactions_page/widgets/categories_tab.dart +++ b/lib/pages/transactions_page/widgets/categories_tab.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:sossoldi/pages/transactions_page/widgets/transaction_type_button.dart'; import '../../../constants/functions.dart'; import '../../../custom_widgets/default_container.dart'; +import '../../../custom_widgets/transaction_type_button.dart'; import '../../../model/category_transaction.dart'; import '../../../model/transaction.dart'; import '../../../providers/categories_provider.dart'; diff --git a/lib/utils/app_theme.dart b/lib/utils/app_theme.dart index a21f887..cf13074 100644 --- a/lib/utils/app_theme.dart +++ b/lib/utils/app_theme.dart @@ -123,6 +123,7 @@ class AppTheme { color: darkBlue1, ), + disabledColor: darkGrey2, //Text style fontFamily: 'SF Pro Text', textTheme: const TextTheme( diff --git a/test/widget/line_chart_test.dart b/test/widget/line_chart_test.dart index 89dbed0..0ff445d 100644 --- a/test/widget/line_chart_test.dart +++ b/test/widget/line_chart_test.dart @@ -1,78 +1,77 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; import "dart:math"; -import '../../lib/custom_widgets/line_chart.dart'; +import 'package:sossoldi/custom_widgets/line_chart.dart'; import 'package:fl_chart/fl_chart.dart'; void main() { - testWidgets('Properly Render Accounts Widget', (WidgetTester tester) async { - final random = Random(); + testWidgets('Properly Render Accounts Widget', (WidgetTester tester) async { + final random = Random(); - double lower = -5; - double upper = 8; + double lower = -5; + double upper = 8; - await tester.pumpWidget(MaterialApp( - home: Material( - child: LineChartWidget( - line1Data: [ - FlSpot(0, lower + random.nextDouble() * (upper - lower)), - FlSpot(1, lower + random.nextDouble() * (upper - lower)), - FlSpot(2, lower + random.nextDouble() * (upper - lower)), - FlSpot(3, lower + random.nextDouble() * (upper - lower)), - FlSpot(4, lower + random.nextDouble() * (upper - lower)), - FlSpot(5, lower + random.nextDouble() * (upper - lower)), - FlSpot(6, lower + random.nextDouble() * (upper - lower)), - FlSpot(7, lower + random.nextDouble() * (upper - lower)), - FlSpot(8, lower + random.nextDouble() * (upper - lower)), - FlSpot(9, lower + random.nextDouble() * (upper - lower)), - FlSpot(10, lower + random.nextDouble() * (upper - lower)), - FlSpot(11, lower + random.nextDouble() * (upper - lower)), - FlSpot(12, lower + random.nextDouble() * (upper - lower)), - FlSpot(13, lower + random.nextDouble() * (upper - lower)), - FlSpot(14, lower + random.nextDouble() * (upper - lower)), - FlSpot(15, lower + random.nextDouble() * (upper - lower)), - FlSpot(16, lower + random.nextDouble() * (upper - lower)), - ], - colorLine1Data: const Color(0xffffffff), - line2Data: [ - FlSpot(0, lower + random.nextDouble() * (upper - lower)), - FlSpot(1, lower + random.nextDouble() * (upper - lower)), - FlSpot(2, lower + random.nextDouble() * (upper - lower)), - FlSpot(3, lower + random.nextDouble() * (upper - lower)), - FlSpot(4, lower + random.nextDouble() * (upper - lower)), - FlSpot(5, lower + random.nextDouble() * (upper - lower)), - FlSpot(6, lower + random.nextDouble() * (upper - lower)), - FlSpot(7, lower + random.nextDouble() * (upper - lower)), - FlSpot(8, lower + random.nextDouble() * (upper - lower)), - FlSpot(9, lower + random.nextDouble() * (upper - lower)), - FlSpot(10, lower + random.nextDouble() * (upper - lower)), - FlSpot(11, lower + random.nextDouble() * (upper - lower)), - FlSpot(12, lower + random.nextDouble() * (upper - lower)), - FlSpot(13, lower + random.nextDouble() * (upper - lower)), - FlSpot(14, lower + random.nextDouble() * (upper - lower)), - FlSpot(15, lower + random.nextDouble() * (upper - lower)), - FlSpot(16, lower + random.nextDouble() * (upper - lower)), - FlSpot(17, lower + random.nextDouble() * (upper - lower)), - FlSpot(18, lower + random.nextDouble() * (upper - lower)), - FlSpot(19, lower + random.nextDouble() * (upper - lower)), - FlSpot(20, lower + random.nextDouble() * (upper - lower)), - FlSpot(21, lower + random.nextDouble() * (upper - lower)), - FlSpot(22, lower + random.nextDouble() * (upper - lower)), - FlSpot(23, lower + random.nextDouble() * (upper - lower)), - FlSpot(24, lower + random.nextDouble() * (upper - lower)), - FlSpot(25, lower + random.nextDouble() * (upper - lower)), - FlSpot(26, lower + random.nextDouble() * (upper - lower)), - FlSpot(27, lower + random.nextDouble() * (upper - lower)), - FlSpot(28, lower + random.nextDouble() * (upper - lower)), - FlSpot(29, lower + random.nextDouble() * (upper - lower)), - ], - colorLine2Data: const Color(0xffffffff), - colorBackground: const Color(0xff356CA3), - period: Period.month, - ), + await tester.pumpWidget(MaterialApp( + home: Material( + child: LineChartWidget( + lineData: [ + FlSpot(0, lower + random.nextDouble() * (upper - lower)), + FlSpot(1, lower + random.nextDouble() * (upper - lower)), + FlSpot(2, lower + random.nextDouble() * (upper - lower)), + FlSpot(3, lower + random.nextDouble() * (upper - lower)), + FlSpot(4, lower + random.nextDouble() * (upper - lower)), + FlSpot(5, lower + random.nextDouble() * (upper - lower)), + FlSpot(6, lower + random.nextDouble() * (upper - lower)), + FlSpot(7, lower + random.nextDouble() * (upper - lower)), + FlSpot(8, lower + random.nextDouble() * (upper - lower)), + FlSpot(9, lower + random.nextDouble() * (upper - lower)), + FlSpot(10, lower + random.nextDouble() * (upper - lower)), + FlSpot(11, lower + random.nextDouble() * (upper - lower)), + FlSpot(12, lower + random.nextDouble() * (upper - lower)), + FlSpot(13, lower + random.nextDouble() * (upper - lower)), + FlSpot(14, lower + random.nextDouble() * (upper - lower)), + FlSpot(15, lower + random.nextDouble() * (upper - lower)), + FlSpot(16, lower + random.nextDouble() * (upper - lower)), + ], + lineColor: const Color(0xffffffff), + line2Data: [ + FlSpot(0, lower + random.nextDouble() * (upper - lower)), + FlSpot(1, lower + random.nextDouble() * (upper - lower)), + FlSpot(2, lower + random.nextDouble() * (upper - lower)), + FlSpot(3, lower + random.nextDouble() * (upper - lower)), + FlSpot(4, lower + random.nextDouble() * (upper - lower)), + FlSpot(5, lower + random.nextDouble() * (upper - lower)), + FlSpot(6, lower + random.nextDouble() * (upper - lower)), + FlSpot(7, lower + random.nextDouble() * (upper - lower)), + FlSpot(8, lower + random.nextDouble() * (upper - lower)), + FlSpot(9, lower + random.nextDouble() * (upper - lower)), + FlSpot(10, lower + random.nextDouble() * (upper - lower)), + FlSpot(11, lower + random.nextDouble() * (upper - lower)), + FlSpot(12, lower + random.nextDouble() * (upper - lower)), + FlSpot(13, lower + random.nextDouble() * (upper - lower)), + FlSpot(14, lower + random.nextDouble() * (upper - lower)), + FlSpot(15, lower + random.nextDouble() * (upper - lower)), + FlSpot(16, lower + random.nextDouble() * (upper - lower)), + FlSpot(17, lower + random.nextDouble() * (upper - lower)), + FlSpot(18, lower + random.nextDouble() * (upper - lower)), + FlSpot(19, lower + random.nextDouble() * (upper - lower)), + FlSpot(20, lower + random.nextDouble() * (upper - lower)), + FlSpot(21, lower + random.nextDouble() * (upper - lower)), + FlSpot(22, lower + random.nextDouble() * (upper - lower)), + FlSpot(23, lower + random.nextDouble() * (upper - lower)), + FlSpot(24, lower + random.nextDouble() * (upper - lower)), + FlSpot(25, lower + random.nextDouble() * (upper - lower)), + FlSpot(26, lower + random.nextDouble() * (upper - lower)), + FlSpot(27, lower + random.nextDouble() * (upper - lower)), + FlSpot(28, lower + random.nextDouble() * (upper - lower)), + FlSpot(29, lower + random.nextDouble() * (upper - lower)), + ], + line2Color: const Color(0xffffffff), + colorBackground: const Color(0xff356CA3), + period: Period.month, ), - ) - ); + ), + )); expect(find.text('2'), findsOneWidget); expect(find.text('5'), findsOneWidget); @@ -84,7 +83,5 @@ void main() { expect(find.text('23'), findsOneWidget); expect(find.text('26'), findsOneWidget); expect(find.text('29'), findsOneWidget); - - } - ); -} \ No newline at end of file + }); +}