mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-27 09:09:29 +01:00
songs list tab
This commit is contained in:
parent
16a79c81cb
commit
a4e4c6fa57
@ -10,6 +10,7 @@ import '../state/source.dart';
|
|||||||
import '../ui/cover_art_theme.dart';
|
import '../ui/cover_art_theme.dart';
|
||||||
import '../ui/gradient.dart';
|
import '../ui/gradient.dart';
|
||||||
import '../ui/lists/header.dart';
|
import '../ui/lists/header.dart';
|
||||||
|
import '../ui/lists/items.dart';
|
||||||
import '../ui/lists/songs_list.dart';
|
import '../ui/lists/songs_list.dart';
|
||||||
|
|
||||||
class AlbumScreen extends HookConsumerWidget {
|
class AlbumScreen extends HookConsumerWidget {
|
||||||
@ -61,7 +62,13 @@ class AlbumScreen extends HookConsumerWidget {
|
|||||||
onMore: () {},
|
onMore: () {},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SongsList(query: query),
|
SongsList(
|
||||||
|
query: query,
|
||||||
|
itemBuilder: (context, item, index) => SongListTile(
|
||||||
|
song: item.song,
|
||||||
|
onTap: () {},
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,13 +1,18 @@
|
|||||||
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
|
import '../../database/query.dart';
|
||||||
import '../../l10n/generated/app_localizations.dart';
|
import '../../l10n/generated/app_localizations.dart';
|
||||||
import '../state/services.dart';
|
import '../state/services.dart';
|
||||||
|
import '../state/source.dart';
|
||||||
import '../ui/lists/albums_grid.dart';
|
import '../ui/lists/albums_grid.dart';
|
||||||
import '../ui/lists/artists_list.dart';
|
import '../ui/lists/artists_list.dart';
|
||||||
|
import '../ui/lists/items.dart';
|
||||||
|
import '../ui/lists/songs_list.dart';
|
||||||
import '../util/custom_scroll_fix.dart';
|
import '../util/custom_scroll_fix.dart';
|
||||||
|
|
||||||
const kIconSize = 26.0;
|
const kIconSize = 26.0;
|
||||||
@ -51,20 +56,7 @@ class LibraryScreen extends HookConsumerWidget {
|
|||||||
builder: (context) => CustomScrollProvider(
|
builder: (context) => CustomScrollProvider(
|
||||||
tabController: tabController,
|
tabController: tabController,
|
||||||
parent: PrimaryScrollController.of(context),
|
parent: PrimaryScrollController.of(context),
|
||||||
child: TabBarView(
|
child: LibraryTabBarView(tabController: tabController),
|
||||||
controller: tabController,
|
|
||||||
children: LibraryTab.values
|
|
||||||
.map(
|
|
||||||
(tab) => TabScrollView(
|
|
||||||
index: LibraryTab.values.indexOf(tab),
|
|
||||||
sliver: switch (tab) {
|
|
||||||
LibraryTab.albums => AlbumsGrid(),
|
|
||||||
_ => ArtistsList(),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -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 {
|
class LibraryTabsHeader extends HookConsumerWidget {
|
||||||
const LibraryTabsHeader({
|
const LibraryTabsHeader({
|
||||||
super.key,
|
super.key,
|
||||||
|
|||||||
@ -66,19 +66,25 @@ class SongListTile extends StatelessWidget {
|
|||||||
const SongListTile({
|
const SongListTile({
|
||||||
super.key,
|
super.key,
|
||||||
required this.song,
|
required this.song,
|
||||||
|
this.coverArt,
|
||||||
|
this.showLeading = false,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Song song;
|
final Song song;
|
||||||
|
final String? coverArt;
|
||||||
|
final bool showLeading;
|
||||||
final void Function()? onTap;
|
final void Function()? onTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
// leading: CoverArtImage(
|
leading: showLeading
|
||||||
// coverArt: song.coverArt,
|
? CoverArtImage(
|
||||||
// thumbnail: true,
|
coverArt: coverArt,
|
||||||
// ),
|
thumbnail: true,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
title: Text(song.title),
|
title: Text(song.title),
|
||||||
subtitle: Text(song.artist ?? ''),
|
subtitle: Text(song.artist ?? ''),
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
|
|||||||
@ -2,12 +2,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||||
|
|
||||||
|
import '../../../database/dao/library_dao.dart';
|
||||||
import '../../../database/query.dart';
|
import '../../../database/query.dart';
|
||||||
import '../../../sources/models.dart';
|
|
||||||
import '../../hooks/use_on_source.dart';
|
import '../../hooks/use_on_source.dart';
|
||||||
import '../../hooks/use_paging_controller.dart';
|
import '../../hooks/use_paging_controller.dart';
|
||||||
import '../../state/database.dart';
|
import '../../state/database.dart';
|
||||||
import 'items.dart';
|
|
||||||
|
|
||||||
const kPageSize = 30;
|
const kPageSize = 30;
|
||||||
|
|
||||||
@ -15,14 +14,17 @@ class SongsList extends HookConsumerWidget {
|
|||||||
const SongsList({
|
const SongsList({
|
||||||
super.key,
|
super.key,
|
||||||
required this.query,
|
required this.query,
|
||||||
|
required this.itemBuilder,
|
||||||
});
|
});
|
||||||
|
|
||||||
final SongsQuery query;
|
final SongsQuery query;
|
||||||
|
final Widget Function(BuildContext context, SongListItem item, int index)
|
||||||
|
itemBuilder;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final db = ref.watch(databaseProvider);
|
final db = ref.watch(databaseProvider);
|
||||||
final controller = usePagingController<int, Song>(
|
final controller = usePagingController<int, SongListItem>(
|
||||||
getNextPageKey: (state) =>
|
getNextPageKey: (state) =>
|
||||||
state.lastPageIsEmpty ? null : state.nextIntPageKey,
|
state.lastPageIsEmpty ? null : state.nextIntPageKey,
|
||||||
fetchPage: (pageKey) => db.libraryDao.listSongs(
|
fetchPage: (pageKey) => db.libraryDao.listSongs(
|
||||||
@ -42,13 +44,8 @@ class SongsList extends HookConsumerWidget {
|
|||||||
return PagedSliverList(
|
return PagedSliverList(
|
||||||
state: state,
|
state: state,
|
||||||
fetchNextPage: fetchNextPage,
|
fetchNextPage: fetchNextPage,
|
||||||
builderDelegate: PagedChildBuilderDelegate<Song>(
|
builderDelegate: PagedChildBuilderDelegate<SongListItem>(
|
||||||
itemBuilder: (context, item, index) {
|
itemBuilder: itemBuilder,
|
||||||
return SongListTile(
|
|
||||||
song: item,
|
|
||||||
onTap: () async {},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -6,7 +6,15 @@ import '../query.dart';
|
|||||||
|
|
||||||
part 'library_dao.g.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 {
|
extension on SortDirection {
|
||||||
OrderingMode toMode() => switch (this) {
|
OrderingMode toMode() => switch (this) {
|
||||||
@ -111,7 +119,7 @@ class LibraryDao extends DatabaseAccessor<SubtracksDatabase>
|
|||||||
.get();
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<models.Song>> listSongs(SongsQuery q) {
|
Future<List<SongListItem>> listSongs(SongsQuery q) {
|
||||||
var joinPlaylistSongs = false;
|
var joinPlaylistSongs = false;
|
||||||
var filter = songs.sourceId.equals(q.sourceId);
|
var filter = songs.sourceId.equals(q.sourceId);
|
||||||
|
|
||||||
@ -127,6 +135,11 @@ class LibraryDao extends DatabaseAccessor<SubtracksDatabase>
|
|||||||
|
|
||||||
final query =
|
final query =
|
||||||
songs.select().join([
|
songs.select().join([
|
||||||
|
leftOuterJoin(
|
||||||
|
albums,
|
||||||
|
albums.id.equalsExp(songs.albumId) &
|
||||||
|
albums.sourceId.equals(q.sourceId),
|
||||||
|
),
|
||||||
if (joinPlaylistSongs)
|
if (joinPlaylistSongs)
|
||||||
leftOuterJoin(
|
leftOuterJoin(
|
||||||
playlistSongs,
|
playlistSongs,
|
||||||
@ -134,6 +147,9 @@ class LibraryDao extends DatabaseAccessor<SubtracksDatabase>
|
|||||||
playlistSongs.songId.equalsExp(songs.id),
|
playlistSongs.songId.equalsExp(songs.id),
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
|
..addColumns([
|
||||||
|
albums.coverArt,
|
||||||
|
])
|
||||||
..where(filter)
|
..where(filter)
|
||||||
..orderBy(
|
..orderBy(
|
||||||
q.sort
|
q.sort
|
||||||
@ -144,6 +160,9 @@ class LibraryDao extends DatabaseAccessor<SubtracksDatabase>
|
|||||||
SongsColumn.starred => songs.starred,
|
SongsColumn.starred => songs.starred,
|
||||||
SongsColumn.disc => songs.disc,
|
SongsColumn.disc => songs.disc,
|
||||||
SongsColumn.track => songs.track,
|
SongsColumn.track => songs.track,
|
||||||
|
SongsColumn.album => songs.album,
|
||||||
|
SongsColumn.artist => songs.artist,
|
||||||
|
SongsColumn.albumArtist => albums.albumArtist,
|
||||||
},
|
},
|
||||||
mode: sort.dir.toMode(),
|
mode: sort.dir.toMode(),
|
||||||
),
|
),
|
||||||
@ -153,7 +172,14 @@ class LibraryDao extends DatabaseAccessor<SubtracksDatabase>
|
|||||||
|
|
||||||
_limitQuery(query: query, limit: q.limit, offset: q.offset);
|
_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<models.Album> getAlbum(int sourceId, String id) {
|
Selectable<models.Album> getAlbum(int sourceId, String id) {
|
||||||
|
|||||||
@ -27,6 +27,9 @@ enum SongsColumn {
|
|||||||
starred,
|
starred,
|
||||||
disc,
|
disc,
|
||||||
track,
|
track,
|
||||||
|
album,
|
||||||
|
artist,
|
||||||
|
albumArtist,
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user