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

@ -1,6 +1,7 @@
{
"cSpell.words": [
"Jellyfin"
"Jellyfin",
"jellyfin"
],
"dart.flutterSdkPath": ".fvm/versions/3.24.3",
"search.exclude": {

View file

@ -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"
}

View file

@ -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<String> latestItemsExcludes,
@Default([]) List<String> searchQueryHistory,
@Default(false) bool quickConnectState,
@Default([]) List<LibraryFiltersModel> savedFilters,
@JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy,
@JsonKey(includeFromJson: false, includeToJson: false) ServerConfiguration? serverConfiguration,
}) = _AccountModel;

View file

@ -30,6 +30,8 @@ mixin _$AccountModel {
List<String> get latestItemsExcludes => throw _privateConstructorUsedError;
List<String> get searchQueryHistory => throw _privateConstructorUsedError;
bool get quickConnectState => throw _privateConstructorUsedError;
List<LibraryFiltersModel> 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<String> latestItemsExcludes,
List<String> searchQueryHistory,
bool quickConnectState,
List<LibraryFiltersModel> 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<LibraryFiltersModel>,
policy: freezed == policy
? _value.policy
: policy // ignore: cast_nullable_to_non_nullable
@ -168,6 +176,7 @@ abstract class _$$AccountModelImplCopyWith<$Res>
List<String> latestItemsExcludes,
List<String> searchQueryHistory,
bool quickConnectState,
List<LibraryFiltersModel> 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<LibraryFiltersModel>,
policy: freezed == policy
? _value.policy
: policy // ignore: cast_nullable_to_non_nullable
@ -266,11 +280,13 @@ class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin {
final List<String> latestItemsExcludes = const [],
final List<String> searchQueryHistory = const [],
this.quickConnectState = false,
final List<LibraryFiltersModel> 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<String, dynamic> json) =>
@ -315,6 +331,15 @@ class _$AccountModelImpl extends _AccountModel with DiagnosticableTreeMixin {
@override
@JsonKey()
final bool quickConnectState;
final List<LibraryFiltersModel> _savedFilters;
@override
@JsonKey()
List<LibraryFiltersModel> 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<String> latestItemsExcludes,
final List<String> searchQueryHistory,
final bool quickConnectState,
final List<LibraryFiltersModel> 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<LibraryFiltersModel> get savedFilters;
@override
@JsonKey(includeFromJson: false, includeToJson: false)
UserPolicy? get policy;
@override

View file

@ -26,6 +26,11 @@ _$AccountModelImpl _$$AccountModelImplFromJson(Map<String, dynamic> json) =>
.toList() ??
const [],
quickConnectState: json['quickConnectState'] as bool? ?? false,
savedFilters: (json['savedFilters'] as List<dynamic>?)
?.map((e) =>
LibraryFiltersModel.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
);
Map<String, dynamic> _$$AccountModelImplToJson(_$AccountModelImpl instance) =>
@ -40,6 +45,7 @@ Map<String, dynamic> _$$AccountModelImplToJson(_$AccountModelImpl instance) =>
'latestItemsExcludes': instance.latestItemsExcludes,
'searchQueryHistory': instance.searchQueryHistory,
'quickConnectState': instance.quickConnectState,
'savedFilters': instance.savedFilters,
};
const _$AuthenticationEnumMap = {

View file

@ -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:

View file

@ -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,

View file

@ -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()

View file

@ -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<String> ids,
required Map<String, bool> genres,
required Map<ItemFilter, bool> filters,
@StudioEncoder() required Map<Studio, bool> studios,
required Map<String, bool> tags,
required Map<int, bool> years,
required Map<String, bool> officialRatings,
required Map<FladderItemType, bool> types,
required SortingOptions sortingOption,
required SortingOrder sortOrder,
required bool favourites,
required bool hideEmptyShows,
required bool recursive,
required GroupBy groupBy,
}) = _LibraryFiltersModel;
factory LibraryFiltersModel.fromJson(Map<String, dynamic> 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<String> otherIds) => ids.length == otherIds.length && Set.from(ids).containsAll(otherIds);
}
class StudioEncoder implements JsonConverter<Map<Studio, bool>, String> {
const StudioEncoder();
@override
Map<Studio, bool> fromJson(String json) {
final decodedMap = jsonDecode(json) as Map<dynamic, dynamic>;
final studios = decodedMap.map((key, value) => MapEntry(Studio.fromJson(key), value as bool));
return studios;
}
@override
String toJson(Map<Studio, bool> studios) => jsonEncode(studios.map((key, value) => MapEntry(key.toJson(), value)));
}

View file

@ -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>(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<String, dynamic> json) {
return _LibraryFiltersModel.fromJson(json);
}
/// @nodoc
mixin _$LibraryFiltersModel {
String get id => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
dynamic get isFavourite => throw _privateConstructorUsedError;
List<String> get ids => throw _privateConstructorUsedError;
Map<String, bool> get genres => throw _privateConstructorUsedError;
Map<ItemFilter, bool> get filters => throw _privateConstructorUsedError;
@StudioEncoder()
Map<Studio, bool> get studios => throw _privateConstructorUsedError;
Map<String, bool> get tags => throw _privateConstructorUsedError;
Map<int, bool> get years => throw _privateConstructorUsedError;
Map<String, bool> get officialRatings => throw _privateConstructorUsedError;
Map<FladderItemType, bool> 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<String, dynamic> 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<LibraryFiltersModel> 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<String> ids,
Map<String, bool> genres,
Map<ItemFilter, bool> filters,
@StudioEncoder() Map<Studio, bool> studios,
Map<String, bool> tags,
Map<int, bool> years,
Map<String, bool> officialRatings,
Map<FladderItemType, bool> 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<String>,
genres: null == genres
? _value.genres
: genres // ignore: cast_nullable_to_non_nullable
as Map<String, bool>,
filters: null == filters
? _value.filters
: filters // ignore: cast_nullable_to_non_nullable
as Map<ItemFilter, bool>,
studios: null == studios
? _value.studios
: studios // ignore: cast_nullable_to_non_nullable
as Map<Studio, bool>,
tags: null == tags
? _value.tags
: tags // ignore: cast_nullable_to_non_nullable
as Map<String, bool>,
years: null == years
? _value.years
: years // ignore: cast_nullable_to_non_nullable
as Map<int, bool>,
officialRatings: null == officialRatings
? _value.officialRatings
: officialRatings // ignore: cast_nullable_to_non_nullable
as Map<String, bool>,
types: null == types
? _value.types
: types // ignore: cast_nullable_to_non_nullable
as Map<FladderItemType, bool>,
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<String> ids,
Map<String, bool> genres,
Map<ItemFilter, bool> filters,
@StudioEncoder() Map<Studio, bool> studios,
Map<String, bool> tags,
Map<int, bool> years,
Map<String, bool> officialRatings,
Map<FladderItemType, bool> 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<String>,
genres: null == genres
? _value._genres
: genres // ignore: cast_nullable_to_non_nullable
as Map<String, bool>,
filters: null == filters
? _value._filters
: filters // ignore: cast_nullable_to_non_nullable
as Map<ItemFilter, bool>,
studios: null == studios
? _value._studios
: studios // ignore: cast_nullable_to_non_nullable
as Map<Studio, bool>,
tags: null == tags
? _value._tags
: tags // ignore: cast_nullable_to_non_nullable
as Map<String, bool>,
years: null == years
? _value._years
: years // ignore: cast_nullable_to_non_nullable
as Map<int, bool>,
officialRatings: null == officialRatings
? _value._officialRatings
: officialRatings // ignore: cast_nullable_to_non_nullable
as Map<String, bool>,
types: null == types
? _value._types
: types // ignore: cast_nullable_to_non_nullable
as Map<FladderItemType, bool>,
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<String> ids,
required final Map<String, bool> genres,
required final Map<ItemFilter, bool> filters,
@StudioEncoder() required final Map<Studio, bool> studios,
required final Map<String, bool> tags,
required final Map<int, bool> years,
required final Map<String, bool> officialRatings,
required final Map<FladderItemType, bool> 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<String, dynamic> json) =>
_$$LibraryFiltersModelImplFromJson(json);
@override
final String id;
@override
final String name;
@override
@JsonKey()
final dynamic isFavourite;
final List<String> _ids;
@override
List<String> get ids {
if (_ids is EqualUnmodifiableListView) return _ids;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_ids);
}
final Map<String, bool> _genres;
@override
Map<String, bool> get genres {
if (_genres is EqualUnmodifiableMapView) return _genres;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_genres);
}
final Map<ItemFilter, bool> _filters;
@override
Map<ItemFilter, bool> get filters {
if (_filters is EqualUnmodifiableMapView) return _filters;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_filters);
}
final Map<Studio, bool> _studios;
@override
@StudioEncoder()
Map<Studio, bool> get studios {
if (_studios is EqualUnmodifiableMapView) return _studios;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_studios);
}
final Map<String, bool> _tags;
@override
Map<String, bool> get tags {
if (_tags is EqualUnmodifiableMapView) return _tags;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_tags);
}
final Map<int, bool> _years;
@override
Map<int, bool> get years {
if (_years is EqualUnmodifiableMapView) return _years;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_years);
}
final Map<String, bool> _officialRatings;
@override
Map<String, bool> get officialRatings {
if (_officialRatings is EqualUnmodifiableMapView) return _officialRatings;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_officialRatings);
}
final Map<FladderItemType, bool> _types;
@override
Map<FladderItemType, bool> 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<String, dynamic> 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<String> ids,
required final Map<String, bool> genres,
required final Map<ItemFilter, bool> filters,
@StudioEncoder() required final Map<Studio, bool> studios,
required final Map<String, bool> tags,
required final Map<int, bool> years,
required final Map<String, bool> officialRatings,
required final Map<FladderItemType, bool> 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<String, dynamic> json) =
_$LibraryFiltersModelImpl.fromJson;
@override
String get id;
@override
String get name;
@override
dynamic get isFavourite;
@override
List<String> get ids;
@override
Map<String, bool> get genres;
@override
Map<ItemFilter, bool> get filters;
@override
@StudioEncoder()
Map<Studio, bool> get studios;
@override
Map<String, bool> get tags;
@override
Map<int, bool> get years;
@override
Map<String, bool> get officialRatings;
@override
Map<FladderItemType, bool> 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;
}

View file

@ -0,0 +1,124 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'library_filters_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$LibraryFiltersModelImpl _$$LibraryFiltersModelImplFromJson(
Map<String, dynamic> json) =>
_$LibraryFiltersModelImpl(
id: json['id'] as String,
name: json['name'] as String,
isFavourite: json['isFavourite'] ?? false,
ids: (json['ids'] as List<dynamic>).map((e) => e as String).toList(),
genres: Map<String, bool>.from(json['genres'] as Map),
filters: (json['filters'] as Map<String, dynamic>).map(
(k, e) => MapEntry($enumDecode(_$ItemFilterEnumMap, k), e as bool),
),
studios: const StudioEncoder().fromJson(json['studios'] as String),
tags: Map<String, bool>.from(json['tags'] as Map),
years: (json['years'] as Map<String, dynamic>).map(
(k, e) => MapEntry(int.parse(k), e as bool),
),
officialRatings: Map<String, bool>.from(json['officialRatings'] as Map),
types: (json['types'] as Map<String, dynamic>).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<String, dynamic> _$$LibraryFiltersModelImplToJson(
_$LibraryFiltersModelImpl instance) =>
<String, dynamic>{
'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',
};

View file

@ -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<String, int> 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,
);
}

View file

@ -83,7 +83,7 @@ class LibrarySearchModelMapper extends ClassMapperBase<LibrarySearchModel> {
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<LibrarySearchModel> {
static SortingOrder _$sortOrder(LibrarySearchModel v) => v.sortOrder;
static const Field<LibrarySearchModel, SortingOrder> _f$sortOrder =
Field('sortOrder', _$sortOrder, opt: true, def: SortingOrder.ascending);
static bool _$hideEmtpyShows(LibrarySearchModel v) => v.hideEmtpyShows;
static const Field<LibrarySearchModel, bool> _f$hideEmtpyShows =
Field('hideEmtpyShows', _$hideEmtpyShows, opt: true, def: true);
static bool _$hideEmptyShows(LibrarySearchModel v) => v.hideEmptyShows;
static const Field<LibrarySearchModel, bool> _f$hideEmptyShows =
Field('hideEmptyShows', _$hideEmptyShows, opt: true, def: true);
static bool _$recursive(LibrarySearchModel v) => v.recursive;
static const Field<LibrarySearchModel, bool> _f$recursive =
Field('recursive', _$recursive, opt: true, def: false);
@ -138,7 +138,7 @@ class LibrarySearchModelMapper extends ClassMapperBase<LibrarySearchModel> {
#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<LibrarySearchModel> {
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<String, int>? lastIndices,
@ -353,7 +353,7 @@ class _LibrarySearchModelCopyWithImpl<$R, $Out>
bool? favourites,
SortingOptions? sortingOption,
SortingOrder? sortOrder,
bool? hideEmtpyShows,
bool? hideEmptyShows,
bool? recursive,
GroupBy? groupBy,
Map<String, int>? 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),

View file

@ -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<LibraryFiltersModel> build(List<String> 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();
}

View file

@ -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<List<LibraryFiltersModel>> {
late final List<String> ids;
List<LibraryFiltersModel> build(
List<String> ids,
);
}
/// See also [LibraryFilters].
@ProviderFor(LibraryFilters)
const libraryFiltersProvider = LibraryFiltersFamily();
/// See also [LibraryFilters].
class LibraryFiltersFamily extends Family<List<LibraryFiltersModel>> {
/// See also [LibraryFilters].
const LibraryFiltersFamily();
/// See also [LibraryFilters].
LibraryFiltersProvider call(
List<String> ids,
) {
return LibraryFiltersProvider(
ids,
);
}
@override
LibraryFiltersProvider getProviderOverride(
covariant LibraryFiltersProvider provider,
) {
return call(
provider.ids,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'libraryFiltersProvider';
}
/// See also [LibraryFilters].
class LibraryFiltersProvider extends AutoDisposeNotifierProviderImpl<
LibraryFilters, List<LibraryFiltersModel>> {
/// See also [LibraryFilters].
LibraryFiltersProvider(
List<String> 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<String> ids;
@override
List<LibraryFiltersModel> 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<LibraryFilters, List<LibraryFiltersModel>>
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<List<LibraryFiltersModel>> {
/// The parameter `ids` of this provider.
List<String> get ids;
}
class _LibraryFiltersProviderElement extends AutoDisposeNotifierProviderElement<
LibraryFilters, List<LibraryFiltersModel>> with LibraryFiltersRef {
_LibraryFiltersProviderElement(super.provider);
@override
List<String> 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

View file

@ -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<LibrarySearchModel> {
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<LibrarySearchModel> {
List<String>? folderId,
String? viewModelId,
bool? favourites,
SortingOrder? sortOrder,
SortingOptions? sortingOptions,
) async {
loading = true;
state = state.resetLazyLoad();
@ -59,7 +65,7 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
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<LibrarySearchModel> {
}
//Pas viewmodel otherwise select first
Future<void> loadViews(String? viewModelId, bool? favourites) async {
Future<void> 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<ViewModel, bool> mappedModels =
@ -159,12 +170,28 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
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<void> loadFolders({List<String>? folderId}) async {
@ -348,7 +375,7 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
recursive: false,
studios: state.studios.setAll(false),
filters: state.filters.setAll(false),
hideEmtpyShows: false,
hideEmptyShows: false,
);
}
@ -356,7 +383,7 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
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<LibrarySearchModel> {
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<LibrarySearchModel> {
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<ItemBaseModel> {

View file

@ -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: []);
}

View file

@ -22,7 +22,7 @@ final showSyncButtonProviderProvider = AutoDisposeProvider<bool>.internal(
);
typedef ShowSyncButtonProviderRef = AutoDisposeProviderRef<bool>;
String _$userHash() => r'4a4302c819d26fc7c28d04b9274d0dfd0dc8e201';
String _$userHash() => r'418b3d4ade830479db9f48c7793ac5b646778b82';
/// See also [User].
@ProviderFor(User)

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),
),
],
),
),
);
}
}

View file

@ -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),
),

View file

@ -36,6 +36,7 @@ extension MapExtensions<T> on Map<T, bool> {
bool get hasEnabled => values.any((element) => element == true);
//Replaces only keys that exist with the new values
Map<T, bool> replaceMap(Map<T, bool> oldMap) {
Map<T, bool> result = {};