mirror of
https://github.com/austinried/subtracks.git
synced 2026-02-09 22:42:44 +01:00
context menu base and move query to state
This commit is contained in:
@@ -1,19 +1,18 @@
|
|||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
import '../../database/query.dart';
|
|
||||||
import '../../l10n/generated/app_localizations.dart';
|
import '../../l10n/generated/app_localizations.dart';
|
||||||
|
import '../state/lists.dart';
|
||||||
import '../state/services.dart';
|
import '../state/services.dart';
|
||||||
import '../state/source.dart';
|
|
||||||
import '../ui/lists/albums_grid.dart';
|
import '../ui/lists/albums_grid.dart';
|
||||||
import '../ui/lists/artists_list.dart';
|
import '../ui/lists/artists_list.dart';
|
||||||
import '../ui/lists/items.dart';
|
import '../ui/lists/items.dart';
|
||||||
import '../ui/lists/playlists_list.dart';
|
import '../ui/lists/playlists_list.dart';
|
||||||
import '../ui/lists/songs_list.dart';
|
import '../ui/lists/songs_list.dart';
|
||||||
|
import '../ui/menus.dart';
|
||||||
import '../util/custom_scroll_fix.dart';
|
import '../util/custom_scroll_fix.dart';
|
||||||
|
|
||||||
const kIconSize = 26.0;
|
const kIconSize = 26.0;
|
||||||
@@ -75,25 +74,7 @@ class LibraryTabBarView extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final sourceId = ref.watch(sourceIdProvider);
|
final songsQuery = ref.watch(songsQueryProvider);
|
||||||
|
|
||||||
final albumsQuery = AlbumsQuery(
|
|
||||||
sourceId: sourceId,
|
|
||||||
sort: IList([
|
|
||||||
SortingTerm.albumsDesc(AlbumsColumn.created),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
final songsQuery = SongsQuery(
|
|
||||||
sourceId: sourceId,
|
|
||||||
sort: IList([
|
|
||||||
SortingTerm.songsAsc(SongsColumn.albumArtist),
|
|
||||||
SortingTerm.songsAsc(SongsColumn.album),
|
|
||||||
SortingTerm.songsAsc(SongsColumn.disc),
|
|
||||||
SortingTerm.songsAsc(SongsColumn.track),
|
|
||||||
SortingTerm.songsAsc(SongsColumn.title),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
return TabBarView(
|
return TabBarView(
|
||||||
controller: tabController,
|
controller: tabController,
|
||||||
@@ -102,7 +83,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(
|
||||||
@@ -116,6 +97,13 @@ class LibraryTabBarView extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
// _ => SliverToBoxAdapter(child: Container()),
|
// _ => SliverToBoxAdapter(child: Container()),
|
||||||
},
|
},
|
||||||
|
menuBuilder: switch (tab) {
|
||||||
|
LibraryTab.albums => (_) => AlbumsGridFilters(),
|
||||||
|
// LibraryTab.artists => (_) => AlbumsGridFilters(),
|
||||||
|
// LibraryTab.playlists => (_) => AlbumsGridFilters(),
|
||||||
|
// LibraryTab.songs => (_) => AlbumsGridFilters(),
|
||||||
|
_ => null,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
@@ -240,10 +228,12 @@ class TabScrollView extends HookConsumerWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
required this.index,
|
required this.index,
|
||||||
required this.sliver,
|
required this.sliver,
|
||||||
|
this.menuBuilder,
|
||||||
});
|
});
|
||||||
|
|
||||||
final int index;
|
final int index;
|
||||||
final Widget sliver;
|
final Widget sliver;
|
||||||
|
final WidgetBuilder? menuBuilder;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -251,14 +241,24 @@ class TabScrollView extends HookConsumerWidget {
|
|||||||
|
|
||||||
final scrollProvider = CustomScrollProviderData.of(context);
|
final scrollProvider = CustomScrollProviderData.of(context);
|
||||||
|
|
||||||
return CustomScrollView(
|
final listBuilder = menuBuilder;
|
||||||
controller: scrollProvider.scrollControllers[index],
|
final floatingActionButton = listBuilder != null
|
||||||
slivers: <Widget>[
|
? FabFilter(
|
||||||
SliverOverlapInjector(
|
listBuilder: listBuilder,
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
)
|
||||||
),
|
: null;
|
||||||
sliver,
|
|
||||||
],
|
return Scaffold(
|
||||||
|
floatingActionButton: floatingActionButton,
|
||||||
|
body: CustomScrollView(
|
||||||
|
controller: scrollProvider.scrollControllers[index],
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverOverlapInjector(
|
||||||
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
|
),
|
||||||
|
sliver,
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import '../../database/database.dart';
|
|||||||
import '../../l10n/generated/app_localizations.dart';
|
import '../../l10n/generated/app_localizations.dart';
|
||||||
import '../../util/logger.dart';
|
import '../../util/logger.dart';
|
||||||
import '../state/database.dart';
|
import '../state/database.dart';
|
||||||
import '../util/padding.dart';
|
import '../ui/menus.dart';
|
||||||
|
|
||||||
class SettingsSourceScreen extends HookConsumerWidget {
|
class SettingsSourceScreen extends HookConsumerWidget {
|
||||||
const SettingsSourceScreen({
|
const SettingsSourceScreen({
|
||||||
|
|||||||
31
lib/app/state/lists.dart
Normal file
31
lib/app/state/lists.dart
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
|
import '../../database/query.dart';
|
||||||
|
import 'source.dart';
|
||||||
|
|
||||||
|
final albumsQueryProvider = Provider<AlbumsQuery>((ref) {
|
||||||
|
final sourceId = ref.watch(sourceIdProvider);
|
||||||
|
|
||||||
|
return AlbumsQuery(
|
||||||
|
sourceId: sourceId,
|
||||||
|
sort: IList([
|
||||||
|
SortingTerm.albumsDesc(AlbumsColumn.created),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
final songsQueryProvider = Provider<SongsQuery>((ref) {
|
||||||
|
final sourceId = ref.watch(sourceIdProvider);
|
||||||
|
|
||||||
|
return SongsQuery(
|
||||||
|
sourceId: sourceId,
|
||||||
|
sort: IList([
|
||||||
|
SortingTerm.songsAsc(SongsColumn.albumArtist),
|
||||||
|
SortingTerm.songsAsc(SongsColumn.album),
|
||||||
|
SortingTerm.songsAsc(SongsColumn.disc),
|
||||||
|
SortingTerm.songsAsc(SongsColumn.track),
|
||||||
|
SortingTerm.songsAsc(SongsColumn.title),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -1,29 +1,28 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||||
|
|
||||||
import '../../../database/query.dart';
|
|
||||||
import '../../../sources/models.dart';
|
import '../../../sources/models.dart';
|
||||||
import '../../hooks/use_on_source.dart';
|
import '../../hooks/use_on_source.dart';
|
||||||
import '../../hooks/use_paging_controller.dart';
|
import '../../hooks/use_paging_controller.dart';
|
||||||
import '../../state/database.dart';
|
import '../../state/database.dart';
|
||||||
|
import '../../state/lists.dart';
|
||||||
import '../../state/source.dart';
|
import '../../state/source.dart';
|
||||||
|
import '../menus.dart';
|
||||||
import 'items.dart';
|
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) {
|
||||||
final db = ref.watch(databaseProvider);
|
final db = ref.watch(databaseProvider);
|
||||||
|
final query = ref.watch(albumsQueryProvider);
|
||||||
|
|
||||||
final controller = usePagingController<int, Album>(
|
final controller = usePagingController<int, Album>(
|
||||||
getNextPageKey: (state) =>
|
getNextPageKey: (state) =>
|
||||||
state.lastPageIsEmpty ? null : state.nextIntPageKey,
|
state.lastPageIsEmpty ? null : state.nextIntPageKey,
|
||||||
@@ -36,8 +35,8 @@ class AlbumsGrid extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
useOnSourceChange(ref, (_) => controller.refresh());
|
|
||||||
useOnSourceSync(ref, controller.refresh);
|
useOnSourceSync(ref, controller.refresh);
|
||||||
|
useValueChanged(query, (_, _) => controller.refresh());
|
||||||
|
|
||||||
return PagingListener(
|
return PagingListener(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
@@ -50,7 +49,9 @@ class AlbumsGrid extends HookConsumerWidget {
|
|||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: 3,
|
crossAxisCount: 3,
|
||||||
),
|
),
|
||||||
|
showNoMoreItemsIndicatorAsGridChild: false,
|
||||||
builderDelegate: PagedChildBuilderDelegate<Album>(
|
builderDelegate: PagedChildBuilderDelegate<Album>(
|
||||||
|
noMoreItemsIndicatorBuilder: (context) => FabPadding(),
|
||||||
itemBuilder: (context, item, index) => AlbumGridTile(
|
itemBuilder: (context, item, index) => AlbumGridTile(
|
||||||
album: item,
|
album: item,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
@@ -64,3 +65,14 @@ class AlbumsGrid extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AlbumsGridFilters extends HookConsumerWidget {
|
||||||
|
const AlbumsGridFilters({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return ListView(
|
||||||
|
children: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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';
|
import '../../state/source.dart';
|
||||||
|
import '../menus.dart';
|
||||||
import 'items.dart';
|
import 'items.dart';
|
||||||
|
|
||||||
const kPageSize = 30;
|
const kPageSize = 30;
|
||||||
@@ -46,6 +47,7 @@ class AlbumsList extends HookConsumerWidget {
|
|||||||
state: state,
|
state: state,
|
||||||
fetchNextPage: fetchNextPage,
|
fetchNextPage: fetchNextPage,
|
||||||
builderDelegate: PagedChildBuilderDelegate<Album>(
|
builderDelegate: PagedChildBuilderDelegate<Album>(
|
||||||
|
noMoreItemsIndicatorBuilder: (context) => FabPadding(),
|
||||||
itemBuilder: (context, item, index) {
|
itemBuilder: (context, item, index) {
|
||||||
final tile = AlbumListTile(
|
final tile = AlbumListTile(
|
||||||
album: item,
|
album: item,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ 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';
|
import '../../state/source.dart';
|
||||||
|
import '../menus.dart';
|
||||||
import 'items.dart';
|
import 'items.dart';
|
||||||
|
|
||||||
const kPageSize = 30;
|
const kPageSize = 30;
|
||||||
@@ -45,6 +46,7 @@ class ArtistsList extends HookConsumerWidget {
|
|||||||
state: state,
|
state: state,
|
||||||
fetchNextPage: fetchNextPage,
|
fetchNextPage: fetchNextPage,
|
||||||
builderDelegate: PagedChildBuilderDelegate<AristListItem>(
|
builderDelegate: PagedChildBuilderDelegate<AristListItem>(
|
||||||
|
noMoreItemsIndicatorBuilder: (context) => FabPadding(),
|
||||||
itemBuilder: (context, item, index) {
|
itemBuilder: (context, item, index) {
|
||||||
final (:artist, :albumCount) = item;
|
final (:artist, :albumCount) = item;
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ 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';
|
import '../../state/source.dart';
|
||||||
|
import '../menus.dart';
|
||||||
import 'items.dart';
|
import 'items.dart';
|
||||||
|
|
||||||
const kPageSize = 30;
|
const kPageSize = 30;
|
||||||
@@ -45,6 +46,7 @@ class PlaylistsList extends HookConsumerWidget {
|
|||||||
state: state,
|
state: state,
|
||||||
fetchNextPage: fetchNextPage,
|
fetchNextPage: fetchNextPage,
|
||||||
builderDelegate: PagedChildBuilderDelegate<Playlist>(
|
builderDelegate: PagedChildBuilderDelegate<Playlist>(
|
||||||
|
noMoreItemsIndicatorBuilder: (context) => FabPadding(),
|
||||||
itemBuilder: (context, item, index) {
|
itemBuilder: (context, item, index) {
|
||||||
return PlaylistListTile(
|
return PlaylistListTile(
|
||||||
playlist: item,
|
playlist: item,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
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 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||||
|
|
||||||
@@ -8,6 +9,7 @@ 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';
|
import '../../state/source.dart';
|
||||||
|
import '../menus.dart';
|
||||||
|
|
||||||
const kPageSize = 30;
|
const kPageSize = 30;
|
||||||
|
|
||||||
@@ -37,8 +39,8 @@ class SongsList extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
useOnSourceChange(ref, (_) => controller.refresh());
|
|
||||||
useOnSourceSync(ref, controller.refresh);
|
useOnSourceSync(ref, controller.refresh);
|
||||||
|
useValueChanged(query, (_, _) => controller.refresh());
|
||||||
|
|
||||||
return PagingListener(
|
return PagingListener(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
@@ -47,6 +49,7 @@ class SongsList extends HookConsumerWidget {
|
|||||||
state: state,
|
state: state,
|
||||||
fetchNextPage: fetchNextPage,
|
fetchNextPage: fetchNextPage,
|
||||||
builderDelegate: PagedChildBuilderDelegate<SongListItem>(
|
builderDelegate: PagedChildBuilderDelegate<SongListItem>(
|
||||||
|
noMoreItemsIndicatorBuilder: (context) => FabPadding(),
|
||||||
itemBuilder: itemBuilder,
|
itemBuilder: itemBuilder,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
76
lib/app/ui/menus.dart
Normal file
76
lib/app/ui/menus.dart
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
|
Future<void> showContextMenu({
|
||||||
|
required BuildContext context,
|
||||||
|
required WidgetBuilder listBuilder,
|
||||||
|
}) => showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) => DraggableScrollableSheet(
|
||||||
|
expand: false,
|
||||||
|
snap: true,
|
||||||
|
initialChildSize: 0.3,
|
||||||
|
minChildSize: 0.3,
|
||||||
|
maxChildSize: 0.4,
|
||||||
|
builder: (context, scrollController) => PrimaryScrollController(
|
||||||
|
controller: scrollController,
|
||||||
|
child: listBuilder(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
class ContextMenuList extends StatelessWidget {
|
||||||
|
const ContextMenuList({
|
||||||
|
super.key,
|
||||||
|
required this.children,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<Widget> children;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListView(
|
||||||
|
children: children,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FabFilter extends StatelessWidget {
|
||||||
|
const FabFilter({
|
||||||
|
super.key,
|
||||||
|
required this.listBuilder,
|
||||||
|
});
|
||||||
|
|
||||||
|
final WidgetBuilder listBuilder;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FloatingActionButton(
|
||||||
|
onPressed: () {
|
||||||
|
showContextMenu(
|
||||||
|
context: context,
|
||||||
|
listBuilder: listBuilder,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
Symbols.filter_list_rounded,
|
||||||
|
weight: 500,
|
||||||
|
opticalSize: 28,
|
||||||
|
size: 28,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FabPadding extends StatelessWidget {
|
||||||
|
const FabPadding({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const SizedBox(height: 86);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class FabPadding extends StatelessWidget {
|
|
||||||
const FabPadding({
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return const SizedBox(height: 86);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user