diff --git a/lib/screens/listening_statistics_screen.dart b/lib/screens/listening_statistics_screen.dart index 0d04dc54..612b3642 100644 --- a/lib/screens/listening_statistics_screen.dart +++ b/lib/screens/listening_statistics_screen.dart @@ -177,8 +177,10 @@ class _ListeningStatisticsScreenState } List _summarySlivers(BuildContext context, ListeningSummary data) { + final colors = Theme.of(context).colorScheme; final artists = data.artists.entries.toList() ..sort((a, b) => b.value.compareTo(a.value)); + final artistArtwork = data.artistArtwork; final days = data.days.entries.toList() ..sort((a, b) => a.key.compareTo(b.key)); return [ @@ -275,7 +277,26 @@ class _ListeningStatisticsScreenState children: [ for (final artist in artists.take(10)) ListTile( - leading: const Icon(Icons.person_outline), + leading: ExcludeSemantics( + child: ClipOval( + child: SizedBox.square( + dimension: 48, + child: artistArtwork[artist.key] == null + ? ColoredBox( + color: colors.surfaceContainerHighest, + child: const Icon(Icons.person_outline), + ) + : PlayerArtwork( + artUri: artistArtwork[artist.key], + colorScheme: colors, + cacheWidth: + (48 * MediaQuery.devicePixelRatioOf(context)) + .ceil(), + iconSize: 24, + ), + ), + ), + ), title: Text(artist.key), subtitle: Text( context.l10n.listeningStatsMinutes( diff --git a/lib/services/listening_statistics.dart b/lib/services/listening_statistics.dart index 48f92c7f..54d42a71 100644 --- a/lib/services/listening_statistics.dart +++ b/lib/services/listening_statistics.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:spotiflac_android/services/sqlite_helpers.dart' as sqlite; import 'package:spotiflac_android/utils/logger.dart'; +import 'package:spotiflac_android/utils/string_utils.dart'; import 'package:sqflite/sqflite.dart'; final _log = AppLogger('ListeningStatistics'); @@ -50,6 +51,23 @@ class ListeningSummary { final Map artists; final Map days; + Map get artistArtwork { + final artwork = {}; + final listeningTime = {}; + for (final total in tracks) { + final cover = normalizeCoverReference(total.track.artwork); + if (cover == null) continue; + final artist = total.track.artist; + if (listeningTime.containsKey(artist) && + total.milliseconds <= listeningTime[artist]!) { + continue; + } + artwork[artist] = cover; + listeningTime[artist] = total.milliseconds; + } + return artwork; + } + factory ListeningSummary.fromRows(List> rows) { final tracks = {}; final artists = {}; diff --git a/test/listening_and_releases_screen_test.dart b/test/listening_and_releases_screen_test.dart index ac59c23d..e399d8de 100644 --- a/test/listening_and_releases_screen_test.dart +++ b/test/listening_and_releases_screen_test.dart @@ -10,6 +10,7 @@ import 'package:spotiflac_android/screens/weekly_releases_screen.dart'; import 'package:spotiflac_android/services/listening_statistics.dart'; import 'package:spotiflac_android/services/weekly_releases.dart'; import 'package:spotiflac_android/theme/mornye_theme.dart'; +import 'package:spotiflac_android/widgets/player_artwork.dart'; class _Settings extends SettingsNotifier { @override @@ -43,7 +44,7 @@ void main() { 'album': 'Album', 'milliseconds': 120000, 'plays': 1, - 'artwork': null, + 'artwork': '/covers/album.jpg', }, ]); }), @@ -57,6 +58,23 @@ void main() { await tester.pumpAndSettle(); expect(requested.last.days, 0); expect(requested.last.year, DateTime.now().year); + final artistTile = find.byWidgetPredicate( + (widget) => + widget is ListTile && + widget.title is Text && + (widget.title! as Text).data == 'Artist', + ); + await tester.scrollUntilVisible(artistTile, 150); + await tester.pumpAndSettle(); + final artistArtwork = find.descendant( + of: artistTile, + matching: find.byType(PlayerArtwork), + ); + expect(artistArtwork, findsOneWidget); + expect( + tester.widget(artistArtwork).artUri, + '/covers/album.jpg', + ); expect(tester.takeException(), isNull); }, ); diff --git a/test/listening_statistics_test.dart b/test/listening_statistics_test.dart index 2ab8e6d5..7abca20e 100644 --- a/test/listening_statistics_test.dart +++ b/test/listening_statistics_test.dart @@ -172,4 +172,58 @@ void main() { expect(summary.artists, {'Artist': 120000}); expect(summary.days.length, 2); }); + + test( + 'artist artwork uses the most listened track with an available cover', + () { + final summary = ListeningSummary( + tracks: [ + ListeningTotal(_one, 180000, 3), + ListeningTotal( + const ListeningTrack( + key: 'other', + title: 'Other', + artist: 'Other Artist', + album: 'Other Album', + artwork: 'https://example.com/other.jpg', + ), + 60000, + 1, + ), + ListeningTotal( + const ListeningTrack( + key: 'less', + title: 'Less', + artist: 'Artist', + album: 'Another Album', + artwork: 'https://example.com/less.jpg', + ), + 30000, + 1, + ), + ListeningTotal( + const ListeningTrack( + key: 'more', + title: 'More', + artist: 'Artist', + album: 'Album', + artwork: ' file:///covers/album.jpg ', + ), + 120000, + 2, + ), + ], + ); + expect(summary.artistArtwork, { + 'Artist': 'file:///covers/album.jpg', + 'Other Artist': 'https://example.com/other.jpg', + }); + expect( + ListeningSummary( + tracks: [ListeningTotal(_one, 60000, 1)], + ).artistArtwork, + isEmpty, + ); + }, + ); }