songs list and serializable list query

This commit is contained in:
austinried 2025-12-05 21:16:48 +09:00
parent 6609671ae2
commit 16a79c81cb
14 changed files with 3107 additions and 148 deletions

View File

@ -1,14 +1,16 @@
import 'dart:async'; 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:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../../database/query.dart';
import '../../l10n/generated/app_localizations.dart'; import '../../l10n/generated/app_localizations.dart';
import '../state/database.dart'; import '../state/database.dart';
import '../state/source.dart'; import '../state/source.dart';
import '../ui/cover_art_theme.dart'; import '../ui/cover_art_theme.dart';
import '../ui/images.dart'; import '../ui/gradient.dart';
import '../ui/lists/header.dart';
import '../ui/lists/songs_list.dart';
class AlbumScreen extends HookConsumerWidget { class AlbumScreen extends HookConsumerWidget {
const AlbumScreen({ const AlbumScreen({
@ -34,111 +36,35 @@ class AlbumScreen extends HookConsumerWidget {
return Container(); return Container();
} }
final query = SongsQuery(
sourceId: sourceId,
filter: IList([SongsFilter.albumId(album.id)]),
sort: IList([
SongsSortingTerm(dir: SortDirection.asc, by: SongsColumn.disc),
SongsSortingTerm(dir: SortDirection.asc, by: SongsColumn.track),
SongsSortingTerm(dir: SortDirection.asc, by: SongsColumn.title),
]),
);
return CoverArtTheme( return CoverArtTheme(
coverArt: album.coverArt, coverArt: album.coverArt,
child: Scaffold( child: Scaffold(
body: Center( body: GradientScrollView(
child: CustomScrollView( slivers: [
slivers: [ SliverToBoxAdapter(
SliverToBoxAdapter( child: SongsListHeader(
child: SafeArea( title: album.name,
child: Padding( subtitle: album.albumArtist,
padding: EdgeInsetsGeometry.symmetric(horizontal: 16), coverArt: album.coverArt,
child: _Header( playText: l.resourcesAlbumActionsPlay,
title: album.name, onPlay: () {},
subtitle: album.albumArtist, onMore: () {},
coverArt: album.coverArt,
playText: l.resourcesAlbumActionsPlay,
onPlay: () {},
onMore: () {},
),
),
),
), ),
], ),
), SongsList(query: query),
],
), ),
), ),
); );
} }
} }
class _Header extends HookConsumerWidget {
const _Header({
required this.title,
this.subtitle,
this.coverArt,
this.playText,
this.onPlay,
this.onMore,
// required this.downloadActions,
});
final String title;
final String? subtitle;
final String? coverArt;
final String? playText;
final void Function()? onPlay;
final FutureOr<void> Function()? onMore;
// final List<DownloadAction> downloadActions;
@override
Widget build(BuildContext context, WidgetRef ref) {
// final inheritedStyle = DefaultTextStyle.of(context).style;
final theme = Theme.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 16),
CoverArtImage(
height: 300,
thumbnail: false,
coverArt: coverArt,
fit: BoxFit.contain,
),
const SizedBox(height: 20),
Column(
children: [
Text(
title,
style: theme.textTheme.headlineMedium,
textAlign: TextAlign.center,
),
Text(
subtitle ?? '',
style: theme.textTheme.headlineSmall,
textAlign: TextAlign.center,
),
],
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.download_done_rounded),
),
if (onPlay != null)
FilledButton.icon(
onPressed: onPlay,
icon: const Icon(Icons.play_arrow_rounded),
label: Text(
playText ?? '',
// style: theme.textTheme.bodyLarge?.copyWith(
// color: theme.colorScheme.onPrimary,
// ),
),
),
if (onMore != null)
IconButton(
onPressed: onMore,
icon: const Icon(Icons.more_horiz),
),
],
),
],
);
}
}

View File

@ -5,9 +5,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
import '../../l10n/generated/app_localizations.dart'; import '../../l10n/generated/app_localizations.dart';
import '../lists/albums_grid.dart';
import '../lists/artists_list.dart';
import '../state/services.dart'; import '../state/services.dart';
import '../ui/lists/albums_grid.dart';
import '../ui/lists/artists_list.dart';
import '../util/custom_scroll_fix.dart'; import '../util/custom_scroll_fix.dart';
const kIconSize = 26.0; const kIconSize = 26.0;

63
lib/app/ui/gradient.dart Normal file
View File

@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:sliver_tools/sliver_tools.dart';
class ThemedGradient extends LinearGradient {
const ThemedGradient({
required super.colors,
super.begin,
super.end,
});
factory ThemedGradient.of(
BuildContext context, {
AlignmentGeometry begin = Alignment.topCenter,
AlignmentGeometry end = Alignment.bottomCenter,
}) {
final colorScheme = Theme.of(context).colorScheme;
return ThemedGradient(
begin: begin,
end: end,
colors: [
colorScheme.primaryContainer,
colorScheme.surface,
],
);
}
}
class GradientScrollView extends HookConsumerWidget {
const GradientScrollView({
super.key,
required this.slivers,
});
final List<Widget> slivers;
@override
Widget build(BuildContext context, WidgetRef ref) {
return CustomScrollView(
slivers: [
SliverStack(
children: [
SliverPositioned.directional(
textDirection: TextDirection.ltr,
start: 0,
end: 0,
top: 0,
child: Ink(
width: double.infinity,
height: MediaQuery.heightOf(context),
decoration: BoxDecoration(
gradient: ThemedGradient.of(context),
),
),
),
MultiSliver(children: slivers),
],
),
],
);
}
}

View File

@ -1,14 +1,16 @@
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.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:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import '../../sources/models.dart'; import '../../../database/query.dart';
import '../hooks/use_on_source.dart'; import '../../../sources/models.dart';
import '../hooks/use_paging_controller.dart'; import '../../hooks/use_on_source.dart';
import '../state/database.dart'; import '../../hooks/use_paging_controller.dart';
import '../state/source.dart'; import '../../state/database.dart';
import 'list_items.dart'; import '../../state/source.dart';
import 'items.dart';
const kPageSize = 60; const kPageSize = 60;
@ -22,9 +24,17 @@ class AlbumsGrid extends HookConsumerWidget {
getNextPageKey: (state) => getNextPageKey: (state) =>
state.lastPageIsEmpty ? null : state.nextIntPageKey, state.lastPageIsEmpty ? null : state.nextIntPageKey,
fetchPage: (pageKey) => db.libraryDao.listAlbums( fetchPage: (pageKey) => db.libraryDao.listAlbums(
sourceId: ref.read(sourceIdProvider), AlbumsQuery(
limit: kPageSize, sourceId: ref.read(sourceIdProvider),
offset: (pageKey - 1) * kPageSize, sort: IList([
AlbumsSortingTerm(
dir: SortDirection.desc,
by: AlbumsColumn.created,
),
]),
limit: kPageSize,
offset: (pageKey - 1) * kPageSize,
),
), ),
); );

View File

@ -1,14 +1,16 @@
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.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:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import '../../database/dao/library_dao.dart'; import '../../../database/dao/library_dao.dart';
import '../hooks/use_on_source.dart'; import '../../../database/query.dart';
import '../hooks/use_paging_controller.dart'; import '../../hooks/use_on_source.dart';
import '../state/database.dart'; import '../../hooks/use_paging_controller.dart';
import '../state/source.dart'; import '../../state/database.dart';
import 'list_items.dart'; import '../../state/source.dart';
import 'items.dart';
const kPageSize = 30; const kPageSize = 30;
@ -22,9 +24,17 @@ class ArtistsList extends HookConsumerWidget {
getNextPageKey: (state) => getNextPageKey: (state) =>
state.lastPageIsEmpty ? null : state.nextIntPageKey, state.lastPageIsEmpty ? null : state.nextIntPageKey,
fetchPage: (pageKey) => db.libraryDao.listArtists( fetchPage: (pageKey) => db.libraryDao.listArtists(
sourceId: ref.read(sourceIdProvider), ArtistsQuery(
limit: kPageSize, sourceId: ref.read(sourceIdProvider),
offset: (pageKey - 1) * kPageSize, sort: IList([
ArtistsSortingTerm(
dir: SortDirection.asc,
by: ArtistsColumn.name,
),
]),
limit: kPageSize,
offset: (pageKey - 1) * kPageSize,
),
), ),
); );

View File

@ -0,0 +1,103 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../images.dart';
class SongsListHeader extends HookConsumerWidget {
const SongsListHeader({
super.key,
required this.title,
this.subtitle,
this.coverArt,
this.playText,
this.onPlay,
this.onMore,
// required this.downloadActions,
});
final String title;
final String? subtitle;
final String? coverArt;
final String? playText;
final void Function()? onPlay;
final FutureOr<void> Function()? onMore;
// final List<DownloadAction> downloadActions;
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = Theme.of(context);
return SafeArea(
minimum: EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 24),
Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
blurRadius: 20,
blurStyle: BlurStyle.normal,
color: Colors.black.withAlpha(100),
offset: Offset.zero,
spreadRadius: 2,
),
],
),
child: CoverArtImage(
height: 300,
thumbnail: false,
coverArt: coverArt,
fit: BoxFit.contain,
),
),
const SizedBox(height: 20),
Column(
children: [
Text(
title,
style: theme.textTheme.headlineMedium,
textAlign: TextAlign.center,
),
Text(
subtitle ?? '',
style: theme.textTheme.headlineSmall,
textAlign: TextAlign.center,
),
],
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
onPressed: () {},
icon: const Icon(Icons.download_done_rounded),
),
if (onPlay != null)
FilledButton.icon(
onPressed: onPlay,
icon: const Icon(Icons.play_arrow_rounded),
label: Text(
playText ?? '',
// style: theme.textTheme.bodyLarge?.copyWith(
// color: theme.colorScheme.onPrimary,
// ),
),
),
if (onMore != null)
IconButton(
onPressed: onMore,
icon: const Icon(Icons.more_horiz),
),
],
),
const SizedBox(height: 24),
],
),
);
}
}

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../../sources/models.dart'; import '../../../sources/models.dart';
import '../ui/images.dart'; import '../../util/clip.dart';
import '../util/clip.dart'; import '../images.dart';
class AlbumGridTile extends HookConsumerWidget { class AlbumGridTile extends HookConsumerWidget {
const AlbumGridTile({ const AlbumGridTile({
@ -62,6 +62,30 @@ class ArtistListTile extends StatelessWidget {
} }
} }
class SongListTile extends StatelessWidget {
const SongListTile({
super.key,
required this.song,
this.onTap,
});
final Song song;
final void Function()? onTap;
@override
Widget build(BuildContext context) {
return ListTile(
// leading: CoverArtImage(
// coverArt: song.coverArt,
// thumbnail: true,
// ),
title: Text(song.title),
subtitle: Text(song.artist ?? ''),
onTap: onTap,
);
}
}
class ImageCard extends StatelessWidget { class ImageCard extends StatelessWidget {
const ImageCard({ const ImageCard({
super.key, super.key,

View File

@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.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;
class SongsList extends HookConsumerWidget {
const SongsList({
super.key,
required this.query,
});
final SongsQuery query;
@override
Widget build(BuildContext context, WidgetRef ref) {
final db = ref.watch(databaseProvider);
final controller = usePagingController<int, Song>(
getNextPageKey: (state) =>
state.lastPageIsEmpty ? null : state.nextIntPageKey,
fetchPage: (pageKey) => db.libraryDao.listSongs(
query.copyWith(
limit: kPageSize,
offset: (pageKey - 1) * kPageSize,
),
),
);
useOnSourceChange(ref, (_) => controller.refresh());
useOnSourceSync(ref, controller.refresh);
return PagingListener(
controller: controller,
builder: (context, state, fetchNextPage) {
return PagedSliverList(
state: state,
fetchNextPage: fetchNextPage,
builderDelegate: PagedChildBuilderDelegate<Song>(
itemBuilder: (context, item, index) {
return SongListTile(
song: item,
onTap: () async {},
);
},
),
);
},
);
}
}

View File

@ -2,37 +2,78 @@ import 'package:drift/drift.dart';
import '../../sources/models.dart' as models; import '../../sources/models.dart' as models;
import '../database.dart'; import '../database.dart';
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});
extension on SortDirection {
OrderingMode toMode() => switch (this) {
SortDirection.asc => OrderingMode.asc,
SortDirection.desc => OrderingMode.desc,
};
}
@DriftAccessor(include: {'../tables.drift'}) @DriftAccessor(include: {'../tables.drift'})
class LibraryDao extends DatabaseAccessor<SubtracksDatabase> class LibraryDao extends DatabaseAccessor<SubtracksDatabase>
with _$LibraryDaoMixin { with _$LibraryDaoMixin {
LibraryDao(super.db); LibraryDao(super.db);
Future<List<models.Album>> listAlbums({ Future<List<models.Album>> listAlbums(AlbumsQuery q) {
required int sourceId,
required int limit,
required int offset,
}) {
final query = albums.select() final query = albums.select()
..where( ..where((albums) {
(f) => f.sourceId.equals(sourceId), var filter = albums.sourceId.equals(q.sourceId);
) for (final queryFilter in q.filter) {
..limit(limit, offset: offset); filter &= switch (queryFilter) {
AlbumsFilterArtistId(:final artistId) => albums.artistId.equals(
artistId,
),
AlbumsFilterYearEquals(:final year) => albums.year.equals(year),
_ => CustomExpression(''),
};
}
return filter;
})
..orderBy(
q.sort
.map(
(sort) =>
(albums) => OrderingTerm(
expression: switch (sort.by) {
AlbumsColumn.name => albums.name,
AlbumsColumn.created => albums.created,
AlbumsColumn.year => albums.year,
AlbumsColumn.starred => albums.starred,
},
mode: sort.dir.toMode(),
),
)
.toList(),
);
_limitQuery(query: query, limit: q.limit, offset: q.offset);
return query.get(); return query.get();
} }
Future<List<AristListItem>> listArtists({ Future<List<AristListItem>> listArtists(ArtistsQuery q) {
required int sourceId,
required int limit,
required int offset,
}) async {
final albumCount = albums.id.count(); final albumCount = albums.id.count();
var filter =
artists.sourceId.equals(q.sourceId) &
albums.sourceId.equals(q.sourceId);
for (final queryFilter in q.filter) {
filter &= switch (queryFilter) {
ArtistsFilterStarred(:final starred) =>
starred ? artists.starred.isNotNull() : artists.starred.isNull(),
ArtistsFilterNameSearch() => CustomExpression(''),
_ => CustomExpression(''),
};
}
final query = final query =
artists.select().join([ artists.select().join([
leftOuterJoin( leftOuterJoin(
@ -41,22 +82,78 @@ class LibraryDao extends DatabaseAccessor<SubtracksDatabase>
), ),
]) ])
..addColumns([albumCount]) ..addColumns([albumCount])
..where( ..where(filter)
artists.sourceId.equals(sourceId) &
albums.sourceId.equals(sourceId),
)
..groupBy([artists.sourceId, artists.id]) ..groupBy([artists.sourceId, artists.id])
..orderBy([OrderingTerm.asc(artists.name)]) ..orderBy(
..limit(limit, offset: offset); q.sort
.map(
(sort) => OrderingTerm(
expression: switch (sort.by) {
ArtistsColumn.name => artists.name,
ArtistsColumn.starred => artists.starred,
ArtistsColumn.albumCount => albumCount,
},
mode: sort.dir.toMode(),
),
)
.toList(),
);
return (await query.get()) _limitQuery(query: query, limit: q.limit, offset: q.offset);
return query
.map( .map(
(row) => ( (row) => (
artist: row.readTable(artists), artist: row.readTable(artists),
albumCount: row.read(albumCount) ?? 0, albumCount: row.read(albumCount) ?? 0,
), ),
) )
.toList(); .get();
}
Future<List<models.Song>> listSongs(SongsQuery q) {
var joinPlaylistSongs = false;
var filter = songs.sourceId.equals(q.sourceId);
for (final queryFilter in q.filter) {
switch (queryFilter) {
case SongsFilterAlbumId(:final albumId):
filter &= songs.albumId.equals(albumId);
case SongsFilterPlaylistId(:final playlistId):
joinPlaylistSongs = true;
filter &= playlistSongs.playlistId.equals(playlistId);
}
}
final query =
songs.select().join([
if (joinPlaylistSongs)
leftOuterJoin(
playlistSongs,
playlistSongs.sourceId.equals(q.sourceId) &
playlistSongs.songId.equalsExp(songs.id),
),
])
..where(filter)
..orderBy(
q.sort
.map(
(sort) => OrderingTerm(
expression: switch (sort.by) {
SongsColumn.title => songs.title,
SongsColumn.starred => songs.starred,
SongsColumn.disc => songs.disc,
SongsColumn.track => songs.track,
},
mode: sort.dir.toMode(),
),
)
.toList(),
);
_limitQuery(query: query, limit: q.limit, offset: q.offset);
return query.map((row) => (row.readTable(songs))).get();
} }
Selectable<models.Album> getAlbum(int sourceId, String id) { Selectable<models.Album> getAlbum(int sourceId, String id) {
@ -64,4 +161,14 @@ class LibraryDao extends DatabaseAccessor<SubtracksDatabase>
(f) => f.sourceId.equals(sourceId) & f.id.equals(id), (f) => f.sourceId.equals(sourceId) & f.id.equals(id),
); );
} }
void _limitQuery({
required LimitContainerMixin query,
required int? limit,
required int? offset,
}) {
if (limit != null) {
query.limit(limit, offset: offset);
}
}
} }

121
lib/database/query.dart Normal file
View File

@ -0,0 +1,121 @@
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'query.freezed.dart';
part 'query.g.dart';
enum SortDirection {
asc,
desc,
}
enum AlbumsColumn {
name,
created,
year,
starred,
}
enum ArtistsColumn {
name,
starred,
albumCount,
}
enum SongsColumn {
title,
starred,
disc,
track,
}
@freezed
abstract class SortingTerm with _$SortingTerm {
const factory SortingTerm.albums({
required SortDirection dir,
required AlbumsColumn by,
}) = AlbumsSortingTerm;
const factory SortingTerm.artists({
required SortDirection dir,
required ArtistsColumn by,
}) = ArtistsSortingTerm;
const factory SortingTerm.songs({
required SortDirection dir,
required SongsColumn by,
}) = SongsSortingTerm;
factory SortingTerm.fromJson(Map<String, Object?> json) =>
_$SortingTermFromJson(json);
}
@freezed
abstract class AlbumsQuery with _$AlbumsQuery {
const factory AlbumsQuery({
required int sourceId,
@Default(IListConst([])) IList<AlbumsFilter> filter,
required IList<AlbumsSortingTerm> sort,
int? limit,
int? offset,
}) = _AlbumsQuery;
factory AlbumsQuery.fromJson(Map<String, Object?> json) =>
_$AlbumsQueryFromJson(json);
}
@freezed
abstract class AlbumsFilter with _$AlbumsFilter {
const factory AlbumsFilter.artistId(String artistId) = AlbumsFilterArtistId;
const factory AlbumsFilter.yearEquals(int year) = AlbumsFilterYearEquals;
factory AlbumsFilter.fromJson(Map<String, Object?> json) =>
_$AlbumsFilterFromJson(json);
}
@freezed
abstract class ArtistsQuery with _$ArtistsQuery {
const factory ArtistsQuery({
required int sourceId,
@Default(IListConst([])) IList<ArtistsFilter> filter,
required IList<ArtistsSortingTerm> sort,
int? limit,
int? offset,
}) = _ArtistsQuery;
factory ArtistsQuery.fromJson(Map<String, Object?> json) =>
_$ArtistsQueryFromJson(json);
}
@freezed
abstract class ArtistsFilter with _$ArtistsFilter {
const factory ArtistsFilter.nameSearch(String name) = ArtistsFilterNameSearch;
const factory ArtistsFilter.starred(bool starred) = ArtistsFilterStarred;
factory ArtistsFilter.fromJson(Map<String, Object?> json) =>
_$ArtistsFilterFromJson(json);
}
@freezed
abstract class SongsQuery with _$SongsQuery {
const factory SongsQuery({
required int sourceId,
@Default(IListConst([])) IList<SongsFilter> filter,
required IList<SongsSortingTerm> sort,
int? limit,
int? offset,
}) = _SongsQuery;
factory SongsQuery.fromJson(Map<String, Object?> json) =>
_$SongsQueryFromJson(json);
}
@freezed
abstract class SongsFilter with _$SongsFilter {
const factory SongsFilter.albumId(String albumId) = SongsFilterAlbumId;
const factory SongsFilter.playlistId(String playlistId) =
SongsFilterPlaylistId;
factory SongsFilter.fromJson(Map<String, Object?> json) =>
_$SongsFilterFromJson(json);
}

File diff suppressed because it is too large Load Diff

226
lib/database/query.g.dart Normal file
View File

@ -0,0 +1,226 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'query.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
AlbumsSortingTerm _$AlbumsSortingTermFromJson(Map<String, dynamic> json) =>
AlbumsSortingTerm(
dir: $enumDecode(_$SortDirectionEnumMap, json['dir']),
by: $enumDecode(_$AlbumsColumnEnumMap, json['by']),
$type: json['runtimeType'] as String?,
);
Map<String, dynamic> _$AlbumsSortingTermToJson(AlbumsSortingTerm instance) =>
<String, dynamic>{
'dir': _$SortDirectionEnumMap[instance.dir]!,
'by': _$AlbumsColumnEnumMap[instance.by]!,
'runtimeType': instance.$type,
};
const _$SortDirectionEnumMap = {
SortDirection.asc: 'asc',
SortDirection.desc: 'desc',
};
const _$AlbumsColumnEnumMap = {
AlbumsColumn.name: 'name',
AlbumsColumn.created: 'created',
AlbumsColumn.year: 'year',
AlbumsColumn.starred: 'starred',
};
ArtistsSortingTerm _$ArtistsSortingTermFromJson(Map<String, dynamic> json) =>
ArtistsSortingTerm(
dir: $enumDecode(_$SortDirectionEnumMap, json['dir']),
by: $enumDecode(_$ArtistsColumnEnumMap, json['by']),
$type: json['runtimeType'] as String?,
);
Map<String, dynamic> _$ArtistsSortingTermToJson(ArtistsSortingTerm instance) =>
<String, dynamic>{
'dir': _$SortDirectionEnumMap[instance.dir]!,
'by': _$ArtistsColumnEnumMap[instance.by]!,
'runtimeType': instance.$type,
};
const _$ArtistsColumnEnumMap = {
ArtistsColumn.name: 'name',
ArtistsColumn.starred: 'starred',
ArtistsColumn.albumCount: 'albumCount',
};
SongsSortingTerm _$SongsSortingTermFromJson(Map<String, dynamic> json) =>
SongsSortingTerm(
dir: $enumDecode(_$SortDirectionEnumMap, json['dir']),
by: $enumDecode(_$SongsColumnEnumMap, json['by']),
$type: json['runtimeType'] as String?,
);
Map<String, dynamic> _$SongsSortingTermToJson(SongsSortingTerm instance) =>
<String, dynamic>{
'dir': _$SortDirectionEnumMap[instance.dir]!,
'by': _$SongsColumnEnumMap[instance.by]!,
'runtimeType': instance.$type,
};
const _$SongsColumnEnumMap = {
SongsColumn.title: 'title',
SongsColumn.starred: 'starred',
SongsColumn.disc: 'disc',
SongsColumn.track: 'track',
};
_AlbumsQuery _$AlbumsQueryFromJson(Map<String, dynamic> json) => _AlbumsQuery(
sourceId: (json['sourceId'] as num).toInt(),
filter: json['filter'] == null
? const IListConst([])
: IList<AlbumsFilter>.fromJson(
json['filter'],
(value) => AlbumsFilter.fromJson(value as Map<String, dynamic>),
),
sort: IList<AlbumsSortingTerm>.fromJson(
json['sort'],
(value) => AlbumsSortingTerm.fromJson(value as Map<String, dynamic>),
),
limit: (json['limit'] as num?)?.toInt(),
offset: (json['offset'] as num?)?.toInt(),
);
Map<String, dynamic> _$AlbumsQueryToJson(_AlbumsQuery instance) =>
<String, dynamic>{
'sourceId': instance.sourceId,
'filter': instance.filter.toJson((value) => value),
'sort': instance.sort.toJson((value) => value),
'limit': instance.limit,
'offset': instance.offset,
};
AlbumsFilterArtistId _$AlbumsFilterArtistIdFromJson(
Map<String, dynamic> json,
) => AlbumsFilterArtistId(
json['artistId'] as String,
$type: json['runtimeType'] as String?,
);
Map<String, dynamic> _$AlbumsFilterArtistIdToJson(
AlbumsFilterArtistId instance,
) => <String, dynamic>{
'artistId': instance.artistId,
'runtimeType': instance.$type,
};
AlbumsFilterYearEquals _$AlbumsFilterYearEqualsFromJson(
Map<String, dynamic> json,
) => AlbumsFilterYearEquals(
(json['year'] as num).toInt(),
$type: json['runtimeType'] as String?,
);
Map<String, dynamic> _$AlbumsFilterYearEqualsToJson(
AlbumsFilterYearEquals instance,
) => <String, dynamic>{'year': instance.year, 'runtimeType': instance.$type};
_ArtistsQuery _$ArtistsQueryFromJson(Map<String, dynamic> json) =>
_ArtistsQuery(
sourceId: (json['sourceId'] as num).toInt(),
filter: json['filter'] == null
? const IListConst([])
: IList<ArtistsFilter>.fromJson(
json['filter'],
(value) => ArtistsFilter.fromJson(value as Map<String, dynamic>),
),
sort: IList<ArtistsSortingTerm>.fromJson(
json['sort'],
(value) => ArtistsSortingTerm.fromJson(value as Map<String, dynamic>),
),
limit: (json['limit'] as num?)?.toInt(),
offset: (json['offset'] as num?)?.toInt(),
);
Map<String, dynamic> _$ArtistsQueryToJson(_ArtistsQuery instance) =>
<String, dynamic>{
'sourceId': instance.sourceId,
'filter': instance.filter.toJson((value) => value),
'sort': instance.sort.toJson((value) => value),
'limit': instance.limit,
'offset': instance.offset,
};
ArtistsFilterNameSearch _$ArtistsFilterNameSearchFromJson(
Map<String, dynamic> json,
) => ArtistsFilterNameSearch(
json['name'] as String,
$type: json['runtimeType'] as String?,
);
Map<String, dynamic> _$ArtistsFilterNameSearchToJson(
ArtistsFilterNameSearch instance,
) => <String, dynamic>{'name': instance.name, 'runtimeType': instance.$type};
ArtistsFilterStarred _$ArtistsFilterStarredFromJson(
Map<String, dynamic> json,
) => ArtistsFilterStarred(
json['starred'] as bool,
$type: json['runtimeType'] as String?,
);
Map<String, dynamic> _$ArtistsFilterStarredToJson(
ArtistsFilterStarred instance,
) => <String, dynamic>{
'starred': instance.starred,
'runtimeType': instance.$type,
};
_SongsQuery _$SongsQueryFromJson(Map<String, dynamic> json) => _SongsQuery(
sourceId: (json['sourceId'] as num).toInt(),
filter: json['filter'] == null
? const IListConst([])
: IList<SongsFilter>.fromJson(
json['filter'],
(value) => SongsFilter.fromJson(value as Map<String, dynamic>),
),
sort: IList<SongsSortingTerm>.fromJson(
json['sort'],
(value) => SongsSortingTerm.fromJson(value as Map<String, dynamic>),
),
limit: (json['limit'] as num?)?.toInt(),
offset: (json['offset'] as num?)?.toInt(),
);
Map<String, dynamic> _$SongsQueryToJson(_SongsQuery instance) =>
<String, dynamic>{
'sourceId': instance.sourceId,
'filter': instance.filter.toJson((value) => value),
'sort': instance.sort.toJson((value) => value),
'limit': instance.limit,
'offset': instance.offset,
};
SongsFilterAlbumId _$SongsFilterAlbumIdFromJson(Map<String, dynamic> json) =>
SongsFilterAlbumId(
json['albumId'] as String,
$type: json['runtimeType'] as String?,
);
Map<String, dynamic> _$SongsFilterAlbumIdToJson(SongsFilterAlbumId instance) =>
<String, dynamic>{
'albumId': instance.albumId,
'runtimeType': instance.$type,
};
SongsFilterPlaylistId _$SongsFilterPlaylistIdFromJson(
Map<String, dynamic> json,
) => SongsFilterPlaylistId(
json['playlistId'] as String,
$type: json['runtimeType'] as String?,
);
Map<String, dynamic> _$SongsFilterPlaylistIdToJson(
SongsFilterPlaylistId instance,
) => <String, dynamic>{
'playlistId': instance.playlistId,
'runtimeType': instance.$type,
};

View File

@ -843,7 +843,7 @@ packages:
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
sliver_tools: sliver_tools:
dependency: transitive dependency: "direct main"
description: description:
name: sliver_tools name: sliver_tools
sha256: eae28220badfb9d0559207badcbbc9ad5331aac829a88cb0964d330d2a4636a6 sha256: eae28220badfb9d0559207badcbbc9ad5331aac829a88cb0964d330d2a4636a6

View File

@ -33,6 +33,7 @@ dependencies:
path: ^1.9.1 path: ^1.9.1
path_provider: ^2.1.5 path_provider: ^2.1.5
pool: ^1.5.2 pool: ^1.5.2
sliver_tools: ^0.2.12
url_launcher: ^6.3.2 url_launcher: ^6.3.2
xml: ^6.6.1 xml: ^6.6.1