From 691293648b26f2ce21ed978f8ae0bd065b50be11 Mon Sep 17 00:00:00 2001 From: PartyDonut <42371342+PartyDonut@users.noreply.github.com> Date: Sat, 2 Nov 2024 15:44:24 +0100 Subject: [PATCH] feature: Added option to save and set default filters for libraries (#107) Co-authored-by: PartyDonut --- .vscode/settings.json | 3 +- lib/l10n/app_en.arb | 17 +- lib/models/account_model.dart | 10 +- lib/models/account_model.freezed.dart | 34 +- lib/models/account_model.g.dart | 6 + lib/models/collection_types.dart | 2 +- lib/models/item_base_model.dart | 8 +- lib/models/items/item_shared_models.dart | 12 +- lib/models/library_filters_model.dart | 79 +++ lib/models/library_filters_model.freezed.dart | 573 ++++++++++++++++++ lib/models/library_filters_model.g.dart | 124 ++++ .../library_search/library_search_model.dart | 19 +- .../library_search_model.mapper.dart | 20 +- lib/providers/library_filters_provider.dart | 21 + lib/providers/library_filters_provider.g.dart | 174 ++++++ lib/providers/library_search_provider.dart | 78 ++- lib/providers/user_provider.dart | 31 +- lib/providers/user_provider.g.dart | 2 +- .../library_search/library_search_screen.dart | 24 +- .../widgets/library_filter_chips.dart | 4 +- .../widgets/library_saved_filters.dart | 171 ++++++ .../item_base_model_extensions.dart | 2 +- lib/util/map_bool_helper.dart | 1 + 23 files changed, 1353 insertions(+), 62 deletions(-) create mode 100644 lib/models/library_filters_model.dart create mode 100644 lib/models/library_filters_model.freezed.dart create mode 100644 lib/models/library_filters_model.g.dart create mode 100644 lib/providers/library_filters_provider.dart create mode 100644 lib/providers/library_filters_provider.g.dart create mode 100644 lib/screens/library_search/widgets/library_saved_filters.dart diff --git a/.vscode/settings.json b/.vscode/settings.json index 4e0401e..b99d4a1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "cSpell.words": [ - "Jellyfin" + "Jellyfin", + "jellyfin" ], "dart.flutterSdkPath": ".fvm/versions/3.24.3", "search.exclude": { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 52afbfa..17122de 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1084,5 +1084,20 @@ "speed": "Speed", "unableToPlayMedia": "There was an error finding a compatible media type", "errorOpeningMedia": "Something went trying to play this media", - "unableToPlayBooksOnWeb": "Books are not supported on web for now" + "unableToPlayBooksOnWeb": "Books are not supported on web for now", + "defaultFilterForLibrary": "Default filter for library", + "updateFilterForLibrary": "Update filter", + "removeFilterForLibrary": "Remove {filter}?", + "@removeFilterForLibrary": { + "description": "removeFilterForLibrary", + "placeholders": { + "filter":{ + "type": "String" + } + } + }, + "deleteFilterConfirmation": "Are you sure you want to delete this filter?", + "libraryFiltersLimitReached" : "Filter limit reached (10) remove some filters", + "libraryFiltersRemoveAll": "Remove all filters", + "libraryFiltersRemoveAllConfirm": "This will delete all saved filters for every library" } diff --git a/lib/models/account_model.dart b/lib/models/account_model.dart index 7bfacc9..80de4b7 100644 --- a/lib/models/account_model.dart +++ b/lib/models/account_model.dart @@ -1,16 +1,17 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first, invalid_annotation_target -import 'package:ficonsax/ficonsax.dart'; -import 'package:fladder/util/localization_helper.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:ficonsax/ficonsax.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart'; import 'package:fladder/models/credentials_model.dart'; +import 'package:fladder/models/library_filters_model.dart'; import 'package:fladder/util/adaptive_layout.dart'; - -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:fladder/util/localization_helper.dart'; part 'account_model.freezed.dart'; part 'account_model.g.dart'; @@ -30,6 +31,7 @@ class AccountModel with _$AccountModel { @Default([]) List latestItemsExcludes, @Default([]) List searchQueryHistory, @Default(false) bool quickConnectState, + @Default([]) List savedFilters, @JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy, @JsonKey(includeFromJson: false, includeToJson: false) ServerConfiguration? serverConfiguration, }) = _AccountModel; diff --git a/lib/models/account_model.freezed.dart b/lib/models/account_model.freezed.dart index 4ed2918..568621b 100644 --- a/lib/models/account_model.freezed.dart +++ b/lib/models/account_model.freezed.dart @@ -30,6 +30,8 @@ mixin _$AccountModel { List get latestItemsExcludes => throw _privateConstructorUsedError; List get searchQueryHistory => throw _privateConstructorUsedError; bool get quickConnectState => throw _privateConstructorUsedError; + List get savedFilters => + throw _privateConstructorUsedError; @JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? get policy => throw _privateConstructorUsedError; @JsonKey(includeFromJson: false, includeToJson: false) @@ -63,6 +65,7 @@ abstract class $AccountModelCopyWith<$Res> { List latestItemsExcludes, List searchQueryHistory, bool quickConnectState, + List savedFilters, @JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy, @JsonKey(includeFromJson: false, includeToJson: false) ServerConfiguration? serverConfiguration}); @@ -93,6 +96,7 @@ class _$AccountModelCopyWithImpl<$Res, $Val extends AccountModel> Object? latestItemsExcludes = null, Object? searchQueryHistory = null, Object? quickConnectState = null, + Object? savedFilters = null, Object? policy = freezed, Object? serverConfiguration = freezed, }) { @@ -137,6 +141,10 @@ class _$AccountModelCopyWithImpl<$Res, $Val extends AccountModel> ? _value.quickConnectState : quickConnectState // ignore: cast_nullable_to_non_nullable as bool, + savedFilters: null == savedFilters + ? _value.savedFilters + : savedFilters // ignore: cast_nullable_to_non_nullable + as List, policy: freezed == policy ? _value.policy : policy // ignore: cast_nullable_to_non_nullable @@ -168,6 +176,7 @@ abstract class _$$AccountModelImplCopyWith<$Res> List latestItemsExcludes, List searchQueryHistory, bool quickConnectState, + List savedFilters, @JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy, @JsonKey(includeFromJson: false, includeToJson: false) ServerConfiguration? serverConfiguration}); @@ -196,6 +205,7 @@ class __$$AccountModelImplCopyWithImpl<$Res> Object? latestItemsExcludes = null, Object? searchQueryHistory = null, Object? quickConnectState = null, + Object? savedFilters = null, Object? policy = freezed, Object? serverConfiguration = freezed, }) { @@ -240,6 +250,10 @@ class __$$AccountModelImplCopyWithImpl<$Res> ? _value.quickConnectState : quickConnectState // ignore: cast_nullable_to_non_nullable as bool, + savedFilters: null == savedFilters + ? _value._savedFilters + : savedFilters // ignore: cast_nullable_to_non_nullable + as List, policy: freezed == policy ? _value.policy : policy // ignore: cast_nullable_to_non_nullable @@ -266,11 +280,13 @@ class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin { final List latestItemsExcludes = const [], final List searchQueryHistory = const [], this.quickConnectState = false, + final List savedFilters = const [], @JsonKey(includeFromJson: false, includeToJson: false) this.policy, @JsonKey(includeFromJson: false, includeToJson: false) this.serverConfiguration}) : _latestItemsExcludes = latestItemsExcludes, _searchQueryHistory = searchQueryHistory, + _savedFilters = savedFilters, super._(); factory _$AccountModelImpl.fromJson(Map json) => @@ -315,6 +331,15 @@ class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin { @override @JsonKey() final bool quickConnectState; + final List _savedFilters; + @override + @JsonKey() + List get savedFilters { + if (_savedFilters is EqualUnmodifiableListView) return _savedFilters; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_savedFilters); + } + @override @JsonKey(includeFromJson: false, includeToJson: false) final UserPolicy? policy; @@ -324,7 +349,7 @@ class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin { @override String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { - return 'AccountModel(name: $name, id: $id, avatar: $avatar, lastUsed: $lastUsed, authMethod: $authMethod, localPin: $localPin, credentials: $credentials, latestItemsExcludes: $latestItemsExcludes, searchQueryHistory: $searchQueryHistory, quickConnectState: $quickConnectState, policy: $policy, serverConfiguration: $serverConfiguration)'; + return 'AccountModel(name: $name, id: $id, avatar: $avatar, lastUsed: $lastUsed, authMethod: $authMethod, localPin: $localPin, credentials: $credentials, latestItemsExcludes: $latestItemsExcludes, searchQueryHistory: $searchQueryHistory, quickConnectState: $quickConnectState, savedFilters: $savedFilters, policy: $policy, serverConfiguration: $serverConfiguration)'; } @override @@ -342,6 +367,7 @@ class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin { ..add(DiagnosticsProperty('latestItemsExcludes', latestItemsExcludes)) ..add(DiagnosticsProperty('searchQueryHistory', searchQueryHistory)) ..add(DiagnosticsProperty('quickConnectState', quickConnectState)) + ..add(DiagnosticsProperty('savedFilters', savedFilters)) ..add(DiagnosticsProperty('policy', policy)) ..add(DiagnosticsProperty('serverConfiguration', serverConfiguration)); } @@ -368,6 +394,8 @@ class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin { .equals(other._searchQueryHistory, _searchQueryHistory) && (identical(other.quickConnectState, quickConnectState) || other.quickConnectState == quickConnectState) && + const DeepCollectionEquality() + .equals(other._savedFilters, _savedFilters) && (identical(other.policy, policy) || other.policy == policy) && (identical(other.serverConfiguration, serverConfiguration) || other.serverConfiguration == serverConfiguration)); @@ -387,6 +415,7 @@ class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin { const DeepCollectionEquality().hash(_latestItemsExcludes), const DeepCollectionEquality().hash(_searchQueryHistory), quickConnectState, + const DeepCollectionEquality().hash(_savedFilters), policy, serverConfiguration); @@ -418,6 +447,7 @@ abstract class _AccountModel extends AccountModel { final List latestItemsExcludes, final List searchQueryHistory, final bool quickConnectState, + final List savedFilters, @JsonKey(includeFromJson: false, includeToJson: false) final UserPolicy? policy, @JsonKey(includeFromJson: false, includeToJson: false) @@ -448,6 +478,8 @@ abstract class _AccountModel extends AccountModel { @override bool get quickConnectState; @override + List get savedFilters; + @override @JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? get policy; @override diff --git a/lib/models/account_model.g.dart b/lib/models/account_model.g.dart index a5e0052..ac7c8f9 100644 --- a/lib/models/account_model.g.dart +++ b/lib/models/account_model.g.dart @@ -26,6 +26,11 @@ _$AccountModelImpl _$$AccountModelImplFromJson(Map json) => .toList() ?? const [], quickConnectState: json['quickConnectState'] as bool? ?? false, + savedFilters: (json['savedFilters'] as List?) + ?.map((e) => + LibraryFiltersModel.fromJson(e as Map)) + .toList() ?? + const [], ); Map _$$AccountModelImplToJson(_$AccountModelImpl instance) => @@ -40,6 +45,7 @@ Map _$$AccountModelImplToJson(_$AccountModelImpl instance) => 'latestItemsExcludes': instance.latestItemsExcludes, 'searchQueryHistory': instance.searchQueryHistory, 'quickConnectState': instance.quickConnectState, + 'savedFilters': instance.savedFilters, }; const _$AuthenticationEnumMap = { diff --git a/lib/models/collection_types.dart b/lib/models/collection_types.dart index 7769c22..1db28dc 100644 --- a/lib/models/collection_types.dart +++ b/lib/models/collection_types.dart @@ -20,7 +20,7 @@ extension CollectionTypeExtension on CollectionType { case CollectionType.tvshows: return {FladderItemType.series}; case CollectionType.homevideos: - return {FladderItemType.photoalbum, FladderItemType.folder, FladderItemType.photo, FladderItemType.video}; + return {FladderItemType.photoAlbum, FladderItemType.folder, FladderItemType.photo, FladderItemType.video}; case CollectionType.boxsets: case CollectionType.folders: case CollectionType.books: diff --git a/lib/models/item_base_model.dart b/lib/models/item_base_model.dart index 40665c9..a900ac0 100644 --- a/lib/models/item_base_model.dart +++ b/lib/models/item_base_model.dart @@ -210,7 +210,7 @@ class ItemBaseModel with ItemBaseModelMappable { MovieModel _ => FladderItemType.movie, SeriesModel _ => FladderItemType.series, SeasonModel _ => FladderItemType.season, - PhotoAlbumModel _ => FladderItemType.photoalbum, + PhotoAlbumModel _ => FladderItemType.photoAlbum, PhotoModel model => model.internalType, EpisodeModel _ => FladderItemType.episode, BookModel _ => FladderItemType.book, @@ -281,7 +281,7 @@ enum FladderItemType { icon: IconsaxOutline.user, selectedicon: IconsaxBold.user, ), - photoalbum( + photoAlbum( icon: IconsaxOutline.gallery, selectedicon: IconsaxBold.gallery, ), @@ -331,7 +331,7 @@ enum FladderItemType { FladderItemType.episode => context.localized.mediaTypeEpisode, FladderItemType.photo => context.localized.mediaTypePhoto, FladderItemType.person => context.localized.mediaTypePerson, - FladderItemType.photoalbum => context.localized.mediaTypePhotoAlbum, + FladderItemType.photoAlbum => context.localized.mediaTypePhotoAlbum, FladderItemType.folder => context.localized.mediaTypeFolder, FladderItemType.boxset => context.localized.mediaTypeBoxset, FladderItemType.playlist => context.localized.mediaTypePlaylist, @@ -352,7 +352,7 @@ enum FladderItemType { FladderItemType.episode => BaseItemKind.episode, FladderItemType.photo => BaseItemKind.photo, FladderItemType.person => BaseItemKind.person, - FladderItemType.photoalbum => BaseItemKind.photoalbum, + FladderItemType.photoAlbum => BaseItemKind.photoalbum, FladderItemType.folder => BaseItemKind.folder, FladderItemType.boxset => BaseItemKind.boxset, FladderItemType.playlist => BaseItemKind.playlist, diff --git a/lib/models/items/item_shared_models.dart b/lib/models/items/item_shared_models.dart index 0e21014..a304821 100644 --- a/lib/models/items/item_shared_models.dart +++ b/lib/models/items/item_shared_models.dart @@ -1,17 +1,17 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:convert'; -import 'package:collection/collection.dart'; -import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:collection/collection.dart'; +import 'package:dart_mappable/dart_mappable.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:json_annotation/json_annotation.dart'; + +import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart' as dto; import 'package:fladder/models/items/images_models.dart'; -import 'package:dart_mappable/dart_mappable.dart'; -import 'package:json_annotation/json_annotation.dart'; - part 'item_shared_models.mapper.dart'; @MappableClass() diff --git a/lib/models/library_filters_model.dart b/lib/models/library_filters_model.dart new file mode 100644 index 0000000..bc38004 --- /dev/null +++ b/lib/models/library_filters_model.dart @@ -0,0 +1,79 @@ +import 'dart:convert'; + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:xid/xid.dart'; + +import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart'; +import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart'; +import 'package:fladder/models/item_base_model.dart'; +import 'package:fladder/models/items/item_shared_models.dart'; +import 'package:fladder/models/library_search/library_search_model.dart'; +import 'package:fladder/models/library_search/library_search_options.dart'; +import 'package:fladder/util/map_bool_helper.dart'; + +part 'library_filters_model.freezed.dart'; +part 'library_filters_model.g.dart'; + +@freezed +class LibraryFiltersModel with _$LibraryFiltersModel { + const LibraryFiltersModel._(); + + factory LibraryFiltersModel._internal({ + required String id, + required String name, + @Default(false) isFavourite, + required List ids, + required Map genres, + required Map filters, + @StudioEncoder() required Map studios, + required Map tags, + required Map years, + required Map officialRatings, + required Map types, + required SortingOptions sortingOption, + required SortingOrder sortOrder, + required bool favourites, + required bool hideEmptyShows, + required bool recursive, + required GroupBy groupBy, + }) = _LibraryFiltersModel; + + factory LibraryFiltersModel.fromJson(Map json) => _$LibraryFiltersModelFromJson(json); + + factory LibraryFiltersModel.fromLibrarySearch(String name, LibrarySearchModel searchModel) { + return LibraryFiltersModel._internal( + id: Xid().toString(), + name: name, + ids: searchModel.views.included.map((e) => e.id).toList(), + genres: searchModel.genres, + filters: searchModel.filters, + studios: searchModel.studios, + tags: searchModel.tags, + years: searchModel.years, + officialRatings: searchModel.officialRatings, + types: searchModel.types, + sortingOption: searchModel.sortingOption, + sortOrder: searchModel.sortOrder, + favourites: searchModel.favourites, + hideEmptyShows: searchModel.hideEmptyShows, + recursive: searchModel.recursive, + groupBy: searchModel.groupBy, + ); + } + + bool containsSameIds(List otherIds) => ids.length == otherIds.length && Set.from(ids).containsAll(otherIds); +} + +class StudioEncoder implements JsonConverter, String> { + const StudioEncoder(); + + @override + Map fromJson(String json) { + final decodedMap = jsonDecode(json) as Map; + final studios = decodedMap.map((key, value) => MapEntry(Studio.fromJson(key), value as bool)); + return studios; + } + + @override + String toJson(Map studios) => jsonEncode(studios.map((key, value) => MapEntry(key.toJson(), value))); +} diff --git a/lib/models/library_filters_model.freezed.dart b/lib/models/library_filters_model.freezed.dart new file mode 100644 index 0000000..c50a98b --- /dev/null +++ b/lib/models/library_filters_model.freezed.dart @@ -0,0 +1,573 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'library_filters_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +LibraryFiltersModel _$LibraryFiltersModelFromJson(Map json) { + return _LibraryFiltersModel.fromJson(json); +} + +/// @nodoc +mixin _$LibraryFiltersModel { + String get id => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + dynamic get isFavourite => throw _privateConstructorUsedError; + List get ids => throw _privateConstructorUsedError; + Map get genres => throw _privateConstructorUsedError; + Map get filters => throw _privateConstructorUsedError; + @StudioEncoder() + Map get studios => throw _privateConstructorUsedError; + Map get tags => throw _privateConstructorUsedError; + Map get years => throw _privateConstructorUsedError; + Map get officialRatings => throw _privateConstructorUsedError; + Map get types => throw _privateConstructorUsedError; + SortingOptions get sortingOption => throw _privateConstructorUsedError; + SortingOrder get sortOrder => throw _privateConstructorUsedError; + bool get favourites => throw _privateConstructorUsedError; + bool get hideEmptyShows => throw _privateConstructorUsedError; + bool get recursive => throw _privateConstructorUsedError; + GroupBy get groupBy => throw _privateConstructorUsedError; + + /// Serializes this LibraryFiltersModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of LibraryFiltersModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $LibraryFiltersModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LibraryFiltersModelCopyWith<$Res> { + factory $LibraryFiltersModelCopyWith( + LibraryFiltersModel value, $Res Function(LibraryFiltersModel) then) = + _$LibraryFiltersModelCopyWithImpl<$Res, LibraryFiltersModel>; + @useResult + $Res call( + {String id, + String name, + dynamic isFavourite, + List ids, + Map genres, + Map filters, + @StudioEncoder() Map studios, + Map tags, + Map years, + Map officialRatings, + Map types, + SortingOptions sortingOption, + SortingOrder sortOrder, + bool favourites, + bool hideEmptyShows, + bool recursive, + GroupBy groupBy}); +} + +/// @nodoc +class _$LibraryFiltersModelCopyWithImpl<$Res, $Val extends LibraryFiltersModel> + implements $LibraryFiltersModelCopyWith<$Res> { + _$LibraryFiltersModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of LibraryFiltersModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? isFavourite = freezed, + Object? ids = null, + Object? genres = null, + Object? filters = null, + Object? studios = null, + Object? tags = null, + Object? years = null, + Object? officialRatings = null, + Object? types = null, + Object? sortingOption = null, + Object? sortOrder = null, + Object? favourites = null, + Object? hideEmptyShows = null, + Object? recursive = null, + Object? groupBy = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + isFavourite: freezed == isFavourite + ? _value.isFavourite + : isFavourite // ignore: cast_nullable_to_non_nullable + as dynamic, + ids: null == ids + ? _value.ids + : ids // ignore: cast_nullable_to_non_nullable + as List, + genres: null == genres + ? _value.genres + : genres // ignore: cast_nullable_to_non_nullable + as Map, + filters: null == filters + ? _value.filters + : filters // ignore: cast_nullable_to_non_nullable + as Map, + studios: null == studios + ? _value.studios + : studios // ignore: cast_nullable_to_non_nullable + as Map, + tags: null == tags + ? _value.tags + : tags // ignore: cast_nullable_to_non_nullable + as Map, + years: null == years + ? _value.years + : years // ignore: cast_nullable_to_non_nullable + as Map, + officialRatings: null == officialRatings + ? _value.officialRatings + : officialRatings // ignore: cast_nullable_to_non_nullable + as Map, + types: null == types + ? _value.types + : types // ignore: cast_nullable_to_non_nullable + as Map, + sortingOption: null == sortingOption + ? _value.sortingOption + : sortingOption // ignore: cast_nullable_to_non_nullable + as SortingOptions, + sortOrder: null == sortOrder + ? _value.sortOrder + : sortOrder // ignore: cast_nullable_to_non_nullable + as SortingOrder, + favourites: null == favourites + ? _value.favourites + : favourites // ignore: cast_nullable_to_non_nullable + as bool, + hideEmptyShows: null == hideEmptyShows + ? _value.hideEmptyShows + : hideEmptyShows // ignore: cast_nullable_to_non_nullable + as bool, + recursive: null == recursive + ? _value.recursive + : recursive // ignore: cast_nullable_to_non_nullable + as bool, + groupBy: null == groupBy + ? _value.groupBy + : groupBy // ignore: cast_nullable_to_non_nullable + as GroupBy, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$LibraryFiltersModelImplCopyWith<$Res> + implements $LibraryFiltersModelCopyWith<$Res> { + factory _$$LibraryFiltersModelImplCopyWith(_$LibraryFiltersModelImpl value, + $Res Function(_$LibraryFiltersModelImpl) then) = + __$$LibraryFiltersModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String id, + String name, + dynamic isFavourite, + List ids, + Map genres, + Map filters, + @StudioEncoder() Map studios, + Map tags, + Map years, + Map officialRatings, + Map types, + SortingOptions sortingOption, + SortingOrder sortOrder, + bool favourites, + bool hideEmptyShows, + bool recursive, + GroupBy groupBy}); +} + +/// @nodoc +class __$$LibraryFiltersModelImplCopyWithImpl<$Res> + extends _$LibraryFiltersModelCopyWithImpl<$Res, _$LibraryFiltersModelImpl> + implements _$$LibraryFiltersModelImplCopyWith<$Res> { + __$$LibraryFiltersModelImplCopyWithImpl(_$LibraryFiltersModelImpl _value, + $Res Function(_$LibraryFiltersModelImpl) _then) + : super(_value, _then); + + /// Create a copy of LibraryFiltersModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? isFavourite = freezed, + Object? ids = null, + Object? genres = null, + Object? filters = null, + Object? studios = null, + Object? tags = null, + Object? years = null, + Object? officialRatings = null, + Object? types = null, + Object? sortingOption = null, + Object? sortOrder = null, + Object? favourites = null, + Object? hideEmptyShows = null, + Object? recursive = null, + Object? groupBy = null, + }) { + return _then(_$LibraryFiltersModelImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + isFavourite: freezed == isFavourite ? _value.isFavourite! : isFavourite, + ids: null == ids + ? _value._ids + : ids // ignore: cast_nullable_to_non_nullable + as List, + genres: null == genres + ? _value._genres + : genres // ignore: cast_nullable_to_non_nullable + as Map, + filters: null == filters + ? _value._filters + : filters // ignore: cast_nullable_to_non_nullable + as Map, + studios: null == studios + ? _value._studios + : studios // ignore: cast_nullable_to_non_nullable + as Map, + tags: null == tags + ? _value._tags + : tags // ignore: cast_nullable_to_non_nullable + as Map, + years: null == years + ? _value._years + : years // ignore: cast_nullable_to_non_nullable + as Map, + officialRatings: null == officialRatings + ? _value._officialRatings + : officialRatings // ignore: cast_nullable_to_non_nullable + as Map, + types: null == types + ? _value._types + : types // ignore: cast_nullable_to_non_nullable + as Map, + sortingOption: null == sortingOption + ? _value.sortingOption + : sortingOption // ignore: cast_nullable_to_non_nullable + as SortingOptions, + sortOrder: null == sortOrder + ? _value.sortOrder + : sortOrder // ignore: cast_nullable_to_non_nullable + as SortingOrder, + favourites: null == favourites + ? _value.favourites + : favourites // ignore: cast_nullable_to_non_nullable + as bool, + hideEmptyShows: null == hideEmptyShows + ? _value.hideEmptyShows + : hideEmptyShows // ignore: cast_nullable_to_non_nullable + as bool, + recursive: null == recursive + ? _value.recursive + : recursive // ignore: cast_nullable_to_non_nullable + as bool, + groupBy: null == groupBy + ? _value.groupBy + : groupBy // ignore: cast_nullable_to_non_nullable + as GroupBy, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$LibraryFiltersModelImpl extends _LibraryFiltersModel { + _$LibraryFiltersModelImpl( + {required this.id, + required this.name, + this.isFavourite = false, + required final List ids, + required final Map genres, + required final Map filters, + @StudioEncoder() required final Map studios, + required final Map tags, + required final Map years, + required final Map officialRatings, + required final Map types, + required this.sortingOption, + required this.sortOrder, + required this.favourites, + required this.hideEmptyShows, + required this.recursive, + required this.groupBy}) + : _ids = ids, + _genres = genres, + _filters = filters, + _studios = studios, + _tags = tags, + _years = years, + _officialRatings = officialRatings, + _types = types, + super._(); + + factory _$LibraryFiltersModelImpl.fromJson(Map json) => + _$$LibraryFiltersModelImplFromJson(json); + + @override + final String id; + @override + final String name; + @override + @JsonKey() + final dynamic isFavourite; + final List _ids; + @override + List get ids { + if (_ids is EqualUnmodifiableListView) return _ids; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_ids); + } + + final Map _genres; + @override + Map get genres { + if (_genres is EqualUnmodifiableMapView) return _genres; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_genres); + } + + final Map _filters; + @override + Map get filters { + if (_filters is EqualUnmodifiableMapView) return _filters; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_filters); + } + + final Map _studios; + @override + @StudioEncoder() + Map get studios { + if (_studios is EqualUnmodifiableMapView) return _studios; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_studios); + } + + final Map _tags; + @override + Map get tags { + if (_tags is EqualUnmodifiableMapView) return _tags; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_tags); + } + + final Map _years; + @override + Map get years { + if (_years is EqualUnmodifiableMapView) return _years; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_years); + } + + final Map _officialRatings; + @override + Map get officialRatings { + if (_officialRatings is EqualUnmodifiableMapView) return _officialRatings; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_officialRatings); + } + + final Map _types; + @override + Map get types { + if (_types is EqualUnmodifiableMapView) return _types; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_types); + } + + @override + final SortingOptions sortingOption; + @override + final SortingOrder sortOrder; + @override + final bool favourites; + @override + final bool hideEmptyShows; + @override + final bool recursive; + @override + final GroupBy groupBy; + + @override + String toString() { + return 'LibraryFiltersModel._internal(id: $id, name: $name, isFavourite: $isFavourite, ids: $ids, genres: $genres, filters: $filters, studios: $studios, tags: $tags, years: $years, officialRatings: $officialRatings, types: $types, sortingOption: $sortingOption, sortOrder: $sortOrder, favourites: $favourites, hideEmptyShows: $hideEmptyShows, recursive: $recursive, groupBy: $groupBy)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LibraryFiltersModelImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + const DeepCollectionEquality() + .equals(other.isFavourite, isFavourite) && + const DeepCollectionEquality().equals(other._ids, _ids) && + const DeepCollectionEquality().equals(other._genres, _genres) && + const DeepCollectionEquality().equals(other._filters, _filters) && + const DeepCollectionEquality().equals(other._studios, _studios) && + const DeepCollectionEquality().equals(other._tags, _tags) && + const DeepCollectionEquality().equals(other._years, _years) && + const DeepCollectionEquality() + .equals(other._officialRatings, _officialRatings) && + const DeepCollectionEquality().equals(other._types, _types) && + (identical(other.sortingOption, sortingOption) || + other.sortingOption == sortingOption) && + (identical(other.sortOrder, sortOrder) || + other.sortOrder == sortOrder) && + (identical(other.favourites, favourites) || + other.favourites == favourites) && + (identical(other.hideEmptyShows, hideEmptyShows) || + other.hideEmptyShows == hideEmptyShows) && + (identical(other.recursive, recursive) || + other.recursive == recursive) && + (identical(other.groupBy, groupBy) || other.groupBy == groupBy)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + name, + const DeepCollectionEquality().hash(isFavourite), + const DeepCollectionEquality().hash(_ids), + const DeepCollectionEquality().hash(_genres), + const DeepCollectionEquality().hash(_filters), + const DeepCollectionEquality().hash(_studios), + const DeepCollectionEquality().hash(_tags), + const DeepCollectionEquality().hash(_years), + const DeepCollectionEquality().hash(_officialRatings), + const DeepCollectionEquality().hash(_types), + sortingOption, + sortOrder, + favourites, + hideEmptyShows, + recursive, + groupBy); + + /// Create a copy of LibraryFiltersModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LibraryFiltersModelImplCopyWith<_$LibraryFiltersModelImpl> get copyWith => + __$$LibraryFiltersModelImplCopyWithImpl<_$LibraryFiltersModelImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$LibraryFiltersModelImplToJson( + this, + ); + } +} + +abstract class _LibraryFiltersModel extends LibraryFiltersModel { + factory _LibraryFiltersModel( + {required final String id, + required final String name, + final dynamic isFavourite, + required final List ids, + required final Map genres, + required final Map filters, + @StudioEncoder() required final Map studios, + required final Map tags, + required final Map years, + required final Map officialRatings, + required final Map types, + required final SortingOptions sortingOption, + required final SortingOrder sortOrder, + required final bool favourites, + required final bool hideEmptyShows, + required final bool recursive, + required final GroupBy groupBy}) = _$LibraryFiltersModelImpl; + _LibraryFiltersModel._() : super._(); + + factory _LibraryFiltersModel.fromJson(Map json) = + _$LibraryFiltersModelImpl.fromJson; + + @override + String get id; + @override + String get name; + @override + dynamic get isFavourite; + @override + List get ids; + @override + Map get genres; + @override + Map get filters; + @override + @StudioEncoder() + Map get studios; + @override + Map get tags; + @override + Map get years; + @override + Map get officialRatings; + @override + Map get types; + @override + SortingOptions get sortingOption; + @override + SortingOrder get sortOrder; + @override + bool get favourites; + @override + bool get hideEmptyShows; + @override + bool get recursive; + @override + GroupBy get groupBy; + + /// Create a copy of LibraryFiltersModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LibraryFiltersModelImplCopyWith<_$LibraryFiltersModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/models/library_filters_model.g.dart b/lib/models/library_filters_model.g.dart new file mode 100644 index 0000000..f260b77 --- /dev/null +++ b/lib/models/library_filters_model.g.dart @@ -0,0 +1,124 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'library_filters_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$LibraryFiltersModelImpl _$$LibraryFiltersModelImplFromJson( + Map json) => + _$LibraryFiltersModelImpl( + id: json['id'] as String, + name: json['name'] as String, + isFavourite: json['isFavourite'] ?? false, + ids: (json['ids'] as List).map((e) => e as String).toList(), + genres: Map.from(json['genres'] as Map), + filters: (json['filters'] as Map).map( + (k, e) => MapEntry($enumDecode(_$ItemFilterEnumMap, k), e as bool), + ), + studios: const StudioEncoder().fromJson(json['studios'] as String), + tags: Map.from(json['tags'] as Map), + years: (json['years'] as Map).map( + (k, e) => MapEntry(int.parse(k), e as bool), + ), + officialRatings: Map.from(json['officialRatings'] as Map), + types: (json['types'] as Map).map( + (k, e) => MapEntry($enumDecode(_$FladderItemTypeEnumMap, k), e as bool), + ), + sortingOption: + $enumDecode(_$SortingOptionsEnumMap, json['sortingOption']), + sortOrder: $enumDecode(_$SortingOrderEnumMap, json['sortOrder']), + favourites: json['favourites'] as bool, + hideEmptyShows: json['hideEmptyShows'] as bool, + recursive: json['recursive'] as bool, + groupBy: $enumDecode(_$GroupByEnumMap, json['groupBy']), + ); + +Map _$$LibraryFiltersModelImplToJson( + _$LibraryFiltersModelImpl instance) => + { + 'id': instance.id, + 'name': instance.name, + 'isFavourite': instance.isFavourite, + 'ids': instance.ids, + 'genres': instance.genres, + 'filters': + instance.filters.map((k, e) => MapEntry(_$ItemFilterEnumMap[k], e)), + 'studios': const StudioEncoder().toJson(instance.studios), + 'tags': instance.tags, + 'years': instance.years.map((k, e) => MapEntry(k.toString(), e)), + 'officialRatings': instance.officialRatings, + 'types': instance.types + .map((k, e) => MapEntry(_$FladderItemTypeEnumMap[k]!, e)), + 'sortingOption': _$SortingOptionsEnumMap[instance.sortingOption]!, + 'sortOrder': _$SortingOrderEnumMap[instance.sortOrder]!, + 'favourites': instance.favourites, + 'hideEmptyShows': instance.hideEmptyShows, + 'recursive': instance.recursive, + 'groupBy': _$GroupByEnumMap[instance.groupBy]!, + }; + +const _$ItemFilterEnumMap = { + ItemFilter.swaggerGeneratedUnknown: null, + ItemFilter.isfolder: 'IsFolder', + ItemFilter.isnotfolder: 'IsNotFolder', + ItemFilter.isunplayed: 'IsUnplayed', + ItemFilter.isplayed: 'IsPlayed', + ItemFilter.isfavorite: 'IsFavorite', + ItemFilter.isresumable: 'IsResumable', + ItemFilter.likes: 'Likes', + ItemFilter.dislikes: 'Dislikes', + ItemFilter.isfavoriteorlikes: 'IsFavoriteOrLikes', +}; + +const _$FladderItemTypeEnumMap = { + FladderItemType.baseType: 'baseType', + FladderItemType.audio: 'audio', + FladderItemType.musicAlbum: 'musicAlbum', + FladderItemType.musicVideo: 'musicVideo', + FladderItemType.collectionFolder: 'collectionFolder', + FladderItemType.video: 'video', + FladderItemType.movie: 'movie', + FladderItemType.series: 'series', + FladderItemType.season: 'season', + FladderItemType.episode: 'episode', + FladderItemType.photo: 'photo', + FladderItemType.person: 'person', + FladderItemType.photoAlbum: 'photoAlbum', + FladderItemType.folder: 'folder', + FladderItemType.boxset: 'boxset', + FladderItemType.playlist: 'playlist', + FladderItemType.book: 'book', +}; + +const _$SortingOptionsEnumMap = { + SortingOptions.name: 'name', + SortingOptions.communityRating: 'communityRating', + SortingOptions.parentalRating: 'parentalRating', + SortingOptions.dateAdded: 'dateAdded', + SortingOptions.dateLastContentAdded: 'dateLastContentAdded', + SortingOptions.favorite: 'favorite', + SortingOptions.datePlayed: 'datePlayed', + SortingOptions.folders: 'folders', + SortingOptions.playCount: 'playCount', + SortingOptions.releaseDate: 'releaseDate', + SortingOptions.runTime: 'runTime', + SortingOptions.random: 'random', +}; + +const _$SortingOrderEnumMap = { + SortingOrder.ascending: 'ascending', + SortingOrder.descending: 'descending', +}; + +const _$GroupByEnumMap = { + GroupBy.none: 'none', + GroupBy.name: 'name', + GroupBy.genres: 'genres', + GroupBy.dateAdded: 'dateAdded', + GroupBy.tags: 'tags', + GroupBy.releaseDate: 'releaseDate', + GroupBy.rating: 'rating', + GroupBy.type: 'type', +}; diff --git a/lib/models/library_search/library_search_model.dart b/lib/models/library_search/library_search_model.dart index e1bcc73..adf7fbe 100644 --- a/lib/models/library_search/library_search_model.dart +++ b/lib/models/library_search/library_search_model.dart @@ -1,15 +1,16 @@ -import 'package:collection/collection.dart'; -import 'package:dart_mappable/dart_mappable.dart'; -import 'package:fladder/util/list_extensions.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:collection/collection.dart'; +import 'package:dart_mappable/dart_mappable.dart'; + import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart'; import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/items/item_shared_models.dart'; import 'package:fladder/models/library_search/library_search_options.dart'; import 'package:fladder/models/view_model.dart'; +import 'package:fladder/util/list_extensions.dart'; import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/map_bool_helper.dart'; @@ -34,7 +35,7 @@ class LibrarySearchModel with LibrarySearchModelMappable { final SortingOptions sortingOption; final SortingOrder sortOrder; final bool favourites; - final bool hideEmtpyShows; + final bool hideEmptyShows; final bool recursive; final GroupBy groupBy; final Map lastIndices; @@ -71,14 +72,14 @@ class LibrarySearchModel with LibrarySearchModelMappable { FladderItemType.musicVideo: false, FladderItemType.photo: false, FladderItemType.person: false, - FladderItemType.photoalbum: false, + FladderItemType.photoAlbum: false, FladderItemType.series: true, FladderItemType.video: true, }, this.favourites = false, this.sortingOption = SortingOptions.name, this.sortOrder = SortingOrder.ascending, - this.hideEmtpyShows = true, + this.hideEmptyShows = true, this.recursive = false, this.groupBy = GroupBy.none, this.lastIndices = const {}, @@ -92,7 +93,7 @@ class LibrarySearchModel with LibrarySearchModelMappable { tags.hasEnabled || years.hasEnabled || officialRatings.hasEnabled || - hideEmtpyShows || + hideEmptyShows || filters.hasEnabled || favourites || searchQuery.isNotEmpty; @@ -146,7 +147,7 @@ class LibrarySearchModel with LibrarySearchModelMappable { if (totalItemCount == 0) return false; return types.included.isEmpty || types.included.containsAny( - {...FladderItemType.galleryItem, FladderItemType.photoalbum, FladderItemType.folder}, + {...FladderItemType.galleryItem, FladderItemType.photoAlbum, FladderItemType.folder}, ); } @@ -177,7 +178,7 @@ class LibrarySearchModel with LibrarySearchModelMappable { favourites: false, recursive: false, studios: const {}, - hideEmtpyShows: true, + hideEmptyShows: true, ); } diff --git a/lib/models/library_search/library_search_model.mapper.dart b/lib/models/library_search/library_search_model.mapper.dart index 2ffe0c8..601c4df 100644 --- a/lib/models/library_search/library_search_model.mapper.dart +++ b/lib/models/library_search/library_search_model.mapper.dart @@ -83,7 +83,7 @@ class LibrarySearchModelMapper extends ClassMapperBase { FladderItemType.musicVideo: false, FladderItemType.photo: false, FladderItemType.person: false, - FladderItemType.photoalbum: false, + FladderItemType.photoAlbum: false, FladderItemType.series: true, FladderItemType.video: true }); @@ -98,9 +98,9 @@ class LibrarySearchModelMapper extends ClassMapperBase { static SortingOrder _$sortOrder(LibrarySearchModel v) => v.sortOrder; static const Field _f$sortOrder = Field('sortOrder', _$sortOrder, opt: true, def: SortingOrder.ascending); - static bool _$hideEmtpyShows(LibrarySearchModel v) => v.hideEmtpyShows; - static const Field _f$hideEmtpyShows = - Field('hideEmtpyShows', _$hideEmtpyShows, opt: true, def: true); + static bool _$hideEmptyShows(LibrarySearchModel v) => v.hideEmptyShows; + static const Field _f$hideEmptyShows = + Field('hideEmptyShows', _$hideEmptyShows, opt: true, def: true); static bool _$recursive(LibrarySearchModel v) => v.recursive; static const Field _f$recursive = Field('recursive', _$recursive, opt: true, def: false); @@ -138,7 +138,7 @@ class LibrarySearchModelMapper extends ClassMapperBase { #favourites: _f$favourites, #sortingOption: _f$sortingOption, #sortOrder: _f$sortOrder, - #hideEmtpyShows: _f$hideEmtpyShows, + #hideEmptyShows: _f$hideEmptyShows, #recursive: _f$recursive, #groupBy: _f$groupBy, #lastIndices: _f$lastIndices, @@ -167,7 +167,7 @@ class LibrarySearchModelMapper extends ClassMapperBase { favourites: data.dec(_f$favourites), sortingOption: data.dec(_f$sortingOption), sortOrder: data.dec(_f$sortOrder), - hideEmtpyShows: data.dec(_f$hideEmtpyShows), + hideEmptyShows: data.dec(_f$hideEmptyShows), recursive: data.dec(_f$recursive), groupBy: data.dec(_f$groupBy), lastIndices: data.dec(_f$lastIndices), @@ -257,7 +257,7 @@ abstract class LibrarySearchModelCopyWith<$R, $In extends LibrarySearchModel, bool? favourites, SortingOptions? sortingOption, SortingOrder? sortOrder, - bool? hideEmtpyShows, + bool? hideEmptyShows, bool? recursive, GroupBy? groupBy, Map? lastIndices, @@ -353,7 +353,7 @@ class _LibrarySearchModelCopyWithImpl<$R, $Out> bool? favourites, SortingOptions? sortingOption, SortingOrder? sortOrder, - bool? hideEmtpyShows, + bool? hideEmptyShows, bool? recursive, GroupBy? groupBy, Map? lastIndices, @@ -377,7 +377,7 @@ class _LibrarySearchModelCopyWithImpl<$R, $Out> if (favourites != null) #favourites: favourites, if (sortingOption != null) #sortingOption: sortingOption, if (sortOrder != null) #sortOrder: sortOrder, - if (hideEmtpyShows != null) #hideEmtpyShows: hideEmtpyShows, + if (hideEmptyShows != null) #hideEmptyShows: hideEmptyShows, if (recursive != null) #recursive: recursive, if (groupBy != null) #groupBy: groupBy, if (lastIndices != null) #lastIndices: lastIndices, @@ -403,7 +403,7 @@ class _LibrarySearchModelCopyWithImpl<$R, $Out> favourites: data.get(#favourites, or: $value.favourites), sortingOption: data.get(#sortingOption, or: $value.sortingOption), sortOrder: data.get(#sortOrder, or: $value.sortOrder), - hideEmtpyShows: data.get(#hideEmtpyShows, or: $value.hideEmtpyShows), + hideEmptyShows: data.get(#hideEmptyShows, or: $value.hideEmptyShows), recursive: data.get(#recursive, or: $value.recursive), groupBy: data.get(#groupBy, or: $value.groupBy), lastIndices: data.get(#lastIndices, or: $value.lastIndices), diff --git a/lib/providers/library_filters_provider.dart b/lib/providers/library_filters_provider.dart new file mode 100644 index 0000000..364405d --- /dev/null +++ b/lib/providers/library_filters_provider.dart @@ -0,0 +1,21 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import 'package:fladder/models/library_filters_model.dart'; +import 'package:fladder/providers/user_provider.dart'; + +part 'library_filters_provider.g.dart'; + +@riverpod +class LibraryFilters extends _$LibraryFilters { + @override + List build(List ids) => ref.watch( + userProvider + .select((value) => (value?.savedFilters ?? []).where((element) => element.containsSameIds(ids)).toList()), + ); + + void removeFilter(LibraryFiltersModel model) => ref.read(userProvider.notifier).removeFilter(model); + + void saveFilter(LibraryFiltersModel model) => ref.read(userProvider.notifier).saveFilter(model); + + void deleteAllFilters() => ref.read(userProvider.notifier).deleteAllFilters(); +} diff --git a/lib/providers/library_filters_provider.g.dart b/lib/providers/library_filters_provider.g.dart new file mode 100644 index 0000000..0fe2951 --- /dev/null +++ b/lib/providers/library_filters_provider.g.dart @@ -0,0 +1,174 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'library_filters_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$libraryFiltersHash() => r'7b4661651df7e0c019dca5bb7eb6433bcd8b3ebb'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$LibraryFilters + extends BuildlessAutoDisposeNotifier> { + late final List ids; + + List build( + List ids, + ); +} + +/// See also [LibraryFilters]. +@ProviderFor(LibraryFilters) +const libraryFiltersProvider = LibraryFiltersFamily(); + +/// See also [LibraryFilters]. +class LibraryFiltersFamily extends Family> { + /// See also [LibraryFilters]. + const LibraryFiltersFamily(); + + /// See also [LibraryFilters]. + LibraryFiltersProvider call( + List ids, + ) { + return LibraryFiltersProvider( + ids, + ); + } + + @override + LibraryFiltersProvider getProviderOverride( + covariant LibraryFiltersProvider provider, + ) { + return call( + provider.ids, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'libraryFiltersProvider'; +} + +/// See also [LibraryFilters]. +class LibraryFiltersProvider extends AutoDisposeNotifierProviderImpl< + LibraryFilters, List> { + /// See also [LibraryFilters]. + LibraryFiltersProvider( + List ids, + ) : this._internal( + () => LibraryFilters()..ids = ids, + from: libraryFiltersProvider, + name: r'libraryFiltersProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$libraryFiltersHash, + dependencies: LibraryFiltersFamily._dependencies, + allTransitiveDependencies: + LibraryFiltersFamily._allTransitiveDependencies, + ids: ids, + ); + + LibraryFiltersProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.ids, + }) : super.internal(); + + final List ids; + + @override + List runNotifierBuild( + covariant LibraryFilters notifier, + ) { + return notifier.build( + ids, + ); + } + + @override + Override overrideWith(LibraryFilters Function() create) { + return ProviderOverride( + origin: this, + override: LibraryFiltersProvider._internal( + () => create()..ids = ids, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + ids: ids, + ), + ); + } + + @override + AutoDisposeNotifierProviderElement> + createElement() { + return _LibraryFiltersProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is LibraryFiltersProvider && other.ids == ids; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, ids.hashCode); + + return _SystemHash.finish(hash); + } +} + +mixin LibraryFiltersRef + on AutoDisposeNotifierProviderRef> { + /// The parameter `ids` of this provider. + List get ids; +} + +class _LibraryFiltersProviderElement extends AutoDisposeNotifierProviderElement< + LibraryFilters, List> with LibraryFiltersRef { + _LibraryFiltersProviderElement(super.provider); + + @override + List get ids => (origin as LibraryFiltersProvider).ids; +} +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/providers/library_search_provider.dart b/lib/providers/library_search_provider.dart index 764f08d..ad23fdb 100644 --- a/lib/providers/library_search_provider.dart +++ b/lib/providers/library_search_provider.dart @@ -13,11 +13,13 @@ import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/items/folder_model.dart'; import 'package:fladder/models/items/item_shared_models.dart'; import 'package:fladder/models/items/photos_model.dart'; +import 'package:fladder/models/library_filters_model.dart'; import 'package:fladder/models/library_search/library_search_model.dart'; import 'package:fladder/models/library_search/library_search_options.dart'; import 'package:fladder/models/playlist_model.dart'; import 'package:fladder/models/view_model.dart'; import 'package:fladder/providers/api_provider.dart'; +import 'package:fladder/providers/library_filters_provider.dart'; import 'package:fladder/providers/service_provider.dart'; import 'package:fladder/providers/settings/client_settings_provider.dart'; import 'package:fladder/providers/user_provider.dart'; @@ -40,6 +42,8 @@ class LibrarySearchNotifier extends StateNotifier { int get pageSize => ref.read(clientSettingsProvider).libraryPageSize ?? 500; + LibraryFiltersProvider get filterProvider => libraryFiltersProvider(state.views.included.map((e) => e.id).toList()); + late final JellyService api = ref.read(jellyApiProvider); set loading(bool loading) => state = state.copyWith(loading: loading); @@ -52,6 +56,8 @@ class LibrarySearchNotifier extends StateNotifier { List? folderId, String? viewModelId, bool? favourites, + SortingOrder? sortOrder, + SortingOptions? sortingOptions, ) async { loading = true; state = state.resetLazyLoad(); @@ -59,7 +65,7 @@ class LibrarySearchNotifier extends StateNotifier { if (folderId != null) { await loadFolders(folderId: folderId); } else { - await loadViews(viewModelId, favourites); + await loadViews(viewModelId, favourites, sortOrder, sortingOptions); } } @@ -151,7 +157,12 @@ class LibrarySearchNotifier extends StateNotifier { } //Pas viewmodel otherwise select first - Future loadViews(String? viewModelId, bool? favourites) async { + Future loadViews( + String? viewModelId, + bool? favourites, + SortingOrder? sortOrder, + SortingOptions? sortingOptions, + ) async { final response = await api.usersUserIdViewsGet(includeHidden: false); final createdViews = response.body?.items?.map((e) => ViewModel.fromBodyDto(e, ref)); Map mappedModels = @@ -159,12 +170,28 @@ class LibrarySearchNotifier extends StateNotifier { final selectedModel = mappedModels.keys.firstWhereOrNull((element) => element.id == viewModelId); + final views = selectedModel != null + ? mappedModels.setKey(mappedModels.keys.firstWhere((element) => element.id == viewModelId), true) + : mappedModels; + state = state.copyWith( - views: selectedModel != null - ? mappedModels.setKey(mappedModels.keys.firstWhere((element) => element.id == viewModelId), true) - : mappedModels, - favourites: favourites, + views: views, ); + + if (sortOrder == null && sortingOptions == null && favourites == null) { + final findFavouriteFilter = ref + .read(libraryFiltersProvider(views.included.map((e) => e.id).toList())) + .firstWhereOrNull((element) => element.isFavourite); + if (findFavouriteFilter != null) { + loadModel(findFavouriteFilter); + } + } else { + state = state.copyWith( + sortOrder: sortOrder, + sortingOption: sortingOptions, + favourites: favourites, + ); + } } Future loadFolders({List? folderId}) async { @@ -348,7 +375,7 @@ class LibrarySearchNotifier extends StateNotifier { recursive: false, studios: state.studios.setAll(false), filters: state.filters.setAll(false), - hideEmtpyShows: false, + hideEmptyShows: false, ); } @@ -356,7 +383,7 @@ class LibrarySearchNotifier extends StateNotifier { void setSortOrder(SortingOrder e) => state = state.copyWith(sortOrder: e); - void setHideEmpty(bool value) => state = state.copyWith(hideEmtpyShows: value); + void setHideEmpty(bool value) => state = state.copyWith(hideEmptyShows: value); void setGroupBy(GroupBy groupBy) => state = state.copyWith(groupBy: groupBy); void setFolderId(ItemBaseModel item) { @@ -460,13 +487,6 @@ class LibrarySearchNotifier extends StateNotifier { state = state.copyWith(posters: currentItems); } - void setDefaultOptions(SortingOrder? sortOrder, SortingOptions? sortingOptions) { - state = state.copyWith( - sortOrder: sortOrder, - sortingOption: sortingOptions, - ); - } - void updateUserDataMain(UserData? userData) { state = state.copyWith( folderOverwrite: [state.folderOverwrite.lastOrNull?.copyWith(userData: userData)].whereNotNull().toList(), @@ -657,6 +677,34 @@ class LibrarySearchNotifier extends StateNotifier { void updateEverything() { state = state.copyWith(); } + + void loadModel(LibraryFiltersModel model) { + state = state.copyWith( + genres: state.genres.replaceMap(model.genres), + filters: state.filters.replaceMap(model.filters), + studios: state.studios.replaceMap(model.studios), + tags: state.tags.replaceMap(model.tags), + years: state.years.replaceMap(model.years), + officialRatings: state.officialRatings.replaceMap(model.officialRatings), + types: state.types.replaceMap(model.types), + sortingOption: model.sortingOption, + sortOrder: model.sortOrder, + favourites: model.favourites, + hideEmptyShows: model.hideEmptyShows, + recursive: model.recursive, + groupBy: model.groupBy, + ); + } + + void saveFiltersNew(String newName) => + ref.read(filterProvider.notifier).saveFilter(LibraryFiltersModel.fromLibrarySearch(newName, state)); + + void updateFilter(LibraryFiltersModel model) { + ref.read(filterProvider.notifier).saveFilter(LibraryFiltersModel.fromLibrarySearch(model.name, state).copyWith( + isFavourite: model.isFavourite, + id: model.id, + )); + } } extension SimpleSorter on List { diff --git a/lib/providers/user_provider.dart b/lib/providers/user_provider.dart index 734dc2a..112e403 100644 --- a/lib/providers/user_provider.dart +++ b/lib/providers/user_provider.dart @@ -1,12 +1,15 @@ import 'package:chopper/chopper.dart'; +import 'package:collection/collection.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + import 'package:fladder/jellyfin/enum_models.dart'; import 'package:fladder/models/account_model.dart'; import 'package:fladder/models/items/item_shared_models.dart'; +import 'package:fladder/models/library_filters_model.dart'; import 'package:fladder/providers/api_provider.dart'; import 'package:fladder/providers/service_provider.dart'; import 'package:fladder/providers/shared_provider.dart'; import 'package:fladder/providers/sync_provider.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'user_provider.g.dart'; @@ -142,4 +145,30 @@ class User extends _$User { AccountModel? build() { return null; } + + void removeFilter(LibraryFiltersModel model) { + final currentList = ((state?.savedFilters ?? [])).toList(growable: true); + currentList.remove(model); + state = state?.copyWith(savedFilters: currentList); + } + + void saveFilter(LibraryFiltersModel model) { + final currentList = (state?.savedFilters ?? []).toList(growable: true); + if (currentList.firstWhereOrNull((value) => value.id == model.id) != null) { + state = state?.copyWith( + savedFilters: currentList.map( + (e) { + if (e.id == model.id) { + return model; + } else { + return e.copyWith(isFavourite: model.isFavourite && model.containsSameIds(e.ids) ? false : e.isFavourite); + } + }, + ).toList()); + } else { + state = state?.copyWith(savedFilters: [model, ...currentList]); + } + } + + void deleteAllFilters() => state = state?.copyWith(savedFilters: []); } diff --git a/lib/providers/user_provider.g.dart b/lib/providers/user_provider.g.dart index 21778b7..c632907 100644 --- a/lib/providers/user_provider.g.dart +++ b/lib/providers/user_provider.g.dart @@ -22,7 +22,7 @@ final showSyncButtonProviderProvider = AutoDisposeProvider.internal( ); typedef ShowSyncButtonProviderRef = AutoDisposeProviderRef; -String _$userHash() => r'4a4302c819d26fc7c28d04b9274d0dfd0dc8e201'; +String _$userHash() => r'418b3d4ade830479db9f48c7793ac5b646778b82'; /// See also [User]. @ProviderFor(User) diff --git a/lib/screens/library_search/library_search_screen.dart b/lib/screens/library_search/library_search_screen.dart index 2ea5060..5710d6f 100644 --- a/lib/screens/library_search/library_search_screen.dart +++ b/lib/screens/library_search/library_search_screen.dart @@ -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 { 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 { 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 { 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 { 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 { 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 { 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), ], diff --git a/lib/screens/library_search/widgets/library_filter_chips.dart b/lib/screens/library_search/widgets/library_filter_chips.dart index 92d9ae9..288f8ec 100644 --- a/lib/screens/library_search/widgets/library_filter_chips.dart +++ b/lib/screens/library_search/widgets/library_filter_chips.dart @@ -190,10 +190,10 @@ List 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, diff --git a/lib/screens/library_search/widgets/library_saved_filters.dart b/lib/screens/library_search/widgets/library_saved_filters.dart new file mode 100644 index 0000000..0751a03 --- /dev/null +++ b/lib/screens/library_search/widgets/library_saved_filters.dart @@ -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 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), + ), + ], + ), + ), + ); + } +} diff --git a/lib/util/item_base_model/item_base_model_extensions.dart b/lib/util/item_base_model/item_base_model_extensions.dart index 159142b..e3a3145 100644 --- a/lib/util/item_base_model/item_base_model_extensions.dart +++ b/lib/util/item_base_model/item_base_model_extensions.dart @@ -106,7 +106,7 @@ extension ItemBaseModelExtensions on ItemBaseModel { ) else if (!exclude.contains(ItemActions.showAlbum) && galleryItem) ItemActionButton( - icon: Icon(FladderItemType.photoalbum.icon), + icon: Icon(FladderItemType.photoAlbum.icon), action: () => (this as PhotoModel).navigateToAlbum(context), label: Text(context.localized.showAlbum), ), diff --git a/lib/util/map_bool_helper.dart b/lib/util/map_bool_helper.dart index b5f0dea..1f34d59 100644 --- a/lib/util/map_bool_helper.dart +++ b/lib/util/map_bool_helper.dart @@ -36,6 +36,7 @@ extension MapExtensions on Map { bool get hasEnabled => values.any((element) => element == true); + //Replaces only keys that exist with the new values Map replaceMap(Map oldMap) { Map result = {};