diff --git a/flutter_client/lib/library/artist_detail_screen.dart b/flutter_client/lib/library/artist_detail_screen.dart index 8a18a0ec..497d24a8 100644 --- a/flutter_client/lib/library/artist_detail_screen.dart +++ b/flutter_client/lib/library/artist_detail_screen.dart @@ -89,8 +89,10 @@ class ArtistDetailScreen extends ConsumerWidget { (constraints.maxWidth - sidePad * 2 - gap * (cols - 1)) / cols; // Card content: cover (cellW - 16) + 8 + title (≤2 lines - // ≈ 36) + artist line ≈ 16 + small fudge. - final cellH = (cellW - 16) + 8 + 36 + 16 + 4; + // ≈ 36) + small fudge. Artist line is suppressed in this + // grid (showArtist: false) since the page header already + // names the artist. + final cellH = (cellW - 16) + 8 + 36 + 4; return GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), @@ -108,6 +110,7 @@ class ArtistDetailScreen extends ConsumerWidget { album: album, width: cellW, titleMaxLines: 2, + showArtist: false, onTap: () => context.push('/albums/${album.id}'), ); }, diff --git a/flutter_client/lib/library/library_providers.dart b/flutter_client/lib/library/library_providers.dart index 3c4d10e7..f212d8d7 100644 --- a/flutter_client/lib/library/library_providers.dart +++ b/flutter_client/lib/library/library_providers.dart @@ -179,7 +179,35 @@ final albumProvider = StreamProvider.family< .getAlbum(albumId) .timeout(const Duration(seconds: 10)); debugPrint('albumProvider($albumId): got ${fresh.tracks.length} tracks, writing to drift'); + // Collect every artist mentioned by the album + its tracks so + // the JOINs that drive both the album header and the track + // rows have something to bind to. Without this, drift returns + // null for the artist row and artistName surfaces empty — + // visible as a missing artist line in the mini player and + // the row list. + final artists = {}; + if (fresh.album.artistId.isNotEmpty && + fresh.album.artistName.isNotEmpty) { + artists[fresh.album.artistId] = ArtistRef( + id: fresh.album.artistId, + name: fresh.album.artistName, + ); + } + for (final t in fresh.tracks) { + if (t.artistId.isNotEmpty && + t.artistName.isNotEmpty && + !artists.containsKey(t.artistId)) { + artists[t.artistId] = ArtistRef( + id: t.artistId, + name: t.artistName, + ); + } + } await db.batch((b) { + if (artists.isNotEmpty) { + b.insertAllOnConflictUpdate( + db.cachedArtists, artists.values.map((a) => a.toDrift()).toList()); + } b.insertAllOnConflictUpdate(db.cachedAlbums, [fresh.album.toDrift()]); b.insertAllOnConflictUpdate(db.cachedTracks, fresh.tracks.map((t) => t.toDrift()).toList()); diff --git a/flutter_client/lib/library/widgets/album_card.dart b/flutter_client/lib/library/widgets/album_card.dart index 54d027e3..24698634 100644 --- a/flutter_client/lib/library/widgets/album_card.dart +++ b/flutter_client/lib/library/widgets/album_card.dart @@ -11,6 +11,7 @@ class AlbumCard extends StatelessWidget { required this.onTap, this.width = 140, this.titleMaxLines = 1, + this.showArtist = true, super.key, }); final AlbumRef album; @@ -26,6 +27,10 @@ class AlbumCard extends StatelessWidget { /// ellipsized character at narrower widths. final int titleMaxLines; + /// Suppress the artist name. Useful on surfaces where the artist is + /// already implied by the page header (e.g. artist detail). + final bool showArtist; + @override Widget build(BuildContext context) { final fs = Theme.of(context).extension()!; @@ -61,12 +66,13 @@ class AlbumCard extends StatelessWidget { overflow: TextOverflow.ellipsis, style: TextStyle(color: fs.parchment, fontSize: 14), ), - Text( - album.artistName, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle(color: fs.ash, fontSize: 12), - ), + if (showArtist && album.artistName.isNotEmpty) + Text( + album.artistName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: fs.ash, fontSize: 12), + ), ], ), ), diff --git a/flutter_client/lib/library/widgets/track_row.dart b/flutter_client/lib/library/widgets/track_row.dart index cc73089d..1c930a99 100644 --- a/flutter_client/lib/library/widgets/track_row.dart +++ b/flutter_client/lib/library/widgets/track_row.dart @@ -30,31 +30,39 @@ class TrackRow extends StatelessWidget { return InkWell( onTap: onTap, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), child: Row(children: [ if (track.trackNumber != null) SizedBox( - width: 28, + width: 22, child: Text( track.trackNumber.toString(), style: TextStyle(color: fs.ash, fontSize: 13), ), ), Expanded( - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - track.title, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle(color: fs.parchment, fontSize: 14), - ), - Text( - track.artistName, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle(color: fs.ash, fontSize: 12), - ), - ]), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + track.title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: fs.parchment, fontSize: 14), + ), + // Skip the artist line entirely when empty so the row + // height collapses to a single line of title — keeps + // dense album views from looking padded. + if (track.artistName.isNotEmpty) + Text( + track.artistName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: fs.ash, fontSize: 12), + ), + ], + ), ), CachedIndicator(trackId: track.id), Text('$mins:$secs', style: TextStyle(color: fs.ash, fontSize: 12)),