From 985a075e491f54ee85128f05b03176899e7ec80a Mon Sep 17 00:00:00 2001 From: mohamedshemees Date: Wed, 4 Feb 2026 15:43:31 +0200 Subject: [PATCH 01/16] feat: add categories breakdown entity and fromJson converter --- lib/domain/entity/categories_breakdown.dart | 36 +++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 lib/domain/entity/categories_breakdown.dart diff --git a/lib/domain/entity/categories_breakdown.dart b/lib/domain/entity/categories_breakdown.dart new file mode 100644 index 0000000..0f3f3b8 --- /dev/null +++ b/lib/domain/entity/categories_breakdown.dart @@ -0,0 +1,36 @@ + +class CategoriesBreakdown { + final List categories; + final double totalSpend; + + CategoriesBreakdown({ + required this.categories, + required this.totalSpend, + }); + + factory CategoriesBreakdown.fromJson(Map json) => CategoriesBreakdown( + categories: List.from(json["categories"].map((category) => BreakDownCategory.fromJson(category))), + totalSpend: json["total_spend"]?.toDouble(), + ); +} + +class BreakDownCategory { + final int? id; + final String name; + final double spend; + final double percentage; + + BreakDownCategory({ + this.id, + required this.name, + required this.spend, + required this.percentage, + }); + + factory BreakDownCategory.fromJson(Map json) => BreakDownCategory( + id: json["id"], + name: json["name"], + spend: json["spend"]?.toDouble(), + percentage: json["percentage"]?.toDouble(), + ); +} From 8bb42cbd0889e65077c058c3852028838e606d67 Mon Sep 17 00:00:00 2001 From: mohamedshemees Date: Wed, 4 Feb 2026 15:56:27 +0200 Subject: [PATCH 02/16] feat: add statistics repository to get categories breakdown --- .../repository/statistics_repository.dart | 35 +++++++++++++++++++ .../repository/statistics_repository.dart | 13 +++++++ 2 files changed, 48 insertions(+) create mode 100644 lib/data/repository/statistics_repository.dart create mode 100644 lib/domain/repository/statistics_repository.dart diff --git a/lib/data/repository/statistics_repository.dart b/lib/data/repository/statistics_repository.dart new file mode 100644 index 0000000..937ca2d --- /dev/null +++ b/lib/data/repository/statistics_repository.dart @@ -0,0 +1,35 @@ +import 'package:moneyplus/domain/entity/categories_breakdown.dart'; + +import '../../core/errors/error_model.dart'; +import '../../core/errors/result.dart'; +import '../../domain/repository/statistics_repository.dart'; +import '../service/app_secrets_provider.dart'; +import '../service/supabase_service.dart'; + +class StatisticsRepositoryImpl implements StatisticsRepository { + final SupabaseService supabaseService; + final AppSecretsProvider appSecrets; + + StatisticsRepositoryImpl({ + required this.supabaseService, + required this.appSecrets, + }); + + @override + Future> getCategoriesBreakDown( + DateTime date, + ) async { + try { + final client = await supabaseService.getClient(); + final data = await client.rpc( + 'get_categories_breakdown', + params: {'date': date.toIso8601String()}, + ); + return Result.success( + CategoriesBreakdown.fromJson(data), + ); + } catch (e) { + return Result.error(ErrorModel(e.toString())); + } + } +} diff --git a/lib/domain/repository/statistics_repository.dart b/lib/domain/repository/statistics_repository.dart new file mode 100644 index 0000000..60ac601 --- /dev/null +++ b/lib/domain/repository/statistics_repository.dart @@ -0,0 +1,13 @@ +import 'dart:ffi'; + +import 'package:moneyplus/domain/entity/categories_breakdown.dart'; +import 'package:moneyplus/domain/entity/transaction.dart'; +import 'package:moneyplus/domain/entity/transaction_category.dart'; +import 'package:moneyplus/domain/entity/transaction_type.dart'; +import 'package:moneyplus/domain/repository/model/top_spending_category.dart'; + +import '../../core/errors/result.dart'; + +abstract class StatisticsRepository { + Future> getCategoriesBreakDown(DateTime date); +} From 82be7e068b3f16caf8e2dd9fd96eb2264c443694 Mon Sep 17 00:00:00 2001 From: mohamedshemees Date: Wed, 4 Feb 2026 16:14:37 +0200 Subject: [PATCH 03/16] add:CategoryBreakdown widget skeleton for statistics screen --- .../statistics/widgets/CategoryBreakdown.dart | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 lib/presentation/statistics/widgets/CategoryBreakdown.dart diff --git a/lib/presentation/statistics/widgets/CategoryBreakdown.dart b/lib/presentation/statistics/widgets/CategoryBreakdown.dart new file mode 100644 index 0000000..8eba9e1 --- /dev/null +++ b/lib/presentation/statistics/widgets/CategoryBreakdown.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:moneyplus/domain/entity/categories_breakdown.dart'; + +class CategoryBreakdownWidget extends StatelessWidget { + final CategoriesBreakdown categoriesBreakdown; + + const CategoryBreakdownWidget({super.key, required this.categoriesBreakdown}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text("Categories Breakdown"), + LinearProgressIndicator(value: .5), + Text("Total Spend: ${categoriesBreakdown.totalSpend}"), + ListView.separated( + shrinkWrap: true, + itemCount: categoriesBreakdown.categories.length, + itemBuilder: (context, index) { + final category = categoriesBreakdown.categories[index]; + return _buildCategoryItem(category); + }, + separatorBuilder: (BuildContext context, int index) { + return Divider(color: Colors.grey, thickness: 1); + }, + ), + ], + ); + } + + Widget _buildCategoryItem(BreakDownCategory category) { + return ListTile( + title: Text(category.name), + subtitle: Text("Percentage: ${category.percentage}%"), + ); + } +} From b1ccefff217d484b0e31541a0951689ab296fe7e Mon Sep 17 00:00:00 2001 From: mohamedshemees Date: Wed, 4 Feb 2026 18:37:10 +0200 Subject: [PATCH 04/16] feat: add statistics screen and related logic --- .../repository/statistics_repository.dart | 7 ++-- lib/di/injection.dart | 8 +++++ lib/presentation/navigation/routes.dart | 18 +++++++++- lib/presentation/navigation/routes.g.dart | 33 ++++++++++++++++++- .../statistics/cubit/statistics_cubit.dart | 28 ++++++++++++++++ .../statistics/cubit/statistics_state.dart | 32 ++++++++++++++++++ .../statistics/screen/statistics_screen.dart | 31 +++++++++++++++++ 7 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 lib/presentation/statistics/cubit/statistics_cubit.dart create mode 100644 lib/presentation/statistics/cubit/statistics_state.dart create mode 100644 lib/presentation/statistics/screen/statistics_screen.dart diff --git a/lib/data/repository/statistics_repository.dart b/lib/data/repository/statistics_repository.dart index 937ca2d..43bd95c 100644 --- a/lib/data/repository/statistics_repository.dart +++ b/lib/data/repository/statistics_repository.dart @@ -8,11 +8,8 @@ import '../service/supabase_service.dart'; class StatisticsRepositoryImpl implements StatisticsRepository { final SupabaseService supabaseService; - final AppSecretsProvider appSecrets; - StatisticsRepositoryImpl({ required this.supabaseService, - required this.appSecrets, }); @override @@ -22,8 +19,8 @@ class StatisticsRepositoryImpl implements StatisticsRepository { try { final client = await supabaseService.getClient(); final data = await client.rpc( - 'get_categories_breakdown', - params: {'date': date.toIso8601String()}, + 'get_expenses_categories_breakdown', + params: {'in_year': 2026, 'in_month': 2}, ); return Result.success( CategoriesBreakdown.fromJson(data), diff --git a/lib/di/injection.dart b/lib/di/injection.dart index b8b605d..eb48e4b 100644 --- a/lib/di/injection.dart +++ b/lib/di/injection.dart @@ -1,12 +1,15 @@ import 'package:get_it/get_it.dart'; import 'package:moneyplus/presentation/account_setup/cubit/account_setup_cubit.dart'; +import 'package:moneyplus/presentation/statistics/cubit/statistics_cubit.dart'; import '../data/repository/account_repository.dart'; import '../data/repository/authentication_repository.dart'; +import '../data/repository/statistics_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/validator/authentication_validator.dart'; import '../presentation/home/cubit/home_cubit.dart'; import '../presentation/login/cubit/login_cubit.dart'; @@ -40,8 +43,13 @@ void initDI() { () => AccountRepositoryImpl(supabaseService: getIt() ) ); + getIt.registerLazySingleton( + () => StatisticsRepositoryImpl(supabaseService: getIt() + ) + ); getIt.registerLazySingleton(() => AccountSetupCubit(getIt())); getIt.registerFactory(() => HomeCubit()); + getIt.registerFactory(() => StatisticsCubit(statisticsRepository: getIt())); } diff --git a/lib/presentation/navigation/routes.dart b/lib/presentation/navigation/routes.dart index f0dc325..cbccc57 100644 --- a/lib/presentation/navigation/routes.dart +++ b/lib/presentation/navigation/routes.dart @@ -4,9 +4,11 @@ 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 'package:moneyplus/presentation/statistics/screen/statistics_screen.dart'; import '../../di/injection.dart'; import '../login/cubit/login_cubit.dart'; +import '../statistics/cubit/statistics_cubit.dart'; part 'routes.g.dart'; @@ -23,7 +25,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 +57,17 @@ 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 BlocProvider( + create: (context) => getIt()..getCategoriesBreakdown(DateTime.now()), + child: const StatisticsScreen(), + ); + } +} diff --git a/lib/presentation/navigation/routes.g.dart b/lib/presentation/navigation/routes.g.dart index 90f3bb2..ce81ddc 100644 --- a/lib/presentation/navigation/routes.g.dart +++ b/lib/presentation/navigation/routes.g.dart @@ -6,7 +6,12 @@ part of 'routes.dart'; // GoRouterGenerator // ************************************************************************** -List get $appRoutes => [$onBoardingRoute, $loginRoute, $homeRoute]; +List get $appRoutes => [ + $onBoardingRoute, + $loginRoute, + $homeRoute, + $statisticsRoute, +]; RouteBase get $onBoardingRoute => GoRouteData.$route(path: '/', factory: $OnBoardingRoute._fromState); @@ -77,3 +82,29 @@ mixin $HomeRoute on GoRouteData { @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/statistics/cubit/statistics_cubit.dart b/lib/presentation/statistics/cubit/statistics_cubit.dart new file mode 100644 index 0000000..99f5712 --- /dev/null +++ b/lib/presentation/statistics/cubit/statistics_cubit.dart @@ -0,0 +1,28 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../domain/repository/statistics_repository.dart'; +import 'statistics_state.dart'; + +class StatisticsCubit extends Cubit { + final StatisticsRepository statisticsRepository; + + StatisticsCubit({required this.statisticsRepository}) + : super(StatisticsState.initial()); + + Future getCategoriesBreakdown(DateTime date) async { + emit(state.copyWith(status: StatisticsStatus.loading)); + + final result = await statisticsRepository.getCategoriesBreakDown(date); + + result.when( + onSuccess: (categoriesBreakdown) { + emit(state.copyWith( + status: StatisticsStatus.success, + categoriesBreakdown: categoriesBreakdown, + )); + }, + onError: (error) { + emit(state.copyWith(status: StatisticsStatus.failure, error: error)); + }, + ); + } +} diff --git a/lib/presentation/statistics/cubit/statistics_state.dart b/lib/presentation/statistics/cubit/statistics_state.dart new file mode 100644 index 0000000..578222d --- /dev/null +++ b/lib/presentation/statistics/cubit/statistics_state.dart @@ -0,0 +1,32 @@ +import 'package:flutter/cupertino.dart'; +import '../../../core/errors/error_model.dart'; +import '../../../domain/entity/categories_breakdown.dart'; + +enum StatisticsStatus { initial, loading, success, failure } + +@immutable +class StatisticsState { + final StatisticsStatus status; + final ErrorModel? error; + final CategoriesBreakdown? categoriesBreakdown; + + const StatisticsState({ + required this.status, + this.error, + this.categoriesBreakdown, + }); + + factory StatisticsState.initial() => const StatisticsState(status: StatisticsStatus.initial); + + StatisticsState copyWith({ + StatisticsStatus? status, + ErrorModel? error, + CategoriesBreakdown? categoriesBreakdown, + }) { + return StatisticsState( + status: status ?? this.status, + error: error ?? this.error, + categoriesBreakdown: categoriesBreakdown ?? this.categoriesBreakdown, + ); + } +} diff --git a/lib/presentation/statistics/screen/statistics_screen.dart b/lib/presentation/statistics/screen/statistics_screen.dart new file mode 100644 index 0000000..552d927 --- /dev/null +++ b/lib/presentation/statistics/screen/statistics_screen.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../cubit/statistics_cubit.dart'; +import '../cubit/statistics_state.dart'; +import '../widgets/CategoryBreakdown.dart'; + +class StatisticsScreen extends StatelessWidget { + const StatisticsScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Statistics'), + ), + body: BlocBuilder( + builder: (context, state) { + if (state.status == StatisticsStatus.loading) { + return const Center(child: CircularProgressIndicator()); + } else if (state.status == StatisticsStatus.failure) { + return Center(child: Text(state.error?.message ?? 'An error occurred')); + } else if (state.status == StatisticsStatus.success && state.categoriesBreakdown != null) { + return CategoryBreakdownWidget(categoriesBreakdown: state.categoriesBreakdown!); + } else { + return const Center(child: Text('No data')); + } + }, + ), + ); + } +} From 02f0f0e29942515a05ba498c26c849bfcbaea403 Mon Sep 17 00:00:00 2001 From: mohamedshemees Date: Wed, 4 Feb 2026 23:15:27 +0200 Subject: [PATCH 05/16] feat: enhance CategoryBreakdown widget UI --- .../statistics/widgets/CategoryBreakdown.dart | 137 +++++++++++++++--- 1 file changed, 116 insertions(+), 21 deletions(-) diff --git a/lib/presentation/statistics/widgets/CategoryBreakdown.dart b/lib/presentation/statistics/widgets/CategoryBreakdown.dart index 8eba9e1..bb712a8 100644 --- a/lib/presentation/statistics/widgets/CategoryBreakdown.dart +++ b/lib/presentation/statistics/widgets/CategoryBreakdown.dart @@ -1,6 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; import 'package:moneyplus/domain/entity/categories_breakdown.dart'; +import '../../../core/l10n/app_localizations.dart'; +import '../../../design_system/theme/money_extension_context.dart'; + class CategoryBreakdownWidget extends StatelessWidget { final CategoriesBreakdown categoriesBreakdown; @@ -8,30 +12,121 @@ class CategoryBreakdownWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Column( - children: [ - Text("Categories Breakdown"), - LinearProgressIndicator(value: .5), - Text("Total Spend: ${categoriesBreakdown.totalSpend}"), - ListView.separated( - shrinkWrap: true, - itemCount: categoriesBreakdown.categories.length, - itemBuilder: (context, index) { - final category = categoriesBreakdown.categories[index]; - return _buildCategoryItem(category); - }, - separatorBuilder: (BuildContext context, int index) { - return Divider(color: Colors.grey, thickness: 1); - }, - ), - ], + final colors = context.colors; + final typography = context.typography; + final localizations = AppLocalizations.of(context)!; + final numberFormat = NumberFormat.decimalPattern(); + + final List colorPalette = [ + colors.primary, + Color(0xffE04967), + Color(0xffff7792), + Color(0xffffa7b9), + Color(0xffffcfd8), + ]; + + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: colors.surfaceLow, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 12, + children: [ + Text( + "Categories Breakdown", + style: typography.label.medium.copyWith(color: colors.title), + ), + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Row( + children: categoriesBreakdown.categories.asMap().entries.map(( + entry, + ) { + final index = entry.key; + final category = entry.value; + return Expanded( + flex: (category.percentage * 1000).toInt(), + child: Container( + height: 24, + margin: EdgeInsets.only( + right: index == categoriesBreakdown.categories.length - 1 + ? 0 + : 2, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2), + color: colorPalette[index % colorPalette.length], + ), + ), + ); + }).toList(), + ), + ), + Row( + spacing: 4, + children: [ + Text( + numberFormat.format(categoriesBreakdown.totalSpend), + style: typography.label.medium.copyWith(color: colors.title), + ), + Text( + "Total Spend", + style: typography.label.xSmall!.copyWith(color: colors.body), + ), + ], + ), + ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: categoriesBreakdown.categories.length, + itemBuilder: (context, index) { + final category = categoriesBreakdown.categories[index]; + final color = colorPalette[index % colorPalette.length]; + return _buildCategoryItem(context, category, color, numberFormat); + }, + separatorBuilder: (BuildContext context, int index) { + return Divider(color: colors.stroke, thickness: .5); + }, + ), + ], + ), ); } - Widget _buildCategoryItem(BreakDownCategory category) { - return ListTile( - title: Text(category.name), - subtitle: Text("Percentage: ${category.percentage}%"), + Widget _buildCategoryItem( + BuildContext context, + BreakDownCategory category, + Color color, + NumberFormat numberFormat, + ) { + final colors = context.colors; + final typography = context.typography; + return Row( + spacing: 4, + children: [ + Container( + width: 6, + height: 6, + decoration: BoxDecoration(color: color, shape: BoxShape.circle), + ), + Text( + "${(category.percentage).toStringAsFixed(0)}%", + textAlign: TextAlign.end, + style: typography.label.xSmall!.copyWith(color: colors.title), + ), + Expanded( + child: Text( + category.name, + style: typography.label.xSmall!.copyWith(color: colors.body), + ), + ), + Text("${numberFormat.format(category.spend)} EGP" + ,style: typography.label.small.copyWith(color: colors.title), + ), + ], ); } } From dc4ecdbced04838a263e7ae3245d9dba293f2e0f Mon Sep 17 00:00:00 2001 From: mohamedshemees Date: Wed, 18 Feb 2026 08:30:18 +0200 Subject: [PATCH 06/16] feature: Integrate categories breakdown into statistics screen --- lib/core/di/injection.dart | 1 - .../fake_statistics_repository.dart | 41 ------------- .../repository/statistics_repository.dart | 32 ----------- .../statistics_repository_impl.dart | 39 +++++++++---- .../repository/statistics_repository.dart | 2 +- .../main_container/screen/main_screen.dart | 3 +- .../statistics/cubit/statistics_cubit.dart | 57 +++++++++---------- .../statistics/cubit/statistics_state.dart | 8 ++- .../statistics/screen/statistics_screen.dart | 31 ---------- .../statistics/statistics_screen.dart | 36 ++++++++---- 10 files changed, 87 insertions(+), 163 deletions(-) delete mode 100644 lib/data/repository/fake_statistics_repository.dart delete mode 100644 lib/data/repository/statistics_repository.dart delete mode 100644 lib/presentation/statistics/screen/statistics_screen.dart diff --git a/lib/core/di/injection.dart b/lib/core/di/injection.dart index b7f9e12..a7eaea6 100644 --- a/lib/core/di/injection.dart +++ b/lib/core/di/injection.dart @@ -4,7 +4,6 @@ import 'package:moneyplus/presentation/account_setup/cubit/account_setup_cubit.d import 'package:moneyplus/presentation/transactions/cubit/transaction_cubit.dart'; import '../../data/repository/account_repository.dart'; import '../../data/repository/authentication_repository.dart'; -import '../../data/repository/fake_statistics_repository.dart'; import '../../data/repository/statistics_repository_impl.dart'; import '../../data/repository/transaction_repository_stub.dart'; import '../../data/repository/user_money_repository.dart'; diff --git a/lib/data/repository/fake_statistics_repository.dart b/lib/data/repository/fake_statistics_repository.dart deleted file mode 100644 index 6904411..0000000 --- a/lib/data/repository/fake_statistics_repository.dart +++ /dev/null @@ -1,41 +0,0 @@ -import '../../domain/entity/monthly_overview.dart'; -import '../../domain/repository/statistics_repository.dart'; - -class FakeStatisticsRepository implements StatisticsRepository { - final bool shouldFail; - final bool shouldReturnEmpty; - - FakeStatisticsRepository({ - this.shouldFail = false, - this.shouldReturnEmpty = false, - }); - - @override - Future getMonthlyOverview({required DateTime month}) async { - // Simulate network delay - await Future.delayed(const Duration(seconds: 1)); - - if (shouldFail) { - throw Exception('Simulated network error'); - } - - if (shouldReturnEmpty) { - return const MonthlyOverview( - income: 0, - expenses: 0, - currency: 'IQD', - maxValue: 100000, - scaleLabels: ['0', '12.5K', '25K', '37.5K', '50K', '62.5K', '75K', '87.5K', '100K'], - ); - } - - // Simulate successful response matching your UI design - return const MonthlyOverview( - income: 1500000, - expenses: 850000, - currency: 'IQD', - maxValue: 2000000, - scaleLabels: ['0', '250K', '500K', '750K', '1M', '1.25M', '1.5M', '1.75M', '2M'], - ); - } -} \ No newline at end of file diff --git a/lib/data/repository/statistics_repository.dart b/lib/data/repository/statistics_repository.dart deleted file mode 100644 index 43bd95c..0000000 --- a/lib/data/repository/statistics_repository.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:moneyplus/domain/entity/categories_breakdown.dart'; - -import '../../core/errors/error_model.dart'; -import '../../core/errors/result.dart'; -import '../../domain/repository/statistics_repository.dart'; -import '../service/app_secrets_provider.dart'; -import '../service/supabase_service.dart'; - -class StatisticsRepositoryImpl implements StatisticsRepository { - final SupabaseService supabaseService; - StatisticsRepositoryImpl({ - required this.supabaseService, - }); - - @override - Future> getCategoriesBreakDown( - DateTime date, - ) async { - try { - final client = await supabaseService.getClient(); - final data = await client.rpc( - 'get_expenses_categories_breakdown', - params: {'in_year': 2026, 'in_month': 2}, - ); - return Result.success( - CategoriesBreakdown.fromJson(data), - ); - } catch (e) { - return Result.error(ErrorModel(e.toString())); - } - } -} diff --git a/lib/data/repository/statistics_repository_impl.dart b/lib/data/repository/statistics_repository_impl.dart index 70cca75..49a2f6f 100644 --- a/lib/data/repository/statistics_repository_impl.dart +++ b/lib/data/repository/statistics_repository_impl.dart @@ -1,4 +1,7 @@ +import '../../core/errors/error_model.dart'; +import '../../core/errors/result.dart'; import '../../data/service/supabase_service.dart'; +import '../../domain/entity/categories_breakdown.dart'; import '../../domain/entity/monthly_overview.dart'; import '../../domain/repository/statistics_repository.dart'; @@ -6,35 +9,31 @@ class StatisticsRepositoryImpl implements StatisticsRepository { final SupabaseService _supabaseService; StatisticsRepositoryImpl({required SupabaseService supabaseService}) - : _supabaseService = supabaseService; + : _supabaseService = supabaseService; @override - Future getMonthlyOverview({required DateTime month}) async { + Future> getMonthlyOverview({required DateTime month}) async { try { final client = await _supabaseService.getClient(); final userId = client.auth.currentUser?.id; if (userId == null) { - return null; + return Result.error(ErrorModel('User not logged in')); } // Call RPC function final response = await client.rpc( 'get_monthly_overview', - params: { - 'in_year': month.year, - 'in_month': month.month, - }, + params: {'in_year': month.year, 'in_month': month.month}, ); - if (response == null) { - return _createEmptyOverview(); + return Result.success(_createEmptyOverview()); } - return _mapResponseToOverview(response as Map); + return Result.success(_mapResponseToOverview(response as Map)); } catch (e) { print('Error fetching monthly overview: $e'); - rethrow; + return Result.error(ErrorModel(e.toString())); } } @@ -112,4 +111,20 @@ class StatisticsRepositoryImpl implements StatisticsRepository { } return value.toStringAsFixed(0); } -} \ No newline at end of file + + @override + Future> getCategoriesBreakDown( + DateTime date, + ) async { + try { + final client = await _supabaseService.getClient(); + final data = await client.rpc( + 'get_expenses_categories_breakdown', + params: {'in_year': 2026, 'in_month': 2}, + ); + return Result.success(CategoriesBreakdown.fromJson(data)); + } catch (e) { + return Result.error(ErrorModel(e.toString())); + } + } +} diff --git a/lib/domain/repository/statistics_repository.dart b/lib/domain/repository/statistics_repository.dart index 76c5d7d..417a31a 100644 --- a/lib/domain/repository/statistics_repository.dart +++ b/lib/domain/repository/statistics_repository.dart @@ -5,6 +5,6 @@ import 'package:moneyplus/domain/entity/categories_breakdown.dart'; import '../../core/errors/result.dart'; abstract class StatisticsRepository { - Future getMonthlyOverview({required DateTime month}); + Future> getMonthlyOverview({required DateTime month}); Future> getCategoriesBreakDown(DateTime date); } diff --git a/lib/presentation/main_container/screen/main_screen.dart b/lib/presentation/main_container/screen/main_screen.dart index f34afbf..ced595b 100644 --- a/lib/presentation/main_container/screen/main_screen.dart +++ b/lib/presentation/main_container/screen/main_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:moneyplus/design_system/theme/money_extension_context.dart'; import 'package:moneyplus/presentation/main_container/cubit/main_state.dart'; +import 'package:moneyplus/presentation/statistics/statistics_screen.dart'; import 'package:moneyplus/presentation/transactions/screen/transactions_screen.dart'; import '../../../design_system/widgets/nav_bar.dart'; @@ -40,7 +41,7 @@ class MainScreen extends StatelessWidget { case NavBarTab.transaction: return const TransactionsScreen(); case NavBarTab.statistics: - return const TransactionsScreen(); //change to statistics + return const StatisticsScreen(); case NavBarTab.account: return const TransactionsScreen(); //change to account } diff --git a/lib/presentation/statistics/cubit/statistics_cubit.dart b/lib/presentation/statistics/cubit/statistics_cubit.dart index 3169d25..48992e4 100644 --- a/lib/presentation/statistics/cubit/statistics_cubit.dart +++ b/lib/presentation/statistics/cubit/statistics_cubit.dart @@ -14,40 +14,37 @@ class StatisticsCubit extends Cubit { 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); - } - Future getCategoriesBreakdown(DateTime date) async { - emit(state.copyWith(status: StatisticsStatus.loading)); - - final result = await statisticsRepository.getCategoriesBreakDown(date); + final monthlyOverviewResult = await _repository.getMonthlyOverview( + month: selectedMonth, + ); - result.when( - onSuccess: (categoriesBreakdown) { - emit(state.copyWith( - status: StatisticsStatus.success, - categoriesBreakdown: categoriesBreakdown, - )); + monthlyOverviewResult.when( + onSuccess: (monthlyOverview) async { + final categoriesBreakdownResult = + await _repository.getCategoriesBreakDown(selectedMonth); + + categoriesBreakdownResult.when( + onSuccess: (categoriesBreakdown) { + emit( + StatisticsSuccess( + monthlyOverview: monthlyOverview, + selectedMonth: selectedMonth, + categoriesBreakdown: categoriesBreakdown, + ), + ); + }, + onError: (error) { + emit(StatisticsFailure(error.message)); + }, + ); }, onError: (error) { - emit(state.copyWith(status: StatisticsStatus.failure, error: error)); + emit(StatisticsFailure(error.message)); }, ); } + + 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 index 5387a97..6017f38 100644 --- a/lib/presentation/statistics/cubit/statistics_state.dart +++ b/lib/presentation/statistics/cubit/statistics_state.dart @@ -1,3 +1,5 @@ +import 'package:moneyplus/domain/entity/categories_breakdown.dart'; + import '../../../domain/entity/monthly_overview.dart'; sealed class StatisticsState { @@ -15,17 +17,19 @@ class StatisticsLoading extends StatisticsState { class StatisticsSuccess extends StatisticsState { final MonthlyOverview? monthlyOverview; final DateTime selectedMonth; + final CategoriesBreakdown? categoriesBreakdown; const StatisticsSuccess({ required this.monthlyOverview, required this.selectedMonth, + required this.categoriesBreakdown, }); - bool get hasNoData => monthlyOverview == null; + bool get hasNoData => monthlyOverview == null && categoriesBreakdown == null; } class StatisticsFailure extends StatisticsState { final String message; const StatisticsFailure(this.message); -} \ No newline at end of file +} diff --git a/lib/presentation/statistics/screen/statistics_screen.dart b/lib/presentation/statistics/screen/statistics_screen.dart deleted file mode 100644 index 552d927..0000000 --- a/lib/presentation/statistics/screen/statistics_screen.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import '../cubit/statistics_cubit.dart'; -import '../cubit/statistics_state.dart'; -import '../widgets/CategoryBreakdown.dart'; - -class StatisticsScreen extends StatelessWidget { - const StatisticsScreen({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Statistics'), - ), - body: BlocBuilder( - builder: (context, state) { - if (state.status == StatisticsStatus.loading) { - return const Center(child: CircularProgressIndicator()); - } else if (state.status == StatisticsStatus.failure) { - return Center(child: Text(state.error?.message ?? 'An error occurred')); - } else if (state.status == StatisticsStatus.success && state.categoriesBreakdown != null) { - return CategoryBreakdownWidget(categoriesBreakdown: state.categoriesBreakdown!); - } else { - return const Center(child: Text('No data')); - } - }, - ), - ); - } -} diff --git a/lib/presentation/statistics/statistics_screen.dart b/lib/presentation/statistics/statistics_screen.dart index ebdecc6..4ad8656 100644 --- a/lib/presentation/statistics/statistics_screen.dart +++ b/lib/presentation/statistics/statistics_screen.dart @@ -1,28 +1,37 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:moneyplus/core/di/injection.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 'package:moneyplus/presentation/statistics/widgets/CategoryBreakdown.dart'; + import 'cubit/statistics_cubit.dart'; import 'cubit/statistics_state.dart'; import 'widgets/monthly_overview/monthly_overview_section.dart'; -class StatisticsScreen extends StatefulWidget { +class StatisticsScreen extends StatelessWidget { const StatisticsScreen({super.key}); @override - State createState() => _StatisticsScreenState(); + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => getIt()..loadStatistics(), + child: const StatisticsView(), + ); + } } -class _StatisticsScreenState extends State { +class StatisticsView extends StatefulWidget { + const StatisticsView({super.key}); + @override - void initState() { - super.initState(); - context.read().loadStatistics(); - } + State createState() => _StatisticsViewState(); +} +class _StatisticsViewState extends State { void _onAddTransaction() { // Navigate to add transaction } @@ -43,9 +52,9 @@ class _StatisticsScreenState extends State { StatisticsLoading() => const AppLoadingIndicator(), StatisticsSuccess() => _buildSuccess(context, state), StatisticsFailure(:final message) => AppErrorView( - message: message, - onRetry: _onRetry, - ), + message: message, + onRetry: _onRetry, + ), }; }, ), @@ -71,9 +80,12 @@ class _StatisticsScreenState extends State { children: [ if (state.monthlyOverview != null) MonthlyOverviewSection(overview: state.monthlyOverview!), - // TODO: Add other sections here + if (state.categoriesBreakdown != null) + CategoryBreakdownWidget( + categoriesBreakdown: state.categoriesBreakdown!, + ), ], ), ); } -} \ No newline at end of file +} From 055f525f4b4673747fcfa081ca15b9db5b2e06a5 Mon Sep 17 00:00:00 2001 From: mohamedshemees Date: Fri, 20 Feb 2026 01:40:59 +0200 Subject: [PATCH 07/16] fix: remove hard coded data from breakdown function --- lib/data/repository/statistics_repository_impl.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data/repository/statistics_repository_impl.dart b/lib/data/repository/statistics_repository_impl.dart index 49a2f6f..9ee7ba1 100644 --- a/lib/data/repository/statistics_repository_impl.dart +++ b/lib/data/repository/statistics_repository_impl.dart @@ -120,7 +120,7 @@ class StatisticsRepositoryImpl implements StatisticsRepository { final client = await _supabaseService.getClient(); final data = await client.rpc( 'get_expenses_categories_breakdown', - params: {'in_year': 2026, 'in_month': 2}, + params: {'in_year': date.year, 'in_month': date.month}, ); return Result.success(CategoriesBreakdown.fromJson(data)); } catch (e) { From fc936fa420d34cea5344b067f8567dcfcbea5e1f Mon Sep 17 00:00:00 2001 From: mohamedshemees Date: Fri, 20 Feb 2026 13:15:55 +0200 Subject: [PATCH 08/16] feat: add top bar and enhance state management --- lib/core/di/injection.dart | 5 ---- .../statistics_repository_impl.dart | 19 ++++++++++---- .../repository/statistics_repository.dart | 2 +- .../statistics/cubit/statistics_cubit.dart | 3 +-- .../statistics/cubit/statistics_state.dart | 6 ++--- .../statistics/statistics_screen.dart | 26 ++++++++++++++----- 6 files changed, 38 insertions(+), 23 deletions(-) diff --git a/lib/core/di/injection.dart b/lib/core/di/injection.dart index c076e5d..6f2dcd6 100644 --- a/lib/core/di/injection.dart +++ b/lib/core/di/injection.dart @@ -4,7 +4,6 @@ import 'package:moneyplus/presentation/account_setup/cubit/account_setup_cubit.d import 'package:moneyplus/presentation/transactions/cubit/transaction_cubit.dart'; import '../../data/repository/account_repository.dart'; import '../../data/repository/authentication_repository.dart'; -import '../../data/repository/fake_statistics_repository.dart'; import '../../data/repository/transaction_repository.dart'; import '../../data/repository/statistics_repository_impl.dart'; import '../../data/repository/user_money_repository.dart'; @@ -62,10 +61,6 @@ void initDI() { () => TransactionRepositoryImpl(service: getIt()), ); - getIt.registerLazySingleton( - () => AccountSetupCubit(getIt()), - ); - getIt.registerLazySingleton( () => AccountSetupCubit(getIt()), ); diff --git a/lib/data/repository/statistics_repository_impl.dart b/lib/data/repository/statistics_repository_impl.dart index 9ee7ba1..fbd90f7 100644 --- a/lib/data/repository/statistics_repository_impl.dart +++ b/lib/data/repository/statistics_repository_impl.dart @@ -12,7 +12,9 @@ class StatisticsRepositoryImpl implements StatisticsRepository { : _supabaseService = supabaseService; @override - Future> getMonthlyOverview({required DateTime month}) async { + Future> getMonthlyOverview({ + required DateTime month, + }) async { try { final client = await _supabaseService.getClient(); final userId = client.auth.currentUser?.id; @@ -30,7 +32,9 @@ class StatisticsRepositoryImpl implements StatisticsRepository { return Result.success(_createEmptyOverview()); } - return Result.success(_mapResponseToOverview(response as Map)); + return Result.success( + _mapResponseToOverview(response as Map), + ); } catch (e) { print('Error fetching monthly overview: $e'); return Result.error(ErrorModel(e.toString())); @@ -113,15 +117,20 @@ class StatisticsRepositoryImpl implements StatisticsRepository { } @override - Future> getCategoriesBreakDown( - DateTime date, - ) async { + Future> getCategoriesBreakDown({ + required DateTime date, + }) async { try { final client = await _supabaseService.getClient(); final data = await client.rpc( 'get_expenses_categories_breakdown', params: {'in_year': date.year, 'in_month': date.month}, ); + if (data == null) { + return Result.success( + CategoriesBreakdown(categories: [], totalSpend: 0.0), + ); + } return Result.success(CategoriesBreakdown.fromJson(data)); } catch (e) { return Result.error(ErrorModel(e.toString())); diff --git a/lib/domain/repository/statistics_repository.dart b/lib/domain/repository/statistics_repository.dart index 417a31a..16935f5 100644 --- a/lib/domain/repository/statistics_repository.dart +++ b/lib/domain/repository/statistics_repository.dart @@ -6,5 +6,5 @@ import '../../core/errors/result.dart'; abstract class StatisticsRepository { Future> getMonthlyOverview({required DateTime month}); - Future> getCategoriesBreakDown(DateTime date); + Future> getCategoriesBreakDown({required DateTime date}); } diff --git a/lib/presentation/statistics/cubit/statistics_cubit.dart b/lib/presentation/statistics/cubit/statistics_cubit.dart index 48992e4..fe96cdb 100644 --- a/lib/presentation/statistics/cubit/statistics_cubit.dart +++ b/lib/presentation/statistics/cubit/statistics_cubit.dart @@ -17,11 +17,10 @@ class StatisticsCubit extends Cubit { final monthlyOverviewResult = await _repository.getMonthlyOverview( month: selectedMonth, ); - monthlyOverviewResult.when( onSuccess: (monthlyOverview) async { final categoriesBreakdownResult = - await _repository.getCategoriesBreakDown(selectedMonth); + await _repository.getCategoriesBreakDown(date:selectedMonth); categoriesBreakdownResult.when( onSuccess: (categoriesBreakdown) { diff --git a/lib/presentation/statistics/cubit/statistics_state.dart b/lib/presentation/statistics/cubit/statistics_state.dart index 6017f38..a8869d9 100644 --- a/lib/presentation/statistics/cubit/statistics_state.dart +++ b/lib/presentation/statistics/cubit/statistics_state.dart @@ -15,9 +15,9 @@ class StatisticsLoading extends StatisticsState { } class StatisticsSuccess extends StatisticsState { - final MonthlyOverview? monthlyOverview; + final MonthlyOverview monthlyOverview; final DateTime selectedMonth; - final CategoriesBreakdown? categoriesBreakdown; + final CategoriesBreakdown categoriesBreakdown; const StatisticsSuccess({ required this.monthlyOverview, @@ -25,7 +25,7 @@ class StatisticsSuccess extends StatisticsState { required this.categoriesBreakdown, }); - bool get hasNoData => monthlyOverview == null && categoriesBreakdown == null; + bool get hasNoData => monthlyOverview.isEmpty && categoriesBreakdown.categories.isEmpty; } class StatisticsFailure extends StatisticsState { diff --git a/lib/presentation/statistics/statistics_screen.dart b/lib/presentation/statistics/statistics_screen.dart index 4ad8656..d3ef7ed 100644 --- a/lib/presentation/statistics/statistics_screen.dart +++ b/lib/presentation/statistics/statistics_screen.dart @@ -3,11 +3,13 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:moneyplus/core/di/injection.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_bar.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 'package:moneyplus/presentation/statistics/widgets/CategoryBreakdown.dart'; +import '../widgets/drop_down_date_dialog.dart'; import 'cubit/statistics_cubit.dart'; import 'cubit/statistics_state.dart'; import 'widgets/monthly_overview/monthly_overview_section.dart'; @@ -52,9 +54,9 @@ class _StatisticsViewState extends State { StatisticsLoading() => const AppLoadingIndicator(), StatisticsSuccess() => _buildSuccess(context, state), StatisticsFailure(:final message) => AppErrorView( - message: message, - onRetry: _onRetry, - ), + message: message, + onRetry: _onRetry, + ), }; }, ), @@ -78,11 +80,21 @@ class _StatisticsViewState extends State { padding: const EdgeInsets.all(16), child: Column( children: [ - if (state.monthlyOverview != null) - MonthlyOverviewSection(overview: state.monthlyOverview!), - if (state.categoriesBreakdown != null) + CustomAppBar( + title: "Statistics", + trailing: DropDownDateDialog( + onDatePick: (date) => { + context.read().changeMonth(date), + }, + year: state.selectedMonth.year, + month: state.selectedMonth.month, + ), + ), + if (!state.monthlyOverview.isEmpty) + MonthlyOverviewSection(overview: state.monthlyOverview), + if (state.categoriesBreakdown.categories.isNotEmpty) CategoryBreakdownWidget( - categoriesBreakdown: state.categoriesBreakdown!, + categoriesBreakdown: state.categoriesBreakdown, ), ], ), From 60735edc5659bc8bf3461eb3f7f87bb98ea9fb6c Mon Sep 17 00:00:00 2001 From: mohamedshemees Date: Fri, 20 Feb 2026 13:24:36 +0200 Subject: [PATCH 09/16] feat: add localization to statistics screen --- lib/core/l10n/app_ar.arb | 7 +++---- lib/core/l10n/app_en.arb | 9 ++++----- lib/presentation/statistics/statistics_screen.dart | 2 +- .../statistics/widgets/CategoryBreakdown.dart | 14 +++++++++----- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/lib/core/l10n/app_ar.arb b/lib/core/l10n/app_ar.arb index 063b7a0..601f708 100644 --- a/lib/core/l10n/app_ar.arb +++ b/lib/core/l10n/app_ar.arb @@ -98,7 +98,6 @@ "noDataAvailable": "لا توجد بيانات متاحة", "current_balance":"الرصيد الحالي", "how_much_money": "ما مقدار المال المتوفر لديك حاليًا؟", - "noDataAvailable": "لا توجد بيانات متاحة", "no_spending_categories": "لا يوجد معاملات متاحة", "month_january": "يناير", "month_february": "فبراير", @@ -114,7 +113,6 @@ "month_december": "ديسمبر", "no_transaction_record_title": "لا توجد سجلات معاملات", "no_transaction_record_content": "أضف أول معاملة للبدء", - "add_transaction": "إضافة معاملة", "all": "الكل", "incomes": "الإيرادات", "expenses": "المصروفات", @@ -129,7 +127,8 @@ "transaction_delete_fail" : "نم مسح المعاملة بنجاح", "add" : "اضافة", "transaction_details": "تفاصيل المعاملة", - "transaction_details": "Transaction details", "income_details": "تفاصيل الدخل", - "expense_details": "تفاصيل المصروف" + "expense_details": "تفاصيل المصروف", + "categoriesBreakdown": "تفاصيل الفئات", + "totalSpend": "إجمالي الإنفاق" } diff --git a/lib/core/l10n/app_en.arb b/lib/core/l10n/app_en.arb index 0337ac0..5af2a7e 100644 --- a/lib/core/l10n/app_en.arb +++ b/lib/core/l10n/app_en.arb @@ -21,10 +21,7 @@ "spendingTrend": "Spending Trend", "noDataAvailable": "No data available", "date": "Date", - "amount": "Amount", "addIncome": "Add income", - "note": "Note", - "add": "Add", "saving": "Saving...", "incomeAddedSuccessfully": "Income added successfully!", "failedToAddIncome": "Failed to add income", @@ -146,5 +143,7 @@ "add" : "Add", "transaction_details": "Transaction details", "income_details": "Income details", - "expense_details": "Expense details" -} + "expense_details": "Expense details", + "categoriesBreakdown": "Categories Breakdown", + "totalSpend": "Total Spend" +} \ No newline at end of file diff --git a/lib/presentation/statistics/statistics_screen.dart b/lib/presentation/statistics/statistics_screen.dart index d3ef7ed..feb76a9 100644 --- a/lib/presentation/statistics/statistics_screen.dart +++ b/lib/presentation/statistics/statistics_screen.dart @@ -81,7 +81,7 @@ class _StatisticsViewState extends State { child: Column( children: [ CustomAppBar( - title: "Statistics", + title: l10n.statistics, trailing: DropDownDateDialog( onDatePick: (date) => { context.read().changeMonth(date), diff --git a/lib/presentation/statistics/widgets/CategoryBreakdown.dart b/lib/presentation/statistics/widgets/CategoryBreakdown.dart index bb712a8..fc80796 100644 --- a/lib/presentation/statistics/widgets/CategoryBreakdown.dart +++ b/lib/presentation/statistics/widgets/CategoryBreakdown.dart @@ -36,13 +36,13 @@ class CategoryBreakdownWidget extends StatelessWidget { spacing: 12, children: [ Text( - "Categories Breakdown", + localizations.categoriesBreakdown, style: typography.label.medium.copyWith(color: colors.title), ), ClipRRect( borderRadius: BorderRadius.circular(8), child: Row( - children: categoriesBreakdown.categories.asMap().entries.map(( + children: categoriesBreakdown.categories.asMap().entries.map(( entry, ) { final index = entry.key; @@ -73,7 +73,7 @@ class CategoryBreakdownWidget extends StatelessWidget { style: typography.label.medium.copyWith(color: colors.title), ), Text( - "Total Spend", + localizations.totalSpend, style: typography.label.xSmall!.copyWith(color: colors.body), ), ], @@ -85,7 +85,7 @@ class CategoryBreakdownWidget extends StatelessWidget { itemBuilder: (context, index) { final category = categoriesBreakdown.categories[index]; final color = colorPalette[index % colorPalette.length]; - return _buildCategoryItem(context, category, color, numberFormat); + return _buildCategoryItem(context, category, color, numberFormat, localizations); }, separatorBuilder: (BuildContext context, int index) { return Divider(color: colors.stroke, thickness: .5); @@ -101,6 +101,7 @@ class CategoryBreakdownWidget extends StatelessWidget { BreakDownCategory category, Color color, NumberFormat numberFormat, + AppLocalizations localizations, ) { final colors = context.colors; final typography = context.typography; @@ -123,7 +124,10 @@ class CategoryBreakdownWidget extends StatelessWidget { style: typography.label.xSmall!.copyWith(color: colors.body), ), ), - Text("${numberFormat.format(category.spend)} EGP" + Text(localizations.moneyAmount( + numberFormat.format(category.spend), + localizations.currencyCode, + ) ,style: typography.label.small.copyWith(color: colors.title), ), ], From a4b16deb1973f60122279c8258e44acaeaf63731 Mon Sep 17 00:00:00 2001 From: mohamedshemees Date: Fri, 20 Feb 2026 13:45:04 +0200 Subject: [PATCH 10/16] feat: add empty state icon empty statitics card --- assets/images/img_no_analysis.png | Bin 0 -> 57315 bytes lib/core/l10n/app_ar.arb | 1 + lib/core/l10n/app_en.arb | 1 + lib/design_system/assets/app_assets.dart | 1 + .../statistics/statistics_screen.dart | 2 -- .../statistics/widgets/CategoryBreakdown.dart | 25 +++++++++++------- .../widgets/section_empty_view.dart | 3 +-- 7 files changed, 20 insertions(+), 13 deletions(-) create mode 100644 assets/images/img_no_analysis.png diff --git a/assets/images/img_no_analysis.png b/assets/images/img_no_analysis.png new file mode 100644 index 0000000000000000000000000000000000000000..9cd5fc7912af5bce0513456e90513a6b09b3ec55 GIT binary patch literal 57315 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 LlqG8=OoRUqMxMBO literal 0 HcmV?d00001 diff --git a/lib/core/l10n/app_ar.arb b/lib/core/l10n/app_ar.arb index 601f708..cbf4919 100644 --- a/lib/core/l10n/app_ar.arb +++ b/lib/core/l10n/app_ar.arb @@ -130,5 +130,6 @@ "income_details": "تفاصيل الدخل", "expense_details": "تفاصيل المصروف", "categoriesBreakdown": "تفاصيل الفئات", + "no_monthly_breakdown": "لا يوجد نفقات لتحليلها بعد", "totalSpend": "إجمالي الإنفاق" } diff --git a/lib/core/l10n/app_en.arb b/lib/core/l10n/app_en.arb index 5af2a7e..a0cc5a1 100644 --- a/lib/core/l10n/app_en.arb +++ b/lib/core/l10n/app_en.arb @@ -145,5 +145,6 @@ "income_details": "Income details", "expense_details": "Expense details", "categoriesBreakdown": "Categories Breakdown", + "no_monthly_breakdown": "No expenses to analyze yet", "totalSpend": "Total Spend" } \ No newline at end of file diff --git a/lib/design_system/assets/app_assets.dart b/lib/design_system/assets/app_assets.dart index 6b374ff..5b7c7be 100644 --- a/lib/design_system/assets/app_assets.dart +++ b/lib/design_system/assets/app_assets.dart @@ -67,4 +67,5 @@ class AppAssets { static const String icEmptyTransactionImage = '$_images/empty_transaction_image.png'; static const String icEmptyTransactionPattern = '$_icons/empty_transaction_pattern.svg'; static const String icFilter = "$_icons/ic_filter.svg"; + static const String imgNoAnalysis = "$_images/img_no_analysis.png"; } diff --git a/lib/presentation/statistics/statistics_screen.dart b/lib/presentation/statistics/statistics_screen.dart index feb76a9..2d14421 100644 --- a/lib/presentation/statistics/statistics_screen.dart +++ b/lib/presentation/statistics/statistics_screen.dart @@ -90,9 +90,7 @@ class _StatisticsViewState extends State { month: state.selectedMonth.month, ), ), - if (!state.monthlyOverview.isEmpty) MonthlyOverviewSection(overview: state.monthlyOverview), - if (state.categoriesBreakdown.categories.isNotEmpty) CategoryBreakdownWidget( categoriesBreakdown: state.categoriesBreakdown, ), diff --git a/lib/presentation/statistics/widgets/CategoryBreakdown.dart b/lib/presentation/statistics/widgets/CategoryBreakdown.dart index fc80796..6a7d6eb 100644 --- a/lib/presentation/statistics/widgets/CategoryBreakdown.dart +++ b/lib/presentation/statistics/widgets/CategoryBreakdown.dart @@ -1,10 +1,18 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:moneyplus/domain/entity/categories_breakdown.dart'; +import 'package:moneyplus/presentation/statistics/widgets/section_empty_view.dart'; import '../../../core/l10n/app_localizations.dart'; import '../../../design_system/theme/money_extension_context.dart'; +const List _colorPaletteBase = [ + Color(0xffE04967), + Color(0xffff7792), + Color(0xffffa7b9), + Color(0xffffcfd8), +]; + class CategoryBreakdownWidget extends StatelessWidget { final CategoriesBreakdown categoriesBreakdown; @@ -17,14 +25,13 @@ class CategoryBreakdownWidget extends StatelessWidget { final localizations = AppLocalizations.of(context)!; final numberFormat = NumberFormat.decimalPattern(); - final List colorPalette = [ - colors.primary, - Color(0xffE04967), - Color(0xffff7792), - Color(0xffffa7b9), - Color(0xffffcfd8), - ]; - + final colorPalette = [colors.primary, ..._colorPaletteBase]; + if (categoriesBreakdown.categories.isEmpty) { + return SectionEmptyView( + title: localizations.categoriesBreakdown, + message: localizations.no_monthly_breakdown, + ); + } return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( @@ -133,4 +140,4 @@ class CategoryBreakdownWidget extends StatelessWidget { ], ); } -} +} \ No newline at end of file diff --git a/lib/presentation/statistics/widgets/section_empty_view.dart b/lib/presentation/statistics/widgets/section_empty_view.dart index a799f27..7caa660 100644 --- a/lib/presentation/statistics/widgets/section_empty_view.dart +++ b/lib/presentation/statistics/widgets/section_empty_view.dart @@ -34,8 +34,7 @@ class SectionEmptyView extends StatelessWidget { ), const SizedBox(height: 24), Image.asset( - // todo : AppAssets.imgStatisticsEmpty, - AppAssets.logo, + AppAssets.imgNoAnalysis, width: 80, height: 80, ), From 29fc8b585c1bfa406419fa8021acb83db7a8a09e Mon Sep 17 00:00:00 2001 From: mohamedshemees Date: Wed, 25 Feb 2026 15:25:58 +0200 Subject: [PATCH 11/16] refactor: use DefaultButton and update image in AppEmptyView --- lib/design_system/widgets/app_empty_view.dart | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/lib/design_system/widgets/app_empty_view.dart b/lib/design_system/widgets/app_empty_view.dart index f6ff288..85ff9a8 100644 --- a/lib/design_system/widgets/app_empty_view.dart +++ b/lib/design_system/widgets/app_empty_view.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:moneyplus/design_system/assets/app_assets.dart'; import 'package:moneyplus/design_system/theme/money_extension_context.dart'; +import 'buttons/button/default_button.dart'; + class AppEmptyView extends StatelessWidget { final String title; final String subtitle; @@ -25,11 +27,9 @@ class AppEmptyView extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( - // todo : AppAssets.imgStatisticsEmpty, - AppAssets.logo, - color: Colors.red, - width: 120, - height: 120, + AppAssets.imgNoAnalysis, + width: 105, + height: 96, ), const SizedBox(height: 24), Text( @@ -50,18 +50,9 @@ class AppEmptyView extends StatelessWidget { if (buttonText != null && onButtonPressed != null) ...[ const SizedBox(height: 24), SizedBox( - width: double.infinity, - child: ElevatedButton( + child: DefaultButton( 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!), + text: buttonText!, ), ), ], @@ -70,4 +61,4 @@ class AppEmptyView extends StatelessWidget { ), ); } -} \ No newline at end of file +} From 262241e14b573d79a0a864a8542c5f6aadd6fc20 Mon Sep 17 00:00:00 2001 From: mohamedshemees Date: Wed, 25 Feb 2026 15:31:44 +0200 Subject: [PATCH 12/16] refactor: delete routs.g.dart --- lib/presentation/navigation/routes.g.dart | 277 ---------------------- 1 file changed, 277 deletions(-) delete mode 100644 lib/presentation/navigation/routes.g.dart diff --git a/lib/presentation/navigation/routes.g.dart b/lib/presentation/navigation/routes.g.dart deleted file mode 100644 index cb8c27c..0000000 --- a/lib/presentation/navigation/routes.g.dart +++ /dev/null @@ -1,277 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'routes.dart'; - -// ************************************************************************** -// GoRouterGenerator -// ************************************************************************** - -List get $appRoutes => [ - $onBoardingRoute, - $loginRoute, - $mainRoute, - $createAccountRoute, - $statisticsRoute, - $transactionDetailsRoute, - $forgetPasswordRoute, - $updatePasswordRoute, - $addIncomeRoute, - $addExpenseRoute, -]; - -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 $createAccountRoute => GoRouteData.$route( - path: '/createAccount', - factory: $CreateAccountRoute._fromState, -); - -mixin $CreateAccountRoute on GoRouteData { - static CreateAccountRoute _fromState(GoRouterState state) => - const CreateAccountRoute(); - - @override - String get location => GoRouteData.$location('/createAccount'); - - @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); -} - -RouteBase get $transactionDetailsRoute => GoRouteData.$route( - path: '/transaction_details', - factory: $TransactionDetailsRoute._fromState, -); - -mixin $TransactionDetailsRoute on GoRouteData { - static TransactionDetailsRoute _fromState(GoRouterState state) => - TransactionDetailsRoute(state.uri.queryParameters['transaction-id']!); - - TransactionDetailsRoute get _self => this as TransactionDetailsRoute; - - @override - String get location => GoRouteData.$location( - '/transaction_details', - queryParams: {'transaction-id': _self.transactionId}, - ); - - @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 $forgetPasswordRoute => GoRouteData.$route( - path: '/forget_password', - factory: $ForgetPasswordRoute._fromState, -); - -mixin $ForgetPasswordRoute on GoRouteData { - static ForgetPasswordRoute _fromState(GoRouterState state) => - const ForgetPasswordRoute(); - - @override - String get location => GoRouteData.$location('/forget_password'); - - @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 $updatePasswordRoute => GoRouteData.$route( - path: '/update_password', - factory: $UpdatePasswordRoute._fromState, -); - -mixin $UpdatePasswordRoute on GoRouteData { - static UpdatePasswordRoute _fromState(GoRouterState state) => - UpdatePasswordRoute(); - - @override - String get location => GoRouteData.$location('/update_password'); - - @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 $addIncomeRoute => GoRouteData.$route( - path: '/add-income', - factory: $AddIncomeRoute._fromState, -); - -mixin $AddIncomeRoute on GoRouteData { - static AddIncomeRoute _fromState(GoRouterState state) => - const AddIncomeRoute(); - - @override - String get location => GoRouteData.$location('/add-income'); - - @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 $addExpenseRoute => GoRouteData.$route( - path: '/add-expense', - factory: $AddExpenseRoute._fromState, -); - -mixin $AddExpenseRoute on GoRouteData { - static AddExpenseRoute _fromState(GoRouterState state) => - const AddExpenseRoute(); - - @override - String get location => GoRouteData.$location('/add-expense'); - - @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); -} From debcb5700623d51928b24b8133542c857b2aa3b1 Mon Sep 17 00:00:00 2001 From: mohamedshemees Date: Wed, 25 Feb 2026 16:47:36 +0200 Subject: [PATCH 13/16] fix: add missing comma in app_en.arb localization file --- lib/core/l10n/app_en.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/l10n/app_en.arb b/lib/core/l10n/app_en.arb index 5b2d3a0..82632c7 100644 --- a/lib/core/l10n/app_en.arb +++ b/lib/core/l10n/app_en.arb @@ -153,7 +153,7 @@ "expense_details": "Expense details", "whereDoYouUsuallySpendYourMoney": "Where do you usually spend your money?", "suggestions": "Suggestions:", - "selectedCategories": "Selected Categories:" + "selectedCategories": "Selected Categories:", "categoriesBreakdown": "Categories Breakdown", "no_monthly_breakdown": "No expenses to analyze yet", "totalSpend": "Total Spend" From 575a1b9ca7fa8fc226cdf3d97e8e1b0ea240e2f6 Mon Sep 17 00:00:00 2001 From: mohamedshemees Date: Wed, 25 Feb 2026 19:40:08 +0200 Subject: [PATCH 14/16] fix: add missing string --- lib/core/l10n/app_en.arb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/core/l10n/app_en.arb b/lib/core/l10n/app_en.arb index 82632c7..d24fcef 100644 --- a/lib/core/l10n/app_en.arb +++ b/lib/core/l10n/app_en.arb @@ -129,6 +129,7 @@ "category_name": "Category name", "transaction_delete_success" : "Transaction deleted successfully", "transaction_delete_fail" : "Failed to delete transaction", + "add" : "Add", "statistics": "Statistics", "savings_message": "You saved {amount} {currency} this month", "@savings_message": { @@ -157,4 +158,4 @@ "categoriesBreakdown": "Categories Breakdown", "no_monthly_breakdown": "No expenses to analyze yet", "totalSpend": "Total Spend" -} \ No newline at end of file +} From 664fd366886968b3e0767c61e55a3beb0661ccf4 Mon Sep 17 00:00:00 2001 From: mohamedshemees Date: Thu, 26 Feb 2026 22:00:12 +0200 Subject: [PATCH 15/16] fix : missed comma --- lib/core/l10n/app_ar.arb | 2 +- lib/core/l10n/app_en.arb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/l10n/app_ar.arb b/lib/core/l10n/app_ar.arb index 2ae0991..c605350 100644 --- a/lib/core/l10n/app_ar.arb +++ b/lib/core/l10n/app_ar.arb @@ -143,7 +143,7 @@ "continueButton": "متابعة", "categoriesFilterTitle": "تصفية الفئات", "categoriesFilterClear": "مسح", - "categoriesFilterApply": "تطبيق" + "categoriesFilterApply": "تطبيق", "categoriesBreakdown": "تفاصيل الفئات", "no_monthly_breakdown": "لا يوجد نفقات لتحليلها بعد", "totalSpend": "إجمالي الإنفاق" diff --git a/lib/core/l10n/app_en.arb b/lib/core/l10n/app_en.arb index da98628..81c8cc1 100644 --- a/lib/core/l10n/app_en.arb +++ b/lib/core/l10n/app_en.arb @@ -160,7 +160,7 @@ "continueButton": "Continue", "categoriesFilterTitle": "Categories filter", "categoriesFilterClear": "Clear", - "categoriesFilterApply": "Apply" + "categoriesFilterApply": "Apply", "categoriesBreakdown": "Categories Breakdown", "no_monthly_breakdown": "No expenses to analyze yet", "totalSpend": "Total Spend" From d2f9cf41a5d012d15d50bf32f292e0b242d80c92 Mon Sep 17 00:00:00 2001 From: mohamedshemees Date: Thu, 26 Feb 2026 22:08:24 +0200 Subject: [PATCH 16/16] refactor: update supabase_service import path in statistics_repository_impl --- lib/data/repository/statistics_repository_impl.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data/repository/statistics_repository_impl.dart b/lib/data/repository/statistics_repository_impl.dart index fbd90f7..41c2bf9 100644 --- a/lib/data/repository/statistics_repository_impl.dart +++ b/lib/data/repository/statistics_repository_impl.dart @@ -1,6 +1,6 @@ import '../../core/errors/error_model.dart'; import '../../core/errors/result.dart'; -import '../../data/service/supabase_service.dart'; +import '../../core/service/supabase_service.dart'; import '../../domain/entity/categories_breakdown.dart'; import '../../domain/entity/monthly_overview.dart'; import '../../domain/repository/statistics_repository.dart';