Files
MatchLiveTv/mobile/lib/features/auth/login_screen.dart
Emiliano Frascaro bba6df52c0 Initial commit: monorepo Match Live TV.
Rails API, app Flutter, infrastruttura Docker/MediaMTX, sito marketing e documentazione di deploy.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 17:45:37 +02:00

158 lines
5.6 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 '../../features/matches/matches_screen.dart';
import '../../features/matches/team_providers.dart';
import '../../providers/auth_provider.dart';
import '../../shared/api_client.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});
@override
ConsumerState<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends ConsumerState<LoginScreen> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController(text: 'coach@matchlivetv.test');
final _passwordController = TextEditingController(text: 'password123');
bool _obscure = true;
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
Future<void> _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 WiFi.',
);
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,
);
ref.invalidate(teamsProvider);
ref.invalidate(matchesProvider);
ref.read(authProvider.notifier).setLoading(false);
if (mounted) context.go('/matches');
return;
} on DioException catch (e) {
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),
Text(
'ACCEDI',
style: theme.textTheme.headlineMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'Gestisci le dirette della tua squadra',
style: theme.textTheme.bodyMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
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: 'ACCEDI',
loading: auth.loading,
onPressed: _login,
),
],
),
),
),
);
}
}