diff --git a/assets/icons/ic_coins.svg b/assets/icons/ic_coins.svg new file mode 100644 index 0000000..fba439d --- /dev/null +++ b/assets/icons/ic_coins.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/ic_currency.svg b/assets/icons/ic_currency.svg new file mode 100644 index 0000000..b1409c5 --- /dev/null +++ b/assets/icons/ic_currency.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/ic_customer_support.svg b/assets/icons/ic_customer_support.svg new file mode 100644 index 0000000..8148786 --- /dev/null +++ b/assets/icons/ic_customer_support.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/ic_help.svg b/assets/icons/ic_help.svg new file mode 100644 index 0000000..0afc8a0 --- /dev/null +++ b/assets/icons/ic_help.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/ic_settings.svg b/assets/icons/ic_settings.svg new file mode 100644 index 0000000..f61d23a --- /dev/null +++ b/assets/icons/ic_settings.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/ic_sun.svg b/assets/icons/ic_sun.svg new file mode 100644 index 0000000..43f429c --- /dev/null +++ b/assets/icons/ic_sun.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/ic_translation.svg b/assets/icons/ic_translation.svg new file mode 100644 index 0000000..d3b2e91 --- /dev/null +++ b/assets/icons/ic_translation.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/money_background.png b/assets/images/money_background.png new file mode 100644 index 0000000..4d7594b Binary files /dev/null and b/assets/images/money_background.png differ diff --git a/lib/core/di/cubit_di.dart b/lib/core/di/cubit_di.dart index 7265b75..dd5ad81 100644 --- a/lib/core/di/cubit_di.dart +++ b/lib/core/di/cubit_di.dart @@ -13,6 +13,7 @@ import 'package:moneyplus/presentation/statistics/cubit/statistics_cubit.dart'; import 'package:moneyplus/presentation/transactions/cubit/transaction_cubit.dart'; import 'package:moneyplus/presentation/trasnaction_details/trasnaction_details_cubit.dart'; +import '../../presentation/accout/cubit/account_cubit.dart'; import '../../presentation/expense/cubit/add_expense_cubit.dart'; import 'injection.dart'; @@ -70,4 +71,7 @@ void initCubitDI() { getIt(), ), ); + getIt.registerFactory( + () => AccountCubit(getIt()), + ); } diff --git a/lib/core/l10n/app_ar.arb b/lib/core/l10n/app_ar.arb index c605350..cec8832 100644 --- a/lib/core/l10n/app_ar.arb +++ b/lib/core/l10n/app_ar.arb @@ -138,6 +138,15 @@ "transaction_details": "تفاصيل المعاملة", "income_details": "تفاصيل الدخل", "expense_details": "تفاصيل المصروف", + "account": "حساب", + "manageCategories": "إدارة الفئات", + "appLanguage": "لغة التطبيق", + "appTheme": "ثيم التطبيق", + "salarySettings": "إعدادات الراتب", + "frequentlyAskedQuestion": "الأسئلة المتكررة", + "helpAndSupport": "المساعدة والدعم", + "appVersion": "اصدار التطبيق", + "expense_details": "تفاصيل المصروف", "add_transaction_sheet_subtitle": "اختر نوع المعاملة التي تريد إضافتها", "make_expense": "تسجيل مصروف", "continueButton": "متابعة", diff --git a/lib/core/l10n/app_en.arb b/lib/core/l10n/app_en.arb index 81c8cc1..9c83c33 100644 --- a/lib/core/l10n/app_en.arb +++ b/lib/core/l10n/app_en.arb @@ -149,6 +149,14 @@ "no_monthly_overview": "No income or expenses recorded for this period", "error_loading_statistics": "Failed to load statistics", "retry": "Retry", + "account": "Account", + "manageCategories": "Manage categories", + "appLanguage": "App language", + "appTheme": "App theme", + "salarySettings": "Salary settings", + "frequentlyAskedQuestion": "Frequently asked question", + "helpAndSupport": "Help & Support", + "appVersion": "App version", "transaction_details": "Transaction details", "income_details": "Income details", "expense_details": "Expense details", diff --git a/lib/data/repository/account_repository.dart b/lib/data/repository/account_repository.dart index b936dcf..eed3085 100644 --- a/lib/data/repository/account_repository.dart +++ b/lib/data/repository/account_repository.dart @@ -1,4 +1,6 @@ import 'package:moneyplus/domain/entity/currency.dart'; +import 'package:moneyplus/domain/entity/user.dart'; +import 'package:supabase_flutter/supabase_flutter.dart' hide User; import '../../domain/repository/account_repository.dart'; import '../../core/service/supabase_service.dart'; @@ -9,15 +11,19 @@ class AccountRepositoryImpl extends AccountRepository { AccountRepositoryImpl({required this.supabaseService}); @override - Future> getCurrencies() async{ - try{ + Future> getCurrencies() async { + try { final client = await supabaseService.getClient(); final response = await client.from('currencies').select(); return response.map((e) => Currency.fromJson(e)).toList(); - }catch(e){ + } catch (e) { throw Exception('Failed to fetch currencies'); } + } + @override + Future getCurrentUser() async { + throw Exception('Failed to get current user'); } } \ 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 2617f89..c21718c 100644 --- a/lib/design_system/assets/app_assets.dart +++ b/lib/design_system/assets/app_assets.dart @@ -70,6 +70,14 @@ class AppAssets { 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"; - static const String icAddAmount = "$_icons/ic_add_transaction_income.svg"; - static const String icAddExpense = "$_icons/ic_add_transaction_expense.svg"; + static const String icAddAmount = '$_icons/ic_add_transaction_income.svg'; + static const String icAddExpense = '$_icons/ic_add_transaction_expense.svg'; + static const String icCoins = '$_icons/ic_coins.svg'; + static const String icCustomerSupport = '$_icons/ic_customer_support.svg'; + static const String icHelp = '$_icons/ic_help.svg'; + static const String icSettings = '$_icons/ic_settings.svg'; + static const String icSun = '$_icons/ic_sun.svg'; + static const String icTranslation = '$_icons/ic_translation.svg'; + static const String icCurrency = '$_icons/ic_currency.svg'; + static const String glowBackground = '$_images/money_background.png'; } diff --git a/lib/domain/repository/account_repository.dart b/lib/domain/repository/account_repository.dart index f11c038..55f8515 100644 --- a/lib/domain/repository/account_repository.dart +++ b/lib/domain/repository/account_repository.dart @@ -1,6 +1,9 @@ - import 'package:moneyplus/domain/entity/currency.dart'; +import 'package:moneyplus/domain/entity/user.dart'; abstract class AccountRepository { Future> getCurrencies(); + + Future getCurrentUser(); + } \ No newline at end of file diff --git a/lib/presentation/accout/cubit/account_cubit.dart b/lib/presentation/accout/cubit/account_cubit.dart new file mode 100644 index 0000000..0ebcfdf --- /dev/null +++ b/lib/presentation/accout/cubit/account_cubit.dart @@ -0,0 +1,21 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../domain/repository/account_repository.dart'; +import 'account_state.dart'; + +class AccountCubit extends Cubit { + final AccountRepository _accountRepository; + + AccountCubit(this._accountRepository) + : super(const AccountLoading(isLoading: true)); + + void loadUserInfo() { + emit(const AccountLoading(isLoading: true)); + + _accountRepository.getCurrentUser().then((user) { + emit(AccountLoaded(user: user)); + }).catchError((error) { + emit(AccountError(errorMessage: error.toString())); + }); + } +} diff --git a/lib/presentation/accout/cubit/account_state.dart b/lib/presentation/accout/cubit/account_state.dart new file mode 100644 index 0000000..1ef474b --- /dev/null +++ b/lib/presentation/accout/cubit/account_state.dart @@ -0,0 +1,30 @@ +import 'package:flutter/cupertino.dart'; + +import '../../../domain/entity/user.dart'; + +@immutable +sealed class AccountState { + const AccountState(); +} + +class AccountLoading extends AccountState { + final bool isLoading; + + const AccountLoading({required this.isLoading}); +} + +class AccountLoaded extends AccountState { + final User user; + + const AccountLoaded({required this.user}); + + AccountLoaded copyWith({User? user}) { + return AccountLoaded(user: user ?? this.user); + } +} + +class AccountError extends AccountState { + final String errorMessage; + + const AccountError({required this.errorMessage}); +} diff --git a/lib/presentation/accout/screen/account_screen.dart b/lib/presentation/accout/screen/account_screen.dart new file mode 100644 index 0000000..b577c99 --- /dev/null +++ b/lib/presentation/accout/screen/account_screen.dart @@ -0,0 +1,126 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../core/di/injection.dart'; +import '../../../core/l10n/app_localizations.dart'; +import '../../../design_system/assets/app_assets.dart'; +import '../../../design_system/theme/money_extension_context.dart'; +import '../../../design_system/widgets/app_bar.dart'; +import '../cubit/account_cubit.dart'; +import '../cubit/account_state.dart'; +import '../widget/account_section.dart'; +import '../widget/personal_info_card.dart'; + +class AccountScreen extends StatelessWidget { + const AccountScreen({super.key}); + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + final colors = context.colors; + final typography = context.typography; + + return Scaffold( + backgroundColor: colors.surface, + appBar: CustomAppBar(title: l10n.account,backgroundColor: colors.surfaceLow), + body: BlocProvider( + create: (_) => getIt()..loadUserInfo(), + child: BlocConsumer( + listener: (context, state) {}, + builder: (context, state) { + return _buildBody(context, state, l10n, colors, typography); + }, + ), + ), + ); + } + + Widget _buildBody( + BuildContext context, + AccountState state, + AppLocalizations l10n, + dynamic colors, + dynamic typography, + ) { + if (state is AccountLoading) { + return const Center(child: CircularProgressIndicator()); + } + + final user = state is AccountLoaded ? state.user : null; + + return Stack( + children: [ + Positioned( + child: Align( + alignment: Alignment.bottomRight, + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.75, + height: 150, + + child: Image.asset( + AppAssets.glowBackground, + fit: BoxFit.contain, + ), + ), + ), + ), + + SingleChildScrollView( + child: Container( + padding: const EdgeInsets.only(top: 24, left: 16, right: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + personalInfoCard( + image: '', + name: user?.name ?? '', + email: user?.email ?? '', + ), + const SizedBox(height: 24), + accountSection( + title: l10n.manageCategories, + iconPath: AppAssets.icSettings, + ), + accountSection( + title: l10n.appLanguage, + iconPath: AppAssets.icTranslation, + ), + accountSection(title: l10n.appTheme, iconPath: AppAssets.icSun), + accountSection( + title: l10n.currency, + iconPath: AppAssets.icCurrency, + ), + accountSection( + title: l10n.salarySettings, + iconPath: AppAssets.iconMoney, + ), + accountSection( + title: l10n.frequentlyAskedQuestion, + iconPath: AppAssets.icHelp, + ), + accountSection( + title: l10n.helpAndSupport, + iconPath: AppAssets.icCustomerSupport, + showDivider: false, + ), + const SizedBox(height: 24), + Align( + alignment: Alignment.center, + child: Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Text( + "${l10n.appVersion} 1.0", + style: typography.label.small.copyWith( + color: colors.body, + ), + ), + ), + ), + ], + ), + ), + ), + ], + ); + } +} diff --git a/lib/presentation/accout/widget/account_section.dart b/lib/presentation/accout/widget/account_section.dart new file mode 100644 index 0000000..ee80fb3 --- /dev/null +++ b/lib/presentation/accout/widget/account_section.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../../design_system/theme/money_colors.dart'; +import '../../../design_system/theme/money_typography.dart'; + +Widget accountSection({ + required String title, + required String iconPath, + bool showDivider = true, + VoidCallback? onTap, +}) { + return InkWell( + onTap: () { + if (onTap != null) { + onTap(); + } + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.rectangle, + color: MoneyColors.light.surfaceHigh, + borderRadius: BorderRadius.circular(12), + ), + padding: EdgeInsets.symmetric(vertical: 12, horizontal: 11), + alignment: Alignment.center, + child: SvgPicture.asset(iconPath, width: 24, height: 24), + ), + SizedBox(width: 8), + Text( + title, + style: MoneyTypography.typography.label.large.copyWith( + color: MoneyColors.light.title, + ), + ), + ], + ), + if (showDivider) + Divider(color: MoneyColors.light.stroke, thickness: 0.5), + ], + ), + ); +} diff --git a/lib/presentation/accout/widget/personal_info_card.dart b/lib/presentation/accout/widget/personal_info_card.dart new file mode 100644 index 0000000..95d24a3 --- /dev/null +++ b/lib/presentation/accout/widget/personal_info_card.dart @@ -0,0 +1,89 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../../design_system/assets/app_assets.dart'; +import '../../../design_system/theme/money_colors.dart'; +import '../../../design_system/theme/money_typography.dart'; + +Widget personalInfoCard({ + required String? image, + required String name, + required String email, +}) { + return Container( + decoration: BoxDecoration( + color: MoneyColors.light.surfaceLow, + borderRadius: BorderRadius.circular(16), + ), + padding: EdgeInsets.all(8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + updateUserAvatar(image, name), + SizedBox(width: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + name, + style: MoneyTypography.typography.title.small.copyWith( + color: MoneyColors.light.title, + ), + ), + Text( + email, + style: MoneyTypography.typography.label.small.copyWith( + color: MoneyColors.light.body, + ), + ), + ], + ), + Spacer(), + GestureDetector( + onTap: () { + // Handle click + }, + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: MoneyColors.light.stroke), + ), + padding: EdgeInsets.all(8), + child: SvgPicture.asset(AppAssets.icEdit, width: 16, height: 16), + ), + ), + ], + ), + ); +} + +Widget updateUserAvatar(String? imageUrl, String fullName) { + if (imageUrl != null && imageUrl.isNotEmpty) { + return Image.asset(imageUrl, height: 52, width: 52); + } else { + String initials = ''; + if (fullName.isNotEmpty) { + final nameParts = fullName.trim().split(' '); + if (nameParts.length >= 2) { + initials = (nameParts[0][0] + nameParts[1][0]).toUpperCase(); + } else { + initials = fullName[0].toUpperCase(); + } + } + return Container( + decoration: BoxDecoration( + shape: BoxShape.rectangle, + color: MoneyColors.light.surface, + borderRadius: BorderRadius.circular(12), + ), + padding: EdgeInsets.symmetric(vertical: 12, horizontal: 11), + alignment: Alignment.center, + child: Text( + initials, + style: MoneyTypography.typography.title.large.copyWith( + color: MoneyColors.light.title, + ), + ), + ); + } +} diff --git a/lib/presentation/main_container/screen/main_screen.dart b/lib/presentation/main_container/screen/main_screen.dart index 1adea93..a8bf6cb 100644 --- a/lib/presentation/main_container/screen/main_screen.dart +++ b/lib/presentation/main_container/screen/main_screen.dart @@ -1,7 +1,7 @@ -import 'package:flutter/cupertino.dart'; 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/accout/screen/account_screen.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'; @@ -44,7 +44,7 @@ class MainScreen extends StatelessWidget { case NavBarTab.statistics: return const StatisticsScreen(); case NavBarTab.account: - return const TransactionsScreen(); //change to account + return const AccountScreen(); } } }