diff --git a/lib/extensions.dart b/lib/extensions.dart index 8da0eca..494136d 100644 --- a/lib/extensions.dart +++ b/lib/extensions.dart @@ -1,19 +1,20 @@ import 'package:flutter/material.dart'; +import 'package:get/get.dart'; import 'package:intl/intl.dart'; +import 'screens/user/user_controller.dart'; + extension DateExtension on DateTime { DateTime copyTime(TimeOfDay from) { return DateTime(year, month, day, from.hour, from.minute); } bool isSameDate(DateTime other) { - return isSameMonth(other) && - day == other.day; + return isSameMonth(other) && day == other.day; } bool isSameMonth(DateTime other) { - return year == other.year && - month == other.month; + return year == other.year && month == other.month; } String formatDate() { @@ -38,8 +39,8 @@ extension VsString on String { String get toPascalCase { return replaceAllMapped( RegExp(r'(\w+)'), - (match) => - "${match.group(0)![0].toUpperCase()}${match.group(0)!.substring(1)}"); + (match) => + "${match.group(0)![0].toUpperCase()}${match.group(0)!.substring(1)}"); } String get toKebabCase { @@ -50,9 +51,32 @@ extension VsString on String { String get toTitleCase { return replaceAllMapped( RegExp(r'(\w+)'), - (match) => - "${match.group(0)![0].toUpperCase()}${match.group(0)! - .substring(1) - .toLowerCase()} "); + (match) => + "${match.group(0)![0].toUpperCase()}${match.group(0)!.substring(1).toLowerCase()} "); + } +} + +extension VsDouble on double { + String get n { + final controller = Get.find(); + final comma = controller.thousandSep.value; + final decimal = controller.decimalSep.value; + var f = NumberFormat.simpleCurrency(locale: 'en-us'); + String amt = f.format(this); + amt = amt.replaceAll('\$', ''); + if (decimal == 1 && comma == 0) { + amt = amt.replaceAll('.', '@'); + amt = amt.replaceAll(',', '.'); + amt = amt.replaceAll('@', ','); + } else if (comma == 0 && decimal == 0) { + amt = amt.replaceAll(',', '.'); + } else if (comma == 1 && decimal == 1) { + amt = amt.replaceAll('.', ','); + } + return controller.currency.value + amt; + } + + String get s { + return n.replaceFirst('-', ''); } } diff --git a/lib/screens/dashboard/dashboard_controller.dart b/lib/screens/dashboard/dashboard_controller.dart index c93507a..2ad872e 100644 --- a/lib/screens/dashboard/dashboard_controller.dart +++ b/lib/screens/dashboard/dashboard_controller.dart @@ -9,6 +9,7 @@ class DashboardController extends GetxController { DashboardController(this.currentDate); RxList sectors = RxList.empty(); DbController dbController = Get.find(); + double total = 0; @override void onInit() { @@ -18,20 +19,29 @@ class DashboardController extends GetxController { Future fetchSectors() async { var transList = await dbController.db.rawQuery( - '''SELECT SUM(${Const.trans}.amount) AS total , count(${Const.categories}.category_name) AS share , ${Const.categories}.category_name , ${Const.categories}.color from ${Const.trans} LEFT JOIN ${Const.categories} + '''SELECT SUM(${Const.trans}.amount) AS total , count(${Const.categories}.category_name) AS share , ${Const.categories}.category_name , + ${Const.categories}.color, ${Const.categories}.icon from ${Const.trans} + LEFT JOIN ${Const.categories} on ${Const.trans}.category_id = ${Const.categories}.id WHERE ${Const.trans}.created_at BETWEEN ${Utils.getFirstDate(currentDate)} AND ${Utils.getLastDate(currentDate)} GROUP BY ${Const.categories}.category_name ORDER BY share DESC ''', ); + total = 0; for (int i = 0; i < transList.length; i++) { - if (double.parse(transList[i]['total'].toString()) < 0 && - transList[i]['category_name'] != null) { + final transaction = transList[i]; + if (double.parse(transaction['total'].toString()) < 0 && + transaction['category_name'] != null) { Sector s = Sector.fromJson(transList[i]); + total += s.amount; sectors.add(s); } } update(); } + + List getFilteredSectors() { + return sectors.where((sector) => sector.include).toList(); + } } diff --git a/lib/screens/dashboard/dashboard_model.dart b/lib/screens/dashboard/dashboard_model.dart index e05ae82..6683bc7 100644 --- a/lib/screens/dashboard/dashboard_model.dart +++ b/lib/screens/dashboard/dashboard_model.dart @@ -5,22 +5,34 @@ import 'package:flutter/material.dart'; class Sector { Sector( {required this.color, - required this.total, + required this.amount, required this.share, - required this.title}); + required this.title, + required this.icon, + this.include = true}); + Color color; - double total; + double amount; int share; String title; + String icon; + bool include; factory Sector.fromJson(Map json) => Sector( - color: json['color'] != null - ? Color(json['color']) - : Colors.primaries[Random().nextInt(Colors.primaries.length)], - title: json['category_name'].length > 10 - ? json['category_name'].substring(0, 9) - : json['category_name'], - share: json['share'], - total: json['total'], - ); + color: json['color'] != null + ? Color(json['color']) + : Colors.primaries[Random().nextInt(Colors.primaries.length)], + title: json['category_name'], + share: json['share'], + amount: json['total'], + icon: json['icon']); + + void switchInclusion() { + include = !include; + } + + String totalPercent(double total) { + if (!include) return "0"; + return ((amount / total) * 100).toStringAsFixed(2); + } } diff --git a/lib/screens/dashboard/dashboard_screen.dart b/lib/screens/dashboard/dashboard_screen.dart index 3244965..fb1d7ba 100644 --- a/lib/screens/dashboard/dashboard_screen.dart +++ b/lib/screens/dashboard/dashboard_screen.dart @@ -1,8 +1,12 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; +import 'package:vase/colors.dart'; +import 'package:vase/extensions.dart'; import 'package:vase/screens/dashboard/dashboard_controller.dart'; +import 'package:vase/screens/dashboard/dashboard_model.dart'; import 'package:vase/screens/dashboard/pie_chart.dart'; +import 'package:vase/widgets/category_icon.dart'; import 'package:vase/widgets/focused_layout.dart'; import 'package:vase/widgets/wrapper.dart'; @@ -32,48 +36,35 @@ class DashboardScreen extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - PieChartWidget(controller.sectors), - const Padding( - padding: EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Title', - style: TextStyle( - fontWeight: FontWeight.bold, fontSize: 16), - ), - Text( - 'Expense', - style: TextStyle( - fontWeight: FontWeight.bold, fontSize: 16), - ) - ], - ), - ), - ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemBuilder: (context, index) { - return ListTile( - leading: Container( - width: 10, - height: 10, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: controller.sectors[index].color, + PieChartWidget(controller.getFilteredSectors()), + Card( + child: ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + Sector sector = controller.sectors[index]; + return ListTile( + onTap: () { + sector.switchInclusion(); + controller.update(); + }, + leading: CategoryIcon( + icon: sector.icon, + bgColor: sector.include + ? sector.color + : AppColors.darkGreyColor, + ), + title: Text(sector.title), + subtitle: Text( + "${sector.share} Items • ${sector.totalPercent(controller.total)}%"), + trailing: Text( + sector.amount.s, + style: const TextStyle(fontSize: 14), ), - ), - title: Text( - controller.sectors[index].title - ), - trailing: Text( - controller.sectors[index].total.toString(), - style: const TextStyle(fontSize: 14), - ), - ); - }, - itemCount: controller.sectors.length, + ); + }, + itemCount: controller.sectors.length, + ), ) ], ); diff --git a/lib/screens/dashboard/pie_chart.dart b/lib/screens/dashboard/pie_chart.dart index 7ec001c..e503898 100644 --- a/lib/screens/dashboard/pie_chart.dart +++ b/lib/screens/dashboard/pie_chart.dart @@ -12,19 +12,19 @@ class PieChartWidget extends StatelessWidget { return AspectRatio( aspectRatio: 1.0, child: PieChart(PieChartData( - sections: _chartSections(sectors), - centerSpaceRadius: 80.0, - ))); + sections: _chartSections(sectors), + centerSpaceRadius: 40.0, + sectionsSpace: 1))); } List _chartSections(List sectors) { final List list = []; for (var sector in sectors) { - const double radius = 20.0; + const double radius = 40.0; final data = PieChartSectionData( - titlePositionPercentageOffset: 2.6, + titlePositionPercentageOffset: 2, color: sector.color, - value: sector.total, + value: sector.amount, radius: radius, title: sector.title, ); diff --git a/lib/screens/transactions/date_list_item.dart b/lib/screens/transactions/date_list_item.dart index f0b0c93..cb88663 100644 --- a/lib/screens/transactions/date_list_item.dart +++ b/lib/screens/transactions/date_list_item.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:flutter_iconpicker/flutter_iconpicker.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; import 'package:vase/screens/transactions/date_chip.dart'; import 'package:vase/screens/transactions/new_transaction.dart'; import 'package:vase/screens/transactions/trans_controller.dart'; import 'package:vase/text_styles.dart'; +import 'package:vase/widgets/category_icon.dart'; import '../../controllers/db_controller.dart'; import '../categories/category_model.dart'; @@ -66,19 +66,7 @@ class DateListItem extends StatelessWidget { }); } }, - leading: CircleAvatar( - child: SizedBox( - width: 40, - height: 20, - child: Icon( - deserializeIcon({ - 'pack': cat != null ? 'fontAwesomeIcons' : 'material', - 'key': cat != null ? cat.icon : 'sync_alt_rounded' - }), - size: 20, - ), - ), - ), + leading: CategoryIcon(icon: cat?.icon ?? 'ban'), title: Text( transaction.desc, style: const TextStyle(fontWeight: FontWeight.bold), diff --git a/lib/screens/widgets/txn_text.dart b/lib/screens/widgets/txn_text.dart index 58828ee..c483d0a 100644 --- a/lib/screens/widgets/txn_text.dart +++ b/lib/screens/widgets/txn_text.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:intl/intl.dart'; import 'package:vase/colors.dart'; -import 'package:vase/screens/user/user_controller.dart'; +import 'package:vase/extensions.dart'; class TxnText extends StatelessWidget { const TxnText( @@ -22,38 +21,19 @@ class TxnText extends StatelessWidget { @override Widget build(BuildContext context) { - return GetBuilder(builder: (controller) { - final comma = controller.thousandSep.value; - final decimal = controller.decimalSep.value; - var f = NumberFormat.simpleCurrency(locale: 'en-us'); - String amt = f.format(amount); - amt = amt.replaceAll('\$', ''); - if (!showSign) { - amt = amt.replaceFirst("-", ""); - } - if (decimal == 1 && comma == 0) { - amt = amt.replaceAll('.', '@'); - amt = amt.replaceAll(',', '.'); - amt = amt.replaceAll('@', ','); - } else if (comma == 0 && decimal == 0) { - amt = amt.replaceAll(',', '.'); - } else if (comma == 1 && decimal == 1) { - amt = amt.replaceAll('.', ','); - } - return Text( - '${controller.currency.value}$amt', - maxLines: 1, - textAlign: textAlign, - style: TextStyle( - color: customColor ?? - (showDynamicColor - ? (amount.isNegative - ? AppColors.errorColor - : AppColors.accentColor) - : null), - fontSize: 14, - fontWeight: FontWeight.w700), - ); - }); + return Obx(() => Text( + amount.s, + maxLines: 1, + textAlign: textAlign, + style: TextStyle( + color: customColor ?? + (showDynamicColor + ? (amount.isNegative + ? AppColors.errorColor + : AppColors.accentColor) + : null), + fontSize: 14, + fontWeight: FontWeight.w700), + )); } } diff --git a/lib/utils.dart b/lib/utils.dart index fa7efd4..db16bb2 100644 --- a/lib/utils.dart +++ b/lib/utils.dart @@ -107,7 +107,7 @@ class Utils { } static int getLastDate(DateTime currentDate) { - DateTime dateTime = DateTime(currentDate.year, currentDate.month + 1, 0); + DateTime dateTime = DateTime(currentDate.year, currentDate.month + 1, 1).subtract(const Duration(seconds: 1)); return dateTime.millisecondsSinceEpoch; } diff --git a/lib/widgets/category_icon.dart b/lib/widgets/category_icon.dart new file mode 100644 index 0000000..5768110 --- /dev/null +++ b/lib/widgets/category_icon.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_iconpicker/flutter_iconpicker.dart'; + +class CategoryIcon extends StatelessWidget { + const CategoryIcon({super.key, required this.icon, this.bgColor}); + + final String? icon; + final Color? bgColor; + + @override + Widget build(BuildContext context) { + return CircleAvatar( + backgroundColor: bgColor, + child: SizedBox( + width: 40, + height: 20, + child: Icon( + deserializeIcon({'pack': 'fontAwesomeIcons', 'key': icon}), + size: 20, + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 4817d22..a31cdf6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.2.0+7 +version: 1.3.0+8 environment: sdk: ">=2.19.0 <3.0.0"