mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-29 17:39:27 +01:00
Compare commits
No commits in common. "f7874bcead403adbc32b7ca89b4d2e89f76d03bd" and "3fcb938f2b94e5a7e3556630f094f74abd5a5842" have entirely different histories.
f7874bcead
...
3fcb938f2b
@ -29,9 +29,8 @@ final router = GoRouter(
|
|||||||
AlbumScreen(id: state.pathParameters['id']!),
|
AlbumScreen(id: state.pathParameters['id']!),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'artists/:id',
|
path: 'artists',
|
||||||
builder: (context, state) =>
|
builder: (context, state) => ArtistScreen(),
|
||||||
ArtistScreen(id: state.pathParameters['id']!),
|
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'playlists/:id',
|
path: 'playlists/:id',
|
||||||
|
|||||||
@ -1,124 +1,12 @@
|
|||||||
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:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
|
|
||||||
import '../../database/query.dart';
|
class ArtistScreen extends StatelessWidget {
|
||||||
import '../../sources/models.dart';
|
const ArtistScreen({super.key});
|
||||||
import '../state/database.dart';
|
|
||||||
import '../state/source.dart';
|
|
||||||
import '../ui/cover_art_theme.dart';
|
|
||||||
import '../ui/gradient.dart';
|
|
||||||
import '../ui/images.dart';
|
|
||||||
import '../ui/lists/albums_list.dart';
|
|
||||||
|
|
||||||
class ArtistScreen extends HookConsumerWidget {
|
|
||||||
const ArtistScreen({
|
|
||||||
super.key,
|
|
||||||
required this.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String id;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final db = ref.watch(databaseProvider);
|
|
||||||
final sourceId = ref.watch(sourceIdProvider);
|
|
||||||
|
|
||||||
final getArtist = useMemoized(
|
|
||||||
() => db.libraryDao.getArtist(sourceId, id).getSingle(),
|
|
||||||
);
|
|
||||||
final artist = useFuture(getArtist).data;
|
|
||||||
|
|
||||||
if (artist == null) {
|
|
||||||
return Container();
|
|
||||||
}
|
|
||||||
|
|
||||||
final query = AlbumsQuery(
|
|
||||||
sourceId: sourceId,
|
|
||||||
filter: IList([AlbumsFilter.artistId(artist.id)]),
|
|
||||||
sort: IList([
|
|
||||||
SortingTerm.albumsDesc(AlbumsColumn.year),
|
|
||||||
SortingTerm.albumsAsc(AlbumsColumn.name),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
return CoverArtTheme(
|
|
||||||
coverArt: artist.coverArt,
|
|
||||||
child: Scaffold(
|
|
||||||
body: GradientScrollView(
|
|
||||||
slivers: [
|
|
||||||
ArtistHeader(artist: artist),
|
|
||||||
AlbumsList(query: query),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ArtistHeader extends StatelessWidget {
|
|
||||||
const ArtistHeader({
|
|
||||||
super.key,
|
|
||||||
required this.artist,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Artist artist;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final textTheme = TextTheme.of(context);
|
return Scaffold(
|
||||||
final colorScheme = ColorScheme.of(context);
|
body: Center(child: Text('Artist!')),
|
||||||
|
|
||||||
return SliverToBoxAdapter(
|
|
||||||
child: Stack(
|
|
||||||
fit: StackFit.passthrough,
|
|
||||||
alignment: AlignmentGeometry.bottomCenter,
|
|
||||||
children: [
|
|
||||||
CoverArtImage(
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
coverArt: artist.coverArt,
|
|
||||||
thumbnail: false,
|
|
||||||
height: 350,
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: double.infinity,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: AlignmentGeometry.centerRight,
|
|
||||||
end: AlignmentGeometry.centerLeft,
|
|
||||||
colors: [
|
|
||||||
colorScheme.surface.withAlpha(220),
|
|
||||||
colorScheme.surface.withAlpha(150),
|
|
||||||
colorScheme.surface.withAlpha(20),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsetsGeometry.symmetric(
|
|
||||||
vertical: 12,
|
|
||||||
horizontal: 16,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
artist.name,
|
|
||||||
textAlign: TextAlign.right,
|
|
||||||
style: textTheme.headlineLarge?.copyWith(
|
|
||||||
shadows: [
|
|
||||||
Shadow(
|
|
||||||
blurRadius: 20,
|
|
||||||
color: colorScheme.surface,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,7 @@ const kIconSize = 26.0;
|
|||||||
const kTabHeight = 36.0;
|
const kTabHeight = 36.0;
|
||||||
|
|
||||||
enum LibraryTab {
|
enum LibraryTab {
|
||||||
// home(Icon(Symbols.home_rounded)),
|
home(Icon(Symbols.home_rounded)),
|
||||||
albums(Icon(Symbols.album_rounded)),
|
albums(Icon(Symbols.album_rounded)),
|
||||||
artists(Icon(Symbols.person_rounded)),
|
artists(Icon(Symbols.person_rounded)),
|
||||||
songs(Icon(Symbols.music_note_rounded)),
|
songs(Icon(Symbols.music_note_rounded)),
|
||||||
@ -41,7 +41,7 @@ class LibraryScreen extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final tabController = useTabController(
|
final tabController = useTabController(
|
||||||
initialLength: LibraryTab.values.length,
|
initialLength: LibraryTab.values.length,
|
||||||
initialIndex: 0,
|
initialIndex: 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@ -77,13 +77,6 @@ class LibraryTabBarView extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final sourceId = ref.watch(sourceIdProvider);
|
final sourceId = ref.watch(sourceIdProvider);
|
||||||
|
|
||||||
final albumsQuery = AlbumsQuery(
|
|
||||||
sourceId: sourceId,
|
|
||||||
sort: IList([
|
|
||||||
SortingTerm.albumsDesc(AlbumsColumn.created),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
final songsQuery = SongsQuery(
|
final songsQuery = SongsQuery(
|
||||||
sourceId: sourceId,
|
sourceId: sourceId,
|
||||||
sort: IList([
|
sort: IList([
|
||||||
@ -102,7 +95,7 @@ class LibraryTabBarView extends HookConsumerWidget {
|
|||||||
(tab) => TabScrollView(
|
(tab) => TabScrollView(
|
||||||
index: LibraryTab.values.indexOf(tab),
|
index: LibraryTab.values.indexOf(tab),
|
||||||
sliver: switch (tab) {
|
sliver: switch (tab) {
|
||||||
LibraryTab.albums => AlbumsGrid(query: albumsQuery),
|
LibraryTab.albums => AlbumsGrid(),
|
||||||
LibraryTab.artists => ArtistsList(),
|
LibraryTab.artists => ArtistsList(),
|
||||||
LibraryTab.playlists => PlaylistsList(),
|
LibraryTab.playlists => PlaylistsList(),
|
||||||
LibraryTab.songs => SongsList(
|
LibraryTab.songs => SongsList(
|
||||||
@ -114,7 +107,7 @@ class LibraryTabBarView extends HookConsumerWidget {
|
|||||||
onTap: () {},
|
onTap: () {},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// _ => SliverToBoxAdapter(child: Container()),
|
_ => ArtistsList(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -216,7 +209,7 @@ class TabTitleText extends HookConsumerWidget {
|
|||||||
|
|
||||||
String tabLocalization(LibraryTab tab) => switch (tab) {
|
String tabLocalization(LibraryTab tab) => switch (tab) {
|
||||||
LibraryTab.albums => l.navigationTabsAlbums,
|
LibraryTab.albums => l.navigationTabsAlbums,
|
||||||
// LibraryTab.home => l.navigationTabsHome,
|
LibraryTab.home => l.navigationTabsHome,
|
||||||
LibraryTab.artists => l.navigationTabsArtists,
|
LibraryTab.artists => l.navigationTabsArtists,
|
||||||
LibraryTab.songs => l.navigationTabsSongs,
|
LibraryTab.songs => l.navigationTabsSongs,
|
||||||
LibraryTab.playlists => l.navigationTabsPlaylists,
|
LibraryTab.playlists => l.navigationTabsPlaylists,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import 'package:drift/drift.dart' show InsertMode, Value;
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
import '../../database/database.dart';
|
import '../../database/database.dart';
|
||||||
@ -7,24 +7,17 @@ final databaseInitializer = FutureProvider<SubtracksDatabase>((ref) async {
|
|||||||
final db = SubtracksDatabase();
|
final db = SubtracksDatabase();
|
||||||
|
|
||||||
await db
|
await db
|
||||||
.batch((batch) {
|
.into(db.sources)
|
||||||
batch.insertAll(
|
.insertOnConflictUpdate(
|
||||||
db.sources,
|
|
||||||
[
|
|
||||||
SourcesCompanion.insert(
|
SourcesCompanion.insert(
|
||||||
id: Value(1),
|
id: Value(1),
|
||||||
name: 'test subsonic',
|
name: 'test subsonic',
|
||||||
isActive: Value(true),
|
// isActive: Value(true),
|
||||||
),
|
),
|
||||||
SourcesCompanion.insert(
|
|
||||||
id: Value(2),
|
|
||||||
name: 'test navidrome',
|
|
||||||
isActive: Value(null),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
mode: InsertMode.insertOrIgnore,
|
|
||||||
);
|
);
|
||||||
batch.insertAllOnConflictUpdate(db.subsonicSettings, [
|
await db
|
||||||
|
.into(db.subsonicSettings)
|
||||||
|
.insertOnConflictUpdate(
|
||||||
SubsonicSettingsCompanion.insert(
|
SubsonicSettingsCompanion.insert(
|
||||||
sourceId: Value(1),
|
sourceId: Value(1),
|
||||||
address: Uri.parse('http://demo.subsonic.org'),
|
address: Uri.parse('http://demo.subsonic.org'),
|
||||||
@ -32,6 +25,19 @@ final databaseInitializer = FutureProvider<SubtracksDatabase>((ref) async {
|
|||||||
password: 'guest',
|
password: 'guest',
|
||||||
useTokenAuth: Value(true),
|
useTokenAuth: Value(true),
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
await db
|
||||||
|
.into(db.sources)
|
||||||
|
.insertOnConflictUpdate(
|
||||||
|
SourcesCompanion.insert(
|
||||||
|
id: Value(2),
|
||||||
|
name: 'test navidrome',
|
||||||
|
// isActive: Value(null),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await db
|
||||||
|
.into(db.subsonicSettings)
|
||||||
|
.insertOnConflictUpdate(
|
||||||
SubsonicSettingsCompanion.insert(
|
SubsonicSettingsCompanion.insert(
|
||||||
sourceId: Value(2),
|
sourceId: Value(2),
|
||||||
address: Uri.parse('http://10.0.2.2:4533'),
|
address: Uri.parse('http://10.0.2.2:4533'),
|
||||||
@ -39,11 +45,7 @@ final databaseInitializer = FutureProvider<SubtracksDatabase>((ref) async {
|
|||||||
password: 'password',
|
password: 'password',
|
||||||
useTokenAuth: Value(true),
|
useTokenAuth: Value(true),
|
||||||
),
|
),
|
||||||
]);
|
);
|
||||||
})
|
|
||||||
.onError((error, stack) {
|
|
||||||
print(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
return db;
|
return db;
|
||||||
});
|
});
|
||||||
|
|||||||
@ -10,14 +10,14 @@ class CoverArtImage extends HookConsumerWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
this.coverArt,
|
this.coverArt,
|
||||||
this.thumbnail = true,
|
this.thumbnail = true,
|
||||||
this.fit = BoxFit.cover,
|
this.fit,
|
||||||
this.height,
|
this.height,
|
||||||
this.width,
|
this.width,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String? coverArt;
|
final String? coverArt;
|
||||||
final bool thumbnail;
|
final bool thumbnail;
|
||||||
final BoxFit fit;
|
final BoxFit? fit;
|
||||||
final double? height;
|
final double? height;
|
||||||
final double? width;
|
final double? width;
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ class CoverArtImage extends HookConsumerWidget {
|
|||||||
cacheKey: '$sourceId$coverArt$thumbnail',
|
cacheKey: '$sourceId$coverArt$thumbnail',
|
||||||
placeholder: (context, url) => Icon(Symbols.cached_rounded),
|
placeholder: (context, url) => Icon(Symbols.cached_rounded),
|
||||||
errorWidget: (context, url, error) => Icon(Icons.error),
|
errorWidget: (context, url, error) => Icon(Icons.error),
|
||||||
fit: fit,
|
fit: BoxFit.cover,
|
||||||
fadeOutDuration: Duration(milliseconds: 100),
|
fadeOutDuration: Duration(milliseconds: 100),
|
||||||
fadeInDuration: Duration(milliseconds: 200),
|
fadeInDuration: Duration(milliseconds: 200),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
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';
|
||||||
@ -14,12 +15,7 @@ import 'items.dart';
|
|||||||
const kPageSize = 60;
|
const kPageSize = 60;
|
||||||
|
|
||||||
class AlbumsGrid extends HookConsumerWidget {
|
class AlbumsGrid extends HookConsumerWidget {
|
||||||
const AlbumsGrid({
|
const AlbumsGrid({super.key});
|
||||||
super.key,
|
|
||||||
required this.query,
|
|
||||||
});
|
|
||||||
|
|
||||||
final AlbumsQuery query;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@ -28,8 +24,11 @@ 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(
|
||||||
query.copyWith(
|
AlbumsQuery(
|
||||||
sourceId: ref.read(sourceIdProvider),
|
sourceId: ref.read(sourceIdProvider),
|
||||||
|
sort: IList([
|
||||||
|
SortingTerm.albumsDesc(AlbumsColumn.created),
|
||||||
|
]),
|
||||||
limit: kPageSize,
|
limit: kPageSize,
|
||||||
offset: (pageKey - 1) * kPageSize,
|
offset: (pageKey - 1) * kPageSize,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,90 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:go_router/go_router.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 '../../state/source.dart';
|
|
||||||
import 'items.dart';
|
|
||||||
|
|
||||||
const kPageSize = 30;
|
|
||||||
|
|
||||||
class AlbumsList extends HookConsumerWidget {
|
|
||||||
const AlbumsList({
|
|
||||||
super.key,
|
|
||||||
required this.query,
|
|
||||||
});
|
|
||||||
|
|
||||||
final AlbumsQuery query;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final db = ref.watch(databaseProvider);
|
|
||||||
final controller = usePagingController<int, Album>(
|
|
||||||
getNextPageKey: (state) =>
|
|
||||||
state.lastPageIsEmpty ? null : state.nextIntPageKey,
|
|
||||||
fetchPage: (pageKey) => db.libraryDao.listAlbums(
|
|
||||||
query.copyWith(
|
|
||||||
sourceId: ref.read(sourceIdProvider),
|
|
||||||
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<Album>(
|
|
||||||
itemBuilder: (context, item, index) {
|
|
||||||
final tile = AlbumListTile(
|
|
||||||
album: item,
|
|
||||||
onTap: () {
|
|
||||||
context.push('/albums/${item.id}');
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
final currentItemYear = item.year;
|
|
||||||
final previousItemYear = index == 0
|
|
||||||
? currentItemYear
|
|
||||||
: controller.items?.elementAtOrNull(index - 1)?.year;
|
|
||||||
|
|
||||||
if (index == 0 || currentItemYear != previousItemYear) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
top: 24,
|
|
||||||
bottom: 8,
|
|
||||||
left: 16,
|
|
||||||
right: 16,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
item.year?.toString() ?? 'Unknown year',
|
|
||||||
style: TextTheme.of(context).headlineMedium,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
tile,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tile;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -36,8 +36,6 @@ class SongsListHeader extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
Container(
|
Container(
|
||||||
height: 300,
|
|
||||||
width: 300,
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
@ -50,6 +48,7 @@ class SongsListHeader extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: CoverArtImage(
|
child: CoverArtImage(
|
||||||
|
height: 300,
|
||||||
thumbnail: false,
|
thumbnail: false,
|
||||||
coverArt: coverArt,
|
coverArt: coverArt,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
|
|||||||
@ -62,63 +62,6 @@ class ArtistListTile extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AlbumListTile extends StatelessWidget {
|
|
||||||
const AlbumListTile({
|
|
||||||
super.key,
|
|
||||||
required this.album,
|
|
||||||
this.onTap,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Album album;
|
|
||||||
final void Function()? onTap;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final textTheme = TextTheme.of(context);
|
|
||||||
|
|
||||||
return InkWell(
|
|
||||||
onTap: onTap,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 8, right: 18),
|
|
||||||
child: RoundedBoxClip(
|
|
||||||
child: CoverArtImage(
|
|
||||||
coverArt: album.coverArt,
|
|
||||||
thumbnail: true,
|
|
||||||
width: 80,
|
|
||||||
height: 80,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Flexible(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
album.name,
|
|
||||||
style: textTheme.bodyLarge?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 2,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
album.albumArtist ?? 'Unknown album artist',
|
|
||||||
style: textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PlaylistListTile extends StatelessWidget {
|
class PlaylistListTile extends StatelessWidget {
|
||||||
const PlaylistListTile({
|
const PlaylistListTile({
|
||||||
super.key,
|
super.key,
|
||||||
@ -134,12 +77,10 @@ class PlaylistListTile extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: RoundedBoxClip(
|
leading: CoverArtImage(
|
||||||
child: CoverArtImage(
|
|
||||||
coverArt: playlist.coverArt,
|
coverArt: playlist.coverArt,
|
||||||
thumbnail: true,
|
thumbnail: true,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
title: Text(playlist.name),
|
title: Text(playlist.name),
|
||||||
subtitle: Text(playlist.comment ?? ''),
|
subtitle: Text(playlist.comment ?? ''),
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
@ -165,11 +106,9 @@ class SongListTile extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: showLeading
|
leading: showLeading
|
||||||
? RoundedBoxClip(
|
? CoverArtImage(
|
||||||
child: CoverArtImage(
|
|
||||||
coverArt: coverArt,
|
coverArt: coverArt,
|
||||||
thumbnail: true,
|
thumbnail: true,
|
||||||
),
|
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
title: Text(song.title),
|
title: Text(song.title),
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import '../../../database/query.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 '../../state/source.dart';
|
|
||||||
|
|
||||||
const kPageSize = 30;
|
const kPageSize = 30;
|
||||||
|
|
||||||
@ -30,7 +29,6 @@ class SongsList extends HookConsumerWidget {
|
|||||||
state.lastPageIsEmpty ? null : state.nextIntPageKey,
|
state.lastPageIsEmpty ? null : state.nextIntPageKey,
|
||||||
fetchPage: (pageKey) => db.libraryDao.listSongs(
|
fetchPage: (pageKey) => db.libraryDao.listSongs(
|
||||||
query.copyWith(
|
query.copyWith(
|
||||||
sourceId: ref.read(sourceIdProvider),
|
|
||||||
limit: kPageSize,
|
limit: kPageSize,
|
||||||
offset: (pageKey - 1) * kPageSize,
|
offset: (pageKey - 1) * kPageSize,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -19,21 +19,3 @@ class CircleClip extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RoundedBoxClip extends StatelessWidget {
|
|
||||||
const RoundedBoxClip({
|
|
||||||
super.key,
|
|
||||||
required this.child,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Widget child;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ClipRRect(
|
|
||||||
clipBehavior: Clip.antiAlias,
|
|
||||||
borderRadius: BorderRadiusGeometry.circular(3),
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -225,12 +225,6 @@ class LibraryDao extends DatabaseAccessor<SubtracksDatabase>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Selectable<models.Artist> getArtist(int sourceId, String id) {
|
|
||||||
return db.managers.artists.filter(
|
|
||||||
(f) => f.sourceId.equals(sourceId) & f.id.equals(id),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Selectable<models.Playlist> getPlaylist(int sourceId, String id) {
|
Selectable<models.Playlist> getPlaylist(int sourceId, String id) {
|
||||||
return db.managers.playlists.filter(
|
return db.managers.playlists.filter(
|
||||||
(f) => f.sourceId.equals(sourceId) & f.id.equals(id),
|
(f) => f.sourceId.equals(sourceId) & f.id.equals(id),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user