feature: Added option to save and set default filters for libraries (#107)

Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
PartyDonut 2024-11-02 15:44:24 +01:00 committed by GitHub
parent d3e34d57e0
commit 691293648b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 1353 additions and 62 deletions

View file

@ -18,6 +18,7 @@ import 'package:fladder/providers/settings/client_settings_provider.dart';
import 'package:fladder/providers/video_player_provider.dart';
import 'package:fladder/screens/collections/add_to_collection.dart';
import 'package:fladder/screens/library_search/widgets/library_filter_chips.dart';
import 'package:fladder/screens/library_search/widgets/library_saved_filters.dart';
import 'package:fladder/screens/library_search/widgets/library_sort_dialogue.dart';
import 'package:fladder/screens/library_search/widgets/library_views.dart';
import 'package:fladder/screens/library_search/widgets/suggestion_search_bar.dart';
@ -31,6 +32,7 @@ import 'package:fladder/util/fab_extended_anim.dart';
import 'package:fladder/util/item_base_model/item_base_model_extensions.dart';
import 'package:fladder/util/list_padding.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/map_bool_helper.dart';
import 'package:fladder/util/refresh_state.dart';
import 'package:fladder/util/router_extension.dart';
import 'package:fladder/util/sliver_list_padding.dart';
@ -105,14 +107,12 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
Future.microtask(
() async {
if (libraryProvider.mounted) {
libraryProvider.setDefaultOptions(widget.sortOrder, widget.sortingOptions);
}
await refreshKey.currentState?.show();
SystemChrome.setEnabledSystemUIMode(
SystemUiMode.edgeToEdge,
overlays: [],
);
if (context.mounted && widget.photoToView != null) {
libraryProvider.viewGallery(context, selected: widget.photoToView);
}
@ -133,7 +133,7 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
Widget build(BuildContext context) {
final isEmptySearchScreen = widget.viewModelId == null && widget.favourites == null && widget.folderId == null;
final librarySearchResults = ref.watch(providerKey);
final postersList = librarySearchResults.posters.hideEmptyChildren(librarySearchResults.hideEmtpyShows);
final postersList = librarySearchResults.posters.hideEmptyChildren(librarySearchResults.hideEmptyShows);
final playerState = ref.watch(mediaPlaybackProvider.select((value) => value.state));
final libraryViewType = ref.watch(libraryViewTypeProvider);
@ -230,7 +230,12 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
onRefresh: () async {
if (libraryProvider.mounted) {
return libraryProvider.initRefresh(
widget.folderId, widget.viewModelId, widget.favourites);
widget.folderId,
widget.viewModelId,
widget.favourites,
widget.sortOrder,
widget.sortingOptions,
);
}
},
refreshOnStart: false,
@ -282,6 +287,11 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
action: () => refreshKey.currentState?.show(),
icon: const Icon(IconsaxOutline.refresh),
);
final showSavedFiltersDialogue = ItemActionButton(
label: Text("Filters"),
action: () => showSavedFilters(context, librarySearchResults, libraryProvider),
icon: const Icon(IconsaxOutline.refresh),
);
final itemViewAction = ItemActionButton(
label: Text(context.localized.selectViewType),
icon: Icon(libraryViewType.icon),
@ -359,6 +369,8 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
itemCountWidget.toPopupMenuItem(useIcons: true),
refreshAction.toPopupMenuItem(useIcons: true),
itemViewAction.toPopupMenuItem(useIcons: true),
if (librarySearchResults.views.hasEnabled == true)
showSavedFiltersDialogue.toPopupMenuItem(useIcons: true),
if (itemActions.isNotEmpty) ItemActionDivider().toPopupMenuItem(),
...itemActions.popupMenuItems(useIcons: true),
],
@ -374,6 +386,8 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
itemCountWidget.toListItem(context, useIcons: true),
refreshAction.toListItem(context, useIcons: true),
itemViewAction.toListItem(context, useIcons: true),
if (librarySearchResults.views.hasEnabled == true)
showSavedFiltersDialogue.toPopupMenuItem(useIcons: true),
if (itemActions.isNotEmpty) ItemActionDivider().toListItem(context),
...itemActions.listTileItems(context, useIcons: true),
],

View file

@ -190,10 +190,10 @@ List<Widget> libraryFilterChips(
if (librarySearchResults.types[FladderItemType.series] == true)
FilterChip(
avatar: Icon(
librarySearchResults.hideEmtpyShows ? Icons.visibility_off_rounded : Icons.visibility_rounded,
librarySearchResults.hideEmptyShows ? Icons.visibility_off_rounded : Icons.visibility_rounded,
color: Theme.of(context).colorScheme.onSurface,
),
selected: librarySearchResults.hideEmtpyShows,
selected: librarySearchResults.hideEmptyShows,
showCheckmark: false,
label: Text(context.localized.hideEmpty),
onSelected: libraryProvider.setHideEmpty,

View file

@ -0,0 +1,171 @@
import 'package:flutter/material.dart';
import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/models/library_search/library_search_model.dart';
import 'package:fladder/providers/library_search_provider.dart';
import 'package:fladder/screens/shared/default_alert_dialog.dart';
import 'package:fladder/screens/shared/flat_button.dart';
import 'package:fladder/screens/shared/outlined_text_field.dart';
import 'package:fladder/util/list_padding.dart';
import 'package:fladder/util/localization_helper.dart';
Future<void> showSavedFilters(
BuildContext context,
LibrarySearchModel model,
LibrarySearchNotifier provider,
) {
return showDialog(
context: context,
builder: (context) => LibrarySavedFiltersDialogue(
searchModel: model,
provider: provider,
),
);
}
class LibrarySavedFiltersDialogue extends ConsumerWidget {
final LibrarySearchModel searchModel;
final LibrarySearchNotifier provider;
const LibrarySavedFiltersDialogue({
required this.searchModel,
required this.provider,
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final controller = TextEditingController();
final filters = ref.watch(provider.filterProvider);
final filterProvider = ref.watch(provider.filterProvider.notifier);
return Dialog(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
context.localized.filter(2),
style: Theme.of(context).textTheme.titleLarge,
),
if (filters.isNotEmpty) ...[
const Divider(),
Flexible(
child: ListView(
shrinkWrap: true,
children: [
...filters.map(
(filter) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 4),
child: Card(
child: FlatButton(
onTap: () => provider.loadModel(filter),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
child: Row(
children: [
Expanded(child: Text(filter.name)),
IconButton.filledTonal(
tooltip: context.localized.defaultFilterForLibrary,
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll(
filter.isFavourite ? Colors.yellowAccent.shade700.withOpacity(0.5) : null,
),
),
onPressed: () =>
filterProvider.saveFilter(filter.copyWith(isFavourite: !filter.isFavourite)),
icon: Icon(
color: filter.isFavourite ? Colors.yellowAccent : null,
filter.isFavourite ? IconsaxBold.star_1 : IconsaxOutline.star,
),
),
IconButton.filledTonal(
tooltip: context.localized.updateFilterForLibrary,
onPressed: () => provider.updateFilter(filter),
icon: Icon(IconsaxBold.refresh),
),
IconButton.filledTonal(
tooltip: context.localized.delete,
onPressed: () {
showDefaultAlertDialog(
context,
context.localized.removeFilterForLibrary(filter.name),
context.localized.deleteFilterConfirmation,
(context) {
filterProvider.removeFilter(filter);
Navigator.of(context).pop();
},
context.localized.delete,
(context) {
Navigator.of(context).pop();
},
context.localized.cancel,
);
},
style: ButtonStyle(
backgroundColor:
WidgetStatePropertyAll(Theme.of(context).colorScheme.errorContainer),
foregroundColor:
WidgetStatePropertyAll(Theme.of(context).colorScheme.onErrorContainer),
),
icon: Icon(IconsaxOutline.trash),
),
].addInBetween(const SizedBox(width: 8)),
),
),
),
),
);
},
),
],
),
),
const Divider(),
],
if (filters.length < 10)
Row(
children: [
Flexible(
child: OutlinedTextField(
controller: controller,
label: context.localized.name,
onSubmitted: (value) => provider.saveFiltersNew(value),
),
),
const SizedBox(width: 6),
FilledButton.tonal(
onPressed: () => provider.saveFiltersNew(controller.text),
child: Icon(IconsaxOutline.save_2),
),
],
)
else
Text(context.localized.libraryFiltersLimitReached),
ElevatedButton(
onPressed: () {
showDefaultAlertDialog(
context,
context.localized.libraryFiltersRemoveAll,
context.localized.libraryFiltersRemoveAllConfirm,
(context) {
filterProvider.deleteAllFilters();
Navigator.of(context).pop();
},
context.localized.delete,
(context) {
Navigator.of(context).pop();
},
context.localized.cancel,
);
},
child: Text(context.localized.libraryFiltersRemoveAll),
),
],
),
),
);
}
}