feat: Improve library search screen (#477)

Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
PartyDonut 2025-08-28 23:26:10 +02:00 committed by GitHub
parent 571b682b80
commit d22d340181
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 2881 additions and 2026 deletions

View file

@ -45,7 +45,7 @@ android {
defaultConfig { defaultConfig {
applicationId = "nl.jknaapen.fladder" applicationId = "nl.jknaapen.fladder"
minSdk = 23 minSdkVersion flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode versionCode = flutter.versionCode
versionName = flutter.versionName versionName = flutter.versionName

View file

@ -31,7 +31,7 @@ abstract class AccountModel with _$AccountModel {
@Default([]) List<String> latestItemsExcludes, @Default([]) List<String> latestItemsExcludes,
@Default([]) List<String> searchQueryHistory, @Default([]) List<String> searchQueryHistory,
@Default(false) bool quickConnectState, @Default(false) bool quickConnectState,
@Default([]) List<LibraryFiltersModel> savedFilters, @Default([]) List<LibraryFiltersModel> libraryFilters,
@JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy, @JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy,
@JsonKey(includeFromJson: false, includeToJson: false) ServerConfiguration? serverConfiguration, @JsonKey(includeFromJson: false, includeToJson: false) ServerConfiguration? serverConfiguration,
@JsonKey(includeFromJson: false, includeToJson: false) UserConfiguration? userConfiguration, @JsonKey(includeFromJson: false, includeToJson: false) UserConfiguration? userConfiguration,

View file

@ -24,7 +24,7 @@ mixin _$AccountModel implements DiagnosticableTreeMixin {
List<String> get latestItemsExcludes; List<String> get latestItemsExcludes;
List<String> get searchQueryHistory; List<String> get searchQueryHistory;
bool get quickConnectState; bool get quickConnectState;
List<LibraryFiltersModel> get savedFilters; List<LibraryFiltersModel> get libraryFilters;
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
UserPolicy? get policy; UserPolicy? get policy;
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@ -58,7 +58,7 @@ mixin _$AccountModel implements DiagnosticableTreeMixin {
..add(DiagnosticsProperty('latestItemsExcludes', latestItemsExcludes)) ..add(DiagnosticsProperty('latestItemsExcludes', latestItemsExcludes))
..add(DiagnosticsProperty('searchQueryHistory', searchQueryHistory)) ..add(DiagnosticsProperty('searchQueryHistory', searchQueryHistory))
..add(DiagnosticsProperty('quickConnectState', quickConnectState)) ..add(DiagnosticsProperty('quickConnectState', quickConnectState))
..add(DiagnosticsProperty('savedFilters', savedFilters)) ..add(DiagnosticsProperty('libraryFilters', libraryFilters))
..add(DiagnosticsProperty('policy', policy)) ..add(DiagnosticsProperty('policy', policy))
..add(DiagnosticsProperty('serverConfiguration', serverConfiguration)) ..add(DiagnosticsProperty('serverConfiguration', serverConfiguration))
..add(DiagnosticsProperty('userConfiguration', userConfiguration)) ..add(DiagnosticsProperty('userConfiguration', userConfiguration))
@ -67,7 +67,7 @@ mixin _$AccountModel implements DiagnosticableTreeMixin {
@override @override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { 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, savedFilters: $savedFilters, policy: $policy, serverConfiguration: $serverConfiguration, userConfiguration: $userConfiguration, userSettings: $userSettings)'; return 'AccountModel(name: $name, id: $id, avatar: $avatar, lastUsed: $lastUsed, authMethod: $authMethod, localPin: $localPin, credentials: $credentials, latestItemsExcludes: $latestItemsExcludes, searchQueryHistory: $searchQueryHistory, quickConnectState: $quickConnectState, libraryFilters: $libraryFilters, policy: $policy, serverConfiguration: $serverConfiguration, userConfiguration: $userConfiguration, userSettings: $userSettings)';
} }
} }
@ -88,7 +88,7 @@ abstract mixin class $AccountModelCopyWith<$Res> {
List<String> latestItemsExcludes, List<String> latestItemsExcludes,
List<String> searchQueryHistory, List<String> searchQueryHistory,
bool quickConnectState, bool quickConnectState,
List<LibraryFiltersModel> savedFilters, List<LibraryFiltersModel> libraryFilters,
@JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy, @JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy,
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
ServerConfiguration? serverConfiguration, ServerConfiguration? serverConfiguration,
@ -121,7 +121,7 @@ class _$AccountModelCopyWithImpl<$Res> implements $AccountModelCopyWith<$Res> {
Object? latestItemsExcludes = null, Object? latestItemsExcludes = null,
Object? searchQueryHistory = null, Object? searchQueryHistory = null,
Object? quickConnectState = null, Object? quickConnectState = null,
Object? savedFilters = null, Object? libraryFilters = null,
Object? policy = freezed, Object? policy = freezed,
Object? serverConfiguration = freezed, Object? serverConfiguration = freezed,
Object? userConfiguration = freezed, Object? userConfiguration = freezed,
@ -168,9 +168,9 @@ class _$AccountModelCopyWithImpl<$Res> implements $AccountModelCopyWith<$Res> {
? _self.quickConnectState ? _self.quickConnectState
: quickConnectState // ignore: cast_nullable_to_non_nullable : quickConnectState // ignore: cast_nullable_to_non_nullable
as bool, as bool,
savedFilters: null == savedFilters libraryFilters: null == libraryFilters
? _self.savedFilters ? _self.libraryFilters
: savedFilters // ignore: cast_nullable_to_non_nullable : libraryFilters // ignore: cast_nullable_to_non_nullable
as List<LibraryFiltersModel>, as List<LibraryFiltersModel>,
policy: freezed == policy policy: freezed == policy
? _self.policy ? _self.policy
@ -310,7 +310,7 @@ extension AccountModelPatterns on AccountModel {
List<String> latestItemsExcludes, List<String> latestItemsExcludes,
List<String> searchQueryHistory, List<String> searchQueryHistory,
bool quickConnectState, bool quickConnectState,
List<LibraryFiltersModel> savedFilters, List<LibraryFiltersModel> libraryFilters,
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
UserPolicy? policy, UserPolicy? policy,
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@ -335,7 +335,7 @@ extension AccountModelPatterns on AccountModel {
_that.latestItemsExcludes, _that.latestItemsExcludes,
_that.searchQueryHistory, _that.searchQueryHistory,
_that.quickConnectState, _that.quickConnectState,
_that.savedFilters, _that.libraryFilters,
_that.policy, _that.policy,
_that.serverConfiguration, _that.serverConfiguration,
_that.userConfiguration, _that.userConfiguration,
@ -371,7 +371,7 @@ extension AccountModelPatterns on AccountModel {
List<String> latestItemsExcludes, List<String> latestItemsExcludes,
List<String> searchQueryHistory, List<String> searchQueryHistory,
bool quickConnectState, bool quickConnectState,
List<LibraryFiltersModel> savedFilters, List<LibraryFiltersModel> libraryFilters,
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
UserPolicy? policy, UserPolicy? policy,
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@ -395,7 +395,7 @@ extension AccountModelPatterns on AccountModel {
_that.latestItemsExcludes, _that.latestItemsExcludes,
_that.searchQueryHistory, _that.searchQueryHistory,
_that.quickConnectState, _that.quickConnectState,
_that.savedFilters, _that.libraryFilters,
_that.policy, _that.policy,
_that.serverConfiguration, _that.serverConfiguration,
_that.userConfiguration, _that.userConfiguration,
@ -430,7 +430,7 @@ extension AccountModelPatterns on AccountModel {
List<String> latestItemsExcludes, List<String> latestItemsExcludes,
List<String> searchQueryHistory, List<String> searchQueryHistory,
bool quickConnectState, bool quickConnectState,
List<LibraryFiltersModel> savedFilters, List<LibraryFiltersModel> libraryFilters,
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
UserPolicy? policy, UserPolicy? policy,
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@ -454,7 +454,7 @@ extension AccountModelPatterns on AccountModel {
_that.latestItemsExcludes, _that.latestItemsExcludes,
_that.searchQueryHistory, _that.searchQueryHistory,
_that.quickConnectState, _that.quickConnectState,
_that.savedFilters, _that.libraryFilters,
_that.policy, _that.policy,
_that.serverConfiguration, _that.serverConfiguration,
_that.userConfiguration, _that.userConfiguration,
@ -479,7 +479,7 @@ class _AccountModel extends AccountModel with DiagnosticableTreeMixin {
final List<String> latestItemsExcludes = const [], final List<String> latestItemsExcludes = const [],
final List<String> searchQueryHistory = const [], final List<String> searchQueryHistory = const [],
this.quickConnectState = false, this.quickConnectState = false,
final List<LibraryFiltersModel> savedFilters = const [], final List<LibraryFiltersModel> libraryFilters = const [],
@JsonKey(includeFromJson: false, includeToJson: false) this.policy, @JsonKey(includeFromJson: false, includeToJson: false) this.policy,
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
this.serverConfiguration, this.serverConfiguration,
@ -488,7 +488,7 @@ class _AccountModel extends AccountModel with DiagnosticableTreeMixin {
this.userSettings}) this.userSettings})
: _latestItemsExcludes = latestItemsExcludes, : _latestItemsExcludes = latestItemsExcludes,
_searchQueryHistory = searchQueryHistory, _searchQueryHistory = searchQueryHistory,
_savedFilters = savedFilters, _libraryFilters = libraryFilters,
super._(); super._();
factory _AccountModel.fromJson(Map<String, dynamic> json) => factory _AccountModel.fromJson(Map<String, dynamic> json) =>
_$AccountModelFromJson(json); _$AccountModelFromJson(json);
@ -532,13 +532,13 @@ class _AccountModel extends AccountModel with DiagnosticableTreeMixin {
@override @override
@JsonKey() @JsonKey()
final bool quickConnectState; final bool quickConnectState;
final List<LibraryFiltersModel> _savedFilters; final List<LibraryFiltersModel> _libraryFilters;
@override @override
@JsonKey() @JsonKey()
List<LibraryFiltersModel> get savedFilters { List<LibraryFiltersModel> get libraryFilters {
if (_savedFilters is EqualUnmodifiableListView) return _savedFilters; if (_libraryFilters is EqualUnmodifiableListView) return _libraryFilters;
// ignore: implicit_dynamic_type // ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_savedFilters); return EqualUnmodifiableListView(_libraryFilters);
} }
@override @override
@ -582,7 +582,7 @@ class _AccountModel extends AccountModel with DiagnosticableTreeMixin {
..add(DiagnosticsProperty('latestItemsExcludes', latestItemsExcludes)) ..add(DiagnosticsProperty('latestItemsExcludes', latestItemsExcludes))
..add(DiagnosticsProperty('searchQueryHistory', searchQueryHistory)) ..add(DiagnosticsProperty('searchQueryHistory', searchQueryHistory))
..add(DiagnosticsProperty('quickConnectState', quickConnectState)) ..add(DiagnosticsProperty('quickConnectState', quickConnectState))
..add(DiagnosticsProperty('savedFilters', savedFilters)) ..add(DiagnosticsProperty('libraryFilters', libraryFilters))
..add(DiagnosticsProperty('policy', policy)) ..add(DiagnosticsProperty('policy', policy))
..add(DiagnosticsProperty('serverConfiguration', serverConfiguration)) ..add(DiagnosticsProperty('serverConfiguration', serverConfiguration))
..add(DiagnosticsProperty('userConfiguration', userConfiguration)) ..add(DiagnosticsProperty('userConfiguration', userConfiguration))
@ -591,7 +591,7 @@ class _AccountModel extends AccountModel with DiagnosticableTreeMixin {
@override @override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { 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, savedFilters: $savedFilters, policy: $policy, serverConfiguration: $serverConfiguration, userConfiguration: $userConfiguration, userSettings: $userSettings)'; return 'AccountModel(name: $name, id: $id, avatar: $avatar, lastUsed: $lastUsed, authMethod: $authMethod, localPin: $localPin, credentials: $credentials, latestItemsExcludes: $latestItemsExcludes, searchQueryHistory: $searchQueryHistory, quickConnectState: $quickConnectState, libraryFilters: $libraryFilters, policy: $policy, serverConfiguration: $serverConfiguration, userConfiguration: $userConfiguration, userSettings: $userSettings)';
} }
} }
@ -614,7 +614,7 @@ abstract mixin class _$AccountModelCopyWith<$Res>
List<String> latestItemsExcludes, List<String> latestItemsExcludes,
List<String> searchQueryHistory, List<String> searchQueryHistory,
bool quickConnectState, bool quickConnectState,
List<LibraryFiltersModel> savedFilters, List<LibraryFiltersModel> libraryFilters,
@JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy, @JsonKey(includeFromJson: false, includeToJson: false) UserPolicy? policy,
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
ServerConfiguration? serverConfiguration, ServerConfiguration? serverConfiguration,
@ -649,7 +649,7 @@ class __$AccountModelCopyWithImpl<$Res>
Object? latestItemsExcludes = null, Object? latestItemsExcludes = null,
Object? searchQueryHistory = null, Object? searchQueryHistory = null,
Object? quickConnectState = null, Object? quickConnectState = null,
Object? savedFilters = null, Object? libraryFilters = null,
Object? policy = freezed, Object? policy = freezed,
Object? serverConfiguration = freezed, Object? serverConfiguration = freezed,
Object? userConfiguration = freezed, Object? userConfiguration = freezed,
@ -696,9 +696,9 @@ class __$AccountModelCopyWithImpl<$Res>
? _self.quickConnectState ? _self.quickConnectState
: quickConnectState // ignore: cast_nullable_to_non_nullable : quickConnectState // ignore: cast_nullable_to_non_nullable
as bool, as bool,
savedFilters: null == savedFilters libraryFilters: null == libraryFilters
? _self._savedFilters ? _self._libraryFilters
: savedFilters // ignore: cast_nullable_to_non_nullable : libraryFilters // ignore: cast_nullable_to_non_nullable
as List<LibraryFiltersModel>, as List<LibraryFiltersModel>,
policy: freezed == policy policy: freezed == policy
? _self.policy ? _self.policy

View file

@ -26,7 +26,7 @@ _AccountModel _$AccountModelFromJson(Map<String, dynamic> json) =>
.toList() ?? .toList() ??
const [], const [],
quickConnectState: json['quickConnectState'] as bool? ?? false, quickConnectState: json['quickConnectState'] as bool? ?? false,
savedFilters: (json['savedFilters'] as List<dynamic>?) libraryFilters: (json['libraryFilters'] as List<dynamic>?)
?.map((e) => ?.map((e) =>
LibraryFiltersModel.fromJson(e as Map<String, dynamic>)) LibraryFiltersModel.fromJson(e as Map<String, dynamic>))
.toList() ?? .toList() ??
@ -48,7 +48,7 @@ Map<String, dynamic> _$AccountModelToJson(_AccountModel instance) =>
'latestItemsExcludes': instance.latestItemsExcludes, 'latestItemsExcludes': instance.latestItemsExcludes,
'searchQueryHistory': instance.searchQueryHistory, 'searchQueryHistory': instance.searchQueryHistory,
'quickConnectState': instance.quickConnectState, 'quickConnectState': instance.quickConnectState,
'savedFilters': instance.savedFilters, 'libraryFilters': instance.libraryFilters,
'userSettings': instance.userSettings, 'userSettings': instance.userSettings,
}; };

View file

@ -30,6 +30,11 @@ extension CollectionTypeExtension on CollectionType {
} }
} }
bool get searchRecursive => switch (this) {
CollectionType.homevideos || CollectionType.photos => false,
_ => true,
};
IconData getIconType(bool outlined) { IconData getIconType(bool outlined) {
switch (this) { switch (this) {
case CollectionType.music: case CollectionType.music:

View file

@ -0,0 +1,163 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.enums.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/routes/auto_router.gr.dart';
import 'package:fladder/util/map_bool_helper.dart';
part 'library_filter_model.freezed.dart';
part 'library_filter_model.g.dart';
@Freezed(copyWith: true)
abstract class LibraryFilterModel with _$LibraryFilterModel {
const LibraryFilterModel._();
const factory LibraryFilterModel({
@Default({}) Map<String, bool> genres,
@Default({
ItemFilter.isplayed: false,
ItemFilter.isunplayed: false,
ItemFilter.isresumable: false,
})
@Default({})
Map<ItemFilter, bool> itemFilters,
@StudioEncoder() @Default({}) Map<Studio, bool> studios,
@Default({}) Map<String, bool> tags,
@Default({}) Map<int, bool> years,
@Default({}) Map<String, bool> officialRatings,
@Default({
FladderItemType.audio: false,
FladderItemType.boxset: false,
FladderItemType.book: false,
FladderItemType.collectionFolder: false,
FladderItemType.episode: false,
FladderItemType.folder: false,
FladderItemType.movie: false,
FladderItemType.musicAlbum: false,
FladderItemType.musicVideo: false,
FladderItemType.photo: false,
FladderItemType.person: false,
FladderItemType.photoAlbum: false,
FladderItemType.series: false,
FladderItemType.video: false,
})
Map<FladderItemType, bool> types,
@Default(SortingOptions.sortName) SortingOptions sortingOption,
@Default(SortingOrder.ascending) SortingOrder sortOrder,
@Default(false) bool favourites,
@Default(true) bool hideEmptyShows,
@Default(true) bool recursive,
@Default(GroupBy.none) GroupBy groupBy,
}) = _LibraryFilterModel;
bool get hasActiveFilters {
return genres.hasEnabled ||
studios.hasEnabled ||
tags.hasEnabled ||
years.hasEnabled ||
officialRatings.hasEnabled ||
hideEmptyShows ||
itemFilters.hasEnabled ||
!recursive ||
favourites;
}
LibraryFilterModel loadModel(LibraryFilterModel model) {
return copyWith(
genres: genres.replaceMap(model.genres),
itemFilters: itemFilters.replaceMap(model.itemFilters),
studios: studios.replaceMap(model.studios),
tags: tags.replaceMap(model.tags),
years: years.replaceMap(model.years),
officialRatings: officialRatings.replaceMap(model.officialRatings),
types: types.replaceMap(model.types),
sortingOption: model.sortingOption,
sortOrder: model.sortOrder,
favourites: model.favourites,
hideEmptyShows: model.hideEmptyShows,
recursive: model.recursive,
groupBy: model.groupBy,
);
}
factory LibraryFilterModel.fromJson(Map<String, dynamic> json) => _$LibraryFilterModelFromJson(json);
@override
bool operator ==(covariant LibraryFilterModel other) {
if (identical(this, other)) return true;
return mapEquals(other.genres, genres) &&
mapEquals(other.studios, studios) &&
mapEquals(other.tags, tags) &&
mapEquals(other.years, years) &&
mapEquals(other.officialRatings, officialRatings) &&
mapEquals(other.types, types) &&
other.sortingOption == sortingOption &&
other.sortOrder == sortOrder &&
other.favourites == favourites &&
other.recursive == recursive;
}
@override
int get hashCode {
return itemFilters.hashCode ^
genres.hashCode ^
studios.hashCode ^
tags.hashCode ^
years.hashCode ^
officialRatings.hashCode ^
types.hashCode ^
sortingOption.hashCode ^
sortOrder.hashCode ^
favourites.hashCode ^
recursive.hashCode;
}
LibraryFilterModel clear() {
return copyWith(
genres: genres.setAll(false),
tags: tags.setAll(false),
officialRatings: officialRatings.setAll(false),
years: years.setAll(false),
favourites: false,
recursive: true,
studios: studios.setAll(false),
itemFilters: itemFilters.setAll(false),
hideEmptyShows: false,
);
}
}
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)));
}
extension LibrarySearchRouteExtension on LibrarySearchRoute {
LibrarySearchRoute withFilter(LibraryFilterModel model) {
return LibrarySearchRoute(
viewModelId: args?.viewModelId,
folderId: args?.folderId,
favourites: model.favourites,
sortOrder: model.sortOrder,
sortingOptions: model.sortingOption,
types: model.types,
genres: model.genres,
recursive: model.recursive,
);
}
}

View file

@ -0,0 +1,691 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// 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_filter_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$LibraryFilterModel implements DiagnosticableTreeMixin {
Map<String, bool> get genres;
Map<ItemFilter, bool> get itemFilters;
@StudioEncoder()
Map<Studio, bool> get studios;
Map<String, bool> get tags;
Map<int, bool> get years;
Map<String, bool> get officialRatings;
Map<FladderItemType, bool> get types;
SortingOptions get sortingOption;
SortingOrder get sortOrder;
bool get favourites;
bool get hideEmptyShows;
bool get recursive;
GroupBy get groupBy;
/// Create a copy of LibraryFilterModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$LibraryFilterModelCopyWith<LibraryFilterModel> get copyWith =>
_$LibraryFilterModelCopyWithImpl<LibraryFilterModel>(
this as LibraryFilterModel, _$identity);
/// Serializes this LibraryFilterModel to a JSON map.
Map<String, dynamic> toJson();
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'LibraryFilterModel'))
..add(DiagnosticsProperty('genres', genres))
..add(DiagnosticsProperty('itemFilters', itemFilters))
..add(DiagnosticsProperty('studios', studios))
..add(DiagnosticsProperty('tags', tags))
..add(DiagnosticsProperty('years', years))
..add(DiagnosticsProperty('officialRatings', officialRatings))
..add(DiagnosticsProperty('types', types))
..add(DiagnosticsProperty('sortingOption', sortingOption))
..add(DiagnosticsProperty('sortOrder', sortOrder))
..add(DiagnosticsProperty('favourites', favourites))
..add(DiagnosticsProperty('hideEmptyShows', hideEmptyShows))
..add(DiagnosticsProperty('recursive', recursive))
..add(DiagnosticsProperty('groupBy', groupBy));
}
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'LibraryFilterModel(genres: $genres, itemFilters: $itemFilters, studios: $studios, tags: $tags, years: $years, officialRatings: $officialRatings, types: $types, sortingOption: $sortingOption, sortOrder: $sortOrder, favourites: $favourites, hideEmptyShows: $hideEmptyShows, recursive: $recursive, groupBy: $groupBy)';
}
}
/// @nodoc
abstract mixin class $LibraryFilterModelCopyWith<$Res> {
factory $LibraryFilterModelCopyWith(
LibraryFilterModel value, $Res Function(LibraryFilterModel) _then) =
_$LibraryFilterModelCopyWithImpl;
@useResult
$Res call(
{Map<String, bool> genres,
Map<ItemFilter, bool> itemFilters,
@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 _$LibraryFilterModelCopyWithImpl<$Res>
implements $LibraryFilterModelCopyWith<$Res> {
_$LibraryFilterModelCopyWithImpl(this._self, this._then);
final LibraryFilterModel _self;
final $Res Function(LibraryFilterModel) _then;
/// Create a copy of LibraryFilterModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? genres = null,
Object? itemFilters = 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(_self.copyWith(
genres: null == genres
? _self.genres
: genres // ignore: cast_nullable_to_non_nullable
as Map<String, bool>,
itemFilters: null == itemFilters
? _self.itemFilters
: itemFilters // ignore: cast_nullable_to_non_nullable
as Map<ItemFilter, bool>,
studios: null == studios
? _self.studios
: studios // ignore: cast_nullable_to_non_nullable
as Map<Studio, bool>,
tags: null == tags
? _self.tags
: tags // ignore: cast_nullable_to_non_nullable
as Map<String, bool>,
years: null == years
? _self.years
: years // ignore: cast_nullable_to_non_nullable
as Map<int, bool>,
officialRatings: null == officialRatings
? _self.officialRatings
: officialRatings // ignore: cast_nullable_to_non_nullable
as Map<String, bool>,
types: null == types
? _self.types
: types // ignore: cast_nullable_to_non_nullable
as Map<FladderItemType, bool>,
sortingOption: null == sortingOption
? _self.sortingOption
: sortingOption // ignore: cast_nullable_to_non_nullable
as SortingOptions,
sortOrder: null == sortOrder
? _self.sortOrder
: sortOrder // ignore: cast_nullable_to_non_nullable
as SortingOrder,
favourites: null == favourites
? _self.favourites
: favourites // ignore: cast_nullable_to_non_nullable
as bool,
hideEmptyShows: null == hideEmptyShows
? _self.hideEmptyShows
: hideEmptyShows // ignore: cast_nullable_to_non_nullable
as bool,
recursive: null == recursive
? _self.recursive
: recursive // ignore: cast_nullable_to_non_nullable
as bool,
groupBy: null == groupBy
? _self.groupBy
: groupBy // ignore: cast_nullable_to_non_nullable
as GroupBy,
));
}
}
/// Adds pattern-matching-related methods to [LibraryFilterModel].
extension LibraryFilterModelPatterns on LibraryFilterModel {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>(
TResult Function(_LibraryFilterModel value)? $default, {
required TResult orElse(),
}) {
final _that = this;
switch (_that) {
case _LibraryFilterModel() when $default != null:
return $default(_that);
case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs
TResult map<TResult extends Object?>(
TResult Function(_LibraryFilterModel value) $default,
) {
final _that = this;
switch (_that) {
case _LibraryFilterModel():
return $default(_that);
case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult? Function(_LibraryFilterModel value)? $default,
) {
final _that = this;
switch (_that) {
case _LibraryFilterModel() when $default != null:
return $default(_that);
case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function(
Map<String, bool> genres,
Map<ItemFilter, bool> itemFilters,
@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)?
$default, {
required TResult orElse(),
}) {
final _that = this;
switch (_that) {
case _LibraryFilterModel() when $default != null:
return $default(
_that.genres,
_that.itemFilters,
_that.studios,
_that.tags,
_that.years,
_that.officialRatings,
_that.types,
_that.sortingOption,
_that.sortOrder,
_that.favourites,
_that.hideEmptyShows,
_that.recursive,
_that.groupBy);
case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs
TResult when<TResult extends Object?>(
TResult Function(
Map<String, bool> genres,
Map<ItemFilter, bool> itemFilters,
@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)
$default,
) {
final _that = this;
switch (_that) {
case _LibraryFilterModel():
return $default(
_that.genres,
_that.itemFilters,
_that.studios,
_that.tags,
_that.years,
_that.officialRatings,
_that.types,
_that.sortingOption,
_that.sortOrder,
_that.favourites,
_that.hideEmptyShows,
_that.recursive,
_that.groupBy);
case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function(
Map<String, bool> genres,
Map<ItemFilter, bool> itemFilters,
@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)?
$default,
) {
final _that = this;
switch (_that) {
case _LibraryFilterModel() when $default != null:
return $default(
_that.genres,
_that.itemFilters,
_that.studios,
_that.tags,
_that.years,
_that.officialRatings,
_that.types,
_that.sortingOption,
_that.sortOrder,
_that.favourites,
_that.hideEmptyShows,
_that.recursive,
_that.groupBy);
case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _LibraryFilterModel extends LibraryFilterModel
with DiagnosticableTreeMixin {
const _LibraryFilterModel(
{final Map<String, bool> genres = const {},
final Map<ItemFilter, bool> itemFilters = const {
ItemFilter.isplayed: false,
ItemFilter.isunplayed: false,
ItemFilter.isresumable: false
},
@StudioEncoder() final Map<Studio, bool> studios = const {},
final Map<String, bool> tags = const {},
final Map<int, bool> years = const {},
final Map<String, bool> officialRatings = const {},
final Map<FladderItemType, bool> types = const {
FladderItemType.audio: false,
FladderItemType.boxset: false,
FladderItemType.book: false,
FladderItemType.collectionFolder: false,
FladderItemType.episode: false,
FladderItemType.folder: false,
FladderItemType.movie: false,
FladderItemType.musicAlbum: false,
FladderItemType.musicVideo: false,
FladderItemType.photo: false,
FladderItemType.person: false,
FladderItemType.photoAlbum: false,
FladderItemType.series: false,
FladderItemType.video: false
},
this.sortingOption = SortingOptions.sortName,
this.sortOrder = SortingOrder.ascending,
this.favourites = false,
this.hideEmptyShows = true,
this.recursive = true,
this.groupBy = GroupBy.none})
: _genres = genres,
_itemFilters = itemFilters,
_studios = studios,
_tags = tags,
_years = years,
_officialRatings = officialRatings,
_types = types,
super._();
factory _LibraryFilterModel.fromJson(Map<String, dynamic> json) =>
_$LibraryFilterModelFromJson(json);
final Map<String, bool> _genres;
@override
@JsonKey()
Map<String, bool> get genres {
if (_genres is EqualUnmodifiableMapView) return _genres;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_genres);
}
final Map<ItemFilter, bool> _itemFilters;
@override
@JsonKey()
Map<ItemFilter, bool> get itemFilters {
if (_itemFilters is EqualUnmodifiableMapView) return _itemFilters;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_itemFilters);
}
final Map<Studio, bool> _studios;
@override
@JsonKey()
@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
@JsonKey()
Map<String, bool> get tags {
if (_tags is EqualUnmodifiableMapView) return _tags;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_tags);
}
final Map<int, bool> _years;
@override
@JsonKey()
Map<int, bool> get years {
if (_years is EqualUnmodifiableMapView) return _years;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_years);
}
final Map<String, bool> _officialRatings;
@override
@JsonKey()
Map<String, bool> get officialRatings {
if (_officialRatings is EqualUnmodifiableMapView) return _officialRatings;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_officialRatings);
}
final Map<FladderItemType, bool> _types;
@override
@JsonKey()
Map<FladderItemType, bool> get types {
if (_types is EqualUnmodifiableMapView) return _types;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_types);
}
@override
@JsonKey()
final SortingOptions sortingOption;
@override
@JsonKey()
final SortingOrder sortOrder;
@override
@JsonKey()
final bool favourites;
@override
@JsonKey()
final bool hideEmptyShows;
@override
@JsonKey()
final bool recursive;
@override
@JsonKey()
final GroupBy groupBy;
/// Create a copy of LibraryFilterModel
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$LibraryFilterModelCopyWith<_LibraryFilterModel> get copyWith =>
__$LibraryFilterModelCopyWithImpl<_LibraryFilterModel>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$LibraryFilterModelToJson(
this,
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'LibraryFilterModel'))
..add(DiagnosticsProperty('genres', genres))
..add(DiagnosticsProperty('itemFilters', itemFilters))
..add(DiagnosticsProperty('studios', studios))
..add(DiagnosticsProperty('tags', tags))
..add(DiagnosticsProperty('years', years))
..add(DiagnosticsProperty('officialRatings', officialRatings))
..add(DiagnosticsProperty('types', types))
..add(DiagnosticsProperty('sortingOption', sortingOption))
..add(DiagnosticsProperty('sortOrder', sortOrder))
..add(DiagnosticsProperty('favourites', favourites))
..add(DiagnosticsProperty('hideEmptyShows', hideEmptyShows))
..add(DiagnosticsProperty('recursive', recursive))
..add(DiagnosticsProperty('groupBy', groupBy));
}
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'LibraryFilterModel(genres: $genres, itemFilters: $itemFilters, studios: $studios, tags: $tags, years: $years, officialRatings: $officialRatings, types: $types, sortingOption: $sortingOption, sortOrder: $sortOrder, favourites: $favourites, hideEmptyShows: $hideEmptyShows, recursive: $recursive, groupBy: $groupBy)';
}
}
/// @nodoc
abstract mixin class _$LibraryFilterModelCopyWith<$Res>
implements $LibraryFilterModelCopyWith<$Res> {
factory _$LibraryFilterModelCopyWith(
_LibraryFilterModel value, $Res Function(_LibraryFilterModel) _then) =
__$LibraryFilterModelCopyWithImpl;
@override
@useResult
$Res call(
{Map<String, bool> genres,
Map<ItemFilter, bool> itemFilters,
@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 __$LibraryFilterModelCopyWithImpl<$Res>
implements _$LibraryFilterModelCopyWith<$Res> {
__$LibraryFilterModelCopyWithImpl(this._self, this._then);
final _LibraryFilterModel _self;
final $Res Function(_LibraryFilterModel) _then;
/// Create a copy of LibraryFilterModel
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? genres = null,
Object? itemFilters = 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(_LibraryFilterModel(
genres: null == genres
? _self._genres
: genres // ignore: cast_nullable_to_non_nullable
as Map<String, bool>,
itemFilters: null == itemFilters
? _self._itemFilters
: itemFilters // ignore: cast_nullable_to_non_nullable
as Map<ItemFilter, bool>,
studios: null == studios
? _self._studios
: studios // ignore: cast_nullable_to_non_nullable
as Map<Studio, bool>,
tags: null == tags
? _self._tags
: tags // ignore: cast_nullable_to_non_nullable
as Map<String, bool>,
years: null == years
? _self._years
: years // ignore: cast_nullable_to_non_nullable
as Map<int, bool>,
officialRatings: null == officialRatings
? _self._officialRatings
: officialRatings // ignore: cast_nullable_to_non_nullable
as Map<String, bool>,
types: null == types
? _self._types
: types // ignore: cast_nullable_to_non_nullable
as Map<FladderItemType, bool>,
sortingOption: null == sortingOption
? _self.sortingOption
: sortingOption // ignore: cast_nullable_to_non_nullable
as SortingOptions,
sortOrder: null == sortOrder
? _self.sortOrder
: sortOrder // ignore: cast_nullable_to_non_nullable
as SortingOrder,
favourites: null == favourites
? _self.favourites
: favourites // ignore: cast_nullable_to_non_nullable
as bool,
hideEmptyShows: null == hideEmptyShows
? _self.hideEmptyShows
: hideEmptyShows // ignore: cast_nullable_to_non_nullable
as bool,
recursive: null == recursive
? _self.recursive
: recursive // ignore: cast_nullable_to_non_nullable
as bool,
groupBy: null == groupBy
? _self.groupBy
: groupBy // ignore: cast_nullable_to_non_nullable
as GroupBy,
));
}
}
// dart format on

View file

@ -0,0 +1,152 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'library_filter_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_LibraryFilterModel _$LibraryFilterModelFromJson(Map<String, dynamic> json) =>
_LibraryFilterModel(
genres: (json['genres'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as bool),
) ??
const {},
itemFilters: (json['itemFilters'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry($enumDecode(_$ItemFilterEnumMap, k), e as bool),
) ??
const {
ItemFilter.isplayed: false,
ItemFilter.isunplayed: false,
ItemFilter.isresumable: false
},
studios: json['studios'] == null
? const {}
: const StudioEncoder().fromJson(json['studios'] as String),
tags: (json['tags'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as bool),
) ??
const {},
years: (json['years'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(int.parse(k), e as bool),
) ??
const {},
officialRatings: (json['officialRatings'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, e as bool),
) ??
const {},
types: (json['types'] as Map<String, dynamic>?)?.map(
(k, e) =>
MapEntry($enumDecode(_$FladderItemTypeEnumMap, k), e as bool),
) ??
const {
FladderItemType.audio: false,
FladderItemType.boxset: false,
FladderItemType.book: false,
FladderItemType.collectionFolder: false,
FladderItemType.episode: false,
FladderItemType.folder: false,
FladderItemType.movie: false,
FladderItemType.musicAlbum: false,
FladderItemType.musicVideo: false,
FladderItemType.photo: false,
FladderItemType.person: false,
FladderItemType.photoAlbum: false,
FladderItemType.series: false,
FladderItemType.video: false
},
sortingOption:
$enumDecodeNullable(_$SortingOptionsEnumMap, json['sortingOption']) ??
SortingOptions.sortName,
sortOrder:
$enumDecodeNullable(_$SortingOrderEnumMap, json['sortOrder']) ??
SortingOrder.ascending,
favourites: json['favourites'] as bool? ?? false,
hideEmptyShows: json['hideEmptyShows'] as bool? ?? true,
recursive: json['recursive'] as bool? ?? true,
groupBy: $enumDecodeNullable(_$GroupByEnumMap, json['groupBy']) ??
GroupBy.none,
);
Map<String, dynamic> _$LibraryFilterModelToJson(_LibraryFilterModel instance) =>
<String, dynamic>{
'genres': instance.genres,
'itemFilters': instance.itemFilters
.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.sortName: 'sortName',
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,14 +1,8 @@
import 'dart:convert';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:xid/xid.dart'; import 'package:xid/xid.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart'; import 'package:fladder/models/library_filter_model.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_model.dart';
import 'package:fladder/models/library_search/library_search_options.dart';
import 'package:fladder/util/map_bool_helper.dart'; import 'package:fladder/util/map_bool_helper.dart';
part 'library_filters_model.freezed.dart'; part 'library_filters_model.freezed.dart';
@ -22,20 +16,8 @@ abstract class LibraryFiltersModel with _$LibraryFiltersModel {
required String id, required String id,
required String name, required String name,
required bool isFavourite, required bool isFavourite,
required List<String> ids, @Default([]) List<String> ids,
required Map<String, bool> genres, @Default(LibraryFilterModel()) LibraryFilterModel filter,
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; }) = _LibraryFiltersModel;
factory LibraryFiltersModel.fromJson(Map<String, dynamic> json) => _$LibraryFiltersModelFromJson(json); factory LibraryFiltersModel.fromJson(Map<String, dynamic> json) => _$LibraryFiltersModelFromJson(json);
@ -51,35 +33,9 @@ abstract class LibraryFiltersModel with _$LibraryFiltersModel {
name: name, name: name,
isFavourite: isFavourite ?? false, isFavourite: isFavourite ?? false,
ids: searchModel.views.included.map((e) => e.id).toList(), ids: searchModel.views.included.map((e) => e.id).toList(),
genres: searchModel.genres, filter: searchModel.filters,
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); 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

@ -18,20 +18,7 @@ mixin _$LibraryFiltersModel {
String get name; String get name;
bool get isFavourite; bool get isFavourite;
List<String> get ids; List<String> get ids;
Map<String, bool> get genres; LibraryFilterModel get filter;
Map<ItemFilter, bool> get filters;
@StudioEncoder()
Map<Studio, bool> get studios;
Map<String, bool> get tags;
Map<int, bool> get years;
Map<String, bool> get officialRatings;
Map<FladderItemType, bool> get types;
SortingOptions get sortingOption;
SortingOrder get sortOrder;
bool get favourites;
bool get hideEmptyShows;
bool get recursive;
GroupBy get groupBy;
/// Create a copy of LibraryFiltersModel /// Create a copy of LibraryFiltersModel
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@ -46,7 +33,7 @@ mixin _$LibraryFiltersModel {
@override @override
String toString() { String toString() {
return 'LibraryFiltersModel(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)'; return 'LibraryFiltersModel(id: $id, name: $name, isFavourite: $isFavourite, ids: $ids, filter: $filter)';
} }
} }
@ -61,19 +48,9 @@ abstract mixin class $LibraryFiltersModelCopyWith<$Res> {
String name, String name,
bool isFavourite, bool isFavourite,
List<String> ids, List<String> ids,
Map<String, bool> genres, LibraryFilterModel filter});
Map<ItemFilter, bool> filters,
@StudioEncoder() Map<Studio, bool> studios, $LibraryFilterModelCopyWith<$Res> get filter;
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 /// @nodoc
@ -93,19 +70,7 @@ class _$LibraryFiltersModelCopyWithImpl<$Res>
Object? name = null, Object? name = null,
Object? isFavourite = null, Object? isFavourite = null,
Object? ids = null, Object? ids = null,
Object? genres = null, Object? filter = 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(_self.copyWith( return _then(_self.copyWith(
id: null == id id: null == id
@ -124,60 +89,22 @@ class _$LibraryFiltersModelCopyWithImpl<$Res>
? _self.ids ? _self.ids
: ids // ignore: cast_nullable_to_non_nullable : ids // ignore: cast_nullable_to_non_nullable
as List<String>, as List<String>,
genres: null == genres filter: null == filter
? _self.genres ? _self.filter
: genres // ignore: cast_nullable_to_non_nullable : filter // ignore: cast_nullable_to_non_nullable
as Map<String, bool>, as LibraryFilterModel,
filters: null == filters
? _self.filters
: filters // ignore: cast_nullable_to_non_nullable
as Map<ItemFilter, bool>,
studios: null == studios
? _self.studios
: studios // ignore: cast_nullable_to_non_nullable
as Map<Studio, bool>,
tags: null == tags
? _self.tags
: tags // ignore: cast_nullable_to_non_nullable
as Map<String, bool>,
years: null == years
? _self.years
: years // ignore: cast_nullable_to_non_nullable
as Map<int, bool>,
officialRatings: null == officialRatings
? _self.officialRatings
: officialRatings // ignore: cast_nullable_to_non_nullable
as Map<String, bool>,
types: null == types
? _self.types
: types // ignore: cast_nullable_to_non_nullable
as Map<FladderItemType, bool>,
sortingOption: null == sortingOption
? _self.sortingOption
: sortingOption // ignore: cast_nullable_to_non_nullable
as SortingOptions,
sortOrder: null == sortOrder
? _self.sortOrder
: sortOrder // ignore: cast_nullable_to_non_nullable
as SortingOrder,
favourites: null == favourites
? _self.favourites
: favourites // ignore: cast_nullable_to_non_nullable
as bool,
hideEmptyShows: null == hideEmptyShows
? _self.hideEmptyShows
: hideEmptyShows // ignore: cast_nullable_to_non_nullable
as bool,
recursive: null == recursive
? _self.recursive
: recursive // ignore: cast_nullable_to_non_nullable
as bool,
groupBy: null == groupBy
? _self.groupBy
: groupBy // ignore: cast_nullable_to_non_nullable
as GroupBy,
)); ));
} }
/// Create a copy of LibraryFiltersModel
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$LibraryFilterModelCopyWith<$Res> get filter {
return $LibraryFilterModelCopyWith<$Res>(_self.filter, (value) {
return _then(_self.copyWith(filter: value));
});
}
} }
/// Adds pattern-matching-related methods to [LibraryFiltersModel]. /// Adds pattern-matching-related methods to [LibraryFiltersModel].
@ -273,24 +200,8 @@ extension LibraryFiltersModelPatterns on LibraryFiltersModel {
@optionalTypeArgs @optionalTypeArgs
TResult maybeWhen<TResult extends Object?>( TResult maybeWhen<TResult extends Object?>(
TResult Function( TResult Function(String id, String name, bool isFavourite, List<String> ids,
String id, LibraryFilterModel filter)?
String name,
bool 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)?
$default, { $default, {
required TResult orElse(), required TResult orElse(),
}) { }) {
@ -298,23 +209,7 @@ extension LibraryFiltersModelPatterns on LibraryFiltersModel {
switch (_that) { switch (_that) {
case _LibraryFiltersModel() when $default != null: case _LibraryFiltersModel() when $default != null:
return $default( return $default(
_that.id, _that.id, _that.name, _that.isFavourite, _that.ids, _that.filter);
_that.name,
_that.isFavourite,
_that.ids,
_that.genres,
_that.filters,
_that.studios,
_that.tags,
_that.years,
_that.officialRatings,
_that.types,
_that.sortingOption,
_that.sortOrder,
_that.favourites,
_that.hideEmptyShows,
_that.recursive,
_that.groupBy);
case _: case _:
return orElse(); return orElse();
} }
@ -335,47 +230,15 @@ extension LibraryFiltersModelPatterns on LibraryFiltersModel {
@optionalTypeArgs @optionalTypeArgs
TResult when<TResult extends Object?>( TResult when<TResult extends Object?>(
TResult Function( TResult Function(String id, String name, bool isFavourite, List<String> ids,
String id, LibraryFilterModel filter)
String name,
bool 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)
$default, $default,
) { ) {
final _that = this; final _that = this;
switch (_that) { switch (_that) {
case _LibraryFiltersModel(): case _LibraryFiltersModel():
return $default( return $default(
_that.id, _that.id, _that.name, _that.isFavourite, _that.ids, _that.filter);
_that.name,
_that.isFavourite,
_that.ids,
_that.genres,
_that.filters,
_that.studios,
_that.tags,
_that.years,
_that.officialRatings,
_that.types,
_that.sortingOption,
_that.sortOrder,
_that.favourites,
_that.hideEmptyShows,
_that.recursive,
_that.groupBy);
case _: case _:
throw StateError('Unexpected subclass'); throw StateError('Unexpected subclass');
} }
@ -395,47 +258,15 @@ extension LibraryFiltersModelPatterns on LibraryFiltersModel {
@optionalTypeArgs @optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>( TResult? whenOrNull<TResult extends Object?>(
TResult? Function( TResult? Function(String id, String name, bool isFavourite,
String id, List<String> ids, LibraryFilterModel filter)?
String name,
bool 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)?
$default, $default,
) { ) {
final _that = this; final _that = this;
switch (_that) { switch (_that) {
case _LibraryFiltersModel() when $default != null: case _LibraryFiltersModel() when $default != null:
return $default( return $default(
_that.id, _that.id, _that.name, _that.isFavourite, _that.ids, _that.filter);
_that.name,
_that.isFavourite,
_that.ids,
_that.genres,
_that.filters,
_that.studios,
_that.tags,
_that.years,
_that.officialRatings,
_that.types,
_that.sortingOption,
_that.sortOrder,
_that.favourites,
_that.hideEmptyShows,
_that.recursive,
_that.groupBy);
case _: case _:
return null; return null;
} }
@ -449,28 +280,9 @@ class _LibraryFiltersModel extends LibraryFiltersModel {
{required this.id, {required this.id,
required this.name, required this.name,
required this.isFavourite, required this.isFavourite,
required final List<String> ids, final List<String> ids = const [],
required final Map<String, bool> genres, this.filter = const LibraryFilterModel()})
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, : _ids = ids,
_genres = genres,
_filters = filters,
_studios = studios,
_tags = tags,
_years = years,
_officialRatings = officialRatings,
_types = types,
super._(); super._();
factory _LibraryFiltersModel.fromJson(Map<String, dynamic> json) => factory _LibraryFiltersModel.fromJson(Map<String, dynamic> json) =>
_$LibraryFiltersModelFromJson(json); _$LibraryFiltersModelFromJson(json);
@ -483,81 +295,16 @@ class _LibraryFiltersModel extends LibraryFiltersModel {
final bool isFavourite; final bool isFavourite;
final List<String> _ids; final List<String> _ids;
@override @override
@JsonKey()
List<String> get ids { List<String> get ids {
if (_ids is EqualUnmodifiableListView) return _ids; if (_ids is EqualUnmodifiableListView) return _ids;
// ignore: implicit_dynamic_type // ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_ids); return EqualUnmodifiableListView(_ids);
} }
final Map<String, bool> _genres;
@override @override
Map<String, bool> get genres { @JsonKey()
if (_genres is EqualUnmodifiableMapView) return _genres; final LibraryFilterModel filter;
// 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;
/// Create a copy of LibraryFiltersModel /// Create a copy of LibraryFiltersModel
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@ -577,7 +324,7 @@ class _LibraryFiltersModel extends LibraryFiltersModel {
@override @override
String toString() { String toString() {
return 'LibraryFiltersModel(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)'; return 'LibraryFiltersModel(id: $id, name: $name, isFavourite: $isFavourite, ids: $ids, filter: $filter)';
} }
} }
@ -594,19 +341,10 @@ abstract mixin class _$LibraryFiltersModelCopyWith<$Res>
String name, String name,
bool isFavourite, bool isFavourite,
List<String> ids, List<String> ids,
Map<String, bool> genres, LibraryFilterModel filter});
Map<ItemFilter, bool> filters,
@StudioEncoder() Map<Studio, bool> studios, @override
Map<String, bool> tags, $LibraryFilterModelCopyWith<$Res> get filter;
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 /// @nodoc
@ -626,19 +364,7 @@ class __$LibraryFiltersModelCopyWithImpl<$Res>
Object? name = null, Object? name = null,
Object? isFavourite = null, Object? isFavourite = null,
Object? ids = null, Object? ids = null,
Object? genres = null, Object? filter = 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(_LibraryFiltersModel( return _then(_LibraryFiltersModel(
id: null == id id: null == id
@ -657,60 +383,22 @@ class __$LibraryFiltersModelCopyWithImpl<$Res>
? _self._ids ? _self._ids
: ids // ignore: cast_nullable_to_non_nullable : ids // ignore: cast_nullable_to_non_nullable
as List<String>, as List<String>,
genres: null == genres filter: null == filter
? _self._genres ? _self.filter
: genres // ignore: cast_nullable_to_non_nullable : filter // ignore: cast_nullable_to_non_nullable
as Map<String, bool>, as LibraryFilterModel,
filters: null == filters
? _self._filters
: filters // ignore: cast_nullable_to_non_nullable
as Map<ItemFilter, bool>,
studios: null == studios
? _self._studios
: studios // ignore: cast_nullable_to_non_nullable
as Map<Studio, bool>,
tags: null == tags
? _self._tags
: tags // ignore: cast_nullable_to_non_nullable
as Map<String, bool>,
years: null == years
? _self._years
: years // ignore: cast_nullable_to_non_nullable
as Map<int, bool>,
officialRatings: null == officialRatings
? _self._officialRatings
: officialRatings // ignore: cast_nullable_to_non_nullable
as Map<String, bool>,
types: null == types
? _self._types
: types // ignore: cast_nullable_to_non_nullable
as Map<FladderItemType, bool>,
sortingOption: null == sortingOption
? _self.sortingOption
: sortingOption // ignore: cast_nullable_to_non_nullable
as SortingOptions,
sortOrder: null == sortOrder
? _self.sortOrder
: sortOrder // ignore: cast_nullable_to_non_nullable
as SortingOrder,
favourites: null == favourites
? _self.favourites
: favourites // ignore: cast_nullable_to_non_nullable
as bool,
hideEmptyShows: null == hideEmptyShows
? _self.hideEmptyShows
: hideEmptyShows // ignore: cast_nullable_to_non_nullable
as bool,
recursive: null == recursive
? _self.recursive
: recursive // ignore: cast_nullable_to_non_nullable
as bool,
groupBy: null == groupBy
? _self.groupBy
: groupBy // ignore: cast_nullable_to_non_nullable
as GroupBy,
)); ));
} }
/// Create a copy of LibraryFiltersModel
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$LibraryFilterModelCopyWith<$Res> get filter {
return $LibraryFilterModelCopyWith<$Res>(_self.filter, (value) {
return _then(_self.copyWith(filter: value));
});
}
} }
// dart format on // dart format on

View file

@ -11,27 +11,11 @@ _LibraryFiltersModel _$LibraryFiltersModelFromJson(Map<String, dynamic> json) =>
id: json['id'] as String, id: json['id'] as String,
name: json['name'] as String, name: json['name'] as String,
isFavourite: json['isFavourite'] as bool, isFavourite: json['isFavourite'] as bool,
ids: (json['ids'] as List<dynamic>).map((e) => e as String).toList(), ids: (json['ids'] as List<dynamic>?)?.map((e) => e as String).toList() ??
genres: Map<String, bool>.from(json['genres'] as Map), const [],
filters: (json['filters'] as Map<String, dynamic>).map( filter: json['filter'] == null
(k, e) => MapEntry($enumDecode(_$ItemFilterEnumMap, k), e as bool), ? const LibraryFilterModel()
), : LibraryFilterModel.fromJson(json['filter'] as Map<String, dynamic>),
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> _$LibraryFiltersModelToJson( Map<String, dynamic> _$LibraryFiltersModelToJson(
@ -41,83 +25,5 @@ Map<String, dynamic> _$LibraryFiltersModelToJson(
'name': instance.name, 'name': instance.name,
'isFavourite': instance.isFavourite, 'isFavourite': instance.isFavourite,
'ids': instance.ids, 'ids': instance.ids,
'genres': instance.genres, 'filter': instance.filter,
'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.sortName: 'sortName',
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

@ -2,103 +2,37 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:dart_mappable/dart_mappable.dart'; import 'package:freezed_annotation/freezed_annotation.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/item_base_model.dart';
import 'package:fladder/models/items/item_shared_models.dart'; import 'package:fladder/models/library_filter_model.dart';
import 'package:fladder/models/library_search/library_search_options.dart';
import 'package:fladder/models/view_model.dart'; import 'package:fladder/models/view_model.dart';
import 'package:fladder/util/list_extensions.dart'; import 'package:fladder/util/list_extensions.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/map_bool_helper.dart'; import 'package:fladder/util/map_bool_helper.dart';
part 'library_search_model.mapper.dart'; part 'library_search_model.freezed.dart';
@MappableClass() @Freezed(copyWith: true)
class LibrarySearchModel with LibrarySearchModelMappable { abstract class LibrarySearchModel with _$LibrarySearchModel {
final bool loading; const factory LibrarySearchModel({
final bool selecteMode; @Default(false) bool loading,
final String searchQuery; @Default(false) bool selecteMode,
final List<ItemBaseModel> folderOverwrite; @Default(<ItemBaseModel>[]) List<ItemBaseModel> folderOverwrite,
final Map<ViewModel, bool> views; @Default("") String searchQuery,
final List<ItemBaseModel> posters; @Default(<ViewModel, bool>{}) Map<ViewModel, bool> views,
final List<ItemBaseModel> selectedPosters; @Default(<ItemBaseModel>[]) List<ItemBaseModel> posters,
final Map<ItemFilter, bool> filters; @Default(<ItemBaseModel>[]) List<ItemBaseModel> selectedPosters,
final Map<String, bool> genres; @Default(LibraryFilterModel()) LibraryFilterModel filters,
final Map<Studio, bool> studios; @Default(<String, int>{}) Map<String, int> lastIndices,
final Map<String, bool> tags; @Default(<String, int>{}) Map<String, int> libraryItemCounts,
final Map<int, bool> years; @Default(false) bool fetchingItems,
final Map<String, bool> officialRatings; }) = _LibrarySearchModel;
final Map<FladderItemType, bool> types;
final SortingOptions sortingOption;
final SortingOrder sortOrder;
final bool favourites;
final bool hideEmptyShows;
final bool recursive;
final GroupBy groupBy;
final Map<String, int> lastIndices;
final Map<String, int> libraryItemCounts;
final bool fetchingItems;
const LibrarySearchModel({
this.loading = false,
this.selecteMode = false,
this.folderOverwrite = const [],
this.searchQuery = "",
this.views = const {},
this.posters = const [],
this.selectedPosters = const [],
this.filters = const {
ItemFilter.isplayed: false,
ItemFilter.isunplayed: false,
ItemFilter.isresumable: false,
},
this.genres = const {},
this.studios = const {},
this.tags = const {},
this.years = const {},
this.officialRatings = const {},
this.types = const {
FladderItemType.audio: false,
FladderItemType.boxset: false,
FladderItemType.book: false,
FladderItemType.collectionFolder: false,
FladderItemType.episode: false,
FladderItemType.folder: false,
FladderItemType.movie: true,
FladderItemType.musicAlbum: false,
FladderItemType.musicVideo: false,
FladderItemType.photo: false,
FladderItemType.person: false,
FladderItemType.photoAlbum: false,
FladderItemType.series: true,
FladderItemType.video: true,
},
this.favourites = false,
this.sortingOption = SortingOptions.sortName,
this.sortOrder = SortingOrder.ascending,
this.hideEmptyShows = true,
this.recursive = false,
this.groupBy = GroupBy.none,
this.lastIndices = const {},
this.libraryItemCounts = const {},
this.fetchingItems = false,
});
bool get hasActiveFilters {
return genres.hasEnabled ||
studios.hasEnabled ||
tags.hasEnabled ||
years.hasEnabled ||
officialRatings.hasEnabled ||
hideEmptyShows ||
filters.hasEnabled ||
favourites ||
searchQuery.isNotEmpty;
} }
extension LibrarySearchModelX on LibrarySearchModel {
bool get hasActiveFilters => filters.hasActiveFilters || searchQuery.isNotEmpty;
int get totalItemCount { int get totalItemCount {
if (libraryItemCounts.isEmpty) return posters.length; if (libraryItemCounts.isEmpty) return posters.length;
int totalCount = 0; int totalCount = 0;
@ -137,16 +71,16 @@ class LibrarySearchModel with LibrarySearchModelMappable {
bool get showPlayButtons { bool get showPlayButtons {
if (totalItemCount == 0) return false; if (totalItemCount == 0) return false;
return types.included.isEmpty || return filters.types.included.isEmpty ||
types.included.containsAny( filters.types.included.containsAny(
{...FladderItemType.playable, FladderItemType.folder}, {...FladderItemType.playable, FladderItemType.folder},
); );
} }
bool get showGalleryButtons { bool get showGalleryButtons {
if (totalItemCount == 0) return false; if (totalItemCount == 0) return false;
return types.included.isEmpty || return filters.types.included.isEmpty ||
types.included.containsAny( filters.types.included.containsAny(
{...FladderItemType.galleryItem, FladderItemType.photoAlbum, FladderItemType.folder}, {...FladderItemType.galleryItem, FladderItemType.photoAlbum, FladderItemType.folder},
); );
} }
@ -170,55 +104,8 @@ class LibrarySearchModel with LibrarySearchModelMappable {
LibrarySearchModel setFiltersToDefault() { LibrarySearchModel setFiltersToDefault() {
return copyWith( return copyWith(
genres: const {},
tags: const {},
officialRatings: const {},
years: const {},
searchQuery: '', searchQuery: '',
favourites: false, filters: const LibraryFilterModel(),
recursive: false,
studios: const {},
hideEmptyShows: true,
); );
} }
@override
bool operator ==(covariant LibrarySearchModel other) {
if (identical(this, other)) return true;
return other.searchQuery == searchQuery &&
listEquals(other.folderOverwrite, folderOverwrite) &&
mapEquals(other.views, views) &&
mapEquals(other.filters, filters) &&
mapEquals(other.genres, genres) &&
mapEquals(other.studios, studios) &&
mapEquals(other.tags, tags) &&
mapEquals(other.years, years) &&
mapEquals(other.officialRatings, officialRatings) &&
mapEquals(other.types, types) &&
other.sortingOption == sortingOption &&
other.sortOrder == sortOrder &&
other.favourites == favourites &&
other.recursive == recursive;
}
@override
int get hashCode {
return searchQuery.hashCode ^
folderOverwrite.hashCode ^
views.hashCode ^
posters.hashCode ^
selectedPosters.hashCode ^
filters.hashCode ^
genres.hashCode ^
studios.hashCode ^
tags.hashCode ^
years.hashCode ^
officialRatings.hashCode ^
types.hashCode ^
sortingOption.hashCode ^
sortOrder.hashCode ^
favourites.hashCode ^
recursive.hashCode;
}
} }

View file

@ -0,0 +1,627 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// 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_search_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$LibrarySearchModel implements DiagnosticableTreeMixin {
bool get loading;
bool get selecteMode;
List<ItemBaseModel> get folderOverwrite;
String get searchQuery;
Map<ViewModel, bool> get views;
List<ItemBaseModel> get posters;
List<ItemBaseModel> get selectedPosters;
LibraryFilterModel get filters;
Map<String, int> get lastIndices;
Map<String, int> get libraryItemCounts;
bool get fetchingItems;
/// Create a copy of LibrarySearchModel
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$LibrarySearchModelCopyWith<LibrarySearchModel> get copyWith =>
_$LibrarySearchModelCopyWithImpl<LibrarySearchModel>(
this as LibrarySearchModel, _$identity);
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'LibrarySearchModel'))
..add(DiagnosticsProperty('loading', loading))
..add(DiagnosticsProperty('selecteMode', selecteMode))
..add(DiagnosticsProperty('folderOverwrite', folderOverwrite))
..add(DiagnosticsProperty('searchQuery', searchQuery))
..add(DiagnosticsProperty('views', views))
..add(DiagnosticsProperty('posters', posters))
..add(DiagnosticsProperty('selectedPosters', selectedPosters))
..add(DiagnosticsProperty('filters', filters))
..add(DiagnosticsProperty('lastIndices', lastIndices))
..add(DiagnosticsProperty('libraryItemCounts', libraryItemCounts))
..add(DiagnosticsProperty('fetchingItems', fetchingItems));
}
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'LibrarySearchModel(loading: $loading, selecteMode: $selecteMode, folderOverwrite: $folderOverwrite, searchQuery: $searchQuery, views: $views, posters: $posters, selectedPosters: $selectedPosters, filters: $filters, lastIndices: $lastIndices, libraryItemCounts: $libraryItemCounts, fetchingItems: $fetchingItems)';
}
}
/// @nodoc
abstract mixin class $LibrarySearchModelCopyWith<$Res> {
factory $LibrarySearchModelCopyWith(
LibrarySearchModel value, $Res Function(LibrarySearchModel) _then) =
_$LibrarySearchModelCopyWithImpl;
@useResult
$Res call(
{bool loading,
bool selecteMode,
List<ItemBaseModel> folderOverwrite,
String searchQuery,
Map<ViewModel, bool> views,
List<ItemBaseModel> posters,
List<ItemBaseModel> selectedPosters,
LibraryFilterModel filters,
Map<String, int> lastIndices,
Map<String, int> libraryItemCounts,
bool fetchingItems});
$LibraryFilterModelCopyWith<$Res> get filters;
}
/// @nodoc
class _$LibrarySearchModelCopyWithImpl<$Res>
implements $LibrarySearchModelCopyWith<$Res> {
_$LibrarySearchModelCopyWithImpl(this._self, this._then);
final LibrarySearchModel _self;
final $Res Function(LibrarySearchModel) _then;
/// Create a copy of LibrarySearchModel
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? loading = null,
Object? selecteMode = null,
Object? folderOverwrite = null,
Object? searchQuery = null,
Object? views = null,
Object? posters = null,
Object? selectedPosters = null,
Object? filters = null,
Object? lastIndices = null,
Object? libraryItemCounts = null,
Object? fetchingItems = null,
}) {
return _then(_self.copyWith(
loading: null == loading
? _self.loading
: loading // ignore: cast_nullable_to_non_nullable
as bool,
selecteMode: null == selecteMode
? _self.selecteMode
: selecteMode // ignore: cast_nullable_to_non_nullable
as bool,
folderOverwrite: null == folderOverwrite
? _self.folderOverwrite
: folderOverwrite // ignore: cast_nullable_to_non_nullable
as List<ItemBaseModel>,
searchQuery: null == searchQuery
? _self.searchQuery
: searchQuery // ignore: cast_nullable_to_non_nullable
as String,
views: null == views
? _self.views
: views // ignore: cast_nullable_to_non_nullable
as Map<ViewModel, bool>,
posters: null == posters
? _self.posters
: posters // ignore: cast_nullable_to_non_nullable
as List<ItemBaseModel>,
selectedPosters: null == selectedPosters
? _self.selectedPosters
: selectedPosters // ignore: cast_nullable_to_non_nullable
as List<ItemBaseModel>,
filters: null == filters
? _self.filters
: filters // ignore: cast_nullable_to_non_nullable
as LibraryFilterModel,
lastIndices: null == lastIndices
? _self.lastIndices
: lastIndices // ignore: cast_nullable_to_non_nullable
as Map<String, int>,
libraryItemCounts: null == libraryItemCounts
? _self.libraryItemCounts
: libraryItemCounts // ignore: cast_nullable_to_non_nullable
as Map<String, int>,
fetchingItems: null == fetchingItems
? _self.fetchingItems
: fetchingItems // ignore: cast_nullable_to_non_nullable
as bool,
));
}
/// Create a copy of LibrarySearchModel
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$LibraryFilterModelCopyWith<$Res> get filters {
return $LibraryFilterModelCopyWith<$Res>(_self.filters, (value) {
return _then(_self.copyWith(filters: value));
});
}
}
/// Adds pattern-matching-related methods to [LibrarySearchModel].
extension LibrarySearchModelPatterns on LibrarySearchModel {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>(
TResult Function(_LibrarySearchModel value)? $default, {
required TResult orElse(),
}) {
final _that = this;
switch (_that) {
case _LibrarySearchModel() when $default != null:
return $default(_that);
case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs
TResult map<TResult extends Object?>(
TResult Function(_LibrarySearchModel value) $default,
) {
final _that = this;
switch (_that) {
case _LibrarySearchModel():
return $default(_that);
case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult? Function(_LibrarySearchModel value)? $default,
) {
final _that = this;
switch (_that) {
case _LibrarySearchModel() when $default != null:
return $default(_that);
case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>(
TResult Function(
bool loading,
bool selecteMode,
List<ItemBaseModel> folderOverwrite,
String searchQuery,
Map<ViewModel, bool> views,
List<ItemBaseModel> posters,
List<ItemBaseModel> selectedPosters,
LibraryFilterModel filters,
Map<String, int> lastIndices,
Map<String, int> libraryItemCounts,
bool fetchingItems)?
$default, {
required TResult orElse(),
}) {
final _that = this;
switch (_that) {
case _LibrarySearchModel() when $default != null:
return $default(
_that.loading,
_that.selecteMode,
_that.folderOverwrite,
_that.searchQuery,
_that.views,
_that.posters,
_that.selectedPosters,
_that.filters,
_that.lastIndices,
_that.libraryItemCounts,
_that.fetchingItems);
case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs
TResult when<TResult extends Object?>(
TResult Function(
bool loading,
bool selecteMode,
List<ItemBaseModel> folderOverwrite,
String searchQuery,
Map<ViewModel, bool> views,
List<ItemBaseModel> posters,
List<ItemBaseModel> selectedPosters,
LibraryFilterModel filters,
Map<String, int> lastIndices,
Map<String, int> libraryItemCounts,
bool fetchingItems)
$default,
) {
final _that = this;
switch (_that) {
case _LibrarySearchModel():
return $default(
_that.loading,
_that.selecteMode,
_that.folderOverwrite,
_that.searchQuery,
_that.views,
_that.posters,
_that.selectedPosters,
_that.filters,
_that.lastIndices,
_that.libraryItemCounts,
_that.fetchingItems);
case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult? Function(
bool loading,
bool selecteMode,
List<ItemBaseModel> folderOverwrite,
String searchQuery,
Map<ViewModel, bool> views,
List<ItemBaseModel> posters,
List<ItemBaseModel> selectedPosters,
LibraryFilterModel filters,
Map<String, int> lastIndices,
Map<String, int> libraryItemCounts,
bool fetchingItems)?
$default,
) {
final _that = this;
switch (_that) {
case _LibrarySearchModel() when $default != null:
return $default(
_that.loading,
_that.selecteMode,
_that.folderOverwrite,
_that.searchQuery,
_that.views,
_that.posters,
_that.selectedPosters,
_that.filters,
_that.lastIndices,
_that.libraryItemCounts,
_that.fetchingItems);
case _:
return null;
}
}
}
/// @nodoc
class _LibrarySearchModel
with DiagnosticableTreeMixin
implements LibrarySearchModel {
const _LibrarySearchModel(
{this.loading = false,
this.selecteMode = false,
final List<ItemBaseModel> folderOverwrite = const <ItemBaseModel>[],
this.searchQuery = "",
final Map<ViewModel, bool> views = const <ViewModel, bool>{},
final List<ItemBaseModel> posters = const <ItemBaseModel>[],
final List<ItemBaseModel> selectedPosters = const <ItemBaseModel>[],
this.filters = const LibraryFilterModel(),
final Map<String, int> lastIndices = const <String, int>{},
final Map<String, int> libraryItemCounts = const <String, int>{},
this.fetchingItems = false})
: _folderOverwrite = folderOverwrite,
_views = views,
_posters = posters,
_selectedPosters = selectedPosters,
_lastIndices = lastIndices,
_libraryItemCounts = libraryItemCounts;
@override
@JsonKey()
final bool loading;
@override
@JsonKey()
final bool selecteMode;
final List<ItemBaseModel> _folderOverwrite;
@override
@JsonKey()
List<ItemBaseModel> get folderOverwrite {
if (_folderOverwrite is EqualUnmodifiableListView) return _folderOverwrite;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_folderOverwrite);
}
@override
@JsonKey()
final String searchQuery;
final Map<ViewModel, bool> _views;
@override
@JsonKey()
Map<ViewModel, bool> get views {
if (_views is EqualUnmodifiableMapView) return _views;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_views);
}
final List<ItemBaseModel> _posters;
@override
@JsonKey()
List<ItemBaseModel> get posters {
if (_posters is EqualUnmodifiableListView) return _posters;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_posters);
}
final List<ItemBaseModel> _selectedPosters;
@override
@JsonKey()
List<ItemBaseModel> get selectedPosters {
if (_selectedPosters is EqualUnmodifiableListView) return _selectedPosters;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_selectedPosters);
}
@override
@JsonKey()
final LibraryFilterModel filters;
final Map<String, int> _lastIndices;
@override
@JsonKey()
Map<String, int> get lastIndices {
if (_lastIndices is EqualUnmodifiableMapView) return _lastIndices;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_lastIndices);
}
final Map<String, int> _libraryItemCounts;
@override
@JsonKey()
Map<String, int> get libraryItemCounts {
if (_libraryItemCounts is EqualUnmodifiableMapView)
return _libraryItemCounts;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_libraryItemCounts);
}
@override
@JsonKey()
final bool fetchingItems;
/// Create a copy of LibrarySearchModel
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$LibrarySearchModelCopyWith<_LibrarySearchModel> get copyWith =>
__$LibrarySearchModelCopyWithImpl<_LibrarySearchModel>(this, _$identity);
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'LibrarySearchModel'))
..add(DiagnosticsProperty('loading', loading))
..add(DiagnosticsProperty('selecteMode', selecteMode))
..add(DiagnosticsProperty('folderOverwrite', folderOverwrite))
..add(DiagnosticsProperty('searchQuery', searchQuery))
..add(DiagnosticsProperty('views', views))
..add(DiagnosticsProperty('posters', posters))
..add(DiagnosticsProperty('selectedPosters', selectedPosters))
..add(DiagnosticsProperty('filters', filters))
..add(DiagnosticsProperty('lastIndices', lastIndices))
..add(DiagnosticsProperty('libraryItemCounts', libraryItemCounts))
..add(DiagnosticsProperty('fetchingItems', fetchingItems));
}
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'LibrarySearchModel(loading: $loading, selecteMode: $selecteMode, folderOverwrite: $folderOverwrite, searchQuery: $searchQuery, views: $views, posters: $posters, selectedPosters: $selectedPosters, filters: $filters, lastIndices: $lastIndices, libraryItemCounts: $libraryItemCounts, fetchingItems: $fetchingItems)';
}
}
/// @nodoc
abstract mixin class _$LibrarySearchModelCopyWith<$Res>
implements $LibrarySearchModelCopyWith<$Res> {
factory _$LibrarySearchModelCopyWith(
_LibrarySearchModel value, $Res Function(_LibrarySearchModel) _then) =
__$LibrarySearchModelCopyWithImpl;
@override
@useResult
$Res call(
{bool loading,
bool selecteMode,
List<ItemBaseModel> folderOverwrite,
String searchQuery,
Map<ViewModel, bool> views,
List<ItemBaseModel> posters,
List<ItemBaseModel> selectedPosters,
LibraryFilterModel filters,
Map<String, int> lastIndices,
Map<String, int> libraryItemCounts,
bool fetchingItems});
@override
$LibraryFilterModelCopyWith<$Res> get filters;
}
/// @nodoc
class __$LibrarySearchModelCopyWithImpl<$Res>
implements _$LibrarySearchModelCopyWith<$Res> {
__$LibrarySearchModelCopyWithImpl(this._self, this._then);
final _LibrarySearchModel _self;
final $Res Function(_LibrarySearchModel) _then;
/// Create a copy of LibrarySearchModel
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$Res call({
Object? loading = null,
Object? selecteMode = null,
Object? folderOverwrite = null,
Object? searchQuery = null,
Object? views = null,
Object? posters = null,
Object? selectedPosters = null,
Object? filters = null,
Object? lastIndices = null,
Object? libraryItemCounts = null,
Object? fetchingItems = null,
}) {
return _then(_LibrarySearchModel(
loading: null == loading
? _self.loading
: loading // ignore: cast_nullable_to_non_nullable
as bool,
selecteMode: null == selecteMode
? _self.selecteMode
: selecteMode // ignore: cast_nullable_to_non_nullable
as bool,
folderOverwrite: null == folderOverwrite
? _self._folderOverwrite
: folderOverwrite // ignore: cast_nullable_to_non_nullable
as List<ItemBaseModel>,
searchQuery: null == searchQuery
? _self.searchQuery
: searchQuery // ignore: cast_nullable_to_non_nullable
as String,
views: null == views
? _self._views
: views // ignore: cast_nullable_to_non_nullable
as Map<ViewModel, bool>,
posters: null == posters
? _self._posters
: posters // ignore: cast_nullable_to_non_nullable
as List<ItemBaseModel>,
selectedPosters: null == selectedPosters
? _self._selectedPosters
: selectedPosters // ignore: cast_nullable_to_non_nullable
as List<ItemBaseModel>,
filters: null == filters
? _self.filters
: filters // ignore: cast_nullable_to_non_nullable
as LibraryFilterModel,
lastIndices: null == lastIndices
? _self._lastIndices
: lastIndices // ignore: cast_nullable_to_non_nullable
as Map<String, int>,
libraryItemCounts: null == libraryItemCounts
? _self._libraryItemCounts
: libraryItemCounts // ignore: cast_nullable_to_non_nullable
as Map<String, int>,
fetchingItems: null == fetchingItems
? _self.fetchingItems
: fetchingItems // ignore: cast_nullable_to_non_nullable
as bool,
));
}
/// Create a copy of LibrarySearchModel
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$LibraryFilterModelCopyWith<$Res> get filters {
return $LibraryFilterModelCopyWith<$Res>(_self.filters, (value) {
return _then(_self.copyWith(filters: value));
});
}
}
// dart format on

View file

@ -1,395 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member
// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter
part of 'library_search_model.dart';
class LibrarySearchModelMapper extends ClassMapperBase<LibrarySearchModel> {
LibrarySearchModelMapper._();
static LibrarySearchModelMapper? _instance;
static LibrarySearchModelMapper ensureInitialized() {
if (_instance == null) {
MapperContainer.globals.use(_instance = LibrarySearchModelMapper._());
ItemBaseModelMapper.ensureInitialized();
}
return _instance!;
}
@override
final String id = 'LibrarySearchModel';
static bool _$loading(LibrarySearchModel v) => v.loading;
static const Field<LibrarySearchModel, bool> _f$loading =
Field('loading', _$loading, opt: true, def: false);
static bool _$selecteMode(LibrarySearchModel v) => v.selecteMode;
static const Field<LibrarySearchModel, bool> _f$selecteMode =
Field('selecteMode', _$selecteMode, opt: true, def: false);
static List<ItemBaseModel> _$folderOverwrite(LibrarySearchModel v) =>
v.folderOverwrite;
static const Field<LibrarySearchModel, List<ItemBaseModel>>
_f$folderOverwrite =
Field('folderOverwrite', _$folderOverwrite, opt: true, def: const []);
static String _$searchQuery(LibrarySearchModel v) => v.searchQuery;
static const Field<LibrarySearchModel, String> _f$searchQuery =
Field('searchQuery', _$searchQuery, opt: true, def: "");
static Map<ViewModel, bool> _$views(LibrarySearchModel v) => v.views;
static const Field<LibrarySearchModel, Map<ViewModel, bool>> _f$views =
Field('views', _$views, opt: true, def: const {});
static List<ItemBaseModel> _$posters(LibrarySearchModel v) => v.posters;
static const Field<LibrarySearchModel, List<ItemBaseModel>> _f$posters =
Field('posters', _$posters, opt: true, def: const []);
static List<ItemBaseModel> _$selectedPosters(LibrarySearchModel v) =>
v.selectedPosters;
static const Field<LibrarySearchModel, List<ItemBaseModel>>
_f$selectedPosters =
Field('selectedPosters', _$selectedPosters, opt: true, def: const []);
static Map<ItemFilter, bool> _$filters(LibrarySearchModel v) => v.filters;
static const Field<LibrarySearchModel, Map<ItemFilter, bool>> _f$filters =
Field('filters', _$filters, opt: true, def: const {
ItemFilter.isplayed: false,
ItemFilter.isunplayed: false,
ItemFilter.isresumable: false
});
static Map<String, bool> _$genres(LibrarySearchModel v) => v.genres;
static const Field<LibrarySearchModel, Map<String, bool>> _f$genres =
Field('genres', _$genres, opt: true, def: const {});
static Map<Studio, bool> _$studios(LibrarySearchModel v) => v.studios;
static const Field<LibrarySearchModel, Map<Studio, bool>> _f$studios =
Field('studios', _$studios, opt: true, def: const {});
static Map<String, bool> _$tags(LibrarySearchModel v) => v.tags;
static const Field<LibrarySearchModel, Map<String, bool>> _f$tags =
Field('tags', _$tags, opt: true, def: const {});
static Map<int, bool> _$years(LibrarySearchModel v) => v.years;
static const Field<LibrarySearchModel, Map<int, bool>> _f$years =
Field('years', _$years, opt: true, def: const {});
static Map<String, bool> _$officialRatings(LibrarySearchModel v) =>
v.officialRatings;
static const Field<LibrarySearchModel, Map<String, bool>> _f$officialRatings =
Field('officialRatings', _$officialRatings, opt: true, def: const {});
static Map<FladderItemType, bool> _$types(LibrarySearchModel v) => v.types;
static const Field<LibrarySearchModel, Map<FladderItemType, bool>> _f$types =
Field('types', _$types, opt: true, def: const {
FladderItemType.audio: false,
FladderItemType.boxset: false,
FladderItemType.book: false,
FladderItemType.collectionFolder: false,
FladderItemType.episode: false,
FladderItemType.folder: false,
FladderItemType.movie: true,
FladderItemType.musicAlbum: false,
FladderItemType.musicVideo: false,
FladderItemType.photo: false,
FladderItemType.person: false,
FladderItemType.photoAlbum: false,
FladderItemType.series: true,
FladderItemType.video: true
});
static bool _$favourites(LibrarySearchModel v) => v.favourites;
static const Field<LibrarySearchModel, bool> _f$favourites =
Field('favourites', _$favourites, opt: true, def: false);
static SortingOptions _$sortingOption(LibrarySearchModel v) =>
v.sortingOption;
static const Field<LibrarySearchModel, SortingOptions> _f$sortingOption =
Field('sortingOption', _$sortingOption,
opt: true, def: SortingOptions.sortName);
static SortingOrder _$sortOrder(LibrarySearchModel v) => v.sortOrder;
static const Field<LibrarySearchModel, SortingOrder> _f$sortOrder =
Field('sortOrder', _$sortOrder, opt: true, def: SortingOrder.ascending);
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);
static GroupBy _$groupBy(LibrarySearchModel v) => v.groupBy;
static const Field<LibrarySearchModel, GroupBy> _f$groupBy =
Field('groupBy', _$groupBy, opt: true, def: GroupBy.none);
static Map<String, int> _$lastIndices(LibrarySearchModel v) => v.lastIndices;
static const Field<LibrarySearchModel, Map<String, int>> _f$lastIndices =
Field('lastIndices', _$lastIndices, opt: true, def: const {});
static Map<String, int> _$libraryItemCounts(LibrarySearchModel v) =>
v.libraryItemCounts;
static const Field<LibrarySearchModel, Map<String, int>>
_f$libraryItemCounts =
Field('libraryItemCounts', _$libraryItemCounts, opt: true, def: const {});
static bool _$fetchingItems(LibrarySearchModel v) => v.fetchingItems;
static const Field<LibrarySearchModel, bool> _f$fetchingItems =
Field('fetchingItems', _$fetchingItems, opt: true, def: false);
@override
final MappableFields<LibrarySearchModel> fields = const {
#loading: _f$loading,
#selecteMode: _f$selecteMode,
#folderOverwrite: _f$folderOverwrite,
#searchQuery: _f$searchQuery,
#views: _f$views,
#posters: _f$posters,
#selectedPosters: _f$selectedPosters,
#filters: _f$filters,
#genres: _f$genres,
#studios: _f$studios,
#tags: _f$tags,
#years: _f$years,
#officialRatings: _f$officialRatings,
#types: _f$types,
#favourites: _f$favourites,
#sortingOption: _f$sortingOption,
#sortOrder: _f$sortOrder,
#hideEmptyShows: _f$hideEmptyShows,
#recursive: _f$recursive,
#groupBy: _f$groupBy,
#lastIndices: _f$lastIndices,
#libraryItemCounts: _f$libraryItemCounts,
#fetchingItems: _f$fetchingItems,
};
@override
final bool ignoreNull = true;
static LibrarySearchModel _instantiate(DecodingData data) {
return LibrarySearchModel(
loading: data.dec(_f$loading),
selecteMode: data.dec(_f$selecteMode),
folderOverwrite: data.dec(_f$folderOverwrite),
searchQuery: data.dec(_f$searchQuery),
views: data.dec(_f$views),
posters: data.dec(_f$posters),
selectedPosters: data.dec(_f$selectedPosters),
filters: data.dec(_f$filters),
genres: data.dec(_f$genres),
studios: data.dec(_f$studios),
tags: data.dec(_f$tags),
years: data.dec(_f$years),
officialRatings: data.dec(_f$officialRatings),
types: data.dec(_f$types),
favourites: data.dec(_f$favourites),
sortingOption: data.dec(_f$sortingOption),
sortOrder: data.dec(_f$sortOrder),
hideEmptyShows: data.dec(_f$hideEmptyShows),
recursive: data.dec(_f$recursive),
groupBy: data.dec(_f$groupBy),
lastIndices: data.dec(_f$lastIndices),
libraryItemCounts: data.dec(_f$libraryItemCounts),
fetchingItems: data.dec(_f$fetchingItems));
}
@override
final Function instantiate = _instantiate;
}
mixin LibrarySearchModelMappable {
LibrarySearchModelCopyWith<LibrarySearchModel, LibrarySearchModel,
LibrarySearchModel>
get copyWith => _LibrarySearchModelCopyWithImpl<LibrarySearchModel,
LibrarySearchModel>(this as LibrarySearchModel, $identity, $identity);
}
extension LibrarySearchModelValueCopy<$R, $Out>
on ObjectCopyWith<$R, LibrarySearchModel, $Out> {
LibrarySearchModelCopyWith<$R, LibrarySearchModel, $Out>
get $asLibrarySearchModel => $base.as(
(v, t, t2) => _LibrarySearchModelCopyWithImpl<$R, $Out>(v, t, t2));
}
abstract class LibrarySearchModelCopyWith<$R, $In extends LibrarySearchModel,
$Out> implements ClassCopyWith<$R, $In, $Out> {
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
get folderOverwrite;
MapCopyWith<$R, ViewModel, bool, ObjectCopyWith<$R, bool, bool>> get views;
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>> get posters;
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
get selectedPosters;
MapCopyWith<$R, ItemFilter, bool, ObjectCopyWith<$R, bool, bool>> get filters;
MapCopyWith<$R, String, bool, ObjectCopyWith<$R, bool, bool>> get genres;
MapCopyWith<$R, Studio, bool, ObjectCopyWith<$R, bool, bool>> get studios;
MapCopyWith<$R, String, bool, ObjectCopyWith<$R, bool, bool>> get tags;
MapCopyWith<$R, int, bool, ObjectCopyWith<$R, bool, bool>> get years;
MapCopyWith<$R, String, bool, ObjectCopyWith<$R, bool, bool>>
get officialRatings;
MapCopyWith<$R, FladderItemType, bool, ObjectCopyWith<$R, bool, bool>>
get types;
MapCopyWith<$R, String, int, ObjectCopyWith<$R, int, int>> get lastIndices;
MapCopyWith<$R, String, int, ObjectCopyWith<$R, int, int>>
get libraryItemCounts;
$R call(
{bool? loading,
bool? selecteMode,
List<ItemBaseModel>? folderOverwrite,
String? searchQuery,
Map<ViewModel, bool>? views,
List<ItemBaseModel>? posters,
List<ItemBaseModel>? selectedPosters,
Map<ItemFilter, bool>? filters,
Map<String, bool>? genres,
Map<Studio, bool>? studios,
Map<String, bool>? tags,
Map<int, bool>? years,
Map<String, bool>? officialRatings,
Map<FladderItemType, bool>? types,
bool? favourites,
SortingOptions? sortingOption,
SortingOrder? sortOrder,
bool? hideEmptyShows,
bool? recursive,
GroupBy? groupBy,
Map<String, int>? lastIndices,
Map<String, int>? libraryItemCounts,
bool? fetchingItems});
LibrarySearchModelCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(
Then<$Out2, $R2> t);
}
class _LibrarySearchModelCopyWithImpl<$R, $Out>
extends ClassCopyWithBase<$R, LibrarySearchModel, $Out>
implements LibrarySearchModelCopyWith<$R, LibrarySearchModel, $Out> {
_LibrarySearchModelCopyWithImpl(super.value, super.then, super.then2);
@override
late final ClassMapperBase<LibrarySearchModel> $mapper =
LibrarySearchModelMapper.ensureInitialized();
@override
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
get folderOverwrite => ListCopyWith($value.folderOverwrite,
(v, t) => v.copyWith.$chain(t), (v) => call(folderOverwrite: v));
@override
MapCopyWith<$R, ViewModel, bool, ObjectCopyWith<$R, bool, bool>> get views =>
MapCopyWith($value.views, (v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(views: v));
@override
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
get posters => ListCopyWith($value.posters,
(v, t) => v.copyWith.$chain(t), (v) => call(posters: v));
@override
ListCopyWith<$R, ItemBaseModel,
ItemBaseModelCopyWith<$R, ItemBaseModel, ItemBaseModel>>
get selectedPosters => ListCopyWith($value.selectedPosters,
(v, t) => v.copyWith.$chain(t), (v) => call(selectedPosters: v));
@override
MapCopyWith<$R, ItemFilter, bool, ObjectCopyWith<$R, bool, bool>>
get filters => MapCopyWith($value.filters,
(v, t) => ObjectCopyWith(v, $identity, t), (v) => call(filters: v));
@override
MapCopyWith<$R, String, bool, ObjectCopyWith<$R, bool, bool>> get genres =>
MapCopyWith($value.genres, (v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(genres: v));
@override
MapCopyWith<$R, Studio, bool, ObjectCopyWith<$R, bool, bool>> get studios =>
MapCopyWith($value.studios, (v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(studios: v));
@override
MapCopyWith<$R, String, bool, ObjectCopyWith<$R, bool, bool>> get tags =>
MapCopyWith($value.tags, (v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(tags: v));
@override
MapCopyWith<$R, int, bool, ObjectCopyWith<$R, bool, bool>> get years =>
MapCopyWith($value.years, (v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(years: v));
@override
MapCopyWith<$R, String, bool, ObjectCopyWith<$R, bool, bool>>
get officialRatings => MapCopyWith(
$value.officialRatings,
(v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(officialRatings: v));
@override
MapCopyWith<$R, FladderItemType, bool, ObjectCopyWith<$R, bool, bool>>
get types => MapCopyWith($value.types,
(v, t) => ObjectCopyWith(v, $identity, t), (v) => call(types: v));
@override
MapCopyWith<$R, String, int, ObjectCopyWith<$R, int, int>> get lastIndices =>
MapCopyWith($value.lastIndices, (v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(lastIndices: v));
@override
MapCopyWith<$R, String, int, ObjectCopyWith<$R, int, int>>
get libraryItemCounts => MapCopyWith(
$value.libraryItemCounts,
(v, t) => ObjectCopyWith(v, $identity, t),
(v) => call(libraryItemCounts: v));
@override
$R call(
{bool? loading,
bool? selecteMode,
List<ItemBaseModel>? folderOverwrite,
String? searchQuery,
Map<ViewModel, bool>? views,
List<ItemBaseModel>? posters,
List<ItemBaseModel>? selectedPosters,
Map<ItemFilter, bool>? filters,
Map<String, bool>? genres,
Map<Studio, bool>? studios,
Map<String, bool>? tags,
Map<int, bool>? years,
Map<String, bool>? officialRatings,
Map<FladderItemType, bool>? types,
bool? favourites,
SortingOptions? sortingOption,
SortingOrder? sortOrder,
bool? hideEmptyShows,
bool? recursive,
GroupBy? groupBy,
Map<String, int>? lastIndices,
Map<String, int>? libraryItemCounts,
bool? fetchingItems}) =>
$apply(FieldCopyWithData({
if (loading != null) #loading: loading,
if (selecteMode != null) #selecteMode: selecteMode,
if (folderOverwrite != null) #folderOverwrite: folderOverwrite,
if (searchQuery != null) #searchQuery: searchQuery,
if (views != null) #views: views,
if (posters != null) #posters: posters,
if (selectedPosters != null) #selectedPosters: selectedPosters,
if (filters != null) #filters: filters,
if (genres != null) #genres: genres,
if (studios != null) #studios: studios,
if (tags != null) #tags: tags,
if (years != null) #years: years,
if (officialRatings != null) #officialRatings: officialRatings,
if (types != null) #types: types,
if (favourites != null) #favourites: favourites,
if (sortingOption != null) #sortingOption: sortingOption,
if (sortOrder != null) #sortOrder: sortOrder,
if (hideEmptyShows != null) #hideEmptyShows: hideEmptyShows,
if (recursive != null) #recursive: recursive,
if (groupBy != null) #groupBy: groupBy,
if (lastIndices != null) #lastIndices: lastIndices,
if (libraryItemCounts != null) #libraryItemCounts: libraryItemCounts,
if (fetchingItems != null) #fetchingItems: fetchingItems
}));
@override
LibrarySearchModel $make(CopyWithData data) => LibrarySearchModel(
loading: data.get(#loading, or: $value.loading),
selecteMode: data.get(#selecteMode, or: $value.selecteMode),
folderOverwrite: data.get(#folderOverwrite, or: $value.folderOverwrite),
searchQuery: data.get(#searchQuery, or: $value.searchQuery),
views: data.get(#views, or: $value.views),
posters: data.get(#posters, or: $value.posters),
selectedPosters: data.get(#selectedPosters, or: $value.selectedPosters),
filters: data.get(#filters, or: $value.filters),
genres: data.get(#genres, or: $value.genres),
studios: data.get(#studios, or: $value.studios),
tags: data.get(#tags, or: $value.tags),
years: data.get(#years, or: $value.years),
officialRatings: data.get(#officialRatings, or: $value.officialRatings),
types: data.get(#types, or: $value.types),
favourites: data.get(#favourites, or: $value.favourites),
sortingOption: data.get(#sortingOption, or: $value.sortingOption),
sortOrder: data.get(#sortOrder, or: $value.sortOrder),
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),
libraryItemCounts:
data.get(#libraryItemCounts, or: $value.libraryItemCounts),
fetchingItems: data.get(#fetchingItems, or: $value.fetchingItems));
@override
LibrarySearchModelCopyWith<$R2, LibrarySearchModel, $Out2> $chain<$R2, $Out2>(
Then<$Out2, $R2> t) =>
_LibrarySearchModelCopyWithImpl<$R2, $Out2>($value, $cast, t);
}

View file

@ -44,23 +44,33 @@ class FavouritesNotifier extends StateNotifier<FavouritesModel> {
} }
Future<List<ItemBaseModel>?> _loadLibrary({ViewModel? viewModel}) async { Future<List<ItemBaseModel>?> _loadLibrary({ViewModel? viewModel}) async {
final response = await api.itemsGet( final kinds = [
parentId: viewModel?.id, BaseItemKind.movie,
isFavorite: true, BaseItemKind.episode,
limit: 10, BaseItemKind.series,
sortOrder: [SortOrder.ascending], BaseItemKind.video,
sortBy: [ItemSortBy.seriessortname, ItemSortBy.sortname, ItemSortBy.datelastcontentadded], BaseItemKind.photo,
); BaseItemKind.book,
final response2 = await api.itemsGet( BaseItemKind.photoalbum,
parentId: viewModel?.id, ];
final futures = kinds.map((kind) => fetchTypes(viewModel?.id, [kind])).toList();
final results = await Future.wait(futures);
return results.expand((list) => list).toList();
}
Future<List<ItemBaseModel>> fetchTypes(String? id, List<BaseItemKind>? includeItemTypes) async {
return (await api.itemsGet(
parentId: id,
isFavorite: true, isFavorite: true,
recursive: true, recursive: true,
limit: 10, limit: 15,
includeItemTypes: [BaseItemKind.photo, BaseItemKind.episode, BaseItemKind.video, BaseItemKind.collectionfolder], includeItemTypes: includeItemTypes,
sortOrder: [SortOrder.ascending], sortOrder: [SortOrder.ascending],
sortBy: [ItemSortBy.seriessortname, ItemSortBy.sortname, ItemSortBy.datelastcontentadded], sortBy: [ItemSortBy.seriessortname, ItemSortBy.sortname, ItemSortBy.datelastcontentadded],
); ))
return [...?response.body?.items, ...?response2.body?.items]; .body
?.items ??
[];
} }
Future<Response<List<ItemBaseModel>>?> _fetchPeople() async { Future<Response<List<ItemBaseModel>>?> _fetchPeople() async {

View file

@ -10,7 +10,7 @@ class LibraryFilters extends _$LibraryFilters {
@override @override
List<LibraryFiltersModel> build(List<String> ids) => ref.watch( List<LibraryFiltersModel> build(List<String> ids) => ref.watch(
userProvider userProvider
.select((value) => (value?.savedFilters ?? []).where((element) => element.containsSameIds(ids)).toList()), .select((value) => (value?.libraryFilters ?? []).where((element) => element.containsSameIds(ids)).toList()),
); );
void removeFilter(LibraryFiltersModel model) => ref.read(userProvider.notifier).removeFilter(model); void removeFilter(LibraryFiltersModel model) => ref.read(userProvider.notifier).removeFilter(model);

View file

@ -6,7 +6,7 @@ part of 'library_filters_provider.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$libraryFiltersHash() => r'fd98699d8d7c1db6daefa6e53d5d90f989a8f776'; String _$libraryFiltersHash() => r'2fae16f2bf8f9aee08b047d8615a47ee794b30b0';
/// Copied from Dart SDK /// Copied from Dart SDK
class _SystemHash { class _SystemHash {

View file

@ -66,7 +66,8 @@ class LibraryScreen extends _$LibraryScreen {
Future<void> fetchAllLibraries() async { Future<void> fetchAllLibraries() async {
final views = await ref.read(viewsProvider.notifier).fetchViews(); final views = await ref.read(viewsProvider.notifier).fetchViews();
state = state.copyWith(views: views?.views ?? []); state = state.copyWith(
views: views?.views.where((element) => element.collectionType != CollectionType.folders).toList() ?? []);
if (state.views.isEmpty) return; if (state.views.isEmpty) return;
final viewModel = state.selectedViewModel ?? state.views.firstOrNull; final viewModel = state.selectedViewModel ?? state.views.firstOrNull;
if (viewModel == null) return; if (viewModel == null) return;

View file

@ -13,6 +13,7 @@ import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/folder_model.dart'; import 'package:fladder/models/items/folder_model.dart';
import 'package:fladder/models/items/item_shared_models.dart'; import 'package:fladder/models/items/item_shared_models.dart';
import 'package:fladder/models/items/photos_model.dart'; import 'package:fladder/models/items/photos_model.dart';
import 'package:fladder/models/library_filter_model.dart';
import 'package:fladder/models/library_filters_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_model.dart';
import 'package:fladder/models/library_search/library_search_options.dart'; import 'package:fladder/models/library_search/library_search_options.dart';
@ -49,15 +50,14 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
set loading(bool loading) => state = state.copyWith(loading: loading); set loading(bool loading) => state = state.copyWith(loading: loading);
bool loadedFilters = false; bool loadedFilters = false;
bool wasInitialized = false;
bool get loading => state.loading; bool get loading => state.loading;
Future<void> initRefresh( Future<void> initRefresh(
List<String>? folderId, List<String>? folderId,
String? viewModelId, String? viewModelId,
bool? favourites, LibraryFilterModel filters,
SortingOrder? sortOrder,
SortingOptions? sortingOptions,
) async { ) async {
loading = true; loading = true;
state = state.resetLazyLoad(); state = state.resetLazyLoad();
@ -65,12 +65,23 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
if (folderId != null) { if (folderId != null) {
await loadFolders(folderId: folderId); await loadFolders(folderId: folderId);
} else { } else {
await loadViews(viewModelId, favourites, sortOrder, sortingOptions); await loadViews(viewModelId, filters);
} }
} }
await loadFilters();
if (!wasInitialized) {
wasInitialized = true;
state = state.copyWith(
filters: state.filters.copyWith(
types: state.filters.types.replaceMap(filters.types, enabledOnly: true),
genres: state.filters.genres.replaceMap(filters.genres, enabledOnly: true),
),
);
}
await loadFilters();
await loadMore(init: true); await loadMore(init: true);
loading = false; loading = false;
} }
@ -125,11 +136,11 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
List<ItemBaseModel> newPosters = results.nonNulls.expand((element) => element.items).toList(); List<ItemBaseModel> newPosters = results.nonNulls.expand((element) => element.items).toList();
if (state.views.included.length > 1) { if (state.views.included.length > 1) {
if (state.sortingOption == SortingOptions.random) { if (state.filters.sortingOption == SortingOptions.random) {
newPosters = newPosters.random(); newPosters = newPosters.random();
} else { } else {
newPosters = newPosters.sorted( newPosters = newPosters.sorted(
(a, b) => sortItems(a, b, state.sortingOption, state.sortOrder), (a, b) => sortItems(a, b, state.filters.sortingOption, state.filters.sortOrder),
); );
} }
} }
@ -145,7 +156,7 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
} else if (state.views.hasEnabled) { } else if (state.views.hasEnabled) {
await handleViewLoading(); await handleViewLoading();
} else { } else {
if (state.searchQuery.isEmpty && !state.favourites) { if (state.searchQuery.isEmpty && !state.filters.favourites) {
state = state.copyWith(posters: []); state = state.copyWith(posters: []);
} else { } else {
final response = await _loadLibrary(recursive: true); final response = await _loadLibrary(recursive: true);
@ -156,12 +167,9 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
loading = false; loading = false;
} }
//Pas viewmodel otherwise select first
Future<void> loadViews( Future<void> loadViews(
String? viewModelId, String? viewModelId,
bool? favourites, LibraryFilterModel filters,
SortingOrder? sortOrder,
SortingOptions? sortingOptions,
) async { ) async {
final response = await api.usersUserIdViewsGet(includeHidden: false); final response = await api.usersUserIdViewsGet(includeHidden: false);
final createdViews = response.body?.items?.map((e) => ViewModel.fromBodyDto(e, ref)); final createdViews = response.body?.items?.map((e) => ViewModel.fromBodyDto(e, ref));
@ -178,34 +186,28 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
views: views, views: views,
); );
if (sortOrder == null && sortingOptions == null && favourites == null) {
final findFavouriteFilter = ref final findFavouriteFilter = ref
.read(libraryFiltersProvider(views.included.map((e) => e.id).toList())) .read(libraryFiltersProvider(views.included.map((e) => e.id).toList()))
.firstWhereOrNull((element) => element.isFavourite); .firstWhereOrNull((element) => element.isFavourite);
if (findFavouriteFilter != null) { if (findFavouriteFilter != null) {
loadModel(findFavouriteFilter); loadModel(findFavouriteFilter.filter);
}
} else { } else {
state = state.copyWith( loadModel(filters);
sortOrder: sortOrder,
sortingOption: sortingOptions,
favourites: favourites,
);
} }
} }
Future<void> loadFolders({List<String>? folderId}) async { Future<void> loadFolders({List<String>? folderId}) async {
final response = await api.itemsGet( final response = await api.itemsGet(
ids: folderId ?? state.folderOverwrite.map((e) => e.id).toList(), ids: folderId ?? state.folderOverwrite.map((e) => e.id).toList(),
sortBy: state.sortingOption.toSortBy, sortBy: state.filters.sortingOption.toSortBy,
sortOrder: [state.sortOrder.sortOrder], sortOrder: [state.filters.sortOrder.sortOrder],
fields: [ fields: [
ItemFields.parentid, ItemFields.parentid,
ItemFields.primaryimageaspectratio, ItemFields.primaryimageaspectratio,
], ],
); );
state = state.copyWith(folderOverwrite: response.body?.items.toList()); state = state.copyWith(folderOverwrite: response.body?.items.toList() ?? []);
} }
Future<void> loadFilters() async { Future<void> loadFilters() async {
@ -225,11 +227,15 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
final tags = mappedList final tags = mappedList
.expand((element) => element?.tags ?? <String>[]) .expand((element) => element?.tags ?? <String>[])
.sorted((a, b) => a.toLowerCase().compareTo(b.toLowerCase())); .sorted((a, b) => a.toLowerCase().compareTo(b.toLowerCase()));
var tempFilters = tempState.filters;
tempState = tempState.copyWith( tempState = tempState.copyWith(
types: state.types.setAll(false).setKeys(enabledCollections, true), filters: tempFilters.copyWith(
genres: {for (var element in genres) element.name: false}.replaceMap(tempState.genres), types: tempFilters.types.setAll(false).setKeys(enabledCollections, true),
studios: {for (var element in studios) element: false}.replaceMap(tempState.studios), genres: {for (var element in genres) element.name: false}.replaceMap(tempFilters.genres),
tags: {for (var element in tags) element: false}.replaceMap(tempState.tags), studios: {for (var element in studios) element: false}.replaceMap(tempFilters.studios),
tags: {for (var element in tags) element: false}.replaceMap(tempFilters.tags),
recursive: state.views.included.firstOrNull?.collectionType.searchRecursive ?? true,
),
); );
state = tempState; state = tempState;
} }
@ -261,18 +267,18 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
final response = await api.itemsGet( final response = await api.itemsGet(
parentId: viewModel?.id ?? id, parentId: viewModel?.id ?? id,
searchTerm: searchString, searchTerm: searchString,
genres: state.genres.included, genres: state.filters.genres.included,
tags: state.tags.included, tags: state.filters.tags.included,
recursive: searchString?.isNotEmpty == true ? true : recursive ?? state.recursive, recursive: searchString?.isNotEmpty == true ? true : recursive ?? state.filters.recursive,
officialRatings: state.officialRatings.included, officialRatings: state.filters.officialRatings.included,
years: state.years.included, years: state.filters.years.included,
isMissing: false, isMissing: false,
limit: (limit ?? 0) > 0 ? limit : null, limit: (limit ?? 0) > 0 ? limit : null,
startIndex: (limit ?? 0) > 0 ? startIndex : null, startIndex: (limit ?? 0) > 0 ? startIndex : null,
collapseBoxSetItems: false, collapseBoxSetItems: false,
studioIds: state.studios.included.map((e) => e.id).toList(), studioIds: state.filters.studios.included.map((e) => e.id).toList(),
sortBy: shuffle == true ? [ItemSortBy.random] : state.sortingOption.toSortBy, sortBy: shuffle == true ? [ItemSortBy.random] : state.filters.sortingOption.toSortBy,
sortOrder: [state.sortOrder.sortOrder], sortOrder: [state.filters.sortOrder.sortOrder],
fields: { fields: {
ItemFields.genres, ItemFields.genres,
ItemFields.parentid, ItemFields.parentid,
@ -286,10 +292,10 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
if (viewModel?.collectionType == CollectionType.tvshows) ItemFields.childcount, if (viewModel?.collectionType == CollectionType.tvshows) ItemFields.childcount,
}.toList(), }.toList(),
filters: [ filters: [
...state.filters.included, ...state.filters.itemFilters.included,
if (state.favourites) ItemFilter.isfavorite, if (state.filters.favourites) ItemFilter.isfavorite,
], ],
includeItemTypes: state.types.included.map((e) => e.dtoKind).toList(), includeItemTypes: state.filters.types.included.map((e) => e.dtoKind).toList(),
); );
return response.body; return response.body;
} }
@ -344,53 +350,58 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
ref.read(userProvider.notifier).addSearchQuery(query); ref.read(userProvider.notifier).addSearchQuery(query);
} }
void toggleFavourite() => state = state.copyWith(favourites: !state.favourites); void toggleFavourite() =>
void toggleRecursive() => state = state.copyWith(recursive: !state.recursive); state = state.copyWith(filters: state.filters.copyWith(favourites: !state.filters.favourites));
void toggleType(FladderItemType type) => state = state.copyWith(types: state.types.toggleKey(type)); void toggleRecursive() =>
state = state.copyWith(filters: state.filters.copyWith(recursive: !state.filters.recursive));
void toggleType(FladderItemType type) =>
state = state.copyWith(filters: state.filters.copyWith(types: state.filters.types.toggleKey(type)));
void toggleView(ViewModel view) => state = state.copyWith(views: state.views.toggleKey(view)); void toggleView(ViewModel view) => state = state.copyWith(views: state.views.toggleKey(view));
void toggleGenre(String genre) => state = state.copyWith(genres: state.genres.toggleKey(genre)); void toggleGenre(String genre) =>
void toggleStudio(Studio studio) => state = state.copyWith(studios: state.studios.toggleKey(studio)); state = state.copyWith(filters: state.filters.copyWith(genres: state.filters.genres.toggleKey(genre)));
void toggleTag(String tag) => state = state.copyWith(tags: state.tags.toggleKey(tag)); void toggleStudio(Studio studio) =>
void toggleRatings(String officialRatings) => state = state.copyWith(filters: state.filters.copyWith(studios: state.filters.studios.toggleKey(studio)));
state = state.copyWith(officialRatings: state.officialRatings.toggleKey(officialRatings)); void toggleTag(String tag) =>
void toggleYears(int year) => state = state.copyWith(years: state.years.toggleKey(year)); state = state.copyWith(filters: state.filters.copyWith(tags: state.filters.tags.toggleKey(tag)));
void toggleFilters(ItemFilter filter) => state = state.copyWith(filters: state.filters.toggleKey(filter)); void toggleRatings(String officialRatings) => state = state.copyWith(
filters: state.filters.copyWith(officialRatings: state.filters.officialRatings.toggleKey(officialRatings)));
void toggleYears(int year) =>
state = state.copyWith(filters: state.filters.copyWith(years: state.filters.years.toggleKey(year)));
void toggleFilters(ItemFilter filter) =>
state = state.copyWith(filters: state.filters.copyWith(itemFilters: state.filters.itemFilters.toggleKey(filter)));
void setViews(Map<ViewModel, bool> views) { void setViews(Map<ViewModel, bool> views) {
loadedFilters = false; loadedFilters = false;
state = state.copyWith(views: views).setFiltersToDefault(); state = state.copyWith(views: views).setFiltersToDefault();
} }
void setGenres(Map<String, bool> genres) => state = state.copyWith(genres: genres); void setGenres(Map<String, bool> genres) => state = state.copyWith(filters: state.filters.copyWith(genres: genres));
void setStudios(Map<Studio, bool> studios) => state = state.copyWith(studios: studios); void setStudios(Map<Studio, bool> studios) =>
void setTags(Map<String, bool> tags) => state = state.copyWith(tags: tags); state = state.copyWith(filters: state.filters.copyWith(studios: studios));
void setTypes(Map<FladderItemType, bool> types) => state = state.copyWith(types: types); void setTags(Map<String, bool> tags) => state = state.copyWith(filters: state.filters.copyWith(tags: tags));
void setRatings(Map<String, bool> officialRatings) => state = state.copyWith(officialRatings: officialRatings); void setTypes(Map<FladderItemType, bool> types) =>
void setYears(Map<int, bool> years) => state = state.copyWith(years: years); state = state.copyWith(filters: state.filters.copyWith(types: types));
void setFilters(Map<ItemFilter, bool> filters) => state = state.copyWith(filters: filters); void setRatings(Map<String, bool> officialRatings) =>
state = state.copyWith(filters: state.filters.copyWith(officialRatings: officialRatings));
void setYears(Map<int, bool> years) => state = state.copyWith(filters: state.filters.copyWith(years: years));
void setFilters(Map<ItemFilter, bool> filters) =>
state = state.copyWith(filters: state.filters.copyWith(itemFilters: filters));
void setSortBy(SortingOptions e) => state = state.copyWith(filters: state.filters.copyWith(sortingOption: e));
void setSortOrder(SortingOrder e) => state = state.copyWith(filters: state.filters.copyWith(sortOrder: e));
void toggleEmptyShows() =>
state = state.copyWith(filters: state.filters.copyWith(hideEmptyShows: !state.filters.hideEmptyShows));
void setGroupBy(GroupBy groupBy) => state = state.copyWith(filters: state.filters.copyWith(groupBy: groupBy));
void clearAllFilters() { void clearAllFilters() {
state = state.copyWith( state = state.copyWith(
genres: state.genres.setAll(false),
tags: state.tags.setAll(false),
officialRatings: state.officialRatings.setAll(false),
years: state.years.setAll(false),
searchQuery: '', searchQuery: '',
favourites: false, filters: state.filters.clear(),
recursive: false,
studios: state.studios.setAll(false),
filters: state.filters.setAll(false),
hideEmptyShows: false,
); );
} }
void setSortBy(SortingOptions e) => state = state.copyWith(sortingOption: e);
void setSortOrder(SortingOrder e) => state = state.copyWith(sortOrder: e);
void setHideEmpty(bool value) => state = state.copyWith(hideEmptyShows: value);
void setGroupBy(GroupBy groupBy) => state = state.copyWith(groupBy: groupBy);
void setFolderId(ItemBaseModel item) { void setFolderId(ItemBaseModel item) {
if (state.folderOverwrite.contains(item)) return; if (state.folderOverwrite.contains(item)) return;
state = state.copyWith(folderOverwrite: [...state.folderOverwrite, item]); state = state.copyWith(folderOverwrite: [...state.folderOverwrite, item]);
@ -402,7 +413,7 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
void clearFolderOverWrite() => state = state.copyWith(folderOverwrite: []); void clearFolderOverWrite() => state = state.copyWith(folderOverwrite: []);
void toggleSelectMode() => void toggleSelectMode() =>
state = state.copyWith(selecteMode: !state.selecteMode, selectedPosters: !state.selecteMode == false ? [] : null); state = state.copyWith(selecteMode: !state.selecteMode, selectedPosters: !state.selecteMode == false ? [] : []);
void toggleSelection(ItemBaseModel item) { void toggleSelection(ItemBaseModel item) {
if (state.selectedPosters.contains(item)) { if (state.selectedPosters.contains(item)) {
@ -539,11 +550,11 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
List<ItemBaseModel> newPosters = results.nonNulls.expand((element) => element.items).toList(); List<ItemBaseModel> newPosters = results.nonNulls.expand((element) => element.items).toList();
if (state.views.included.length > 1) { if (state.views.included.length > 1) {
if (shuffle || state.sortingOption == SortingOptions.random) { if (shuffle || state.filters.sortingOption == SortingOptions.random) {
newPosters = newPosters.random(); newPosters = newPosters.random();
} else { } else {
newPosters = newPosters.sorted( newPosters = newPosters.sorted(
(a, b) => sortItems(a, b, state.sortingOption, state.sortOrder), (a, b) => sortItems(a, b, state.filters.sortingOption, state.filters.sortOrder),
); );
} }
} }
@ -556,7 +567,7 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
} else if (state.views.hasEnabled) { } else if (state.views.hasEnabled) {
await handleViewLoading(); await handleViewLoading();
} else { } else {
if (state.searchQuery.isEmpty && !state.favourites) { if (state.searchQuery.isEmpty && !state.filters.favourites) {
itemsToPlay = []; itemsToPlay = [];
} else { } else {
final response = await _loadLibrary(recursive: true, shuffle: shuffle); final response = await _loadLibrary(recursive: true, shuffle: shuffle);
@ -601,7 +612,8 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
List<PhotoModel> albumItems = []; List<PhotoModel> albumItems = [];
if (!state.types.included.containsAny([FladderItemType.video, FladderItemType.photo]) && state.recursive) { if (!state.filters.types.included.containsAny([FladderItemType.video, FladderItemType.photo]) &&
state.filters.recursive) {
for (var album in itemsToPlay.where( for (var album in itemsToPlay.where(
(element) => element is PhotoAlbumModel || element is FolderModel, (element) => element is PhotoAlbumModel || element is FolderModel,
)) { )) {
@ -622,8 +634,8 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
ItemFields.primaryimageaspectratio, ItemFields.primaryimageaspectratio,
}.toList(), }.toList(),
filters: [ filters: [
...state.filters.included, ...state.filters.itemFilters.included,
if (state.favourites) ItemFilter.isfavorite, if (state.filters.favourites) ItemFilter.isfavorite,
], ],
sortBy: shuffle ? [ItemSortBy.random] : null, sortBy: shuffle ? [ItemSortBy.random] : null,
); );
@ -683,23 +695,7 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
state = state.copyWith(); state = state.copyWith();
} }
void loadModel(LibraryFiltersModel model) { void loadModel(LibraryFilterModel model) => state = state.copyWith(filters: state.filters.loadModel(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) => void saveFiltersNew(String newName) =>
ref.read(filterProvider.notifier).saveFilter(LibraryFiltersModel.fromLibrarySearch(newName, state)); ref.read(filterProvider.notifier).saveFilter(LibraryFiltersModel.fromLibrarySearch(newName, state));

View file

@ -189,16 +189,16 @@ class User extends _$User {
} }
void removeFilter(LibraryFiltersModel model) { void removeFilter(LibraryFiltersModel model) {
final currentList = ((state?.savedFilters ?? [])).toList(growable: true); final currentList = ((state?.libraryFilters ?? [])).toList(growable: true);
currentList.remove(model); currentList.remove(model);
state = state?.copyWith(savedFilters: currentList); userState = state?.copyWith(libraryFilters: currentList);
} }
void saveFilter(LibraryFiltersModel model) { void saveFilter(LibraryFiltersModel model) {
final currentList = (state?.savedFilters ?? []).toList(growable: true); final currentList = (state?.libraryFilters ?? []).toList(growable: true);
if (currentList.firstWhereOrNull((value) => value.id == model.id) != null) { if (currentList.firstWhereOrNull((value) => value.id == model.id) != null) {
state = state?.copyWith( userState = state?.copyWith(
savedFilters: currentList.map( libraryFilters: currentList.map(
(e) { (e) {
if (e.id == model.id) { if (e.id == model.id) {
return model; return model;
@ -210,11 +210,11 @@ class User extends _$User {
}, },
).toList()); ).toList());
} else { } else {
state = state?.copyWith(savedFilters: [model, ...currentList]); userState = state?.copyWith(libraryFilters: [model, ...currentList]);
} }
} }
void deleteAllFilters() => state = state?.copyWith(savedFilters: []); void deleteAllFilters() => userState = state?.copyWith(libraryFilters: []);
String? createDownloadUrl(ItemBaseModel item) => String? createDownloadUrl(ItemBaseModel item) =>
Uri.encodeFull("${state?.server}/Items/${item.id}/Download?api_key=${state?.credentials.token}"); Uri.encodeFull("${state?.server}/Items/${item.id}/Download?api_key=${state?.credentials.token}");

View file

@ -24,7 +24,7 @@ final showSyncButtonProviderProvider = AutoDisposeProvider<bool>.internal(
@Deprecated('Will be removed in 3.0. Use Ref instead') @Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element // ignore: unused_element
typedef ShowSyncButtonProviderRef = AutoDisposeProviderRef<bool>; typedef ShowSyncButtonProviderRef = AutoDisposeProviderRef<bool>;
String _$userHash() => r'24b34a88eae11aec1e377a82d1e507f293b7816a'; String _$userHash() => r'2b2a9f10a3bbb2c7ce51a926e7fb6e807a0e6377';
/// See also [User]. /// See also [User].
@ProviderFor(User) @ProviderFor(User)

View file

@ -198,6 +198,9 @@ class LibrarySearchRoute extends _i18.PageRouteInfo<LibrarySearchRouteArgs> {
bool? favourites, bool? favourites,
_i21.SortingOrder? sortOrder, _i21.SortingOrder? sortOrder,
_i21.SortingOptions? sortingOptions, _i21.SortingOptions? sortingOptions,
Map<_i19.FladderItemType, bool>? types,
Map<String, bool>? genres,
bool recursive = true,
_i22.PhotoModel? photoToView, _i22.PhotoModel? photoToView,
_i20.Key? key, _i20.Key? key,
List<_i18.PageRouteInfo>? children, List<_i18.PageRouteInfo>? children,
@ -209,6 +212,9 @@ class LibrarySearchRoute extends _i18.PageRouteInfo<LibrarySearchRouteArgs> {
favourites: favourites, favourites: favourites,
sortOrder: sortOrder, sortOrder: sortOrder,
sortingOptions: sortingOptions, sortingOptions: sortingOptions,
types: types,
genres: genres,
recursive: recursive,
photoToView: photoToView, photoToView: photoToView,
key: key, key: key,
), ),
@ -218,6 +224,9 @@ class LibrarySearchRoute extends _i18.PageRouteInfo<LibrarySearchRouteArgs> {
'favourites': favourites, 'favourites': favourites,
'sortOrder': sortOrder, 'sortOrder': sortOrder,
'sortOptions': sortingOptions, 'sortOptions': sortingOptions,
'itemTypes': types,
'genres': genres,
'recursive': recursive,
}, },
initialChildren: children, initialChildren: children,
); );
@ -235,6 +244,9 @@ class LibrarySearchRoute extends _i18.PageRouteInfo<LibrarySearchRouteArgs> {
favourites: queryParams.optBool('favourites'), favourites: queryParams.optBool('favourites'),
sortOrder: queryParams.get('sortOrder'), sortOrder: queryParams.get('sortOrder'),
sortingOptions: queryParams.get('sortOptions'), sortingOptions: queryParams.get('sortOptions'),
types: queryParams.get('itemTypes'),
genres: queryParams.get('genres'),
recursive: queryParams.getBool('recursive', true),
), ),
); );
return _i8.LibrarySearchScreen( return _i8.LibrarySearchScreen(
@ -243,6 +255,9 @@ class LibrarySearchRoute extends _i18.PageRouteInfo<LibrarySearchRouteArgs> {
favourites: args.favourites, favourites: args.favourites,
sortOrder: args.sortOrder, sortOrder: args.sortOrder,
sortingOptions: args.sortingOptions, sortingOptions: args.sortingOptions,
types: args.types,
genres: args.genres,
recursive: args.recursive,
photoToView: args.photoToView, photoToView: args.photoToView,
key: args.key, key: args.key,
); );
@ -257,6 +272,9 @@ class LibrarySearchRouteArgs {
this.favourites, this.favourites,
this.sortOrder, this.sortOrder,
this.sortingOptions, this.sortingOptions,
this.types,
this.genres,
this.recursive = true,
this.photoToView, this.photoToView,
this.key, this.key,
}); });
@ -271,13 +289,19 @@ class LibrarySearchRouteArgs {
final _i21.SortingOptions? sortingOptions; final _i21.SortingOptions? sortingOptions;
final Map<_i19.FladderItemType, bool>? types;
final Map<String, bool>? genres;
final bool recursive;
final _i22.PhotoModel? photoToView; final _i22.PhotoModel? photoToView;
final _i20.Key? key; final _i20.Key? key;
@override @override
String toString() { String toString() {
return 'LibrarySearchRouteArgs{viewModelId: $viewModelId, folderId: $folderId, favourites: $favourites, sortOrder: $sortOrder, sortingOptions: $sortingOptions, photoToView: $photoToView, key: $key}'; return 'LibrarySearchRouteArgs{viewModelId: $viewModelId, folderId: $folderId, favourites: $favourites, sortOrder: $sortOrder, sortingOptions: $sortingOptions, types: $types, genres: $genres, recursive: $recursive, photoToView: $photoToView, key: $key}';
} }
@override @override
@ -289,6 +313,9 @@ class LibrarySearchRouteArgs {
favourites == other.favourites && favourites == other.favourites &&
sortOrder == other.sortOrder && sortOrder == other.sortOrder &&
sortingOptions == other.sortingOptions && sortingOptions == other.sortingOptions &&
const _i23.MapEquality().equals(types, other.types) &&
const _i23.MapEquality().equals(genres, other.genres) &&
recursive == other.recursive &&
photoToView == other.photoToView && photoToView == other.photoToView &&
key == other.key; key == other.key;
} }
@ -300,6 +327,9 @@ class LibrarySearchRouteArgs {
favourites.hashCode ^ favourites.hashCode ^
sortOrder.hashCode ^ sortOrder.hashCode ^
sortingOptions.hashCode ^ sortingOptions.hashCode ^
const _i23.MapEquality().hash(types) ^
const _i23.MapEquality().hash(genres) ^
recursive.hashCode ^
photoToView.hashCode ^ photoToView.hashCode ^
key.hashCode; key.hashCode;
} }

View file

@ -8,6 +8,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/collection_types.dart'; import 'package:fladder/models/collection_types.dart';
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/library_search/library_search_options.dart'; import 'package:fladder/models/library_search/library_search_options.dart';
import 'package:fladder/models/settings/home_settings_model.dart'; import 'package:fladder/models/settings/home_settings_model.dart';
import 'package:fladder/providers/dashboard_provider.dart'; import 'package:fladder/providers/dashboard_provider.dart';
@ -177,10 +178,16 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
contentPadding: padding, contentPadding: padding,
label: context.localized.dashboardRecentlyAdded(view.name), label: context.localized.dashboardRecentlyAdded(view.name),
collectionAspectRatio: view.collectionType.aspectRatio, collectionAspectRatio: view.collectionType.aspectRatio,
onLabelClick: () => context.router.push(LibrarySearchRoute( onLabelClick: () => context.router.push(
LibrarySearchRoute(
viewModelId: view.id, viewModelId: view.id,
types: switch (view.collectionType) {
CollectionType.tvshows => {
FladderItemType.episode: true,
},
_ => {},
},
sortingOptions: switch (view.collectionType) { sortingOptions: switch (view.collectionType) {
CollectionType.tvshows ||
CollectionType.books || CollectionType.books ||
CollectionType.boxsets || CollectionType.boxsets ||
CollectionType.folders || CollectionType.folders ||
@ -189,7 +196,8 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
_ => SortingOptions.dateAdded, _ => SortingOptions.dateAdded,
}, },
sortOrder: SortingOrder.descending, sortOrder: SortingOrder.descending,
)), ),
),
posters: view.recentlyAdded, posters: view.recentlyAdded,
), ),
)), )),

View file

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/models/library_filter_model.dart';
import 'package:fladder/providers/favourites_provider.dart'; import 'package:fladder/providers/favourites_provider.dart';
import 'package:fladder/providers/settings/client_settings_provider.dart'; import 'package:fladder/providers/settings/client_settings_provider.dart';
import 'package:fladder/routes/auto_router.gr.dart'; import 'package:fladder/routes/auto_router.gr.dart';
@ -56,6 +57,15 @@ class FavouritesScreen extends ConsumerWidget {
(e) => SliverToBoxAdapter( (e) => SliverToBoxAdapter(
child: PosterRow( child: PosterRow(
contentPadding: padding, contentPadding: padding,
onLabelClick: () => context.pushRoute(
LibrarySearchRoute().withFilter(
LibraryFilterModel(
favourites: true,
types: {e.key: true},
recursive: true,
),
),
),
label: e.key.label(context), label: e.key.label(context),
posters: e.value, posters: e.value,
), ),

View file

@ -6,6 +6,7 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:iconsax_plus/iconsax_plus.dart'; import 'package:iconsax_plus/iconsax_plus.dart';
import 'package:fladder/models/library_filter_model.dart';
import 'package:fladder/models/recommended_model.dart'; import 'package:fladder/models/recommended_model.dart';
import 'package:fladder/models/view_model.dart'; import 'package:fladder/models/view_model.dart';
import 'package:fladder/providers/library_screen_provider.dart'; import 'package:fladder/providers/library_screen_provider.dart';
@ -15,6 +16,7 @@ import 'package:fladder/screens/shared/flat_button.dart';
import 'package:fladder/screens/shared/media/poster_row.dart'; import 'package:fladder/screens/shared/media/poster_row.dart';
import 'package:fladder/screens/shared/nested_scaffold.dart'; import 'package:fladder/screens/shared/nested_scaffold.dart';
import 'package:fladder/screens/shared/nested_sliver_appbar.dart'; import 'package:fladder/screens/shared/nested_sliver_appbar.dart';
import 'package:fladder/theme.dart';
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart'; import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
import 'package:fladder/util/fladder_image.dart'; import 'package:fladder/util/fladder_image.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
@ -159,6 +161,16 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen> with SingleTicker
padding: const EdgeInsets.symmetric(vertical: 8.0), padding: const EdgeInsets.symmetric(vertical: 8.0),
child: PosterRow( child: PosterRow(
contentPadding: padding, contentPadding: padding,
onLabelClick: () => context.pushRoute(
LibrarySearchRoute(
viewModelId: libraryScreenState.selectedViewModel?.id ?? "",
).withFilter(
const LibraryFilterModel(
favourites: true,
recursive: true,
),
),
),
posters: favourites, posters: favourites,
label: context.localized.favorites, label: context.localized.favorites,
), ),
@ -173,6 +185,16 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen> with SingleTicker
child: PosterRow( child: PosterRow(
contentPadding: padding, contentPadding: padding,
posters: element.posters, posters: element.posters,
onLabelClick: () => context.pushRoute(
LibrarySearchRoute(
viewModelId: libraryScreenState.selectedViewModel?.id ?? "",
).withFilter(
LibraryFilterModel(
recursive: true,
genres: {(element.name as Other).customLabel: true},
),
),
),
label: element.type != null label: element.type != null
? "${element.type?.label(context)} - ${element.name.label(context)}" ? "${element.type?.label(context)} - ${element.name.label(context)}"
: element.name.label(context), : element.name.label(context),
@ -210,7 +232,7 @@ class LibraryRow extends ConsumerWidget {
label: context.localized.library(views.length), label: context.localized.library(views.length),
items: views, items: views,
startIndex: selectedView != null ? views.indexOf(selectedView!) : null, startIndex: selectedView != null ? views.indexOf(selectedView!) : null,
height: 165, height: 125,
contentPadding: padding, contentPadding: padding,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final view = views[index]; final view = views[index];
@ -240,19 +262,22 @@ class LibraryRow extends ConsumerWidget {
items: viewActions.popupMenuItems(useIcons: true), items: viewActions.popupMenuItems(useIcons: true),
); );
}, },
child: Card( child: Stack(
color: isSelected ? Theme.of(context).colorScheme.primaryContainer : null,
shadowColor: Colors.transparent,
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 4,
children: [ children: [
SizedBox( AnimatedContainer(
duration: const Duration(milliseconds: 250),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
border: Border.all(
width: isSelected ? 4 : 0,
color: isSelected ? Theme.of(context).colorScheme.primary : Colors.transparent,
),
borderRadius: FladderTheme.defaultShape.borderRadius,
),
clipBehavior: Clip.hardEdge,
width: 200, width: 200,
child: Card( child: ClipRRect(
borderRadius: FladderTheme.smallShape.borderRadius,
child: AspectRatio( child: AspectRatio(
aspectRatio: 1.60, aspectRatio: 1.60,
child: FladderImage( child: FladderImage(
@ -271,21 +296,17 @@ class LibraryRow extends ConsumerWidget {
), ),
), ),
), ),
Expanded( Positioned.fill(
child: Align(
alignment: Alignment.bottomLeft,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 250),
opacity: isSelected ? 0 : 1,
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0), padding: const EdgeInsets.all(6),
child: Row( child: Row(
spacing: 8, spacing: 8,
children: [ children: [
if (isSelected)
Container(
height: 12,
width: 12,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
shape: BoxShape.circle,
),
),
Text( Text(
view.name, view.name,
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
@ -297,10 +318,10 @@ class LibraryRow extends ConsumerWidget {
), ),
), ),
), ),
),
),
], ],
), ),
),
),
); );
}, },
); );

View file

@ -9,6 +9,7 @@ import 'package:iconsax_plus/iconsax_plus.dart';
import 'package:fladder/models/boxset_model.dart'; import 'package:fladder/models/boxset_model.dart';
import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/photos_model.dart'; import 'package:fladder/models/items/photos_model.dart';
import 'package:fladder/models/library_filter_model.dart';
import 'package:fladder/models/library_search/library_search_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/library_search/library_search_options.dart';
import 'package:fladder/models/playlist_model.dart'; import 'package:fladder/models/playlist_model.dart';
@ -44,7 +45,6 @@ import 'package:fladder/widgets/shared/pinch_poster_zoom.dart';
import 'package:fladder/widgets/shared/poster_size_slider.dart'; import 'package:fladder/widgets/shared/poster_size_slider.dart';
import 'package:fladder/widgets/shared/pull_to_refresh.dart'; import 'package:fladder/widgets/shared/pull_to_refresh.dart';
import 'package:fladder/widgets/shared/scroll_position.dart'; import 'package:fladder/widgets/shared/scroll_position.dart';
import 'package:fladder/widgets/shared/shapes.dart';
@RoutePage() @RoutePage()
class LibrarySearchScreen extends ConsumerStatefulWidget { class LibrarySearchScreen extends ConsumerStatefulWidget {
@ -53,6 +53,9 @@ class LibrarySearchScreen extends ConsumerStatefulWidget {
final List<String>? folderId; final List<String>? folderId;
final SortingOrder? sortOrder; final SortingOrder? sortOrder;
final SortingOptions? sortingOptions; final SortingOptions? sortingOptions;
final Map<FladderItemType, bool>? types;
final Map<String, bool>? genres;
final bool recursive;
final PhotoModel? photoToView; final PhotoModel? photoToView;
const LibrarySearchScreen({ const LibrarySearchScreen({
@QueryParam("parentId") this.viewModelId, @QueryParam("parentId") this.viewModelId,
@ -60,6 +63,9 @@ class LibrarySearchScreen extends ConsumerStatefulWidget {
@QueryParam("favourites") this.favourites, @QueryParam("favourites") this.favourites,
@QueryParam("sortOrder") this.sortOrder, @QueryParam("sortOrder") this.sortOrder,
@QueryParam("sortOptions") this.sortingOptions, @QueryParam("sortOptions") this.sortingOptions,
@QueryParam("itemTypes") this.types,
@QueryParam("genres") this.genres,
@QueryParam("recursive") this.recursive = true,
this.photoToView, this.photoToView,
super.key, super.key,
}); });
@ -69,7 +75,6 @@ class LibrarySearchScreen extends ConsumerStatefulWidget {
} }
class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> { class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
final SearchController searchController = SearchController();
final Debouncer debouncer = Debouncer(const Duration(seconds: 1)); final Debouncer debouncer = Debouncer(const Duration(seconds: 1));
final GlobalKey<RefreshIndicatorState> refreshKey = GlobalKey<RefreshIndicatorState>(); final GlobalKey<RefreshIndicatorState> refreshKey = GlobalKey<RefreshIndicatorState>();
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
@ -93,18 +98,12 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((value) {
initLibrary(); initLibrary();
});
} }
void initLibrary() { Future<void> initLibrary() async {
searchController.addListener(() {
debouncer.run(() {
ref.read(providerKey.notifier).setSearch(searchController.text);
});
});
Future.microtask(
() async {
await refreshKey.currentState?.show(); await refreshKey.currentState?.show();
SystemChrome.setEnabledSystemUIMode( SystemChrome.setEnabledSystemUIMode(
SystemUiMode.edgeToEdge, SystemUiMode.edgeToEdge,
@ -117,8 +116,6 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
scrollController.addListener(() { scrollController.addListener(() {
scrollPosition(); scrollPosition();
}); });
},
);
} }
void scrollPosition() { void scrollPosition() {
@ -127,19 +124,27 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
} }
} }
Future<void> refreshSearch() async {
await refreshKey.currentState?.show();
scrollController.jumpTo(0);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final surfaceColor = Theme.of(context).colorScheme.surface;
final isEmptySearchScreen = widget.viewModelId == null && widget.favourites == null && widget.folderId == null; final isEmptySearchScreen = widget.viewModelId == null && widget.favourites == null && widget.folderId == null;
final librarySearchResults = ref.watch(providerKey); final librarySearchResults = ref.watch(providerKey);
final postersList = librarySearchResults.posters.hideEmptyChildren(librarySearchResults.hideEmptyShows); final postersList = librarySearchResults.posters.hideEmptyChildren(librarySearchResults.filters.hideEmptyShows);
final libraryViewType = ref.watch(libraryViewTypeProvider); final libraryViewType = ref.watch(libraryViewTypeProvider);
final floatingAppBar = AdaptiveLayout.layoutModeOf(context) != LayoutMode.single;
ref.listen( ref.listen(
providerKey, providerKey,
(previous, next) { (previous, next) {
if (previous != next) { if (previous?.filters != next.filters) {
refreshKey.currentState?.show(); refreshSearch();
scrollController.jumpTo(0);
} }
}, },
); );
@ -153,20 +158,17 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
} }
}, },
child: NestedScaffold( child: NestedScaffold(
background: BackgroundImage(items: librarySearchResults.activePosters), background: BackgroundImage(images: postersList.map((e) => e.images).nonNulls.toList()),
body: Padding( body: Padding(
padding: EdgeInsets.only(left: AdaptiveLayout.of(context).sideBarWidth), padding: EdgeInsets.only(left: AdaptiveLayout.of(context).sideBarWidth),
child: Scaffold( child: Scaffold(
extendBody: true, extendBody: true,
backgroundColor: Colors.transparent,
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,
floatingActionButton: HideOnScroll( floatingActionButton: HideOnScroll(
controller: scrollController, controller: scrollController,
visibleBuilder: (visible) => Column( visibleBuilder: (visible) => librarySearchResults.activePosters.isNotEmpty
crossAxisAlignment: CrossAxisAlignment.end, ? FloatingActionButtonAnimated(
mainAxisSize: MainAxisSize.min,
children: [
if (librarySearchResults.activePosters.isNotEmpty)
FloatingActionButtonAnimated(
key: Key(context.localized.playLabel), key: Key(context.localized.playLabel),
isExtended: visible, isExtended: visible,
tooltip: context.localized.playVideos, tooltip: context.localized.playVideos,
@ -192,12 +194,12 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
}, },
label: Text(context.localized.playLabel), label: Text(context.localized.playLabel),
icon: const Icon(IconsaxPlusBold.play), icon: const Icon(IconsaxPlusBold.play),
), )
].addInBetween(const SizedBox(height: 10)), : null,
),
), ),
bottomNavigationBar: HideOnScroll( bottomNavigationBar: HideOnScroll(
controller: AdaptiveLayout.of(context).isDesktop ? null : scrollController, controller: scrollController,
canHide: !floatingAppBar,
child: IgnorePointer( child: IgnorePointer(
ignoring: librarySearchResults.fetchingItems, ignoring: librarySearchResults.fetchingItems,
child: _LibrarySearchBottomBar( child: _LibrarySearchBottomBar(
@ -209,17 +211,8 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
), ),
), ),
), ),
body: Stack( body: PinchPosterZoom(
fit: StackFit.expand, scaleDifference: (difference) => ref.read(clientSettingsProvider.notifier).addPosterSize(difference),
children: [
Positioned.fill(
child: PinchPosterZoom(
scaleDifference: (difference) =>
ref.read(clientSettingsProvider.notifier).addPosterSize(difference),
child: ClipRRect(
borderRadius: AdaptiveLayout.viewSizeOf(context) == ViewSize.desktop
? BorderRadius.circular(15)
: BorderRadius.circular(0),
child: FladderScrollbar( child: FladderScrollbar(
visible: AdaptiveLayout.of(context).inputDevice != InputDevice.pointer, visible: AdaptiveLayout.of(context).inputDevice != InputDevice.pointer,
controller: scrollController, controller: scrollController,
@ -228,13 +221,19 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
autoFocus: false, autoFocus: false,
contextRefresh: false, contextRefresh: false,
onRefresh: () async { onRefresh: () async {
final defaultFilter = const LibraryFilterModel();
if (libraryProvider.mounted) { if (libraryProvider.mounted) {
return libraryProvider.initRefresh( return libraryProvider.initRefresh(
widget.folderId, widget.folderId,
widget.viewModelId, widget.viewModelId,
widget.favourites, defaultFilter.copyWith(
widget.sortOrder, favourites: widget.favourites ?? defaultFilter.favourites,
widget.sortingOptions, sortOrder: widget.sortOrder ?? defaultFilter.sortOrder,
sortingOption: widget.sortingOptions ?? defaultFilter.sortingOption,
types: widget.types ?? {},
genres: widget.genres ?? {},
recursive: widget.recursive,
),
); );
} }
}, },
@ -244,24 +243,32 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
slivers: [ slivers: [
SliverAppBar( SliverAppBar(
floating: !AdaptiveLayout.of(context).isDesktop, floating: !floatingAppBar,
collapsedHeight: 80, collapsedHeight: 80,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
pinned: AdaptiveLayout.of(context).isDesktop,
primary: true, primary: true,
pinned: floatingAppBar,
elevation: 5, elevation: 5,
leading: context.router.backButton(),
surfaceTintColor: Colors.transparent, surfaceTintColor: Colors.transparent,
shadowColor: Colors.transparent, shadowColor: Colors.transparent,
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Colors.transparent,
shape: AppBarShape(),
titleSpacing: 4, titleSpacing: 4,
leadingWidth: 48, flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
surfaceColor.withValues(alpha: 0.8),
surfaceColor.withValues(alpha: 0.75),
surfaceColor.withValues(alpha: 0.5),
surfaceColor.withValues(alpha: 0),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
)),
),
actions: [ actions: [
const SizedBox(width: 4),
Builder(builder: (context) { Builder(builder: (context) {
final isFavorite = final isFavorite = librarySearchResults.nestedCurrentItem?.userData.isFavourite == true;
librarySearchResults.nestedCurrentItem?.userData.isFavourite == true;
final itemActions = librarySearchResults.nestedCurrentItem?.generateActions( final itemActions = librarySearchResults.nestedCurrentItem?.generateActions(
context, context,
ref, ref,
@ -289,7 +296,7 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
); );
final showSavedFiltersDialogue = ItemActionButton( final showSavedFiltersDialogue = ItemActionButton(
label: Text(context.localized.filter(2)), label: Text(context.localized.filter(2)),
action: () => showSavedFilters(context, librarySearchResults, libraryProvider), action: () => showSavedFilters(context, uniqueKey),
icon: const Icon(IconsaxPlusLinear.refresh), icon: const Icon(IconsaxPlusLinear.refresh),
); );
final itemViewAction = ItemActionButton( final itemViewAction = ItemActionButton(
@ -314,8 +321,7 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
(e) => FilledButton.tonal( (e) => FilledButton.tonal(
style: FilledButtonTheme.of(context).style?.copyWith( style: FilledButtonTheme.of(context).style?.copyWith(
padding: const WidgetStatePropertyAll( padding: const WidgetStatePropertyAll(
EdgeInsets.symmetric( EdgeInsets.symmetric(horizontal: 12, vertical: 24)),
horizontal: 12, vertical: 24)),
backgroundColor: WidgetStateProperty.resolveWith( backgroundColor: WidgetStateProperty.resolveWith(
(states) { (states) {
if (e != currentType) { if (e != currentType) {
@ -363,8 +369,7 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
position: RelativeRect.fromLTRB(left, top, 40, 100), position: RelativeRect.fromLTRB(left, top, 40, 100),
items: <PopupMenuEntry>[ items: <PopupMenuEntry>[
PopupMenuItem( PopupMenuItem(
child: Text( child: Text(librarySearchResults.nestedCurrentItem?.type.label(context) ??
librarySearchResults.nestedCurrentItem?.type.label(context) ??
context.localized.library(0))), context.localized.library(0))),
itemCountWidget.toPopupMenuItem(useIcons: true), itemCountWidget.toPopupMenuItem(useIcons: true),
refreshAction.toPopupMenuItem(useIcons: true), refreshAction.toPopupMenuItem(useIcons: true),
@ -415,7 +420,20 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
], ],
const SizedBox(width: 12) const SizedBox(width: 12)
], ],
title: Hero( title: Row(
spacing: 2,
children: [
const SizedBox(width: 2),
Center(
child: SizedBox.square(
dimension: 47,
child: Card(
child: context.router.backButton(),
),
),
),
Flexible(
child: Hero(
tag: "PrimarySearch", tag: "PrimarySearch",
child: SuggestionSearchBar( child: SuggestionSearchBar(
autoFocus: isEmptySearchScreen, autoFocus: isEmptySearchScreen,
@ -434,6 +452,9 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
}, },
), ),
), ),
),
],
),
bottom: PreferredSize( bottom: PreferredSize(
preferredSize: const Size(0, 50), preferredSize: const Size(0, 50),
child: Transform.translate( child: Transform.translate(
@ -472,12 +493,11 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
if (postersList.isNotEmpty) if (postersList.isNotEmpty)
SliverPadding( SliverPadding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
left: MediaQuery.of(context).padding.left, left: MediaQuery.of(context).padding.left, right: MediaQuery.of(context).padding.right),
right: MediaQuery.of(context).padding.right),
sliver: LibraryViews( sliver: LibraryViews(
key: uniqueKey, key: uniqueKey,
items: postersList, items: postersList,
groupByType: librarySearchResults.groupBy, groupByType: librarySearchResults.filters.groupBy,
), ),
) )
else else
@ -494,37 +514,6 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
), ),
), ),
), ),
if (librarySearchResults.fetchingItems) ...[
Container(
color: Colors.black.withValues(alpha: 0.1),
),
Center(
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const CircularProgressIndicator.adaptive(),
Text(context.localized.fetchingLibrary, style: Theme.of(context).textTheme.titleMedium),
IconButton(
onPressed: () => libraryProvider.cancelFetch(),
icon: const Icon(IconsaxPlusLinear.close_square),
)
].addInBetween(const SizedBox(width: 16)),
),
),
),
)
],
],
),
),
),
), ),
); );
} }
@ -663,7 +652,7 @@ class _LibrarySearchBottomBar extends ConsumerWidget {
context, context,
libraryProvider: libraryProvider, libraryProvider: libraryProvider,
uniqueKey: uniqueKey, uniqueKey: uniqueKey,
options: (librarySearchResults.sortingOption, librarySearchResults.sortOrder), options: (librarySearchResults.filters.sortingOption, librarySearchResults.filters.sortOrder),
); );
if (newOptions != null) { if (newOptions != null) {
if (newOptions.$1 != null) { if (newOptions.$1 != null) {

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:iconsax_plus/iconsax_plus.dart'; import 'package:iconsax_plus/iconsax_plus.dart';
@ -11,7 +12,9 @@ import 'package:fladder/providers/library_search_provider.dart';
import 'package:fladder/screens/shared/chips/category_chip.dart'; import 'package:fladder/screens/shared/chips/category_chip.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/map_bool_helper.dart'; import 'package:fladder/util/map_bool_helper.dart';
import 'package:fladder/util/position_provider.dart';
import 'package:fladder/util/refresh_state.dart'; import 'package:fladder/util/refresh_state.dart';
import 'package:fladder/widgets/shared/button_group.dart';
class LibraryFilterChips extends ConsumerStatefulWidget { class LibraryFilterChips extends ConsumerStatefulWidget {
const LibraryFilterChips({super.key}); const LibraryFilterChips({super.key});
@ -25,27 +28,26 @@ class _LibraryFilterChipsState extends ConsumerState<LibraryFilterChips> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final uniqueKey = widget.key ?? UniqueKey(); final uniqueKey = widget.key ?? UniqueKey();
final libraryProvider = ref.watch(librarySearchProvider(uniqueKey).notifier); final libraryProvider = ref.watch(librarySearchProvider(uniqueKey).notifier);
final groupBy = ref.watch(librarySearchProvider(uniqueKey).select((v) => v.groupBy)); final groupBy = ref.watch(librarySearchProvider(uniqueKey).select((v) => v.filters.groupBy));
final favourites = ref.watch(librarySearchProvider(uniqueKey).select((v) => v.favourites)); final favourites = ref.watch(librarySearchProvider(uniqueKey).select((v) => v.filters.favourites));
final recursive = ref.watch(librarySearchProvider(uniqueKey).select((v) => v.recursive)); final recursive = ref.watch(librarySearchProvider(uniqueKey).select((v) => v.filters.recursive));
final hideEmpty = ref.watch(librarySearchProvider(uniqueKey).select((v) => v.hideEmptyShows)); final hideEmpty = ref.watch(librarySearchProvider(uniqueKey).select((v) => v.filters.hideEmptyShows));
final librarySearchResults = ref.watch(librarySearchProvider(uniqueKey)); final librarySearchResults = ref.watch(librarySearchProvider(uniqueKey));
return Row( final chips = [
spacing: 8,
children: [
if (librarySearchResults.folderOverwrite.isEmpty) if (librarySearchResults.folderOverwrite.isEmpty)
CategoryChip( CategoryChip(
label: Text(context.localized.library(2)), label: Text(context.localized.library(2)),
items: librarySearchResults.views, items: librarySearchResults.views.sortByKey((value) => value.name),
labelBuilder: (item) => Text(item.name), labelBuilder: (item) => Text(item.name),
onSave: (value) => libraryProvider.setViews(value), onSave: (value) => libraryProvider.setViews(value),
onCancel: () => libraryProvider.setViews(librarySearchResults.views), onCancel: () => libraryProvider.setViews(librarySearchResults.views),
onClear: () => libraryProvider.setViews(librarySearchResults.views.setAll(false)), onClear: () => libraryProvider.setViews(librarySearchResults.views.setAll(false)),
), ),
CategoryChip<FladderItemType>( CategoryChip<FladderItemType>(
label: Text(context.localized.type(librarySearchResults.types.length)), label: Text(context.localized.type(librarySearchResults.filters.types.length)),
items: librarySearchResults.types, items: librarySearchResults.filters.types.sortByKey((value) => value.label(context)),
activeIcon: IconsaxPlusBold.filter_tick,
labelBuilder: (item) => Row( labelBuilder: (item) => Row(
children: [ children: [
Icon(item.icon), Icon(item.icon),
@ -54,104 +56,109 @@ class _LibraryFilterChipsState extends ConsumerState<LibraryFilterChips> {
], ],
), ),
onSave: (value) => libraryProvider.setTypes(value), onSave: (value) => libraryProvider.setTypes(value),
onClear: () => libraryProvider.setTypes(librarySearchResults.types.setAll(false)), onClear: () => libraryProvider.setTypes(librarySearchResults.filters.types.setAll(false)),
), ),
FilterChip( ExpressiveButton(
isSelected: favourites,
icon: favourites ? const Icon(IconsaxPlusBold.heart) : null,
label: Text(context.localized.favorites), label: Text(context.localized.favorites),
avatar: Icon( onPressed: () {
favourites ? IconsaxPlusBold.heart : IconsaxPlusLinear.heart,
color: Theme.of(context).colorScheme.onSurface,
),
selected: favourites,
showCheckmark: false,
onSelected: (_) {
libraryProvider.toggleFavourite(); libraryProvider.toggleFavourite();
context.refreshData(); context.refreshData();
}, },
), ),
FilterChip( ExpressiveButton(
isSelected: recursive,
icon: recursive ? const Icon(IconsaxPlusBold.tick_circle) : null,
label: Text(context.localized.recursive), label: Text(context.localized.recursive),
selected: recursive, onPressed: () {
onSelected: (_) {
libraryProvider.toggleRecursive(); libraryProvider.toggleRecursive();
context.refreshData(); context.refreshData();
}, },
), ),
if (librarySearchResults.genres.isNotEmpty) if (librarySearchResults.filters.genres.isNotEmpty)
CategoryChip<String>( CategoryChip<String>(
label: Text(context.localized.genre(librarySearchResults.genres.length)), label: Text(context.localized.genre(librarySearchResults.filters.genres.length)),
activeIcon: IconsaxPlusBold.hierarchy_2, activeIcon: IconsaxPlusBold.hierarchy_2,
items: librarySearchResults.genres, items: librarySearchResults.filters.genres,
labelBuilder: (item) => Text(item), labelBuilder: (item) => Text(item),
onSave: (value) => libraryProvider.setGenres(value), onSave: (value) => libraryProvider.setGenres(value),
onCancel: () => libraryProvider.setGenres(librarySearchResults.genres), onCancel: () => libraryProvider.setGenres(librarySearchResults.filters.genres),
onClear: () => libraryProvider.setGenres(librarySearchResults.genres.setAll(false)), onClear: () => libraryProvider.setGenres(librarySearchResults.filters.genres.setAll(false)),
), ),
if (librarySearchResults.studios.isNotEmpty) if (librarySearchResults.filters.studios.isNotEmpty)
CategoryChip<Studio>( CategoryChip<Studio>(
label: Text(context.localized.studio(librarySearchResults.studios.length)), label: Text(context.localized.studio(librarySearchResults.filters.studios.length)),
activeIcon: IconsaxPlusBold.airdrop, activeIcon: IconsaxPlusBold.airdrop,
items: librarySearchResults.studios, items: librarySearchResults.filters.studios,
labelBuilder: (item) => Text(item.name), labelBuilder: (item) => Text(item.name),
onSave: (value) => libraryProvider.setStudios(value), onSave: (value) => libraryProvider.setStudios(value),
onCancel: () => libraryProvider.setStudios(librarySearchResults.studios), onCancel: () => libraryProvider.setStudios(librarySearchResults.filters.studios),
onClear: () => libraryProvider.setStudios(librarySearchResults.studios.setAll(false)), onClear: () => libraryProvider.setStudios(librarySearchResults.filters.studios.setAll(false)),
), ),
if (librarySearchResults.tags.isNotEmpty) if (librarySearchResults.filters.tags.isNotEmpty)
CategoryChip<String>( CategoryChip<String>(
label: Text(context.localized.label(librarySearchResults.tags.length)), label: Text(context.localized.label(librarySearchResults.filters.tags.length)),
activeIcon: Icons.label_rounded, activeIcon: Icons.label_rounded,
items: librarySearchResults.tags, items: librarySearchResults.filters.tags,
labelBuilder: (item) => Text(item), labelBuilder: (item) => Text(item),
onSave: (value) => libraryProvider.setTags(value), onSave: (value) => libraryProvider.setTags(value),
onCancel: () => libraryProvider.setTags(librarySearchResults.tags), onCancel: () => libraryProvider.setTags(librarySearchResults.filters.tags),
onClear: () => libraryProvider.setTags(librarySearchResults.tags.setAll(false)), onClear: () => libraryProvider.setTags(librarySearchResults.filters.tags.setAll(false)),
), ),
FilterChip( ExpressiveButton(
isSelected: groupBy != GroupBy.none,
icon: groupBy != GroupBy.none ? const Icon(IconsaxPlusBold.bag_tick) : null,
label: Text(context.localized.group), label: Text(context.localized.group),
selected: groupBy != GroupBy.none, onPressed: () {
onSelected: (_) {
_openGroupDialogue(context, ref, libraryProvider, uniqueKey); _openGroupDialogue(context, ref, libraryProvider, uniqueKey);
}, },
), ),
CategoryChip<ItemFilter>( CategoryChip<ItemFilter>(
label: Text(context.localized.filter(librarySearchResults.filters.length)), label: Text(context.localized.filter(librarySearchResults.filters.itemFilters.length)),
items: librarySearchResults.filters, items: librarySearchResults.filters.itemFilters,
labelBuilder: (item) => Text(item.label(context)), labelBuilder: (item) => Text(item.label(context)),
onSave: (value) => libraryProvider.setFilters(value), onSave: (value) => libraryProvider.setFilters(value),
onClear: () => libraryProvider.setFilters(librarySearchResults.filters.setAll(false)), onClear: () => libraryProvider.setFilters(librarySearchResults.filters.itemFilters.setAll(false)),
), ),
if (librarySearchResults.types[FladderItemType.series] == true) if (librarySearchResults.filters.types[FladderItemType.series] == true)
FilterChip( ExpressiveButton(
avatar: Icon( isSelected: !hideEmpty,
hideEmpty ? Icons.visibility_off_rounded : Icons.visibility_rounded, icon: !hideEmpty ? const Icon(IconsaxPlusBold.ghost) : null,
color: Theme.of(context).colorScheme.onSurface, label: Text(!hideEmpty ? context.localized.hideEmpty : context.localized.showEmpty),
onPressed: libraryProvider.toggleEmptyShows,
), ),
selected: hideEmpty, if (librarySearchResults.filters.officialRatings.isNotEmpty)
showCheckmark: false,
label: Text(context.localized.hideEmpty),
onSelected: libraryProvider.setHideEmpty,
),
if (librarySearchResults.officialRatings.isNotEmpty)
CategoryChip<String>( CategoryChip<String>(
label: Text(context.localized.rating(librarySearchResults.officialRatings.length)), label: Text(context.localized.rating(librarySearchResults.filters.officialRatings.length)),
activeIcon: Icons.star_rate_rounded, activeIcon: Icons.star_rate_rounded,
items: librarySearchResults.officialRatings, items: librarySearchResults.filters.officialRatings,
labelBuilder: (item) => Text(item), labelBuilder: (item) => Text(item),
onSave: (value) => libraryProvider.setRatings(value), onSave: (value) => libraryProvider.setRatings(value),
onCancel: () => libraryProvider.setRatings(librarySearchResults.officialRatings), onCancel: () => libraryProvider.setRatings(librarySearchResults.filters.officialRatings),
onClear: () => libraryProvider.setRatings(librarySearchResults.officialRatings.setAll(false)), onClear: () => libraryProvider.setRatings(librarySearchResults.filters.officialRatings.setAll(false)),
), ),
if (librarySearchResults.years.isNotEmpty) if (librarySearchResults.filters.years.isNotEmpty)
CategoryChip<int>( CategoryChip<int>(
label: Text(context.localized.year(librarySearchResults.years.length)), label: Text(context.localized.year(librarySearchResults.filters.years.length)),
items: librarySearchResults.years, items: librarySearchResults.filters.years,
labelBuilder: (item) => Text(item.toString()), labelBuilder: (item) => Text(item.toString()),
onSave: (value) => libraryProvider.setYears(value), onSave: (value) => libraryProvider.setYears(value),
onCancel: () => libraryProvider.setYears(librarySearchResults.years), onCancel: () => libraryProvider.setYears(librarySearchResults.filters.years),
onClear: () => libraryProvider.setYears(librarySearchResults.years.setAll(false)), onClear: () => libraryProvider.setYears(librarySearchResults.filters.years.setAll(false)),
), ),
], ];
return Row(
spacing: 4,
children: chips.mapIndexed(
(index, element) {
final position = index == 0
? PositionContext.first
: (index == chips.length - 1 ? PositionContext.last : PositionContext.middle);
return PositionProvider(position: position, child: element);
},
).toList(),
); );
} }
@ -164,7 +171,7 @@ class _LibraryFilterChipsState extends ConsumerState<LibraryFilterChips> {
showDialog( showDialog(
context: context, context: context,
builder: (context) { builder: (context) {
final groupBy = ref.watch(librarySearchProvider(uniqueKey).select((v) => v.groupBy)); final groupBy = ref.watch(librarySearchProvider(uniqueKey).select((v) => v.filters.groupBy));
return AlertDialog( return AlertDialog(
content: SizedBox( content: SizedBox(
width: MediaQuery.of(context).size.width * 0.65, width: MediaQuery.of(context).size.width * 0.65,

View file

@ -3,41 +3,40 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:iconsax_plus/iconsax_plus.dart'; import 'package:iconsax_plus/iconsax_plus.dart';
import 'package:fladder/models/library_search/library_search_model.dart';
import 'package:fladder/providers/library_search_provider.dart'; import 'package:fladder/providers/library_search_provider.dart';
import 'package:fladder/screens/shared/default_alert_dialog.dart'; import 'package:fladder/screens/shared/default_alert_dialog.dart';
import 'package:fladder/screens/shared/outlined_text_field.dart'; import 'package:fladder/screens/shared/outlined_text_field.dart';
import 'package:fladder/util/list_padding.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
Future<void> showSavedFilters( Future<void> showSavedFilters(
BuildContext context, BuildContext context,
LibrarySearchModel model, Key providerKey,
LibrarySearchNotifier provider,
) { ) {
return showDialog( return showDialog(
context: context, context: context,
builder: (context) => LibrarySavedFiltersDialogue( builder: (context) => LibrarySavedFiltersDialogue(
searchModel: model, providerKey: providerKey,
provider: provider,
), ),
); );
} }
class LibrarySavedFiltersDialogue extends ConsumerWidget { class LibrarySavedFiltersDialogue extends ConsumerWidget {
final LibrarySearchModel searchModel; final Key providerKey;
final LibrarySearchNotifier provider;
const LibrarySavedFiltersDialogue({ const LibrarySavedFiltersDialogue({
required this.searchModel,
required this.provider,
super.key, super.key,
required this.providerKey,
}); });
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final controller = TextEditingController(); final controller = TextEditingController();
final provider = ref.watch(librarySearchProvider(providerKey).notifier);
final currentFilters = ref.watch(librarySearchProvider(providerKey).select((value) => value.filters));
final filters = ref.watch(provider.filterProvider); final filters = ref.watch(provider.filterProvider);
final filterProvider = ref.watch(provider.filterProvider.notifier); final filterProvider = ref.watch(provider.filterProvider.notifier);
final anyFilterSelected = filters.any((element) => element.filter == currentFilters);
return Dialog( return Dialog(
child: Padding( child: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
@ -57,35 +56,45 @@ class LibrarySavedFiltersDialogue extends ConsumerWidget {
children: [ children: [
...filters.map( ...filters.map(
(filter) { (filter) {
final isCurrentFilter = filter.filter == currentFilters;
return Container( return Container(
margin: const EdgeInsets.symmetric(vertical: 4), margin: const EdgeInsets.symmetric(vertical: 4),
child: Card( child: Card(
child: InkWell( color: isCurrentFilter
onTap: () => provider.loadModel(filter), ? Theme.of(context).colorScheme.primaryContainer.withValues(alpha: 0.75)
: null,
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), padding: const EdgeInsets.only(right: 8),
child: Row( child: Row(spacing: 8, children: [
children: [ Expanded(
Expanded(child: Text(filter.name)), child: OutlinedTextField(
fillColor: Colors.transparent,
controller: TextEditingController(text: filter.name),
onSubmitted: (value) => provider.updateFilter(filter.copyWith(name: value)),
),
),
IconButton.filledTonal(
onPressed: isCurrentFilter ? null : () => provider.loadModel(filter.filter),
icon: const Icon(IconsaxPlusBold.filter_add),
),
IconButton.filledTonal( IconButton.filledTonal(
tooltip: context.localized.defaultFilterForLibrary, tooltip: context.localized.defaultFilterForLibrary,
style: ButtonStyle( style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll( backgroundColor: WidgetStatePropertyAll(
filter.isFavourite filter.isFavourite ? Colors.yellowAccent.shade700.withValues(alpha: 0.5) : null,
? Colors.yellowAccent.shade700.withValues(alpha: 0.5)
: null,
), ),
), ),
onPressed: () => onPressed: () =>
filterProvider.saveFilter(filter.copyWith(isFavourite: !filter.isFavourite)), filterProvider.saveFilter(filter.copyWith(isFavourite: !filter.isFavourite)),
icon: Icon( icon: Icon(
color: filter.isFavourite ? Colors.yellowAccent : null, color: filter.isFavourite ? Colors.yellowAccent : null,
filter.isFavourite ? IconsaxPlusBold.star_1 : IconsaxPlusLinear.star, filter.isFavourite ? IconsaxPlusBold.star_1 : IconsaxPlusLinear.star_1,
), ),
), ),
IconButton.filledTonal( IconButton.filledTonal(
tooltip: context.localized.updateFilterForLibrary, tooltip: context.localized.updateFilterForLibrary,
onPressed: () => provider.updateFilter(filter), onPressed:
isCurrentFilter || anyFilterSelected ? null : () => provider.updateFilter(filter),
icon: const Icon(IconsaxPlusBold.refresh), icon: const Icon(IconsaxPlusBold.refresh),
), ),
IconButton.filledTonal( IconButton.filledTonal(
@ -109,16 +118,13 @@ class LibrarySavedFiltersDialogue extends ConsumerWidget {
style: ButtonStyle( style: ButtonStyle(
backgroundColor: backgroundColor:
WidgetStatePropertyAll(Theme.of(context).colorScheme.errorContainer), WidgetStatePropertyAll(Theme.of(context).colorScheme.errorContainer),
iconColor: iconColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.onErrorContainer),
WidgetStatePropertyAll(Theme.of(context).colorScheme.onErrorContainer),
foregroundColor: foregroundColor:
WidgetStatePropertyAll(Theme.of(context).colorScheme.onErrorContainer), WidgetStatePropertyAll(Theme.of(context).colorScheme.onErrorContainer),
), ),
icon: const Icon(IconsaxPlusLinear.trash), icon: const Icon(IconsaxPlusLinear.trash),
), ),
].addInBetween(const SizedBox(width: 8)), ]),
),
),
), ),
), ),
); );
@ -129,11 +135,17 @@ class LibrarySavedFiltersDialogue extends ConsumerWidget {
), ),
const Divider(), const Divider(),
], ],
if (filters.length < 10) if (filters.length < 10 && !anyFilterSelected)
StatefulBuilder(builder: (context, setState) { StatefulBuilder(builder: (context, setState) {
return Row( return Container(
margin: const EdgeInsets.symmetric(vertical: 4),
child: Card(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 12),
child: Row(
spacing: 8,
children: [ children: [
Flexible( Expanded(
child: OutlinedTextField( child: OutlinedTextField(
controller: controller, controller: controller,
label: context.localized.name, label: context.localized.name,
@ -141,19 +153,24 @@ class LibrarySavedFiltersDialogue extends ConsumerWidget {
onSubmitted: (value) => provider.saveFiltersNew(value), onSubmitted: (value) => provider.saveFiltersNew(value),
), ),
), ),
const SizedBox(width: 6), FilledButton(
FilledButton.tonal(
onPressed: controller.text.isEmpty onPressed: controller.text.isEmpty
? null ? null
: () { : () {
provider.saveFiltersNew(controller.text); provider.saveFiltersNew(controller.text);
}, },
child: const Icon(IconsaxPlusLinear.save_2), child: Row(
spacing: 8,
children: [Text(context.localized.save), const Icon(IconsaxPlusLinear.save_2)],
), ),
)
], ],
),
),
),
); );
}) })
else else if (filters.length >= 10)
Text(context.localized.libraryFiltersLimitReached), Text(context.localized.libraryFiltersLimitReached),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {

View file

@ -14,6 +14,7 @@ import 'package:sliver_tools/sliver_tools.dart';
import 'package:fladder/models/boxset_model.dart'; import 'package:fladder/models/boxset_model.dart';
import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/photos_model.dart'; import 'package:fladder/models/items/photos_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/library_search/library_search_options.dart';
import 'package:fladder/models/playlist_model.dart'; import 'package:fladder/models/playlist_model.dart';
import 'package:fladder/providers/library_search_provider.dart'; import 'package:fladder/providers/library_search_provider.dart';
@ -76,7 +77,7 @@ class LibraryViews extends ConsumerWidget {
ref.watch(clientSettingsProvider.select((value) => value.posterSize))); ref.watch(clientSettingsProvider.select((value) => value.posterSize)));
final decimal = posterSize - posterSize.toInt(); final decimal = posterSize - posterSize.toInt();
final sortingOptions = ref.watch(librarySearchProvider(key!).select((value) => value.sortingOption)); final sortingOptions = ref.watch(librarySearchProvider(key!).select((value) => value.filters.sortingOption));
List<ItemAction> otherActions(ItemBaseModel item) { List<ItemAction> otherActions(ItemBaseModel item) {
return [ return [

View file

@ -66,7 +66,7 @@ class _SearchBarState extends ConsumerState<SuggestionSearchBar> {
return Card( return Card(
elevation: 2, elevation: 2,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: FladderTheme.largeShape.borderRadius, borderRadius: FladderTheme.smallShape.borderRadius,
), ),
shadowColor: Colors.transparent, shadowColor: Colors.transparent,
child: TypeAheadField<ItemBaseModel>( child: TypeAheadField<ItemBaseModel>(
@ -83,7 +83,7 @@ class _SearchBarState extends ConsumerState<SuggestionSearchBar> {
decorationBuilder: (context, child) => DecoratedBox( decorationBuilder: (context, child) => DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondaryContainer, color: Theme.of(context).colorScheme.secondaryContainer,
borderRadius: FladderTheme.largeShape.borderRadius, borderRadius: FladderTheme.smallShape.borderRadius,
), ),
child: child, child: child,
), ),

View file

@ -7,6 +7,7 @@ import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
import 'package:fladder/util/list_padding.dart'; import 'package:fladder/util/list_padding.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/map_bool_helper.dart'; import 'package:fladder/util/map_bool_helper.dart';
import 'package:fladder/widgets/shared/button_group.dart';
import 'package:fladder/widgets/shared/modal_bottom_sheet.dart'; import 'package:fladder/widgets/shared/modal_bottom_sheet.dart';
import 'package:fladder/widgets/shared/modal_side_sheet.dart'; import 'package:fladder/widgets/shared/modal_side_sheet.dart';
@ -20,7 +21,6 @@ class CategoryChip<T> extends StatelessWidget {
final VoidCallback? onCancel; final VoidCallback? onCancel;
final VoidCallback? onClear; final VoidCallback? onClear;
final VoidCallback? onDismiss; final VoidCallback? onDismiss;
const CategoryChip({ const CategoryChip({
required this.label, required this.label,
this.dialogueTitle, this.dialogueTitle,
@ -37,37 +37,21 @@ class CategoryChip<T> extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var selection = items.included.isNotEmpty; var selection = items.included.isNotEmpty;
return FilterChip( return ExpressiveButton(
selected: selection, isSelected: selection,
showCheckmark: activeIcon == null, icon: selection ? Icon(activeIcon ?? IconsaxPlusBold.archive_tick) : null,
label: Row( label: Row(
mainAxisSize: MainAxisSize.min, spacing: 6,
children: [ children: [
if (activeIcon != null)
AnimatedSize(
duration: const Duration(milliseconds: 250),
child: selection
? Padding(
padding: const EdgeInsets.only(right: 12),
child: Icon(
activeIcon!,
size: 20,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
)
: const SizedBox(),
),
label, label,
const SizedBox(width: 8), const Icon(
Icon( IconsaxPlusLinear.arrow_down,
Icons.arrow_drop_down_rounded, size: 16,
size: 20, )
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
], ],
), ),
onSelected: items.isNotEmpty onPressed: items.isNotEmpty
? (_) async { ? () async {
final newEntry = await openActionSheet(context); final newEntry = await openActionSheet(context);
if (newEntry != null) { if (newEntry != null) {
onSave?.call(newEntry); onSave?.call(newEntry);

View file

@ -189,7 +189,7 @@ class _DetailScaffoldState extends ConsumerState<DetailScaffold> {
onPressed: () => context.router.popBack(), onPressed: () => context.router.popBack(),
icon: Padding( icon: Padding(
padding: EdgeInsets.all(AdaptiveLayout.of(context).inputDevice == InputDevice.pointer ? 0 : 4), padding: EdgeInsets.all(AdaptiveLayout.of(context).inputDevice == InputDevice.pointer ? 0 : 4),
child: const Icon(IconsaxPlusLinear.arrow_left_2), child: const BackButtonIcon(),
), ),
), ),
const Spacer(), const Spacer(),

View file

@ -83,6 +83,8 @@ class _PosterImageState extends ConsumerState<PosterImage> {
await widget.poster.navigateTo(context); await widget.poster.navigateTo(context);
} }
final posterRadius = FladderTheme.smallShape.borderRadius;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final poster = widget.poster; final poster = widget.poster;
@ -101,7 +103,7 @@ class _PosterImageState extends ConsumerState<PosterImage> {
width: 1.0, width: 1.0,
color: Colors.white.withValues(alpha: 0.10), color: Colors.white.withValues(alpha: 0.10),
), ),
borderRadius: FladderTheme.defaultShape.borderRadius, borderRadius: posterRadius,
), ),
child: Stack( child: Stack(
fit: StackFit.expand, fit: StackFit.expand,
@ -135,7 +137,7 @@ class _PosterImageState extends ConsumerState<PosterImage> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.15), color: Colors.black.withValues(alpha: 0.15),
border: Border.all(width: 3, color: Theme.of(context).colorScheme.primary), border: Border.all(width: 3, color: Theme.of(context).colorScheme.primary),
borderRadius: FladderTheme.defaultShape.borderRadius, borderRadius: posterRadius,
), ),
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
child: Stack( child: Stack(
@ -201,6 +203,102 @@ class _PosterImageState extends ConsumerState<PosterImage> {
], ],
), ),
), ),
if (widget.poster.unWatched)
Align(
alignment: Alignment.topLeft,
child: StatusCard(
color: Colors.amber,
child: Padding(
padding: const EdgeInsets.all(10),
child: Container(
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.amber,
),
),
),
),
),
if (widget.inlineTitle)
IgnorePointer(
child: Align(
alignment: Alignment.topLeft,
child: Padding(
padding: const EdgeInsets.all(8),
child: Text(
widget.poster.title.maxLength(limitTo: 25),
style:
Theme.of(context).textTheme.labelLarge?.copyWith(fontSize: 20, fontWeight: FontWeight.bold),
),
),
),
),
if (widget.poster.unPlayedItemCount != null && widget.poster is SeriesModel)
IgnorePointer(
child: Align(
alignment: Alignment.topRight,
child: StatusCard(
color: Theme.of(context).colorScheme.primary,
useFittedBox: widget.poster.unPlayedItemCount != 0,
child: Padding(
padding: const EdgeInsets.all(6),
child: widget.poster.unPlayedItemCount != 0
? Container(
constraints: const BoxConstraints(minWidth: 16),
child: Text(
widget.poster.userData.unPlayedItemCount.toString(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold,
overflow: TextOverflow.visible,
fontSize: 14,
),
),
)
: Icon(
Icons.check_rounded,
size: 20,
color: Theme.of(context).colorScheme.primary,
),
),
),
),
),
if (widget.poster.overview.runTime != null &&
((widget.poster is PhotoModel) &&
(widget.poster as PhotoModel).internalType == FladderItemType.video)) ...{
Align(
alignment: Alignment.topRight,
child: Padding(
padding: padding,
child: Card(
elevation: 5,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
widget.poster.overview.runTime.humanizeSmall ?? "",
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onSurface,
),
),
const SizedBox(width: 2),
Icon(
Icons.play_arrow_rounded,
color: Theme.of(context).colorScheme.onSurface,
),
],
),
),
),
),
)
},
//Desktop overlay //Desktop overlay
if (AdaptiveLayout.of(context).inputDevice != InputDevice.touch && if (AdaptiveLayout.of(context).inputDevice != InputDevice.touch &&
widget.poster.type != FladderItemType.person) widget.poster.type != FladderItemType.person)
@ -215,7 +313,7 @@ class _PosterImageState extends ConsumerState<PosterImage> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.55), color: Colors.black.withValues(alpha: 0.55),
border: Border.all(width: 3, color: Theme.of(context).colorScheme.primary), border: Border.all(width: 3, color: Theme.of(context).colorScheme.primary),
borderRadius: FladderTheme.defaultShape.borderRadius, borderRadius: posterRadius,
)), )),
//Poster Button //Poster Button
Focus( Focus(
@ -317,102 +415,6 @@ class _PosterImageState extends ConsumerState<PosterImage> {
}, },
), ),
), ),
if (widget.poster.unWatched)
Align(
alignment: Alignment.topLeft,
child: StatusCard(
color: Colors.amber,
child: Padding(
padding: const EdgeInsets.all(10),
child: Container(
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.amber,
),
),
),
),
),
if (widget.inlineTitle)
IgnorePointer(
child: Align(
alignment: Alignment.topLeft,
child: Padding(
padding: const EdgeInsets.all(8),
child: Text(
widget.poster.title.maxLength(limitTo: 25),
style:
Theme.of(context).textTheme.labelLarge?.copyWith(fontSize: 20, fontWeight: FontWeight.bold),
),
),
),
),
if (widget.poster.unPlayedItemCount != null && widget.poster is SeriesModel)
IgnorePointer(
child: Align(
alignment: Alignment.topRight,
child: StatusCard(
color: Theme.of(context).colorScheme.primary,
useFittedBox: widget.poster.unPlayedItemCount != 0,
child: Padding(
padding: const EdgeInsets.all(6),
child: widget.poster.unPlayedItemCount != 0
? Container(
constraints: const BoxConstraints(minWidth: 16),
child: Text(
widget.poster.userData.unPlayedItemCount.toString(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold,
overflow: TextOverflow.visible,
fontSize: 14,
),
),
)
: Icon(
Icons.check_rounded,
size: 20,
color: Theme.of(context).colorScheme.primary,
),
),
),
),
),
if (widget.poster.overview.runTime != null &&
((widget.poster is PhotoModel) &&
(widget.poster as PhotoModel).internalType == FladderItemType.video)) ...{
Align(
alignment: Alignment.topRight,
child: Padding(
padding: padding,
child: Card(
elevation: 5,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
widget.poster.overview.runTime.humanizeSmall ?? "",
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onSurface,
),
),
const SizedBox(width: 2),
Icon(
Icons.play_arrow_rounded,
color: Theme.of(context).colorScheme.onSurface,
),
],
),
),
),
),
)
}
], ],
), ),
), ),

View file

@ -27,7 +27,7 @@ class NestedScaffold extends ConsumerWidget {
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
colors: [ colors: [
Theme.of(context).colorScheme.surface.withValues(alpha: backgroundOpacity), Theme.of(context).colorScheme.surface.withValues(alpha: backgroundOpacity),
Theme.of(context).colorScheme.surface.withValues(alpha: backgroundOpacity - 0.15), Theme.of(context).colorScheme.surface.withValues(alpha: backgroundOpacity / 1.5),
], ],
), ),
), ),

View file

@ -1,9 +1,11 @@
import 'package:fladder/screens/shared/animated_fade_size.dart';
import 'package:fladder/theme.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/screens/shared/animated_fade_size.dart';
import 'package:fladder/theme.dart';
class OutlinedTextField extends ConsumerStatefulWidget { class OutlinedTextField extends ConsumerStatefulWidget {
final String? label; final String? label;
final FocusNode? focusNode; final FocusNode? focusNode;

View file

@ -37,15 +37,30 @@ extension MapExtensions<T> on Map<T, bool> {
bool get hasEnabled => values.any((element) => element == true); bool get hasEnabled => values.any((element) => element == true);
//Replaces only keys that exist with the new values //Replaces only keys that exist with the new values
Map<T, bool> replaceMap(Map<T, bool> oldMap) { Map<T, bool> replaceMap(Map<T, bool> oldMap, {bool enabledOnly = false}) {
if (oldMap.isEmpty) return this;
Map<T, bool> result = {}; Map<T, bool> result = {};
forEach((key, value) { forEach((key, value) {
if (enabledOnly) {
if (oldMap[key] == true) {
result[key] = true;
} else {
result[key] = value;
}
} else {
result[key] = oldMap[key] ?? false; result[key] = oldMap[key] ?? false;
}
}); });
return result; return result;
} }
Map<T, bool> sortByKey(String Function(T value) keySelector) {
final sortedEntries = entries.toList()..sort((a, b) => keySelector(a.key).compareTo(keySelector(b.key)));
return Map<T, bool>.fromEntries(sortedEntries);
}
} }
extension MapExtensionsGeneric<K, V> on Map<K, V> { extension MapExtensionsGeneric<K, V> on Map<K, V> {

View file

@ -0,0 +1,22 @@
import 'package:flutter/widgets.dart';
enum PositionContext { first, middle, last }
class PositionProvider extends InheritedWidget {
final PositionContext position;
const PositionProvider({
required this.position,
required super.child,
super.key,
});
static PositionContext of(BuildContext context) {
final provider = context.dependOnInheritedWidgetOfExactType<PositionProvider>();
assert(provider != null, 'No PositionProvider found in context');
return provider?.position ?? PositionContext.middle;
}
@override
bool updateShouldNotify(PositionProvider oldWidget) => position != oldWidget.position;
}

View file

@ -10,6 +10,8 @@ import 'package:fladder/providers/api_provider.dart';
import 'package:fladder/providers/settings/client_settings_provider.dart'; import 'package:fladder/providers/settings/client_settings_provider.dart';
import 'package:fladder/util/fladder_image.dart'; import 'package:fladder/util/fladder_image.dart';
final _backgroundImageProvider = StateProvider<ImageData?>((ref) => null);
class BackgroundImage extends ConsumerStatefulWidget { class BackgroundImage extends ConsumerStatefulWidget {
final List<ItemBaseModel> items; final List<ItemBaseModel> items;
final List<ImagesData> images; final List<ImagesData> images;
@ -20,7 +22,11 @@ class BackgroundImage extends ConsumerStatefulWidget {
} }
class _BackgroundImageState extends ConsumerState<BackgroundImage> { class _BackgroundImageState extends ConsumerState<BackgroundImage> {
ImageData? backgroundImage; @override
void initState() {
super.initState();
updateItems();
}
@override @override
void didUpdateWidget(covariant BackgroundImage oldWidget) { void didUpdateWidget(covariant BackgroundImage oldWidget) {
@ -30,27 +36,18 @@ class _BackgroundImageState extends ConsumerState<BackgroundImage> {
} }
} }
@override
void initState() {
super.initState();
updateItems();
}
void updateItems() { void updateItems() {
final enabled = WidgetsBinding.instance.addPostFrameCallback((_) async {
ref.read(clientSettingsProvider.select((value) => value.backgroundImage != BackgroundType.disabled)); final enabled = ref.read(
clientSettingsProvider.select((value) => value.backgroundImage != BackgroundType.disabled),
);
if (!enabled || !mounted) return;
WidgetsBinding.instance.addPostFrameCallback((value) async { ImageData? newImage;
if (!enabled && mounted) return;
if (widget.images.isNotEmpty) { if (widget.images.isNotEmpty) {
final image = widget.images.shuffled().firstOrNull?.primary; newImage = widget.images.shuffled().firstOrNull?.primary;
if (mounted) setState(() => backgroundImage = image); } else if (widget.items.isNotEmpty) {
return;
}
if (widget.items.isEmpty) return;
final randomItem = widget.items.shuffled().firstOrNull; final randomItem = widget.items.shuffled().firstOrNull;
final itemId = switch (randomItem?.type) { final itemId = switch (randomItem?.type) {
FladderItemType.folder => randomItem?.id, FladderItemType.folder => randomItem?.id,
@ -58,27 +55,46 @@ class _BackgroundImageState extends ConsumerState<BackgroundImage> {
_ => randomItem?.id, _ => randomItem?.id,
}; };
if (itemId == null) return; if (itemId != null) {
final apiResponse = await ref.read(jellyApiProvider).usersUserIdItemsItemIdGet(itemId: itemId); final apiResponse = await ref.read(jellyApiProvider).usersUserIdItemsItemIdGet(itemId: itemId);
final image = apiResponse.body?.parentBaseModel.getPosters?.randomBackDrop ??
newImage = apiResponse.body?.parentBaseModel.getPosters?.randomBackDrop ??
apiResponse.body?.getPosters?.randomBackDrop ?? apiResponse.body?.getPosters?.randomBackDrop ??
apiResponse.body?.getPosters?.primary; apiResponse.body?.getPosters?.primary;
}
}
if (mounted) setState(() => backgroundImage = image); if (newImage != null && mounted) {
ref.read(_backgroundImageProvider.notifier).state = newImage;
}
}); });
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final state = ref.watch(clientSettingsProvider.select((value) => value.backgroundImage)); final settings = ref.watch(clientSettingsProvider.select((value) => value.backgroundImage));
final enabled = state != BackgroundType.disabled; final enabled = settings != BackgroundType.disabled;
return enabled final image = ref.watch(_backgroundImageProvider);
? FladderImage(
image: backgroundImage, if (!enabled || image == null) return const SizedBox.shrink();
return AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
layoutBuilder: (currentChild, previousChildren) {
return Stack(
fit: StackFit.expand,
children: [
...previousChildren,
if (currentChild != null) currentChild,
],
);
},
child: FladderImage(
key: ValueKey(image.key),
image: image,
fit: BoxFit.cover, fit: BoxFit.cover,
blurOnly: state == BackgroundType.blurred, blurOnly: settings == BackgroundType.blurred,
) ),
: const SizedBox.shrink(); );
} }
} }

View file

@ -64,7 +64,8 @@ class _SideNavigationBarState extends ConsumerState<SideNavigationBar> {
children: [ children: [
AdaptiveLayoutBuilder( AdaptiveLayoutBuilder(
adaptiveLayout: AdaptiveLayout.of(context).copyWith( adaptiveLayout: AdaptiveLayout.of(context).copyWith(
sideBarWidth: fullyExpanded ? expandedWidth : collapsedWidth, // -0.1 offset to fix single visible pixel line
sideBarWidth: (fullyExpanded ? expandedWidth : collapsedWidth) - 0.1,
), ),
child: (context) => widget.child, child: (context) => widget.child,
), ),

View file

@ -1,5 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fladder/util/position_provider.dart';
class ExpressiveButtonGroup<T> extends StatelessWidget { class ExpressiveButtonGroup<T> extends StatelessWidget {
final List<ButtonGroupOption<T>> options; final List<ButtonGroupOption<T>> options;
final Set<T> selectedValues; final Set<T> selectedValues;
@ -20,29 +22,22 @@ class ExpressiveButtonGroup<T> extends StatelessWidget {
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
spacing: 2, spacing: 2,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: List.generate(options.length, (index) { children: List.generate(
options.length,
(index) {
final option = options[index]; final option = options[index];
final isSelected = selectedValues.contains(option.value); final isSelected = selectedValues.contains(option.value);
final isFirst = index == 0;
final isLast = index == options.length - 1;
final borderRadius = BorderRadius.horizontal( final position = index == 0
left: isSelected || isFirst ? const Radius.circular(20) : const Radius.circular(6), ? PositionContext.first
right: isSelected || isLast ? const Radius.circular(20) : const Radius.circular(6), : (index == options.length - 1 ? PositionContext.last : PositionContext.middle);
);
return ElevatedButton.icon( return PositionProvider(
style: ElevatedButton.styleFrom( position: position,
shape: RoundedRectangleBorder(borderRadius: borderRadius), child: ExpressiveButton(
elevation: isSelected ? 3 : 0, isSelected: isSelected,
backgroundColor: isSelected label: option.child,
? Theme.of(context).colorScheme.primary icon: isSelected ? option.selected ?? const Icon(Icons.check_rounded) : option.icon,
: Theme.of(context).colorScheme.surfaceContainerHighest,
foregroundColor:
isSelected ? Theme.of(context).colorScheme.onPrimary : Theme.of(context).colorScheme.onSurfaceVariant,
textStyle: Theme.of(context).textTheme.labelLarge,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () { onPressed: () {
final newSet = Set<T>.from(selectedValues); final newSet = Set<T>.from(selectedValues);
if (multiSelection) { if (multiSelection) {
@ -54,10 +49,50 @@ class ExpressiveButtonGroup<T> extends StatelessWidget {
} }
onSelected(newSet); onSelected(newSet);
}, },
label: option.child, ),
icon: isSelected ? option.selected ?? const Icon(Icons.check_rounded) : option.icon,
); );
}), },
),
);
}
}
class ExpressiveButton extends StatelessWidget {
const ExpressiveButton({
super.key,
required this.isSelected,
required this.label,
this.icon,
required this.onPressed,
});
final bool isSelected;
final Widget label;
final Widget? icon;
final Function()? onPressed;
@override
Widget build(BuildContext context) {
final position = PositionProvider.of(context);
final borderRadius = BorderRadius.horizontal(
left: isSelected || position == PositionContext.first ? const Radius.circular(16) : const Radius.circular(4),
right: isSelected || position == PositionContext.last ? const Radius.circular(16) : const Radius.circular(4),
);
return ElevatedButton.icon(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(borderRadius: borderRadius),
elevation: isSelected ? 4 : 0,
backgroundColor:
isSelected ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.surfaceContainerHighest,
foregroundColor:
isSelected ? Theme.of(context).colorScheme.onPrimary : Theme.of(context).colorScheme.onSurfaceVariant,
textStyle: Theme.of(context).textTheme.labelLarge,
visualDensity: VisualDensity.comfortable,
padding: const EdgeInsets.all(12),
),
onPressed: onPressed,
label: label,
icon: icon,
); );
} }
} }

View file

@ -11,6 +11,7 @@ class HideOnScroll extends ConsumerStatefulWidget {
final double height; final double height;
final Widget? Function(bool visible)? visibleBuilder; final Widget? Function(bool visible)? visibleBuilder;
final Duration duration; final Duration duration;
final bool canHide;
final bool forceHide; final bool forceHide;
const HideOnScroll({ const HideOnScroll({
this.child, this.child,
@ -18,6 +19,7 @@ class HideOnScroll extends ConsumerStatefulWidget {
this.height = kBottomNavigationBarHeight, this.height = kBottomNavigationBarHeight,
this.visibleBuilder, this.visibleBuilder,
this.duration = const Duration(milliseconds: 200), this.duration = const Duration(milliseconds: 200),
this.canHide = true,
this.forceHide = false, this.forceHide = false,
super.key, super.key,
}) : assert(child != null || visibleBuilder != null); }) : assert(child != null || visibleBuilder != null);
@ -46,6 +48,12 @@ class _HideOnScrollState extends ConsumerState<HideOnScroll> {
} }
void _onScroll() { void _onScroll() {
if (!widget.canHide) {
if (!isVisible) {
setState(() => isVisible = true);
}
return;
}
final position = scrollController.position; final position = scrollController.position;
final direction = position.userScrollDirection; final direction = position.userScrollDirection;
@ -78,9 +86,9 @@ class _HideOnScrollState extends ConsumerState<HideOnScroll> {
alignment: const Alignment(0, -1), alignment: const Alignment(0, -1),
heightFactor: widget.forceHide heightFactor: widget.forceHide
? 0 ? 0
: isVisible : !isVisible && widget.canHide
? 1.0 ? 0.0
: 0, : 1.0,
duration: widget.duration, duration: widget.duration,
child: Wrap( child: Wrap(
children: [widget.child!], children: [widget.child!],