feat(player): Now Playing als Tab statt Push-Screen, Favoriten-Herz

- songToMediaItem trägt jetzt die Song-UUID in extras['songId'], damit
  Downstream-Code (Favoriten-Button, später Playback-History) den echten
  Song wiederfinden kann statt nur die file-URI zu haben
- NowPlayingScreen: Schließen-Button entfernt (AppBar-leading), da der
  Screen jetzt als eigener Tab läuft statt gepusht zu werden
- FavoriteButton für den aktuellen Song in die AppBar-Actions eingebaut,
  reaktiv über einen verschachtelten StreamBuilder<MediaItem?>; ohne
  gültige songId (z.B. nichts spielt) wird kein Button gerendert

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Hermes (Server)
2026-08-18 17:01:31 +02:00
co-authored by Claude Haiku 4.5
parent 62c65e8eca
commit 2712ace665
3 changed files with 34 additions and 5 deletions
+1
View File
@@ -10,6 +10,7 @@ MediaItem songToMediaItem(Song s) => MediaItem(
album: s.album, album: s.album,
duration: s.durationMs != null ? Duration(milliseconds: s.durationMs!) : null, duration: s.durationMs != null ? Duration(milliseconds: s.durationMs!) : null,
artUri: s.coverPath != null ? Uri.file(s.coverPath!) : null, artUri: s.coverPath != null ? Uri.file(s.coverPath!) : null,
extras: {'songId': s.id},
); );
/// Spielt [songs] ab [startIndex] ab. /// Spielt [songs] ab [startIndex] ab.
+9 -5
View File
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../shared/cover.dart'; import '../shared/cover.dart';
import '../shared/favorite_button.dart';
import '../shared/theme.dart'; import '../shared/theme.dart';
import 'audio_handler.dart'; import 'audio_handler.dart';
@@ -16,12 +17,15 @@ class NowPlayingScreen extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
leading: IconButton(
tooltip: 'Schließen',
icon: const Icon(Icons.keyboard_arrow_down),
onPressed: () => Navigator.of(context).maybePop(),
),
actions: [ actions: [
StreamBuilder<MediaItem?>(
stream: handler.mediaItem,
builder: (context, snapshot) {
final songId = snapshot.data?.extras?['songId'] as String?;
if (songId == null) return const SizedBox.shrink();
return FavoriteButton(songId: songId);
},
),
_SleepTimerButton(handler: handler), _SleepTimerButton(handler: handler),
], ],
), ),
+24
View File
@@ -0,0 +1,24 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:melo/library/database.dart';
import 'package:melo/library/song_media.dart';
void main() {
test('songToMediaItem trägt die Song-UUID in extras', () {
final song = Song(
id: 'song-1',
path: '/a.mp3',
title: 'A',
artist: null,
album: null,
durationMs: null,
coverPath: null,
dateAddedMs: 0,
updatedAtMs: 0,
deleted: false,
);
final item = songToMediaItem(song);
expect(item.extras?['songId'], song.id);
});
}