diff --git a/flutter_client/lib/library/album_detail_screen.dart b/flutter_client/lib/library/album_detail_screen.dart index 20b4c82a..a563dd0b 100644 --- a/flutter_client/lib/library/album_detail_screen.dart +++ b/flutter_client/lib/library/album_detail_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../api/endpoints/likes.dart'; import '../likes/like_button.dart'; +import '../player/player_provider.dart'; import '../theme/theme_extension.dart'; import 'library_providers.dart'; import 'widgets/track_row.dart'; @@ -42,7 +43,7 @@ class AlbumDetailScreen extends ConsumerWidget { decoration: BoxDecoration(color: fs.accent, shape: BoxShape.circle), child: IconButton( icon: Icon(Icons.play_arrow, color: fs.parchment), - onPressed: () { /* play wired in Task 18 */ }, + onPressed: () => ref.read(playerActionsProvider).playTracks(r.tracks), ), ), LikeButton(kind: LikeKind.album, id: r.album.id, size: 28), @@ -51,7 +52,10 @@ class AlbumDetailScreen extends ConsumerWidget { for (final t in r.tracks) TrackRow( track: t, - onTap: () { /* play wired in Task 18 */ }, + onTap: () { + final start = r.tracks.indexOf(t); + ref.read(playerActionsProvider).playTracks(r.tracks, initialIndex: start); + }, trailing: LikeButton(kind: LikeKind.track, id: t.id), ), ]), diff --git a/flutter_client/lib/library/artist_detail_screen.dart b/flutter_client/lib/library/artist_detail_screen.dart index f3185170..32d49809 100644 --- a/flutter_client/lib/library/artist_detail_screen.dart +++ b/flutter_client/lib/library/artist_detail_screen.dart @@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart'; import '../api/endpoints/likes.dart'; import '../likes/like_button.dart'; import '../models/album.dart'; +import '../player/player_provider.dart'; import '../theme/theme_extension.dart'; import 'library_providers.dart'; import 'widgets/album_card.dart'; @@ -49,7 +50,12 @@ class ArtistDetailScreen extends ConsumerWidget { decoration: BoxDecoration(color: fs.accent, shape: BoxShape.circle), child: IconButton( icon: Icon(Icons.play_arrow, color: fs.parchment), - onPressed: () { /* play wired in Task 18 */ }, + onPressed: () async { + final tracks = await ref.read(artistTracksProvider(id).future); + if (tracks.isEmpty) return; + final shuffled = [...tracks]..shuffle(); + ref.read(playerActionsProvider).playTracks(shuffled); + }, ), ), LikeButton(kind: LikeKind.artist, id: a.id, size: 28), diff --git a/flutter_client/lib/library/home_screen.dart b/flutter_client/lib/library/home_screen.dart index dbffd213..069b8ed7 100644 --- a/flutter_client/lib/library/home_screen.dart +++ b/flutter_client/lib/library/home_screen.dart @@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart'; import '../models/album.dart'; import '../models/artist.dart'; import '../models/track.dart'; +import '../player/player_provider.dart'; import '../theme/theme_extension.dart'; import 'library_providers.dart'; import 'widgets/album_card.dart'; @@ -66,7 +67,10 @@ class HomeScreen extends ConsumerWidget { for (final t in tracks) SizedBox( width: 280, - child: TrackRow(track: t, onTap: () { /* play wired in Task 18 */ }), + child: TrackRow( + track: t, + onTap: () => ref.read(playerActionsProvider).playTracks([t]), + ), ), ], ); diff --git a/flutter_client/lib/player/player_bar.dart b/flutter_client/lib/player/player_bar.dart new file mode 100644 index 00000000..fbda3315 --- /dev/null +++ b/flutter_client/lib/player/player_bar.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; + +import '../theme/theme_extension.dart'; +import 'player_provider.dart'; + +class PlayerBar extends ConsumerWidget { + const PlayerBar({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final fs = Theme.of(context).extension()!; + final mediaItem = ref.watch(mediaItemProvider).valueOrNull; + final playback = ref.watch(playbackStateProvider).valueOrNull; + if (mediaItem == null) return const SizedBox.shrink(); + + return Material( + color: fs.iron, + child: InkWell( + onTap: () => context.push('/now-playing'), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row(children: [ + Container(width: 48, height: 48, color: fs.slate), + const SizedBox(width: 12), + Expanded(child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(mediaItem.title, maxLines: 1, overflow: TextOverflow.ellipsis, + style: TextStyle(color: fs.parchment, fontSize: 14)), + Text(mediaItem.artist ?? '', maxLines: 1, overflow: TextOverflow.ellipsis, + style: TextStyle(color: fs.ash, fontSize: 12)), + ], + )), + IconButton( + icon: Icon(playback?.playing == true ? Icons.pause : Icons.play_arrow, color: fs.parchment), + onPressed: () { + final h = ref.read(audioHandlerProvider); + if (playback?.playing == true) { h.pause(); } else { h.play(); } + }, + ), + IconButton( + icon: Icon(Icons.skip_next, color: fs.parchment), + onPressed: () => ref.read(audioHandlerProvider).skipToNext(), + ), + ]), + ), + ), + ); + } +} diff --git a/flutter_client/lib/shared/routing.dart b/flutter_client/lib/shared/routing.dart index b9d6d870..b376aa5c 100644 --- a/flutter_client/lib/shared/routing.dart +++ b/flutter_client/lib/shared/routing.dart @@ -8,6 +8,7 @@ import '../auth/server_url_screen.dart'; import '../library/album_detail_screen.dart'; import '../library/artist_detail_screen.dart'; import '../library/home_screen.dart'; +import '../player/player_bar.dart'; import 'widgets/version_gate.dart'; GoRouter buildRouter(Ref ref) { @@ -53,10 +54,10 @@ class _ShellWithPlayerBar extends StatelessWidget { final Widget child; @override Widget build(BuildContext context) { - // PlayerBar is wired in Task 18; this placeholder leaves the shell slot. return Column( children: [ Expanded(child: child), + const PlayerBar(), ], ); }