mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-27 00:59:28 +01:00
songs list and serializable list query
This commit is contained in:
parent
6609671ae2
commit
16a79c81cb
@ -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),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -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
63
lib/app/ui/gradient.dart
Normal 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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -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,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
103
lib/app/ui/lists/header.dart
Normal file
103
lib/app/ui/lists/header.dart
Normal 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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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,
|
||||||
57
lib/app/ui/lists/songs_list.dart
Normal file
57
lib/app/ui/lists/songs_list.dart
Normal 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 {},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
121
lib/database/query.dart
Normal 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);
|
||||||
|
}
|
||||||
2311
lib/database/query.freezed.dart
Normal file
2311
lib/database/query.freezed.dart
Normal file
File diff suppressed because it is too large
Load Diff
226
lib/database/query.g.dart
Normal file
226
lib/database/query.g.dart
Normal 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,
|
||||||
|
};
|
||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user