import 'package:flutter/material.dart'; import '../services/auth_service.dart'; import '../services/navidrome_service.dart'; import '../utils/farb_theme.dart'; import '../utils/user_effekte.dart'; import '../config/app_config.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 _userCtrl = TextEditingController(); final _passCtrl = TextEditingController(); bool _ladt = false; String? _status; bool _statusOk = false; bool _passSichtbar = false; 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(); } @override void dispose() { _userCtrl.dispose(); _passCtrl.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); // Per-User-Akzent (überschreibbar in den Einstellungen) + Begrüßung await UserEffekt.anwenden(user); // Navidrome automatisch mit gleichen Credentials syncen await NavidromeService().speichereZugangsdaten( AppConfig.navidromeUrl, user, pass); 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); } } @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: _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), // ─── 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 _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 _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, ), ); } }