import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:mileograph_flutter/services/authservice.dart'; import 'package:provider/provider.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({super.key}); @override State createState() => _LoginScreenState(); } class _LoginScreenState extends State { bool _checkingSession = true; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) => _checkExistingSession()); } Future _checkExistingSession() async { final auth = context.read(); try { final valid = await auth.validateStoredToken(); if (!valid) return; await auth.tryRestoreSession(); if (!mounted) return; context.go('/'); } finally { if (mounted) setState(() => _checkingSession = false); } } @override Widget build(BuildContext context) { return Scaffold( resizeToAvoidBottomInset: true, body: Container( color: Theme.of(context).scaffoldBackgroundColor, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text.rich( TextSpan( children: [ TextSpan( text: "Mile", style: TextStyle( color: Theme.of(context).textTheme.bodyLarge?.color, ), ), const TextSpan( text: "O", style: TextStyle(color: Colors.red), ), TextSpan( text: "graph", style: TextStyle( color: Theme.of(context).textTheme.bodyLarge?.color, ), ), ], style: const TextStyle( decoration: TextDecoration.none, color: Colors.white, fontFamily: "Tomatoes", fontSize: 50, ), ), ), if (_checkingSession) const Padding( padding: EdgeInsets.only(top: 12), child: SizedBox( height: 24, width: 24, child: CircularProgressIndicator(strokeWidth: 2), ), ), const SizedBox(height: 50), const LoginPanel(), ], ), ), ), ); } } class LoginPanel extends StatefulWidget { const LoginPanel({super.key}); @override State createState() => _LoginPanelState(); } class _LoginPanelState extends State { bool registerMode = false; void toggleMode() { setState(() { registerMode = !registerMode; }); } @override Widget build(BuildContext context) { final authService = context.read(); return Center( child: SizedBox( width: 400, child: Card( child: Padding( padding: EdgeInsets.all(20), child: registerMode ? RegisterPanelContent( onBack: toggleMode, authService: authService, ) : LoginPanelContent( registerCb: toggleMode, authService: authService, ), ), ), ), ); } } class LoginPanelContent extends StatefulWidget { const LoginPanelContent({ required this.registerCb, required this.authService, super.key, }); final VoidCallback registerCb; final AuthService authService; @override State createState() => _LoginPanelContentState(); } class _LoginPanelContentState extends State { final _usernameController = TextEditingController(); final _passwordController = TextEditingController(); bool _loggingIn = false; @override void dispose() { _usernameController.dispose(); _passwordController.dispose(); super.dispose(); } Future login() async { final username = _usernameController.text; final password = _passwordController.text; final auth = context.read(); setState(() { _loggingIn = true; }); try { await auth.login(username, password); if (!mounted) return; setState(() { _loggingIn = false; }); } catch (e) { if (!mounted) return; setState(() { _loggingIn = false; }); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Login failed: $e')), ); } } @override Widget build(BuildContext context) { Widget loginButtonContent = Text("Login"); if (_loggingIn) { loginButtonContent = Padding( padding: const EdgeInsets.all(8.0), child: SizedBox( height: 10, width: 10, child: CircularProgressIndicator( color: Theme.of(context).textTheme.bodyLarge?.color, strokeWidth: 2, ), ), ); } else { loginButtonContent = Text("Login"); } return Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 50), child: Text( "Login", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), ), ), const SizedBox(height: 8), TextFormField( controller: _usernameController, decoration: InputDecoration( border: OutlineInputBorder(), labelText: "Username", ), onFieldSubmitted: (_) => login(), ), const SizedBox(height: 8), TextFormField( controller: _passwordController, obscureText: true, decoration: InputDecoration( border: OutlineInputBorder(), labelText: "Password", ), onFieldSubmitted: (_) => login(), ), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ FilledButton(onPressed: login, child: loginButtonContent), const SizedBox(width: 10), ElevatedButton( onPressed: widget.registerCb, child: Text("Register"), ), ], ), ], ); } } class RegisterPanelContent extends StatefulWidget { const RegisterPanelContent({ super.key, required this.onBack, required this.authService, }); final VoidCallback onBack; final AuthService authService; @override State createState() => _RegisterPanelContentState(); } class _RegisterPanelContentState extends State { final _usernameController = TextEditingController(); final _displayNameController = TextEditingController(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); final _inviteController = TextEditingController(); bool _registering = false; @override void dispose() { _usernameController.dispose(); _displayNameController.dispose(); _emailController.dispose(); _passwordController.dispose(); _inviteController.dispose(); super.dispose(); } Future _register() async { setState(() => _registering = true); try { await widget.authService.register( username: _usernameController.text.trim(), email: _emailController.text.trim(), fullName: _displayNameController.text.trim(), password: _passwordController.text, inviteCode: _inviteController.text.trim(), ); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Registration successful. Please log in.')), ); widget.onBack(); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Registration failed: $e')), ); } finally { if (mounted) setState(() => _registering = false); } } @override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ Row( children: [ IconButton( icon: const Icon(Icons.arrow_back), onPressed: widget.onBack, tooltip: 'Back to login', ), Expanded( child: Center( child: Text( "Register", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), ), ), ), const SizedBox(width: 48), ], ), const SizedBox(height: 16), TextField( controller: _usernameController, decoration: const InputDecoration( border: OutlineInputBorder(), labelText: "Username", ), ), const SizedBox(height: 8), TextField( controller: _displayNameController, decoration: const InputDecoration( border: OutlineInputBorder(), labelText: "Display Name", ), ), const SizedBox(height: 8), TextField( controller: _emailController, decoration: const InputDecoration( border: OutlineInputBorder(), labelText: "Email", ), ), const SizedBox(height: 8), TextField( controller: _passwordController, obscureText: true, decoration: const InputDecoration( border: OutlineInputBorder(), labelText: "Password", ), ), const SizedBox(height: 8), TextField( controller: _inviteController, decoration: const InputDecoration( border: OutlineInputBorder(), labelText: "Invite Code", ), ), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ FilledButton( onPressed: _registering ? null : _register, child: _registering ? const SizedBox( height: 14, width: 14, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text("Register"), ), ], ), ], ); } }