Files
Melo/lib/playlists/playlist_detail_screen.dart
T

158 lines
5.5 KiB
Dart

import 'package:audio_service/audio_service.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../library/category_service.dart';
import '../library/database.dart';
import '../library/playlist_service.dart';
import '../library/song_media.dart';
import '../player/audio_handler.dart';
import '../player/now_playing_screen.dart';
import '../settings/app_settings.dart';
import '../shared/cover.dart';
import '../shared/lauf_balken.dart';
import '../shared/theme.dart';
/// Playlist-Detailansicht: Songs anzeigen, per Drag & Drop umsortieren, entfernen.
class PlaylistDetailScreen extends StatelessWidget {
const PlaylistDetailScreen({super.key, required this.playlist});
final Playlist playlist;
@override
Widget build(BuildContext context) {
final db = context.read<MeloDb>();
final service = context.read<PlaylistService>();
return Scaffold(
appBar: AppBar(title: Text(playlist.name)),
body: StreamBuilder<List<Song>>(
stream: db.watchPlaylistSongs(playlist.id),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(
child: Text('Fehler: ${snapshot.error}',
style: const TextStyle(color: MeloTheme.text2)),
);
}
final songs = snapshot.data ?? const [];
if (songs.isEmpty) {
return const Center(
child: Text('Playlist ist leer — füge Songs aus der Bibliothek hinzu',
style: TextStyle(color: MeloTheme.text2)),
);
}
return ReorderableListView.builder(
itemCount: songs.length,
onReorderItem: (oldIndex, newIndex) {
final reordered = List<Song>.from(songs);
final moved = reordered.removeAt(oldIndex);
reordered.insert(newIndex, moved);
service.reorderAll(playlist.id, reordered.map((s) => s.id).toList());
},
itemBuilder: (context, i) => _Zeile(
key: ValueKey(songs[i].id),
song: songs[i],
alleSongs: songs,
index: i,
onEntfernen: () =>
service.removeSongFromPlaylist(playlist.id, songs[i].id),
),
);
},
),
);
}
}
/// Eine Zeile der Wiedergabeliste.
///
/// Nicht [SongZeile]: hier steht rechts „Aus Playlist entfernen" statt Herz
/// und Menü. Die Lauf-Markierung ist dieselbe — der Nutzer soll dasselbe
/// Zeichen sehen wie in der Bibliothek.
class _Zeile extends StatelessWidget {
const _Zeile({
super.key,
required this.song,
required this.alleSongs,
required this.index,
required this.onEntfernen,
});
final Song song;
final List<Song> alleSongs;
final int index;
final VoidCallback onEntfernen;
@override
Widget build(BuildContext context) {
final handler = context.read<MeloAudioHandler>();
final categories = context.watch<CategoryService>();
final settings = context.watch<AppSettings>();
final cover = categories.coverFor(song,
groupByCategory: settings.groupCoversByCategory);
return StreamBuilder<MediaItem?>(
stream: handler.mediaItem,
initialData: handler.mediaItem.valueOrNull,
builder: (context, laufSnap) {
final markiert = istAmLaufen(laufSnap.data, song.id);
return StreamBuilder<PlaybackState>(
stream: markiert ? handler.playbackState : null,
initialData: handler.playbackState.valueOrNull,
builder: (context, zustandSnap) => ListTile(
leading: LaufMarkierung(
markiert: markiert,
spielt: markiert && (zustandSnap.data?.playing ?? false),
radius: 6,
child: CoverImage(
artUri: cover != null ? Uri.file(cover) : null,
size: 48,
radius: 6,
),
),
title: Text(
song.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: markiert
? const TextStyle(
color: MeloTheme.red, fontWeight: FontWeight.w600)
: null,
),
subtitle: Text(song.artist ?? 'Unbekannt',
maxLines: 1, overflow: TextOverflow.ellipsis),
trailing: IconButton(
tooltip: 'Aus Playlist entfernen',
icon: const Icon(Icons.remove_circle_outline),
onPressed: onEntfernen,
),
onTap: () async {
final messenger = ScaffoldMessenger.of(context);
try {
final uebersprungen = await playSongs(
handler,
alleSongs,
index,
coverOf: (s) => categories.coverFor(s,
groupByCategory: settings.groupCoversByCategory),
);
if (!context.mounted) return;
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const NowPlayingScreen()),
);
if (uebersprungen > 0) {
messenger.showSnackBar(
SnackBar(content: Text(offlineHinweis(uebersprungen))),
);
}
} catch (e) {
messenger.showSnackBar(
SnackBar(content: Text('Wiedergabe fehlgeschlagen: $e')),
);
}
},
),
);
},
);
}
}