import 'package:flutter/material.dart'; import '../services/auth_service.dart'; import '../services/navidrome_service.dart'; import '../utils/farb_theme.dart'; import 'home_screen.dart'; /// Melo Login-Screen – Schwarz+Rot Design /// Baka-Auth + Navidrome-Credentials class LoginScreen extends StatefulWidget { const LoginScreen({super.key}); @override State createState() => _LoginScreenState(); } class _LoginScreenState extends State with SingleTickerProviderStateMixin { final AuthService _auth = AuthService(); final NavidromeService _navidrome = NavidromeService(); final _userCtrl = TextEditingController(); final _passCtrl = TextEditingController(); final _urlCtrl = TextEditingController(); final _navUserCtrl = TextEditingController(); final _navPassCtrl = TextEditingController(); bool _ladt = false; String? _status; bool _statusOk = false; bool _zeigeNavidrome = false; bool _zeigeRegistrierung = false; bool _passSichtbar = false; bool _navPassSichtbar = false; bool _regPassSichtbar = false; // Registrierungs-Felder final _regEmailCtrl = TextEditingController(); final _regUserCtrl = TextEditingController(); final _regPassCtrl = TextEditingController(); late final AnimationController _animCtrl; late final Animation _fadeAnim; @override void initState() { super.initState(); _animCtrl = AnimationController( vsync: this, duration: const Duration(milliseconds: 800), ); _fadeAnim = CurvedAnimation(parent: _animCtrl, curve: Curves.easeOut); _animCtrl.forward(); _ladeGespeicherteDaten(); } Future _ladeGespeicherteDaten() async { await _navidrome.ladeGespeicherteZugangsdaten(); // Navidrome-Daten für Anzeige vorbereiten if (_navidrome.istVerbunden) { setState(() { _zeigeNavidrome = true; }); } } @override void dispose() { _userCtrl.dispose(); _passCtrl.dispose(); _urlCtrl.dispose(); _navUserCtrl.dispose(); _navPassCtrl.dispose(); _regEmailCtrl.dispose(); _regUserCtrl.dispose(); _regPassCtrl.dispose(); _animCtrl.dispose(); super.dispose(); } void _setzeStatus(String msg, {bool ok = false}) { setState(() { _status = msg; _statusOk = ok; }); } Future _login() async { final user = _userCtrl.text.trim(); final pass = _passCtrl.text.trim(); if (user.isEmpty || pass.isEmpty) { _setzeStatus('Bitte Benutzername und Passwort eingeben'); return; } setState(() => _ladt = true); _setzeStatus('Verbinde mit Baka-Auth...'); final result = await _auth.login(user, pass); if (!mounted) return; if (result.erfolg) { _setzeStatus('✅ Login erfolgreich!', ok: true); // Navidrome-Credentials speichern falls eingegeben if (_zeigeNavidrome) { final url = _urlCtrl.text.trim(); final nUser = _navUserCtrl.text.trim(); final nPass = _navPassCtrl.text.trim(); if (url.isNotEmpty && nUser.isNotEmpty && nPass.isNotEmpty) { await _navidrome.speichereZugangsdaten(url, nUser, nPass); } } // Kurz warten, dann navigieren await Future.delayed(const Duration(milliseconds: 600)); if (mounted) { Navigator.of(context).pushReplacement( MaterialPageRoute(builder: (_) => const MeloHome()), ); } } else { _setzeStatus('❌ ${result.fehler ?? "Login fehlgeschlagen"}'); setState(() => _ladt = false); } } Future _registrieren() async { final user = _regUserCtrl.text.trim(); final pass = _regPassCtrl.text.trim(); final email = _regEmailCtrl.text.trim(); if (user.isEmpty || pass.isEmpty) { _setzeStatus('Bitte alle Felder ausfüllen'); return; } setState(() => _ladt = true); _setzeStatus('Registriere...'); final result = await _auth.registrieren(user, pass, email); if (!mounted) return; if (result.erfolg) { _setzeStatus('✅ Registrierung erfolgreich!', ok: true); await Future.delayed(const Duration(milliseconds: 600)); if (mounted) { Navigator.of(context).pushReplacement( MaterialPageRoute(builder: (_) => const MeloHome()), ); } } else { _setzeStatus('❌ ${result.fehler ?? "Registrierung fehlgeschlagen"}'); setState(() => _ladt = false); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: MeloTheme.schwarz, body: SafeArea( child: Center( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 24), child: FadeTransition( opacity: _fadeAnim, child: _zeigeRegistrierung ? _buildRegister() : _buildLogin(), ), ), ), ), ); } Widget _buildLogin() { return Column( mainAxisSize: MainAxisSize.min, children: [ // ─── Logo ─── _logoBereich(), const SizedBox(height: 36), // ─── Login-Karte ─── Container( width: double.infinity, padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: MeloTheme.dunkel1, borderRadius: BorderRadius.circular(20), border: Border.all(color: MeloTheme.dunkel2), boxShadow: [ BoxShadow( color: MeloTheme.rot.withValues(alpha: 0.15), blurRadius: 30, offset: const Offset(0, 8), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Anmelden', style: TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.w700, ), ), const SizedBox(height: 4), const Text( 'Mit deinem Baka-Account', style: TextStyle(color: MeloTheme.textSekundaer, fontSize: 13), ), const SizedBox(height: 24), // Benutzername _eingabeFeld( controller: _userCtrl, label: 'Benutzername', icon: Icons.person_outline, onSubmitted: (_) => _login(), ), const SizedBox(height: 16), // Passwort _eingabeFeld( controller: _passCtrl, label: 'Passwort', icon: Icons.lock_outline, istPasswort: true, passSichtbar: _passSichtbar, onPasswortToggle: () => setState(() => _passSichtbar = !_passSichtbar), onSubmitted: (_) => _login(), ), const SizedBox(height: 24), // Login Button SizedBox( width: double.infinity, height: 50, child: ElevatedButton( onPressed: _ladt ? null : _login, style: ElevatedButton.styleFrom( backgroundColor: MeloTheme.rot, disabledBackgroundColor: MeloTheme.dunkel2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), elevation: 0, ), child: _ladt ? const SizedBox( width: 22, height: 22, child: CircularProgressIndicator( strokeWidth: 2.5, color: Colors.white, ), ) : const Text( 'Anmelden', style: TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600, ), ), ), ), // Status if (_status != null) ...[ const SizedBox(height: 16), _statusWidget(), ], ], ), ), const SizedBox(height: 20), // ─── Navidrome (optional) ─── _navidromeBereich(), const SizedBox(height: 20), // ─── Registrieren Link ─── Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( 'Noch keinen Account? ', style: TextStyle(color: MeloTheme.textSekundaer, fontSize: 13), ), GestureDetector( onTap: () => setState(() => _zeigeRegistrierung = true), child: const Text( 'Registrieren', style: TextStyle( color: MeloTheme.rot, fontSize: 13, fontWeight: FontWeight.w600, ), ), ), ], ), // ─── Offline-Modus ─── const SizedBox(height: 16), GestureDetector( onTap: () { Navigator.of(context).pushReplacement( MaterialPageRoute(builder: (_) => const MeloHome()), ); }, child: const Text( 'Ohne Login fortfahren', style: TextStyle(color: MeloTheme.textSekundaer, fontSize: 12), ), ), const SizedBox(height: 24), ], ); } Widget _buildRegister() { return Column( mainAxisSize: MainAxisSize.min, children: [ _logoBereich(klein: true), const SizedBox(height: 28), Container( width: double.infinity, padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: MeloTheme.dunkel1, borderRadius: BorderRadius.circular(20), border: Border.all(color: MeloTheme.dunkel2), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Registrieren', style: TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.w700), ), const SizedBox(height: 20), _eingabeFeld( controller: _regUserCtrl, label: 'Benutzername', icon: Icons.person_outline, ), const SizedBox(height: 14), _eingabeFeld( controller: _regEmailCtrl, label: 'E-Mail (optional)', icon: Icons.email_outlined, keyboardType: TextInputType.emailAddress, ), const SizedBox(height: 14), _eingabeFeld( controller: _regPassCtrl, label: 'Passwort', icon: Icons.lock_outline, istPasswort: true, passSichtbar: _regPassSichtbar, onPasswortToggle: () => setState(() => _regPassSichtbar = !_regPassSichtbar), ), const SizedBox(height: 24), SizedBox( width: double.infinity, height: 50, child: ElevatedButton( onPressed: _ladt ? null : _registrieren, style: ElevatedButton.styleFrom( backgroundColor: MeloTheme.rot, disabledBackgroundColor: MeloTheme.dunkel2, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), elevation: 0, ), child: _ladt ? const SizedBox( width: 22, height: 22, child: CircularProgressIndicator(strokeWidth: 2.5, color: Colors.white), ) : const Text('Registrieren', style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600)), ), ), if (_status != null) ...[ const SizedBox(height: 16), _statusWidget(), ], ], ), ), const SizedBox(height: 16), GestureDetector( onTap: () { setState(() { _zeigeRegistrierung = false; _status = null; }); }, child: const Text( '← Zurück zum Login', style: TextStyle(color: MeloTheme.rot, fontSize: 13, fontWeight: FontWeight.w600), ), ), ], ); } Widget _logoBereich({bool klein = false}) { return Column( children: [ // Icon Container( width: klein ? 64 : 80, height: klein ? 64 : 80, decoration: BoxDecoration( shape: BoxShape.circle, gradient: const LinearGradient( colors: [Color(0xFFCC0000), Color(0xFF660000)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), boxShadow: [ BoxShadow( color: MeloTheme.rot.withValues(alpha: 0.4), blurRadius: 24, offset: const Offset(0, 6), ), ], ), child: const Center( child: Text( '♪', style: TextStyle(fontSize: 36, color: Colors.white, fontWeight: FontWeight.w300), ), ), ), const SizedBox(height: 16), const Text( 'MELO', style: TextStyle( color: Colors.white, fontSize: 32, fontWeight: FontWeight.w800, letterSpacing: 6, ), ), const SizedBox(height: 4), Text( 'Musik. Für immer.', style: TextStyle( color: MeloTheme.rot.withValues(alpha: 0.8), fontSize: 13, letterSpacing: 2, fontWeight: FontWeight.w400, ), ), ], ); } Widget _navidromeBereich() { return Column( children: [ // Toggle GestureDetector( onTap: () => setState(() => _zeigeNavidrome = !_zeigeNavidrome), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( color: MeloTheme.dunkel1, borderRadius: BorderRadius.circular(12), border: Border.all(color: MeloTheme.dunkel2), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( _zeigeNavidrome ? Icons.dns : Icons.dns_outlined, color: _zeigeNavidrome ? MeloTheme.rot : MeloTheme.textSekundaer, size: 18, ), const SizedBox(width: 8), Text( '🌐 Navidrome Server', style: TextStyle( color: _zeigeNavidrome ? Colors.white : MeloTheme.textSekundaer, fontSize: 13, ), ), const SizedBox(width: 6), Icon( _zeigeNavidrome ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down, color: MeloTheme.textSekundaer, size: 16, ), ], ), ), ), // Erweiterte Navidrome-Felder if (_zeigeNavidrome) ...[ const SizedBox(height: 12), Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: MeloTheme.dunkel1, borderRadius: BorderRadius.circular(14), border: Border.all(color: MeloTheme.dunkel2), ), child: Column( children: [ _eingabeFeld( controller: _urlCtrl, label: 'Server-URL', icon: Icons.link, hint: 'https://musik.baka-net.de', ), const SizedBox(height: 12), _eingabeFeld( controller: _navUserCtrl, label: 'Navidrome Benutzer', icon: Icons.person_outline, ), const SizedBox(height: 12), _eingabeFeld( controller: _navPassCtrl, label: 'Navidrome Passwort', icon: Icons.lock_outline, istPasswort: true, passSichtbar: _navPassSichtbar, onPasswortToggle: () => setState(() => _navPassSichtbar = !_navPassSichtbar), ), ], ), ), ], ], ); } Widget _statusWidget() { return Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration( color: _statusOk ? const Color(0xFF0D3B1E) : const Color(0xFF3B0D0D), borderRadius: BorderRadius.circular(10), border: Border.all( color: _statusOk ? const Color(0xFF2E7D32) : const Color(0xFF7D2E2E), ), ), child: Row( children: [ Icon( _statusOk ? Icons.check_circle : Icons.error_outline, size: 16, color: _statusOk ? const Color(0xFF4CAF50) : const Color(0xFFEF5350), ), const SizedBox(width: 8), Expanded( child: Text( _status!, style: TextStyle( color: _statusOk ? const Color(0xFFA5D6A7) : const Color(0xFFEF9A9A), fontSize: 12, ), ), ), ], ), ); } Widget _eingabeFeld({ required TextEditingController controller, required String label, required IconData icon, bool istPasswort = false, bool passSichtbar = false, VoidCallback? onPasswortToggle, String? hint, TextInputType keyboardType = TextInputType.text, ValueChanged? onSubmitted, }) { return TextField( controller: controller, obscureText: istPasswort && !passSichtbar, keyboardType: keyboardType, style: const TextStyle(color: Colors.white, fontSize: 15), onSubmitted: onSubmitted, decoration: InputDecoration( labelText: label, hintText: hint, hintStyle: const TextStyle(color: Color(0xFF444444), fontSize: 13), labelStyle: const TextStyle(color: MeloTheme.textSekundaer, fontSize: 13), prefixIcon: Icon(icon, color: MeloTheme.textSekundaer, size: 20), filled: true, fillColor: MeloTheme.dunkel2, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: MeloTheme.rot, width: 1.5), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: MeloTheme.dunkel2), ), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), suffixIcon: istPasswort ? IconButton( icon: Icon( passSichtbar ? Icons.visibility : Icons.visibility_off, size: 18, color: MeloTheme.textSekundaer, ), onPressed: onPasswortToggle, ) : null, ), ); } }