From e446aa3c80657b3ad348234a4ef44e144927da27 Mon Sep 17 00:00:00 2001 From: Emon526 Date: Sun, 17 Nov 2024 05:35:48 +0600 Subject: [PATCH] feat: Custom Duration Chart --- lib/main.dart | 2 - lib/pages/home_page.dart | 4 +- lib/providers/chartprovider.dart | 35 ++++++- lib/theme/theme.dart | 155 ++++++++++++++++++++++++++++- lib/widgets/chart.dart | 161 ++++++++++++------------------- lib/widgets/chart_bar.dart | 13 ++- pubspec.yaml | 22 ++--- 7 files changed, 273 insertions(+), 119 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 1b6186e..d7820d4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,8 +19,6 @@ void main() { } //TODO:: Check Responsive - -//TODO:: Custom chart duration class MyApp extends StatelessWidget { const MyApp({super.key}); diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 0b65f1d..9b6829d 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -166,7 +166,7 @@ class HomePage extends StatelessWidget { 0.3, child: Consumer( builder: (context, provider, child) { - return Chart(provider.recentTransactions); + return Chart(); }, ), ), @@ -208,7 +208,7 @@ class HomePage extends StatelessWidget { appBar.preferredSize.height - mediaQuery.padding.top) * 0.7, - child: Chart(provider.recentTransactions), + child: Chart(), ) : txListWidget; }, diff --git a/lib/providers/chartprovider.dart b/lib/providers/chartprovider.dart index deb8fd4..ba98ee9 100644 --- a/lib/providers/chartprovider.dart +++ b/lib/providers/chartprovider.dart @@ -1,4 +1,3 @@ -import 'package:expense_planner/providers/transaction_provider.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -7,6 +6,7 @@ import '../models/transaction.dart'; class ChartProvider with ChangeNotifier { int _selectedDays = 7; int get selectedDays => _selectedDays; + void set selectedDays(int value) { if (value != _selectedDays) { _selectedDays = value; @@ -21,4 +21,37 @@ class ChartProvider with ChangeNotifier { ]; List get selectedDaysList => _selectedDaysList; + + List> groupTransactionValues( + List transactions) { + return List.generate( + _selectedDays, + (index) { + final weekDay = DateTime.now().subtract( + Duration(days: index), + ); + var totalSum = 0.0; + + for (var i = 0; i < transactions.length; i++) { + if (transactions[i].date.day == weekDay.day && + transactions[i].date.month == weekDay.month && + transactions[i].date.year == weekDay.year) { + totalSum += transactions[i].amount; + } + } + + return { + 'day': DateFormat.E().format(weekDay).substring(0, 1), + 'amount': totalSum, + }; + }, + ).reversed.toList(); + } + + // Calculate total spending using the groupTransactionValues + double totalSpending(List transactions) { + return groupTransactionValues(transactions).fold(0.0, (sum, item) { + return sum + (item['amount'] as double); + }); + } } diff --git a/lib/theme/theme.dart b/lib/theme/theme.dart index 16077fa..8fab65b 100644 --- a/lib/theme/theme.dart +++ b/lib/theme/theme.dart @@ -21,7 +21,7 @@ ThemeData lightTheme = ThemeData( brightness: Brightness.light, primaryColor: primaryColor, // canvasColor: Colors.white, - // fontFamily: fontFamily, + fontFamily: fontFamily, indicatorColor: primaryColor, textSelectionTheme: TextSelectionThemeData( cursorColor: primaryColor, @@ -48,6 +48,82 @@ ThemeData lightTheme = ThemeData( ), behavior: SnackBarBehavior.floating, ), + datePickerTheme: DatePickerThemeData( + // backgroundColor: Colors.white, + // dayOverlayColor: WidgetStatePropertyAll(Colors.amberAccent), + // inputDecorationTheme: InputDecorationTheme(fillColor: Colors.amber), + // shadowColor: Colors.amber, + todayBorder: BorderSide( + color: primaryColor, + ), + + // surfaceTintColor: Colors.red, + todayForegroundColor: + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return null; + } + if (states.contains(WidgetState.selected)) { + return Colors.white; + } + return Colors.black; + }), + todayBackgroundColor: + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return null; + } + if (states.contains(WidgetState.selected)) { + return primaryColor; + } + return null; + }), + dayBackgroundColor: + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return null; + } + if (states.contains(WidgetState.selected)) { + return primaryColor; + } + return null; + }), + dayForegroundColor: + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return null; + } + if (states.contains(WidgetState.selected)) { + return Colors.white; + } + return Colors.black; + }), + yearBackgroundColor: + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return null; + } + if (states.contains(WidgetState.selected)) { + return primaryColor; + } + return null; + }), + yearForegroundColor: + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return null; + } + if (states.contains(WidgetState.selected)) { + return Colors.white; + } + return Colors.black; + }), + cancelButtonStyle: + ButtonStyle(foregroundColor: WidgetStatePropertyAll(primaryColor)), + confirmButtonStyle: + ButtonStyle(foregroundColor: WidgetStatePropertyAll(primaryColor)), + ), + textTheme: TextTheme( bodyLarge: TextStyle( color: Colors.black87, @@ -261,7 +337,7 @@ ThemeData darkTheme = ThemeData( brightness: Brightness.dark, primaryColor: secondaryColor, // canvasColor: Colors.white, - // fontFamily: fontFamily, + fontFamily: fontFamily, indicatorColor: secondaryColor, textSelectionTheme: TextSelectionThemeData( cursorColor: secondaryColor, @@ -287,6 +363,81 @@ ThemeData darkTheme = ThemeData( ), behavior: SnackBarBehavior.floating, ), + datePickerTheme: DatePickerThemeData( + // backgroundColor: Colors.white, + // dayOverlayColor: WidgetStatePropertyAll(Colors.amberAccent), + // inputDecorationTheme: InputDecorationTheme(fillColor: Colors.amber), + // shadowColor: Colors.amber, + todayBorder: BorderSide( + color: secondaryColor, + ), + + // surfaceTintColor: Colors.red, + todayForegroundColor: + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return null; + } + if (states.contains(WidgetState.selected)) { + return Colors.white; + } + return Colors.white; + }), + todayBackgroundColor: + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return null; + } + if (states.contains(WidgetState.selected)) { + return secondaryColor; + } + return null; + }), + dayBackgroundColor: + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return null; + } + if (states.contains(WidgetState.selected)) { + return secondaryColor; + } + return null; + }), + dayForegroundColor: + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return null; + } + if (states.contains(WidgetState.selected)) { + return Colors.white; + } + return Colors.white; + }), + yearBackgroundColor: + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return null; + } + if (states.contains(WidgetState.selected)) { + return secondaryColor; + } + return null; + }), + yearForegroundColor: + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return null; + } + if (states.contains(WidgetState.selected)) { + return Colors.white; + } + return Colors.white; + }), + cancelButtonStyle: + ButtonStyle(foregroundColor: WidgetStatePropertyAll(secondaryColor)), + confirmButtonStyle: + ButtonStyle(foregroundColor: WidgetStatePropertyAll(secondaryColor)), + ), textTheme: TextTheme( bodyLarge: TextStyle( diff --git a/lib/widgets/chart.dart b/lib/widgets/chart.dart index a70d4a4..198ef72 100644 --- a/lib/widgets/chart.dart +++ b/lib/widgets/chart.dart @@ -1,109 +1,76 @@ -import 'package:expense_planner/providers/chartprovider.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import '../models/transaction.dart'; -import 'package:intl/intl.dart'; -import './chart_bar.dart'; -import 'dart:developer'; -class Chart extends StatefulWidget { - final List recentTransactions; +import '../providers/chartprovider.dart'; +import '../providers/transaction_provider.dart'; +import '../utils/utils.dart'; +import 'chart_bar.dart'; - Chart(this.recentTransactions); - - @override - _ChartState createState() => _ChartState(); -} - -class _ChartState extends State { +class Chart extends StatelessWidget { @override Widget build(BuildContext context) { - return Card( - elevation: 6, - child: Padding( - padding: EdgeInsets.all(10), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Transaction Chart', - // style: Theme.of(context).textTheme.headline6, - ), - DropdownButton( - value: context.watch().selectedDays, - items: context - .watch() - .selectedDaysList - .map( - (days) => DropdownMenuItem( - value: days, - child: Text('Last $days Days'), - ), - ) - .toList(), - onChanged: (value) { - if (value != null) { - context.read().selectedDays = value; - } - }, + final transactions = context.watch().userTransactions; + return Consumer(builder: (context, chartProvider, child) { + return Card( + elevation: 6, + child: Padding( + padding: EdgeInsets.all(10), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Transaction Chart:', + style: Theme.of(context).textTheme.bodyLarge, + ), + SizedBox( + width: Utils(context).getScreenSize.width * 0.01, + ), + DropdownButton( + value: chartProvider.selectedDays, + items: chartProvider.selectedDaysList + .map( + (days) => DropdownMenuItem( + value: days, + child: Text('Last $days Days'), + ), + ) + .toList(), + onChanged: (value) { + if (value != null) { + chartProvider.selectedDays = value; + } + }, + ), + ], + ), + SizedBox(height: 10), + Flexible( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: chartProvider + .groupTransactionValues(transactions) + .map((data) { + return Flexible( + fit: FlexFit.tight, + child: ChartBar( + label: data['day'] as String, + spendingAmount: data['amount'] as double, + spendingPercentOfTotal: + chartProvider.totalSpending(transactions) == 0.0 + ? 0.0 + : (data['amount'] as double) / + chartProvider + .totalSpending(transactions)), + ); + }).toList(), ), - ], - ), - SizedBox(height: 10), - Flexible( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: groupTransactionValues.map((data) { - return Flexible( - fit: FlexFit.tight, - child: ChartBar( - data['day'] as String, - data['amount'] as double, - totalSpending == 0.0 - ? 0.0 - : (data['amount'] as double) / totalSpending), - ); - }).toList(), ), - ), - ], + ], + ), ), - ), - ); - } - - List> get groupTransactionValues { - // debugPrint(widget.recentTransactions.toString()); - log(widget.recentTransactions.toString()); - return List.generate( - context.watch().selectedDays, - (index) { - final weekDay = DateTime.now().subtract( - Duration(days: index), - ); - var totalSum = 0.0; - - for (var i = 0; i < widget.recentTransactions.length; i++) { - if (widget.recentTransactions[i].date.day == weekDay.day && - widget.recentTransactions[i].date.month == weekDay.month && - widget.recentTransactions[i].date.year == weekDay.year) { - totalSum += widget.recentTransactions[i].amount; - } - } - - return { - 'day': DateFormat.E().format(weekDay).substring(0, 1), - 'amount': totalSum, - }; - }, - ).reversed.toList(); - } - - double get totalSpending { - return groupTransactionValues.fold(0.0, (sum, item) { - return sum + (item['amount'] as double); + ); }); } } diff --git a/lib/widgets/chart_bar.dart b/lib/widgets/chart_bar.dart index 79abb4e..0105819 100644 --- a/lib/widgets/chart_bar.dart +++ b/lib/widgets/chart_bar.dart @@ -5,7 +5,12 @@ class ChartBar extends StatelessWidget { final double spendingAmount; final double spendingPercentOfTotal; - ChartBar(this.label, this.spendingAmount, this.spendingPercentOfTotal); + const ChartBar({ + super.key, + required this.label, + required this.spendingAmount, + required this.spendingPercentOfTotal, + }); @override Widget build(BuildContext context) { @@ -24,14 +29,14 @@ class ChartBar extends StatelessWidget { ), Container( height: constraints.maxHeight * 0.6, - width: 10, + width: constraints.maxWidth * 0.5, child: Stack( children: [ Container( decoration: BoxDecoration( border: Border.all(color: Colors.grey, width: 1.0), color: Color.fromRGBO(220, 220, 220, 1), - borderRadius: BorderRadius.circular(10), + borderRadius: BorderRadius.circular(4), ), ), FractionallySizedBox( @@ -39,7 +44,7 @@ class ChartBar extends StatelessWidget { child: Container( decoration: BoxDecoration( color: Theme.of(context).primaryColor, - borderRadius: BorderRadius.circular(10), + borderRadius: BorderRadius.circular(4), ), ), ), diff --git a/pubspec.yaml b/pubspec.yaml index ba996d3..b0bfc9b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -63,17 +63,17 @@ flutter: # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: - # fonts: - # - family: OpenSans - # fonts: - # - asset: assets/fonts/OpenSans-Regular.ttf - # - asset: assets/fonts/OpenSans-Bold.ttf - # weight: 700 - # - family: Quicksand - # fonts: - # - asset: assets/fonts/Quicksand-Regular.ttf - # - asset: assets/fonts/Quicksand-Bold.ttf - # weight: 700 + fonts: + - family: OpenSans + fonts: + - asset: assets/fonts/OpenSans-Regular.ttf + - asset: assets/fonts/OpenSans-Bold.ttf + weight: 700 + - family: Quicksand + fonts: + - asset: assets/fonts/Quicksand-Regular.ttf + - asset: assets/fonts/Quicksand-Bold.ttf + weight: 700 # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages