import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../app/theme.dart'; import '../../core/invite_storage.dart'; import '../../features/matches/team_providers.dart'; import '../../providers/auth_provider.dart'; import '../../shared/api_client.dart'; import '../../shared/models/invite_preview.dart'; import '../../shared/widgets/match_live_wordmark.dart'; import '../../shared/widgets/primary_cta.dart'; import '../../shared/widgets/screen_insets.dart'; class LoginScreen extends ConsumerStatefulWidget { const LoginScreen({super.key, this.inviteToken}); final String? inviteToken; @override ConsumerState createState() => _LoginScreenState(); } class _LoginScreenState extends ConsumerState { final _formKey = GlobalKey(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(text: 'password123'); bool _obscure = true; InvitePreview? _invite; String? _inviteToken; bool _loadingInvite = false; @override void initState() { super.initState(); _bootstrapInvite(); } Future _bootstrapInvite() async { _inviteToken = widget.inviteToken ?? await InviteStorage.readToken(); if (_inviteToken == null) return; setState(() => _loadingInvite = true); try { final preview = await ApiClient().fetchInvitePreview(_inviteToken!); if (!mounted) return; setState(() { _invite = preview; if (preview.email.isNotEmpty) { _emailController.text = preview.email; } }); } catch (_) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Invito non valido o scaduto')), ); } await InviteStorage.clear(); } finally { if (mounted) setState(() => _loadingInvite = false); } } @override void dispose() { _emailController.dispose(); _passwordController.dispose(); super.dispose(); } Future _acceptInviteIfNeeded(ApiClient client) async { final token = _inviteToken; if (token == null) return; try { final message = await client.acceptInvitation(token); await InviteStorage.clear(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message)), ); } } on DioException catch (e) { final err = e.response?.data; final msg = err is Map ? (err['error'] as String?) : null; if (mounted && msg != null) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); } rethrow; } } Future _login() async { if (!_formKey.currentState!.validate()) return; final connectivity = await Connectivity().checkConnectivity(); if (connectivity.contains(ConnectivityResult.none)) { ref.read(authProvider.notifier).setError( 'Nessuna connessione. Disattiva la modalità aereo e verifica Wi‑Fi.', ); return; } ref.read(authProvider.notifier).setLoading(true); try { final client = ApiClient(); final result = await client.login( email: _emailController.text.trim(), password: _passwordController.text, ); ref.read(authProvider.notifier).setSession( user: result.user, accessToken: result.tokens.accessToken, refreshToken: result.tokens.refreshToken, ); final authed = ApiClient(accessToken: result.tokens.accessToken); if (_inviteToken != null) { await _acceptInviteIfNeeded(authed); } ref.invalidate(teamsProvider); ref.invalidate(matchesProvider); ref.read(authProvider.notifier).setLoading(false); if (mounted) context.go('/matches'); return; } on DioException catch (e) { if (e.response?.statusCode == 422) { ref.read(authProvider.notifier).setLoading(false); return; } final message = switch (e.type) { DioExceptionType.connectionTimeout || DioExceptionType.receiveTimeout || DioExceptionType.connectionError => 'Server non raggiungibile. Verifica che il backend sia avviato (docker compose up).', DioExceptionType.badResponse when e.response?.statusCode == 401 => 'Email o password non corretti', _ => 'Errore di rete: ${e.message}', }; ref.read(authProvider.notifier).setError(message); } catch (e) { ref.read(authProvider.notifier).setError('Errore imprevisto: $e'); } ref.read(authProvider.notifier).setLoading(false); } @override Widget build(BuildContext context) { final auth = ref.watch(authProvider); final theme = Theme.of(context); return Scaffold( body: SingleChildScrollView( padding: ScreenInsets.contentPadding(context, horizontal: 24, vertical: 16), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: 32), const Center(child: MatchLiveWordmark(showSlogan: true)), const SizedBox(height: 48), if (_loadingInvite) const Padding( padding: EdgeInsets.only(bottom: 16), child: Center( child: CircularProgressIndicator(color: AppTheme.primaryRed), ), ), if (_invite != null) ...[ Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: AppTheme.surfaceElevated, borderRadius: BorderRadius.circular(10), border: Border.all(color: AppTheme.primaryRed.withValues(alpha: 0.35)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Invito staff', style: theme.textTheme.titleMedium?.copyWith(color: AppTheme.primaryRed), ), const SizedBox(height: 6), Text( 'Entra in ${_invite!.teamName} come responsabile trasmissione.', style: theme.textTheme.bodyMedium, ), const SizedBox(height: 4), Text( 'Accedi con ${_invite!.email}', style: theme.textTheme.bodySmall, ), ], ), ), const SizedBox(height: 20), ], Text( 'ACCEDI', style: theme.textTheme.headlineMedium, textAlign: TextAlign.center, ), const SizedBox(height: 8), Text( _invite != null ? 'Usa l\'email dell\'invito, poi collega YouTube e vai in diretta' : 'Gestisci le dirette della tua squadra', style: theme.textTheme.bodyMedium, textAlign: TextAlign.center, ), const SizedBox(height: 32), TextFormField( controller: _emailController, keyboardType: TextInputType.emailAddress, readOnly: _invite != null, decoration: const InputDecoration( labelText: 'Email', hintText: 'coach@squadra.it', ), validator: (v) => v == null || v.isEmpty ? 'Inserisci l\'email' : null, ), const SizedBox(height: 16), TextFormField( controller: _passwordController, obscureText: _obscure, decoration: InputDecoration( labelText: 'Password', suffixIcon: IconButton( icon: Icon( _obscure ? Icons.visibility : Icons.visibility_off, ), onPressed: () => setState(() => _obscure = !_obscure), ), ), validator: (v) => v == null || v.isEmpty ? 'Inserisci la password' : null, ), if (auth.error != null) ...[ const SizedBox(height: 12), Text( auth.error!, style: theme.textTheme.bodyMedium?.copyWith( color: AppTheme.primaryRed, ), textAlign: TextAlign.center, ), ], const SizedBox(height: 32), PrimaryCta( label: _invite != null ? 'ACCEDI E ACCETTA INVITO' : 'ACCEDI', loading: auth.loading, onPressed: _login, ), ], ), ), ), ); } }