ec0c10f312
ApiClient.buildDio takes on401; the library's dioProvider wires it to the auth controller's clearSession. The router redirect already handles navigation away from authed screens once the session goes null. HomeScreen surfaces the banner on connection_refused with Retry + Change URL.
89 lines
3.1 KiB
Dart
89 lines
3.1 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,
|
|
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]),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|