/// Account Page /// /// Displays user account information and settings menu. /// Features: /// - User profile card with avatar, name, role, tier, and phone /// - Account menu section (personal info, orders, addresses, etc.) /// - Support section (contact, FAQ, about) /// - Logout button library; import 'dart:async'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:go_router/go_router.dart'; import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/database/hive_initializer.dart'; import 'package:worker/core/database/models/enums.dart'; import 'package:worker/core/router/app_router.dart'; import 'package:worker/core/theme/colors.dart'; import 'package:worker/features/account/domain/entities/user_info.dart' as domain; import 'package:worker/features/account/presentation/providers/user_info_provider.dart' hide UserInfo; import 'package:worker/features/account/presentation/widgets/account_menu_item.dart'; import 'package:worker/features/auth/presentation/providers/auth_provider.dart'; /// Account Page /// /// Main account/settings page accessible from the bottom navigation bar. class AccountPage extends ConsumerWidget { const AccountPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final colorScheme = Theme.of(context).colorScheme; return Scaffold( backgroundColor: colorScheme.surfaceContainerLowest, body: SafeArea( child: RefreshIndicator( onRefresh: () async { await ref.read(userInfoProvider.notifier).refresh(); }, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: Column( children: [ // Simple Header _buildHeader(context), const SizedBox(height: AppSpacing.md), // User Profile Card - only this depends on provider const _ProfileCardSection(), const SizedBox(height: AppSpacing.md), // Account Menu Section - independent _buildAccountMenu(context), const SizedBox(height: AppSpacing.md), // Support Section - independent _buildSupportSection(context), const SizedBox(height: AppSpacing.md), // Logout Button - independent (uses ref only for logout action) _LogoutButton(), const SizedBox(height: AppSpacing.lg), ], ), ), ), ), ); } /// Build simple header with title Widget _buildHeader(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), decoration: BoxDecoration( color: colorScheme.surface, boxShadow: [ BoxShadow( color: colorScheme.shadow.withValues(alpha: 0.05), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Text( 'Tài khoản', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: colorScheme.onSurface, ), ), ); } /// Build account menu section Widget _buildAccountMenu(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Container( margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), decoration: BoxDecoration( color: colorScheme.surface, borderRadius: BorderRadius.circular(AppRadius.card), boxShadow: [ BoxShadow( color: colorScheme.shadow.withValues(alpha: 0.05), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Column( children: [ AccountMenuItem( icon: FontAwesomeIcons.penToSquare, title: 'Thông tin cá nhân', subtitle: 'Cập nhật thông tin tài khoản', onTap: () { context.push(RouteNames.profile); }, ), AccountMenuItem( icon: FontAwesomeIcons.clockRotateLeft, title: 'Lịch sử đơn hàng', subtitle: 'Xem các đơn hàng đã đặt', onTap: () { context.push(RouteNames.orders); }, ), AccountMenuItem( icon: FontAwesomeIcons.locationDot, title: 'Địa chỉ đã lưu', subtitle: 'Quản lý địa chỉ giao hàng', onTap: () { context.push(RouteNames.addresses); }, ), AccountMenuItem( icon: FontAwesomeIcons.bell, title: 'Cài đặt thông báo', subtitle: 'Quản lý thông báo đẩy', onTap: () { _showComingSoon(context); }, ), AccountMenuItem( icon: FontAwesomeIcons.lock, title: 'Đổi mật khẩu', subtitle: 'Cập nhật mật khẩu mới', onTap: () { context.push(RouteNames.changePassword); }, ), // AccountMenuItem( // icon: FontAwesomeIcons.language, // title: 'Ngôn ngữ', // subtitle: 'Tiếng Việt', // onTap: () { // _showComingSoon(context); // }, // ), AccountMenuItem( icon: FontAwesomeIcons.palette, title: 'Giao diện', subtitle: 'Màu sắc và chế độ hiển thị', onTap: () { context.push(RouteNames.themeSettings); }, ), ], ), ); } /// Build support section Widget _buildSupportSection(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Container( margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), decoration: BoxDecoration( color: colorScheme.surface, borderRadius: BorderRadius.circular(AppRadius.card), boxShadow: [ BoxShadow( color: colorScheme.shadow.withValues(alpha: 0.05), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Section title Padding( padding: const EdgeInsets.fromLTRB( AppSpacing.md, AppSpacing.md, AppSpacing.md, AppSpacing.sm, ), child: Text( 'Hỗ trợ', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: colorScheme.onSurface, ), ), ), // Support menu items AccountMenuItem( icon: FontAwesomeIcons.headset, title: 'Liên hệ hỗ trợ', subtitle: 'Hotline: 1900 1234', trailing: FaIcon( FontAwesomeIcons.phone, size: 18, color: colorScheme.onSurfaceVariant, ), onTap: () { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Hotline: 1900 1234'), duration: Duration(seconds: 2), ), ); }, ), AccountMenuItem( icon: FontAwesomeIcons.circleQuestion, title: 'Câu hỏi thường gặp', onTap: () { _showComingSoon(context); }, ), AccountMenuItem( icon: FontAwesomeIcons.circleInfo, title: 'Về ứng dụng', subtitle: 'Phiên bản 1.0.0', onTap: () { _showAboutDialog(context); }, ), ], ), ); } /// Show coming soon message void _showComingSoon(BuildContext context) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Tính năng đang phát triển'), duration: Duration(seconds: 1), ), ); } /// Show about dialog void _showAboutDialog(BuildContext context) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Về ứng dụng'), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'EuroTile & Vasta Stone Worker', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), ), const SizedBox(height: 8), const Text('Phiên bản: 1.0.0'), const SizedBox(height: 8), Text( 'Ứng dụng dành cho thầu thợ, kiến trúc sư, đại lý và môi giới trong ngành gạch ốp lát và nội thất.', style: TextStyle(fontSize: 14, color: Theme.of(context).colorScheme.onSurfaceVariant), ), ], ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Đóng'), ), ], ), ); } } /// Profile Card Section Widget /// /// Isolated widget that depends on userInfoProvider. /// Shows loading/error/data states independently. class _ProfileCardSection extends ConsumerWidget { const _ProfileCardSection(); @override Widget build(BuildContext context, WidgetRef ref) { final colorScheme = Theme.of(context).colorScheme; final userInfoAsync = ref.watch(userInfoProvider); return Container( margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), child: userInfoAsync.when( loading: () => _buildLoadingCard(colorScheme), error: (error, stack) => _buildErrorCard(context, ref, error, colorScheme), data: (userInfo) => _buildProfileCard(context, userInfo, colorScheme), ), ); } Widget _buildLoadingCard(ColorScheme colorScheme) { return Container( padding: const EdgeInsets.all(AppSpacing.md), decoration: BoxDecoration( color: colorScheme.surface, borderRadius: BorderRadius.circular(AppRadius.card), boxShadow: [ BoxShadow( color: colorScheme.shadow.withValues(alpha: 0.05), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Row( children: [ // Avatar placeholder Container( width: 80, height: 80, decoration: BoxDecoration( shape: BoxShape.circle, color: colorScheme.surfaceContainerHighest, ), child: Center( child: CircularProgressIndicator( color: colorScheme.primary, strokeWidth: 2, ), ), ), const SizedBox(width: AppSpacing.md), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 20, width: 150, decoration: BoxDecoration( color: colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(4), ), ), const SizedBox(height: 8), Container( height: 14, width: 100, decoration: BoxDecoration( color: colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(4), ), ), ], ), ), ], ), ); } Widget _buildErrorCard(BuildContext context, WidgetRef ref, Object error, ColorScheme colorScheme) { return Container( padding: const EdgeInsets.all(AppSpacing.md), decoration: BoxDecoration( color: colorScheme.surface, borderRadius: BorderRadius.circular(AppRadius.card), boxShadow: [ BoxShadow( color: colorScheme.shadow.withValues(alpha: 0.05), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Row( children: [ Container( width: 80, height: 80, decoration: BoxDecoration( shape: BoxShape.circle, color: colorScheme.surfaceContainerHighest, ), child: const Center( child: FaIcon( FontAwesomeIcons.circleExclamation, color: AppColors.danger, size: 32, ), ), ), const SizedBox(width: AppSpacing.md), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Không thể tải thông tin', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: colorScheme.onSurface, ), ), const SizedBox(height: 8), GestureDetector( onTap: () => ref.read(userInfoProvider.notifier).refresh(), child: Text( 'Nhấn để thử lại', style: TextStyle( fontSize: 14, color: colorScheme.primary, fontWeight: FontWeight.w500, ), ), ), ], ), ), ], ), ); } Widget _buildProfileCard(BuildContext context, domain.UserInfo userInfo, ColorScheme colorScheme) { return Container( padding: const EdgeInsets.all(AppSpacing.md), decoration: BoxDecoration( color: colorScheme.surface, borderRadius: BorderRadius.circular(AppRadius.card), boxShadow: [ BoxShadow( color: colorScheme.shadow.withValues(alpha: 0.05), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Row( children: [ // Avatar with API data or gradient fallback userInfo.avatarUrl != null ? ClipOval( child: CachedNetworkImage( imageUrl: userInfo.avatarUrl!, width: 80, height: 80, fit: BoxFit.cover, placeholder: (context, url) => Container( width: 80, height: 80, decoration: BoxDecoration( shape: BoxShape.circle, color: colorScheme.primaryContainer, ), child: Center( child: CircularProgressIndicator( color: colorScheme.onPrimaryContainer, strokeWidth: 2, ), ), ), errorWidget: (context, url, error) => Container( width: 80, height: 80, decoration: BoxDecoration( shape: BoxShape.circle, color: colorScheme.primaryContainer, ), child: Center( child: Text( userInfo.initials, style: TextStyle( color: colorScheme.onPrimaryContainer, fontSize: 32, fontWeight: FontWeight.w700, ), ), ), ), ), ) : Container( width: 80, height: 80, decoration: BoxDecoration( shape: BoxShape.circle, color: colorScheme.primaryContainer, ), child: Center( child: Text( userInfo.initials, style: TextStyle( color: colorScheme.onPrimaryContainer, fontSize: 32, fontWeight: FontWeight.w700, ), ), ), ), const SizedBox(width: AppSpacing.md), // User info from API Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( userInfo.fullName, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: colorScheme.onSurface, ), ), const SizedBox(height: AppSpacing.xs), Text( '${_getRoleDisplayName(userInfo.role)} · Hạng ${userInfo.tierDisplayName}', style: TextStyle( fontSize: 13, color: colorScheme.onSurfaceVariant, ), ), if (userInfo.phoneNumber != null) ...[ const SizedBox(height: AppSpacing.xs), Text( userInfo.phoneNumber!, style: TextStyle( fontSize: 13, color: colorScheme.primary, ), ), ], ], ), ), ], ), ); } /// Get Vietnamese display name for user role String _getRoleDisplayName(UserRole role) { switch (role) { case UserRole.customer: return 'Khách hàng'; case UserRole.distributor: return 'Đại lý phân phối'; case UserRole.admin: return 'Quản trị viên'; case UserRole.staff: return 'Nhân viên'; } } } /// Logout Button Widget /// /// Isolated widget that handles logout functionality. class _LogoutButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final colorScheme = Theme.of(context).colorScheme; return Container( margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), width: double.infinity, child: OutlinedButton.icon( onPressed: () { _showLogoutConfirmation(context, ref, colorScheme); }, icon: const FaIcon(FontAwesomeIcons.arrowRightFromBracket, size: 18), label: const Text('Đăng xuất'), style: OutlinedButton.styleFrom( foregroundColor: AppColors.danger, side: const BorderSide(color: AppColors.danger, width: 1.5), padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.button), ), ), ), ); } /// Show logout confirmation dialog void _showLogoutConfirmation(BuildContext context, WidgetRef ref, ColorScheme colorScheme) { showDialog( context: context, builder: (context) => AlertDialog( backgroundColor: colorScheme.surface, title: Text( 'Đăng xuất', style: TextStyle(color: colorScheme.onSurface), ), content: Text( 'Bạn có chắc chắn muốn đăng xuất?', style: TextStyle(color: colorScheme.onSurfaceVariant), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Hủy'), ), TextButton( onPressed: () => _performLogout(context, ref), style: TextButton.styleFrom(foregroundColor: AppColors.danger), child: const Text('Đăng xuất'), ), ], ), ); } /// Perform logout operation /// /// Handles the complete logout process: /// 1. Close confirmation dialog /// 2. Show loading indicator /// 3. Clear ALL Hive local data (reset, not just user data) /// 4. Clear ALL Flutter Secure Storage keys /// 5. Call auth provider logout (clears session, gets new public session) /// 6. Navigate to login screen (handled by router redirect) /// 7. Show success message Future _performLogout(BuildContext context, WidgetRef ref) async { // Close confirmation dialog Navigator.of(context).pop(); // Show loading dialog unawaited(showDialog( context: context, barrierDismissible: false, builder: (context) => const Center( child: Card( child: Padding( padding: EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator(), SizedBox(height: 16), Text('Đang đăng xuất...'), ], ), ), ), ), )); try { // 1. Clear ALL Hive data (complete reset) await HiveInitializer.reset(); // 2. Clear ALL Flutter Secure Storage keys const secureStorage = FlutterSecureStorage( aOptions: AndroidOptions(encryptedSharedPreferences: true), iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock), ); await secureStorage.deleteAll(); // 3. Call auth provider logout // This will: // - Clear FrappeAuthService session // - Get new public session for login/registration // - Update auth state to null (logged out) await ref.read(authProvider.notifier).logout(); // Close loading dialog if (context.mounted) { Navigator.of(context).pop(); } // Show success message if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Đã đăng xuất thành công'), duration: Duration(seconds: 2), backgroundColor: AppColors.success, ), ); } // Navigation to login screen is handled automatically by GoRouter redirect // when auth state becomes null } catch (e) { // Close loading dialog if (context.mounted) { Navigator.of(context).pop(); } // Show error message if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Lỗi đăng xuất: ${e.toString()}'), duration: const Duration(seconds: 3), backgroundColor: AppColors.danger, ), ); } } } }