mirror of
https://github.com/austinried/subtracks.git
synced 2026-02-11 07:12:44 +01:00
display albums from db
This commit is contained in:
@@ -1,20 +1,35 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
|
||||
import '../../sources/models.dart';
|
||||
import '../hooks/use_paging_controller.dart';
|
||||
import '../state/database.dart';
|
||||
import '../state/settings.dart';
|
||||
import 'list_items.dart';
|
||||
|
||||
class AlbumsGrid extends HookWidget {
|
||||
const kPageSize = 30;
|
||||
|
||||
class AlbumsGrid extends HookConsumerWidget {
|
||||
const AlbumsGrid({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = usePagingController<int, String>(
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final db = ref.watch(databaseProvider);
|
||||
final sourceId = ref.watch(sourceIdProvider);
|
||||
|
||||
final controller = usePagingController<int, Album>(
|
||||
getNextPageKey: (state) =>
|
||||
state.lastPageIsEmpty ? null : state.nextIntPageKey,
|
||||
fetchPage: (pageKey) => List.generate(30, (_) => pageKey.toString()),
|
||||
fetchPage: (pageKey) async {
|
||||
final query = db.albums.select()
|
||||
..where((f) => f.sourceId.equals(sourceId))
|
||||
..limit(kPageSize, offset: (pageKey - 1) * kPageSize);
|
||||
|
||||
return await query.get();
|
||||
},
|
||||
);
|
||||
|
||||
return PagingListener(
|
||||
@@ -26,10 +41,11 @@ class AlbumsGrid extends HookWidget {
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
),
|
||||
builderDelegate: PagedChildBuilderDelegate<String>(
|
||||
builderDelegate: PagedChildBuilderDelegate<Album>(
|
||||
itemBuilder: (context, item, index) => AlbumGridTile(
|
||||
onTap: () {
|
||||
context.push('/album');
|
||||
album: item,
|
||||
onTap: () async {
|
||||
context.push('/album/${item.id}');
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:octo_image/octo_image.dart';
|
||||
|
||||
import '../../sources/models.dart';
|
||||
import '../state/source.dart';
|
||||
import '../util/clip.dart';
|
||||
|
||||
class AlbumGridTile extends StatelessWidget {
|
||||
class AlbumGridTile extends HookConsumerWidget {
|
||||
const AlbumGridTile({
|
||||
super.key,
|
||||
required this.album,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
final Album album;
|
||||
final void Function()? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return CardTheme(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: RoundedRectangleBorder(
|
||||
@@ -21,16 +29,50 @@ class AlbumGridTile extends StatelessWidget {
|
||||
margin: EdgeInsets.all(2),
|
||||
child: ImageCard(
|
||||
onTap: onTap,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: 'https://placehold.net/400x400.png',
|
||||
placeholder: (context, url) => CircularProgressIndicator(),
|
||||
errorWidget: (context, url, error) => Icon(Icons.error),
|
||||
),
|
||||
child: CoverArtImage(coverArt: album.coverArt),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CoverArtImage extends HookConsumerWidget {
|
||||
const CoverArtImage({
|
||||
super.key,
|
||||
this.coverArt,
|
||||
this.thumbnail = false,
|
||||
});
|
||||
|
||||
final String? coverArt;
|
||||
final bool thumbnail;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final source = ref.watch(sourceProvider);
|
||||
|
||||
CachedNetworkImageProvider buildImageProvider() =>
|
||||
CachedNetworkImageProvider(
|
||||
coverArt != null
|
||||
? source.coverArtUri(coverArt!, thumbnail: thumbnail).toString()
|
||||
: 'https://placehold.net/400x400.png',
|
||||
);
|
||||
|
||||
final imageProvider = useState(buildImageProvider());
|
||||
useEffect(() {
|
||||
imageProvider.value = buildImageProvider();
|
||||
return;
|
||||
}, [source, coverArt, thumbnail]);
|
||||
|
||||
return OctoImage(
|
||||
image: imageProvider.value,
|
||||
placeholderBuilder: (context) => Icon(Symbols.album_rounded),
|
||||
errorBuilder: (context, error, trace) => Icon(Icons.error),
|
||||
fit: BoxFit.cover,
|
||||
fadeOutDuration: Duration(milliseconds: 100),
|
||||
fadeInDuration: Duration(milliseconds: 200),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ArtistListTile extends StatelessWidget {
|
||||
const ArtistListTile({super.key});
|
||||
|
||||
@@ -71,7 +113,7 @@ class ImageCard extends StatelessWidget {
|
||||
child,
|
||||
Positioned.fill(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
onLongPress: onLongPress,
|
||||
|
||||
@@ -17,8 +17,9 @@ final router = GoRouter(
|
||||
builder: (context, state) => LibraryScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'album',
|
||||
builder: (context, state) => AlbumScreen(),
|
||||
path: 'album/:id',
|
||||
builder: (context, state) =>
|
||||
AlbumScreen(id: state.pathParameters['id']!),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'artist',
|
||||
|
||||
@@ -3,7 +3,12 @@ import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class AlbumScreen extends StatelessWidget {
|
||||
const AlbumScreen({super.key});
|
||||
const AlbumScreen({
|
||||
super.key,
|
||||
required this.id,
|
||||
});
|
||||
|
||||
final String id;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -12,7 +17,7 @@ class AlbumScreen extends StatelessWidget {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Album!'),
|
||||
Text('Album $id!'),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
context.push('/artist');
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
import '../lists/albums_grid.dart';
|
||||
import '../state/services.dart';
|
||||
import '../util/custom_scroll_fix.dart';
|
||||
|
||||
class LibraryScreen extends StatefulWidget {
|
||||
@@ -126,13 +128,18 @@ class _LibraryScreenState extends State<LibraryScreen>
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
context.push('/settings');
|
||||
},
|
||||
icon: Icon(
|
||||
Symbols.settings_rounded,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
SyncButton(),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
context.push('/settings');
|
||||
},
|
||||
icon: Icon(
|
||||
Symbols.settings_rounded,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -209,3 +216,19 @@ class _NewWidgetState extends State<NewWidget>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SyncButton extends HookConsumerWidget {
|
||||
const SyncButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final syncService = ref.watch(syncServiceProvider);
|
||||
|
||||
return IconButton(
|
||||
icon: Icon(Symbols.sync_rounded),
|
||||
onPressed: () {
|
||||
syncService.sync();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
7
lib/app/state/database.dart
Normal file
7
lib/app/state/database.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
import '../../database/database.dart';
|
||||
|
||||
final databaseProvider = Provider<SubtracksDatabase>((ref) {
|
||||
return SubtracksDatabase();
|
||||
});
|
||||
14
lib/app/state/services.dart
Normal file
14
lib/app/state/services.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
import '../../services/sync_services.dart';
|
||||
import 'database.dart';
|
||||
import 'settings.dart';
|
||||
import 'source.dart';
|
||||
|
||||
final syncServiceProvider = Provider<SyncService>((ref) {
|
||||
final db = ref.watch(databaseProvider);
|
||||
final source = ref.watch(sourceProvider);
|
||||
final sourceId = ref.watch(sourceIdProvider);
|
||||
|
||||
return SyncService(source: source, db: db, sourceId: sourceId);
|
||||
});
|
||||
5
lib/app/state/settings.dart
Normal file
5
lib/app/state/settings.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
final sourceIdProvider = Provider<int>((ref) {
|
||||
return 1;
|
||||
});
|
||||
39
lib/app/state/source.dart
Normal file
39
lib/app/state/source.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
import '../../sources/music_source.dart';
|
||||
import '../../sources/subsonic/client.dart';
|
||||
import '../../sources/subsonic/source.dart';
|
||||
import '../../util/http.dart';
|
||||
import 'database.dart';
|
||||
import 'settings.dart';
|
||||
|
||||
final _sourceProvider = FutureProvider<MusicSource>((ref) async {
|
||||
final db = ref.watch(databaseProvider);
|
||||
final sourceId = ref.watch(sourceIdProvider);
|
||||
|
||||
final query = db.sources.select().join([
|
||||
leftOuterJoin(
|
||||
db.subsonicSettings,
|
||||
db.subsonicSettings.sourceId.equalsExp(db.sources.id),
|
||||
),
|
||||
])..where(db.sources.id.equals(sourceId));
|
||||
|
||||
final result = await query.getSingle();
|
||||
final subsonicSettings = result.readTable(db.subsonicSettings);
|
||||
|
||||
return SubsonicSource(
|
||||
SubsonicClient(
|
||||
http: SubtracksHttpClient(),
|
||||
address: subsonicSettings.address,
|
||||
username: subsonicSettings.username,
|
||||
password: subsonicSettings.password,
|
||||
useTokenAuth: subsonicSettings.useTokenAuth,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
final sourceProvider = Provider<MusicSource>((ref) {
|
||||
final source = ref.watch(_sourceProvider);
|
||||
return source.requireValue;
|
||||
});
|
||||
Reference in New Issue
Block a user