[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>> {
|
||||
let viewModel = ItemTypeLibraryViewModel(
|
||||
itemTypes: [.series],
|
||||
filters: .default
|
||||
let viewModel = ItemLibraryViewModel(
|
||||
filters: .init(itemTypes: [.series])
|
||||
)
|
||||
return NavigationViewCoordinator(LibraryCoordinator(viewModel: viewModel))
|
||||
}
|
||||
|
@ -65,9 +64,8 @@ final class MainTabCoordinator: TabCoordinatable {
|
|||
}
|
||||
|
||||
func makeMovies() -> NavigationViewCoordinator<LibraryCoordinator<BaseItemDto>> {
|
||||
let viewModel = ItemTypeLibraryViewModel(
|
||||
itemTypes: [.movie],
|
||||
filters: .default
|
||||
let viewModel = ItemLibraryViewModel(
|
||||
filters: .init(itemTypes: [.movie])
|
||||
)
|
||||
return NavigationViewCoordinator(LibraryCoordinator(viewModel: viewModel))
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ extension BaseItemDto: Displayable {
|
|||
}
|
||||
|
||||
extension BaseItemDto: LibraryParent {
|
||||
|
||||
var libraryType: BaseItemKind? {
|
||||
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 {
|
||||
|
||||
var libraryType: BaseItemKind? {
|
||||
.person
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import JellyfinAPI
|
|||
struct ItemFilterCollection: Codable, Defaults.Serializable, Hashable {
|
||||
|
||||
var genres: [ItemGenre] = []
|
||||
var itemTypes: [BaseItemKind] = []
|
||||
var letter: [ItemLetter] = []
|
||||
var sortBy: [ItemSortBy] = [ItemSortBy.name]
|
||||
var sortOrder: [ItemSortOrder] = [ItemSortOrder.ascending]
|
||||
|
@ -32,7 +33,10 @@ struct ItemFilterCollection: Codable, Defaults.Serializable, Hashable {
|
|||
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(
|
||||
letter: ItemLetter.allCases,
|
||||
sortBy: ItemSortBy.allCases,
|
||||
|
|
|
@ -6,17 +6,58 @@
|
|||
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import JellyfinAPI
|
||||
|
||||
protocol LibraryParent: Displayable, Hashable, Identifiable<String?> {
|
||||
|
||||
// Only called `libraryType` because `BaseItemPerson` has
|
||||
// a different `type` property. However, people should have
|
||||
// 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
|
||||
/// The type of the library, reusing `BaseItemKind` for some
|
||||
/// ease of provided variety like `folder` and `userView`.
|
||||
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
|
||||
|
||||
private let parent: (any LibraryParent)?
|
||||
private let itemTypes: [BaseItemKind]?
|
||||
|
||||
init(
|
||||
parent: (any LibraryParent)? = nil,
|
||||
currentFilters: ItemFilterCollection = .default
|
||||
) {
|
||||
self.parent = parent
|
||||
self.itemTypes = nil
|
||||
self.currentFilters = currentFilters
|
||||
super.init()
|
||||
}
|
||||
|
||||
init(
|
||||
itemTypes: [BaseItemKind],
|
||||
currentFilters: ItemFilterCollection = .default
|
||||
) {
|
||||
self.parent = nil
|
||||
self.itemTypes = itemTypes
|
||||
self.currentFilters = currentFilters
|
||||
super.init()
|
||||
if let parent {
|
||||
self.allFilters.itemTypes = parent.supportedItemTypes
|
||||
}
|
||||
}
|
||||
|
||||
/// 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]) {
|
||||
|
||||
let parameters = Paths.GetQueryFiltersLegacyParameters(
|
||||
userID: userSession.user.id,
|
||||
parentID: parent?.id as? String,
|
||||
includeItemTypes: itemTypes
|
||||
parentID: parent?.id
|
||||
)
|
||||
|
||||
let request = Paths.getQueryFiltersLegacy(parameters: parameters)
|
||||
|
|
|
@ -48,42 +48,23 @@ final class ItemLibraryViewModel: PagingLibraryViewModel<BaseItemDto> {
|
|||
|
||||
// MARK: item parameters
|
||||
|
||||
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: ()
|
||||
}
|
||||
}
|
||||
private func itemParameters(for page: Int?) -> Paths.GetItemsByUserIDParameters {
|
||||
|
||||
var parameters = Paths.GetItemsByUserIDParameters()
|
||||
|
||||
parameters.enableUserData = true
|
||||
parameters.fields = .MinimumFields
|
||||
parameters.includeItemTypes = includeItemTypes
|
||||
parameters.isRecursive = isRecursive
|
||||
parameters.parentID = libraryID
|
||||
parameters.personIDs = personIDs
|
||||
parameters.studioIDs = studioIDs
|
||||
|
||||
// Default values, expected to be overridden
|
||||
// by parent or filters
|
||||
parameters.includeItemTypes = BaseItemKind.supportedCases
|
||||
parameters.sortOrder = [.ascending]
|
||||
parameters.sortBy = [ItemSortBy.name.rawValue]
|
||||
|
||||
// Parent
|
||||
if let parent {
|
||||
parameters = parent.setParentParameters(parameters)
|
||||
}
|
||||
|
||||
// Page size
|
||||
if let page {
|
||||
|
@ -101,6 +82,12 @@ final class ItemLibraryViewModel: PagingLibraryViewModel<BaseItemDto> {
|
|||
parameters.tags = filters.tags.map(\.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 == "#" {
|
||||
parameters.nameLessThan = "A"
|
||||
} 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.parent = parent
|
||||
|
||||
if let filters {
|
||||
var filters = filters
|
||||
|
||||
if var filters {
|
||||
if let id = parent?.id, Defaults[.Customization.Library.rememberSort] {
|
||||
// TODO: see `StoredValues.User.libraryFilters` for TODO
|
||||
// on remembering other filters
|
||||
|
||||
let storedFilters = StoredValues[.User.libraryFilters(parentID: id)]
|
||||
|
||||
filters = filters
|
||||
.mutating(\.sortBy, with: storedFilters.sortBy)
|
||||
.mutating(\.sortOrder, with: storedFilters.sortOrder)
|
||||
filters.sortBy = storedFilters.sortBy
|
||||
filters.sortOrder = storedFilters.sortOrder
|
||||
}
|
||||
|
||||
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(
|
||||
title: String,
|
||||
id: String?,
|
||||
|
|
|
@ -102,7 +102,7 @@ struct PagingLibraryView<Element: Poster & Identifiable>: View {
|
|||
private func select(item: BaseItemDto) {
|
||||
switch item.type {
|
||||
case .collectionFolder, .folder:
|
||||
let viewModel = ItemLibraryViewModel(parent: item)
|
||||
let viewModel = ItemLibraryViewModel(parent: item, filters: .default)
|
||||
router.route(to: \.library, viewModel)
|
||||
case .person:
|
||||
let viewModel = ItemLibraryViewModel(parent: item)
|
||||
|
|
|
@ -399,7 +399,6 @@
|
|||
BDFF67B02D2CA59A009A9A3A /* UserLocalSecurityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF67AD2D2CA59A009A9A3A /* UserLocalSecurityView.swift */; };
|
||||
BDFF67B22D2CA59A009A9A3A /* UserProfileSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF67AE2D2CA59A009A9A3A /* UserProfileSettingsView.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 */; };
|
||||
C44FA6E12AACD19C00EDEB56 /* LiveLargePlaybackButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44FA6DF2AACD19C00EDEB56 /* LiveLargePlaybackButtons.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 */; };
|
||||
E13332912953B91000EE76AB /* DownloadTaskCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13332902953B91000EE76AB /* DownloadTaskCoordinator.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 */; };
|
||||
E1356E0429A731EB00382563 /* SeparatorHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1356E0129A7309D00382563 /* SeparatorHStack.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 */; };
|
||||
E1A5056B2D0B733F007EE305 /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A505692D0B733F007EE305 /* Optional.swift */; };
|
||||
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 */; };
|
||||
E1A7F0E02BD4EC7400620DDD /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A7F0DE2BD4EC7400620DDD /* Dictionary.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -1664,6 +1663,7 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -4612,6 +4612,7 @@
|
|||
children = (
|
||||
4E49DED12CE54D6900352DCD /* ActiveSessionsPolicy.swift */,
|
||||
E1D37F5B2B9CF02600343D2B /* BaseItemDto */,
|
||||
E1343DAC2D4EE4C8003145A8 /* BaseItemKind.swift */,
|
||||
E1D37F5A2B9CF01F00343D2B /* BaseItemPerson */,
|
||||
E1002B632793CEE700E47059 /* ChapterInfo.swift */,
|
||||
E1CB758A2C80F9EC00217C76 /* CodecProfile.swift */,
|
||||
|
@ -4979,7 +4980,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
62E632DF267D30CA0063E547 /* ItemLibraryViewModel.swift */,
|
||||
C40CD924271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift */,
|
||||
E1ED91142B95897500802036 /* LatestInLibraryViewModel.swift */,
|
||||
E12CC1AD28D0FAEA00678D5D /* NextUpLibraryViewModel.swift */,
|
||||
E111D8F428D03B7500400001 /* PagingLibraryViewModel.swift */,
|
||||
|
@ -5449,7 +5449,6 @@
|
|||
E146A9D92BE6E9830034DA1E /* StoredValue.swift in Sources */,
|
||||
4E13FAD82D18D5AF007785F6 /* ImageInfo.swift in Sources */,
|
||||
E13DD3FA2717E961009D4DAF /* SelectUserViewModel.swift in Sources */,
|
||||
C40CD926271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift in Sources */,
|
||||
E13D98EE2D0664C1005FE96D /* NotificationSet.swift in Sources */,
|
||||
E1575E63293E77B5001665B1 /* CaseIterablePicker.swift in Sources */,
|
||||
E1CB757F2C80F28F00217C76 /* SubtitleProfile.swift in Sources */,
|
||||
|
@ -5510,6 +5509,7 @@
|
|||
E12376B32A33DFAC001F5B44 /* ItemOverviewView.swift in Sources */,
|
||||
E1ED7FDB2CAA4B6D00ACB6E3 /* PlayerStateInfo.swift in Sources */,
|
||||
4E661A222CEFE61000025C99 /* ParentalRatingsViewModel.swift in Sources */,
|
||||
E1343DAD2D4EE4C8003145A8 /* BaseItemKind.swift in Sources */,
|
||||
E1A7F0E02BD4EC7400620DDD /* Dictionary.swift in Sources */,
|
||||
E1CAF6602BA345830087D991 /* MediaViewModel.swift in Sources */,
|
||||
E19D41A82BEEDC5F0082B8B2 /* UserLocalSecurityViewModel.swift in Sources */,
|
||||
|
@ -6268,6 +6268,7 @@
|
|||
E1DA654C28E69B0500592A73 /* SpecialFeatureType.swift in Sources */,
|
||||
E11CEB8B28998552003E74C7 /* View-iOS.swift in Sources */,
|
||||
E10B1ECD2BD9AFD800A92EAF /* SwiftfinStore+V2.swift in Sources */,
|
||||
E1343DAE2D4EE4C8003145A8 /* BaseItemKind.swift in Sources */,
|
||||
E1401CA92938140700E8B599 /* DarkAppIcon.swift in Sources */,
|
||||
E1A1529028FD23D600600579 /* PlaybackSettingsCoordinator.swift in Sources */,
|
||||
4EA78B252D2B5DBD0093BFCE /* ItemImagePickerCoordinator.swift in Sources */,
|
||||
|
@ -6343,7 +6344,6 @@
|
|||
BD3957792C113EC40078CEF8 /* SubtitleSection.swift in Sources */,
|
||||
091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */,
|
||||
E1721FAE28FB801C00762992 /* SmallPlaybackButtons.swift in Sources */,
|
||||
E1A7B1662B9ADAD300152546 /* ItemTypeLibraryViewModel.swift in Sources */,
|
||||
4EB7B33B2CBDE645004A342E /* ChevronAlertButton.swift in Sources */,
|
||||
E1E750682A33E9B400B2C1EE /* OverviewCard.swift in Sources */,
|
||||
E1CCF13128AC07EC006CAC9E /* PosterHStack.swift in Sources */,
|
||||
|
|
|
@ -72,7 +72,7 @@ extension FilterView {
|
|||
type: ItemFilterType
|
||||
) {
|
||||
|
||||
let selectionBinding = Binding {
|
||||
let selectionBinding: Binding<[AnyItemFilter]> = Binding {
|
||||
viewModel.currentFilters[keyPath: type.collectionAnyKeyPath]
|
||||
} set: { newValue in
|
||||
switch type {
|
||||
|
|
Loading…
Reference in New Issue