import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../core/api/api_error_mapper.dart'; import '../../../core/l10n/l10n.dart'; import '../../auth/data/auth_providers.dart'; import '../data/profile_repository.dart'; import '../domain/user_profile.dart'; import 'user_aliases_screen.dart'; class ProfileScreen extends ConsumerStatefulWidget { const ProfileScreen({super.key}); @override ConsumerState createState() => _ProfileScreenState(); } class _ProfileScreenState extends ConsumerState { final _formKey = GlobalKey(); bool _isLoading = true; bool _isSaving = false; String? _error; UserProfile? _profile; late final TextEditingController _emailCtrl; late final TextEditingController _firstNameCtrl; late final TextEditingController _lastNameCtrl; @override void initState() { super.initState(); _emailCtrl = TextEditingController(); _firstNameCtrl = TextEditingController(); _lastNameCtrl = TextEditingController(); _loadProfile(); } @override void dispose() { _emailCtrl.dispose(); _firstNameCtrl.dispose(); _lastNameCtrl.dispose(); super.dispose(); } Future _loadProfile() async { setState(() { _isLoading = true; _error = null; }); try { final profile = await ref.read(profileRepositoryProvider).getMe(); if (!mounted) return; setState(() { _profile = profile; _emailCtrl.text = profile.email; _firstNameCtrl.text = profile.firstName ?? ''; _lastNameCtrl.text = profile.lastName ?? ''; }); } catch (e) { if (!mounted) return; setState(() => _error = mapErrorToUserMessage(e, context)); } finally { if (mounted) setState(() => _isLoading = false); } } Future _save() async { if (!_formKey.currentState!.validate()) return; setState(() => _isSaving = true); try { final updated = await ref.read(profileRepositoryProvider).updateMe( email: _emailCtrl.text.trim(), firstName: _firstNameCtrl.text.trim().isEmpty ? null : _firstNameCtrl.text.trim(), lastName: _lastNameCtrl.text.trim().isEmpty ? null : _lastNameCtrl.text.trim(), ); if (!mounted) return; setState(() => _profile = updated); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(context.l10n.profileSaved)), ); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( buildCopyableErrorSnackBar(context, mapErrorToUserMessage(e, context)), ); } finally { if (mounted) setState(() => _isSaving = false); } } Future _logout() async { await ref.read(authStateProvider.notifier).logout(); if (!mounted) return; context.go('/login'); } Widget _buildProfileForm(BuildContext context, ThemeData theme) { return Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( context.l10n.profileUsernameLabel, style: theme.textTheme.labelMedium?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 4), Text(_profile?.username ?? '', style: theme.textTheme.bodyLarge), const Divider(height: 32), TextFormField( controller: _emailCtrl, decoration: InputDecoration( labelText: context.l10n.profileEmailLabel, border: const OutlineInputBorder(), ), keyboardType: TextInputType.emailAddress, validator: (v) { if (v == null || v.isEmpty) return context.l10n.profileEmailHint; if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(v)) { return context.l10n.profileEmailInvalid; } return null; }, ), const SizedBox(height: 16), TextFormField( controller: _firstNameCtrl, decoration: InputDecoration( labelText: context.l10n.profileFirstNameLabel, border: const OutlineInputBorder(), ), ), const SizedBox(height: 16), TextFormField( controller: _lastNameCtrl, decoration: InputDecoration( labelText: context.l10n.profileLastNameLabel, border: const OutlineInputBorder(), ), ), const SizedBox(height: 24), SizedBox( width: double.infinity, child: FilledButton.icon( onPressed: _isSaving ? null : _save, icon: _isSaving ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.save_outlined), label: Text(context.l10n.profileSaveAction), ), ), ], ), ); } @override Widget build(BuildContext context) { final theme = Theme.of(context); if (_isLoading) { return const Center(child: CircularProgressIndicator()); } if (_error != null) { return buildCopyableErrorPanel( context: context, message: _error!, onRetry: _loadProfile, title: 'Kunde inte läsa profilen', ); } final profile = _profile!; return ListView( padding: const EdgeInsets.all(16), children: [ Card( child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ CircleAvatar( radius: 28, child: Text( (profile.username.isNotEmpty ? profile.username[0] : '?').toUpperCase(), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(profile.username, style: theme.textTheme.titleLarge), const SizedBox(height: 4), Text( profile.email, style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), if ((profile.firstName ?? '').isNotEmpty || (profile.lastName ?? '').isNotEmpty) ...[ const SizedBox(height: 4), Text( [profile.firstName, profile.lastName] .where((part) => part != null && part.trim().isNotEmpty) .join(' '), style: theme.textTheme.bodySmall, ), ], ], ), ), if (profile.isAdmin) Chip( label: const Text('Admin'), avatar: const Icon(Icons.shield_outlined, size: 16), backgroundColor: theme.colorScheme.primaryContainer, labelStyle: TextStyle(color: theme.colorScheme.onPrimaryContainer), ), ], ), ), ), const SizedBox(height: 12), Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Min profil', style: theme.textTheme.titleMedium), const SizedBox(height: 8), Text( 'Här uppdaterar du kontaktuppgifter och ditt namn. Alias och importrelaterad data finns i en separat vy.', style: theme.textTheme.bodyMedium, ), const SizedBox(height: 12), _buildProfileForm(context, theme), ], ), ), ), const SizedBox(height: 12), Card( child: ListTile( leading: const Icon(Icons.link_outlined), title: const Text('Mina kvittoalias'), subtitle: const Text( 'Visa privata alias och globala fallback-alias som används i receipt-importen.', ), trailing: const Icon(Icons.chevron_right), onTap: () => Navigator.of(context).push( MaterialPageRoute(builder: (_) => const UserAliasesScreen()), ), ), ), const SizedBox(height: 12), Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Snabbåtgärder', style: theme.textTheme.titleMedium), const SizedBox(height: 8), Text( 'Logga ut eller gå vidare till aliasvyn när du behöver granska importmatchningar.', style: theme.textTheme.bodyMedium, ), const SizedBox(height: 12), SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: _logout, icon: const Icon(Icons.logout), label: Text(context.l10n.logoutAction), ), ), ], ), ), ), ], ); } }