From 2c29a5f050543126896614357a5676a451d16e50 Mon Sep 17 00:00:00 2001 From: abdo-essam Date: Fri, 6 Feb 2026 22:56:17 +0200 Subject: [PATCH 01/15] feat: add monthly overview widgets for statistics screen Adds a collection of new widgets to build a "Monthly Overview" section. This includes: - `MonthlyOverview`: The main container widget. - `SummaryCard`: Displays income or expenses with an icon and formatted value. - `ProgressBarSection`: Shows animated progress bars for income and expenses. - `ScaleLabels`: Provides a horizontal scale for the progress bars. - `SavingsBanner`: A banner to show the total savings for the month. - A `formatNumber` utility function for currency display. - New SVG icons for wallet add and money remove actions. - A new `StatisticsScreen` and its corresponding route to display the feature. --- assets/icons/ic_money_remove.svg | 17 +++ assets/icons/ic_wallet_add.svg | 14 +++ lib/design_system/assets/app_assets.dart | 10 +- lib/presentation/navigation/routes.dart | 17 ++- .../statistics/statistics_screen.dart | 23 ++++ lib/presentation/statistics/utils.dart | 9 ++ .../monthly_overview/monthly_overview.dart | 110 ++++++++++++++++++ .../progress_bar_section.dart | 94 +++++++++++++++ .../monthly_overview/savings_banner.dart | 44 +++++++ .../monthly_overview/scale_labels.dart | 32 +++++ .../monthly_overview/summary_card.dart | 74 ++++++++++++ 11 files changed, 437 insertions(+), 7 deletions(-) create mode 100644 assets/icons/ic_money_remove.svg create mode 100644 assets/icons/ic_wallet_add.svg create mode 100644 lib/presentation/statistics/statistics_screen.dart create mode 100644 lib/presentation/statistics/utils.dart create mode 100644 lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart create mode 100644 lib/presentation/statistics/widgets/monthly_overview/progress_bar_section.dart create mode 100644 lib/presentation/statistics/widgets/monthly_overview/savings_banner.dart create mode 100644 lib/presentation/statistics/widgets/monthly_overview/scale_labels.dart create mode 100644 lib/presentation/statistics/widgets/monthly_overview/summary_card.dart diff --git a/assets/icons/ic_money_remove.svg b/assets/icons/ic_money_remove.svg new file mode 100644 index 0000000..c83fd56 --- /dev/null +++ b/assets/icons/ic_money_remove.svg @@ -0,0 +1,17 @@ + + + + + + + diff --git a/assets/icons/ic_wallet_add.svg b/assets/icons/ic_wallet_add.svg new file mode 100644 index 0000000..bc236eb --- /dev/null +++ b/assets/icons/ic_wallet_add.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/lib/design_system/assets/app_assets.dart b/lib/design_system/assets/app_assets.dart index 33346fe..c0a0cb7 100644 --- a/lib/design_system/assets/app_assets.dart +++ b/lib/design_system/assets/app_assets.dart @@ -6,7 +6,7 @@ class AppAssets { static const icAppLogo = '${_icons}ic_app_logo.svg'; static const icArrowDown = '$_icons/ic_arrow_down.svg'; static const icArrowUp = '$_icons/ic_arrow_up.svg'; - static const icArrowRight ='$_icons/ic_arrow_right.svg'; + static const icArrowRight = '$_icons/ic_arrow_right.svg'; static const String iconCancel = "$_icons/ic_cancel.svg"; static const String iconError = "$_icons/ic_error.svg"; static const String iconSuccess = "$_icons/ic_success.svg"; @@ -19,7 +19,8 @@ class AppAssets { static const String icAccountGray = '$_icons/ic_account_gray.svg'; static const String icStatisticsPrimary = '$_icons/ic_statistics_primary.svg'; static const String icStatisticsGray = '$_icons/ic_statistics_gray.svg'; - static const String icTransactionPrimary = '$_icons/ic_transaction_primary.svg'; + static const String icTransactionPrimary = + '$_icons/ic_transaction_primary.svg'; static const String icTransactionGray = '$_icons/ic_transaction_gray.svg'; static const String icArrowDownRound = '$_icons/ic_arrow_down_round.svg'; static const String icCalender = '$_icons/ic_calendar.svg'; @@ -47,7 +48,8 @@ class AppAssets { static const String google = "$_icons/ic_google.svg"; static const icMoneyAmount = '$_icons/ic_money_amount.svg'; - static const String imgForgetPasswordLock = "$_icons/img_forget_password_lock.png"; + static const String imgForgetPasswordLock = + "$_icons/img_forget_password_lock.png"; static const String icEmail = "$_icons/ic_email.svg"; static const String icAppBrand = "$_icons/ic_app_brand.svg"; static const String background = "$_images/background.png"; @@ -55,4 +57,6 @@ class AppAssets { static const String icLoading = "$_icons/ic_loading.svg"; static const String icAmountPrimary = '$_icons/ic_amount_primary.svg'; static const String icAmountGray = '$_icons/ic_amount_gray.svg'; + static const String icWalletAdd = '$_icons/ic_wallet_add.svg'; + static const String icMoneyRemove = '$_icons/ic_money_remove.svg'; } diff --git a/lib/presentation/navigation/routes.dart b/lib/presentation/navigation/routes.dart index f0dc325..61b5152 100644 --- a/lib/presentation/navigation/routes.dart +++ b/lib/presentation/navigation/routes.dart @@ -1,13 +1,11 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:moneyplus/presentation/home/screen/home_screen.dart'; import 'package:moneyplus/presentation/login/screen/login_screen.dart'; - import '../../di/injection.dart'; import '../login/cubit/login_cubit.dart'; - +import '../statistics/statistics_screen.dart'; part 'routes.g.dart'; @TypedGoRoute(path: '/') @@ -23,7 +21,7 @@ class OnBoardingRoute extends GoRouteData with $OnBoardingRoute { mainAxisAlignment: MainAxisAlignment.center, children: [ Text("onBoarding screen"), - ElevatedButton(onPressed: (){ LoginRoute().push(context);}, child: Text("Go to Login")) + ElevatedButton(onPressed: (){ StatisticsRoute().push(context);}, child: Text("Go to Login")) ], ), ), @@ -55,3 +53,14 @@ class HomeRoute extends GoRouteData with $HomeRoute { return HomeScreen(); } } + +@TypedGoRoute(path: '/statistics') +@immutable +class StatisticsRoute extends GoRouteData with $StatisticsRoute { + const StatisticsRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) { + return StatisticsScreen(); + } +} \ No newline at end of file diff --git a/lib/presentation/statistics/statistics_screen.dart b/lib/presentation/statistics/statistics_screen.dart new file mode 100644 index 0000000..98d4488 --- /dev/null +++ b/lib/presentation/statistics/statistics_screen.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +import 'widgets/monthly_overview/monthly_overview.dart'; + +class StatisticsScreen extends StatelessWidget { + const StatisticsScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.grey.shade100, + body: Padding( + padding: const EdgeInsets.all(16), + child: MonthlyOverview( + income: 1500000, + expenses: 850000, + currency: 'IQD', + maxValue: 2000000, + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/presentation/statistics/utils.dart b/lib/presentation/statistics/utils.dart new file mode 100644 index 0000000..9ace213 --- /dev/null +++ b/lib/presentation/statistics/utils.dart @@ -0,0 +1,9 @@ + +String formatNumber(double value) { + if (value >= 1000000) { + return '${(value / 1000000).toStringAsFixed(1)}M'; + } else if (value >= 1000) { + return '${(value / 1000).toStringAsFixed(0)},${(value % 1000).toStringAsFixed(0).padLeft(3, '0')}'; + } + return value.toStringAsFixed(0); +} \ No newline at end of file diff --git a/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart b/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart new file mode 100644 index 0000000..c816398 --- /dev/null +++ b/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../../../design_system/assets/app_assets.dart'; +import 'progress_bar_section.dart'; +import 'savings_banner.dart'; +import 'scale_labels.dart'; +import 'summary_card.dart'; + +class MonthlyOverview extends StatelessWidget { + final double income; + final double expenses; + final String currency; + final double maxValue; + + const MonthlyOverview({ + super.key, + required this.income, + required this.expenses, + this.currency = 'IQD', + this.maxValue = 2000000, + }); + + double get savings => income - expenses; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + // Header + const Text( + 'Monthly Overview', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + const SizedBox(height: 16), + + // Income & Expenses Cards + Row( + children: [ + Expanded( + child: SummaryCard( + icon: SvgPicture.asset( + AppAssets.icWalletAdd, + width: 24, + height: 24, + ), + iconBackgroundColor: const Color(0xFF00BFA5), + label: 'Income', + value: income, + currency: currency, + isPositive: true, + ), + ), + const SizedBox(width: 12), + Expanded( + child: SummaryCard( + icon: SvgPicture.asset( + AppAssets.icMoneyRemove, + width: 24, + height: 24, + ), + iconBackgroundColor: const Color(0xFFE91E63), + label: 'Expenses', + value: expenses, + currency: currency, + isPositive: false, + ), + ), + ], + ), + const SizedBox(height: 20), + + // Progress Bars + ProgressBarSection( + income: income, + expenses: expenses, + maxValue: maxValue, + ), + const SizedBox(height: 8), + + // Scale + ScaleLabels(maxValue: maxValue), + const SizedBox(height: 16), + + // Savings Banner + if (savings > 0) SavingsBanner(savings: savings, currency: currency), + ], + ), + ); + } +} diff --git a/lib/presentation/statistics/widgets/monthly_overview/progress_bar_section.dart b/lib/presentation/statistics/widgets/monthly_overview/progress_bar_section.dart new file mode 100644 index 0000000..0add877 --- /dev/null +++ b/lib/presentation/statistics/widgets/monthly_overview/progress_bar_section.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; + +class ProgressBarSection extends StatelessWidget { + final double income; + final double expenses; + final double maxValue; + + const ProgressBarSection({ + super.key, + required this.income, + required this.expenses, + required this.maxValue, + }); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + // Income Bar + _AnimatedProgressBar( + value: income, + maxValue: maxValue, + color: const Color(0xFF00BFA5), + icon: Icons.account_balance_wallet, + ), + const SizedBox(height: 8), + // Expenses Bar + _AnimatedProgressBar( + value: expenses, + maxValue: maxValue, + color: const Color(0xFFE91E63), + icon: Icons.receipt_long, + ), + ], + ); + } +} + +class _AnimatedProgressBar extends StatelessWidget { + final double value; + final double maxValue; + final Color color; + final IconData icon; + + const _AnimatedProgressBar({ + required this.value, + required this.maxValue, + required this.color, + required this.icon, + }); + + @override + Widget build(BuildContext context) { + final percentage = (value / maxValue).clamp(0.0, 1.0); + + return Stack( + children: [ + // Background + Container( + height: 32, + decoration: BoxDecoration( + color: Colors.grey.shade200, + borderRadius: BorderRadius.circular(16), + ), + ), + // Progress + FractionallySizedBox( + widthFactor: percentage, + child: Container( + height: 32, + decoration: BoxDecoration( + gradient: LinearGradient(colors: [color, color.withOpacity(0.8)]), + borderRadius: BorderRadius.circular(16), + ), + child: Padding( + padding: const EdgeInsets.only(right: 8), + child: Align( + alignment: Alignment.centerRight, + child: Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.3), + borderRadius: BorderRadius.circular(6), + ), + child: Icon(icon, color: Colors.white, size: 16), + ), + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/presentation/statistics/widgets/monthly_overview/savings_banner.dart b/lib/presentation/statistics/widgets/monthly_overview/savings_banner.dart new file mode 100644 index 0000000..bf18722 --- /dev/null +++ b/lib/presentation/statistics/widgets/monthly_overview/savings_banner.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; + +import '../../utils.dart'; + +class SavingsBanner extends StatelessWidget { + final double savings; + final String currency; + + const SavingsBanner({super.key, + required this.savings, + required this.currency, + }); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + decoration: BoxDecoration( + color: const Color(0xFFE8F5F2), + borderRadius: BorderRadius.circular(24), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.check_circle, + color: Color(0xFF00BFA5), + size: 18, + ), + const SizedBox(width: 8), + Text( + 'You saved ${formatNumber(savings)} $currency this month', + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: Color(0xFF00897B), + ), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/presentation/statistics/widgets/monthly_overview/scale_labels.dart b/lib/presentation/statistics/widgets/monthly_overview/scale_labels.dart new file mode 100644 index 0000000..273fb47 --- /dev/null +++ b/lib/presentation/statistics/widgets/monthly_overview/scale_labels.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +class ScaleLabels extends StatelessWidget { + final double maxValue; + + const ScaleLabels({super.key, required this.maxValue}); + + @override + Widget build(BuildContext context) { + final labels = [ + '0', + '15K', + '50K', + '150K', + '300K', + '600K', + '1M', + '1M500K', + '2M', + ]; + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: labels.map((label) { + return Text( + label, + style: TextStyle(fontSize: 10, color: Colors.grey.shade500), + ); + }).toList(), + ); + } +} diff --git a/lib/presentation/statistics/widgets/monthly_overview/summary_card.dart b/lib/presentation/statistics/widgets/monthly_overview/summary_card.dart new file mode 100644 index 0000000..cab1488 --- /dev/null +++ b/lib/presentation/statistics/widgets/monthly_overview/summary_card.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import '../../utils.dart'; + +class SummaryCard extends StatelessWidget { + final Widget icon; + final Color iconBackgroundColor; + final String label; + final double value; + final String currency; + final bool isPositive; + + const SummaryCard({ + super.key, + required this.icon, + required this.iconBackgroundColor, + required this.label, + required this.value, + required this.currency, + required this.isPositive, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.grey.shade50, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + icon, + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: iconBackgroundColor, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 4), + Text( + label, + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + '${isPositive ? '+' : '-'}${formatNumber(value)} $currency', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: iconBackgroundColor, + ), + ), + ], + ), + ), + ], + ), + ); + } +} From 9ab3aa929543d91674305cf9fd342f2c17f6c7d9 Mon Sep 17 00:00:00 2001 From: abdo-essam Date: Fri, 6 Feb 2026 23:44:10 +0200 Subject: [PATCH 02/15] feat: set statistics screen as initial route and refactor Monthly Overview Sets the initial route to '/statistics'. Refactors the `MonthlyOverview` widget for a cleaner UI and adds localization. Updates the `.gitignore` file to include more patterns and improve organization. --- .gitignore | 48 +- ios/.gitignore | 2 + ios/Flutter/Debug.xcconfig | 1 - ios/Flutter/Release.xcconfig | 1 - lib/core/l10n/app_en.arb | 3 +- lib/design_system/widgets/app_bar.dart | 184 +-- lib/money_app.dart | 1 + lib/presentation/navigation/routes.g.dart | 79 - .../statistics/statistics_screen.dart | 15 +- .../monthly_overview/monthly_overview.dart | 27 +- .../monthly_overview/summary_card.dart | 10 +- pubspec.lock | 1327 ----------------- 12 files changed, 148 insertions(+), 1550 deletions(-) delete mode 100644 ios/Flutter/Debug.xcconfig delete mode 100644 ios/Flutter/Release.xcconfig delete mode 100644 lib/presentation/navigation/routes.g.dart delete mode 100644 pubspec.lock diff --git a/.gitignore b/.gitignore index b815a03..58f7a87 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ +# ============================== # Miscellaneous +# ============================== *.class *.log *.pyc @@ -15,39 +17,45 @@ migrate_working_dir/ *.secret *.properties *.lock -# IntelliJ related + +# ============================== +# IDE +# ============================== *.iml *.ipr *.iws .idea/ -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ +# Uncomment if you DON'T want VS Code settings in git +# .vscode/ -# Flutter/Dart/Pub related +# ============================== +# Flutter / Dart / Pub +# ============================== **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins-dependencies .pub-cache/ .pub/ -/build/ -/coverage/ -pubspec.lock +build/ +coverage/ -# Symbolication related -app.*.symbols +# ⚠️ If this is a PACKAGE keep this ignored +# ⚠️ If this is an APP you may want to track it +pubspec.lock -# Obfuscation related -app.*.map.json +# Generated files +*.g.dart +*.freezed.dart +*.gen.dart -# Android Studio will place build artifacts here -/android/app/debug -/android/app/profile -/android/app/release +# ============================== +# iOS +# ============================== +ios/Flutter/Debug.xcconfig +ios/Flutter/Release.xcconfig +ios/Podfile -# Flutter generated localization files -**/l10n/app_localizations.dart -**/l10n/app_localizations_*.dart \ No newline at end of file +# =========================== +/lib/presentation/navigation/routes.g.dart diff --git a/ios/.gitignore b/ios/.gitignore index 7a7f987..0e7682c 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -32,3 +32,5 @@ Runner/GeneratedPluginRegistrant.* !default.mode2v3 !default.pbxuser !default.perspectivev3 +/Flutter/Debug.xcconfig +/Flutter/Release.xcconfig diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig deleted file mode 100644 index 592ceee..0000000 --- a/ios/Flutter/Debug.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig deleted file mode 100644 index 592ceee..0000000 --- a/ios/Flutter/Release.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "Generated.xcconfig" diff --git a/lib/core/l10n/app_en.arb b/lib/core/l10n/app_en.arb index 2e4d0a1..2c439a9 100644 --- a/lib/core/l10n/app_en.arb +++ b/lib/core/l10n/app_en.arb @@ -107,5 +107,6 @@ "current": {}, "total": {} } - } + }, + "monthly_overview" : "Monthly Overview" } \ No newline at end of file diff --git a/lib/design_system/widgets/app_bar.dart b/lib/design_system/widgets/app_bar.dart index 53c6acb..d12e48a 100644 --- a/lib/design_system/widgets/app_bar.dart +++ b/lib/design_system/widgets/app_bar.dart @@ -1,111 +1,111 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:moneyplus/design_system/assets/app_assets.dart'; -import 'package:moneyplus/design_system/theme/money_extension_context.dart'; + import 'package:flutter/material.dart'; + import 'package:flutter_svg/flutter_svg.dart'; + import 'package:moneyplus/design_system/assets/app_assets.dart'; + import 'package:moneyplus/design_system/theme/money_extension_context.dart'; -class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { - final String? title; - final Widget? leading; - final Widget? trailing; - final double? leadingWidth; - final Color? backgroundColor; + class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { + final String? title; + final Widget? leading; + final Widget? trailing; + final double? leadingWidth; + final Color? backgroundColor; - const CustomAppBar({ - super.key, - this.title, - this.leading, - this.trailing, - this.leadingWidth, - this.backgroundColor, - }); + const CustomAppBar({ + super.key, + this.title, + this.leading, + this.trailing, + this.leadingWidth, + this.backgroundColor, + }); - @override - Widget build(BuildContext context) { - final typo = context.typography; - final colors = context.colors; - final contentColor = colors.title; + @override + Widget build(BuildContext context) { + final typo = context.typography; + final colors = context.colors; + final contentColor = colors.title; - return AppBar( - backgroundColor: backgroundColor, - titleSpacing: 8, - leadingWidth: leadingWidth, - automaticallyImplyLeading: false, + return AppBar( + backgroundColor: backgroundColor, + titleSpacing: 8, + leadingWidth: leadingWidth, + automaticallyImplyLeading: false, - title: title != null - ? Text(title!, style: typo.title.small.copyWith(color: contentColor)) - : null, + title: title != null + ? Text(title!, style: typo.title.small.copyWith(color: contentColor)) + : null, - leading: leading != null - ? Padding( - padding: const EdgeInsetsDirectional.only(start: 16.0), - child: leading!, - ) - : null, + leading: leading != null + ? Padding( + padding: const EdgeInsetsDirectional.only(start: 16.0), + child: leading!, + ) + : null, - actions: [ - if (trailing != null) - Padding( - padding: const EdgeInsetsDirectional.only(end: 16), - child: trailing!, - ), - ], - ); - } + actions: [ + if (trailing != null) + Padding( + padding: const EdgeInsetsDirectional.only(end: 16), + child: trailing!, + ), + ], + ); + } - @override - Size get preferredSize => const Size.fromHeight(kToolbarHeight); -} + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); + } -class AppBarCircleButton extends StatelessWidget { - final String assetPath; - final VoidCallback? onTap; + class AppBarCircleButton extends StatelessWidget { + final String assetPath; + final VoidCallback? onTap; - const AppBarCircleButton({super.key, required this.assetPath, this.onTap}); + const AppBarCircleButton({super.key, required this.assetPath, this.onTap}); - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: onTap, - child: Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: context.colors.surfaceHigh, - shape: BoxShape.circle, + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: context.colors.surfaceHigh, + shape: BoxShape.circle, + ), + alignment: Alignment.center, + child: SvgPicture.asset(assetPath, width: 20, height: 20), ), - alignment: Alignment.center, - child: SvgPicture.asset(assetPath, width: 20, height: 20), - ), - ); + ); + } } -} -class AppBarCalendar extends StatelessWidget { - final VoidCallback onTap; - final String date; + class AppBarCalendar extends StatelessWidget { + final VoidCallback onTap; + final String date; - const AppBarCalendar({super.key, required this.onTap, required this.date}); + const AppBarCalendar({super.key, required this.onTap, required this.date}); - @override - Widget build(BuildContext context) { - final typo = context.typography; - final colors = context.colors; - final contentColor = colors.title; + @override + Widget build(BuildContext context) { + final typo = context.typography; + final colors = context.colors; + final contentColor = colors.title; - return GestureDetector( - onTap: onTap, - behavior: HitTestBehavior.opaque, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - child: Row( - mainAxisSize: MainAxisSize.min, - spacing: 4, - children: [ - Text(date, style: typo.label.small.copyWith(color: contentColor)), - SvgPicture.asset(AppAssets.icNormalArrowDown, width: 20, height: 20), - ], + return GestureDetector( + onTap: onTap, + behavior: HitTestBehavior.opaque, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + spacing: 4, + children: [ + Text(date, style: typo.label.small.copyWith(color: contentColor)), + SvgPicture.asset(AppAssets.icNormalArrowDown, width: 20, height: 20), + ], + ), ), - ), - ); + ); + } } -} diff --git a/lib/money_app.dart b/lib/money_app.dart index dcff974..9904dd4 100644 --- a/lib/money_app.dart +++ b/lib/money_app.dart @@ -8,6 +8,7 @@ import 'core/l10n/app_localizations.dart'; final _router = GoRouter( routes: $appRoutes, + initialLocation: '/statistics' ); diff --git a/lib/presentation/navigation/routes.g.dart b/lib/presentation/navigation/routes.g.dart deleted file mode 100644 index 90f3bb2..0000000 --- a/lib/presentation/navigation/routes.g.dart +++ /dev/null @@ -1,79 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'routes.dart'; - -// ************************************************************************** -// GoRouterGenerator -// ************************************************************************** - -List get $appRoutes => [$onBoardingRoute, $loginRoute, $homeRoute]; - -RouteBase get $onBoardingRoute => - GoRouteData.$route(path: '/', factory: $OnBoardingRoute._fromState); - -mixin $OnBoardingRoute on GoRouteData { - static OnBoardingRoute _fromState(GoRouterState state) => - const OnBoardingRoute(); - - @override - String get location => GoRouteData.$location('/'); - - @override - void go(BuildContext context) => context.go(location); - - @override - Future push(BuildContext context) => context.push(location); - - @override - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - @override - void replace(BuildContext context) => context.replace(location); -} - -RouteBase get $loginRoute => - GoRouteData.$route(path: '/login', factory: $LoginRoute._fromState); - -mixin $LoginRoute on GoRouteData { - static LoginRoute _fromState(GoRouterState state) => const LoginRoute(); - - @override - String get location => GoRouteData.$location('/login'); - - @override - void go(BuildContext context) => context.go(location); - - @override - Future push(BuildContext context) => context.push(location); - - @override - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - @override - void replace(BuildContext context) => context.replace(location); -} - -RouteBase get $homeRoute => - GoRouteData.$route(path: '/home', factory: $HomeRoute._fromState); - -mixin $HomeRoute on GoRouteData { - static HomeRoute _fromState(GoRouterState state) => const HomeRoute(); - - @override - String get location => GoRouteData.$location('/home'); - - @override - void go(BuildContext context) => context.go(location); - - @override - Future push(BuildContext context) => context.push(location); - - @override - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - @override - void replace(BuildContext context) => context.replace(location); -} diff --git a/lib/presentation/statistics/statistics_screen.dart b/lib/presentation/statistics/statistics_screen.dart index 98d4488..f2507d8 100644 --- a/lib/presentation/statistics/statistics_screen.dart +++ b/lib/presentation/statistics/statistics_screen.dart @@ -11,11 +11,16 @@ class StatisticsScreen extends StatelessWidget { backgroundColor: Colors.grey.shade100, body: Padding( padding: const EdgeInsets.all(16), - child: MonthlyOverview( - income: 1500000, - expenses: 850000, - currency: 'IQD', - maxValue: 2000000, + child: Column( + children: [ + SizedBox(height: 60), + MonthlyOverview( + income: 1500000, + expenses: 850000, + currency: 'IQD', + maxValue: 2000000, + ) + ], ), ), ); diff --git a/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart b/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart index c816398..7b277a2 100644 --- a/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart +++ b/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:moneyplus/design_system/theme/money_extension_context.dart'; +import '../../../../core/l10n/app_localizations.dart'; import '../../../../design_system/assets/app_assets.dart'; import 'progress_bar_section.dart'; import 'savings_banner.dart'; @@ -26,32 +28,23 @@ class MonthlyOverview extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], + color: context.colors.surfaceLow, + borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ // Header - const Text( - 'Monthly Overview', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - color: Colors.black87, + Text( + AppLocalizations.of(context)!.monthly_overview, + style: context.typography.label.medium.copyWith( + color: context.colors.title, ), ), - const SizedBox(height: 16), + const SizedBox(height: 12), // Income & Expenses Cards Row( diff --git a/lib/presentation/statistics/widgets/monthly_overview/summary_card.dart b/lib/presentation/statistics/widgets/monthly_overview/summary_card.dart index cab1488..0e1b436 100644 --- a/lib/presentation/statistics/widgets/monthly_overview/summary_card.dart +++ b/lib/presentation/statistics/widgets/monthly_overview/summary_card.dart @@ -23,14 +23,10 @@ class SummaryCard extends StatelessWidget { Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.grey.shade50, - borderRadius: BorderRadius.circular(12), - ), child: Row( children: [ icon, - const SizedBox(width: 10), + const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -38,8 +34,8 @@ class SummaryCard extends StatelessWidget { Row( children: [ Container( - width: 8, - height: 8, + width: 6, + height: 6, decoration: BoxDecoration( color: iconBackgroundColor, shape: BoxShape.circle, diff --git a/pubspec.lock b/pubspec.lock deleted file mode 100644 index 5190c57..0000000 --- a/pubspec.lock +++ /dev/null @@ -1,1327 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - sha256: "5b7468c326d2f8a4f630056404ca0d291ade42918f4a3c6233618e724f39da8e" - url: "https://pub.dev" - source: hosted - version: "92.0.0" - adaptive_number: - dependency: transitive - description: - name: adaptive_number - sha256: "3a567544e9b5c9c803006f51140ad544aedc79604fd4f3f2c1380003f97c1d77" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - analyzer: - dependency: transitive - description: - name: analyzer - sha256: "70e4b1ef8003c64793a9e268a551a82869a8a96f39deb73dea28084b0e8bf75e" - url: "https://pub.dev" - source: hosted - version: "9.0.0" - ansicolor: - dependency: transitive - description: - name: ansicolor - sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f" - url: "https://pub.dev" - source: hosted - version: "2.0.3" - app_links: - dependency: transitive - description: - name: app_links - sha256: "5f88447519add627fe1cbcab4fd1da3d4fed15b9baf29f28b22535c95ecee3e8" - url: "https://pub.dev" - source: hosted - version: "6.4.1" - app_links_linux: - dependency: transitive - description: - name: app_links_linux - sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81 - url: "https://pub.dev" - source: hosted - version: "1.0.3" - app_links_platform_interface: - dependency: transitive - description: - name: app_links_platform_interface - sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f" - url: "https://pub.dev" - source: hosted - version: "2.0.2" - app_links_web: - dependency: transitive - description: - name: app_links_web - sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555 - url: "https://pub.dev" - source: hosted - version: "1.0.4" - archive: - dependency: transitive - description: - name: archive - sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" - url: "https://pub.dev" - source: hosted - version: "4.0.7" - args: - dependency: transitive - description: - name: args - sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.dev" - source: hosted - version: "2.7.0" - async: - dependency: transitive - description: - name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" - url: "https://pub.dev" - source: hosted - version: "2.13.0" - bloc: - dependency: transitive - description: - name: bloc - sha256: a48653a82055a900b88cd35f92429f068c5a8057ae9b136d197b3d56c57efb81 - url: "https://pub.dev" - source: hosted - version: "9.2.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - build: - dependency: transitive - description: - name: build - sha256: "275bf6bb2a00a9852c28d4e0b410da1d833a734d57d39d44f94bfc895a484ec3" - url: "https://pub.dev" - source: hosted - version: "4.0.4" - build_config: - dependency: transitive - description: - name: build_config - sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - build_daemon: - dependency: transitive - description: - name: build_daemon - sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 - url: "https://pub.dev" - source: hosted - version: "4.1.1" - build_runner: - dependency: "direct dev" - description: - name: build_runner - sha256: b4d854962a32fd9f8efc0b76f98214790b833af8b2e9b2df6bfc927c0415a072 - url: "https://pub.dev" - source: hosted - version: "2.10.5" - build_verify: - dependency: "direct dev" - description: - name: build_verify - sha256: "3b17b59b6d66f9d3e6014996f089902d56cec5760e051c353cc387b9da577652" - url: "https://pub.dev" - source: hosted - version: "3.1.1" - built_collection: - dependency: transitive - description: - name: built_collection - sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" - url: "https://pub.dev" - source: hosted - version: "5.1.1" - built_value: - dependency: transitive - description: - name: built_value - sha256: "7931c90b84bc573fef103548e354258ae4c9d28d140e41961df6843c5d60d4d8" - url: "https://pub.dev" - source: hosted - version: "8.12.3" - characters: - dependency: transitive - description: - name: characters - sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b - url: "https://pub.dev" - source: hosted - version: "1.4.1" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" - url: "https://pub.dev" - source: hosted - version: "2.0.4" - cli_config: - dependency: transitive - description: - name: cli_config - sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec - url: "https://pub.dev" - source: hosted - version: "0.2.0" - clock: - dependency: transitive - description: - name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" - source: hosted - version: "1.1.2" - code_assets: - dependency: transitive - description: - name: code_assets - sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - code_builder: - dependency: transitive - description: - name: code_builder - sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" - url: "https://pub.dev" - source: hosted - version: "4.11.1" - collection: - dependency: transitive - description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" - source: hosted - version: "1.19.1" - convert: - dependency: transitive - description: - name: convert - sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 - url: "https://pub.dev" - source: hosted - version: "3.1.2" - coverage: - dependency: transitive - description: - name: coverage - sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" - url: "https://pub.dev" - source: hosted - version: "1.15.0" - crypto: - dependency: transitive - description: - name: crypto - sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf - url: "https://pub.dev" - source: hosted - version: "3.0.7" - csslib: - dependency: transitive - description: - name: csslib - sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" - url: "https://pub.dev" - source: hosted - version: "1.0.2" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.dev" - source: hosted - version: "1.0.8" - dart_jsonwebtoken: - dependency: transitive - description: - name: dart_jsonwebtoken - sha256: "0de65691c1d736e9459f22f654ddd6fd8368a271d4e41aa07e53e6301eff5075" - url: "https://pub.dev" - source: hosted - version: "3.3.1" - dart_style: - dependency: transitive - description: - name: dart_style - sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b - url: "https://pub.dev" - source: hosted - version: "3.1.3" - device_frame: - dependency: transitive - description: - name: device_frame - sha256: "7b2ebb2a09d6cc0f086b51bd1412d7be83e0170056a7290349169be41164c86a" - url: "https://pub.dev" - source: hosted - version: "1.4.0" - device_preview: - dependency: "direct main" - description: - name: device_preview - sha256: "88aa1cc73ee9a8ec771b309dcbc4000cc66b5d8456b825980997640ab1195bf5" - url: "https://pub.dev" - source: hosted - version: "1.3.1" - ed25519_edwards: - dependency: transitive - description: - name: ed25519_edwards - sha256: "6ce0112d131327ec6d42beede1e5dfd526069b18ad45dcf654f15074ad9276cd" - url: "https://pub.dev" - source: hosted - version: "0.3.1" - equatable: - dependency: transitive - description: - name: equatable - sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" - url: "https://pub.dev" - source: hosted - version: "2.0.8" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.dev" - source: hosted - version: "1.3.3" - ffi: - dependency: transitive - description: - name: ffi - sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c - url: "https://pub.dev" - source: hosted - version: "2.1.5" - file: - dependency: transitive - description: - name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" - source: hosted - version: "7.0.1" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.dev" - source: hosted - version: "1.1.1" - fl_chart: - dependency: "direct main" - description: - name: fl_chart - sha256: "7ca9a40f4eb85949190e54087be8b4d6ac09dc4c54238d782a34cf1f7c011de9" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_bloc: - dependency: "direct main" - description: - name: flutter_bloc - sha256: cf51747952201a455a1c840f8171d273be009b932c75093020f9af64f2123e38 - url: "https://pub.dev" - source: hosted - version: "9.1.1" - flutter_dotenv: - dependency: "direct main" - description: - name: flutter_dotenv - sha256: d4130c4a43e0b13fefc593bc3961f2cb46e30cb79e253d4a526b1b5d24ae1ce4 - url: "https://pub.dev" - source: hosted - version: "6.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" - url: "https://pub.dev" - source: hosted - version: "6.0.0" - flutter_localizations: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_native_splash: - dependency: "direct main" - description: - name: flutter_native_splash - sha256: "4fb9f4113350d3a80841ce05ebf1976a36de622af7d19aca0ca9a9911c7ff002" - url: "https://pub.dev" - source: hosted - version: "2.4.7" - flutter_svg: - dependency: "direct main" - description: - name: flutter_svg - sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95" - url: "https://pub.dev" - source: hosted - version: "2.2.3" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - freezed_annotation: - dependency: transitive - description: - name: freezed_annotation - sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" - url: "https://pub.dev" - source: hosted - version: "3.1.0" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.dev" - source: hosted - version: "4.0.0" - functions_client: - dependency: transitive - description: - name: functions_client - sha256: "94074d62167ae634127ef6095f536835063a7dc80f2b1aa306d2346ff9023996" - url: "https://pub.dev" - source: hosted - version: "2.5.0" - get_it: - dependency: "direct main" - description: - name: get_it - sha256: "1d648d2dd2047d7f7450d5727ca24ee435f240385753d90b49650e3cdff32e56" - url: "https://pub.dev" - source: hosted - version: "9.2.0" - glob: - dependency: transitive - description: - name: glob - sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.dev" - source: hosted - version: "2.1.3" - go_router: - dependency: "direct main" - description: - name: go_router - sha256: "7974313e217a7771557add6ff2238acb63f635317c35fa590d348fb238f00896" - url: "https://pub.dev" - source: hosted - version: "17.1.0" - go_router_builder: - dependency: "direct dev" - description: - name: go_router_builder - sha256: "135648d2febcf6e45d87ad12f4dbcf2d0fb46eb1aa14722c0a42c7a60f02ac55" - url: "https://pub.dev" - source: hosted - version: "4.2.0" - google_identity_services_web: - dependency: transitive - description: - name: google_identity_services_web - sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454" - url: "https://pub.dev" - source: hosted - version: "0.3.3+1" - google_sign_in: - dependency: "direct main" - description: - name: google_sign_in - sha256: "521031b65853b4409b8213c0387d57edaad7e2a949ce6dea0d8b2afc9cb29763" - url: "https://pub.dev" - source: hosted - version: "7.2.0" - google_sign_in_android: - dependency: transitive - description: - name: google_sign_in_android - sha256: "5ec98ab35387c68c0050495bb211bd88375873723a80fae7c2e9266ea0bdd8bb" - url: "https://pub.dev" - source: hosted - version: "7.2.7" - google_sign_in_ios: - dependency: transitive - description: - name: google_sign_in_ios - sha256: "234fc2830b55d1bbeb7e05662967691f5994143ff43dc70d3f139d1bbb3b8fb2" - url: "https://pub.dev" - source: hosted - version: "6.2.5" - google_sign_in_platform_interface: - dependency: transitive - description: - name: google_sign_in_platform_interface - sha256: "7f59208c42b415a3cca203571128d6f84f885fead2d5b53eb65a9e27f2965bb5" - url: "https://pub.dev" - source: hosted - version: "3.1.0" - google_sign_in_web: - dependency: transitive - description: - name: google_sign_in_web - sha256: "2fc1f941e6443b2d6984f4056a727a3eaeab15d8ee99ba7125d79029be75a1da" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - gotrue: - dependency: transitive - description: - name: gotrue - sha256: f7b52008311941a7c3e99f9590c4ee32dfc102a5442e43abf1b287d9f8cc39b2 - url: "https://pub.dev" - source: hosted - version: "2.18.0" - graphs: - dependency: transitive - description: - name: graphs - sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" - url: "https://pub.dev" - source: hosted - version: "2.3.2" - gtk: - dependency: transitive - description: - name: gtk - sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c - url: "https://pub.dev" - source: hosted - version: "2.1.0" - hooks: - dependency: transitive - description: - name: hooks - sha256: "7a08a0d684cb3b8fb604b78455d5d352f502b68079f7b80b831c62220ab0a4f6" - url: "https://pub.dev" - source: hosted - version: "1.0.1" - html: - dependency: transitive - description: - name: html - sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" - url: "https://pub.dev" - source: hosted - version: "0.15.6" - http: - dependency: transitive - description: - name: http - sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" - url: "https://pub.dev" - source: hosted - version: "1.6.0" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 - url: "https://pub.dev" - source: hosted - version: "3.2.2" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.dev" - source: hosted - version: "4.1.2" - image: - dependency: transitive - description: - name: image - sha256: "492bd52f6c4fbb6ee41f781ff27765ce5f627910e1e0cbecfa3d9add5562604c" - url: "https://pub.dev" - source: hosted - version: "4.7.2" - intl: - dependency: "direct main" - description: - name: intl - sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" - url: "https://pub.dev" - source: hosted - version: "0.20.2" - io: - dependency: transitive - description: - name: io - sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b - url: "https://pub.dev" - source: hosted - version: "1.0.5" - json_annotation: - dependency: transitive - description: - name: json_annotation - sha256: "805fa86df56383000f640384b282ce0cb8431f1a7a2396de92fb66186d8c57df" - url: "https://pub.dev" - source: hosted - version: "4.10.0" - jwt_decode: - dependency: transitive - description: - name: jwt_decode - sha256: d2e9f68c052b2225130977429d30f187aa1981d789c76ad104a32243cfdebfbb - url: "https://pub.dev" - source: hosted - version: "0.3.1" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.dev" - source: hosted - version: "11.0.2" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.dev" - source: hosted - version: "3.0.10" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - lints: - dependency: transitive - description: - name: lints - sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" - url: "https://pub.dev" - source: hosted - version: "6.1.0" - logging: - dependency: "direct main" - description: - name: logging - sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" - source: hosted - version: "1.3.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" - url: "https://pub.dev" - source: hosted - version: "0.12.18" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" - url: "https://pub.dev" - source: hosted - version: "0.13.0" - meta: - dependency: transitive - description: - name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" - source: hosted - version: "1.17.0" - mime: - dependency: transitive - description: - name: mime - sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - month_year_picker: - dependency: "direct main" - description: - name: month_year_picker - sha256: "0990a698061ce4f421ba4f2163895dae5ea88a2f2ff09294f0d77d7c9e6c0491" - url: "https://pub.dev" - source: hosted - version: "0.5.0+1" - native_toolchain_c: - dependency: transitive - description: - name: native_toolchain_c - sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac" - url: "https://pub.dev" - source: hosted - version: "0.17.4" - nested: - dependency: transitive - description: - name: nested - sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - node_preamble: - dependency: transitive - description: - name: node_preamble - sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" - url: "https://pub.dev" - source: hosted - version: "2.0.2" - objective_c: - dependency: transitive - description: - name: objective_c - sha256: "983c7fa1501f6dcc0cb7af4e42072e9993cb28d73604d25ebf4dab08165d997e" - url: "https://pub.dev" - source: hosted - version: "9.2.5" - package_config: - dependency: transitive - description: - name: package_config - sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc - url: "https://pub.dev" - source: hosted - version: "2.2.0" - path: - dependency: transitive - description: - name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - path_parsing: - dependency: transitive - description: - name: path_parsing - sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - path_provider: - dependency: transitive - description: - name: path_provider - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.dev" - source: hosted - version: "2.1.5" - path_provider_android: - dependency: transitive - description: - name: path_provider_android - sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e - url: "https://pub.dev" - source: hosted - version: "2.2.22" - path_provider_foundation: - dependency: transitive - description: - name: path_provider_foundation - sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" - url: "https://pub.dev" - source: hosted - version: "2.6.0" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" - source: hosted - version: "2.2.1" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.dev" - source: hosted - version: "2.3.0" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" - url: "https://pub.dev" - source: hosted - version: "7.0.1" - platform: - dependency: transitive - description: - name: platform - sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" - source: hosted - version: "3.1.6" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" - source: hosted - version: "2.1.8" - pointycastle: - dependency: transitive - description: - name: pointycastle - sha256: "92aa3841d083cc4b0f4709b5c74fd6409a3e6ba833ffc7dc6a8fee096366acf5" - url: "https://pub.dev" - source: hosted - version: "4.0.0" - pool: - dependency: transitive - description: - name: pool - sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" - url: "https://pub.dev" - source: hosted - version: "1.5.2" - posix: - dependency: transitive - description: - name: posix - sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" - url: "https://pub.dev" - source: hosted - version: "6.0.3" - postgrest: - dependency: transitive - description: - name: postgrest - sha256: f4b6bb24b465c47649243ef0140475de8a0ec311dc9c75ebe573b2dcabb10460 - url: "https://pub.dev" - source: hosted - version: "2.6.0" - provider: - dependency: transitive - description: - name: provider - sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" - url: "https://pub.dev" - source: hosted - version: "6.1.5+1" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" - url: "https://pub.dev" - source: hosted - version: "1.5.0" - realtime_client: - dependency: transitive - description: - name: realtime_client - sha256: "5268afc208d02fb9109854d262c1ebf6ece224cd285199ae1d2f92d2ff49dbf1" - url: "https://pub.dev" - source: hosted - version: "2.7.0" - retry: - dependency: transitive - description: - name: retry - sha256: "822e118d5b3aafed083109c72d5f484c6dc66707885e07c0fbcb8b986bba7efc" - url: "https://pub.dev" - source: hosted - version: "3.1.2" - rxdart: - dependency: transitive - description: - name: rxdart - sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" - url: "https://pub.dev" - source: hosted - version: "0.28.0" - shared_preferences: - dependency: transitive - description: - name: shared_preferences - sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" - url: "https://pub.dev" - source: hosted - version: "2.5.4" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - sha256: cbc40be9be1c5af4dab4d6e0de4d5d3729e6f3d65b89d21e1815d57705644a6f - url: "https://pub.dev" - source: hosted - version: "2.4.20" - shared_preferences_foundation: - dependency: transitive - description: - name: shared_preferences_foundation - sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" - url: "https://pub.dev" - source: hosted - version: "2.5.6" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 - url: "https://pub.dev" - source: hosted - version: "2.4.3" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - shelf: - dependency: transitive - description: - name: shelf - sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 - url: "https://pub.dev" - source: hosted - version: "1.4.2" - shelf_packages_handler: - dependency: transitive - description: - name: shelf_packages_handler - sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - shelf_static: - dependency: transitive - description: - name: shelf_static - sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 - url: "https://pub.dev" - source: hosted - version: "1.1.3" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" - url: "https://pub.dev" - source: hosted - version: "3.0.0" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - source_gen: - dependency: transitive - description: - name: source_gen - sha256: "1d562a3c1f713904ebbed50d2760217fd8a51ca170ac4b05b0db490699dbac17" - url: "https://pub.dev" - source: hosted - version: "4.2.0" - source_helper: - dependency: transitive - description: - name: source_helper - sha256: "4a85e90b50694e652075cbe4575665539d253e6ec10e46e76b45368ab5e3caae" - url: "https://pub.dev" - source: hosted - version: "1.3.10" - source_map_stack_trace: - dependency: transitive - description: - name: source_map_stack_trace - sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b - url: "https://pub.dev" - source: hosted - version: "2.1.2" - source_maps: - dependency: transitive - description: - name: source_maps - sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" - url: "https://pub.dev" - source: hosted - version: "0.10.13" - source_span: - dependency: transitive - description: - name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - url: "https://pub.dev" - source: hosted - version: "1.10.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" - source: hosted - version: "1.12.1" - storage_client: - dependency: transitive - description: - name: storage_client - sha256: "1c61b19ed9e78f37fdd1ca8b729ab8484e6c8fe82e15c87e070b861951183657" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - stream_transform: - dependency: transitive - description: - name: stream_transform - sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 - url: "https://pub.dev" - source: hosted - version: "2.1.1" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" - source: hosted - version: "1.4.1" - supabase: - dependency: transitive - description: - name: supabase - sha256: cc039f63a3168386b3a4f338f3bff342c860d415a3578f3fbe854024aee6f911 - url: "https://pub.dev" - source: hosted - version: "2.10.2" - supabase_flutter: - dependency: "direct main" - description: - name: supabase_flutter - sha256: "92b2416ecb6a5c3ed34cf6e382b35ce6cc8921b64f2a9299d5d28968d42b09bb" - url: "https://pub.dev" - source: hosted - version: "2.12.0" - svg_flutter: - dependency: "direct main" - description: - name: svg_flutter - sha256: "5d6b37ca069e5e0eaa987691dc61594674e687b5bec8fd9797877a5f331d43e1" - url: "https://pub.dev" - source: hosted - version: "0.0.1" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" - source: hosted - version: "1.2.2" - test: - dependency: transitive - description: - name: test - sha256: "77cc98ea27006c84e71a7356cf3daf9ddbde2d91d84f77dbfe64cf0e4d9611ae" - url: "https://pub.dev" - source: hosted - version: "1.28.0" - test_api: - dependency: transitive - description: - name: test_api - sha256: "19a78f63e83d3a61f00826d09bc2f60e191bf3504183c001262be6ac75589fb8" - url: "https://pub.dev" - source: hosted - version: "0.7.8" - test_core: - dependency: transitive - description: - name: test_core - sha256: f1072617a6657e5fc09662e721307f7fb009b4ed89b19f47175d11d5254a62d4 - url: "https://pub.dev" - source: hosted - version: "0.6.14" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - universal_io: - dependency: transitive - description: - name: universal_io - sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2 - url: "https://pub.dev" - source: hosted - version: "2.3.1" - url_launcher: - dependency: transitive - description: - name: url_launcher - sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 - url: "https://pub.dev" - source: hosted - version: "6.3.2" - url_launcher_android: - dependency: transitive - description: - name: url_launcher_android - sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" - url: "https://pub.dev" - source: hosted - version: "6.3.28" - url_launcher_ios: - dependency: transitive - description: - name: url_launcher_ios - sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad - url: "https://pub.dev" - source: hosted - version: "6.3.6" - url_launcher_linux: - dependency: transitive - description: - name: url_launcher_linux - sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a - url: "https://pub.dev" - source: hosted - version: "3.2.2" - url_launcher_macos: - dependency: transitive - description: - name: url_launcher_macos - sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" - url: "https://pub.dev" - source: hosted - version: "3.2.5" - url_launcher_platform_interface: - dependency: transitive - description: - name: url_launcher_platform_interface - sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" - url: "https://pub.dev" - source: hosted - version: "2.3.2" - url_launcher_web: - dependency: transitive - description: - name: url_launcher_web - sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f - url: "https://pub.dev" - source: hosted - version: "2.4.2" - url_launcher_windows: - dependency: transitive - description: - name: url_launcher_windows - sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" - url: "https://pub.dev" - source: hosted - version: "3.1.5" - vector_graphics: - dependency: transitive - description: - name: vector_graphics - sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 - url: "https://pub.dev" - source: hosted - version: "1.1.19" - vector_graphics_codec: - dependency: transitive - description: - name: vector_graphics_codec - sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" - url: "https://pub.dev" - source: hosted - version: "1.1.13" - vector_graphics_compiler: - dependency: transitive - description: - name: vector_graphics_compiler - sha256: "201e876b5d52753626af64b6359cd13ac6011b80728731428fd34bc840f71c9b" - url: "https://pub.dev" - source: hosted - version: "1.1.20" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.dev" - source: hosted - version: "2.2.0" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" - url: "https://pub.dev" - source: hosted - version: "15.0.2" - watcher: - dependency: transitive - description: - name: watcher - sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" - url: "https://pub.dev" - source: hosted - version: "1.2.1" - web: - dependency: transitive - description: - name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - web_socket: - dependency: transitive - description: - name: web_socket - sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" - url: "https://pub.dev" - source: hosted - version: "1.0.1" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 - url: "https://pub.dev" - source: hosted - version: "3.0.3" - webkit_inspection_protocol: - dependency: transitive - description: - name: webkit_inspection_protocol - sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" - url: "https://pub.dev" - source: hosted - version: "1.2.1" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - xml: - dependency: transitive - description: - name: xml - sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" - url: "https://pub.dev" - source: hosted - version: "6.6.1" - yaml: - dependency: transitive - description: - name: yaml - sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.dev" - source: hosted - version: "3.1.3" - yet_another_json_isolate: - dependency: transitive - description: - name: yet_another_json_isolate - sha256: fe45897501fa156ccefbfb9359c9462ce5dec092f05e8a56109db30be864f01e - url: "https://pub.dev" - source: hosted - version: "2.1.0" -sdks: - dart: ">=3.10.4 <4.0.0" - flutter: ">=3.38.4" From a84c02d4485c03b98ac5d7442f1ded7bf15d761b Mon Sep 17 00:00:00 2001 From: abdo-essam Date: Fri, 6 Feb 2026 23:44:41 +0200 Subject: [PATCH 03/15] chore: ignore generated localization files and CMakeLists.txt --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 58f7a87..faa5313 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,7 @@ ios/Podfile # =========================== /lib/presentation/navigation/routes.g.dart +/lib/core/l10n/app_localizations.dart +/lib/core/l10n/app_localizations_ar.dart +/lib/core/l10n/app_localizations_en.dart +/lib/utils/CMakeLists.txt From 14baec3c76d6fce8226a2ef8ec901b60e3bb3e91 Mon Sep 17 00:00:00 2001 From: abdo-essam Date: Fri, 6 Feb 2026 23:47:22 +0200 Subject: [PATCH 04/15] refactor: rename SummaryCard to SummaryItem and enhance UI Renamed `SummaryCard` to `SummaryItem` and updated its usage in the `MonthlyOverview` widget. The `SummaryItem` widget has been enhanced with a more detailed and theme-aware UI: - Replaced the simple background color with a more refined design using theme colors (`colors.secondaryVariant`, `colors.primaryVariant`). - Introduced a gradient circle indicator for income and expenses. - Utilized `RichText` to apply distinct styles for the positive/negative sign and the amount. - Replaced the boolean `isPositive` property with `isIncome` for better clarity. - Removed the `iconBackgroundColor` property in favor of theme-based coloring. --- .../monthly_overview/monthly_overview.dart | 12 +- .../monthly_overview/summary_card.dart | 70 ----------- .../monthly_overview/summary_item.dart | 114 ++++++++++++++++++ 3 files changed, 119 insertions(+), 77 deletions(-) delete mode 100644 lib/presentation/statistics/widgets/monthly_overview/summary_card.dart create mode 100644 lib/presentation/statistics/widgets/monthly_overview/summary_item.dart diff --git a/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart b/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart index 7b277a2..093f991 100644 --- a/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart +++ b/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart @@ -7,7 +7,7 @@ import '../../../../design_system/assets/app_assets.dart'; import 'progress_bar_section.dart'; import 'savings_banner.dart'; import 'scale_labels.dart'; -import 'summary_card.dart'; +import 'summary_item.dart'; class MonthlyOverview extends StatelessWidget { final double income; @@ -50,32 +50,30 @@ class MonthlyOverview extends StatelessWidget { Row( children: [ Expanded( - child: SummaryCard( + child: SummaryItem( icon: SvgPicture.asset( AppAssets.icWalletAdd, width: 24, height: 24, ), - iconBackgroundColor: const Color(0xFF00BFA5), label: 'Income', value: income, currency: currency, - isPositive: true, + isIncome: true, ), ), const SizedBox(width: 12), Expanded( - child: SummaryCard( + child: SummaryItem( icon: SvgPicture.asset( AppAssets.icMoneyRemove, width: 24, height: 24, ), - iconBackgroundColor: const Color(0xFFE91E63), label: 'Expenses', value: expenses, currency: currency, - isPositive: false, + isIncome: false, ), ), ], diff --git a/lib/presentation/statistics/widgets/monthly_overview/summary_card.dart b/lib/presentation/statistics/widgets/monthly_overview/summary_card.dart deleted file mode 100644 index 0e1b436..0000000 --- a/lib/presentation/statistics/widgets/monthly_overview/summary_card.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../utils.dart'; - -class SummaryCard extends StatelessWidget { - final Widget icon; - final Color iconBackgroundColor; - final String label; - final double value; - final String currency; - final bool isPositive; - - const SummaryCard({ - super.key, - required this.icon, - required this.iconBackgroundColor, - required this.label, - required this.value, - required this.currency, - required this.isPositive, - }); - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(12), - child: Row( - children: [ - icon, - const SizedBox(width: 8), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - width: 6, - height: 6, - decoration: BoxDecoration( - color: iconBackgroundColor, - shape: BoxShape.circle, - ), - ), - const SizedBox(width: 4), - Text( - label, - style: TextStyle( - fontSize: 12, - color: Colors.grey.shade600, - ), - ), - ], - ), - const SizedBox(height: 4), - Text( - '${isPositive ? '+' : '-'}${formatNumber(value)} $currency', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: iconBackgroundColor, - ), - ), - ], - ), - ), - ], - ), - ); - } -} diff --git a/lib/presentation/statistics/widgets/monthly_overview/summary_item.dart b/lib/presentation/statistics/widgets/monthly_overview/summary_item.dart new file mode 100644 index 0000000..1495025 --- /dev/null +++ b/lib/presentation/statistics/widgets/monthly_overview/summary_item.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:moneyplus/design_system/theme/money_extension_context.dart'; + +class SummaryItem extends StatelessWidget { + final Widget icon; + final String label; + final double value; + final String currency; + final bool isIncome; + + const SummaryItem({ + super.key, + required this.icon, + required this.label, + required this.value, + required this.currency, + required this.isIncome, + }); + + String _formatNumber(double value) { + final intValue = value.toInt(); + return intValue.toString().replaceAllMapped( + RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), + (Match m) => '${m[1]},', + ); + } + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final typography = context.typography; + + // Linear gradient colors for the circle indicator + final List gradientColors = isIncome + ? [const Color(0xFF0496AD), const Color(0xFF097C8E)] + : [const Color(0xFFDC143C), const Color(0xFFA01A35)]; + + return Row( + children: [ + // Icon Container - 48x48 with 12px radius + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: isIncome ? colors.secondaryVariant : colors.primaryVariant, + borderRadius: BorderRadius.circular(12), + ), + alignment: Alignment.center, + child: SizedBox(width: 28, height: 28, child: icon), + ), + const SizedBox(width: 8), + + // Text Content + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + // Label Row with gradient circle indicator + Row( + children: [ + // Gradient Circle Indicator + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: gradientColors, + ), + ), + ), + const SizedBox(width: 4), + // Label - Secondary/Primary color, Label/XSmall + Text( + label, + style: typography.label.xSmall?.copyWith( + color: isIncome ? colors.secondary : colors.primary, + ), + ), + ], + ), + const SizedBox(height: 2), + + // Amount Row + RichText( + text: TextSpan( + children: [ + // Sign (+/-) with specific color + TextSpan( + text: isIncome ? '+' : '-', + style: typography.label.medium.copyWith( + color: isIncome ? colors.green : colors.primary, + ), + ), + // Amount - Label/Medium with Title color + TextSpan( + text: '${_formatNumber(value)} $currency', + style: typography.label.medium.copyWith( + color: colors.title, + ), + ), + ], + ), + ), + ], + ), + ), + ], + ); + } +} From d9cf9da1c557c60333c48f478fe1ff10365f20c0 Mon Sep 17 00:00:00 2001 From: abdo-essam Date: Fri, 6 Feb 2026 23:49:07 +0200 Subject: [PATCH 05/15] refactor: replace `_AnimatedProgressBar` with `_GradientProgressBar` Replaced the existing `_AnimatedProgressBar` with the new `_GradientProgressBar` to update the visual style of the income and expenses bars in the monthly overview. Changes include: - Renaming `_AnimatedProgressBar` to `_GradientProgressBar`. - Updating the progress bar's visual appearance to use a gradient, a border, and a shadow. - Removing the icon from the progress bar. - Adjusting the height and layout to match the new design. --- .../progress_bar_section.dart | 107 ++++++++++-------- 1 file changed, 61 insertions(+), 46 deletions(-) diff --git a/lib/presentation/statistics/widgets/monthly_overview/progress_bar_section.dart b/lib/presentation/statistics/widgets/monthly_overview/progress_bar_section.dart index 0add877..eefa8ba 100644 --- a/lib/presentation/statistics/widgets/monthly_overview/progress_bar_section.dart +++ b/lib/presentation/statistics/widgets/monthly_overview/progress_bar_section.dart @@ -17,78 +17,93 @@ class ProgressBarSection extends StatelessWidget { return Column( children: [ // Income Bar - _AnimatedProgressBar( + _GradientProgressBar( value: income, maxValue: maxValue, - color: const Color(0xFF00BFA5), - icon: Icons.account_balance_wallet, + gradientColors: const [Color(0xFF0496AD), Color(0xFF097C8E)], + shadowColor: const Color(0xFF0496AD), ), const SizedBox(height: 8), // Expenses Bar - _AnimatedProgressBar( + _GradientProgressBar( value: expenses, maxValue: maxValue, - color: const Color(0xFFE91E63), - icon: Icons.receipt_long, + gradientColors: const [Color(0xFFDC143C), Color(0xFFA01A35)], + shadowColor: const Color(0xFFDC143C), ), ], ); } } -class _AnimatedProgressBar extends StatelessWidget { +class _GradientProgressBar extends StatelessWidget { final double value; final double maxValue; - final Color color; - final IconData icon; + final List gradientColors; + final Color shadowColor; - const _AnimatedProgressBar({ + const _GradientProgressBar({ required this.value, required this.maxValue, - required this.color, - required this.icon, + required this.gradientColors, + required this.shadowColor, }); @override Widget build(BuildContext context) { final percentage = (value / maxValue).clamp(0.0, 1.0); - return Stack( - children: [ - // Background - Container( - height: 32, - decoration: BoxDecoration( - color: Colors.grey.shade200, - borderRadius: BorderRadius.circular(16), - ), - ), - // Progress - FractionallySizedBox( - widthFactor: percentage, - child: Container( - height: 32, - decoration: BoxDecoration( - gradient: LinearGradient(colors: [color, color.withOpacity(0.8)]), - borderRadius: BorderRadius.circular(16), - ), - child: Padding( - padding: const EdgeInsets.only(right: 8), - child: Align( - alignment: Alignment.centerRight, - child: Container( - padding: const EdgeInsets.all(4), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.3), - borderRadius: BorderRadius.circular(6), + return SizedBox( + height: 20, + child: LayoutBuilder( + builder: (context, constraints) { + final totalWidth = constraints.maxWidth; + final progressWidth = totalWidth * percentage; + + return Stack( + children: [ + // Background Track (Group 72) - Border only, no fill + Container( + height: 20, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + border: Border.all( + color: const Color(0x1A1F1F1F), // #1F1F1F 10% + width: 1, ), - child: Icon(icon, color: Colors.white, size: 16), ), ), - ), - ), - ), - ], + + // Progress Bar (Rectangle 144) + if (progressWidth > 0) + Container( + width: progressWidth.clamp(20.0, totalWidth), + height: 20, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: gradientColors, + ), + border: Border.all( + color: const Color(0x1A1F1F1F), // #1F1F1F 10% + width: 0.5, + ), + boxShadow: [ + BoxShadow( + color: shadowColor.withOpacity(0.12), // 12% opacity + offset: const Offset(0, 4), + blurRadius: 8, + spreadRadius: 0, + ), + ], + ), + ), + ], + ); + }, + ), ); } -} +} \ No newline at end of file From 1caf56c9c042b3ecb97ead44732c39b8618bf864 Mon Sep 17 00:00:00 2001 From: abdo-essam Date: Fri, 6 Feb 2026 23:49:49 +0200 Subject: [PATCH 06/15] refactor: update ScaleLabels widget styling Refactors the `ScaleLabels` widget to use the application's design system for typography and colors. Also, one of the scale labels was updated from "1M500K" to "1.5M". --- .../monthly_overview/scale_labels.dart | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/lib/presentation/statistics/widgets/monthly_overview/scale_labels.dart b/lib/presentation/statistics/widgets/monthly_overview/scale_labels.dart index 273fb47..49ce380 100644 --- a/lib/presentation/statistics/widgets/monthly_overview/scale_labels.dart +++ b/lib/presentation/statistics/widgets/monthly_overview/scale_labels.dart @@ -1,32 +1,31 @@ import 'package:flutter/material.dart'; +import 'package:moneyplus/design_system/theme/money_extension_context.dart'; class ScaleLabels extends StatelessWidget { final double maxValue; - const ScaleLabels({super.key, required this.maxValue}); + const ScaleLabels({ + super.key, + required this.maxValue, + }); @override Widget build(BuildContext context) { - final labels = [ - '0', - '15K', - '50K', - '150K', - '300K', - '600K', - '1M', - '1M500K', - '2M', - ]; + final colors = context.colors; + final typography = context.typography; + + final labels = ['0', '15K', '50K', '150K', '300K', '600K', '1M', '1.5M', '2M']; return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: labels.map((label) { return Text( label, - style: TextStyle(fontSize: 10, color: Colors.grey.shade500), + style: typography.label.xSmall?.copyWith( + color: colors.hint, + ), ); }).toList(), ); } -} +} \ No newline at end of file From 550f6c6a269e186158013e1b33273cf47391415d Mon Sep 17 00:00:00 2001 From: abdo-essam Date: Fri, 6 Feb 2026 23:58:16 +0200 Subject: [PATCH 07/15] feat: refactor MonthlyOverview and SavingsBanner UI Refactors the `MonthlyOverview` widget by separating the main content container from the `SavingsBanner`. The `SavingsBanner` is now positioned below the main overview container, with adjusted styles, colors, and icons. Changes include: - Updated border radius logic in `MonthlyOverview` to conditionally curve corners based on whether savings are displayed. - Revised the `SavingsBanner` with new styling, including color, height, and content alignment, using theme-based properties. - Increased icon sizes within `SummaryItem`. --- .../monthly_overview/monthly_overview.dart | 137 ++++++++++-------- .../monthly_overview/savings_banner.dart | 55 ++++--- 2 files changed, 116 insertions(+), 76 deletions(-) diff --git a/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart b/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart index 093f991..a45bfff 100644 --- a/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart +++ b/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart @@ -27,75 +27,94 @@ class MonthlyOverview extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: context.colors.surfaceLow, - borderRadius: BorderRadius.circular(12), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - // Header - Text( - AppLocalizations.of(context)!.monthly_overview, - style: context.typography.label.medium.copyWith( - color: context.colors.title, + final colors = context.colors; + final typography = context.typography; + final l10n = AppLocalizations.of(context)!; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Main Container + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: colors.surfaceLow, + borderRadius: BorderRadius.only( + topLeft: const Radius.circular(12), + topRight: const Radius.circular(12), + // If savings > 0, bottom corners are 0, else 12 + bottomLeft: Radius.circular(savings > 0 ? 0 : 12), + bottomRight: Radius.circular(savings > 0 ? 0 : 12), ), ), - const SizedBox(height: 12), - - // Income & Expenses Cards - Row( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ - Expanded( - child: SummaryItem( - icon: SvgPicture.asset( - AppAssets.icWalletAdd, - width: 24, - height: 24, - ), - label: 'Income', - value: income, - currency: currency, - isIncome: true, + // Header + Text( + l10n.monthly_overview, + style: typography.label.medium.copyWith( + color: colors.title, ), ), - const SizedBox(width: 12), - Expanded( - child: SummaryItem( - icon: SvgPicture.asset( - AppAssets.icMoneyRemove, - width: 24, - height: 24, + const SizedBox(height: 12), + + // Income & Expenses Items + Row( + children: [ + Expanded( + child: SummaryItem( + icon: SvgPicture.asset( + AppAssets.icWalletAdd, + width: 28, + height: 28, + ), + label: l10n.income, + value: income, + currency: currency, + isIncome: true, + ), ), - label: 'Expenses', - value: expenses, - currency: currency, - isIncome: false, - ), + const SizedBox(width: 12), + Expanded( + child: SummaryItem( + icon: SvgPicture.asset( + AppAssets.icMoneyRemove, + width: 28, + height: 28, + ), + label: l10n.expense, + value: expenses, + currency: currency, + isIncome: false, + ), + ), + ], ), + const SizedBox(height: 16), + + // Progress Bars + ProgressBarSection( + income: income, + expenses: expenses, + maxValue: maxValue, + ), + const SizedBox(height: 6), + + // Scale Labels + ScaleLabels(maxValue: maxValue), ], ), - const SizedBox(height: 20), + ), - // Progress Bars - ProgressBarSection( - income: income, - expenses: expenses, - maxValue: maxValue, + // Savings Banner - Outside the main container, at the bottom + if (savings > 0) + SavingsBanner( + savings: savings, + currency: currency, ), - const SizedBox(height: 8), - - // Scale - ScaleLabels(maxValue: maxValue), - const SizedBox(height: 16), - - // Savings Banner - if (savings > 0) SavingsBanner(savings: savings, currency: currency), - ], - ), + ], ); } -} +} \ No newline at end of file diff --git a/lib/presentation/statistics/widgets/monthly_overview/savings_banner.dart b/lib/presentation/statistics/widgets/monthly_overview/savings_banner.dart index bf18722..13a2341 100644 --- a/lib/presentation/statistics/widgets/monthly_overview/savings_banner.dart +++ b/lib/presentation/statistics/widgets/monthly_overview/savings_banner.dart @@ -1,40 +1,61 @@ import 'package:flutter/material.dart'; - -import '../../utils.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:moneyplus/design_system/assets/app_assets.dart'; +import 'package:moneyplus/design_system/theme/money_extension_context.dart'; class SavingsBanner extends StatelessWidget { final double savings; final String currency; - const SavingsBanner({super.key, + const SavingsBanner({ + super.key, required this.savings, required this.currency, }); + String _formatNumber(double value) { + final intValue = value.toInt(); + return intValue.toString().replaceAllMapped( + RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), + (Match m) => '${m[1]},', + ); + } + @override Widget build(BuildContext context) { + final colors = context.colors; + final typography = context.typography; + return Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + height: 22, + padding: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( - color: const Color(0xFFE8F5F2), - borderRadius: BorderRadius.circular(24), + color: colors.secondaryVariant, // #EAF3F4 + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(12), + bottomRight: Radius.circular(12), + ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, children: [ - const Icon( - Icons.check_circle, - color: Color(0xFF00BFA5), - size: 18, + // Icon - You can replace with your specific icon + SvgPicture.asset( + AppAssets.icWalletAdd, // Replace with your savings icon if different + width: 14, + height: 14, + colorFilter: ColorFilter.mode( + colors.secondary, + BlendMode.srcIn, + ), ), - const SizedBox(width: 8), + const SizedBox(width: 4), + // Text - Label/XSmall, Secondary color Text( - 'You saved ${formatNumber(savings)} $currency this month', - style: const TextStyle( - fontSize: 13, - fontWeight: FontWeight.w500, - color: Color(0xFF00897B), + 'You saved ${_formatNumber(savings)} $currency this month', + style: typography.label.xSmall?.copyWith( + color: colors.secondary, ), ), ], From 35370a8e147c1245ad0dbb9dfa44fe0cb104dda5 Mon Sep 17 00:00:00 2001 From: abdo-essam Date: Sat, 7 Feb 2026 00:02:16 +0200 Subject: [PATCH 08/15] refactor: simplify border radius in MonthlyOverview The `BorderRadius.only` logic, which conditionally changed the bottom corners based on the `savings` value, has been removed. The `BoxDecoration` now uses a consistent `BorderRadius.all` with a radius of 12 for all corners, simplifying the widget's styling. --- .../widgets/monthly_overview/monthly_overview.dart | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart b/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart index a45bfff..d5bc50a 100644 --- a/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart +++ b/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart @@ -39,12 +39,8 @@ class MonthlyOverview extends StatelessWidget { padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: colors.surfaceLow, - borderRadius: BorderRadius.only( - topLeft: const Radius.circular(12), - topRight: const Radius.circular(12), - // If savings > 0, bottom corners are 0, else 12 - bottomLeft: Radius.circular(savings > 0 ? 0 : 12), - bottomRight: Radius.circular(savings > 0 ? 0 : 12), + borderRadius: BorderRadius.all( + Radius.circular(12), ), ), child: Column( From e381e7f7ca9db2f270db37451cbdcb74bb626271 Mon Sep 17 00:00:00 2001 From: abdo-essam Date: Sat, 7 Feb 2026 00:10:44 +0200 Subject: [PATCH 09/15] refactor: update monthly overview summary item UI Reduces the size of the icon container and its border radius in the `SummaryItem` widget. The gradient circle indicator size is also decreased, and the icon sizes for income and expense in `MonthlyOverview` are adjusted to fit the new layout. --- .../widgets/monthly_overview/monthly_overview.dart | 9 ++++----- .../widgets/monthly_overview/summary_item.dart | 14 ++++++-------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart b/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart index d5bc50a..56e2eef 100644 --- a/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart +++ b/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:moneyplus/design_system/theme/money_extension_context.dart'; - import '../../../../core/l10n/app_localizations.dart'; import '../../../../design_system/assets/app_assets.dart'; import 'progress_bar_section.dart'; @@ -63,8 +62,8 @@ class MonthlyOverview extends StatelessWidget { child: SummaryItem( icon: SvgPicture.asset( AppAssets.icWalletAdd, - width: 28, - height: 28, + width: 16, + height: 16, ), label: l10n.income, value: income, @@ -77,8 +76,8 @@ class MonthlyOverview extends StatelessWidget { child: SummaryItem( icon: SvgPicture.asset( AppAssets.icMoneyRemove, - width: 28, - height: 28, + width: 16, + height: 16, ), label: l10n.expense, value: expenses, diff --git a/lib/presentation/statistics/widgets/monthly_overview/summary_item.dart b/lib/presentation/statistics/widgets/monthly_overview/summary_item.dart index 1495025..9a7ac9e 100644 --- a/lib/presentation/statistics/widgets/monthly_overview/summary_item.dart +++ b/lib/presentation/statistics/widgets/monthly_overview/summary_item.dart @@ -37,16 +37,15 @@ class SummaryItem extends StatelessWidget { return Row( children: [ - // Icon Container - 48x48 with 12px radius Container( - width: 48, - height: 48, + width: 28, + height: 28, decoration: BoxDecoration( color: isIncome ? colors.secondaryVariant : colors.primaryVariant, - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(8), ), alignment: Alignment.center, - child: SizedBox(width: 28, height: 28, child: icon), + child: icon, ), const SizedBox(width: 8), @@ -61,8 +60,8 @@ class SummaryItem extends StatelessWidget { children: [ // Gradient Circle Indicator Container( - width: 8, - height: 8, + width: 6, + height: 6, decoration: BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient( @@ -82,7 +81,6 @@ class SummaryItem extends StatelessWidget { ), ], ), - const SizedBox(height: 2), // Amount Row RichText( From fabe4e1eb21dab1671e9b839c41cdc3ba25718ed Mon Sep 17 00:00:00 2001 From: abdo-essam Date: Sat, 7 Feb 2026 00:13:11 +0200 Subject: [PATCH 10/15] refactor: adjust spacing in MonthlyOverview widget The vertical and horizontal spacing within the `MonthlyOverview` component has been increased for better visual separation of elements. Specifically, the spacing between the income/expense summary items and between the progress bars and surrounding elements has been enlarged. Additionally, minor code formatting cleanup was performed. --- .../monthly_overview/monthly_overview.dart | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart b/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart index 56e2eef..9e8a0ea 100644 --- a/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart +++ b/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart @@ -38,9 +38,7 @@ class MonthlyOverview extends StatelessWidget { padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: colors.surfaceLow, - borderRadius: BorderRadius.all( - Radius.circular(12), - ), + borderRadius: BorderRadius.all(Radius.circular(12)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -49,9 +47,7 @@ class MonthlyOverview extends StatelessWidget { // Header Text( l10n.monthly_overview, - style: typography.label.medium.copyWith( - color: colors.title, - ), + style: typography.label.medium.copyWith(color: colors.title), ), const SizedBox(height: 12), @@ -71,7 +67,8 @@ class MonthlyOverview extends StatelessWidget { isIncome: true, ), ), - const SizedBox(width: 12), + const SizedBox(width: 24), + Expanded( child: SummaryItem( icon: SvgPicture.asset( @@ -87,15 +84,15 @@ class MonthlyOverview extends StatelessWidget { ), ], ), - const SizedBox(height: 16), - + const SizedBox(height: 24), // Progress Bars ProgressBarSection( income: income, expenses: expenses, maxValue: maxValue, ), - const SizedBox(height: 6), + + const SizedBox(height: 12), // Scale Labels ScaleLabels(maxValue: maxValue), @@ -104,12 +101,8 @@ class MonthlyOverview extends StatelessWidget { ), // Savings Banner - Outside the main container, at the bottom - if (savings > 0) - SavingsBanner( - savings: savings, - currency: currency, - ), + if (savings > 0) SavingsBanner(savings: savings, currency: currency), ], ); } -} \ No newline at end of file +} From 1ba86eaf396df373d01994ed8b2269221a037268 Mon Sep 17 00:00:00 2001 From: abdo-essam Date: Sat, 7 Feb 2026 00:21:25 +0200 Subject: [PATCH 11/15] refactor: use theme colors in ProgressBarSection Replaced hardcoded color values with theme-based colors from `context.colors` for the progress bar's background, border, and shadow. --- .../widgets/monthly_overview/progress_bar_section.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/presentation/statistics/widgets/monthly_overview/progress_bar_section.dart b/lib/presentation/statistics/widgets/monthly_overview/progress_bar_section.dart index eefa8ba..fa6d77e 100644 --- a/lib/presentation/statistics/widgets/monthly_overview/progress_bar_section.dart +++ b/lib/presentation/statistics/widgets/monthly_overview/progress_bar_section.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:moneyplus/design_system/theme/money_extension_context.dart'; class ProgressBarSection extends StatelessWidget { final double income; @@ -62,19 +63,18 @@ class _GradientProgressBar extends StatelessWidget { return Stack( children: [ - // Background Track (Group 72) - Border only, no fill Container( height: 20, decoration: BoxDecoration( + color: context.colors.stroke.withAlpha(10), borderRadius: BorderRadius.circular(30), border: Border.all( - color: const Color(0x1A1F1F1F), // #1F1F1F 10% + color: context.colors.stroke.withAlpha(10), width: 1, ), ), ), - // Progress Bar (Rectangle 144) if (progressWidth > 0) Container( width: progressWidth.clamp(20.0, totalWidth), @@ -87,12 +87,12 @@ class _GradientProgressBar extends StatelessWidget { colors: gradientColors, ), border: Border.all( - color: const Color(0x1A1F1F1F), // #1F1F1F 10% + color: context.colors.stroke.withAlpha(10), width: 0.5, ), boxShadow: [ BoxShadow( - color: shadowColor.withOpacity(0.12), // 12% opacity + color: shadowColor.withAlpha(12), offset: const Offset(0, 4), blurRadius: 8, spreadRadius: 0, From 230b706702cf2bfd6b18f7f90b147177b1fa9ff8 Mon Sep 17 00:00:00 2001 From: abdo-essam Date: Sat, 7 Feb 2026 00:22:45 +0200 Subject: [PATCH 12/15] feat: update scale label text color for monthly overview The color of the scale labels in the monthly overview statistics widget has been updated from `hint` to `body`. --- .../statistics/widgets/monthly_overview/scale_labels.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/statistics/widgets/monthly_overview/scale_labels.dart b/lib/presentation/statistics/widgets/monthly_overview/scale_labels.dart index 49ce380..4b7c7ae 100644 --- a/lib/presentation/statistics/widgets/monthly_overview/scale_labels.dart +++ b/lib/presentation/statistics/widgets/monthly_overview/scale_labels.dart @@ -22,7 +22,7 @@ class ScaleLabels extends StatelessWidget { return Text( label, style: typography.label.xSmall?.copyWith( - color: colors.hint, + color: colors.body, ), ); }).toList(), From ff643f6ce5f635990a1b9deea13721b38f0ba173 Mon Sep 17 00:00:00 2001 From: abdo-essam Date: Wed, 11 Feb 2026 19:12:54 +0200 Subject: [PATCH 13/15] refactor: modularize Statistics screen and introduce dedicated state management Decomposes the `StatisticsScreen` by extracting UI components into dedicated widgets and introduces a BLoC (`StatisticsCubit`) for state management, along with a repository pattern for data fetching. Key changes: - **State Management**: Introduces `StatisticsCubit`, `StatisticsState`, and `StatisticsRepository` to handle loading, success, and error states for statistics data. - **Component Refactoring**: The monolithic `MonthlyOverview` widget is broken down into smaller, reusable components: - `MonthlyOverviewSection`: The main container. - `OverviewProgressBar`: Manages progress bar UI. - `OverviewSavingsBanner`: Displays user savings. - `OverviewScaleLabels`: Renders the scale below the progress bars. - `SummaryItem`: Cleaned up to use a centralized `NumberFormatter`. - **Centralized Constants & Utilities**: - Adds `DesignConstants` for consistent sizing, spacing, and styling values. - Implements `NumberFormatter` to standardize number and currency formatting. - **UI States**: Adds `AppLoadingIndicator`, `AppErrorView`, and `AppEmptyView` to provide feedback for different data loading states. - **Dependency Injection**: Moves the `injection.dart` file to `core/di` and registers the new `StatisticsRepository` and `StatisticsCubit`. - **Localization**: Adds new strings for the statistics screen, including empty/error states and savings messages. --- lib/{ => core}/di/injection.dart | 41 ++++-- lib/core/l10n/app_ar.arb | 24 +++- lib/core/l10n/app_en.arb | 22 ++- lib/core/utils/number_formatter.dart | 15 ++ .../statistics_repository_impl.dart | 133 ++++++++++++++++++ .../constants/design_constants.dart | 54 +++++++ lib/design_system/widgets/app_empty_view.dart | 73 ++++++++++ lib/design_system/widgets/app_error_view.dart | 56 ++++++++ .../widgets/app_loading_indicator.dart | 15 ++ lib/domain/entity/monthly_overview.dart | 29 ++++ .../repository/statistics_repository.dart | 5 + lib/main.dart | 2 +- .../screen/account_setup_screen.dart | 3 +- .../screen/forget_password_screen.dart | 2 +- lib/presentation/home/screen/home_screen.dart | 3 +- .../income/screen/income_screen.dart | 3 +- lib/presentation/navigation/routes.dart | 10 +- .../statistics/cubit/statistics_cubit.dart | 36 +++++ .../statistics/cubit/statistics_state.dart | 31 ++++ .../statistics/statistics_screen.dart | 83 ++++++++--- .../monthly_overview/monthly_overview.dart | 108 -------------- .../monthly_overview_section.dart | 111 +++++++++++++++ .../overview_progress_bar.dart | 107 ++++++++++++++ .../overview_savings_banner.dart | 62 ++++++++ .../overview_scale_labels.dart | 25 ++++ .../progress_bar_section.dart | 109 -------------- .../monthly_overview/savings_banner.dart | 65 --------- .../monthly_overview/scale_labels.dart | 31 ---- .../monthly_overview/summary_item.dart | 65 ++++----- .../widgets/section_empty_view.dart | 54 +++++++ .../screen/update_password_screen.dart | 3 +- 31 files changed, 983 insertions(+), 397 deletions(-) rename lib/{ => core}/di/injection.dart (53%) create mode 100644 lib/core/utils/number_formatter.dart create mode 100644 lib/data/repository/statistics_repository_impl.dart create mode 100644 lib/design_system/constants/design_constants.dart create mode 100644 lib/design_system/widgets/app_empty_view.dart create mode 100644 lib/design_system/widgets/app_error_view.dart create mode 100644 lib/design_system/widgets/app_loading_indicator.dart create mode 100644 lib/domain/entity/monthly_overview.dart create mode 100644 lib/domain/repository/statistics_repository.dart create mode 100644 lib/presentation/statistics/cubit/statistics_cubit.dart create mode 100644 lib/presentation/statistics/cubit/statistics_state.dart delete mode 100644 lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart create mode 100644 lib/presentation/statistics/widgets/monthly_overview/monthly_overview_section.dart create mode 100644 lib/presentation/statistics/widgets/monthly_overview/overview_progress_bar.dart create mode 100644 lib/presentation/statistics/widgets/monthly_overview/overview_savings_banner.dart create mode 100644 lib/presentation/statistics/widgets/monthly_overview/overview_scale_labels.dart delete mode 100644 lib/presentation/statistics/widgets/monthly_overview/progress_bar_section.dart delete mode 100644 lib/presentation/statistics/widgets/monthly_overview/savings_banner.dart delete mode 100644 lib/presentation/statistics/widgets/monthly_overview/scale_labels.dart create mode 100644 lib/presentation/statistics/widgets/section_empty_view.dart diff --git a/lib/di/injection.dart b/lib/core/di/injection.dart similarity index 53% rename from lib/di/injection.dart rename to lib/core/di/injection.dart index adc885b..a34e314 100644 --- a/lib/di/injection.dart +++ b/lib/core/di/injection.dart @@ -1,18 +1,20 @@ import 'package:get_it/get_it.dart'; import 'package:moneyplus/presentation/account_setup/cubit/account_setup_cubit.dart'; - -import '../data/repository/account_repository.dart'; -import '../data/repository/authentication_repository.dart'; -import '../data/repository/transaction_repository_stub.dart'; -import '../data/service/app_secrets_provider.dart'; -import '../data/service/supabase_service.dart'; -import '../domain/repository/account_repository.dart'; -import '../domain/repository/authentication_repository.dart'; -import '../domain/validator/authentication_validator.dart'; -import '../domain/repository/transaction_repository.dart'; -import '../presentation/home/cubit/home_cubit.dart'; -import '../presentation/income/cubit/add_income_cubit.dart'; -import '../presentation/login/cubit/login_cubit.dart'; +import '../../data/repository/account_repository.dart'; +import '../../data/repository/authentication_repository.dart'; +import '../../data/repository/statistics_repository_impl.dart'; +import '../../data/repository/transaction_repository_stub.dart'; +import '../../data/service/app_secrets_provider.dart'; +import '../../data/service/supabase_service.dart'; +import '../../domain/repository/account_repository.dart'; +import '../../domain/repository/authentication_repository.dart'; +import '../../domain/repository/statistics_repository.dart'; +import '../../domain/validator/authentication_validator.dart'; +import '../../domain/repository/transaction_repository.dart'; +import '../../presentation/home/cubit/home_cubit.dart'; +import '../../presentation/income/cubit/add_income_cubit.dart'; +import '../../presentation/login/cubit/login_cubit.dart'; +import '../../presentation/statistics/cubit/statistics_cubit.dart'; final getIt = GetIt.instance; @@ -55,4 +57,17 @@ void initDI() { getIt.registerFactory( () => AddIncomeCubit(repository: getIt()), ); + + // Statistics + getIt.registerLazySingleton( + () => StatisticsRepositoryImpl( + supabaseService: getIt(), + ), + ); + + getIt.registerFactory( + () => StatisticsCubit( + repository: getIt(), + ), + ); } diff --git a/lib/core/l10n/app_ar.arb b/lib/core/l10n/app_ar.arb index 750407c..2516cf0 100644 --- a/lib/core/l10n/app_ar.arb +++ b/lib/core/l10n/app_ar.arb @@ -69,7 +69,27 @@ "auth_error_default": "حدث خطأ غير متوقع. يرجى المحاولة لاحقاً", "no_internet_connection": "لا يوجد اتصال بالانترنت", "currencyCode": "دينار", - "spendingTrend": "اتجاه الإنفاق", - "noDataAvailable": "لا توجد بيانات متاحة" + "noDataAvailable": "لا توجد بيانات متاحة", + "statistics": "الإحصائيات", + "monthly_overview": "نظرة عامة شهرية", + "savings_message": "لقد وفرت {amount} {currency} هذا الشهر", + "@savings_message": { + "placeholders": { + "amount": {}, + "currency": {} + } + }, + "highest_spending_message": "أعلى إنفاق لك كان في {date}", + "@highest_spending_message": { + "placeholders": { + "date": {} + } + }, + "no_statistics_title": "لا توجد بيانات كافية بعد", + "no_statistics_subtitle": "أضف المزيد من المعاملات لرؤية الإحصائيات", + "add_transaction": "إضافة معاملة", + "no_monthly_overview": "لم يتم تسجيل أي دخل أو مصروفات لهذه الفترة", + "error_loading_statistics": "فشل تحميل الإحصائيات", + "retry": "إعادة المحاولة" } diff --git a/lib/core/l10n/app_en.arb b/lib/core/l10n/app_en.arb index 2c439a9..0f73f63 100644 --- a/lib/core/l10n/app_en.arb +++ b/lib/core/l10n/app_en.arb @@ -108,5 +108,25 @@ "total": {} } }, - "monthly_overview" : "Monthly Overview" + "statistics": "Statistics", + "monthly_overview": "Monthly Overview", + "savings_message": "You saved {amount} {currency} this month", + "@savings_message": { + "placeholders": { + "amount": {}, + "currency": {} + } + }, + "highest_spending_message": "Your highest spending was on {date}", + "@highest_spending_message": { + "placeholders": { + "date": {} + } + }, + "no_statistics_title": "Not enough data yet", + "no_statistics_subtitle": "Add more transactions to see insights", + "add_transaction": "Add transaction", + "no_monthly_overview": "No income or expenses recorded for this period", + "error_loading_statistics": "Failed to load statistics", + "retry": "Retry" } \ No newline at end of file diff --git a/lib/core/utils/number_formatter.dart b/lib/core/utils/number_formatter.dart new file mode 100644 index 0000000..f4fc68e --- /dev/null +++ b/lib/core/utils/number_formatter.dart @@ -0,0 +1,15 @@ +class NumberFormatter { + NumberFormatter._(); + + static String formatWithCommas(double value) { + final intValue = value.toInt(); + return intValue.toString().replaceAllMapped( + RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), + (Match m) => '${m[1]},', + ); + } + + static String formatWithCurrency(double value, String currency) { + return '${formatWithCommas(value)} $currency'; + } +} diff --git a/lib/data/repository/statistics_repository_impl.dart b/lib/data/repository/statistics_repository_impl.dart new file mode 100644 index 0000000..e89bde7 --- /dev/null +++ b/lib/data/repository/statistics_repository_impl.dart @@ -0,0 +1,133 @@ + +import '../../data/service/supabase_service.dart'; +import '../../domain/entity/monthly_overview.dart'; +import '../../domain/repository/statistics_repository.dart'; + +class StatisticsRepositoryImpl implements StatisticsRepository { + final SupabaseService _supabaseService; + + StatisticsRepositoryImpl({required SupabaseService supabaseService}) + : _supabaseService = supabaseService; + + @override + Future getMonthlyOverview({required DateTime month}) async { + final client = await _supabaseService.getClient(); + // final userId = client.auth.currentUser?.id; + + final userId = "cdc4f388-4c4a-4ab4-84b1-5076fbe759c9"; + if (userId == null) { + return null; + } + + + // Get first and last day of the month + final startOfMonth = DateTime(month.year, month.month, 1); + final endOfMonth = DateTime(month.year, month.month + 1, 0, 23, 59, 59); + + // Query transactions for the month + final response = await client + .from('transactions') + .select(''' + amount, + transaction_type, + currency_id, + currencies!inner(abbreviation, abbreviation_ar) + ''') + .eq('user_id', userId) + .limit(100); + + /* .gte('created_at', startOfMonth.toIso8601String()) + .lte('created_at', endOfMonth.toIso8601String());*/ + + final transactions = response as List; + + print('Total transactions found: ${transactions.length}'); + + if (transactions.isEmpty) { + return null; + } + + // Calculate income and expenses + // Assuming transaction_type: 1 = income, 2 = expense (adjust based on your schema) + double totalIncome = 0; + double totalExpenses = 0; + String currency = 'IQD'; + + for (final transaction in transactions) { + final amount = (transaction['amount'] as num).toDouble(); + final type = transaction['transaction_type'] as int; + + // Get currency from first transaction + if (transaction['currencies'] != null) { + currency = transaction['currencies']['abbreviation'] ?? 'IQD'; + } + + if (type == 1) { + // Income + totalIncome += amount; + } else { + // Expense + totalExpenses += amount; + } + } + + // Calculate max value and scale labels dynamically + final maxAmount = totalIncome > totalExpenses ? totalIncome : totalExpenses; + final maxValue = _calculateMaxValue(maxAmount); + final scaleLabels = _generateScaleLabels(maxValue); + + return MonthlyOverview( + income: totalIncome, + expenses: totalExpenses, + currency: currency, + maxValue: maxValue, + scaleLabels: scaleLabels, + ); + } + + double _calculateMaxValue(double maxAmount) { + if (maxAmount <= 0) return 100000; + + // Round up to nearest significant value + final magnitude = maxAmount.toString().length - 1; + final base = _pow(10, magnitude).toDouble(); + return ((maxAmount / base).ceil() * base).toDouble(); + } + + int _pow(int base, int exponent) { + int result = 1; + for (int i = 0; i < exponent; i++) { + result *= base; + } + return result; + } + + List _generateScaleLabels(double maxValue) { + final step = maxValue / 8; + final labels = []; + + for (int i = 0; i <= 8; i++) { + final value = step * i; + labels.add(_formatScaleValue(value)); + } + + return labels; + } + + String _formatScaleValue(double value) { + if (value == 0) return '0'; + if (value >= 1000000) { + final millions = value / 1000000; + return millions == millions.truncate() + ? '${millions.truncate()}M' + : '${millions.toStringAsFixed(1)}M'; + } + if (value >= 1000) { + final thousands = value / 1000; + return thousands == thousands.truncate() + ? '${thousands.truncate()}K' + : '${thousands.toStringAsFixed(0)}K'; + } + return value.toStringAsFixed(0); + } +} \ No newline at end of file diff --git a/lib/design_system/constants/design_constants.dart b/lib/design_system/constants/design_constants.dart new file mode 100644 index 0000000..9780691 --- /dev/null +++ b/lib/design_system/constants/design_constants.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; + +class DesignConstants { + DesignConstants._(); + + // Border Radius + static const double radiusSmall = 8.0; + static const double radiusMedium = 12.0; + static const double radiusLarge = 20.0; + static const double radiusXLarge = 30.0; + + // Spacing + static const double spacingXSmall = 4.0; + static const double spacingSmall = 8.0; + static const double spacingMedium = 12.0; + static const double spacingLarge = 16.0; + static const double spacingXLarge = 24.0; + + // Icon Sizes + static const double iconSizeSmall = 14.0; + static const double iconSizeMedium = 16.0; + static const double iconSizeLarge = 28.0; + + // Component Sizes + static const double summaryIconContainerSize = 28.0; + static const double progressBarHeight = 20.0; + static const double savingsBannerHeight = 22.0; + static const double indicatorCircleSize = 6.0; + + // Progress Bar + static const double progressBarBorderWidth = 0.5; + static const double progressBarBackgroundBorderWidth = 1.0; + static const double progressBarMinWidth = 20.0; + + // Shadow + static const Offset progressBarShadowOffset = Offset(0, 4); + static const double progressBarShadowBlur = 8.0; + static const double progressBarShadowOpacity = 0.12; + static const int strokeAlpha = 10; +} + +class GradientColors { + GradientColors._(); + + static const Color incomeStart = Color(0xFF0496AD); + static const Color incomeEnd = Color(0xFF097C8E); + + static const Color expenseStart = Color(0xFFDC143C); + static const Color expenseEnd = Color(0xFFA01A35); + + static List get incomeGradient => [incomeStart, incomeEnd]; + + static List get expenseGradient => [expenseStart, expenseEnd]; +} diff --git a/lib/design_system/widgets/app_empty_view.dart b/lib/design_system/widgets/app_empty_view.dart new file mode 100644 index 0000000..f6ff288 --- /dev/null +++ b/lib/design_system/widgets/app_empty_view.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:moneyplus/design_system/assets/app_assets.dart'; +import 'package:moneyplus/design_system/theme/money_extension_context.dart'; + +class AppEmptyView extends StatelessWidget { + final String title; + final String subtitle; + final String? buttonText; + final VoidCallback? onButtonPressed; + + const AppEmptyView({ + super.key, + required this.title, + required this.subtitle, + this.buttonText, + this.onButtonPressed, + }); + + @override + Widget build(BuildContext context) { + return Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + // todo : AppAssets.imgStatisticsEmpty, + AppAssets.logo, + color: Colors.red, + width: 120, + height: 120, + ), + const SizedBox(height: 24), + Text( + title, + style: context.typography.title.medium.copyWith( + color: context.colors.title, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + subtitle, + style: context.typography.body.small.copyWith( + color: context.colors.body, + ), + textAlign: TextAlign.center, + ), + if (buttonText != null && onButtonPressed != null) ...[ + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: onButtonPressed, + style: ElevatedButton.styleFrom( + backgroundColor: context.colors.primary, + foregroundColor: context.colors.onPrimary, + padding: const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + ), + child: Text(buttonText!), + ), + ), + ], + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/design_system/widgets/app_error_view.dart b/lib/design_system/widgets/app_error_view.dart new file mode 100644 index 0000000..7112a9e --- /dev/null +++ b/lib/design_system/widgets/app_error_view.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:moneyplus/core/l10n/app_localizations.dart'; +import 'package:moneyplus/design_system/theme/money_extension_context.dart'; + +class AppErrorView extends StatelessWidget { + final String message; + final VoidCallback onRetry; + + const AppErrorView({ + super.key, + required this.message, + required this.onRetry, + }); + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + + return Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.error_outline, + size: 48, + color: context.colors.primary, + ), + const SizedBox(height: 16), + Text( + l10n.error_loading_statistics, + style: context.typography.title.small.copyWith( + color: context.colors.title, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + message, + style: context.typography.body.small.copyWith( + color: context.colors.body, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + ElevatedButton( + onPressed: onRetry, + child: Text(l10n.retry), + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/design_system/widgets/app_loading_indicator.dart b/lib/design_system/widgets/app_loading_indicator.dart new file mode 100644 index 0000000..4662ec1 --- /dev/null +++ b/lib/design_system/widgets/app_loading_indicator.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; +import 'package:moneyplus/design_system/theme/money_extension_context.dart'; + +class AppLoadingIndicator extends StatelessWidget { + const AppLoadingIndicator({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: CircularProgressIndicator( + color: context.colors.primary, + ), + ); + } +} \ No newline at end of file diff --git a/lib/domain/entity/monthly_overview.dart b/lib/domain/entity/monthly_overview.dart new file mode 100644 index 0000000..79d827c --- /dev/null +++ b/lib/domain/entity/monthly_overview.dart @@ -0,0 +1,29 @@ +class MonthlyOverview { + final double income; + final double expenses; + final String currency; + final double maxValue; + final List scaleLabels; + + const MonthlyOverview({ + required this.income, + required this.expenses, + required this.currency, + required this.maxValue, + required this.scaleLabels, + }); + + double get savings => income - expenses; + + bool get hasSavings => savings > 0; + + bool get isEmpty => income == 0 && expenses == 0; + + double get incomePercentage => maxValue > 0 + ? (income / maxValue).clamp(0.0, 1.0) + : 0.0; + + double get expensePercentage => maxValue > 0 + ? (expenses / maxValue).clamp(0.0, 1.0) + : 0.0; +} \ No newline at end of file diff --git a/lib/domain/repository/statistics_repository.dart b/lib/domain/repository/statistics_repository.dart new file mode 100644 index 0000000..b51c302 --- /dev/null +++ b/lib/domain/repository/statistics_repository.dart @@ -0,0 +1,5 @@ +import '../entity/monthly_overview.dart'; + +abstract class StatisticsRepository { + Future getMonthlyOverview({required DateTime month}); +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 613afd7..fc7f6a5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; -import 'package:moneyplus/di/injection.dart'; import 'package:moneyplus/money_app.dart'; +import 'core/di/injection.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); diff --git a/lib/presentation/account_setup/screen/account_setup_screen.dart b/lib/presentation/account_setup/screen/account_setup_screen.dart index a461d3d..3c46234 100644 --- a/lib/presentation/account_setup/screen/account_setup_screen.dart +++ b/lib/presentation/account_setup/screen/account_setup_screen.dart @@ -4,11 +4,10 @@ import 'package:flutter_svg/svg.dart'; import 'package:moneyplus/design_system/assets/app_assets.dart'; import 'package:moneyplus/design_system/widgets/app_bar.dart'; import 'package:moneyplus/presentation/account_setup/screen/page1.dart'; - +import '../../../core/di/injection.dart'; import '../../../core/l10n/app_localizations.dart'; import '../../../design_system/theme/money_extension_context.dart'; import '../../../design_system/widgets/buttons/button/default_button.dart'; -import '../../../di/injection.dart'; import '../cubit/account_setup_cubit.dart'; import '../cubit/account_setup_state.dart'; diff --git a/lib/presentation/forget_password/screen/forget_password_screen.dart b/lib/presentation/forget_password/screen/forget_password_screen.dart index fcce245..48f891c 100644 --- a/lib/presentation/forget_password/screen/forget_password_screen.dart +++ b/lib/presentation/forget_password/screen/forget_password_screen.dart @@ -5,12 +5,12 @@ import 'package:moneyplus/design_system/assets/app_assets.dart'; import 'package:moneyplus/design_system/widgets/app_bar.dart'; import 'package:moneyplus/design_system/widgets/app_logo.dart'; import 'package:moneyplus/design_system/widgets/text_field.dart'; -import 'package:moneyplus/di/injection.dart'; import 'package:moneyplus/domain/repository/authentication_repository.dart'; import 'package:moneyplus/presentation/forget_password/cubit/forget_password_cubit.dart'; import 'package:moneyplus/presentation/update_password/screen/update_password_screen.dart'; import 'package:svg_flutter/svg.dart'; +import '../../../core/di/injection.dart'; import '../../../design_system/theme/money_extension_context.dart'; import '../../../design_system/widgets/buttons/button/default_button.dart'; import '../cubit/forget_password_state.dart'; diff --git a/lib/presentation/home/screen/home_screen.dart b/lib/presentation/home/screen/home_screen.dart index ef3d828..817d1f1 100644 --- a/lib/presentation/home/screen/home_screen.dart +++ b/lib/presentation/home/screen/home_screen.dart @@ -5,16 +5,15 @@ import 'package:moneyplus/design_system/theme/money_colors.dart'; import 'package:moneyplus/design_system/theme/money_extension_context.dart'; import 'package:moneyplus/design_system/widgets/income_expense.dart'; import 'package:moneyplus/design_system/widgets/top_spending_card.dart'; -import 'package:moneyplus/di/injection.dart'; import 'package:moneyplus/presentation/home/cubit/home_cubit.dart'; import 'package:moneyplus/presentation/home/cubit/home_state.dart'; import 'package:moneyplus/presentation/home/widget/current_balance.dart'; +import '../../../core/di/injection.dart'; import '../../../design_system/widgets/buttons/button/varient_button.dart'; import '../../../design_system/widgets/buttons/secondary/sm_secondary_button.dart'; import '../../../design_system/widgets/custom_date_picker.dart'; import '../utils/StringFormattingHelpers.dart'; import '../widget/home_app_bar.dart'; -import 'package:month_year_picker/month_year_picker.dart'; class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); diff --git a/lib/presentation/income/screen/income_screen.dart b/lib/presentation/income/screen/income_screen.dart index 0f2e846..3d0da5c 100644 --- a/lib/presentation/income/screen/income_screen.dart +++ b/lib/presentation/income/screen/income_screen.dart @@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; import 'package:moneyplus/design_system/assets/app_assets.dart'; import 'package:moneyplus/domain/model/form_status.dart'; - +import '../../../core/di/injection.dart'; import '../../../core/l10n/app_localizations.dart'; import '../../../design_system/theme/money_extension_context.dart'; import '../../../design_system/widgets/app_bar.dart'; @@ -12,7 +12,6 @@ import '../../../design_system/widgets/chip.dart'; import '../../../design_system/widgets/snack_bar.dart'; import '../../../design_system/widgets/text_field.dart'; import '../../../design_system/widgets/text_field_date_Picker.dart'; -import '../../../di/injection.dart'; import '../cubit/add_income_cubit.dart'; import '../cubit/add_income_state.dart'; diff --git a/lib/presentation/navigation/routes.dart b/lib/presentation/navigation/routes.dart index 61b5152..15f770c 100644 --- a/lib/presentation/navigation/routes.dart +++ b/lib/presentation/navigation/routes.dart @@ -3,8 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:moneyplus/presentation/home/screen/home_screen.dart'; import 'package:moneyplus/presentation/login/screen/login_screen.dart'; -import '../../di/injection.dart'; +import '../../core/di/injection.dart'; import '../login/cubit/login_cubit.dart'; +import '../statistics/cubit/statistics_cubit.dart'; import '../statistics/statistics_screen.dart'; part 'routes.g.dart'; @@ -56,11 +57,14 @@ class HomeRoute extends GoRouteData with $HomeRoute { @TypedGoRoute(path: '/statistics') @immutable -class StatisticsRoute extends GoRouteData with $StatisticsRoute { +class StatisticsRoute extends GoRouteData with $StatisticsRoute { const StatisticsRoute(); @override Widget build(BuildContext context, GoRouterState state) { - return StatisticsScreen(); + return BlocProvider( + create: (_) => getIt(), + child: const StatisticsScreen(), + ); } } \ No newline at end of file diff --git a/lib/presentation/statistics/cubit/statistics_cubit.dart b/lib/presentation/statistics/cubit/statistics_cubit.dart new file mode 100644 index 0000000..80f4163 --- /dev/null +++ b/lib/presentation/statistics/cubit/statistics_cubit.dart @@ -0,0 +1,36 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../domain/repository/statistics_repository.dart'; +import 'statistics_state.dart'; + +class StatisticsCubit extends Cubit { + final StatisticsRepository _repository; + + StatisticsCubit({required StatisticsRepository repository}) + : _repository = repository, + super(const StatisticsIdle()); + + Future loadStatistics({DateTime? month}) async { + final selectedMonth = month ?? DateTime.now(); + + emit(const StatisticsLoading()); + + try { + final monthlyOverview = await _repository.getMonthlyOverview( + month: selectedMonth, + ); + + emit( + StatisticsSuccess( + monthlyOverview: monthlyOverview, + selectedMonth: selectedMonth, + ), + ); + } catch (e) { + emit(StatisticsFailure(e.toString())); + } + } + + void changeMonth(DateTime month) { + loadStatistics(month: month); + } +} diff --git a/lib/presentation/statistics/cubit/statistics_state.dart b/lib/presentation/statistics/cubit/statistics_state.dart new file mode 100644 index 0000000..5387a97 --- /dev/null +++ b/lib/presentation/statistics/cubit/statistics_state.dart @@ -0,0 +1,31 @@ +import '../../../domain/entity/monthly_overview.dart'; + +sealed class StatisticsState { + const StatisticsState(); +} + +class StatisticsIdle extends StatisticsState { + const StatisticsIdle(); +} + +class StatisticsLoading extends StatisticsState { + const StatisticsLoading(); +} + +class StatisticsSuccess extends StatisticsState { + final MonthlyOverview? monthlyOverview; + final DateTime selectedMonth; + + const StatisticsSuccess({ + required this.monthlyOverview, + required this.selectedMonth, + }); + + bool get hasNoData => monthlyOverview == null; +} + +class StatisticsFailure extends StatisticsState { + final String message; + + const StatisticsFailure(this.message); +} \ No newline at end of file diff --git a/lib/presentation/statistics/statistics_screen.dart b/lib/presentation/statistics/statistics_screen.dart index f2507d8..7c6d252 100644 --- a/lib/presentation/statistics/statistics_screen.dart +++ b/lib/presentation/statistics/statistics_screen.dart @@ -1,28 +1,79 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:moneyplus/core/l10n/app_localizations.dart'; +import 'package:moneyplus/design_system/theme/money_extension_context.dart'; +import 'package:moneyplus/design_system/widgets/app_empty_view.dart'; +import 'package:moneyplus/design_system/widgets/app_error_view.dart'; +import 'package:moneyplus/design_system/widgets/app_loading_indicator.dart'; +import 'cubit/statistics_cubit.dart'; +import 'cubit/statistics_state.dart'; +import 'widgets/monthly_overview/monthly_overview_section.dart'; -import 'widgets/monthly_overview/monthly_overview.dart'; - -class StatisticsScreen extends StatelessWidget { +class StatisticsScreen extends StatefulWidget { const StatisticsScreen({super.key}); + @override + State createState() => _StatisticsScreenState(); +} + +class _StatisticsScreenState extends State { + @override + void initState() { + super.initState(); + context.read().loadStatistics(); + } + + void _onAddTransaction() { + // Navigate to add transaction + } + + void _onRetry() { + context.read().loadStatistics(); + } + @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Colors.grey.shade100, - body: Padding( - padding: const EdgeInsets.all(16), - child: Column( - children: [ - SizedBox(height: 60), - MonthlyOverview( - income: 1500000, - expenses: 850000, - currency: 'IQD', - maxValue: 2000000, - ) - ], + backgroundColor: context.colors.surface, + body: SafeArea( + child: BlocBuilder( + builder: (context, state) { + return switch (state) { + StatisticsIdle() => const SizedBox.shrink(), + StatisticsLoading() => const AppLoadingIndicator(), + StatisticsSuccess() => _buildSuccess(context, state), + StatisticsFailure(:final message) => AppErrorView( + message: message, + onRetry: _onRetry, + ), + }; + }, ), ), ); } + + Widget _buildSuccess(BuildContext context, StatisticsSuccess state) { + final l10n = AppLocalizations.of(context)!; + + if (state.hasNoData) { + return AppEmptyView( + title: l10n.no_statistics_title, + subtitle: l10n.no_statistics_subtitle, + buttonText: l10n.add_transaction, + onButtonPressed: _onAddTransaction, + ); + } + + return SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + if (state.monthlyOverview != null) + MonthlyOverviewSection(overview: state.monthlyOverview!), + // Add other sections here + ], + ), + ); + } } \ No newline at end of file diff --git a/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart b/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart deleted file mode 100644 index 9e8a0ea..0000000 --- a/lib/presentation/statistics/widgets/monthly_overview/monthly_overview.dart +++ /dev/null @@ -1,108 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:moneyplus/design_system/theme/money_extension_context.dart'; -import '../../../../core/l10n/app_localizations.dart'; -import '../../../../design_system/assets/app_assets.dart'; -import 'progress_bar_section.dart'; -import 'savings_banner.dart'; -import 'scale_labels.dart'; -import 'summary_item.dart'; - -class MonthlyOverview extends StatelessWidget { - final double income; - final double expenses; - final String currency; - final double maxValue; - - const MonthlyOverview({ - super.key, - required this.income, - required this.expenses, - this.currency = 'IQD', - this.maxValue = 2000000, - }); - - double get savings => income - expenses; - - @override - Widget build(BuildContext context) { - final colors = context.colors; - final typography = context.typography; - final l10n = AppLocalizations.of(context)!; - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Main Container - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: colors.surfaceLow, - borderRadius: BorderRadius.all(Radius.circular(12)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - // Header - Text( - l10n.monthly_overview, - style: typography.label.medium.copyWith(color: colors.title), - ), - const SizedBox(height: 12), - - // Income & Expenses Items - Row( - children: [ - Expanded( - child: SummaryItem( - icon: SvgPicture.asset( - AppAssets.icWalletAdd, - width: 16, - height: 16, - ), - label: l10n.income, - value: income, - currency: currency, - isIncome: true, - ), - ), - const SizedBox(width: 24), - - Expanded( - child: SummaryItem( - icon: SvgPicture.asset( - AppAssets.icMoneyRemove, - width: 16, - height: 16, - ), - label: l10n.expense, - value: expenses, - currency: currency, - isIncome: false, - ), - ), - ], - ), - const SizedBox(height: 24), - // Progress Bars - ProgressBarSection( - income: income, - expenses: expenses, - maxValue: maxValue, - ), - - const SizedBox(height: 12), - - // Scale Labels - ScaleLabels(maxValue: maxValue), - ], - ), - ), - - // Savings Banner - Outside the main container, at the bottom - if (savings > 0) SavingsBanner(savings: savings, currency: currency), - ], - ); - } -} diff --git a/lib/presentation/statistics/widgets/monthly_overview/monthly_overview_section.dart b/lib/presentation/statistics/widgets/monthly_overview/monthly_overview_section.dart new file mode 100644 index 0000000..9e8c122 --- /dev/null +++ b/lib/presentation/statistics/widgets/monthly_overview/monthly_overview_section.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:moneyplus/core/l10n/app_localizations.dart'; +import 'package:moneyplus/design_system/assets/app_assets.dart'; +import 'package:moneyplus/design_system/constants/design_constants.dart'; +import 'package:moneyplus/design_system/theme/money_extension_context.dart'; +import '../../../../domain/entity/monthly_overview.dart'; +import '../section_empty_view.dart'; +import 'overview_progress_bar.dart'; +import 'overview_savings_banner.dart'; +import 'overview_scale_labels.dart'; +import 'summary_item.dart'; + +class MonthlyOverviewSection extends StatelessWidget { + final MonthlyOverview overview; + + const MonthlyOverviewSection({super.key, required this.overview}); + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + + if (overview.isEmpty) { + return SectionEmptyView( + title: l10n.monthly_overview, + message: l10n.no_monthly_overview, + ); + } + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _Content(overview: overview), + if (overview.hasSavings) + OverviewSavingsBanner( + savings: overview.savings, + currency: overview.currency, + ), + ], + ); + } +} + +class _Content extends StatelessWidget { + final MonthlyOverview overview; + + const _Content({required this.overview}); + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + + return Container( + padding: const EdgeInsets.all(DesignConstants.spacingMedium), + decoration: BoxDecoration( + color: context.colors.surfaceLow, + borderRadius: const BorderRadius.all( + Radius.circular(DesignConstants.radiusMedium), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + l10n.monthly_overview, + style: context.typography.label.medium.copyWith( + color: context.colors.title, + ), + ), + const SizedBox(height: DesignConstants.spacingMedium), + Row( + children: [ + Expanded( + child: SummaryItem( + icon: SvgPicture.asset( + AppAssets.icWalletAdd, + width: DesignConstants.iconSizeMedium, + height: DesignConstants.iconSizeMedium, + ), + label: l10n.income, + value: overview.income, + currency: overview.currency, + isIncome: true, + ), + ), + const SizedBox(width: DesignConstants.spacingXLarge), + Expanded( + child: SummaryItem( + icon: SvgPicture.asset( + AppAssets.icMoneyRemove, + width: DesignConstants.iconSizeMedium, + height: DesignConstants.iconSizeMedium, + ), + label: l10n.expense, + value: overview.expenses, + currency: overview.currency, + isIncome: false, + ), + ), + ], + ), + const SizedBox(height: DesignConstants.spacingXLarge), + OverviewProgressBar(overview: overview), + const SizedBox(height: DesignConstants.spacingMedium), + OverviewScaleLabels(labels: overview.scaleLabels), + ], + ), + ); + } +} diff --git a/lib/presentation/statistics/widgets/monthly_overview/overview_progress_bar.dart b/lib/presentation/statistics/widgets/monthly_overview/overview_progress_bar.dart new file mode 100644 index 0000000..2cd7454 --- /dev/null +++ b/lib/presentation/statistics/widgets/monthly_overview/overview_progress_bar.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; +import 'package:moneyplus/design_system/constants/design_constants.dart'; +import 'package:moneyplus/design_system/theme/money_extension_context.dart'; +import '../../../../domain/entity/monthly_overview.dart'; + +class OverviewProgressBar extends StatelessWidget { + final MonthlyOverview overview; + + const OverviewProgressBar({super.key, required this.overview}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + _ProgressBar( + percentage: overview.incomePercentage, + gradientColors: GradientColors.incomeGradient, + shadowColor: GradientColors.incomeStart, + ), + const SizedBox(height: DesignConstants.spacingSmall), + _ProgressBar( + percentage: overview.expensePercentage, + gradientColors: GradientColors.expenseGradient, + shadowColor: GradientColors.expenseStart, + ), + ], + ); + } +} + +class _ProgressBar extends StatelessWidget { + final double percentage; + final List gradientColors; + final Color shadowColor; + + const _ProgressBar({ + required this.percentage, + required this.gradientColors, + required this.shadowColor, + }); + + @override + Widget build(BuildContext context) { + final strokeColor = context.colors.stroke; + + return SizedBox( + height: DesignConstants.progressBarHeight, + child: LayoutBuilder( + builder: (context, constraints) { + final totalWidth = constraints.maxWidth; + final progressWidth = totalWidth * percentage; + + return Stack( + children: [ + + Container( + height: DesignConstants.progressBarHeight, + decoration: BoxDecoration( + color: strokeColor.withAlpha(DesignConstants.strokeAlpha), + borderRadius: BorderRadius.circular( + DesignConstants.radiusXLarge, + ), + border: Border.all( + color: strokeColor.withAlpha(DesignConstants.strokeAlpha), + width: DesignConstants.progressBarBackgroundBorderWidth, + ), + ), + ), + + if (progressWidth > 0) + Container( + width: progressWidth.clamp( + DesignConstants.progressBarMinWidth, + totalWidth, + ), + height: DesignConstants.progressBarHeight, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + DesignConstants.radiusXLarge, + ), + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: gradientColors, + ), + border: Border.all( + color: strokeColor.withAlpha(DesignConstants.strokeAlpha), + width: DesignConstants.progressBarBorderWidth, + ), + boxShadow: [ + BoxShadow( + color: shadowColor.withOpacity( + DesignConstants.progressBarShadowOpacity, + ), + offset: DesignConstants.progressBarShadowOffset, + blurRadius: DesignConstants.progressBarShadowBlur, + ), + ], + ), + ), + ], + ); + }, + ), + ); + } +} diff --git a/lib/presentation/statistics/widgets/monthly_overview/overview_savings_banner.dart b/lib/presentation/statistics/widgets/monthly_overview/overview_savings_banner.dart new file mode 100644 index 0000000..80adaa8 --- /dev/null +++ b/lib/presentation/statistics/widgets/monthly_overview/overview_savings_banner.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:moneyplus/core/l10n/app_localizations.dart'; +import 'package:moneyplus/core/utils/number_formatter.dart'; +import 'package:moneyplus/design_system/assets/app_assets.dart'; +import 'package:moneyplus/design_system/constants/design_constants.dart'; +import 'package:moneyplus/design_system/theme/money_extension_context.dart'; + +class OverviewSavingsBanner extends StatelessWidget { + final double savings; + final String currency; + + const OverviewSavingsBanner({ + super.key, + required this.savings, + required this.currency, + }); + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + + return Container( + height: DesignConstants.savingsBannerHeight, + padding: const EdgeInsets.symmetric( + horizontal: DesignConstants.spacingSmall, + ), + decoration: BoxDecoration( + color: context.colors.secondaryVariant, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(DesignConstants.radiusMedium), + bottomRight: Radius.circular(DesignConstants.radiusMedium), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + AppAssets.icWalletAdd, + width: DesignConstants.iconSizeSmall, + height: DesignConstants.iconSizeSmall, + colorFilter: ColorFilter.mode( + context.colors.secondary, + BlendMode.srcIn, + ), + ), + const SizedBox(width: DesignConstants.spacingXSmall), + Text( + l10n.savings_message( + NumberFormatter.formatWithCommas(savings), + currency, + ), + style: context.typography.label.xSmall?.copyWith( + color: context.colors.secondary, + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/statistics/widgets/monthly_overview/overview_scale_labels.dart b/lib/presentation/statistics/widgets/monthly_overview/overview_scale_labels.dart new file mode 100644 index 0000000..a393592 --- /dev/null +++ b/lib/presentation/statistics/widgets/monthly_overview/overview_scale_labels.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:moneyplus/design_system/theme/money_extension_context.dart'; + +class OverviewScaleLabels extends StatelessWidget { + final List labels; + + const OverviewScaleLabels({super.key, required this.labels}); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: labels + .map( + (label) => Text( + label, + style: context.typography.label.xSmall?.copyWith( + color: context.colors.body, + ), + ), + ) + .toList(), + ); + } +} diff --git a/lib/presentation/statistics/widgets/monthly_overview/progress_bar_section.dart b/lib/presentation/statistics/widgets/monthly_overview/progress_bar_section.dart deleted file mode 100644 index fa6d77e..0000000 --- a/lib/presentation/statistics/widgets/monthly_overview/progress_bar_section.dart +++ /dev/null @@ -1,109 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:moneyplus/design_system/theme/money_extension_context.dart'; - -class ProgressBarSection extends StatelessWidget { - final double income; - final double expenses; - final double maxValue; - - const ProgressBarSection({ - super.key, - required this.income, - required this.expenses, - required this.maxValue, - }); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - // Income Bar - _GradientProgressBar( - value: income, - maxValue: maxValue, - gradientColors: const [Color(0xFF0496AD), Color(0xFF097C8E)], - shadowColor: const Color(0xFF0496AD), - ), - const SizedBox(height: 8), - // Expenses Bar - _GradientProgressBar( - value: expenses, - maxValue: maxValue, - gradientColors: const [Color(0xFFDC143C), Color(0xFFA01A35)], - shadowColor: const Color(0xFFDC143C), - ), - ], - ); - } -} - -class _GradientProgressBar extends StatelessWidget { - final double value; - final double maxValue; - final List gradientColors; - final Color shadowColor; - - const _GradientProgressBar({ - required this.value, - required this.maxValue, - required this.gradientColors, - required this.shadowColor, - }); - - @override - Widget build(BuildContext context) { - final percentage = (value / maxValue).clamp(0.0, 1.0); - - return SizedBox( - height: 20, - child: LayoutBuilder( - builder: (context, constraints) { - final totalWidth = constraints.maxWidth; - final progressWidth = totalWidth * percentage; - - return Stack( - children: [ - Container( - height: 20, - decoration: BoxDecoration( - color: context.colors.stroke.withAlpha(10), - borderRadius: BorderRadius.circular(30), - border: Border.all( - color: context.colors.stroke.withAlpha(10), - width: 1, - ), - ), - ), - - if (progressWidth > 0) - Container( - width: progressWidth.clamp(20.0, totalWidth), - height: 20, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30), - gradient: LinearGradient( - begin: Alignment.centerLeft, - end: Alignment.centerRight, - colors: gradientColors, - ), - border: Border.all( - color: context.colors.stroke.withAlpha(10), - width: 0.5, - ), - boxShadow: [ - BoxShadow( - color: shadowColor.withAlpha(12), - offset: const Offset(0, 4), - blurRadius: 8, - spreadRadius: 0, - ), - ], - ), - ), - ], - ); - }, - ), - ); - } -} \ No newline at end of file diff --git a/lib/presentation/statistics/widgets/monthly_overview/savings_banner.dart b/lib/presentation/statistics/widgets/monthly_overview/savings_banner.dart deleted file mode 100644 index 13a2341..0000000 --- a/lib/presentation/statistics/widgets/monthly_overview/savings_banner.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:moneyplus/design_system/assets/app_assets.dart'; -import 'package:moneyplus/design_system/theme/money_extension_context.dart'; - -class SavingsBanner extends StatelessWidget { - final double savings; - final String currency; - - const SavingsBanner({ - super.key, - required this.savings, - required this.currency, - }); - - String _formatNumber(double value) { - final intValue = value.toInt(); - return intValue.toString().replaceAllMapped( - RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), - (Match m) => '${m[1]},', - ); - } - - @override - Widget build(BuildContext context) { - final colors = context.colors; - final typography = context.typography; - - return Container( - height: 22, - padding: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - color: colors.secondaryVariant, // #EAF3F4 - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(12), - bottomRight: Radius.circular(12), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - // Icon - You can replace with your specific icon - SvgPicture.asset( - AppAssets.icWalletAdd, // Replace with your savings icon if different - width: 14, - height: 14, - colorFilter: ColorFilter.mode( - colors.secondary, - BlendMode.srcIn, - ), - ), - const SizedBox(width: 4), - // Text - Label/XSmall, Secondary color - Text( - 'You saved ${_formatNumber(savings)} $currency this month', - style: typography.label.xSmall?.copyWith( - color: colors.secondary, - ), - ), - ], - ), - ); - } -} \ No newline at end of file diff --git a/lib/presentation/statistics/widgets/monthly_overview/scale_labels.dart b/lib/presentation/statistics/widgets/monthly_overview/scale_labels.dart deleted file mode 100644 index 4b7c7ae..0000000 --- a/lib/presentation/statistics/widgets/monthly_overview/scale_labels.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:moneyplus/design_system/theme/money_extension_context.dart'; - -class ScaleLabels extends StatelessWidget { - final double maxValue; - - const ScaleLabels({ - super.key, - required this.maxValue, - }); - - @override - Widget build(BuildContext context) { - final colors = context.colors; - final typography = context.typography; - - final labels = ['0', '15K', '50K', '150K', '300K', '600K', '1M', '1.5M', '2M']; - - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: labels.map((label) { - return Text( - label, - style: typography.label.xSmall?.copyWith( - color: colors.body, - ), - ); - }).toList(), - ); - } -} \ No newline at end of file diff --git a/lib/presentation/statistics/widgets/monthly_overview/summary_item.dart b/lib/presentation/statistics/widgets/monthly_overview/summary_item.dart index 9a7ac9e..cbbaf6d 100644 --- a/lib/presentation/statistics/widgets/monthly_overview/summary_item.dart +++ b/lib/presentation/statistics/widgets/monthly_overview/summary_item.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:moneyplus/core/utils/number_formatter.dart'; +import 'package:moneyplus/design_system/constants/design_constants.dart'; import 'package:moneyplus/design_system/theme/money_extension_context.dart'; class SummaryItem extends StatelessWidget { @@ -17,51 +19,37 @@ class SummaryItem extends StatelessWidget { required this.isIncome, }); - String _formatNumber(double value) { - final intValue = value.toInt(); - return intValue.toString().replaceAllMapped( - RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), - (Match m) => '${m[1]},', - ); - } - @override Widget build(BuildContext context) { - final colors = context.colors; - final typography = context.typography; - - // Linear gradient colors for the circle indicator - final List gradientColors = isIncome - ? [const Color(0xFF0496AD), const Color(0xFF097C8E)] - : [const Color(0xFFDC143C), const Color(0xFFA01A35)]; + final gradientColors = isIncome + ? GradientColors.incomeGradient + : GradientColors.expenseGradient; return Row( children: [ Container( - width: 28, - height: 28, + width: DesignConstants.summaryIconContainerSize, + height: DesignConstants.summaryIconContainerSize, decoration: BoxDecoration( - color: isIncome ? colors.secondaryVariant : colors.primaryVariant, - borderRadius: BorderRadius.circular(8), + color: isIncome + ? context.colors.secondaryVariant + : context.colors.primaryVariant, + borderRadius: BorderRadius.circular(DesignConstants.radiusSmall), ), alignment: Alignment.center, child: icon, ), - const SizedBox(width: 8), - - // Text Content + const SizedBox(width: DesignConstants.spacingSmall), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - // Label Row with gradient circle indicator Row( children: [ - // Gradient Circle Indicator Container( - width: 6, - height: 6, + width: DesignConstants.indicatorCircleSize, + height: DesignConstants.indicatorCircleSize, decoration: BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient( @@ -71,33 +59,32 @@ class SummaryItem extends StatelessWidget { ), ), ), - const SizedBox(width: 4), - // Label - Secondary/Primary color, Label/XSmall + const SizedBox(width: DesignConstants.spacingXSmall), Text( label, - style: typography.label.xSmall?.copyWith( - color: isIncome ? colors.secondary : colors.primary, + style: context.typography.label.xSmall?.copyWith( + color: isIncome + ? context.colors.secondary + : context.colors.primary, ), ), ], ), - - // Amount Row RichText( text: TextSpan( children: [ - // Sign (+/-) with specific color TextSpan( text: isIncome ? '+' : '-', - style: typography.label.medium.copyWith( - color: isIncome ? colors.green : colors.primary, + style: context.typography.label.medium.copyWith( + color: isIncome + ? context.colors.green + : context.colors.primary, ), ), - // Amount - Label/Medium with Title color TextSpan( - text: '${_formatNumber(value)} $currency', - style: typography.label.medium.copyWith( - color: colors.title, + text: NumberFormatter.formatWithCurrency(value, currency), + style: context.typography.label.medium.copyWith( + color: context.colors.title, ), ), ], diff --git a/lib/presentation/statistics/widgets/section_empty_view.dart b/lib/presentation/statistics/widgets/section_empty_view.dart new file mode 100644 index 0000000..a799f27 --- /dev/null +++ b/lib/presentation/statistics/widgets/section_empty_view.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:moneyplus/design_system/assets/app_assets.dart'; +import 'package:moneyplus/design_system/constants/design_constants.dart'; +import 'package:moneyplus/design_system/theme/money_extension_context.dart'; + +class SectionEmptyView extends StatelessWidget { + final String title; + final String message; + + const SectionEmptyView({ + super.key, + required this.title, + required this.message, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(DesignConstants.spacingMedium), + decoration: BoxDecoration( + color: context.colors.surfaceLow, + borderRadius: BorderRadius.circular(DesignConstants.radiusMedium), + ), + child: Column( + children: [ + Align( + alignment: AlignmentDirectional.centerStart, + child: Text( + title, + style: context.typography.label.medium.copyWith( + color: context.colors.title, + ), + ), + ), + const SizedBox(height: 24), + Image.asset( + // todo : AppAssets.imgStatisticsEmpty, + AppAssets.logo, + width: 80, + height: 80, + ), + const SizedBox(height: 16), + Text( + message, + style: context.typography.body.small.copyWith( + color: context.colors.body, + ), + textAlign: TextAlign.center, + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/presentation/update_password/screen/update_password_screen.dart b/lib/presentation/update_password/screen/update_password_screen.dart index 8c512bc..a6b7386 100644 --- a/lib/presentation/update_password/screen/update_password_screen.dart +++ b/lib/presentation/update_password/screen/update_password_screen.dart @@ -7,10 +7,9 @@ import 'package:moneyplus/design_system/widgets/app_bar.dart'; import 'package:moneyplus/design_system/widgets/app_logo.dart'; import 'package:moneyplus/design_system/widgets/snack_bar.dart'; import 'package:moneyplus/design_system/widgets/text_field.dart'; -import 'package:moneyplus/di/injection.dart'; import 'package:moneyplus/domain/repository/authentication_repository.dart'; import 'package:moneyplus/money_app.dart'; - +import '../../../core/di/injection.dart'; import '../../../design_system/widgets/buttons/button/default_button.dart'; import '../cubit/update_password_cubit.dart'; import '../cubit/update_password_state.dart'; From 6edf0277fc98469841940fcfb7223f3ccfa35623 Mon Sep 17 00:00:00 2001 From: abdo-essam Date: Wed, 11 Feb 2026 20:50:27 +0200 Subject: [PATCH 14/15] refactor(core): update DI and navigation routes Refactor dependency injection setup by adding TransactionCubit and updating imports. Update GoRouter generated routes with new paths for onboarding, login, main, and statistics screens. --- lib/core/di/injection.dart | 3 + lib/money_app.dart | 4 +- lib/presentation/navigation/routes.g.dart | 110 ++++++++++++++++++ .../screen/transactions_screen.dart | 2 +- 4 files changed, 116 insertions(+), 3 deletions(-) diff --git a/lib/core/di/injection.dart b/lib/core/di/injection.dart index fe0f9ac..63bd095 100644 --- a/lib/core/di/injection.dart +++ b/lib/core/di/injection.dart @@ -5,17 +5,20 @@ import '../../data/repository/account_repository.dart'; import '../../data/repository/authentication_repository.dart'; import '../../data/repository/statistics_repository_impl.dart'; import '../../data/repository/transaction_repository_stub.dart'; +import '../../data/repository/user_money_repository.dart'; import '../../data/service/app_secrets_provider.dart'; import '../../data/service/supabase_service.dart'; import '../../domain/repository/account_repository.dart'; import '../../domain/repository/authentication_repository.dart'; import '../../domain/repository/statistics_repository.dart'; +import '../../domain/repository/user_money_repository.dart'; import '../../domain/validator/authentication_validator.dart'; import '../../domain/repository/transaction_repository.dart'; import '../../presentation/home/cubit/home_cubit.dart'; import '../../presentation/income/cubit/add_income_cubit.dart'; import '../../presentation/login/cubit/login_cubit.dart'; import '../../presentation/statistics/cubit/statistics_cubit.dart'; +import '../../presentation/transactions/cubit/transaction_cubit.dart'; final getIt = GetIt.instance; diff --git a/lib/money_app.dart b/lib/money_app.dart index 9904dd4..3adb996 100644 --- a/lib/money_app.dart +++ b/lib/money_app.dart @@ -7,8 +7,8 @@ import 'package:moneyplus/presentation/navigation/routes.dart'; import 'core/l10n/app_localizations.dart'; final _router = GoRouter( - routes: $appRoutes, - initialLocation: '/statistics' + routes: $appRoutes, + initialLocation: '/statistics' ); diff --git a/lib/presentation/navigation/routes.g.dart b/lib/presentation/navigation/routes.g.dart index e69de29..1096931 100644 --- a/lib/presentation/navigation/routes.g.dart +++ b/lib/presentation/navigation/routes.g.dart @@ -0,0 +1,110 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'routes.dart'; + +// ************************************************************************** +// GoRouterGenerator +// ************************************************************************** + +List get $appRoutes => [ + $onBoardingRoute, + $loginRoute, + $mainRoute, + $statisticsRoute, +]; + +RouteBase get $onBoardingRoute => + GoRouteData.$route(path: '/', factory: $OnBoardingRoute._fromState); + +mixin $OnBoardingRoute on GoRouteData { + static OnBoardingRoute _fromState(GoRouterState state) => + const OnBoardingRoute(); + + @override + String get location => GoRouteData.$location('/'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +RouteBase get $loginRoute => + GoRouteData.$route(path: '/login', factory: $LoginRoute._fromState); + +mixin $LoginRoute on GoRouteData { + static LoginRoute _fromState(GoRouterState state) => const LoginRoute(); + + @override + String get location => GoRouteData.$location('/login'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +RouteBase get $mainRoute => + GoRouteData.$route(path: '/main', factory: $MainRoute._fromState); + +mixin $MainRoute on GoRouteData { + static MainRoute _fromState(GoRouterState state) => const MainRoute(); + + @override + String get location => GoRouteData.$location('/main'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +RouteBase get $statisticsRoute => GoRouteData.$route( + path: '/statistics', + factory: $StatisticsRoute._fromState, +); + +mixin $StatisticsRoute on GoRouteData { + static StatisticsRoute _fromState(GoRouterState state) => + const StatisticsRoute(); + + @override + String get location => GoRouteData.$location('/statistics'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} diff --git a/lib/presentation/transactions/screen/transactions_screen.dart b/lib/presentation/transactions/screen/transactions_screen.dart index d2d943e..b4e9733 100644 --- a/lib/presentation/transactions/screen/transactions_screen.dart +++ b/lib/presentation/transactions/screen/transactions_screen.dart @@ -3,7 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:moneyplus/core/l10n/app_localizations.dart'; import 'package:moneyplus/design_system/theme/money_extension_context.dart'; import 'package:moneyplus/design_system/widgets/snack_bar.dart'; -import 'package:moneyplus/di/injection.dart'; import 'package:moneyplus/presentation/transactions/cubit/transaction_cubit.dart'; import 'package:moneyplus/presentation/transactions/cubit/transaction_state.dart'; import 'package:moneyplus/presentation/transactions/widget/empty_transactions.dart'; @@ -11,6 +10,7 @@ import 'package:moneyplus/presentation/transactions/widget/loading_view.dart'; import 'package:moneyplus/presentation/transactions/widget/tabs_row.dart'; import 'package:moneyplus/presentation/transactions/widget/transaction_app_bar.dart'; import 'package:moneyplus/presentation/transactions/widget/transactions_list.dart'; +import '../../../core/di/injection.dart'; class TransactionsScreen extends StatelessWidget { const TransactionsScreen({super.key}); From 3b1666bad76e83aaf47983082ac7b3d115331dc6 Mon Sep 17 00:00:00 2001 From: abdo-essam Date: Wed, 11 Feb 2026 20:54:24 +0200 Subject: [PATCH 15/15] feat: add fake statistics repository for UI development Introduces a `FakeStatisticsRepository` to provide mock data for the monthly overview, facilitating UI development and testing. This fake repository is now registered in the dependency injection container instead of the real `StatisticsRepositoryImpl`. Changes include: - A new `FakeStatisticsRepository` that returns a hardcoded `MonthlyOverview` object. - The dependency injection setup now uses `FakeStatisticsRepository` for the `StatisticsRepository` singleton. - Minor cleanup of a `TODO` comment and an empty line in related files. --- lib/core/di/injection.dart | 12 +++++++++--- .../repository/fake_statistics_repository.dart | 18 ++++++++++++++++++ .../statistics/statistics_screen.dart | 2 +- lib/presentation/statistics/utils.dart | 3 +-- 4 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 lib/data/repository/fake_statistics_repository.dart diff --git a/lib/core/di/injection.dart b/lib/core/di/injection.dart index a34e314..a492ad2 100644 --- a/lib/core/di/injection.dart +++ b/lib/core/di/injection.dart @@ -4,6 +4,7 @@ import '../../data/repository/account_repository.dart'; import '../../data/repository/authentication_repository.dart'; import '../../data/repository/statistics_repository_impl.dart'; import '../../data/repository/transaction_repository_stub.dart'; +import '../../data/repository/fake_statistics_repository.dart'; import '../../data/service/app_secrets_provider.dart'; import '../../data/service/supabase_service.dart'; import '../../domain/repository/account_repository.dart'; @@ -59,11 +60,16 @@ void initDI() { ); // Statistics + // TODO: Remove this when done testing UI getIt.registerLazySingleton( - () => StatisticsRepositoryImpl( - supabaseService: getIt(), - ), + () => FakeStatisticsRepository(), ); + + // getIt.registerLazySingleton( + // () => StatisticsRepositoryImpl( + // supabaseService: getIt(), + // ), + // ); getIt.registerFactory( () => StatisticsCubit( diff --git a/lib/data/repository/fake_statistics_repository.dart b/lib/data/repository/fake_statistics_repository.dart new file mode 100644 index 0000000..6724f49 --- /dev/null +++ b/lib/data/repository/fake_statistics_repository.dart @@ -0,0 +1,18 @@ +import '../../domain/entity/monthly_overview.dart'; +import '../../domain/repository/statistics_repository.dart'; + +class FakeStatisticsRepository implements StatisticsRepository { + @override + Future getMonthlyOverview({required DateTime month}) async { + // Simulate network delay + await Future.delayed(const Duration(seconds: 1)); + + return const MonthlyOverview( + income: 5000.0, + expenses: 3500.0, + currency: "AED", + maxValue: 6000.0, + scaleLabels: ["0", "1k", "2k", "3k", "4k", "5k", "6k"], + ); + } +} diff --git a/lib/presentation/statistics/statistics_screen.dart b/lib/presentation/statistics/statistics_screen.dart index 7c6d252..ebdecc6 100644 --- a/lib/presentation/statistics/statistics_screen.dart +++ b/lib/presentation/statistics/statistics_screen.dart @@ -71,7 +71,7 @@ class _StatisticsScreenState extends State { children: [ if (state.monthlyOverview != null) MonthlyOverviewSection(overview: state.monthlyOverview!), - // Add other sections here + // TODO: Add other sections here ], ), ); diff --git a/lib/presentation/statistics/utils.dart b/lib/presentation/statistics/utils.dart index 9ace213..4725c0e 100644 --- a/lib/presentation/statistics/utils.dart +++ b/lib/presentation/statistics/utils.dart @@ -1,4 +1,3 @@ - String formatNumber(double value) { if (value >= 1000000) { return '${(value / 1000000).toStringAsFixed(1)}M'; @@ -6,4 +5,4 @@ String formatNumber(double value) { return '${(value / 1000).toStringAsFixed(0)},${(value % 1000).toStringAsFixed(0).padLeft(3, '0')}'; } return value.toStringAsFixed(0); -} \ No newline at end of file +}