import 'package:flutter/material.dart'; import '../services/intra_api_exception.dart'; import '../services/intra_profile_service.dart'; /// Vue 2 — profil complet (étape 4). class ProfileScreen extends StatefulWidget { const ProfileScreen({super.key, required this.user}); /// Résultat minimal de la recherche (au moins `id` + `login`). final Map user; @override State createState() => _ProfileScreenState(); } class _ProfileScreenState extends State { final _profiles = IntraProfileService.instance; bool _loading = true; String? _error; IntraUserProfile? _profile; @override void initState() { super.initState(); _load(); } Future _load() async { setState(() { _loading = true; _error = null; }); try { final profile = await _profiles.fetchProfile(widget.user); if (!mounted) return; setState(() { _profile = profile; _loading = false; }); } on IntraApiException catch (e) { if (!mounted) return; setState(() { _error = e.message; _loading = false; }); } catch (e) { if (!mounted) return; setState(() { _error = e.toString(); _loading = false; }); } } String? _imageUrl(Map user) { final image = user['image']; if (image is Map) { final link = image['link']; if (link is String && link.isNotEmpty) return link; } final url = user['image_url']; if (url is String && url.isNotEmpty) return url; return null; } String _display(dynamic value, {String fallback = '—'}) { if (value == null) return fallback; final text = value.toString().trim(); return text.isEmpty ? fallback : text; } @override Widget build(BuildContext context) { final user = _profile?.user ?? widget.user; final login = _display(user['login'], fallback: '?'); final imageUrl = _imageUrl(user); return Scaffold( appBar: AppBar( title: Text(login), backgroundColor: const Color(0xFF00BABC), foregroundColor: Colors.black, ), body: _loading ? const Center(child: CircularProgressIndicator()) : _error != null ? _ErrorBody(message: _error!, onRetry: _load) : LayoutBuilder( builder: (context, constraints) { final wide = constraints.maxWidth >= 720; final profile = _profile!; final header = _ProfileHeader( login: login, imageUrl: imageUrl, ); final details = _DetailsCard( user: profile.user, cursusLevel: profile.cursusLevel, display: _display, ); final skills = _SkillsCard(skills: profile.skills); final projects = _ProjectsCard(projects: profile.projects); if (wide) { return SingleChildScrollView( padding: const EdgeInsets.all(24), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 2, child: Column( children: [ header, const SizedBox(height: 16), details, ], ), ), const SizedBox(width: 24), Expanded( flex: 3, child: Column( children: [ skills, const SizedBox(height: 16), projects, ], ), ), ], ), ); } return SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ header, const SizedBox(height: 16), details, const SizedBox(height: 16), skills, const SizedBox(height: 16), projects, ], ), ); }, ), ); } } class _ErrorBody extends StatelessWidget { const _ErrorBody({required this.message, required this.onRetry}); final String message; final VoidCallback onRetry; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( message, textAlign: TextAlign.center, style: TextStyle(color: Theme.of(context).colorScheme.error), ), const SizedBox(height: 16), FilledButton(onPressed: onRetry, child: const Text('Réessayer')), ], ), ); } } class _ProfileHeader extends StatelessWidget { const _ProfileHeader({required this.login, required this.imageUrl}); final String login; final String? imageUrl; @override Widget build(BuildContext context) { return Column( children: [ CircleAvatar( radius: 56, backgroundImage: imageUrl != null ? NetworkImage(imageUrl!) : null, child: imageUrl == null ? Text( login.isNotEmpty ? login[0].toUpperCase() : '?', style: const TextStyle(fontSize: 32), ) : null, ), const SizedBox(height: 12), Text( login, textAlign: TextAlign.center, style: Theme.of(context).textTheme.headlineSmall, ), ], ); } } class _DetailsCard extends StatelessWidget { const _DetailsCard({ required this.user, required this.cursusLevel, required this.display, }); final Map user; final double? cursusLevel; final String Function(dynamic value, {String fallback}) display; @override Widget build(BuildContext context) { final levelText = cursusLevel != null ? cursusLevel!.toStringAsFixed(2) : display(user['level']); return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Informations', style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 12), _DetailRow(label: 'Email', value: display(user['email'])), _DetailRow(label: 'Téléphone', value: display(user['phone'])), _DetailRow(label: 'Niveau cursus', value: levelText), _DetailRow( label: 'Localisation', value: display(user['location']), ), _DetailRow( label: 'Wallet', value: display(user['wallet']), ), ], ), ), ); } } class _DetailRow extends StatelessWidget { const _DetailRow({required this.label, required this.value}); final String label; final String value; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 120, child: Text( label, style: const TextStyle(fontWeight: FontWeight.w600), ), ), Expanded(child: Text(value)), ], ), ); } } class _SkillsCard extends StatelessWidget { const _SkillsCard({required this.skills}); final List skills; @override Widget build(BuildContext context) { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Skills', style: Theme.of(context).textTheme.titleMedium), const SizedBox(height: 12), if (skills.isEmpty) const Text('Aucune compétence disponible.') else ...skills.map( (s) => Padding( padding: const EdgeInsets.only(bottom: 8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('${s.name} — niveau ${s.level} (${s.percentage} %)'), const SizedBox(height: 4), LinearProgressIndicator( value: (s.level + s.percentage / 100).clamp(0, 15) / 15, minHeight: 6, backgroundColor: Colors.grey.shade300, color: const Color(0xFF00BABC), ), ], ), ), ), ], ), ), ); } } class _ProjectsCard extends StatelessWidget { const _ProjectsCard({required this.projects}); final List projects; @override Widget build(BuildContext context) { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Projets (validés et failed)', style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 12), if (projects.isEmpty) const Text('Aucun projet terminé ou en échec.') else ...projects.map((p) { final statusLabel = p.status == 'failed' ? 'Échec' : 'Validé'; final mark = p.finalMark != null ? ' · ${p.finalMark}' : ''; return ListTile( dense: true, contentPadding: EdgeInsets.zero, title: Text(p.name), trailing: Text( '$statusLabel$mark', style: TextStyle( color: p.status == 'failed' ? Theme.of(context).colorScheme.error : Colors.green.shade700, fontWeight: FontWeight.w600, ), ), ); }), ], ), ), ); } }