import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../core/utils/media_url.dart'; import '../../../../core/utils/validators.dart'; import '../../../../shared/models/user_management_models.dart'; import '../../../../shared/providers/auth_provider.dart'; import '../../../../shared/widgets/app_button.dart'; import '../../../../shared/widgets/app_text_field.dart'; import '../../../../shared/widgets/app_toast.dart'; import '../../../../shared/widgets/user_avatar.dart'; import '../../../auth/data/repositories/auth_repository_impl.dart'; class UserProfileScreen extends ConsumerStatefulWidget { const UserProfileScreen({super.key}); @override ConsumerState createState() => _UserProfileScreenState(); } class _UserProfileScreenState extends ConsumerState { final _formKey = GlobalKey(); final _fullNameController = TextEditingController(); final _nameController = TextEditingController(); final _emailController = TextEditingController(); final _mobileController = TextEditingController(); bool _isUpdatingProfile = false; bool _isUploadingAvatar = false; @override void initState() { super.initState(); _loadUser(); } void _loadUser() { final user = ref.read(authStateProvider).user; if (user == null) return; _fullNameController.text = user.name; _nameController.text = user.name; _emailController.text = user.email; _mobileController.text = user.mobile; } @override void dispose() { _fullNameController.dispose(); _nameController.dispose(); _emailController.dispose(); _mobileController.dispose(); super.dispose(); } Future _pickAndUploadAvatar() async { final result = await FilePicker.pickFiles( type: FileType.custom, allowedExtensions: const ['jpg', 'jpeg', 'png', 'webp'], withData: true, ); if (result == null || result.files.isEmpty) return; final file = result.files.first; final bytes = file.bytes; if (bytes == null || bytes.isEmpty) { if (!mounted) return; showAppToastFromSnackBar( context, const SnackBar(content: Text('Could not read the selected image.')), ); return; } setState(() => _isUploadingAvatar = true); final repository = ref.read(authRepositoryProvider); final uploadResult = await repository.uploadProfileAvatar( bytes: bytes, filename: file.name.isNotEmpty ? file.name : 'avatar.png', ); if (!mounted) return; if (uploadResult.failure != null) { setState(() => _isUploadingAvatar = false); showAppToastFromSnackBar( context, SnackBar( content: Text( uploadResult.failure?.message ?? 'Unable to upload profile picture.', ), ), ); return; } final current = ref.read(authStateProvider).user; if (current != null) { ref.read(authStateProvider.notifier).setUser( current.copyWith(avatarUrl: uploadResult.data), ); } // Sync role/department/avatar from /auth/me when available. await ref.read(authStateProvider.notifier).refreshCurrentUser(); if (!mounted) return; setState(() => _isUploadingAvatar = false); showAppToastFromSnackBar( context, const SnackBar(content: Text('Profile picture updated')), ); } Future _updateProfile() async { if (!_formKey.currentState!.validate()) return; setState(() => _isUpdatingProfile = true); final repository = ref.read(authRepositoryProvider); final result = await repository.updateProfile( UpdateProfileRequest( fullName: _fullNameController.text.trim(), name: _nameController.text.trim(), email: _emailController.text.trim(), mobile: _mobileController.text.trim(), ), ); if (!mounted) return; setState(() => _isUpdatingProfile = false); if (result.failure != null) { showAppToastFromSnackBar( context, SnackBar( content: Text( result.failure?.message ?? 'Unable to update profile.', ), ), ); return; } if (result.data != null) { ref.read(authStateProvider.notifier).setUser(result.data!); _fullNameController.text = result.data!.name; _nameController.text = result.data!.name; _emailController.text = result.data!.email; _mobileController.text = result.data!.mobile; } showAppToastFromSnackBar( context, const SnackBar(content: Text('Profile updated')), ); } @override Widget build(BuildContext context) { final user = ref.watch(authStateProvider).user; final theme = Theme.of(context); if (user == null) { return const Center(child: Text('Not signed in')); } final avatarUrl = resolveMediaUrl(user.avatarUrl); return SingleChildScrollView( padding: const EdgeInsets.all(24), child: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 640), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( 'User Profile', style: theme.textTheme.headlineSmall, ), const SizedBox(height: 24), Center( child: Stack( clipBehavior: Clip.none, children: [ UserAvatar( name: user.name, avatarUrl: avatarUrl, radius: 48, ), Positioned( bottom: 0, right: 0, child: IconButton.filled( onPressed: _isUploadingAvatar ? null : _pickAndUploadAvatar, icon: _isUploadingAvatar ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : const Icon(Icons.camera_alt, size: 18), ), ), ], ), ), const SizedBox(height: 24), Form( key: _formKey, child: Column( children: [ AppTextField( controller: _fullNameController, label: 'Full Name', validator: (v) => Validators.required(v, fieldName: 'Full Name'), ), const SizedBox(height: 16), AppTextField( controller: _nameController, label: 'Name', validator: (v) => Validators.required(v, fieldName: 'Name'), ), const SizedBox(height: 16), AppTextField( controller: _emailController, label: 'Email', keyboardType: TextInputType.emailAddress, validator: Validators.email, ), const SizedBox(height: 16), AppTextField( controller: _mobileController, label: 'Mobile', keyboardType: TextInputType.phone, validator: Validators.mobile, inputFormatters: Validators.mobileInput, ), const SizedBox(height: 8), Align( alignment: Alignment.centerLeft, child: Text('Department: ${user.department ?? '—'}'), ), const SizedBox(height: 4), Align( alignment: Alignment.centerLeft, child: Text('Role: ${user.role}'), ), const SizedBox(height: 24), AppButton( label: 'Update Profile', isLoading: _isUpdatingProfile, onPressed: _updateProfile, ), ], ), ), ], ), ), ), ); } }