[iOS & tvOS] ItemLibraryViewModel - Cleanup (#1411)
* Move ItemType to Filter * Init but normally... * filter on people? * Default to easiest / least change solution. * Reset `.collectionFolder`, `.folder`, and `.BaseItemPerson` in `PagingLibraryView` to have the default filters. This was originally in place. This Commit just ensures that iOS and tvOS have the same implementation. * wip * Update ItemLibraryViewModel.swift * Update ItemLibraryViewModel.swift --------- Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com>
This commit is contained in:
parent
21cf7865c3
commit
3ee2abec5c
|
@ -48,9 +48,8 @@ final class MainTabCoordinator: TabCoordinatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeTVShows() -> NavigationViewCoordinator<LibraryCoordinator<BaseItemDto>> {
|
func makeTVShows() -> NavigationViewCoordinator<LibraryCoordinator<BaseItemDto>> {
|
||||||
let viewModel = ItemTypeLibraryViewModel(
|
let viewModel = ItemLibraryViewModel(
|
||||||
itemTypes: [.series],
|
filters: .init(itemTypes: [.series])
|
||||||
filters: .default
|
|
||||||
)
|
)
|
||||||
return NavigationViewCoordinator(LibraryCoordinator(viewModel: viewModel))
|
return NavigationViewCoordinator(LibraryCoordinator(viewModel: viewModel))
|
||||||
}
|
}
|
||||||
|
@ -65,9 +64,8 @@ final class MainTabCoordinator: TabCoordinatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeMovies() -> NavigationViewCoordinator<LibraryCoordinator<BaseItemDto>> {
|
func makeMovies() -> NavigationViewCoordinator<LibraryCoordinator<BaseItemDto>> {
|
||||||
let viewModel = ItemTypeLibraryViewModel(
|
let viewModel = ItemLibraryViewModel(
|
||||||
itemTypes: [.movie],
|
filters: .init(itemTypes: [.movie])
|
||||||
filters: .default
|
|
||||||
)
|
)
|
||||||
return NavigationViewCoordinator(LibraryCoordinator(viewModel: viewModel))
|
return NavigationViewCoordinator(LibraryCoordinator(viewModel: viewModel))
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ extension BaseItemDto: Displayable {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension BaseItemDto: LibraryParent {
|
extension BaseItemDto: LibraryParent {
|
||||||
|
|
||||||
var libraryType: BaseItemKind? {
|
var libraryType: BaseItemKind? {
|
||||||
type
|
type
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import JellyfinAPI
|
||||||
|
|
||||||
|
extension BaseItemKind: SupportedCaseIterable {
|
||||||
|
|
||||||
|
/// The base supported cases for media navigation.
|
||||||
|
/// This differs from media viewing, which may include
|
||||||
|
/// `.episode`.
|
||||||
|
///
|
||||||
|
/// These is the *base* supported cases and other objects
|
||||||
|
/// like `LibararyParent` may have additional supported
|
||||||
|
/// cases for querying a library.
|
||||||
|
static var supportedCases: [BaseItemKind] {
|
||||||
|
[.movie, .series, .boxSet]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension BaseItemKind: ItemFilter {
|
||||||
|
|
||||||
|
// TODO: localize
|
||||||
|
var displayTitle: String {
|
||||||
|
rawValue
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ extension BaseItemPerson: Displayable {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension BaseItemPerson: LibraryParent {
|
extension BaseItemPerson: LibraryParent {
|
||||||
|
|
||||||
var libraryType: BaseItemKind? {
|
var libraryType: BaseItemKind? {
|
||||||
.person
|
.person
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import JellyfinAPI
|
||||||
struct ItemFilterCollection: Codable, Defaults.Serializable, Hashable {
|
struct ItemFilterCollection: Codable, Defaults.Serializable, Hashable {
|
||||||
|
|
||||||
var genres: [ItemGenre] = []
|
var genres: [ItemGenre] = []
|
||||||
|
var itemTypes: [BaseItemKind] = []
|
||||||
var letter: [ItemLetter] = []
|
var letter: [ItemLetter] = []
|
||||||
var sortBy: [ItemSortBy] = [ItemSortBy.name]
|
var sortBy: [ItemSortBy] = [ItemSortBy.name]
|
||||||
var sortOrder: [ItemSortOrder] = [ItemSortOrder.ascending]
|
var sortOrder: [ItemSortOrder] = [ItemSortOrder.ascending]
|
||||||
|
@ -32,7 +33,10 @@ struct ItemFilterCollection: Codable, Defaults.Serializable, Hashable {
|
||||||
sortOrder: [ItemSortOrder.descending]
|
sortOrder: [ItemSortOrder.descending]
|
||||||
)
|
)
|
||||||
|
|
||||||
/// A collection that has all statically available values
|
/// A collection that has all statically available values.
|
||||||
|
///
|
||||||
|
/// These may be altered when used to better represent all
|
||||||
|
/// available values within the current context.
|
||||||
static let all: ItemFilterCollection = .init(
|
static let all: ItemFilterCollection = .init(
|
||||||
letter: ItemLetter.allCases,
|
letter: ItemLetter.allCases,
|
||||||
sortBy: ItemSortBy.allCases,
|
sortBy: ItemSortBy.allCases,
|
||||||
|
|
|
@ -6,17 +6,58 @@
|
||||||
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors
|
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
|
||||||
protocol LibraryParent: Displayable, Hashable, Identifiable<String?> {
|
protocol LibraryParent: Displayable, Hashable, Identifiable<String?> {
|
||||||
|
|
||||||
// Only called `libraryType` because `BaseItemPerson` has
|
/// The type of the library, reusing `BaseItemKind` for some
|
||||||
// a different `type` property. However, people should have
|
/// ease of provided variety like `folder` and `userView`.
|
||||||
// different views so this can be renamed when they do, or
|
|
||||||
// this protocol to be removed entirely and replace just with
|
|
||||||
// a concrete `BaseItemDto`
|
|
||||||
//
|
|
||||||
// edit: studios also implement `LibraryParent` - reconsider above comment
|
|
||||||
var libraryType: BaseItemKind? { get }
|
var libraryType: BaseItemKind? { get }
|
||||||
|
|
||||||
|
/// The `BaseItemKind` types that this library parent
|
||||||
|
/// support. Mainly used for `.folder` support.
|
||||||
|
///
|
||||||
|
/// When using filters, this is used to determine the initial
|
||||||
|
/// set of supported types and then
|
||||||
|
var supportedItemTypes: [BaseItemKind] { get }
|
||||||
|
|
||||||
|
/// Modifies the parameters for the items request per this library parent.
|
||||||
|
func setParentParameters(_ parameters: Paths.GetItemsByUserIDParameters) -> Paths.GetItemsByUserIDParameters
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LibraryParent {
|
||||||
|
|
||||||
|
var supportedItemTypes: [BaseItemKind] {
|
||||||
|
switch libraryType {
|
||||||
|
case .folder:
|
||||||
|
BaseItemKind.supportedCases
|
||||||
|
.appending([.folder, .collectionFolder])
|
||||||
|
default:
|
||||||
|
BaseItemKind.supportedCases
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setParentParameters(_ parameters: Paths.GetItemsByUserIDParameters) -> Paths.GetItemsByUserIDParameters {
|
||||||
|
|
||||||
|
guard let id else { return parameters }
|
||||||
|
|
||||||
|
var parameters = parameters
|
||||||
|
parameters.isRecursive = true
|
||||||
|
parameters.includeItemTypes = supportedItemTypes
|
||||||
|
|
||||||
|
switch libraryType {
|
||||||
|
case .collectionFolder, .userView:
|
||||||
|
parameters.parentID = id
|
||||||
|
case .folder:
|
||||||
|
parameters.parentID = id
|
||||||
|
parameters.isRecursive = nil
|
||||||
|
case .person:
|
||||||
|
parameters.personIDs = [id]
|
||||||
|
case .studio:
|
||||||
|
parameters.studioIDs = [id]
|
||||||
|
default: ()
|
||||||
|
}
|
||||||
|
|
||||||
|
return parameters
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,26 +19,18 @@ final class FilterViewModel: ViewModel {
|
||||||
var allFilters: ItemFilterCollection = .all
|
var allFilters: ItemFilterCollection = .all
|
||||||
|
|
||||||
private let parent: (any LibraryParent)?
|
private let parent: (any LibraryParent)?
|
||||||
private let itemTypes: [BaseItemKind]?
|
|
||||||
|
|
||||||
init(
|
init(
|
||||||
parent: (any LibraryParent)? = nil,
|
parent: (any LibraryParent)? = nil,
|
||||||
currentFilters: ItemFilterCollection = .default
|
currentFilters: ItemFilterCollection = .default
|
||||||
) {
|
) {
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.itemTypes = nil
|
|
||||||
self.currentFilters = currentFilters
|
self.currentFilters = currentFilters
|
||||||
super.init()
|
super.init()
|
||||||
}
|
|
||||||
|
|
||||||
init(
|
if let parent {
|
||||||
itemTypes: [BaseItemKind],
|
self.allFilters.itemTypes = parent.supportedItemTypes
|
||||||
currentFilters: ItemFilterCollection = .default
|
}
|
||||||
) {
|
|
||||||
self.parent = nil
|
|
||||||
self.itemTypes = itemTypes
|
|
||||||
self.currentFilters = currentFilters
|
|
||||||
super.init()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the query filters from the parent
|
/// Sets the query filters from the parent
|
||||||
|
@ -53,10 +45,10 @@ final class FilterViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getQueryFilters() async -> (genres: [ItemGenre], tags: [ItemTag], years: [ItemYear]) {
|
private func getQueryFilters() async -> (genres: [ItemGenre], tags: [ItemTag], years: [ItemYear]) {
|
||||||
|
|
||||||
let parameters = Paths.GetQueryFiltersLegacyParameters(
|
let parameters = Paths.GetQueryFiltersLegacyParameters(
|
||||||
userID: userSession.user.id,
|
userID: userSession.user.id,
|
||||||
parentID: parent?.id as? String,
|
parentID: parent?.id
|
||||||
includeItemTypes: itemTypes
|
|
||||||
)
|
)
|
||||||
|
|
||||||
let request = Paths.getQueryFiltersLegacy(parameters: parameters)
|
let request = Paths.getQueryFiltersLegacy(parameters: parameters)
|
||||||
|
|
|
@ -48,42 +48,23 @@ final class ItemLibraryViewModel: PagingLibraryViewModel<BaseItemDto> {
|
||||||
|
|
||||||
// MARK: item parameters
|
// MARK: item parameters
|
||||||
|
|
||||||
func itemParameters(for page: Int?) -> Paths.GetItemsByUserIDParameters {
|
private func itemParameters(for page: Int?) -> Paths.GetItemsByUserIDParameters {
|
||||||
|
|
||||||
var libraryID: String?
|
|
||||||
var personIDs: [String]?
|
|
||||||
var studioIDs: [String]?
|
|
||||||
var includeItemTypes: [BaseItemKind] = [.movie, .series, .boxSet]
|
|
||||||
var isRecursive: Bool? = true
|
|
||||||
|
|
||||||
// TODO: this logic should be moved to a `LibraryParent` function
|
|
||||||
// that transforms a `GetItemsByUserIDParameters` struct, instead
|
|
||||||
// of having to do this case-by-case.
|
|
||||||
|
|
||||||
if let libraryType = parent?.libraryType, let id = parent?.id {
|
|
||||||
switch libraryType {
|
|
||||||
case .collectionFolder, .userView:
|
|
||||||
libraryID = id
|
|
||||||
case .folder:
|
|
||||||
libraryID = id
|
|
||||||
isRecursive = nil
|
|
||||||
includeItemTypes = [.movie, .series, .boxSet, .folder, .collectionFolder]
|
|
||||||
case .person:
|
|
||||||
personIDs = [id]
|
|
||||||
case .studio:
|
|
||||||
studioIDs = [id]
|
|
||||||
default: ()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var parameters = Paths.GetItemsByUserIDParameters()
|
var parameters = Paths.GetItemsByUserIDParameters()
|
||||||
|
|
||||||
parameters.enableUserData = true
|
parameters.enableUserData = true
|
||||||
parameters.fields = .MinimumFields
|
parameters.fields = .MinimumFields
|
||||||
parameters.includeItemTypes = includeItemTypes
|
|
||||||
parameters.isRecursive = isRecursive
|
// Default values, expected to be overridden
|
||||||
parameters.parentID = libraryID
|
// by parent or filters
|
||||||
parameters.personIDs = personIDs
|
parameters.includeItemTypes = BaseItemKind.supportedCases
|
||||||
parameters.studioIDs = studioIDs
|
parameters.sortOrder = [.ascending]
|
||||||
|
parameters.sortBy = [ItemSortBy.name.rawValue]
|
||||||
|
|
||||||
|
// Parent
|
||||||
|
if let parent {
|
||||||
|
parameters = parent.setParentParameters(parameters)
|
||||||
|
}
|
||||||
|
|
||||||
// Page size
|
// Page size
|
||||||
if let page {
|
if let page {
|
||||||
|
@ -101,6 +82,12 @@ final class ItemLibraryViewModel: PagingLibraryViewModel<BaseItemDto> {
|
||||||
parameters.tags = filters.tags.map(\.value)
|
parameters.tags = filters.tags.map(\.value)
|
||||||
parameters.years = filters.years.compactMap { Int($0.value) }
|
parameters.years = filters.years.compactMap { Int($0.value) }
|
||||||
|
|
||||||
|
// Only set filtering on item types if selected, where
|
||||||
|
// supported values should have been set by the parent.
|
||||||
|
if filters.itemTypes.isNotEmpty {
|
||||||
|
parameters.includeItemTypes = filters.itemTypes
|
||||||
|
}
|
||||||
|
|
||||||
if filters.letter.first?.value == "#" {
|
if filters.letter.first?.value == "#" {
|
||||||
parameters.nameLessThan = "A"
|
parameters.nameLessThan = "A"
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,105 +0,0 @@
|
||||||
//
|
|
||||||
// Swiftfin is subject to the terms of the Mozilla Public
|
|
||||||
// License, v2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors
|
|
||||||
//
|
|
||||||
|
|
||||||
import Combine
|
|
||||||
import Foundation
|
|
||||||
import Get
|
|
||||||
import JellyfinAPI
|
|
||||||
|
|
||||||
// TODO: filtering on `itemTypes` should be moved to `ItemFilterCollection`,
|
|
||||||
// but there is additional logic based on the parent type, mainly `.folder`.
|
|
||||||
final class ItemTypeLibraryViewModel: PagingLibraryViewModel<BaseItemDto> {
|
|
||||||
|
|
||||||
let itemTypes: [BaseItemKind]
|
|
||||||
|
|
||||||
// MARK: Initializer
|
|
||||||
|
|
||||||
init(
|
|
||||||
itemTypes: [BaseItemKind],
|
|
||||||
filters: ItemFilterCollection? = nil
|
|
||||||
) {
|
|
||||||
self.itemTypes = itemTypes
|
|
||||||
|
|
||||||
super.init(
|
|
||||||
itemTypes: itemTypes,
|
|
||||||
filters: filters
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Get Page
|
|
||||||
|
|
||||||
override func get(page: Int) async throws -> [BaseItemDto] {
|
|
||||||
|
|
||||||
let parameters = itemParameters(for: page)
|
|
||||||
let request = Paths.getItemsByUserID(userID: userSession.user.id, parameters: parameters)
|
|
||||||
let response = try await userSession.client.send(request)
|
|
||||||
|
|
||||||
return response.value.items ?? []
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Item Parameters
|
|
||||||
|
|
||||||
func itemParameters(for page: Int?) -> Paths.GetItemsByUserIDParameters {
|
|
||||||
|
|
||||||
var parameters = Paths.GetItemsByUserIDParameters()
|
|
||||||
parameters.enableUserData = true
|
|
||||||
parameters.fields = .MinimumFields
|
|
||||||
parameters.includeItemTypes = itemTypes
|
|
||||||
parameters.isRecursive = true
|
|
||||||
|
|
||||||
// Page size
|
|
||||||
if let page {
|
|
||||||
parameters.limit = pageSize
|
|
||||||
parameters.startIndex = page * pageSize
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filters
|
|
||||||
if let filterViewModel {
|
|
||||||
let filters = filterViewModel.currentFilters
|
|
||||||
parameters.filters = filters.traits
|
|
||||||
parameters.genres = filters.genres.map(\.value)
|
|
||||||
parameters.sortBy = filters.sortBy.map(\.rawValue)
|
|
||||||
parameters.sortOrder = filters.sortOrder
|
|
||||||
parameters.tags = filters.tags.map(\.value)
|
|
||||||
parameters.years = filters.years.compactMap { Int($0.value) }
|
|
||||||
|
|
||||||
if filters.letter.first?.value == "#" {
|
|
||||||
parameters.nameLessThan = "A"
|
|
||||||
} else {
|
|
||||||
parameters.nameStartsWith = filters.letter
|
|
||||||
.map(\.value)
|
|
||||||
.filter { $0 != "#" }
|
|
||||||
.first
|
|
||||||
}
|
|
||||||
|
|
||||||
// Random sort won't take into account previous items, so
|
|
||||||
// manual exclusion is necessary. This could possibly be
|
|
||||||
// a performance issue for loading pages after already loading
|
|
||||||
// many items, but there's nothing we can do about that.
|
|
||||||
if filters.sortBy.first == ItemSortBy.random {
|
|
||||||
parameters.excludeItemIDs = elements.compactMap(\.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return parameters
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Get Random Item
|
|
||||||
|
|
||||||
override func getRandomItem() async -> BaseItemDto? {
|
|
||||||
|
|
||||||
var parameters = itemParameters(for: nil)
|
|
||||||
parameters.limit = 1
|
|
||||||
parameters.sortBy = [ItemSortBy.random.rawValue]
|
|
||||||
|
|
||||||
let request = Paths.getItemsByUserID(userID: userSession.user.id, parameters: parameters)
|
|
||||||
let response = try? await userSession.client.send(request)
|
|
||||||
|
|
||||||
return response?.value.items?.first
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -173,18 +173,15 @@ class PagingLibraryViewModel<Element: Poster>: ViewModel, Eventful, Stateful {
|
||||||
self.pageSize = pageSize
|
self.pageSize = pageSize
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
|
|
||||||
if let filters {
|
if var filters {
|
||||||
var filters = filters
|
|
||||||
|
|
||||||
if let id = parent?.id, Defaults[.Customization.Library.rememberSort] {
|
if let id = parent?.id, Defaults[.Customization.Library.rememberSort] {
|
||||||
// TODO: see `StoredValues.User.libraryFilters` for TODO
|
// TODO: see `StoredValues.User.libraryFilters` for TODO
|
||||||
// on remembering other filters
|
// on remembering other filters
|
||||||
|
|
||||||
let storedFilters = StoredValues[.User.libraryFilters(parentID: id)]
|
let storedFilters = StoredValues[.User.libraryFilters(parentID: id)]
|
||||||
|
|
||||||
filters = filters
|
filters.sortBy = storedFilters.sortBy
|
||||||
.mutating(\.sortBy, with: storedFilters.sortBy)
|
filters.sortOrder = storedFilters.sortOrder
|
||||||
.mutating(\.sortOrder, with: storedFilters.sortOrder)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.filterViewModel = .init(
|
self.filterViewModel = .init(
|
||||||
|
@ -220,52 +217,6 @@ class PagingLibraryViewModel<Element: Poster>: ViewModel, Eventful, Stateful {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// paging item type
|
|
||||||
init(
|
|
||||||
itemTypes: [BaseItemKind],
|
|
||||||
filters: ItemFilterCollection? = nil,
|
|
||||||
pageSize: Int = DefaultPageSize
|
|
||||||
) {
|
|
||||||
self.elements = IdentifiedArray([], id: \.unwrappedIDHashOrZero, uniquingIDsWith: { x, _ in x })
|
|
||||||
self.isStatic = false
|
|
||||||
self.pageSize = pageSize
|
|
||||||
|
|
||||||
self.parent = nil
|
|
||||||
|
|
||||||
if let filters {
|
|
||||||
self.filterViewModel = .init(
|
|
||||||
itemTypes: itemTypes,
|
|
||||||
currentFilters: filters
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
self.filterViewModel = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
Notifications[.didDeleteItem]
|
|
||||||
.publisher
|
|
||||||
.sink { id in
|
|
||||||
self.elements.remove(id: id.hashValue)
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
if let filterViewModel {
|
|
||||||
filterViewModel.$currentFilters
|
|
||||||
.dropFirst()
|
|
||||||
.debounce(for: 1, scheduler: RunLoop.main)
|
|
||||||
.removeDuplicates()
|
|
||||||
.sink { [weak self] _ in
|
|
||||||
guard let self else { return }
|
|
||||||
|
|
||||||
Task { @MainActor in
|
|
||||||
self.send(.refresh)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
convenience init(
|
convenience init(
|
||||||
title: String,
|
title: String,
|
||||||
id: String?,
|
id: String?,
|
||||||
|
|
|
@ -102,7 +102,7 @@ struct PagingLibraryView<Element: Poster & Identifiable>: View {
|
||||||
private func select(item: BaseItemDto) {
|
private func select(item: BaseItemDto) {
|
||||||
switch item.type {
|
switch item.type {
|
||||||
case .collectionFolder, .folder:
|
case .collectionFolder, .folder:
|
||||||
let viewModel = ItemLibraryViewModel(parent: item)
|
let viewModel = ItemLibraryViewModel(parent: item, filters: .default)
|
||||||
router.route(to: \.library, viewModel)
|
router.route(to: \.library, viewModel)
|
||||||
case .person:
|
case .person:
|
||||||
let viewModel = ItemLibraryViewModel(parent: item)
|
let viewModel = ItemLibraryViewModel(parent: item)
|
||||||
|
|
|
@ -399,7 +399,6 @@
|
||||||
BDFF67B02D2CA59A009A9A3A /* UserLocalSecurityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF67AD2D2CA59A009A9A3A /* UserLocalSecurityView.swift */; };
|
BDFF67B02D2CA59A009A9A3A /* UserLocalSecurityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF67AD2D2CA59A009A9A3A /* UserLocalSecurityView.swift */; };
|
||||||
BDFF67B22D2CA59A009A9A3A /* UserProfileSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF67AE2D2CA59A009A9A3A /* UserProfileSettingsView.swift */; };
|
BDFF67B22D2CA59A009A9A3A /* UserProfileSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF67AE2D2CA59A009A9A3A /* UserProfileSettingsView.swift */; };
|
||||||
BDFF67B32D2CA99D009A9A3A /* UserProfileRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BE1CED2BDB68CD008176A9 /* UserProfileRow.swift */; };
|
BDFF67B32D2CA99D009A9A3A /* UserProfileRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BE1CED2BDB68CD008176A9 /* UserProfileRow.swift */; };
|
||||||
C40CD926271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD924271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift */; };
|
|
||||||
C44FA6E02AACD19C00EDEB56 /* LiveSmallPlaybackButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44FA6DE2AACD19C00EDEB56 /* LiveSmallPlaybackButton.swift */; };
|
C44FA6E02AACD19C00EDEB56 /* LiveSmallPlaybackButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44FA6DE2AACD19C00EDEB56 /* LiveSmallPlaybackButton.swift */; };
|
||||||
C44FA6E12AACD19C00EDEB56 /* LiveLargePlaybackButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44FA6DF2AACD19C00EDEB56 /* LiveLargePlaybackButtons.swift */; };
|
C44FA6E12AACD19C00EDEB56 /* LiveLargePlaybackButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44FA6DF2AACD19C00EDEB56 /* LiveLargePlaybackButtons.swift */; };
|
||||||
C45C36542A8B1F2C003DAE46 /* LiveVideoPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45C36532A8B1F2C003DAE46 /* LiveVideoPlayerManager.swift */; };
|
C45C36542A8B1F2C003DAE46 /* LiveVideoPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45C36532A8B1F2C003DAE46 /* LiveVideoPlayerManager.swift */; };
|
||||||
|
@ -588,6 +587,8 @@
|
||||||
E133328F2953B71000EE76AB /* DownloadTaskView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E133328E2953B71000EE76AB /* DownloadTaskView.swift */; };
|
E133328F2953B71000EE76AB /* DownloadTaskView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E133328E2953B71000EE76AB /* DownloadTaskView.swift */; };
|
||||||
E13332912953B91000EE76AB /* DownloadTaskCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13332902953B91000EE76AB /* DownloadTaskCoordinator.swift */; };
|
E13332912953B91000EE76AB /* DownloadTaskCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13332902953B91000EE76AB /* DownloadTaskCoordinator.swift */; };
|
||||||
E13332942953BAA100EE76AB /* DownloadTaskContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13332932953BAA100EE76AB /* DownloadTaskContentView.swift */; };
|
E13332942953BAA100EE76AB /* DownloadTaskContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13332932953BAA100EE76AB /* DownloadTaskContentView.swift */; };
|
||||||
|
E1343DAD2D4EE4C8003145A8 /* BaseItemKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1343DAC2D4EE4C8003145A8 /* BaseItemKind.swift */; };
|
||||||
|
E1343DAE2D4EE4C8003145A8 /* BaseItemKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1343DAC2D4EE4C8003145A8 /* BaseItemKind.swift */; };
|
||||||
E1356E0329A730B200382563 /* SeparatorHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1356E0129A7309D00382563 /* SeparatorHStack.swift */; };
|
E1356E0329A730B200382563 /* SeparatorHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1356E0129A7309D00382563 /* SeparatorHStack.swift */; };
|
||||||
E1356E0429A731EB00382563 /* SeparatorHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1356E0129A7309D00382563 /* SeparatorHStack.swift */; };
|
E1356E0429A731EB00382563 /* SeparatorHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1356E0129A7309D00382563 /* SeparatorHStack.swift */; };
|
||||||
E1366A222C826DA700A36DED /* EditCustomDeviceProfileCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EC1C8572C80332500E2879E /* EditCustomDeviceProfileCoordinator.swift */; };
|
E1366A222C826DA700A36DED /* EditCustomDeviceProfileCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EC1C8572C80332500E2879E /* EditCustomDeviceProfileCoordinator.swift */; };
|
||||||
|
@ -960,7 +961,6 @@
|
||||||
E1A5056A2D0B733F007EE305 /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A505692D0B733F007EE305 /* Optional.swift */; };
|
E1A5056A2D0B733F007EE305 /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A505692D0B733F007EE305 /* Optional.swift */; };
|
||||||
E1A5056B2D0B733F007EE305 /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A505692D0B733F007EE305 /* Optional.swift */; };
|
E1A5056B2D0B733F007EE305 /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A505692D0B733F007EE305 /* Optional.swift */; };
|
||||||
E1A7B1652B9A9F7800152546 /* PreferencesView in Frameworks */ = {isa = PBXBuildFile; productRef = E1A7B1642B9A9F7800152546 /* PreferencesView */; };
|
E1A7B1652B9A9F7800152546 /* PreferencesView in Frameworks */ = {isa = PBXBuildFile; productRef = E1A7B1642B9A9F7800152546 /* PreferencesView */; };
|
||||||
E1A7B1662B9ADAD300152546 /* ItemTypeLibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD924271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift */; };
|
|
||||||
E1A7F0DF2BD4EC7400620DDD /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A7F0DE2BD4EC7400620DDD /* Dictionary.swift */; };
|
E1A7F0DF2BD4EC7400620DDD /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A7F0DE2BD4EC7400620DDD /* Dictionary.swift */; };
|
||||||
E1A7F0E02BD4EC7400620DDD /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A7F0DE2BD4EC7400620DDD /* Dictionary.swift */; };
|
E1A7F0E02BD4EC7400620DDD /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A7F0DE2BD4EC7400620DDD /* Dictionary.swift */; };
|
||||||
E1A8FDEC2C0574A800D0A51C /* ListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A8FDEB2C0574A800D0A51C /* ListRow.swift */; };
|
E1A8FDEC2C0574A800D0A51C /* ListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A8FDEB2C0574A800D0A51C /* ListRow.swift */; };
|
||||||
|
@ -1535,7 +1535,6 @@
|
||||||
BDA623522D0D0854009A157F /* SelectUserBottomBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectUserBottomBar.swift; sourceTree = "<group>"; };
|
BDA623522D0D0854009A157F /* SelectUserBottomBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectUserBottomBar.swift; sourceTree = "<group>"; };
|
||||||
BDFF67AD2D2CA59A009A9A3A /* UserLocalSecurityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserLocalSecurityView.swift; sourceTree = "<group>"; };
|
BDFF67AD2D2CA59A009A9A3A /* UserLocalSecurityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserLocalSecurityView.swift; sourceTree = "<group>"; };
|
||||||
BDFF67AE2D2CA59A009A9A3A /* UserProfileSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileSettingsView.swift; sourceTree = "<group>"; };
|
BDFF67AE2D2CA59A009A9A3A /* UserProfileSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileSettingsView.swift; sourceTree = "<group>"; };
|
||||||
C40CD924271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemTypeLibraryViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
C44FA6DE2AACD19C00EDEB56 /* LiveSmallPlaybackButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveSmallPlaybackButton.swift; sourceTree = "<group>"; };
|
C44FA6DE2AACD19C00EDEB56 /* LiveSmallPlaybackButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveSmallPlaybackButton.swift; sourceTree = "<group>"; };
|
||||||
C44FA6DF2AACD19C00EDEB56 /* LiveLargePlaybackButtons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveLargePlaybackButtons.swift; sourceTree = "<group>"; };
|
C44FA6DF2AACD19C00EDEB56 /* LiveLargePlaybackButtons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveLargePlaybackButtons.swift; sourceTree = "<group>"; };
|
||||||
C45C36532A8B1F2C003DAE46 /* LiveVideoPlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveVideoPlayerManager.swift; sourceTree = "<group>"; };
|
C45C36532A8B1F2C003DAE46 /* LiveVideoPlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveVideoPlayerManager.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1664,6 +1663,7 @@
|
||||||
E133328E2953B71000EE76AB /* DownloadTaskView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadTaskView.swift; sourceTree = "<group>"; };
|
E133328E2953B71000EE76AB /* DownloadTaskView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadTaskView.swift; sourceTree = "<group>"; };
|
||||||
E13332902953B91000EE76AB /* DownloadTaskCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadTaskCoordinator.swift; sourceTree = "<group>"; };
|
E13332902953B91000EE76AB /* DownloadTaskCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadTaskCoordinator.swift; sourceTree = "<group>"; };
|
||||||
E13332932953BAA100EE76AB /* DownloadTaskContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadTaskContentView.swift; sourceTree = "<group>"; };
|
E13332932953BAA100EE76AB /* DownloadTaskContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadTaskContentView.swift; sourceTree = "<group>"; };
|
||||||
|
E1343DAC2D4EE4C8003145A8 /* BaseItemKind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseItemKind.swift; sourceTree = "<group>"; };
|
||||||
E1356E0129A7309D00382563 /* SeparatorHStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorHStack.swift; sourceTree = "<group>"; };
|
E1356E0129A7309D00382563 /* SeparatorHStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorHStack.swift; sourceTree = "<group>"; };
|
||||||
E1388A40293F0AAD009721B1 /* PreferenceUIHostingSwizzling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferenceUIHostingSwizzling.swift; sourceTree = "<group>"; };
|
E1388A40293F0AAD009721B1 /* PreferenceUIHostingSwizzling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferenceUIHostingSwizzling.swift; sourceTree = "<group>"; };
|
||||||
E1388A41293F0AAD009721B1 /* PreferenceUIHostingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferenceUIHostingController.swift; sourceTree = "<group>"; };
|
E1388A41293F0AAD009721B1 /* PreferenceUIHostingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferenceUIHostingController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -4612,6 +4612,7 @@
|
||||||
children = (
|
children = (
|
||||||
4E49DED12CE54D6900352DCD /* ActiveSessionsPolicy.swift */,
|
4E49DED12CE54D6900352DCD /* ActiveSessionsPolicy.swift */,
|
||||||
E1D37F5B2B9CF02600343D2B /* BaseItemDto */,
|
E1D37F5B2B9CF02600343D2B /* BaseItemDto */,
|
||||||
|
E1343DAC2D4EE4C8003145A8 /* BaseItemKind.swift */,
|
||||||
E1D37F5A2B9CF01F00343D2B /* BaseItemPerson */,
|
E1D37F5A2B9CF01F00343D2B /* BaseItemPerson */,
|
||||||
E1002B632793CEE700E47059 /* ChapterInfo.swift */,
|
E1002B632793CEE700E47059 /* ChapterInfo.swift */,
|
||||||
E1CB758A2C80F9EC00217C76 /* CodecProfile.swift */,
|
E1CB758A2C80F9EC00217C76 /* CodecProfile.swift */,
|
||||||
|
@ -4979,7 +4980,6 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
62E632DF267D30CA0063E547 /* ItemLibraryViewModel.swift */,
|
62E632DF267D30CA0063E547 /* ItemLibraryViewModel.swift */,
|
||||||
C40CD924271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift */,
|
|
||||||
E1ED91142B95897500802036 /* LatestInLibraryViewModel.swift */,
|
E1ED91142B95897500802036 /* LatestInLibraryViewModel.swift */,
|
||||||
E12CC1AD28D0FAEA00678D5D /* NextUpLibraryViewModel.swift */,
|
E12CC1AD28D0FAEA00678D5D /* NextUpLibraryViewModel.swift */,
|
||||||
E111D8F428D03B7500400001 /* PagingLibraryViewModel.swift */,
|
E111D8F428D03B7500400001 /* PagingLibraryViewModel.swift */,
|
||||||
|
@ -5449,7 +5449,6 @@
|
||||||
E146A9D92BE6E9830034DA1E /* StoredValue.swift in Sources */,
|
E146A9D92BE6E9830034DA1E /* StoredValue.swift in Sources */,
|
||||||
4E13FAD82D18D5AF007785F6 /* ImageInfo.swift in Sources */,
|
4E13FAD82D18D5AF007785F6 /* ImageInfo.swift in Sources */,
|
||||||
E13DD3FA2717E961009D4DAF /* SelectUserViewModel.swift in Sources */,
|
E13DD3FA2717E961009D4DAF /* SelectUserViewModel.swift in Sources */,
|
||||||
C40CD926271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift in Sources */,
|
|
||||||
E13D98EE2D0664C1005FE96D /* NotificationSet.swift in Sources */,
|
E13D98EE2D0664C1005FE96D /* NotificationSet.swift in Sources */,
|
||||||
E1575E63293E77B5001665B1 /* CaseIterablePicker.swift in Sources */,
|
E1575E63293E77B5001665B1 /* CaseIterablePicker.swift in Sources */,
|
||||||
E1CB757F2C80F28F00217C76 /* SubtitleProfile.swift in Sources */,
|
E1CB757F2C80F28F00217C76 /* SubtitleProfile.swift in Sources */,
|
||||||
|
@ -5510,6 +5509,7 @@
|
||||||
E12376B32A33DFAC001F5B44 /* ItemOverviewView.swift in Sources */,
|
E12376B32A33DFAC001F5B44 /* ItemOverviewView.swift in Sources */,
|
||||||
E1ED7FDB2CAA4B6D00ACB6E3 /* PlayerStateInfo.swift in Sources */,
|
E1ED7FDB2CAA4B6D00ACB6E3 /* PlayerStateInfo.swift in Sources */,
|
||||||
4E661A222CEFE61000025C99 /* ParentalRatingsViewModel.swift in Sources */,
|
4E661A222CEFE61000025C99 /* ParentalRatingsViewModel.swift in Sources */,
|
||||||
|
E1343DAD2D4EE4C8003145A8 /* BaseItemKind.swift in Sources */,
|
||||||
E1A7F0E02BD4EC7400620DDD /* Dictionary.swift in Sources */,
|
E1A7F0E02BD4EC7400620DDD /* Dictionary.swift in Sources */,
|
||||||
E1CAF6602BA345830087D991 /* MediaViewModel.swift in Sources */,
|
E1CAF6602BA345830087D991 /* MediaViewModel.swift in Sources */,
|
||||||
E19D41A82BEEDC5F0082B8B2 /* UserLocalSecurityViewModel.swift in Sources */,
|
E19D41A82BEEDC5F0082B8B2 /* UserLocalSecurityViewModel.swift in Sources */,
|
||||||
|
@ -6268,6 +6268,7 @@
|
||||||
E1DA654C28E69B0500592A73 /* SpecialFeatureType.swift in Sources */,
|
E1DA654C28E69B0500592A73 /* SpecialFeatureType.swift in Sources */,
|
||||||
E11CEB8B28998552003E74C7 /* View-iOS.swift in Sources */,
|
E11CEB8B28998552003E74C7 /* View-iOS.swift in Sources */,
|
||||||
E10B1ECD2BD9AFD800A92EAF /* SwiftfinStore+V2.swift in Sources */,
|
E10B1ECD2BD9AFD800A92EAF /* SwiftfinStore+V2.swift in Sources */,
|
||||||
|
E1343DAE2D4EE4C8003145A8 /* BaseItemKind.swift in Sources */,
|
||||||
E1401CA92938140700E8B599 /* DarkAppIcon.swift in Sources */,
|
E1401CA92938140700E8B599 /* DarkAppIcon.swift in Sources */,
|
||||||
E1A1529028FD23D600600579 /* PlaybackSettingsCoordinator.swift in Sources */,
|
E1A1529028FD23D600600579 /* PlaybackSettingsCoordinator.swift in Sources */,
|
||||||
4EA78B252D2B5DBD0093BFCE /* ItemImagePickerCoordinator.swift in Sources */,
|
4EA78B252D2B5DBD0093BFCE /* ItemImagePickerCoordinator.swift in Sources */,
|
||||||
|
@ -6343,7 +6344,6 @@
|
||||||
BD3957792C113EC40078CEF8 /* SubtitleSection.swift in Sources */,
|
BD3957792C113EC40078CEF8 /* SubtitleSection.swift in Sources */,
|
||||||
091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */,
|
091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */,
|
||||||
E1721FAE28FB801C00762992 /* SmallPlaybackButtons.swift in Sources */,
|
E1721FAE28FB801C00762992 /* SmallPlaybackButtons.swift in Sources */,
|
||||||
E1A7B1662B9ADAD300152546 /* ItemTypeLibraryViewModel.swift in Sources */,
|
|
||||||
4EB7B33B2CBDE645004A342E /* ChevronAlertButton.swift in Sources */,
|
4EB7B33B2CBDE645004A342E /* ChevronAlertButton.swift in Sources */,
|
||||||
E1E750682A33E9B400B2C1EE /* OverviewCard.swift in Sources */,
|
E1E750682A33E9B400B2C1EE /* OverviewCard.swift in Sources */,
|
||||||
E1CCF13128AC07EC006CAC9E /* PosterHStack.swift in Sources */,
|
E1CCF13128AC07EC006CAC9E /* PosterHStack.swift in Sources */,
|
||||||
|
|
|
@ -72,7 +72,7 @@ extension FilterView {
|
||||||
type: ItemFilterType
|
type: ItemFilterType
|
||||||
) {
|
) {
|
||||||
|
|
||||||
let selectionBinding = Binding {
|
let selectionBinding: Binding<[AnyItemFilter]> = Binding {
|
||||||
viewModel.currentFilters[keyPath: type.collectionAnyKeyPath]
|
viewModel.currentFilters[keyPath: type.collectionAnyKeyPath]
|
||||||
} set: { newValue in
|
} set: { newValue in
|
||||||
switch type {
|
switch type {
|
||||||
|
|
Loading…
Reference in New Issue