From a03328e50adc348e8b6acdddb7dffb1af4737ecb Mon Sep 17 00:00:00 2001 From: yousef_osama11 Date: Sat, 28 Feb 2026 17:43:25 +0200 Subject: [PATCH 01/13] feat: add salary settings success and failure messages in localization files --- lib/core/l10n/app_ar.arb | 4 +++- lib/core/l10n/app_en.arb | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/core/l10n/app_ar.arb b/lib/core/l10n/app_ar.arb index cec88328..dc49867c 100644 --- a/lib/core/l10n/app_ar.arb +++ b/lib/core/l10n/app_ar.arb @@ -155,5 +155,7 @@ "categoriesFilterApply": "تطبيق", "categoriesBreakdown": "تفاصيل الفئات", "no_monthly_breakdown": "لا يوجد نفقات لتحليلها بعد", - "totalSpend": "إجمالي الإنفاق" + "totalSpend": "إجمالي الإنفاق", + "salary_saved": "تم حفظ إعدادات الراتب بنجاح", + "failed_to_save_salary": "فشل في حفظ إعدادات الراتب" } diff --git a/lib/core/l10n/app_en.arb b/lib/core/l10n/app_en.arb index 9c83c339..3b9a9863 100644 --- a/lib/core/l10n/app_en.arb +++ b/lib/core/l10n/app_en.arb @@ -171,5 +171,7 @@ "categoriesFilterApply": "Apply", "categoriesBreakdown": "Categories Breakdown", "no_monthly_breakdown": "No expenses to analyze yet", - "totalSpend": "Total Spend" + "totalSpend": "Total Spend", + "salary_saved": "Salary settings saved successfully", + "failed_to_save_salary": "Failed to save salary settings" } From cb9fd878ba8e415787a00616e4a2efd9cf0c7ed9 Mon Sep 17 00:00:00 2001 From: yousef_osama11 Date: Sat, 28 Feb 2026 17:43:34 +0200 Subject: [PATCH 02/13] feat: register EditSalaryCubit in dependency injection --- lib/core/di/cubit_di.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/core/di/cubit_di.dart b/lib/core/di/cubit_di.dart index dd5ad814..a750b96a 100644 --- a/lib/core/di/cubit_di.dart +++ b/lib/core/di/cubit_di.dart @@ -6,6 +6,7 @@ import 'package:moneyplus/domain/repository/user_money_repository.dart'; import 'package:moneyplus/domain/validator/authentication_validator.dart'; import 'package:moneyplus/presentation/account_setup/cubit/account_setup_cubit.dart'; import 'package:moneyplus/presentation/createAccount/cubit/create_account_cubit.dart'; +import 'package:moneyplus/presentation/edit_salary/edit_salary_cubit.dart'; import 'package:moneyplus/presentation/home/cubit/home_cubit.dart'; import 'package:moneyplus/presentation/income/cubit/add_income_cubit.dart'; import 'package:moneyplus/presentation/login/cubit/login_cubit.dart'; @@ -43,6 +44,7 @@ void initCubitDI() { userMoneyRepository: getIt(), ), ); + getIt.registerFactory( () => AddIncomeCubit( transactionRepository: getIt(), @@ -71,7 +73,12 @@ void initCubitDI() { getIt(), ), ); + getIt.registerFactory( - () => AccountCubit(getIt()), + () => AccountCubit(getIt()), + ); + + getIt.registerFactory( + () => EditSalaryCubit(userMoneyRepository: getIt()), ); } From 420f8b6a42545611faee08dafb1caccf1c12c735 Mon Sep 17 00:00:00 2001 From: yousef_osama11 Date: Sat, 28 Feb 2026 17:44:19 +0200 Subject: [PATCH 03/13] feat: add error content and loading indicator widgets --- lib/presentation/widgets/error_content.dart | 5 +++++ lib/presentation/widgets/loading_indicator.dart | 15 +++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 lib/presentation/widgets/error_content.dart create mode 100644 lib/presentation/widgets/loading_indicator.dart diff --git a/lib/presentation/widgets/error_content.dart b/lib/presentation/widgets/error_content.dart new file mode 100644 index 00000000..f4393b7d --- /dev/null +++ b/lib/presentation/widgets/error_content.dart @@ -0,0 +1,5 @@ +import 'package:flutter/material.dart'; + +Widget errorContent(String errorMsg) { + return Scaffold(body: Center(child: Text(errorMsg))); +} \ No newline at end of file diff --git a/lib/presentation/widgets/loading_indicator.dart b/lib/presentation/widgets/loading_indicator.dart new file mode 100644 index 00000000..d4d48233 --- /dev/null +++ b/lib/presentation/widgets/loading_indicator.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; +import '../../design_system/theme/money_colors.dart'; + +class LoadingIndicator extends StatelessWidget { + const LoadingIndicator({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: CircularProgressIndicator(color: MoneyColors.light.primary), + ), + ); + } +} From 131603d1ad7c4930dc57eec7a5b0b65c63b24edc Mon Sep 17 00:00:00 2001 From: yousef_osama11 Date: Sat, 28 Feb 2026 17:44:41 +0200 Subject: [PATCH 04/13] feat: implement salary retrieval and update methods in user_money_repository --- .../repository/user_money_repository.dart | 34 +++++++++++++++++++ .../repository/user_money_repository.dart | 8 +++++ 2 files changed, 42 insertions(+) diff --git a/lib/data/repository/user_money_repository.dart b/lib/data/repository/user_money_repository.dart index 4fab5672..1e843f07 100644 --- a/lib/data/repository/user_money_repository.dart +++ b/lib/data/repository/user_money_repository.dart @@ -139,4 +139,38 @@ class UserRepositoryImpl implements UserMoneyRepository { } } + @override + Future getSalary() async { + final client = await service.getClient(); + final response = await client.from('users').select('salary_amount'); + final balance = (response.firstOrNull?['salary_amount'] as num?)?.toDouble() ?? 0.0; + return balance; + } + + @override + Future getSalaryDay() async { + final client = await service.getClient(); + final response = await client.from('users').select('salary_day'); + final balance = (response.firstOrNull?['salary_day'] as int?)?.toInt() ?? 0; + return balance; + } + + @override + Future updateSalary(double salary) async { + final client = await service.getClient(); + await client + .from('users') + .update({'salary_amount': salary}) + .eq('id', client.auth.currentUser!.id); + } + + @override + Future updateSalaryDay(int salaryDay) async { + final client = await service.getClient(); + await client + .from('users') + .update({'salary_day': salaryDay}) + .eq('id', client.auth.currentUser!.id); + } + } diff --git a/lib/domain/repository/user_money_repository.dart b/lib/domain/repository/user_money_repository.dart index 34c70c50..fada9504 100644 --- a/lib/domain/repository/user_money_repository.dart +++ b/lib/domain/repository/user_money_repository.dart @@ -15,4 +15,12 @@ abstract class UserMoneyRepository { Future getCurrency(); Future getSavingSpendingPercentage(int month, int year); + + Future getSalary(); + + Future getSalaryDay(); + + Future updateSalary(double salary); + + Future updateSalaryDay(int salaryDay); } From 830fd697792bb335670a192949b018fbf34b4a3e Mon Sep 17 00:00:00 2001 From: yousef_osama11 Date: Sat, 28 Feb 2026 17:47:51 +0200 Subject: [PATCH 05/13] feat: add navigation to Edit Salary screen in account section --- lib/presentation/accout/screen/account_screen.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/presentation/accout/screen/account_screen.dart b/lib/presentation/accout/screen/account_screen.dart index b577c992..7c6fa82b 100644 --- a/lib/presentation/accout/screen/account_screen.dart +++ b/lib/presentation/accout/screen/account_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:moneyplus/presentation/navigation/routes.dart'; import '../../../core/di/injection.dart'; import '../../../core/l10n/app_localizations.dart'; @@ -93,6 +94,9 @@ class AccountScreen extends StatelessWidget { accountSection( title: l10n.salarySettings, iconPath: AppAssets.iconMoney, + onTap: () { + EditSalaryRoute().push(context); + }, ), accountSection( title: l10n.frequentlyAskedQuestion, From 6abf9f1cd7c706cfba550bfbbc52f1b02e78d695 Mon Sep 17 00:00:00 2001 From: yousef_osama11 Date: Sat, 28 Feb 2026 17:48:50 +0200 Subject: [PATCH 06/13] feat: add salary settings cubit and state --- lib/core/di/cubit_di.dart | 6 +- .../edit_salary/salary_settings_cubit.dart | 77 +++++++++++++++++++ .../edit_salary/salary_settings_state.dart | 36 +++++++++ 3 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 lib/presentation/edit_salary/salary_settings_cubit.dart create mode 100644 lib/presentation/edit_salary/salary_settings_state.dart diff --git a/lib/core/di/cubit_di.dart b/lib/core/di/cubit_di.dart index a750b96a..647c5465 100644 --- a/lib/core/di/cubit_di.dart +++ b/lib/core/di/cubit_di.dart @@ -6,7 +6,7 @@ import 'package:moneyplus/domain/repository/user_money_repository.dart'; import 'package:moneyplus/domain/validator/authentication_validator.dart'; import 'package:moneyplus/presentation/account_setup/cubit/account_setup_cubit.dart'; import 'package:moneyplus/presentation/createAccount/cubit/create_account_cubit.dart'; -import 'package:moneyplus/presentation/edit_salary/edit_salary_cubit.dart'; +import 'package:moneyplus/presentation/edit_salary/salary_settings_cubit.dart'; import 'package:moneyplus/presentation/home/cubit/home_cubit.dart'; import 'package:moneyplus/presentation/income/cubit/add_income_cubit.dart'; import 'package:moneyplus/presentation/login/cubit/login_cubit.dart'; @@ -78,7 +78,7 @@ void initCubitDI() { () => AccountCubit(getIt()), ); - getIt.registerFactory( - () => EditSalaryCubit(userMoneyRepository: getIt()), + getIt.registerFactory( + () => SalarySettingsCubit(userMoneyRepository: getIt()), ); } diff --git a/lib/presentation/edit_salary/salary_settings_cubit.dart b/lib/presentation/edit_salary/salary_settings_cubit.dart new file mode 100644 index 00000000..2b7e0b62 --- /dev/null +++ b/lib/presentation/edit_salary/salary_settings_cubit.dart @@ -0,0 +1,77 @@ +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:moneyplus/domain/repository/user_money_repository.dart'; +part 'salary_settings_state.dart'; + +class SalarySettingsCubit extends Cubit { + SalarySettingsCubit({required this.userMoneyRepository}) : super(EditSalaryLoading()); + + UserMoneyRepository userMoneyRepository; + + void getData() async { + try{ + final salary = await userMoneyRepository.getSalary(); + final salaryDay = await userMoneyRepository.getSalaryDay(); + emit( + EditSalaryLoaded( + salary: salary.toString(), + salaryDay: salaryDay.toString(), + isButtonEnabled: false, + ), + ); + }catch(e){ + emit(EditSalaryError("Failed to get data")); + } + _setButtonVisibility(); + } + + void updateSalary(String salary) { + if (state is EditSalaryLoaded) { + var currentState = state as EditSalaryLoaded; + emit(currentState.copyWith(salary: salary)); + _setButtonVisibility(); + } + } + + void updateSalaryDay(String salaryDay) { + if (state is EditSalaryLoaded) { + var currentState = state as EditSalaryLoaded; + emit(currentState.copyWith(salaryDay: salaryDay)); + _setButtonVisibility(); + } + } + + Future saveChanges() async { + try{ + if (state is EditSalaryLoaded) { + var currentState = state as EditSalaryLoaded; + await userMoneyRepository.updateSalary(double.parse(currentState.salary)); + await userMoneyRepository.updateSalaryDay(int.parse(currentState.salaryDay)); + } + return true; + }catch(e){ + return false; + } + + } + + + + void _setButtonVisibility() { + bool checkIfButtonShouldBeEnabled(EditSalaryLoaded state) { + final salary = double.tryParse(state.salary); + if (salary == null) return false; + final salaryDay = int.tryParse(state.salaryDay); + if (salaryDay == null) return false; + if(salaryDay < 1 || salaryDay > 31) return false; + if(salary < 0) return false; + return true; + } + + if (state is EditSalaryLoaded) { + var currentState = state as EditSalaryLoaded; + final shouldBeEnabled = checkIfButtonShouldBeEnabled(currentState); + emit(currentState.copyWith(isButtonEnabled: shouldBeEnabled)); + } + } +} diff --git a/lib/presentation/edit_salary/salary_settings_state.dart b/lib/presentation/edit_salary/salary_settings_state.dart new file mode 100644 index 00000000..2724b942 --- /dev/null +++ b/lib/presentation/edit_salary/salary_settings_state.dart @@ -0,0 +1,36 @@ +part of 'salary_settings_cubit.dart'; + +@immutable +sealed class SalarySettingsState {} + +final class EditSalaryLoading extends SalarySettingsState {} + +final class EditSalaryLoaded extends SalarySettingsState { + final String salary; + final String salaryDay; + final bool isButtonEnabled; + + EditSalaryLoaded({ + required this.salary, + required this.salaryDay, + required this.isButtonEnabled, + }); + + EditSalaryLoaded copyWith({ + String? salary, + String? salaryDay, + bool? isButtonEnabled, + }) { + return EditSalaryLoaded( + salary: salary ?? this.salary, + salaryDay: salaryDay ?? this.salaryDay, + isButtonEnabled: isButtonEnabled ?? this.isButtonEnabled, + ); + } +} + +final class EditSalaryError extends SalarySettingsState { + final String errorMessage; + + EditSalaryError(this.errorMessage); +} From efda8582828d4b24f884aa72bf98dcbd1b6a658a Mon Sep 17 00:00:00 2001 From: yousef_osama11 Date: Sat, 28 Feb 2026 17:49:01 +0200 Subject: [PATCH 07/13] feat: add salary settings screen --- .../edit_salary/salary_settings_screen.dart | 168 ++++++++++++++++++ lib/presentation/navigation/routes.dart | 12 ++ 2 files changed, 180 insertions(+) create mode 100644 lib/presentation/edit_salary/salary_settings_screen.dart diff --git a/lib/presentation/edit_salary/salary_settings_screen.dart b/lib/presentation/edit_salary/salary_settings_screen.dart new file mode 100644 index 00000000..a44e0501 --- /dev/null +++ b/lib/presentation/edit_salary/salary_settings_screen.dart @@ -0,0 +1,168 @@ +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/core/di/injection.dart'; +import 'package:moneyplus/design_system/assets/app_assets.dart'; +import 'package:moneyplus/design_system/theme/money_extension_context.dart'; +import 'package:moneyplus/design_system/widgets/app_bar.dart'; +import 'package:moneyplus/presentation/edit_salary/salary_settings_cubit.dart'; +import 'package:moneyplus/presentation/widgets/error_content.dart'; +import 'package:moneyplus/presentation/widgets/loading_indicator.dart'; +import 'package:svg_flutter/svg.dart'; + +import '../../design_system/widgets/buttons/button/default_button.dart'; +import '../../design_system/widgets/snack_bar.dart'; +import '../../design_system/widgets/text_field.dart'; + +class SalarySettingsScreen extends StatelessWidget { + const SalarySettingsScreen({super.key}); + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final localization = context.localizations; + return Scaffold( + appBar: CustomAppBar( + title: localization.salarySettings, + leading: _appBarLeading(context), + backgroundColor: colors.surfaceLow, + ), + body: BlocProvider( + create: (context) => getIt()..getData(), + child: BlocBuilder( + builder: (context, state) { + var content = switch (state) { + EditSalaryLoading() => LoadingIndicator(), + EditSalaryLoaded() => _loadedContent(context, state), + EditSalaryError() => errorContent(state.errorMessage), + }; + return content; + }, + ), + ), + ); + } +} + +Widget _loadedContent(BuildContext context, EditSalaryLoaded state) { + final colors = context.colors; + final cubit = context.read(); + + return Container( + color: colors.surface, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + _salaryBox(context, state, cubit), + SizedBox(height: 12), + _salaryDayBox(context, state, cubit), + Spacer(), + _saveButton(context, state, cubit), + ], + ), + ), + ); +} + +Widget _salaryBox( + BuildContext context, + EditSalaryLoaded state, + SalarySettingsCubit cubit, +) { + final localization = context.localizations; + return MTextField( + hint: localization.salary, + leading: Padding( + padding: const EdgeInsetsDirectional.only(top: 14, bottom: 14, end: 8), + child: SvgPicture.asset(AppAssets.iconMoney), + ), + keyboardType: TextInputType.number, + value: state.salary, + onChanged: (value) { + cubit.updateSalary(value); + }, + ); +} + +Widget _salaryDayBox( + BuildContext context, + EditSalaryLoaded state, + SalarySettingsCubit cubit, +) { + final localization = context.localizations; + return MTextField( + hint: localization.salaryDay, + leading: Padding( + padding: const EdgeInsetsDirectional.only(top: 14, bottom: 14, end: 8), + child: SvgPicture.asset(AppAssets.iconCalender), + ), + trailing: Padding( + padding: const EdgeInsetsDirectional.only(top: 14, bottom: 14, end: 8), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + color: context.colors.surface, + ), + child: Padding( + padding: const EdgeInsetsDirectional.symmetric( + vertical: 4, + horizontal: 8, + ), + child: Text( + localization.fromEachMonth, + style: context.typography.label.small.copyWith( + color: context.colors.body, + ), + ), + ), + ), + ), + keyboardType: TextInputType.number, + value: state.salaryDay, + onChanged: (value) { + cubit.updateSalaryDay(value); + }, + ); +} + +Widget _saveButton( + BuildContext context, + EditSalaryLoaded state, + SalarySettingsCubit cubit, +) { + final localization = context.localizations; + return DefaultButton( + text: localization.save, + isEnabled: state.isButtonEnabled, + onPressed: () { + cubit.saveChanges().then((success) { + if (success) { + MSnackBar.success( + message: context.localizations.salary_saved, + title: "Success", + ).showSnackBar(context: context); + context.pop(); + } else { + MSnackBar.error( + message: context.localizations.failed_to_save_salary, + title: "Error", + ).showSnackBar(context: context); + } + }); + }, + ); +} + +Widget _appBarLeading(BuildContext context) { + final colors = context.colors; + return GestureDetector( + onTap: () => context.pop(), + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration(color: colors.surface, shape: BoxShape.circle), + child: SvgPicture.asset(AppAssets.icArrowLeft), + ), + ); +} diff --git a/lib/presentation/navigation/routes.dart b/lib/presentation/navigation/routes.dart index 579a2f2d..a411f27b 100644 --- a/lib/presentation/navigation/routes.dart +++ b/lib/presentation/navigation/routes.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:moneyplus/presentation/createAccount/screen/create_account_screen.dart'; +import 'package:moneyplus/presentation/edit_salary/salary_settings_screen.dart'; import 'package:moneyplus/presentation/expense/screen/add_expense_screen.dart'; import 'package:moneyplus/presentation/login/screen/login_screen.dart'; import 'package:moneyplus/presentation/update_password/screen/update_password_screen.dart'; @@ -143,3 +144,14 @@ class AddExpenseRoute extends GoRouteData with $AddExpenseRoute { return const AddExpenseScreen(); } } + +@TypedGoRoute(path: '/edit-salary') +@immutable +class EditSalaryRoute extends GoRouteData with $EditSalaryRoute { + const EditSalaryRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) { + return SalarySettingsScreen(); + } +} From 0988e85e1b6863d712f98790fc183fe2cc1ed153 Mon Sep 17 00:00:00 2001 From: yousef_osama11 Date: Sun, 1 Mar 2026 21:16:41 +0200 Subject: [PATCH 08/13] feat: add error message for failed salary settings loading --- lib/core/l10n/app_ar.arb | 3 ++- lib/core/l10n/app_en.arb | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/core/l10n/app_ar.arb b/lib/core/l10n/app_ar.arb index dc49867c..8c1b3965 100644 --- a/lib/core/l10n/app_ar.arb +++ b/lib/core/l10n/app_ar.arb @@ -157,5 +157,6 @@ "no_monthly_breakdown": "لا يوجد نفقات لتحليلها بعد", "totalSpend": "إجمالي الإنفاق", "salary_saved": "تم حفظ إعدادات الراتب بنجاح", - "failed_to_save_salary": "فشل في حفظ إعدادات الراتب" + "failed_to_save_salary": "فشل في حفظ إعدادات الراتب", + "failed_to_load_salary_settings": "فشل في تحميل إعدادات الراتب" } diff --git a/lib/core/l10n/app_en.arb b/lib/core/l10n/app_en.arb index 3b9a9863..daf0610e 100644 --- a/lib/core/l10n/app_en.arb +++ b/lib/core/l10n/app_en.arb @@ -173,5 +173,6 @@ "no_monthly_breakdown": "No expenses to analyze yet", "totalSpend": "Total Spend", "salary_saved": "Salary settings saved successfully", - "failed_to_save_salary": "Failed to save salary settings" + "failed_to_save_salary": "Failed to save salary settings", + "failed_to_load_salary_settings": "Failed to load salary settings" } From da5ab89ac223caa468408edc520bd69558484061 Mon Sep 17 00:00:00 2001 From: yousef_osama11 Date: Sun, 1 Mar 2026 21:17:11 +0200 Subject: [PATCH 09/13] refactor: enhance error handling in salary settings with localized messages --- .../edit_salary/salary_settings_cubit.dart | 6 +++++- .../edit_salary/salary_settings_screen.dart | 20 +++++++++++++------ .../edit_salary/salary_settings_state.dart | 4 ++-- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/lib/presentation/edit_salary/salary_settings_cubit.dart b/lib/presentation/edit_salary/salary_settings_cubit.dart index 2b7e0b62..a5b9625e 100644 --- a/lib/presentation/edit_salary/salary_settings_cubit.dart +++ b/lib/presentation/edit_salary/salary_settings_cubit.dart @@ -20,7 +20,7 @@ class SalarySettingsCubit extends Cubit { ), ); }catch(e){ - emit(EditSalaryError("Failed to get data")); + emit(EditSalaryError(failure: SalarySettingsFailure.loadFailed)); } _setButtonVisibility(); } @@ -75,3 +75,7 @@ class SalarySettingsCubit extends Cubit { } } } + +enum SalarySettingsFailure { + loadFailed, +} diff --git a/lib/presentation/edit_salary/salary_settings_screen.dart b/lib/presentation/edit_salary/salary_settings_screen.dart index a44e0501..e9299bcc 100644 --- a/lib/presentation/edit_salary/salary_settings_screen.dart +++ b/lib/presentation/edit_salary/salary_settings_screen.dart @@ -35,7 +35,7 @@ class SalarySettingsScreen extends StatelessWidget { var content = switch (state) { EditSalaryLoading() => LoadingIndicator(), EditSalaryLoaded() => _loadedContent(context, state), - EditSalaryError() => errorContent(state.errorMessage), + EditSalaryError() => errorContent(_getErrorMessage(state.failure, context)), }; return content; }, @@ -54,9 +54,9 @@ Widget _loadedContent(BuildContext context, EditSalaryLoaded state) { child: Padding( padding: const EdgeInsets.all(16), child: Column( + spacing:12, children: [ _salaryBox(context, state, cubit), - SizedBox(height: 12), _salaryDayBox(context, state, cubit), Spacer(), _saveButton(context, state, cubit), @@ -140,14 +140,14 @@ Widget _saveButton( cubit.saveChanges().then((success) { if (success) { MSnackBar.success( - message: context.localizations.salary_saved, - title: "Success", + message: localization.salary_saved, + title: localization.success, ).showSnackBar(context: context); context.pop(); } else { MSnackBar.error( - message: context.localizations.failed_to_save_salary, - title: "Error", + message: localization.failed_to_save_salary, + title: localization.error, ).showSnackBar(context: context); } }); @@ -166,3 +166,11 @@ Widget _appBarLeading(BuildContext context) { ), ); } + +String _getErrorMessage(SalarySettingsFailure failure, BuildContext context) { + final localization = context.localizations; + switch (failure) { + case SalarySettingsFailure.loadFailed: + return localization.failed_to_load_salary_settings; + } +} diff --git a/lib/presentation/edit_salary/salary_settings_state.dart b/lib/presentation/edit_salary/salary_settings_state.dart index 2724b942..fdc2e8c7 100644 --- a/lib/presentation/edit_salary/salary_settings_state.dart +++ b/lib/presentation/edit_salary/salary_settings_state.dart @@ -30,7 +30,7 @@ final class EditSalaryLoaded extends SalarySettingsState { } final class EditSalaryError extends SalarySettingsState { - final String errorMessage; + final SalarySettingsFailure failure; - EditSalaryError(this.errorMessage); + EditSalaryError({required this.failure}); } From 252b32d5cb05aaf7df7421d3bcdc365fe53ef353 Mon Sep 17 00:00:00 2001 From: yousef_osama11 Date: Sun, 1 Mar 2026 21:17:17 +0200 Subject: [PATCH 10/13] feat: update loading indicator to use theme context for color --- lib/presentation/widgets/loading_indicator.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/presentation/widgets/loading_indicator.dart b/lib/presentation/widgets/loading_indicator.dart index d4d48233..f313ca61 100644 --- a/lib/presentation/widgets/loading_indicator.dart +++ b/lib/presentation/widgets/loading_indicator.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:moneyplus/design_system/theme/money_extension_context.dart'; import '../../design_system/theme/money_colors.dart'; class LoadingIndicator extends StatelessWidget { @@ -8,7 +9,7 @@ class LoadingIndicator extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( body: Center( - child: CircularProgressIndicator(color: MoneyColors.light.primary), + child: CircularProgressIndicator(color: context.colors.primary), ), ); } From edbc3d8baf0af676ee74af77ab2e7ab1a1588bee Mon Sep 17 00:00:00 2001 From: yousef_osama11 Date: Mon, 2 Mar 2026 12:14:55 +0200 Subject: [PATCH 11/13] refactor: rename salary settings states for clarity and consistency --- .../edit_salary/salary_settings_cubit.dart | 32 ++++++++++--------- .../edit_salary/salary_settings_screen.dart | 16 +++++----- .../edit_salary/salary_settings_state.dart | 20 ++++++------ 3 files changed, 35 insertions(+), 33 deletions(-) diff --git a/lib/presentation/edit_salary/salary_settings_cubit.dart b/lib/presentation/edit_salary/salary_settings_cubit.dart index a5b9625e..f1f0f36d 100644 --- a/lib/presentation/edit_salary/salary_settings_cubit.dart +++ b/lib/presentation/edit_salary/salary_settings_cubit.dart @@ -4,7 +4,7 @@ import 'package:moneyplus/domain/repository/user_money_repository.dart'; part 'salary_settings_state.dart'; class SalarySettingsCubit extends Cubit { - SalarySettingsCubit({required this.userMoneyRepository}) : super(EditSalaryLoading()); + SalarySettingsCubit({required this.userMoneyRepository}) : super(SalarySettingsLoading()); UserMoneyRepository userMoneyRepository; @@ -13,29 +13,29 @@ class SalarySettingsCubit extends Cubit { final salary = await userMoneyRepository.getSalary(); final salaryDay = await userMoneyRepository.getSalaryDay(); emit( - EditSalaryLoaded( + SalarySettingsLoaded( salary: salary.toString(), salaryDay: salaryDay.toString(), - isButtonEnabled: false, + isSaveButtonEnabled: false, ), ); }catch(e){ - emit(EditSalaryError(failure: SalarySettingsFailure.loadFailed)); + emit(SalarySettingsError(failure: SalarySettingsFailure.loadFailed)); } _setButtonVisibility(); } void updateSalary(String salary) { - if (state is EditSalaryLoaded) { - var currentState = state as EditSalaryLoaded; + if (state is SalarySettingsLoaded) { + var currentState = state as SalarySettingsLoaded; emit(currentState.copyWith(salary: salary)); _setButtonVisibility(); } } void updateSalaryDay(String salaryDay) { - if (state is EditSalaryLoaded) { - var currentState = state as EditSalaryLoaded; + if (state is SalarySettingsLoaded) { + var currentState = state as SalarySettingsLoaded; emit(currentState.copyWith(salaryDay: salaryDay)); _setButtonVisibility(); } @@ -43,10 +43,12 @@ class SalarySettingsCubit extends Cubit { Future saveChanges() async { try{ - if (state is EditSalaryLoaded) { - var currentState = state as EditSalaryLoaded; - await userMoneyRepository.updateSalary(double.parse(currentState.salary)); - await userMoneyRepository.updateSalaryDay(int.parse(currentState.salaryDay)); + if (state is SalarySettingsLoaded) { + var currentState = state as SalarySettingsLoaded; + await userMoneyRepository.updateSalarySettings( + salary: double.parse(currentState.salary), + salaryDay: int.parse(currentState.salaryDay), + ); } return true; }catch(e){ @@ -58,7 +60,7 @@ class SalarySettingsCubit extends Cubit { void _setButtonVisibility() { - bool checkIfButtonShouldBeEnabled(EditSalaryLoaded state) { + bool checkIfButtonShouldBeEnabled(SalarySettingsLoaded state) { final salary = double.tryParse(state.salary); if (salary == null) return false; final salaryDay = int.tryParse(state.salaryDay); @@ -68,8 +70,8 @@ class SalarySettingsCubit extends Cubit { return true; } - if (state is EditSalaryLoaded) { - var currentState = state as EditSalaryLoaded; + if (state is SalarySettingsLoaded) { + var currentState = state as SalarySettingsLoaded; final shouldBeEnabled = checkIfButtonShouldBeEnabled(currentState); emit(currentState.copyWith(isButtonEnabled: shouldBeEnabled)); } diff --git a/lib/presentation/edit_salary/salary_settings_screen.dart b/lib/presentation/edit_salary/salary_settings_screen.dart index e9299bcc..7b5afa75 100644 --- a/lib/presentation/edit_salary/salary_settings_screen.dart +++ b/lib/presentation/edit_salary/salary_settings_screen.dart @@ -33,9 +33,9 @@ class SalarySettingsScreen extends StatelessWidget { child: BlocBuilder( builder: (context, state) { var content = switch (state) { - EditSalaryLoading() => LoadingIndicator(), - EditSalaryLoaded() => _loadedContent(context, state), - EditSalaryError() => errorContent(_getErrorMessage(state.failure, context)), + SalarySettingsLoading() => LoadingIndicator(), + SalarySettingsLoaded() => _loadedContent(context, state), + SalarySettingsError() => errorContent(_getErrorMessage(state.failure, context)), }; return content; }, @@ -45,7 +45,7 @@ class SalarySettingsScreen extends StatelessWidget { } } -Widget _loadedContent(BuildContext context, EditSalaryLoaded state) { +Widget _loadedContent(BuildContext context, SalarySettingsLoaded state) { final colors = context.colors; final cubit = context.read(); @@ -68,7 +68,7 @@ Widget _loadedContent(BuildContext context, EditSalaryLoaded state) { Widget _salaryBox( BuildContext context, - EditSalaryLoaded state, + SalarySettingsLoaded state, SalarySettingsCubit cubit, ) { final localization = context.localizations; @@ -88,7 +88,7 @@ Widget _salaryBox( Widget _salaryDayBox( BuildContext context, - EditSalaryLoaded state, + SalarySettingsLoaded state, SalarySettingsCubit cubit, ) { final localization = context.localizations; @@ -129,13 +129,13 @@ Widget _salaryDayBox( Widget _saveButton( BuildContext context, - EditSalaryLoaded state, + SalarySettingsLoaded state, SalarySettingsCubit cubit, ) { final localization = context.localizations; return DefaultButton( text: localization.save, - isEnabled: state.isButtonEnabled, + isEnabled: state.isSaveButtonEnabled, onPressed: () { cubit.saveChanges().then((success) { if (success) { diff --git a/lib/presentation/edit_salary/salary_settings_state.dart b/lib/presentation/edit_salary/salary_settings_state.dart index fdc2e8c7..055e9aef 100644 --- a/lib/presentation/edit_salary/salary_settings_state.dart +++ b/lib/presentation/edit_salary/salary_settings_state.dart @@ -3,34 +3,34 @@ part of 'salary_settings_cubit.dart'; @immutable sealed class SalarySettingsState {} -final class EditSalaryLoading extends SalarySettingsState {} +final class SalarySettingsLoading extends SalarySettingsState {} -final class EditSalaryLoaded extends SalarySettingsState { +final class SalarySettingsLoaded extends SalarySettingsState { final String salary; final String salaryDay; - final bool isButtonEnabled; + final bool isSaveButtonEnabled; - EditSalaryLoaded({ + SalarySettingsLoaded({ required this.salary, required this.salaryDay, - required this.isButtonEnabled, + required this.isSaveButtonEnabled, }); - EditSalaryLoaded copyWith({ + SalarySettingsLoaded copyWith({ String? salary, String? salaryDay, bool? isButtonEnabled, }) { - return EditSalaryLoaded( + return SalarySettingsLoaded( salary: salary ?? this.salary, salaryDay: salaryDay ?? this.salaryDay, - isButtonEnabled: isButtonEnabled ?? this.isButtonEnabled, + isSaveButtonEnabled: isButtonEnabled ?? this.isSaveButtonEnabled, ); } } -final class EditSalaryError extends SalarySettingsState { +final class SalarySettingsError extends SalarySettingsState { final SalarySettingsFailure failure; - EditSalaryError({required this.failure}); + SalarySettingsError({required this.failure}); } From a9ce4ea40f4b3fcb3f771c53f57283c3f06730c5 Mon Sep 17 00:00:00 2001 From: yousef_osama11 Date: Mon, 2 Mar 2026 12:15:35 +0200 Subject: [PATCH 12/13] refactor: convert salary update methods to be one method --- .../repository/user_money_repository.dart | 33 ++++++++----------- .../repository/user_money_repository.dart | 14 +++++--- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/lib/data/repository/user_money_repository.dart b/lib/data/repository/user_money_repository.dart index 1e843f07..244a9238 100644 --- a/lib/data/repository/user_money_repository.dart +++ b/lib/data/repository/user_money_repository.dart @@ -81,7 +81,7 @@ class UserRepositoryImpl implements UserMoneyRepository { } List _getTopSpendingCategoriesFromResponseRows( - List rows, + List rows, ) { return rows.map((row) { final data = row as Map; @@ -100,9 +100,9 @@ class UserRepositoryImpl implements UserMoneyRepository { @override Future getCurrency() async { - final client = await service.getClient(); - final response = await client.rpc('get_default_currency'); - return Currency.fromJson(response); + final client = await service.getClient(); + final response = await client.rpc('get_default_currency'); + return Currency.fromJson(response); } @override @@ -133,12 +133,6 @@ class UserRepositoryImpl implements UserMoneyRepository { return ((currentMonthBalance - previousMonthBalance) / previousMonthBalance) * 100; } - void _validateMonth(int month){ - if(month < 1 || month > 12){ - throw Exception('Month value: "$month" is not valid, Month must be between 1 and 12'); - } - } - @override Future getSalary() async { final client = await service.getClient(); @@ -156,21 +150,22 @@ class UserRepositoryImpl implements UserMoneyRepository { } @override - Future updateSalary(double salary) async { + Future updateSalarySettings({ + required double salary, + required int salaryDay, + }) async { final client = await service.getClient(); + await client .from('users') - .update({'salary_amount': salary}) + .update({'salary_amount': salary, 'salary_day': salaryDay}) .eq('id', client.auth.currentUser!.id); } - @override - Future updateSalaryDay(int salaryDay) async { - final client = await service.getClient(); - await client - .from('users') - .update({'salary_day': salaryDay}) - .eq('id', client.auth.currentUser!.id); + void _validateMonth(int month){ + if(month < 1 || month > 12){ + throw Exception('Month value: "$month" is not valid, Month must be between 1 and 12'); + } } } diff --git a/lib/domain/repository/user_money_repository.dart b/lib/domain/repository/user_money_repository.dart index fada9504..9efbe094 100644 --- a/lib/domain/repository/user_money_repository.dart +++ b/lib/domain/repository/user_money_repository.dart @@ -9,8 +9,11 @@ abstract class UserMoneyRepository { Future getMonthExpense(int month, int year); - Future> getTopSpendingCategoriesInMonth( - {required int month,required int year, required int count}); + Future> getTopSpendingCategoriesInMonth({ + required int month, + required int year, + required int count, + }); Future getCurrency(); @@ -20,7 +23,8 @@ abstract class UserMoneyRepository { Future getSalaryDay(); - Future updateSalary(double salary); - - Future updateSalaryDay(int salaryDay); + Future updateSalarySettings({ + required double salary, + required int salaryDay, + }); } From 2b701a39d78a8b281ffc3fe4e9724d63fd6aecbc Mon Sep 17 00:00:00 2001 From: yousef_osama11 Date: Mon, 2 Mar 2026 12:19:06 +0200 Subject: [PATCH 13/13] pull develop --- lib/core/l10n/app_ar.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/l10n/app_ar.arb b/lib/core/l10n/app_ar.arb index 6739e78c..6abf2f6a 100644 --- a/lib/core/l10n/app_ar.arb +++ b/lib/core/l10n/app_ar.arb @@ -158,7 +158,7 @@ "totalSpend": "إجمالي الإنفاق", "salary_saved": "تم حفظ إعدادات الراتب بنجاح", "failed_to_save_salary": "فشل في حفظ إعدادات الراتب", - "failed_to_load_salary_settings": "فشل في تحميل إعدادات الراتب" + "failed_to_load_salary_settings": "فشل في تحميل إعدادات الراتب", "cancel": "إلغاء", "english": "الإنجليزية", "arabic": "العربية",