import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_appauth/flutter_appauth.dart'; import '../config/intra_config.dart'; import '../services/intra_api_exception.dart'; import '../services/intra_auth_service.dart'; import '../services/intra_user_service.dart'; import 'profile_screen.dart'; /// Vue 1 — recherche par login 42. class SearchScreen extends StatefulWidget { const SearchScreen({super.key}); @override State createState() => _SearchScreenState(); } class _SearchScreenState extends State { final _auth = IntraAuthService.instance; final _users = IntraUserService.instance; final _loginController = TextEditingController(); bool _connecting = false; bool _searching = false; String? _error; String? _sessionLogin; @override void dispose() { _loginController.dispose(); super.dispose(); } Future _connect() async { setState(() { _connecting = true; _error = null; }); try { await _auth.signIn(); final me = await _auth.fetchMe(); if (!mounted) return; setState(() { _sessionLogin = me['login'] as String?; }); } on FlutterAppAuthUserCancelledException { if (!mounted) return; setState(() => _error = 'Connexion annulée'); } on FlutterAppAuthPlatformException catch (e) { final d = e.platformErrorDetails; if (!mounted) return; setState(() { _error = d.errorDescription ?? d.error ?? d.errorDebugDescription ?? e.message ?? 'Échec OAuth'; }); } on PlatformException catch (e) { if (!mounted) return; setState(() => _error = e.message ?? 'Erreur OAuth'); } catch (e) { if (!mounted) return; setState(() => _error = e.toString()); } finally { if (mounted) setState(() => _connecting = false); } } void _disconnect() { _auth.signOut(); setState(() { _sessionLogin = null; _error = null; }); } Future _search() async { setState(() { _searching = true; _error = null; }); try { final user = await _users.fetchUserByLogin(_loginController.text); if (!mounted) return; await Navigator.of(context).push( MaterialPageRoute( builder: (_) => ProfileScreen(user: user), ), ); } on IntraApiException catch (e) { if (!mounted) return; setState(() => _error = e.message); } catch (e) { if (!mounted) return; setState(() => _error = e.toString()); } finally { if (mounted) setState(() => _searching = false); } } @override Widget build(BuildContext context) { if (!IntraConfig.isConfigured) { return Scaffold( appBar: AppBar(title: const Text('Swifty Companion')), body: const Padding( padding: EdgeInsets.all(24), child: Text( 'Fichier .env manquant dans swifty_companion/.env', ), ), ); } final isAuthenticated = _auth.isAuthenticated; return Scaffold( appBar: AppBar( title: const Text('Swifty Companion'), backgroundColor: const Color(0xFF00BABC), foregroundColor: Colors.black, actions: [ if (isAuthenticated) IconButton( onPressed: _connecting || _searching ? null : _disconnect, icon: const Icon(Icons.logout), tooltip: 'Déconnexion API', ), ], ), body: Padding( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (!isAuthenticated) ...[ const Text( 'Connexion API 42', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), const Text( 'Connecte-toi une fois ; le token servira pour toutes les recherches.', ), const SizedBox(height: 16), FilledButton( onPressed: _connecting ? null : _connect, child: _connecting ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('Se connecter avec 42'), ), ] else ...[ Text( 'Session : ${_sessionLogin ?? 'connecté'}', style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 24), const Text( 'Rechercher un étudiant', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const SizedBox(height: 12), TextField( controller: _loginController, decoration: const InputDecoration( labelText: 'Login 42', hintText: 'ex. fgras-ca', border: OutlineInputBorder(), ), textInputAction: TextInputAction.search, onSubmitted: (_) { if (!_searching) _search(); }, enabled: !_searching, ), const SizedBox(height: 16), FilledButton( onPressed: _searching ? null : _search, child: _searching ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('Rechercher'), ), ], if (_error != null) ...[ const SizedBox(height: 16), Text( _error!, style: TextStyle(color: Theme.of(context).colorScheme.error), ), ], ], ), ), ); } }