import 'package:flutter/foundation.dart'; 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 '../theme/swifty_ui.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: SwiftyUi.appBar(title: 'Swifty Companion'), body: const ResponsiveScrollBody( maxWidth: SwiftyUi.searchMaxWidth, child: Text( 'Fichier .env manquant dans swifty_companion/.env', ), ), ); } final isAuthenticated = _auth.isAuthenticated; final busy = _connecting || _searching; return Scaffold( appBar: SwiftyUi.appBar( title: 'Swifty Companion', actions: [ if (isAuthenticated) IconButton( onPressed: busy ? null : _disconnect, icon: const Icon(Icons.logout), tooltip: 'Déconnexion API', ), ], ), body: ResponsiveScrollBody( maxWidth: SwiftyUi.searchMaxWidth, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (!isAuthenticated) ...[ Text( 'Connexion API 42', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 8), Text( 'Connecte-toi une fois ; le token servira pour toutes les recherches.', style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: 20), LoadingFilledButton( label: 'Se connecter avec 42', loading: _connecting, onPressed: _connect, ), ] else ...[ Text( 'Session : ${_sessionLogin ?? 'connecté'}', style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 28), Text( 'Rechercher un étudiant', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 12), TextField( controller: _loginController, decoration: const InputDecoration( labelText: 'Login 42', hintText: 'ex. fgras-ca', border: OutlineInputBorder(), ), textInputAction: TextInputAction.search, autocorrect: false, enableSuggestions: false, onSubmitted: (_) { if (!_searching) _search(); }, enabled: !_searching, ), const SizedBox(height: 16), LoadingFilledButton( label: 'Rechercher', loading: _searching, onPressed: _search, ), if (kDebugMode) ...[ const SizedBox(height: 12), TextButton( onPressed: _auth.canRefresh ? () { _auth.debugInvalidateAccessToken(); setState(() { _error = 'DEBUG : access token invalidé (refresh conservé). ' 'Étape suivante : saisis un login et appuie sur « Rechercher ». ' 'Si le bonus marche, le profil s’ouvre sans repasser par OAuth 42.'; }); } : null, child: const Text('DEBUG : simuler token expiré'), ), ], ], if (_error != null) ...[ const SizedBox(height: 20), ErrorMessage(message: _error!), ], ], ), ), ); } }