dfbeed01a6
- models/my_profile.dart (MyProfile + ListenBrainzStatus) - api/endpoints/settings.dart (profile / password / listenbrainz CRUD) - settings/settings_screen.dart with three sections: - Profile: display_name + email, Save profile - Password: current + new + confirm, validates >=8 + match - ListenBrainz: token paste + scrobble-enabled toggle - /settings route + Settings icon on home AppBar - Home AppBar now: Library + Playlists + Search + Settings (was just Search)
116 lines
4.0 KiB
Dart
116 lines
4.0 KiB
Dart
import 'package:dio/dio.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
|
|
import '../api/errors.dart';
|
|
import '../models/album.dart';
|
|
import '../models/artist.dart';
|
|
import '../models/track.dart';
|
|
import '../player/player_provider.dart';
|
|
import '../shared/widgets/connection_error_banner.dart';
|
|
import '../theme/theme_extension.dart';
|
|
import 'library_providers.dart';
|
|
import 'widgets/album_card.dart';
|
|
import 'widgets/artist_card.dart';
|
|
import 'widgets/horizontal_scroll_row.dart';
|
|
import 'widgets/track_row.dart';
|
|
|
|
class HomeScreen extends ConsumerWidget {
|
|
const HomeScreen({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final fs = Theme.of(context).extension<FabledSwordTheme>()!;
|
|
return Scaffold(
|
|
backgroundColor: fs.obsidian,
|
|
appBar: AppBar(
|
|
backgroundColor: fs.obsidian,
|
|
elevation: 0,
|
|
title: Text('Minstrel', style: TextStyle(color: fs.parchment)),
|
|
actions: [
|
|
IconButton(
|
|
icon: Icon(Icons.library_music, color: fs.parchment),
|
|
tooltip: 'Library',
|
|
onPressed: () => context.push('/library'),
|
|
),
|
|
IconButton(
|
|
icon: Icon(Icons.queue_music, color: fs.parchment),
|
|
tooltip: 'Playlists',
|
|
onPressed: () => context.push('/playlists'),
|
|
),
|
|
IconButton(
|
|
icon: Icon(Icons.search, color: fs.parchment),
|
|
tooltip: 'Search',
|
|
onPressed: () => context.push('/search'),
|
|
),
|
|
IconButton(
|
|
icon: Icon(Icons.settings, color: fs.parchment),
|
|
tooltip: 'Settings',
|
|
onPressed: () => context.push('/settings'),
|
|
),
|
|
],
|
|
),
|
|
body: SafeArea(
|
|
child: ref.watch(homeProvider).when(
|
|
error: (e, _) {
|
|
final code = e is DioException ? ApiError.fromDio(e).code : 'unknown';
|
|
if (code == 'connection_refused') {
|
|
return ConnectionErrorBanner(
|
|
onRetry: () => ref.refresh(homeProvider),
|
|
);
|
|
}
|
|
return Center(child: Text('$e', style: TextStyle(color: fs.error)));
|
|
},
|
|
loading: () => const Center(child: CircularProgressIndicator()),
|
|
data: (h) => RefreshIndicator(
|
|
onRefresh: () async => ref.refresh(homeProvider.future),
|
|
child: ListView(children: [
|
|
_albumsRow(context, 'Recently added', h.recentlyAddedAlbums),
|
|
_albumsRow(context, 'Rediscover', h.rediscoverAlbums),
|
|
_artistsRow(context, 'Rediscover artists', h.rediscoverArtists),
|
|
_tracksRow(context, ref, 'Most played', h.mostPlayedTracks),
|
|
_artistsRow(context, 'Last played artists', h.lastPlayedArtists),
|
|
const SizedBox(height: 96),
|
|
]),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _albumsRow(BuildContext ctx, String title, List<AlbumRef> albums) =>
|
|
HorizontalScrollRow(
|
|
title: title,
|
|
children: [
|
|
for (final a in albums)
|
|
AlbumCard(album: a, onTap: () => ctx.push('/albums/${a.id}')),
|
|
],
|
|
);
|
|
|
|
Widget _artistsRow(BuildContext ctx, String title, List<ArtistRef> artists) =>
|
|
HorizontalScrollRow(
|
|
title: title,
|
|
children: [
|
|
for (final a in artists)
|
|
ArtistCard(artist: a, onTap: () => ctx.push('/artists/${a.id}')),
|
|
],
|
|
);
|
|
|
|
Widget _tracksRow(BuildContext ctx, WidgetRef ref, String title, List<TrackRef> tracks) =>
|
|
HorizontalScrollRow(
|
|
title: title,
|
|
height: 64,
|
|
children: [
|
|
for (final t in tracks)
|
|
SizedBox(
|
|
width: 280,
|
|
child: TrackRow(
|
|
track: t,
|
|
onTap: () => ref.read(playerActionsProvider).playTracks([t]),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|