nkbmusic/lib/features/library/library_screen.dart
metatroncubeswdev ad8c4f1c6b App: speaker/Bluetooth picker, Settings & help, Privacy Policy, Terms, FAQ, song reports
- Now Playing shows where music plays and opens the phone's output switcher
- Settings hub: Wi-Fi-only downloads (default on), storage, sound, help, legal
- Privacy Policy and Terms bundled as Markdown (assets/legal), shown in-app
- 'Is this your song?' report form feeding takedown_requests (+ kind, user_id)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:00:16 -04:00

137 lines
5.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../app/theme.dart';
import '../../core/env.dart';
import '../../audio/player_providers.dart';
import '../../core/models.dart';
import '../../data/user_library.dart';
import '../../shared/widgets/artwork.dart';
import '../../shared/widgets/catalog_builder.dart';
import '../../shared/widgets/section_header.dart';
import '../../shared/widgets/track_tile.dart';
enum _LibraryView { liked, recent, singers }
class LibraryScreen extends ConsumerStatefulWidget {
const LibraryScreen({super.key});
@override
ConsumerState<LibraryScreen> createState() => _LibraryScreenState();
}
class _LibraryScreenState extends ConsumerState<LibraryScreen> {
_LibraryView _view = _LibraryView.liked;
@override
Widget build(BuildContext context) {
return SafeArea(
bottom: false,
child: CatalogBuilder(builder: (catalog) {
final liked = ref.watch(likedSongsProvider);
final recent = ref.watch(recentlyPlayedProvider);
final controller = ref.read(playerControllerProvider);
List<Track> resolve(Iterable<String> ids) => ids.map(catalog.trackById).whereType<Track>().toList();
final Widget content = switch (_view) {
_LibraryView.liked => _TrackList(
tracks: resolve(liked),
empty: const EmptyState(
icon: Icons.favorite_border,
title: 'No liked songs yet',
message: 'Tap the heart on any song to keep it here.',
),
onPlay: controller.playTracks,
),
_LibraryView.recent => _TrackList(
tracks: resolve(recent),
empty: const EmptyState(icon: Icons.history, title: 'Nothing played yet'),
onPlay: controller.playTracks,
),
_LibraryView.singers => ListView(
padding: const EdgeInsets.symmetric(horizontal: 18),
children: [
for (final a in catalog.artists)
ListTile(
contentPadding: EdgeInsets.zero,
leading: SingerAvatar(artist: a, size: 52),
title: Text(a.name, style: const TextStyle(fontWeight: FontWeight.w700)),
subtitle: Text('${catalog.byArtist(a.id).length} songs', style: const TextStyle(color: NkColors.muted)),
onTap: () => context.push('/artist/${a.id}'),
),
],
),
};
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(18, 12, 8, 12),
child: Row(
children: [
Expanded(child: Text('Your library', style: Theme.of(context).textTheme.headlineMedium)),
IconButton(
tooltip: 'Settings & help',
icon: const Icon(Icons.settings_outlined),
onPressed: () => context.push('/settings'),
),
if (Env.hasSupabase)
IconButton(
tooltip: 'Your profile',
icon: const Icon(Icons.account_circle_outlined),
onPressed: () => context.push('/profile'),
),
],
),
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 18),
child: Row(
children: [
for (final (view, label) in const [
(_LibraryView.liked, 'Liked songs'),
(_LibraryView.recent, 'Recently played'),
(_LibraryView.singers, 'Singers'),
])
Padding(
padding: const EdgeInsets.only(right: 8),
child: ChoiceChip(
label: Text(label),
selected: _view == view,
onSelected: (_) => setState(() => _view = view),
),
),
],
),
),
const SizedBox(height: 8),
Expanded(child: content),
],
);
}),
);
}
}
class _TrackList extends StatelessWidget {
const _TrackList({required this.tracks, required this.empty, required this.onPlay});
final List<Track> tracks;
final Widget empty;
final Future<void> Function(List<Track>, {int index, bool shuffle}) onPlay;
@override
Widget build(BuildContext context) {
if (tracks.isEmpty) return Center(child: SingleChildScrollView(child: empty));
return ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 18),
itemCount: tracks.length,
itemBuilder: (_, i) => TrackTile(track: tracks[i], onTap: () => onPlay(tracks, index: i)),
);
}
}