UX-Simulation: Mehrfachauswahl überall, Baka-Login, Bestätigungen, Suche

7 Agenten sind die App als normaler Nutzer durchgegangen (Erststart,
Bibliothek, Download, Suche, Favoriten, Player, Einstellungen) und haben
39 Reibungspunkte gefunden. Erster Block:

- Mehrfachauswahl lief nur im Lieder-/Favoriten-Reiter, nicht in
  Kategorie-/Künstler-/Album-Ansichten — jetzt überall, per geteilter
  auswahl_leiste.dart
- Zwei unterschiedlich ausgestattete Favoriten-Listen (Tab vs. Playlisten)
  vereinheitlicht
- Baka-Login zeigt Ladezustand + Fehler im Dialog statt bis zu 15s stumm
  zu bleiben (wie der Navidrome-Login)
- Cache leeren, Navidrome-Abmelden, Download einzeln entfernen fragen jetzt
  nach
- Download-Fehlermeldung bei toter Serververbindung nennt den echten Grund
  statt "War schon heruntergeladen"
- YouTube-Fehlermeldungen sind jetzt rot statt neutralfarben
- Suche schließt die Tastatur nach Auswahl, nennt den Suchbegriff bei
  "Nichts gefunden", einzelne Verlaufseinträge sind entfernbar
- Icon-/Text-Korrekturen (Mikrofon-Icon fälschlich bei Speicherzugriff,
  "Online"-Tab existiert nicht, ReplayGain-Jargon)

537 Tests grün (vorher 533), flutter analyze ohne Befund.
This commit is contained in:
Hermes (Server)
2026-08-25 09:42:41 +02:00
parent 60c57577a3
commit 07acf77ba4
15 changed files with 511 additions and 202 deletions
+21 -4
View File
@@ -92,6 +92,11 @@ class _SearchScreenState extends State<SearchScreen> {
if (mounted) setState(() => _verlauf = const []);
}
Future<void> _verlaufEntferne(String begriff) async {
final neu = await SuchVerlauf.entferne(begriff);
if (mounted) setState(() => _verlauf = neu);
}
void _leeren() {
_entprellen?.cancel();
_feld.clear();
@@ -118,6 +123,7 @@ class _SearchScreenState extends State<SearchScreen> {
begriffe: _verlauf,
onWaehlen: _uebernimm,
onLeeren: _verlaufLeeren,
onEntfernen: _verlaufEntferne,
)
: StreamBuilder<List<Song>>(
stream: _songs,
@@ -138,7 +144,7 @@ class _SearchScreenState extends State<SearchScreen> {
query: _query,
);
if (treffer.istLeer) {
return const _Hinweis('Nichts gefunden');
return _Hinweis('Nichts gefunden für „$_query"');
}
return _Ergebnisse(
treffer: treffer,
@@ -228,11 +234,13 @@ class _Verlauf extends StatelessWidget {
required this.begriffe,
required this.onWaehlen,
required this.onLeeren,
required this.onEntfernen,
});
final List<String> begriffe;
final ValueChanged<String> onWaehlen;
final VoidCallback onLeeren;
final ValueChanged<String> onEntfernen;
@override
Widget build(BuildContext context) {
@@ -252,6 +260,11 @@ class _Verlauf extends StatelessWidget {
ListTile(
leading: const Icon(Icons.history),
title: Text(begriff),
trailing: IconButton(
tooltip: '$begriff" aus dem Verlauf entfernen',
icon: const Icon(Icons.close, size: 18),
onPressed: () => onEntfernen(begriff),
),
onTap: () => onWaehlen(begriff),
),
],
@@ -270,6 +283,7 @@ class _Ergebnisse extends StatelessWidget {
final List<Song> alleSongs;
void _oeffne(BuildContext context, String titel, List<Song> songs) {
FocusScope.of(context).unfocus();
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => TitelListenScreen(titel: titel, songs: songs),
));
@@ -334,9 +348,12 @@ class _Ergebnisse extends StatelessWidget {
leading: const Icon(Icons.queue_music),
title:
Text(liste.name, maxLines: 1, overflow: TextOverflow.ellipsis),
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (_) => PlaylistDetailScreen(playlist: liste),
)),
onTap: () {
FocusScope.of(context).unfocus();
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => PlaylistDetailScreen(playlist: liste),
));
},
),
],
const SizedBox(height: MeloSpace.md),