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(),
),
),
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(
leading: const Icon(Icons.wifi_off),
title: const Text('📴 Offline-Modus'),
@@ -147,9 +155,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
}
void _zeigeNavidromeDialog() {
final urlCtrl = TextEditingController();
final userCtrl = TextEditingController();
final urlCtrl = TextEditingController(text: 'https://musik.baka-net.de');
final passCtrl = TextEditingController();
String selectedUser = 'Dustin';
bool verbindet = false;
String? fehler;
@@ -158,9 +166,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
builder: (ctx) => StatefulBuilder(
builder: (ctx, setDialogState) => AlertDialog(
backgroundColor: Colors.grey.shade900,
title: const Text('🌐 Navidrome Verbinden',
title: const Text('🌐 Navidrome Login',
style: TextStyle(color: Colors.white, fontSize: 16)),
content: Column(
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
@@ -168,23 +177,53 @@ class _SettingsScreenState extends State<SettingsScreen> {
style: const TextStyle(color: Colors.white),
decoration: const InputDecoration(
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),
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
const Text('Benutzer:', style: TextStyle(fontSize: 12, color: Colors.white70)),
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(
controller: passCtrl,
style: const TextStyle(color: Colors.white),
@@ -212,6 +251,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
@@ -222,20 +262,20 @@ class _SettingsScreenState extends State<SettingsScreen> {
: () async {
setDialogState(() => verbindet = true);
_navidrome.setCredentials(
urlCtrl.text, userCtrl.text, passCtrl.text);
urlCtrl.text, selectedUser, passCtrl.text);
final ok = await _navidrome.ping();
setDialogState(() => verbindet = false);
if (ok && ctx.mounted) {
await _navidrome.speichereZugangsdaten(
urlCtrl.text, userCtrl.text, passCtrl.text);
urlCtrl.text, selectedUser, passCtrl.text);
if (ctx.mounted) Navigator.pop(ctx);
if (mounted) setState(() {});
} else if (ctx.mounted) {
setDialogState(() =>
fehler = 'Verbindung fehlgeschlagen\nPrüfe URL + Zugangsdaten');
fehler = 'Login fehlgeschlagen');
}
},
child: const Text('Verbinden',
child: const Text('Login',
style: TextStyle(color: Colors.redAccent)),
),
],
@@ -243,7 +283,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
),
).then((_) {
urlCtrl.dispose();
userCtrl.dispose();
passCtrl.dispose();
});
}