diff --git a/assets/icons/icon_cancel_category.svg b/assets/icons/icon_cancel_category.svg new file mode 100644 index 0000000..02620d3 --- /dev/null +++ b/assets/icons/icon_cancel_category.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/core/l10n/app_ar.arb b/lib/core/l10n/app_ar.arb index 326108e..e337729 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": "تفاصيل المصروف" } diff --git a/lib/core/l10n/app_en.arb b/lib/core/l10n/app_en.arb index e9285ba..32ca868 100644 --- a/lib/core/l10n/app_en.arb +++ b/lib/core/l10n/app_en.arb @@ -21,9 +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!", @@ -67,9 +65,9 @@ "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", @@ -151,8 +149,10 @@ "no_monthly_overview": "No income or expenses recorded for this period", "error_loading_statistics": "Failed to load statistics", "retry": "Retry", - "add" : "Add", "transaction_details": "Transaction details", "income_details": "Income details", - "expense_details": "Expense details" + "expense_details": "Expense details", + "whereDoYouUsuallySpendYourMoney": "Where do you usually spend your money?", + "suggestions": "Suggestions:", + "selectedCategories": "Selected Categories:" } diff --git a/lib/design_system/assets/app_assets.dart b/lib/design_system/assets/app_assets.dart index 9ad969f..40468a6 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 0000000..b7e1eb7 --- /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 3c2ea7e..cabb8db 100644 --- a/lib/presentation/account_setup/cubit/account_setup_cubit.dart +++ b/lib/presentation/account_setup/cubit/account_setup_cubit.dart @@ -72,4 +72,13 @@ class AccountSetupCubit extends Cubit { break; } } + 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 b81c676..e541e64 100644 --- a/lib/presentation/account_setup/cubit/account_setup_state.dart +++ b/lib/presentation/account_setup/cubit/account_setup_state.dart @@ -14,6 +14,8 @@ class AccountSetupState { final String query; final bool isButtonEnabled; final List currencies; + final List categories; + final List suggestions; final bool isLoading; final String errorMessage; final AccountSetupStep accountStep; @@ -27,6 +29,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 = "", this.accountStep = AccountSetupStep.step1, @@ -41,6 +56,8 @@ class AccountSetupState { String? query, bool? isButtonEnabled, List? currencies, + List? categories, + List? suggestions, String? errorMessage, bool? isLoading, AccountSetupStep? accountStep, diff --git a/lib/presentation/account_setup/screen/account_setup_screen.dart b/lib/presentation/account_setup/screen/account_setup_screen.dart index ff9e187..b5592f0 100644 --- a/lib/presentation/account_setup/screen/account_setup_screen.dart +++ b/lib/presentation/account_setup/screen/account_setup_screen.dart @@ -5,6 +5,7 @@ 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/account_setup_step_three.dart'; import '../../../core/di/injection.dart'; import '../../../core/l10n/app_localizations.dart'; @@ -108,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)), + ], ), ), diff --git a/lib/presentation/account_setup/screen/account_setup_step_three.dart b/lib/presentation/account_setup/screen/account_setup_step_three.dart new file mode 100644 index 0000000..2d473b6 --- /dev/null +++ b/lib/presentation/account_setup/screen/account_setup_step_three.dart @@ -0,0 +1,122 @@ +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 AccountSetupStepThree extends StatefulWidget { + final AccountSetupState state; + const AccountSetupStepThree({super.key, required this.state}); + + @override + State createState() => _AccountSetupStepThreeState(); +} + +class _AccountSetupStepThreeState 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, + children: [ + Text( + l10n.whereDoYouUsuallySpendYourMoney, + style: context.typography.label.small.copyWith( + color: context.colors.body, + ), + ), + const SizedBox(height: 24), + MTextField( + hint: l10n.category_name, + keyboardType: TextInputType.text, + value: categoryController.text, + onChanged: (value) { + categoryController.text = value; + }, + ), + const SizedBox(height: 16), + if (filteredSuggestions.isNotEmpty) ...[ + Text( + l10n.suggestions, + style: context.typography.label.medium.copyWith( + color: context.colors.title, + ), + ), + 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( + l10n.selectedCategories, + 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), + ], + ), + ); + } +}