From 94bec413f80f3b6cc17e2e64882f93415b6cebb1 Mon Sep 17 00:00:00 2001 From: Mostafa Mohammed Date: Thu, 12 Feb 2026 14:37:57 +0300 Subject: [PATCH 1/5] feat(account-setup): implement category selection step - Add `categories` and `suggestions` to `AccountSetupState`. - Implement `toggleCategory` logic in `AccountSetupCubit`. - Create `Page2` for the account setup flow to allow users to select or add spending categories. - Introduce `SelectedCategoryItem` widget and `icon_cancel_category.svg` asset for the category selection UI. - Update `AccountSetupScreen` to support multi-page navigation and validation for the finish button. --- assets/icons/icon_cancel_category.svg | 3 + lib/design_system/assets/app_assets.dart | 1 + .../widgets/selected_category_item.dart | 57 ++++++++++ .../cubit/account_setup_cubit.dart | 18 +++- .../cubit/account_setup_state.dart | 28 ++++- .../screen/account_setup_screen.dart | 25 +++-- .../account_setup/screen/page2.dart | 101 ++++++++++++++++++ 7 files changed, 212 insertions(+), 21 deletions(-) create mode 100644 assets/icons/icon_cancel_category.svg create mode 100644 lib/design_system/widgets/selected_category_item.dart create mode 100644 lib/presentation/account_setup/screen/page2.dart diff --git a/assets/icons/icon_cancel_category.svg b/assets/icons/icon_cancel_category.svg new file mode 100644 index 00000000..02620d3f --- /dev/null +++ b/assets/icons/icon_cancel_category.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/design_system/assets/app_assets.dart b/lib/design_system/assets/app_assets.dart index 82c2f6cf..8eaeaac9 100644 --- a/lib/design_system/assets/app_assets.dart +++ b/lib/design_system/assets/app_assets.dart @@ -8,6 +8,7 @@ class AppAssets { static const icArrowUp = '$_icons/ic_arrow_up.svg'; static const icArrowRight ='$_icons/ic_arrow_right.svg'; static const String iconCancel = "$_icons/ic_cancel.svg"; + static const String iconCancelCategory = "$_icons/icon_cancel_category.svg"; static const String iconError = "$_icons/ic_error.svg"; static const String iconSuccess = "$_icons/ic_success.svg"; static const String icCategory = "$_icons/ic_menu-square.svg"; diff --git a/lib/design_system/widgets/selected_category_item.dart b/lib/design_system/widgets/selected_category_item.dart new file mode 100644 index 00000000..b7e1eb76 --- /dev/null +++ b/lib/design_system/widgets/selected_category_item.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:moneyplus/design_system/assets/app_assets.dart'; +import 'package:moneyplus/design_system/theme/money_extension_context.dart'; + +class SelectedCategoryItem extends StatelessWidget { + final String label; + final VoidCallback onDelete; + + const SelectedCategoryItem({ + super.key, + required this.label, + required this.onDelete, + }); + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final typo = context.typography; + + return Row( + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: colors.surfaceLow, + borderRadius: BorderRadius.circular(16), + ), + child: Text( + label, + style: typo.body.medium.copyWith(color: colors.title), + ), + ), + ), + const SizedBox(width: 4), + GestureDetector( + onTap: onDelete, + behavior: HitTestBehavior.opaque, + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: colors.redVariant, + borderRadius: BorderRadius.circular(16), + ), + child: SvgPicture.asset( + AppAssets.iconCancelCategory, + width: 52, + height: 28, + colorFilter: ColorFilter.mode(colors.red, BlendMode.srcIn), + ), + ), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/presentation/account_setup/cubit/account_setup_cubit.dart b/lib/presentation/account_setup/cubit/account_setup_cubit.dart index 546a2a83..73398ae6 100644 --- a/lib/presentation/account_setup/cubit/account_setup_cubit.dart +++ b/lib/presentation/account_setup/cubit/account_setup_cubit.dart @@ -8,13 +8,21 @@ class AccountSetupCubit extends Cubit { AccountSetupCubit(this._accountSetupRepository) : super(AccountSetupState()); Future fetchCurrencies() async { - try{ + try { final currencies = await _accountSetupRepository.getCurrencies(); - emit(state.copyWith(currencies : currencies,isLoading: false)); - }catch(e){ + emit(state.copyWith(currencies: currencies, isLoading: false)); + } catch (e) { emit(state.copyWith(isLoading: false, errorMessage: e.toString())); } } - -} \ No newline at end of file + void toggleCategory(String category) { + final List updatedCategories = List.from(state.categories); + if (updatedCategories.contains(category)) { + updatedCategories.remove(category); + } else { + updatedCategories.add(category); + } + emit(state.copyWith(categories: updatedCategories)); + } +} diff --git a/lib/presentation/account_setup/cubit/account_setup_state.dart b/lib/presentation/account_setup/cubit/account_setup_state.dart index 40a4a0e9..7a334d72 100644 --- a/lib/presentation/account_setup/cubit/account_setup_state.dart +++ b/lib/presentation/account_setup/cubit/account_setup_state.dart @@ -1,4 +1,3 @@ - import 'package:moneyplus/domain/entity/currency.dart'; class AccountSetupState { @@ -8,6 +7,8 @@ class AccountSetupState { final String query; final bool isButtonEnabled; final List currencies; + final List categories; + final List suggestions; final bool isLoading; final String errorMessage; @@ -18,6 +19,19 @@ class AccountSetupState { this.query = "", this.isButtonEnabled = false, this.currencies = const [], + this.categories = const [], + this.suggestions = const [ + 'Food', + 'Transport', + 'Shopping', + 'Health', + 'Education', + 'Gift', + 'Cafe', + 'Work', + 'Home', + 'Travel' + ], this.isLoading = true, this.errorMessage = "", }); @@ -29,6 +43,8 @@ class AccountSetupState { String? query, bool? isButtonEnabled, List? currencies, + List? categories, + List? suggestions, String? errorMessage, bool? isLoading, }) { @@ -37,10 +53,12 @@ class AccountSetupState { salary: salary ?? this.salary, salaryDay: salaryDay ?? this.salaryDay, query: query ?? this.query, - isButtonEnabled: isButtonEnabled?? this.isButtonEnabled, - currencies: currencies?? this.currencies, - errorMessage: errorMessage?? this.errorMessage, - isLoading: isLoading?? this.isLoading, + isButtonEnabled: isButtonEnabled ?? this.isButtonEnabled, + currencies: currencies ?? this.currencies, + categories: categories ?? this.categories, + suggestions: suggestions ?? this.suggestions, + errorMessage: errorMessage ?? this.errorMessage, + isLoading: isLoading ?? this.isLoading, ); } } diff --git a/lib/presentation/account_setup/screen/account_setup_screen.dart b/lib/presentation/account_setup/screen/account_setup_screen.dart index a461d3d6..1a8cbb58 100644 --- a/lib/presentation/account_setup/screen/account_setup_screen.dart +++ b/lib/presentation/account_setup/screen/account_setup_screen.dart @@ -4,6 +4,7 @@ import 'package:flutter_svg/svg.dart'; import 'package:moneyplus/design_system/assets/app_assets.dart'; import 'package:moneyplus/design_system/widgets/app_bar.dart'; import 'package:moneyplus/presentation/account_setup/screen/page1.dart'; +import 'package:moneyplus/presentation/account_setup/screen/page2.dart'; import '../../../core/l10n/app_localizations.dart'; import '../../../design_system/theme/money_extension_context.dart'; @@ -44,6 +45,8 @@ class _AccountSetupScreenState extends State { create: (context) => cubit..fetchCurrencies(), child: BlocBuilder( builder: (context, state) { + final isLastStep = (currentIndex == 1 && state.categories.isNotEmpty) || currentIndex == 2; + return Scaffold( backgroundColor: context.colors.surface, body: SafeArea( @@ -58,23 +61,23 @@ class _AccountSetupScreenState extends State { title: l10n.accountSetup, trailing: SvgPicture.asset(AppAssets.appBrand,), ), - SizedBox(height: 36,), + const SizedBox(height: 36,), Indicator(currentIndex: currentIndex), - SizedBox(height: 16,), + const SizedBox(height: 16,), Text( l10n.stepOfTotal(currentIndex + 1, 3), style: context.typography.label.small.copyWith( color: context.colors.body, ), ), - SizedBox(height: 4), + const SizedBox(height: 4), Text( l10n.setUpYourAccount, style: context.typography.headline.medium.copyWith( color: context.colors.title, ), ), - SizedBox(height: 4), + const SizedBox(height: 4), Expanded( child: PageView( controller: pageController, @@ -84,23 +87,23 @@ class _AccountSetupScreenState extends State { }); }, children: [ - SingleChildScrollView(child: Page1(state: state)) - // page2() - // page3() + SingleChildScrollView(child: Page1(state: state)), + SingleChildScrollView(child: Page1(state: state)), + SingleChildScrollView(child: Page2(state: state)), ], ), ), DefaultButton( - text: currentIndex == 2 ? l10n.finishSetup : l10n.next, - isEnabled: false, + text: isLastStep ? l10n.finishSetup : l10n.next, + isEnabled: true, onPressed: () { - if (currentIndex < 2) { + if (!isLastStep) { pageController.nextPage( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); } else { - // Navigate to home + // TODO: Navigate to home } }, ), diff --git a/lib/presentation/account_setup/screen/page2.dart b/lib/presentation/account_setup/screen/page2.dart new file mode 100644 index 00000000..544a48cd --- /dev/null +++ b/lib/presentation/account_setup/screen/page2.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:moneyplus/design_system/widgets/chip.dart'; +import 'package:moneyplus/design_system/widgets/text_field.dart'; +import 'package:moneyplus/presentation/account_setup/cubit/account_setup_cubit.dart'; +import 'package:moneyplus/presentation/account_setup/cubit/account_setup_state.dart'; + +import '../../../core/l10n/app_localizations.dart'; +import '../../../design_system/theme/money_extension_context.dart'; +import '../../../design_system/widgets/selected_category_item.dart'; + +class Page2 extends StatefulWidget { + final AccountSetupState state; + const Page2({super.key, required this.state}); + + @override + State createState() => _Page2State(); +} + +class _Page2State extends State { + final TextEditingController categoryController = TextEditingController(); + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + final cubit = context.read(); + + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Where do you usually spend your money?', + style: context.typography.label.small.copyWith( + color: context.colors.body, + ), + ), + const SizedBox(height: 24), + MTextField( + hint: 'Category name', + keyboardType: TextInputType.text, + value: categoryController.text, + onChanged: (value) { + categoryController.text = value; + }, + ), + const SizedBox(height: 16), + + Text( + 'Suggestions:', + style: context.typography.label.medium.copyWith( + color: context.colors.title, + ), + ), + const SizedBox(height: 12), + Wrap( + spacing: 8, + runSpacing: 8, + children: widget.state.suggestions.map((suggestion) { + final isSelected = widget.state.categories.contains(suggestion); + if (isSelected) { + return const SizedBox.shrink(); + } + return MChip( + label: suggestion, + selected: false, + onTap: () => cubit.toggleCategory(suggestion), + ); + }).toList(), + ), + + if (widget.state.categories.isNotEmpty) ...[ + const SizedBox(height: 24), + Text( + 'Selected Categories:', + style: context.typography.label.medium.copyWith( + color: context.colors.title, + ), + ), + const SizedBox(height: 12), + ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: widget.state.categories.length, + separatorBuilder: (context, index) => const SizedBox(height: 8), + itemBuilder: (context, index) { + final category = widget.state.categories[index]; + return SelectedCategoryItem( + label: category, + onDelete: () => cubit.toggleCategory(category), + ); + }, + ), + ], + + const SizedBox(height: 24), + ], + ), + ); + } +} \ No newline at end of file From 7a2b24f87b197afc0a6a055fea68ad78af95ab08 Mon Sep 17 00:00:00 2001 From: Mostafa Mohammed Date: Fri, 13 Feb 2026 11:02:53 +0300 Subject: [PATCH 2/5] feat(account-setup): implement category selection step - Add `categories` and `suggestions` to `AccountSetupState`. - Implement `toggleCategory` logic in `AccountSetupCubit`. - Create `Page2` for the account setup flow to allow users to select or add spending categories. - Introduce `SelectedCategoryItem` widget and `icon_cancel_category.svg` asset for the category selection UI. - Update `AccountSetupScreen` to support multi-page navigation and validation for the finish button. --- lib/design_system/widgets/text_field.dart | 31 ++++++-- .../screen/account_setup_screen.dart | 15 ++-- .../screen/{page2.dart => page3.dart} | 79 ++++++++++++------- 3 files changed, 83 insertions(+), 42 deletions(-) rename lib/presentation/account_setup/screen/{page2.dart => page3.dart} (63%) diff --git a/lib/design_system/widgets/text_field.dart b/lib/design_system/widgets/text_field.dart index 335c4d35..87f474c6 100644 --- a/lib/design_system/widgets/text_field.dart +++ b/lib/design_system/widgets/text_field.dart @@ -4,7 +4,8 @@ import '../theme/money_extension_context.dart'; class MTextField extends StatefulWidget { final String hint; - final String value; + final String? value; + final TextEditingController? controller; final ValueChanged onChanged; final IconData? leadingIcon; final Widget? leading; @@ -18,7 +19,8 @@ class MTextField extends StatefulWidget { const MTextField({ super.key, required this.hint, - required this.value, + this.value, + this.controller, required this.onChanged, this.leadingIcon, this.leading, @@ -36,19 +38,32 @@ class MTextField extends StatefulWidget { class _MTextFieldState extends State { late FocusNode _focusNode; - late TextEditingController _controller; + TextEditingController? _internalController; + + TextEditingController get _effectiveController => + widget.controller ?? (_internalController ??= TextEditingController(text: widget.value)); @override void initState() { super.initState(); _focusNode = FocusNode()..addListener(() => setState(() {})); - _controller = TextEditingController(text: widget.value); + } + + @override + void didUpdateWidget(covariant MTextField oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.controller == null && oldWidget.controller != null) { + _internalController = TextEditingController(text: _effectiveController.text); + } else if (widget.controller != null && oldWidget.controller == null) { + _internalController?.dispose(); + _internalController = null; + } } @override void dispose() { _focusNode.dispose(); - _controller.dispose(); + _internalController?.dispose(); super.dispose(); } @@ -78,7 +93,6 @@ class _MTextFieldState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - // padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), border: Border.all( @@ -97,7 +111,7 @@ class _MTextFieldState extends State { ), if (widget.leadingIcon != null) Padding( - padding: EdgeInsets.only(top: 14, right: 8), + padding: const EdgeInsets.only(top: 14, right: 8), child: Icon( widget.leadingIcon, color: showBorder ? borderColor : colors.body, @@ -106,7 +120,7 @@ class _MTextFieldState extends State { ), Expanded( child: TextField( - controller: _controller, + controller: _effectiveController, focusNode: _focusNode, keyboardType: widget.keyboardType, obscureText: widget.obscureText, @@ -118,6 +132,7 @@ class _MTextFieldState extends State { style: typography.body.medium.copyWith(color: colors.title), onChanged: widget.onChanged, decoration: InputDecoration( + contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), hintText: _focusNode.hasFocus ? null : widget.hint, hintStyle: typography.label.medium.copyWith( color: colors.body, diff --git a/lib/presentation/account_setup/screen/account_setup_screen.dart b/lib/presentation/account_setup/screen/account_setup_screen.dart index 1a8cbb58..3a125d32 100644 --- a/lib/presentation/account_setup/screen/account_setup_screen.dart +++ b/lib/presentation/account_setup/screen/account_setup_screen.dart @@ -4,7 +4,7 @@ import 'package:flutter_svg/svg.dart'; import 'package:moneyplus/design_system/assets/app_assets.dart'; import 'package:moneyplus/design_system/widgets/app_bar.dart'; import 'package:moneyplus/presentation/account_setup/screen/page1.dart'; -import 'package:moneyplus/presentation/account_setup/screen/page2.dart'; +import 'package:moneyplus/presentation/account_setup/screen/page3.dart'; import '../../../core/l10n/app_localizations.dart'; import '../../../design_system/theme/money_extension_context.dart'; @@ -45,7 +45,12 @@ class _AccountSetupScreenState extends State { create: (context) => cubit..fetchCurrencies(), child: BlocBuilder( builder: (context, state) { - final isLastStep = (currentIndex == 1 && state.categories.isNotEmpty) || currentIndex == 2; + bool isEnabled = true; + if (currentIndex == 2) { + isEnabled = state.categories.isNotEmpty; + } + + final isLastStep = currentIndex == 2; return Scaffold( backgroundColor: context.colors.surface, @@ -88,14 +93,14 @@ class _AccountSetupScreenState extends State { }, children: [ SingleChildScrollView(child: Page1(state: state)), - SingleChildScrollView(child: Page1(state: state)), - SingleChildScrollView(child: Page2(state: state)), + //todo page2 + SingleChildScrollView(child: Page3(state: state)), ], ), ), DefaultButton( text: isLastStep ? l10n.finishSetup : l10n.next, - isEnabled: true, + isEnabled: isEnabled, onPressed: () { if (!isLastStep) { pageController.nextPage( diff --git a/lib/presentation/account_setup/screen/page2.dart b/lib/presentation/account_setup/screen/page3.dart similarity index 63% rename from lib/presentation/account_setup/screen/page2.dart rename to lib/presentation/account_setup/screen/page3.dart index 544a48cd..da14aba8 100644 --- a/lib/presentation/account_setup/screen/page2.dart +++ b/lib/presentation/account_setup/screen/page3.dart @@ -9,22 +9,45 @@ import '../../../core/l10n/app_localizations.dart'; import '../../../design_system/theme/money_extension_context.dart'; import '../../../design_system/widgets/selected_category_item.dart'; -class Page2 extends StatefulWidget { +class Page3 extends StatefulWidget { final AccountSetupState state; - const Page2({super.key, required this.state}); + const Page3({super.key, required this.state}); @override - State createState() => _Page2State(); + State createState() => _Page3State(); } -class _Page2State extends State { +class _Page3State extends State { final TextEditingController categoryController = TextEditingController(); + String _searchQuery = ''; + + @override + void initState() { + super.initState(); + categoryController.addListener(() { + setState(() { + _searchQuery = categoryController.text.toLowerCase(); + }); + }); + } + + @override + void dispose() { + categoryController.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final cubit = context.read(); + final filteredSuggestions = widget.state.suggestions + .where((suggestion) => + suggestion.toLowerCase().contains(_searchQuery) && + !widget.state.categories.contains(suggestion)) + .toList(); + return SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -45,30 +68,29 @@ class _Page2State extends State { }, ), const SizedBox(height: 16), - - Text( - 'Suggestions:', - style: context.typography.label.medium.copyWith( - color: context.colors.title, + if (filteredSuggestions.isNotEmpty) ...[ + Text( + 'Suggestions:', + style: context.typography.label.medium.copyWith( + color: context.colors.title, + ), ), - ), - const SizedBox(height: 12), - Wrap( - spacing: 8, - runSpacing: 8, - children: widget.state.suggestions.map((suggestion) { - final isSelected = widget.state.categories.contains(suggestion); - if (isSelected) { - return const SizedBox.shrink(); - } - return MChip( - label: suggestion, - selected: false, - onTap: () => cubit.toggleCategory(suggestion), - ); - }).toList(), - ), - + const SizedBox(height: 12), + Wrap( + spacing: 8, + runSpacing: 8, + children: filteredSuggestions.map((suggestion) { + return MChip( + label: suggestion, + selected: false, + onTap: () { + cubit.toggleCategory(suggestion); + categoryController.clear(); + }, + ); + }).toList(), + ), + ], if (widget.state.categories.isNotEmpty) ...[ const SizedBox(height: 24), Text( @@ -92,10 +114,9 @@ class _Page2State extends State { }, ), ], - const SizedBox(height: 24), ], ), ); } -} \ No newline at end of file +} From 782b07ad4f7a095834a365456bd3bea5042d899f Mon Sep 17 00:00:00 2001 From: Mostafa Mohammed Date: Sun, 22 Feb 2026 22:11:44 +0300 Subject: [PATCH 3/5] refactor(account_setup): Rename Page3 to AccountSetupStepThree and add localization Renames `page3.dart` to `account_setup_step_three.dart` for better clarity and updates its usage in `account_setup_screen.dart`. This also implements localization for hardcoded strings within the `AccountSetupStepThree` widget by adding new keys to `app_en.arb` and using them in the UI. --- lib/core/l10n/app_en.arb | 60 ++++++------------- .../screen/account_setup_screen.dart | 4 +- ...ge3.dart => account_setup_step_three.dart} | 16 ++--- 3 files changed, 29 insertions(+), 51 deletions(-) rename lib/presentation/account_setup/screen/{page3.dart => account_setup_step_three.dart} (89%) diff --git a/lib/core/l10n/app_en.arb b/lib/core/l10n/app_en.arb index 80273a9b..b6674e57 100644 --- a/lib/core/l10n/app_en.arb +++ b/lib/core/l10n/app_en.arb @@ -19,9 +19,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!", @@ -37,20 +35,18 @@ "select": "Select", "next": "Next", "finishSetup": "Finish setup", - "stepOf": "Step {current} of {total}", - "stepOfTotal": "Step {current} of {total}", - "@stepOfTotal": { - "placeholders": { - "current": {}, - "total": {} - } - }, + "stepOfTotal": "Step {current} of {total}", + "@stepOfTotal": { + "placeholders": { + "current": {}, + "total": {} + } + }, "forgetPasswordAppBarTitle": "Forget Password", "forgetPasswordTitle": "Forget your password", "forgetPasswordSubtitle": "Enter your email and we’ll send you a reset link", "forgetPasswordEmailHint": "Email", "forgetPasswordButton": "Send Reset Link", - "updatePasswordAppBarTitle": "Update Password", "updatePasswordTitle": "Enter a new password for", "updatePasswordPasswordHint": "New Password", @@ -58,11 +54,10 @@ "updatePasswordButton": "Update Password", "updatePasswordSuccessMessage": "Password updated successfully", "updatePasswordErrorMessage": "Error updating password", - "error": "Error", - "success" : "Success", - "loading" : "Loading", - "login_successfully" : "Login Successfully", + "success": "Success", + "loading": "Loading", + "login_successfully": "Login Successfully", "login_welcome_title": "Welcome again!", "login_welcome_subtitle": "Enter your credentials to access your account", "login_email_hint": "Email", @@ -93,7 +88,6 @@ "auth_error_not_implemented": "This feature is not available on the server", "auth_error_default": "An unexpected error occurred. Please try again later", "no_internet_connection": "No internet connection", - "no_transaction_record_title": "No transaction records", "no_transaction_record_content": "Add your first one to get started", "add_transaction": "Add transaction", @@ -103,25 +97,7 @@ "transaction": "Transaction", "transaction_error_title": "transactions error", "transaction_error_content": "failed to load transaction", - "currencyCode": "IQD", - "@stepOf": { - "placeholders": { - "current": { - "type": "int" - }, - "total": { - "type": "int" - } - } - }, - "stepOfTotal": "Step {current} of {total}", - "@stepOfTotal": { - "placeholders": { - "current": {}, - "total": {} - } - }, "no_spending_categories": "No spending categories available", "month_january": "January", "month_february": "February", @@ -135,11 +111,13 @@ "month_october": "October", "month_november": "November", "month_december": "December", - "manage_categories" : "Manage categories", + "manage_categories": "Manage categories", "add_new_category": "Add new category", - "add_custom_category" : "Add custom category", - "category_name" : "Category name", - "transaction_delete_success" : "Transaction deleted successfully", - "transaction_delete_fail" : "Failed to delete transaction", - "add" : "Add" -} \ No newline at end of file + "add_custom_category": "Add custom category", + "category_name": "Category name", + "transaction_delete_success": "Transaction deleted successfully", + "transaction_delete_fail": "Failed to delete transaction", + "whereDoYouUsuallySpendYourMoney": "Where do you usually spend your money?", + "suggestions": "Suggestions:", + "selectedCategories": "Selected Categories:" +} diff --git a/lib/presentation/account_setup/screen/account_setup_screen.dart b/lib/presentation/account_setup/screen/account_setup_screen.dart index 3a125d32..44377b3c 100644 --- a/lib/presentation/account_setup/screen/account_setup_screen.dart +++ b/lib/presentation/account_setup/screen/account_setup_screen.dart @@ -4,7 +4,7 @@ import 'package:flutter_svg/svg.dart'; import 'package:moneyplus/design_system/assets/app_assets.dart'; import 'package:moneyplus/design_system/widgets/app_bar.dart'; import 'package:moneyplus/presentation/account_setup/screen/page1.dart'; -import 'package:moneyplus/presentation/account_setup/screen/page3.dart'; +import 'package:moneyplus/presentation/account_setup/screen/account_setup_step_three.dart'; import '../../../core/l10n/app_localizations.dart'; import '../../../design_system/theme/money_extension_context.dart'; @@ -94,7 +94,7 @@ class _AccountSetupScreenState extends State { children: [ SingleChildScrollView(child: Page1(state: state)), //todo page2 - SingleChildScrollView(child: Page3(state: state)), + SingleChildScrollView(child: AccountSetupStepThree(state: state)), ], ), ), diff --git a/lib/presentation/account_setup/screen/page3.dart b/lib/presentation/account_setup/screen/account_setup_step_three.dart similarity index 89% rename from lib/presentation/account_setup/screen/page3.dart rename to lib/presentation/account_setup/screen/account_setup_step_three.dart index da14aba8..2d473b6c 100644 --- a/lib/presentation/account_setup/screen/page3.dart +++ b/lib/presentation/account_setup/screen/account_setup_step_three.dart @@ -9,15 +9,15 @@ import '../../../core/l10n/app_localizations.dart'; import '../../../design_system/theme/money_extension_context.dart'; import '../../../design_system/widgets/selected_category_item.dart'; -class Page3 extends StatefulWidget { +class AccountSetupStepThree extends StatefulWidget { final AccountSetupState state; - const Page3({super.key, required this.state}); + const AccountSetupStepThree({super.key, required this.state}); @override - State createState() => _Page3State(); + State createState() => _AccountSetupStepThreeState(); } -class _Page3State extends State { +class _AccountSetupStepThreeState extends State { final TextEditingController categoryController = TextEditingController(); String _searchQuery = ''; @@ -53,14 +53,14 @@ class _Page3State extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Where do you usually spend your money?', + l10n.whereDoYouUsuallySpendYourMoney, style: context.typography.label.small.copyWith( color: context.colors.body, ), ), const SizedBox(height: 24), MTextField( - hint: 'Category name', + hint: l10n.category_name, keyboardType: TextInputType.text, value: categoryController.text, onChanged: (value) { @@ -70,7 +70,7 @@ class _Page3State extends State { const SizedBox(height: 16), if (filteredSuggestions.isNotEmpty) ...[ Text( - 'Suggestions:', + l10n.suggestions, style: context.typography.label.medium.copyWith( color: context.colors.title, ), @@ -94,7 +94,7 @@ class _Page3State extends State { if (widget.state.categories.isNotEmpty) ...[ const SizedBox(height: 24), Text( - 'Selected Categories:', + l10n.selectedCategories, style: context.typography.label.medium.copyWith( color: context.colors.title, ), From 530c2aee70f6ad6e612c60795a209418ed2a399b Mon Sep 17 00:00:00 2001 From: Mostafa Mohammed Date: Sun, 22 Feb 2026 22:32:22 +0300 Subject: [PATCH 4/5] feat(account-setup): implement step three in account setup flow Integrates the `AccountSetupStepThree` widget into the `AccountSetupScreen` page view, replacing the previous placeholder. --- .../account_setup/screen/account_setup_screen.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/presentation/account_setup/screen/account_setup_screen.dart b/lib/presentation/account_setup/screen/account_setup_screen.dart index 12833607..b5592f0d 100644 --- a/lib/presentation/account_setup/screen/account_setup_screen.dart +++ b/lib/presentation/account_setup/screen/account_setup_screen.dart @@ -109,7 +109,8 @@ class _AccountSetupScreenState extends State { children: [ SingleChildScrollView(child: Page1(state: state,)), SingleChildScrollView(child: Page2(currency: state.currency, currentBalanceState: state.currentBalance)), - // page3() + SingleChildScrollView(child: AccountSetupStepThree(state: state)), + ], ), ), From bc7327bec779305b0d067f621fa24caa6fa8de4d Mon Sep 17 00:00:00 2001 From: Mostafa Mohammed Date: Sun, 22 Feb 2026 22:38:19 +0300 Subject: [PATCH 5/5] clean up redundant localization keys in app_ar.arb --- lib/core/l10n/app_ar.arb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/core/l10n/app_ar.arb b/lib/core/l10n/app_ar.arb index 326108e7..e3377295 100644 --- a/lib/core/l10n/app_ar.arb +++ b/lib/core/l10n/app_ar.arb @@ -105,10 +105,8 @@ "no_monthly_overview": "لم يتم تسجيل أي دخل أو مصروفات لهذه الفترة", "error_loading_statistics": "فشل تحميل الإحصائيات", "retry": "إعادة المحاولة", - "noDataAvailable": "لا توجد بيانات متاحة", "current_balance":"الرصيد الحالي", "how_much_money": "ما مقدار المال المتوفر لديك حاليًا؟", - "noDataAvailable": "لا توجد بيانات متاحة", "no_spending_categories": "لا يوجد معاملات متاحة", "month_january": "يناير", "month_february": "فبراير", @@ -124,7 +122,6 @@ "month_december": "ديسمبر", "no_transaction_record_title": "لا توجد سجلات معاملات", "no_transaction_record_content": "أضف أول معاملة للبدء", - "add_transaction": "إضافة معاملة", "all": "الكل", "incomes": "الإيرادات", "expenses": "المصروفات", @@ -139,7 +136,6 @@ "transaction_delete_fail" : "نم مسح المعاملة بنجاح", "add" : "اضافة", "transaction_details": "تفاصيل المعاملة", - "transaction_details": "Transaction details", "income_details": "تفاصيل الدخل", "expense_details": "تفاصيل المصروف" }