diff --git a/flutter_client/lib/cache/cache_first.dart b/flutter_client/lib/cache/cache_first.dart index 3c6c15d9..ad4e3beb 100644 --- a/flutter_client/lib/cache/cache_first.dart +++ b/flutter_client/lib/cache/cache_first.dart @@ -17,6 +17,8 @@ import 'dart:async'; +import 'package:flutter/foundation.dart' show debugPrint; + /// Wraps the watch + cold-cache fallback pattern. Generic over: /// D — the drift row type (or TypedResult for joins) /// T — the result type the caller wants (e.g. `List`) @@ -35,14 +37,19 @@ Stream cacheFirst({ required T Function(List) toResult, required Future Function() isOnline, bool alwaysRefresh = false, + String? tag, }) async* { // Tracks whether we've already kicked off a stale-while-revalidate // refresh for this stream subscription, so we don't fire one on every // drift re-emission (otherwise the populate cycles forever). var revalidated = false; + void log(String msg) { + if (tag != null) debugPrint('cacheFirst[$tag]: $msg'); + } await for (final rows in driftStream) { if (rows.isNotEmpty) { + log('drift hit (${rows.length} rows)'); yield toResult(rows); // Stale-while-revalidate: yield cache immediately, then kick off // a REST refresh in the background. Drift watch() picks up the @@ -55,15 +62,20 @@ Stream cacheFirst({ } continue; } + log('drift miss; checking connectivity'); if (await isOnline()) { + log('online; calling fetchAndPopulate'); try { await fetchAndPopulate(); + log('fetchAndPopulate done; awaiting drift re-emit'); // The drift watch() stream re-emits with the populated rows on // the next loop iteration. Don't yield here. - } catch (_) { + } catch (e, st) { + log('fetchAndPopulate failed: $e\n$st'); yield toResult(rows); // empty result; caller surfaces error } } else { + log('offline; yielding empty'); yield toResult(rows); // empty result; offline } } diff --git a/flutter_client/lib/library/album_detail_screen.dart b/flutter_client/lib/library/album_detail_screen.dart index 90825b90..a7bc694a 100644 --- a/flutter_client/lib/library/album_detail_screen.dart +++ b/flutter_client/lib/library/album_detail_screen.dart @@ -6,6 +6,7 @@ import '../cache/audio_cache_manager.dart'; import '../cache/db.dart'; import '../likes/like_button.dart'; import '../player/player_provider.dart'; +import '../shared/widgets/server_image.dart'; import '../theme/theme_extension.dart'; import 'library_providers.dart'; import 'widgets/track_row.dart'; @@ -27,7 +28,18 @@ class AlbumDetailScreen extends ConsumerWidget { Padding( padding: const EdgeInsets.all(16), child: Row(children: [ - Container(width: 96, height: 96, color: fs.slate), + ClipRRect( + borderRadius: BorderRadius.circular(6), + child: SizedBox( + width: 96, + height: 96, + child: ServerImage( + url: '/api/albums/$id/cover', + fit: BoxFit.cover, + fallback: Container(color: fs.slate), + ), + ), + ), const SizedBox(width: 16), Expanded(child: Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/flutter_client/lib/library/artist_detail_screen.dart b/flutter_client/lib/library/artist_detail_screen.dart index 32d49809..27c11554 100644 --- a/flutter_client/lib/library/artist_detail_screen.dart +++ b/flutter_client/lib/library/artist_detail_screen.dart @@ -6,6 +6,7 @@ import '../api/endpoints/likes.dart'; import '../likes/like_button.dart'; import '../models/album.dart'; import '../player/player_provider.dart'; +import '../shared/widgets/server_image.dart'; import '../theme/theme_extension.dart'; import 'library_providers.dart'; import 'widgets/album_card.dart'; @@ -30,9 +31,18 @@ class ArtistDetailScreen extends ConsumerWidget { Padding( padding: const EdgeInsets.all(16), child: Row(children: [ - Container( - width: 96, height: 96, - decoration: BoxDecoration(color: fs.slate, shape: BoxShape.circle), + ClipOval( + child: SizedBox( + width: 96, + height: 96, + child: a.coverUrl.isEmpty + ? Container(color: fs.slate) + : ServerImage( + url: a.coverUrl, + fit: BoxFit.cover, + fallback: Container(color: fs.slate), + ), + ), ), const SizedBox(width: 16), Expanded( diff --git a/flutter_client/lib/library/library_providers.dart b/flutter_client/lib/library/library_providers.dart index 256d5056..3c4d10e7 100644 --- a/flutter_client/lib/library/library_providers.dart +++ b/flutter_client/lib/library/library_providers.dart @@ -61,8 +61,10 @@ final artistProvider = toResult: (rows) => rows.isEmpty ? const ArtistRef(id: '', name: '') : rows.first.toRef(), - isOnline: () async => - (await ref.read(connectivityProvider.future)), + isOnline: () async => (await ref + .read(connectivityProvider.future) + .timeout(const Duration(seconds: 3), onTimeout: () => true)), + tag: 'artist($id)', ); }); @@ -90,8 +92,10 @@ final artistAlbumsProvider = final artist = r.readTableOrNull(db.cachedArtists); return album.toRef(artistName: artist?.name ?? ''); }).toList(), - isOnline: () async => - (await ref.read(connectivityProvider.future)), + isOnline: () async => (await ref + .read(connectivityProvider.future) + .timeout(const Duration(seconds: 3), onTimeout: () => true)), + tag: 'artistAlbums($artistId)', ); });