/// Addresses Page /// /// Displays list of saved addresses with management options. /// Features: /// - List of saved addresses /// - Default address indicator /// - Edit/delete actions /// - Set as default functionality /// - Add new address library; import 'package:flutter/material.dart'; import 'package:worker/core/widgets/loading_indicator.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/router/app_router.dart'; import 'package:worker/core/theme/colors.dart'; import 'package:worker/features/account/domain/entities/address.dart'; import 'package:worker/features/account/presentation/providers/address_provider.dart'; import 'package:worker/features/account/presentation/widgets/address_card.dart'; /// Addresses Page /// /// Page for managing saved delivery addresses. /// Supports selection mode for returning selected address. class AddressesPage extends HookConsumerWidget { const AddressesPage({super.key, this.extra}); final Map? extra; @override Widget build(BuildContext context, WidgetRef ref) { // Check if in selection mode final selectMode = extra?['selectMode'] == true; final currentAddress = extra?['currentAddress'] as Address?; // Selected address state (for selection mode) final selectedAddress = useState(currentAddress); // Watch addresses from API final addressesAsync = ref.watch(addressesProvider); final colorScheme = Theme.of(context).colorScheme; return Scaffold( backgroundColor: colorScheme.surfaceContainerLowest, appBar: AppBar( backgroundColor: colorScheme.surface, elevation: AppBarSpecs.elevation, leading: IconButton( icon: FaIcon( FontAwesomeIcons.arrowLeft, color: colorScheme.onSurface, size: 20, ), onPressed: () => context.pop(), ), title: Text( selectMode ? 'Chọn địa chỉ' : 'Địa chỉ của bạn', style: TextStyle(color: colorScheme.onSurface), ), foregroundColor: colorScheme.onSurface, centerTitle: false, actions: [ IconButton( icon: FaIcon( FontAwesomeIcons.circleInfo, color: colorScheme.onSurface, size: 20, ), onPressed: () { _showInfoDialog(context); }, ), const SizedBox(width: AppSpacing.sm), ], ), body: addressesAsync.when( data: (addresses) => Column( children: [ // Address List Expanded( child: RefreshIndicator( onRefresh: () async { await ref.read(addressesProvider.notifier).refresh(); }, child: addresses.isEmpty ? _buildEmptyState(context, colorScheme) : ListView.separated( padding: const EdgeInsets.all(AppSpacing.md), itemCount: addresses.length, separatorBuilder: (context, index) => const SizedBox(height: AppSpacing.md), itemBuilder: (context, index) { final address = addresses[index]; final isSelected = selectedAddress.value?.name == address.name; // In selection mode, show radio button if (selectMode) { return AddressCard( name: address.addressTitle, phone: address.phone, address: address.fullAddress, isDefault: address.isDefault, showRadio: true, isSelected: isSelected, onRadioTap: () { selectedAddress.value = address; }, // Keep edit/delete actions in selection mode onEdit: () { context.push( RouteNames.addressForm, extra: address, ); }, onDelete: () { _showDeleteConfirmation(context, ref, address); }, onSetDefault: null, // Hide set default in selection mode ); } // Normal mode - show all actions return AddressCard( name: address.addressTitle, phone: address.phone, address: address.fullAddress, isDefault: address.isDefault, onEdit: () { context.push( RouteNames.addressForm, extra: address, ); }, onDelete: () { _showDeleteConfirmation(context, ref, address); }, onSetDefault: () { _setDefaultAddress(context, ref, address); }, ); }, ), ), ), // Bottom Buttons Padding( padding: const EdgeInsets.all(AppSpacing.md), child: selectMode ? Row( children: [ // Add New Address Button (Selection Mode) Expanded( child: OutlinedButton.icon( onPressed: () { context.push(RouteNames.addressForm); }, icon: const FaIcon(FontAwesomeIcons.plus, size: 16), label: const Text( 'Thêm mới', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, ), ), style: OutlinedButton.styleFrom( foregroundColor: colorScheme.primary, side: BorderSide( color: colorScheme.primary, width: 1.5, ), padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( AppRadius.button, ), ), ), ), ), const SizedBox(width: AppSpacing.md), // Select Address Button Expanded( flex: 2, child: ElevatedButton.icon( onPressed: selectedAddress.value == null ? null : () { // Return selected address without setting as default context.pop(selectedAddress.value); }, icon: const FaIcon( FontAwesomeIcons.check, size: 16, ), label: const Text( 'Chọn địa chỉ này', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, ), ), style: ElevatedButton.styleFrom( backgroundColor: colorScheme.primary, foregroundColor: colorScheme.onPrimary, disabledBackgroundColor: colorScheme.surfaceContainerHighest, disabledForegroundColor: colorScheme.onSurfaceVariant, padding: const EdgeInsets.symmetric(vertical: 14), elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( AppRadius.button, ), ), ), ), ), ], ) : SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: () { context.push(RouteNames.addressForm); }, icon: const FaIcon(FontAwesomeIcons.plus, size: 18), label: const Text( 'Thêm địa chỉ mới', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, ), ), style: ElevatedButton.styleFrom( backgroundColor: colorScheme.primary, foregroundColor: colorScheme.onPrimary, padding: const EdgeInsets.symmetric(vertical: 14), elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( AppRadius.button, ), ), ), ), ), ), ], ), loading: () => const CustomLoadingIndicator(), error: (error, stack) => Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const FaIcon( FontAwesomeIcons.triangleExclamation, size: 64, color: AppColors.danger, ), const SizedBox(height: 16), Text( 'Không thể tải danh sách địa chỉ', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: colorScheme.onSurface, ), ), const SizedBox(height: 8), Text( error.toString(), style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant), textAlign: TextAlign.center, ), const SizedBox(height: 24), ElevatedButton.icon( onPressed: () { ref.read(addressesProvider.notifier).refresh(); }, icon: const FaIcon(FontAwesomeIcons.arrowsRotate, size: 18), label: const Text('Thử lại'), style: ElevatedButton.styleFrom( backgroundColor: colorScheme.primary, foregroundColor: colorScheme.onPrimary, ), ), ], ), ), ), ); } /// Build empty state Widget _buildEmptyState(BuildContext context, ColorScheme colorScheme) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ FaIcon( FontAwesomeIcons.locationDot, size: 64, color: colorScheme.onSurfaceVariant.withValues(alpha: 0.4), ), const SizedBox(height: 16), Text( 'Chưa có địa chỉ nào', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: colorScheme.onSurface, ), ), const SizedBox(height: 8), Text( 'Thêm địa chỉ để nhận hàng nhanh hơn', style: TextStyle( fontSize: 14, color: colorScheme.onSurfaceVariant.withValues(alpha: 0.8), ), ), const SizedBox(height: 24), ElevatedButton.icon( onPressed: () { context.push(RouteNames.addressForm); }, icon: const FaIcon(FontAwesomeIcons.plus, size: 18), label: const Text( 'Thêm địa chỉ mới', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600), ), style: ElevatedButton.styleFrom( backgroundColor: colorScheme.primary, foregroundColor: colorScheme.onPrimary, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.button), ), ), ), ], ), ); } /// Set address as default void _setDefaultAddress( BuildContext context, WidgetRef ref, Address address, ) { ref.read(addressesProvider.notifier).setDefaultAddress(address.name); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Row( children: [ FaIcon( FontAwesomeIcons.circleCheck, color: Colors.white, size: 18, ), SizedBox(width: 12), Text('Đã đặt làm địa chỉ mặc định'), ], ), backgroundColor: AppColors.success, duration: Duration(seconds: 2), ), ); } /// Show info dialog void _showInfoDialog(BuildContext context) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text( 'Hướng dẫn sử dụng', style: TextStyle(fontWeight: FontWeight.bold), ), content: const SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text('Quản lý địa chỉ giao hàng của bạn:'), SizedBox(height: 12), Text('• Thêm địa chỉ mới để dễ dàng đặt hàng'), Text('• Đặt địa chỉ mặc định cho đơn hàng'), Text('• Chỉnh sửa hoặc xóa địa chỉ bất kỳ'), Text('• Lưu nhiều địa chỉ cho các mục đích khác nhau'), ], ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Đóng'), ), ], ), ); } /// Show delete confirmation dialog void _showDeleteConfirmation( BuildContext context, WidgetRef ref, Address address, ) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Xóa địa chỉ'), content: const Text('Bạn có chắc chắn muốn xóa địa chỉ này?'), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Hủy'), ), TextButton( onPressed: () { Navigator.pop(context); _deleteAddress(context, ref, address); }, style: TextButton.styleFrom(foregroundColor: AppColors.danger), child: const Text('Xóa'), ), ], ), ); } /// Delete address void _deleteAddress( BuildContext context, WidgetRef ref, Address address, ) async { try { await ref.read(addressesProvider.notifier).deleteAddress(address.name); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Row( children: [ FaIcon( FontAwesomeIcons.circleCheck, color: Colors.white, size: 18, ), SizedBox(width: 12), Text('Đã xóa địa chỉ'), ], ), backgroundColor: AppColors.success, duration: Duration(seconds: 2), ), ); } } catch (e) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ const FaIcon( FontAwesomeIcons.circleExclamation, color: Colors.white, size: 18, ), const SizedBox(width: 12), Text('Lỗi: ${e.toString()}'), ], ), backgroundColor: AppColors.danger, duration: const Duration(seconds: 3), ), ); } } } }