diff --git a/lib/app/screens/album_screen.dart b/lib/app/screens/album_screen.dart index 43f20b8..44f18f6 100644 --- a/lib/app/screens/album_screen.dart +++ b/lib/app/screens/album_screen.dart @@ -10,6 +10,7 @@ import '../state/source.dart'; import '../ui/cover_art_theme.dart'; import '../ui/gradient.dart'; import '../ui/lists/header.dart'; +import '../ui/lists/items.dart'; import '../ui/lists/songs_list.dart'; class AlbumScreen extends HookConsumerWidget { @@ -61,7 +62,13 @@ class AlbumScreen extends HookConsumerWidget { onMore: () {}, ), ), - SongsList(query: query), + SongsList( + query: query, + itemBuilder: (context, item, index) => SongListTile( + song: item.song, + onTap: () {}, + ), + ), ], ), ), diff --git a/lib/app/screens/library_screen.dart b/lib/app/screens/library_screen.dart index 563132c..1882de1 100644 --- a/lib/app/screens/library_screen.dart +++ b/lib/app/screens/library_screen.dart @@ -1,13 +1,18 @@ +import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:material_symbols_icons/symbols.dart'; +import '../../database/query.dart'; import '../../l10n/generated/app_localizations.dart'; import '../state/services.dart'; +import '../state/source.dart'; import '../ui/lists/albums_grid.dart'; import '../ui/lists/artists_list.dart'; +import '../ui/lists/items.dart'; +import '../ui/lists/songs_list.dart'; import '../util/custom_scroll_fix.dart'; const kIconSize = 26.0; @@ -51,20 +56,7 @@ class LibraryScreen extends HookConsumerWidget { builder: (context) => CustomScrollProvider( tabController: tabController, parent: PrimaryScrollController.of(context), - child: TabBarView( - controller: tabController, - children: LibraryTab.values - .map( - (tab) => TabScrollView( - index: LibraryTab.values.indexOf(tab), - sliver: switch (tab) { - LibraryTab.albums => AlbumsGrid(), - _ => ArtistsList(), - }, - ), - ) - .toList(), - ), + child: LibraryTabBarView(tabController: tabController), ), ), ), @@ -72,6 +64,56 @@ class LibraryScreen extends HookConsumerWidget { } } +class LibraryTabBarView extends HookConsumerWidget { + const LibraryTabBarView({ + super.key, + required this.tabController, + }); + + final TabController tabController; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final sourceId = ref.watch(sourceIdProvider); + + final songsQuery = SongsQuery( + sourceId: sourceId, + sort: IList([ + SongsSortingTerm(dir: SortDirection.asc, by: SongsColumn.albumArtist), + SongsSortingTerm(dir: SortDirection.asc, by: SongsColumn.album), + SongsSortingTerm(dir: SortDirection.asc, by: SongsColumn.disc), + SongsSortingTerm(dir: SortDirection.asc, by: SongsColumn.track), + SongsSortingTerm(dir: SortDirection.asc, by: SongsColumn.title), + ]), + ); + + return TabBarView( + controller: tabController, + children: LibraryTab.values + .map( + (tab) => TabScrollView( + index: LibraryTab.values.indexOf(tab), + sliver: switch (tab) { + LibraryTab.albums => AlbumsGrid(), + LibraryTab.artists => ArtistsList(), + LibraryTab.songs => SongsList( + query: songsQuery, + itemBuilder: (context, item, index) => SongListTile( + song: item.song, + coverArt: item.albumCoverArt, + showLeading: true, + onTap: () {}, + ), + ), + _ => ArtistsList(), + }, + ), + ) + .toList(), + ); + } +} + class LibraryTabsHeader extends HookConsumerWidget { const LibraryTabsHeader({ super.key, diff --git a/lib/app/ui/lists/items.dart b/lib/app/ui/lists/items.dart index 9ba01a9..89eb7e2 100644 --- a/lib/app/ui/lists/items.dart +++ b/lib/app/ui/lists/items.dart @@ -66,19 +66,25 @@ class SongListTile extends StatelessWidget { const SongListTile({ super.key, required this.song, + this.coverArt, + this.showLeading = false, this.onTap, }); final Song song; + final String? coverArt; + final bool showLeading; final void Function()? onTap; @override Widget build(BuildContext context) { return ListTile( - // leading: CoverArtImage( - // coverArt: song.coverArt, - // thumbnail: true, - // ), + leading: showLeading + ? CoverArtImage( + coverArt: coverArt, + thumbnail: true, + ) + : null, title: Text(song.title), subtitle: Text(song.artist ?? ''), onTap: onTap, diff --git a/lib/app/ui/lists/songs_list.dart b/lib/app/ui/lists/songs_list.dart index 5eb3740..e08b5d3 100644 --- a/lib/app/ui/lists/songs_list.dart +++ b/lib/app/ui/lists/songs_list.dart @@ -2,12 +2,11 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; +import '../../../database/dao/library_dao.dart'; import '../../../database/query.dart'; -import '../../../sources/models.dart'; import '../../hooks/use_on_source.dart'; import '../../hooks/use_paging_controller.dart'; import '../../state/database.dart'; -import 'items.dart'; const kPageSize = 30; @@ -15,14 +14,17 @@ class SongsList extends HookConsumerWidget { const SongsList({ super.key, required this.query, + required this.itemBuilder, }); final SongsQuery query; + final Widget Function(BuildContext context, SongListItem item, int index) + itemBuilder; @override Widget build(BuildContext context, WidgetRef ref) { final db = ref.watch(databaseProvider); - final controller = usePagingController( + final controller = usePagingController( getNextPageKey: (state) => state.lastPageIsEmpty ? null : state.nextIntPageKey, fetchPage: (pageKey) => db.libraryDao.listSongs( @@ -42,13 +44,8 @@ class SongsList extends HookConsumerWidget { return PagedSliverList( state: state, fetchNextPage: fetchNextPage, - builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (context, item, index) { - return SongListTile( - song: item, - onTap: () async {}, - ); - }, + builderDelegate: PagedChildBuilderDelegate( + itemBuilder: itemBuilder, ), ); }, diff --git a/lib/database/dao/library_dao.dart b/lib/database/dao/library_dao.dart index 5545649..6515a50 100644 --- a/lib/database/dao/library_dao.dart +++ b/lib/database/dao/library_dao.dart @@ -6,7 +6,15 @@ import '../query.dart'; part 'library_dao.g.dart'; -typedef AristListItem = ({models.Artist artist, int? albumCount}); +typedef AristListItem = ({ + models.Artist artist, + int? albumCount, +}); + +typedef SongListItem = ({ + models.Song song, + String? albumCoverArt, +}); extension on SortDirection { OrderingMode toMode() => switch (this) { @@ -111,7 +119,7 @@ class LibraryDao extends DatabaseAccessor .get(); } - Future> listSongs(SongsQuery q) { + Future> listSongs(SongsQuery q) { var joinPlaylistSongs = false; var filter = songs.sourceId.equals(q.sourceId); @@ -127,6 +135,11 @@ class LibraryDao extends DatabaseAccessor final query = songs.select().join([ + leftOuterJoin( + albums, + albums.id.equalsExp(songs.albumId) & + albums.sourceId.equals(q.sourceId), + ), if (joinPlaylistSongs) leftOuterJoin( playlistSongs, @@ -134,6 +147,9 @@ class LibraryDao extends DatabaseAccessor playlistSongs.songId.equalsExp(songs.id), ), ]) + ..addColumns([ + albums.coverArt, + ]) ..where(filter) ..orderBy( q.sort @@ -144,6 +160,9 @@ class LibraryDao extends DatabaseAccessor SongsColumn.starred => songs.starred, SongsColumn.disc => songs.disc, SongsColumn.track => songs.track, + SongsColumn.album => songs.album, + SongsColumn.artist => songs.artist, + SongsColumn.albumArtist => albums.albumArtist, }, mode: sort.dir.toMode(), ), @@ -153,7 +172,14 @@ class LibraryDao extends DatabaseAccessor _limitQuery(query: query, limit: q.limit, offset: q.offset); - return query.map((row) => (row.readTable(songs))).get(); + return query + .map( + (row) => ( + song: row.readTable(songs), + albumCoverArt: row.read(albums.coverArt), + ), + ) + .get(); } Selectable getAlbum(int sourceId, String id) { diff --git a/lib/database/query.dart b/lib/database/query.dart index cd3bb6f..fec969e 100644 --- a/lib/database/query.dart +++ b/lib/database/query.dart @@ -27,6 +27,9 @@ enum SongsColumn { starred, disc, track, + album, + artist, + albumArtist, } @freezed