Files
network-app-mobile/lib/features/profile/presentation/profile_screen.dart

256 lines
8.6 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../shared/services/api_client.dart';
import '../../../shared/providers/auth_provider.dart';
final profileProvider = FutureProvider.autoDispose<Map<String, dynamic>>((ref) async {
final apiClient = ref.watch(apiClientProvider);
return apiClient.getProfile();
});
class ProfileScreen extends ConsumerStatefulWidget {
const ProfileScreen({super.key});
@override
ConsumerState<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends ConsumerState<ProfileScreen> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _titleController = TextEditingController();
final _companyController = TextEditingController();
final _phoneController = TextEditingController();
final _signatureController = TextEditingController();
bool _isLoading = false;
bool _initialized = false;
@override
void dispose() {
_nameController.dispose();
_titleController.dispose();
_companyController.dispose();
_phoneController.dispose();
_signatureController.dispose();
super.dispose();
}
void _initializeForm(Map<String, dynamic> profile) {
if (!_initialized) {
_nameController.text = profile['name'] ?? '';
_titleController.text = profile['title'] ?? '';
_companyController.text = profile['company'] ?? '';
_phoneController.text = profile['phone'] ?? '';
_signatureController.text = profile['emailSignature'] ?? '';
_initialized = true;
}
}
Future<void> _saveProfile() async {
if (!_formKey.currentState!.validate()) return;
setState(() => _isLoading = true);
try {
await ref.read(apiClientProvider).updateProfile({
'name': _nameController.text.trim(),
'title': _titleController.text.trim(),
'company': _companyController.text.trim(),
'phone': _phoneController.text.trim(),
'emailSignature': _signatureController.text.trim(),
});
ref.invalidate(profileProvider);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Profile saved')),
);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to save: $e')),
);
}
} finally {
if (mounted) {
setState(() => _isLoading = false);
}
}
}
@override
Widget build(BuildContext context) {
final profileAsync = ref.watch(profileProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Profile'),
actions: [
IconButton(
icon: const Icon(Icons.logout),
tooltip: 'Sign Out',
onPressed: () async {
await ref.read(authStateProvider.notifier).signOut();
},
),
],
),
body: profileAsync.when(
data: (profile) {
_initializeForm(profile);
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Avatar
Center(
child: CircleAvatar(
radius: 48,
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
child: Text(
_getInitials(profile['name'] ?? ''),
style: TextStyle(
fontSize: 32,
color: Theme.of(context).colorScheme.onPrimaryContainer,
fontWeight: FontWeight.bold,
),
),
),
),
const SizedBox(height: 8),
Center(
child: Text(
profile['email'] ?? '',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.grey,
),
),
),
const SizedBox(height: 32),
// Form fields
Text(
'Basic Info',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
TextFormField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Full Name',
hintText: 'John Smith',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Name is required';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _titleController,
decoration: const InputDecoration(
labelText: 'Title',
hintText: 'Senior Wealth Advisor',
),
),
const SizedBox(height: 16),
TextFormField(
controller: _companyController,
decoration: const InputDecoration(
labelText: 'Company',
hintText: 'ABC Financial Group',
),
),
const SizedBox(height: 16),
TextFormField(
controller: _phoneController,
decoration: const InputDecoration(
labelText: 'Phone',
hintText: '(555) 123-4567',
),
keyboardType: TextInputType.phone,
),
const SizedBox(height: 32),
Text(
'Email Signature',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
'Optional custom signature for generated emails',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Colors.grey,
),
),
const SizedBox(height: 16),
TextFormField(
controller: _signatureController,
decoration: const InputDecoration(
hintText: 'Best regards,\nJohn Smith\nSenior Wealth Advisor\n(555) 123-4567',
alignLabelWithHint: true,
),
maxLines: 5,
),
const SizedBox(height: 32),
FilledButton(
onPressed: _isLoading ? null : _saveProfile,
child: _isLoading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Save Profile'),
),
],
),
),
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, size: 64, color: Colors.red.shade300),
const SizedBox(height: 16),
const Text('Failed to load profile'),
TextButton(
onPressed: () => ref.invalidate(profileProvider),
child: const Text('Retry'),
),
],
),
),
),
);
}
String _getInitials(String name) {
final parts = name.trim().split(' ');
if (parts.isEmpty) return '?';
if (parts.length == 1) return parts[0][0].toUpperCase();
return '${parts[0][0]}${parts[parts.length - 1][0]}'.toUpperCase();
}
}