Feature: Login-Verbesserungen für Dustin!

1. 🔗 AUTO-URL: Server-URL \"https://musik.baka-net.de\" vorausgefüllt (editierbar)
2. 👤 USER-BUTTONS: Baka/Tinker/Dustin als Button-Auswahl (statt Text-Input)
3. 🔐 PASSWORT-ONLY: Dialog zeigt nur Passwort-Feld + User-Auswahl (wie beim Register!)
4. 💡 GAST-MODUS: Info-Text erklärt lokale Nutzung ohne Server-Konto
   → App funktioniert auch komplett offline mit lokaler Musikbibliothek!

UX-Verbesserungen:
- Weniger zu tippen (URL + User vorgegeben)
- Schneller Login für Baka/Tinker/Dustin
- Klare Fallback-Option für lokale Nutzung

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJzQjUtnvYUtHdnTs3iCru
This commit is contained in:
Hermes (Server)
2026-08-19 21:12:38 +02:00
co-authored by Claude Haiku 4.5
parent ea6ce5d4a6
commit 2948285d82
+60 -21
View File
@@ -79,6 +79,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
onPressed: () => _zeigeNavidromeDialog(), onPressed: () => _zeigeNavidromeDialog(),
), ),
), ),
if (!_navidrome.istVerbunden)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Text(
'💡 Tipp: Oder nutze Melo lokal ohne Server-Konto (nur deine Musik auf dem Gerät)',
style: TextStyle(fontSize: 12, color: Colors.white54),
),
),
ListTile( ListTile(
leading: const Icon(Icons.wifi_off), leading: const Icon(Icons.wifi_off),
title: const Text('📴 Offline-Modus'), title: const Text('📴 Offline-Modus'),
@@ -147,9 +155,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
} }
void _zeigeNavidromeDialog() { void _zeigeNavidromeDialog() {
final urlCtrl = TextEditingController(); final urlCtrl = TextEditingController(text: 'https://musik.baka-net.de');
final userCtrl = TextEditingController();
final passCtrl = TextEditingController(); final passCtrl = TextEditingController();
String selectedUser = 'Dustin';
bool verbindet = false; bool verbindet = false;
String? fehler; String? fehler;
@@ -158,9 +166,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
builder: (ctx) => StatefulBuilder( builder: (ctx) => StatefulBuilder(
builder: (ctx, setDialogState) => AlertDialog( builder: (ctx, setDialogState) => AlertDialog(
backgroundColor: Colors.grey.shade900, backgroundColor: Colors.grey.shade900,
title: const Text('🌐 Navidrome Verbinden', title: const Text('🌐 Navidrome Login',
style: TextStyle(color: Colors.white, fontSize: 16)), style: TextStyle(color: Colors.white, fontSize: 16)),
content: Column( content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
TextField( TextField(
@@ -168,23 +177,53 @@ class _SettingsScreenState extends State<SettingsScreen> {
style: const TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Server-URL', labelText: 'Server-URL',
hintText: 'https://musik.example.com',
labelStyle: TextStyle(color: Colors.grey),
hintStyle: TextStyle(color: Colors.grey),
border: OutlineInputBorder(),
),
),
const SizedBox(height: 8),
TextField(
controller: userCtrl,
style: const TextStyle(color: Colors.white),
decoration: const InputDecoration(
labelText: 'Benutzer',
labelStyle: TextStyle(color: Colors.grey), labelStyle: TextStyle(color: Colors.grey),
border: OutlineInputBorder(), border: OutlineInputBorder(),
), ),
), ),
const SizedBox(height: 12),
const Text('Benutzer:', style: TextStyle(fontSize: 12, color: Colors.white70)),
const SizedBox(height: 8), const SizedBox(height: 8),
Row(
children: [
Expanded(
child: FilledButton(
onPressed: () => setDialogState(() => selectedUser = 'Baka'),
style: FilledButton.styleFrom(
backgroundColor: selectedUser == 'Baka'
? Colors.redAccent
: Colors.grey.shade700,
),
child: const Text('Baka'),
),
),
const SizedBox(width: 8),
Expanded(
child: FilledButton(
onPressed: () => setDialogState(() => selectedUser = 'Tinker'),
style: FilledButton.styleFrom(
backgroundColor: selectedUser == 'Tinker'
? Colors.redAccent
: Colors.grey.shade700,
),
child: const Text('Tinker'),
),
),
const SizedBox(width: 8),
Expanded(
child: FilledButton(
onPressed: () => setDialogState(() => selectedUser = 'Dustin'),
style: FilledButton.styleFrom(
backgroundColor: selectedUser == 'Dustin'
? Colors.redAccent
: Colors.grey.shade700,
),
child: const Text('Dustin'),
),
),
],
),
const SizedBox(height: 12),
TextField( TextField(
controller: passCtrl, controller: passCtrl,
style: const TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
@@ -212,6 +251,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
), ),
], ],
), ),
),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(ctx), onPressed: () => Navigator.pop(ctx),
@@ -222,20 +262,20 @@ class _SettingsScreenState extends State<SettingsScreen> {
: () async { : () async {
setDialogState(() => verbindet = true); setDialogState(() => verbindet = true);
_navidrome.setCredentials( _navidrome.setCredentials(
urlCtrl.text, userCtrl.text, passCtrl.text); urlCtrl.text, selectedUser, passCtrl.text);
final ok = await _navidrome.ping(); final ok = await _navidrome.ping();
setDialogState(() => verbindet = false); setDialogState(() => verbindet = false);
if (ok && ctx.mounted) { if (ok && ctx.mounted) {
await _navidrome.speichereZugangsdaten( await _navidrome.speichereZugangsdaten(
urlCtrl.text, userCtrl.text, passCtrl.text); urlCtrl.text, selectedUser, passCtrl.text);
if (ctx.mounted) Navigator.pop(ctx); if (ctx.mounted) Navigator.pop(ctx);
if (mounted) setState(() {}); if (mounted) setState(() {});
} else if (ctx.mounted) { } else if (ctx.mounted) {
setDialogState(() => setDialogState(() =>
fehler = 'Verbindung fehlgeschlagen\nPrüfe URL + Zugangsdaten'); fehler = 'Login fehlgeschlagen');
} }
}, },
child: const Text('Verbinden', child: const Text('Login',
style: TextStyle(color: Colors.redAccent)), style: TextStyle(color: Colors.redAccent)),
), ),
], ],
@@ -243,7 +283,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
), ),
).then((_) { ).then((_) {
urlCtrl.dispose(); urlCtrl.dispose();
userCtrl.dispose();
passCtrl.dispose(); passCtrl.dispose();
}); });
} }