/// Profile Edit Page /// /// Allows users to edit their profile information. /// Features: /// - Avatar upload with image picker /// - Form fields for personal information /// - Form validation /// - Save/cancel actions library; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/theme/colors.dart'; /// Profile Edit Page /// /// Page for editing user profile information with avatar upload. class ProfileEditPage extends HookConsumerWidget { const ProfileEditPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { // Form key for validation final formKey = useMemoized(() => GlobalKey()); // Image picker final selectedImage = useState(null); // Form controllers final nameController = useTextEditingController(text: 'Hoàng Minh Hiệp'); final phoneController = useTextEditingController(text: '0347302911'); final emailController = useTextEditingController( text: 'hoanghiep@example.com', ); final birthDateController = useTextEditingController(text: '15/03/1985'); final idNumberController = useTextEditingController(text: '123456789012'); final taxIdController = useTextEditingController(text: '0359837618'); final companyController = useTextEditingController( text: 'Công ty TNHH Xây dựng ABC', ); final addressController = useTextEditingController( text: '123 Man Thiện, Thủ Đức, Hồ Chí Minh', ); final experienceController = useTextEditingController(text: '10'); // Dropdown values final selectedGender = useState('male'); final selectedPosition = useState('contractor'); // Has unsaved changes final hasChanges = useState(false); return PopScope( canPop: !hasChanges.value, onPopInvokedWithResult: (didPop, result) async { if (didPop) return; final shouldPop = await _showUnsavedChangesDialog(context); if (shouldPop == true && context.mounted) { Navigator.of(context).pop(); } }, child: Scaffold( backgroundColor: const Color(0xFFF4F6F8), appBar: AppBar( backgroundColor: Colors.white, elevation: 0, leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.black), onPressed: () async { if (hasChanges.value) { final shouldPop = await _showUnsavedChangesDialog(context); if (shouldPop == true && context.mounted) { context.pop(); } } else { context.pop(); } }, ), title: const Text( 'Thông tin cá nhân', style: TextStyle( color: Colors.black, fontSize: 18, fontWeight: FontWeight.bold, ), ), centerTitle: false, actions: const [SizedBox(width: AppSpacing.sm)], ), body: Form( key: formKey, onChanged: () { hasChanges.value = true; }, child: SingleChildScrollView( child: Column( children: [ const SizedBox(height: AppSpacing.md), // Profile Avatar Section _buildAvatarSection(context, selectedImage), const SizedBox(height: AppSpacing.md), // Form Card Container( margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md), padding: const EdgeInsets.all(AppSpacing.md), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(AppRadius.card), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Full Name _buildTextField( label: 'Họ và tên', controller: nameController, required: true, validator: (value) { if (value == null || value.isEmpty) { return 'Vui lòng nhập họ và tên'; } return null; }, ), const SizedBox(height: AppSpacing.md), // Phone _buildTextField( label: 'Số điện thoại', controller: phoneController, required: true, keyboardType: TextInputType.phone, validator: (value) { if (value == null || value.isEmpty) { return 'Vui lòng nhập số điện thoại'; } return null; }, ), const SizedBox(height: AppSpacing.md), // Email _buildTextField( label: 'Email', controller: emailController, keyboardType: TextInputType.emailAddress, ), const SizedBox(height: AppSpacing.md), // Birth Date _buildDateField( context: context, label: 'Ngày sinh', controller: birthDateController, ), const SizedBox(height: AppSpacing.md), // Gender _buildDropdownField( label: 'Giới tính', value: selectedGender.value, items: const [ {'value': 'male', 'label': 'Nam'}, {'value': 'female', 'label': 'Nữ'}, {'value': 'other', 'label': 'Khác'}, ], onChanged: (value) { if (value != null) { selectedGender.value = value; hasChanges.value = true; } }, ), const SizedBox(height: AppSpacing.md), // ID Number _buildTextField( label: 'Số CMND/CCCD', controller: idNumberController, keyboardType: TextInputType.number, ), const SizedBox(height: AppSpacing.md), // Tax ID _buildTextField( label: 'Mã số thuế', controller: taxIdController, ), const SizedBox(height: AppSpacing.md), // Company _buildTextField( label: 'Công ty', controller: companyController, ), const SizedBox(height: AppSpacing.md), // Address _buildTextField( label: 'Địa chỉ', controller: addressController, maxLines: 2, ), const SizedBox(height: AppSpacing.md), // Position _buildDropdownField( label: 'Chức vụ', value: selectedPosition.value, items: const [ {'value': 'contractor', 'label': 'Thầu thợ'}, {'value': 'architect', 'label': 'Kiến trúc sư'}, {'value': 'dealer', 'label': 'Đại lý phân phối'}, {'value': 'broker', 'label': 'Môi giới'}, {'value': 'other', 'label': 'Khác'}, ], onChanged: (value) { if (value != null) { selectedPosition.value = value; hasChanges.value = true; } }, ), const SizedBox(height: AppSpacing.md), // Experience _buildTextField( label: 'Kinh nghiệm (năm)', controller: experienceController, keyboardType: TextInputType.number, ), ], ), ), const SizedBox(height: AppSpacing.lg), // Action Buttons _buildActionButtons( context: context, formKey: formKey, hasChanges: hasChanges, ), const SizedBox(height: AppSpacing.lg), ], ), ), ), ), ); } /// Build avatar section with edit button Widget _buildAvatarSection( BuildContext context, ValueNotifier selectedImage, ) { return Center( child: Stack( children: [ // Avatar Container( width: 100, height: 100, decoration: BoxDecoration( shape: BoxShape.circle, color: AppColors.primaryBlue, image: selectedImage.value != null ? DecorationImage( image: FileImage(selectedImage.value!), fit: BoxFit.cover, ) : null, ), child: selectedImage.value == null ? const Center( child: Text( 'HMH', style: TextStyle( fontSize: 32, fontWeight: FontWeight.bold, color: Colors.white, ), ), ) : null, ), // Edit Button Positioned( bottom: 0, right: 0, child: GestureDetector( onTap: () async { await _pickImage(context, selectedImage); }, child: Container( width: 32, height: 32, decoration: BoxDecoration( color: AppColors.primaryBlue, shape: BoxShape.circle, border: Border.all(color: Colors.white, width: 2), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.1), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: const Icon( Icons.camera_alt, size: 16, color: Colors.white, ), ), ), ), ], ), ); } /// Build text field Widget _buildTextField({ required String label, required TextEditingController controller, bool required = false, TextInputType? keyboardType, int maxLines = 1, String? Function(String?)? validator, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ RichText( text: TextSpan( text: label, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xFF1E293B), ), children: [ if (required) const TextSpan( text: ' *', style: TextStyle(color: AppColors.danger), ), ], ), ), const SizedBox(height: 8), TextFormField( controller: controller, keyboardType: keyboardType, maxLines: maxLines, validator: validator, decoration: InputDecoration( hintText: 'Nhập $label', hintStyle: TextStyle( color: AppColors.grey500.withValues(alpha: 0.6), fontSize: 14, ), filled: true, fillColor: const Color(0xFFF8FAFC), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(AppRadius.input), borderSide: const BorderSide(color: Color(0xFFE2E8F0)), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(AppRadius.input), borderSide: const BorderSide(color: Color(0xFFE2E8F0)), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(AppRadius.input), borderSide: const BorderSide( color: AppColors.primaryBlue, width: 2, ), ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(AppRadius.input), borderSide: const BorderSide(color: AppColors.danger), ), focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(AppRadius.input), borderSide: const BorderSide(color: AppColors.danger, width: 2), ), ), ), ], ); } /// Build date field Widget _buildDateField({ required BuildContext context, required String label, required TextEditingController controller, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xFF1E293B), ), ), const SizedBox(height: 8), TextFormField( controller: controller, readOnly: true, onTap: () async { final date = await showDatePicker( context: context, initialDate: DateTime(1985, 3, 15), firstDate: DateTime(1940), lastDate: DateTime.now(), ); if (date != null) { controller.text = '${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year}'; } }, decoration: InputDecoration( hintText: 'Chọn ngày sinh', hintStyle: TextStyle( color: AppColors.grey500.withValues(alpha: 0.6), fontSize: 14, ), filled: true, fillColor: const Color(0xFFF8FAFC), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), suffixIcon: const Icon(Icons.calendar_today, size: 20), border: OutlineInputBorder( borderRadius: BorderRadius.circular(AppRadius.input), borderSide: const BorderSide(color: Color(0xFFE2E8F0)), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(AppRadius.input), borderSide: const BorderSide(color: Color(0xFFE2E8F0)), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(AppRadius.input), borderSide: const BorderSide( color: AppColors.primaryBlue, width: 2, ), ), ), ), ], ); } /// Build dropdown field Widget _buildDropdownField({ required String label, required String value, required List> items, required ValueChanged onChanged, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xFF1E293B), ), ), const SizedBox(height: 8), DropdownButtonFormField( initialValue: value, onChanged: onChanged, decoration: InputDecoration( filled: true, fillColor: const Color(0xFFF8FAFC), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(AppRadius.input), borderSide: const BorderSide(color: Color(0xFFE2E8F0)), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(AppRadius.input), borderSide: const BorderSide(color: Color(0xFFE2E8F0)), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(AppRadius.input), borderSide: const BorderSide( color: AppColors.primaryBlue, width: 2, ), ), ), items: items.map((item) { return DropdownMenuItem( value: item['value'], child: Text(item['label']!, style: const TextStyle(fontSize: 14)), ); }).toList(), ), ], ); } /// Build action buttons Widget _buildActionButtons({ required BuildContext context, required GlobalKey formKey, required ValueNotifier hasChanges, }) { return Padding( padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md), child: Row( children: [ // Cancel Button Expanded( child: OutlinedButton( onPressed: () { context.pop(); }, style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 14), side: const BorderSide(color: AppColors.grey100), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.button), ), ), child: const Text( 'Hủy bỏ', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.grey900, ), ), ), ), const SizedBox(width: AppSpacing.sm), // Save Button Expanded( flex: 2, child: ElevatedButton.icon( onPressed: () { if (formKey.currentState?.validate() ?? false) { // TODO: Save profile data ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Thông tin đã được cập nhật thành công!'), backgroundColor: AppColors.success, duration: Duration(seconds: 2), ), ); hasChanges.value = false; context.pop(); } }, icon: const Icon(Icons.save, size: 20), label: const Text( 'Lưu thay đổi', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), ), style: ElevatedButton.styleFrom( backgroundColor: AppColors.primaryBlue, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.button), ), ), ), ), ], ), ); } /// Pick image from gallery or camera Future _pickImage( BuildContext context, ValueNotifier selectedImage, ) async { final ImagePicker picker = ImagePicker(); // Show dialog to choose source final source = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Chọn ảnh từ'), content: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: const Icon(Icons.camera_alt), title: const Text('Máy ảnh'), onTap: () => Navigator.pop(context, ImageSource.camera), ), ListTile( leading: const Icon(Icons.photo_library), title: const Text('Thư viện ảnh'), onTap: () => Navigator.pop(context, ImageSource.gallery), ), ], ), ), ); if (source != null) { final XFile? image = await picker.pickImage( source: source, maxWidth: 512, maxHeight: 512, imageQuality: 85, ); if (image != null) { selectedImage.value = File(image.path); } } } /// Show unsaved changes dialog Future _showUnsavedChangesDialog(BuildContext context) { return showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Thay đổi chưa được lưu'), content: const Text( 'Bạn có thay đổi chưa được lưu. Bạn có muốn thoát không?', ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('Ở lại'), ), TextButton( onPressed: () => Navigator.pop(context, true), style: TextButton.styleFrom(foregroundColor: AppColors.danger), child: const Text('Thoát'), ), ], ), ); } }