From 3ee2abec5cb95efc9bf85bed28997b653731bb6f Mon Sep 17 00:00:00 2001 From: Joe Kribs Date: Sun, 2 Feb 2025 10:17:46 -0700 Subject: [PATCH] [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 --- .../tvOSMainTabCoordinator.swift | 10 +- .../JellyfinAPI/BaseItemDto/BaseItemDto.swift | 1 + .../Extensions/JellyfinAPI/BaseItemKind.swift | 31 ++++++ .../BaseItemPerson/BaseItemPerson.swift | 1 + .../ItemFilter/ItemFilterCollection.swift | 6 +- .../Objects/LibraryParent/LibraryParent.swift | 57 ++++++++-- Shared/ViewModels/FilterViewModel.swift | 18 +-- .../ItemLibraryViewModel.swift | 51 ++++----- .../ItemTypeLibraryViewModel.swift | 105 ------------------ .../PagingLibraryViewModel.swift | 55 +-------- .../PagingLibraryView/PagingLibraryView.swift | 2 +- Swiftfin.xcodeproj/project.pbxproj | 12 +- Swiftfin/Views/FilterView.swift | 2 +- 13 files changed, 126 insertions(+), 225 deletions(-) create mode 100644 Shared/Extensions/JellyfinAPI/BaseItemKind.swift delete mode 100644 Shared/ViewModels/LibraryViewModel/ItemTypeLibraryViewModel.swift diff --git a/Shared/Coordinators/MainCoordinator/tvOSMainTabCoordinator.swift b/Shared/Coordinators/MainCoordinator/tvOSMainTabCoordinator.swift index 71159a29d..6d1f827df 100644 --- a/Shared/Coordinators/MainCoordinator/tvOSMainTabCoordinator.swift +++ b/Shared/Coordinators/MainCoordinator/tvOSMainTabCoordinator.swift @@ -48,9 +48,8 @@ final class MainTabCoordinator: TabCoordinatable { } func makeTVShows() -> NavigationViewCoordinator> { - 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> { - let viewModel = ItemTypeLibraryViewModel( - itemTypes: [.movie], - filters: .default + let viewModel = ItemLibraryViewModel( + filters: .init(itemTypes: [.movie]) ) return NavigationViewCoordinator(LibraryCoordinator(viewModel: viewModel)) } diff --git a/Shared/Extensions/JellyfinAPI/BaseItemDto/BaseItemDto.swift b/Shared/Extensions/JellyfinAPI/BaseItemDto/BaseItemDto.swift index 0f0d267fb..1a4e9a2c8 100644 --- a/Shared/Extensions/JellyfinAPI/BaseItemDto/BaseItemDto.swift +++ b/Shared/Extensions/JellyfinAPI/BaseItemDto/BaseItemDto.swift @@ -22,6 +22,7 @@ extension BaseItemDto: Displayable { } extension BaseItemDto: LibraryParent { + var libraryType: BaseItemKind? { type } diff --git a/Shared/Extensions/JellyfinAPI/BaseItemKind.swift b/Shared/Extensions/JellyfinAPI/BaseItemKind.swift new file mode 100644 index 000000000..230050cf0 --- /dev/null +++ b/Shared/Extensions/JellyfinAPI/BaseItemKind.swift @@ -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 + } +} diff --git a/Shared/Extensions/JellyfinAPI/BaseItemPerson/BaseItemPerson.swift b/Shared/Extensions/JellyfinAPI/BaseItemPerson/BaseItemPerson.swift index 7d93f69fc..3bed20797 100644 --- a/Shared/Extensions/JellyfinAPI/BaseItemPerson/BaseItemPerson.swift +++ b/Shared/Extensions/JellyfinAPI/BaseItemPerson/BaseItemPerson.swift @@ -17,6 +17,7 @@ extension BaseItemPerson: Displayable { } extension BaseItemPerson: LibraryParent { + var libraryType: BaseItemKind? { .person } diff --git a/Shared/Objects/ItemFilter/ItemFilterCollection.swift b/Shared/Objects/ItemFilter/ItemFilterCollection.swift index c62c35964..12cb8bd26 100644 --- a/Shared/Objects/ItemFilter/ItemFilterCollection.swift +++ b/Shared/Objects/ItemFilter/ItemFilterCollection.swift @@ -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, diff --git a/Shared/Objects/LibraryParent/LibraryParent.swift b/Shared/Objects/LibraryParent/LibraryParent.swift index 0165513ba..d1ec33ae0 100644 --- a/Shared/Objects/LibraryParent/LibraryParent.swift +++ b/Shared/Objects/LibraryParent/LibraryParent.swift @@ -6,17 +6,58 @@ // Copyright (c) 2025 Jellyfin & Jellyfin Contributors // -import Foundation import JellyfinAPI protocol LibraryParent: Displayable, Hashable, Identifiable { - // 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 + } } diff --git a/Shared/ViewModels/FilterViewModel.swift b/Shared/ViewModels/FilterViewModel.swift index b400e1554..0fef09733 100644 --- a/Shared/ViewModels/FilterViewModel.swift +++ b/Shared/ViewModels/FilterViewModel.swift @@ -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) diff --git a/Shared/ViewModels/LibraryViewModel/ItemLibraryViewModel.swift b/Shared/ViewModels/LibraryViewModel/ItemLibraryViewModel.swift index 2ac76e410..41f246fe6 100644 --- a/Shared/ViewModels/LibraryViewModel/ItemLibraryViewModel.swift +++ b/Shared/ViewModels/LibraryViewModel/ItemLibraryViewModel.swift @@ -48,42 +48,23 @@ final class ItemLibraryViewModel: PagingLibraryViewModel { // 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 { 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 { diff --git a/Shared/ViewModels/LibraryViewModel/ItemTypeLibraryViewModel.swift b/Shared/ViewModels/LibraryViewModel/ItemTypeLibraryViewModel.swift deleted file mode 100644 index 739544883..000000000 --- a/Shared/ViewModels/LibraryViewModel/ItemTypeLibraryViewModel.swift +++ /dev/null @@ -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 { - - 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 - } -} diff --git a/Shared/ViewModels/LibraryViewModel/PagingLibraryViewModel.swift b/Shared/ViewModels/LibraryViewModel/PagingLibraryViewModel.swift index 11c370288..be0b62c89 100644 --- a/Shared/ViewModels/LibraryViewModel/PagingLibraryViewModel.swift +++ b/Shared/ViewModels/LibraryViewModel/PagingLibraryViewModel.swift @@ -173,18 +173,15 @@ class PagingLibraryViewModel: 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: 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?, diff --git a/Swiftfin tvOS/Views/PagingLibraryView/PagingLibraryView.swift b/Swiftfin tvOS/Views/PagingLibraryView/PagingLibraryView.swift index b319408a1..52f7355e9 100644 --- a/Swiftfin tvOS/Views/PagingLibraryView/PagingLibraryView.swift +++ b/Swiftfin tvOS/Views/PagingLibraryView/PagingLibraryView.swift @@ -102,7 +102,7 @@ struct PagingLibraryView: 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) diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 1353a9ba4..fd6addffa 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -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 = ""; }; BDFF67AD2D2CA59A009A9A3A /* UserLocalSecurityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserLocalSecurityView.swift; sourceTree = ""; }; BDFF67AE2D2CA59A009A9A3A /* UserProfileSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileSettingsView.swift; sourceTree = ""; }; - C40CD924271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemTypeLibraryViewModel.swift; sourceTree = ""; }; C44FA6DE2AACD19C00EDEB56 /* LiveSmallPlaybackButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveSmallPlaybackButton.swift; sourceTree = ""; }; C44FA6DF2AACD19C00EDEB56 /* LiveLargePlaybackButtons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveLargePlaybackButtons.swift; sourceTree = ""; }; C45C36532A8B1F2C003DAE46 /* LiveVideoPlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveVideoPlayerManager.swift; sourceTree = ""; }; @@ -1664,6 +1663,7 @@ E133328E2953B71000EE76AB /* DownloadTaskView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadTaskView.swift; sourceTree = ""; }; E13332902953B91000EE76AB /* DownloadTaskCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadTaskCoordinator.swift; sourceTree = ""; }; E13332932953BAA100EE76AB /* DownloadTaskContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadTaskContentView.swift; sourceTree = ""; }; + E1343DAC2D4EE4C8003145A8 /* BaseItemKind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseItemKind.swift; sourceTree = ""; }; E1356E0129A7309D00382563 /* SeparatorHStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorHStack.swift; sourceTree = ""; }; E1388A40293F0AAD009721B1 /* PreferenceUIHostingSwizzling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferenceUIHostingSwizzling.swift; sourceTree = ""; }; E1388A41293F0AAD009721B1 /* PreferenceUIHostingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferenceUIHostingController.swift; sourceTree = ""; }; @@ -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 */, diff --git a/Swiftfin/Views/FilterView.swift b/Swiftfin/Views/FilterView.swift index 2968fb348..afe25400f 100644 --- a/Swiftfin/Views/FilterView.swift +++ b/Swiftfin/Views/FilterView.swift @@ -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 {