From 39a5b6a2e7f5bbc634747ecb0ebb291110bfc4b9 Mon Sep 17 00:00:00 2001 From: PangMo5 Date: Sat, 19 Jun 2021 07:16:17 +0900 Subject: [PATCH] refactor filters system add LibraryFilterViewModel some func name lowercased --- JellyfinPlayer.xcodeproj/project.pbxproj | 6 + JellyfinPlayer/LibraryFilterView.swift | 121 +++++++++--------- JellyfinPlayer/LibraryView.swift | 10 ++ Shared/Extensions/MultiSelectorView.swift | 18 +-- Shared/Typings/Typings.swift | 56 +++++++- .../ViewModels/ConnectToServerViewModel.swift | 4 +- Shared/ViewModels/EpisodeItemViewModel.swift | 8 +- Shared/ViewModels/HomeViewModel.swift | 10 +- Shared/ViewModels/LatestMediaViewModel.swift | 2 +- .../ViewModels/LibraryFilterViewModel.swift | 61 +++++++++ Shared/ViewModels/LibraryListViewModel.swift | 2 +- .../ViewModels/LibrarySearchViewModel.swift | 2 +- Shared/ViewModels/LibraryViewModel.swift | 45 +++++-- Shared/ViewModels/MovieItemViewModel.swift | 8 +- Shared/ViewModels/SeasonItemViewModel.swift | 2 +- Shared/ViewModels/SeriesItemViewModel.swift | 2 +- Shared/ViewModels/ViewModel.swift | 2 +- 17 files changed, 257 insertions(+), 102 deletions(-) create mode 100644 Shared/ViewModels/LibraryFilterViewModel.swift diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 89214094..35373edb 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -131,6 +131,8 @@ 62E632EA267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632E8267D3FF50063E547 /* SeasonItemViewModel.swift */; }; 62E632EC267D410B0063E547 /* SeriesItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */; }; 62E632ED267D410B0063E547 /* SeriesItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */; }; + 62E632EF267D43320063E547 /* LibraryFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */; }; + 62E632F0267D43320063E547 /* LibraryFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */; }; 62EC3527267665D8000E9F2D /* MobileVLCKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */; }; 62EC3528267665D8000E9F2D /* MobileVLCKit.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 62EC352C26766675000E9F2D /* ServerEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352B26766675000E9F2D /* ServerEnvironment.swift */; }; @@ -286,6 +288,7 @@ 62E632E5267D3F5B0063E547 /* EpisodeItemViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EpisodeItemViewModel.swift; sourceTree = ""; }; 62E632E8267D3FF50063E547 /* SeasonItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeasonItemViewModel.swift; sourceTree = ""; }; 62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeriesItemViewModel.swift; sourceTree = ""; }; + 62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryFilterViewModel.swift; sourceTree = ""; }; 62EC352B26766675000E9F2D /* ServerEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerEnvironment.swift; sourceTree = ""; }; 62EC352E267666A5000E9F2D /* SessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = ""; }; 62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRotationViewModifier.swift; sourceTree = ""; }; @@ -354,6 +357,7 @@ 62E632E5267D3F5B0063E547 /* EpisodeItemViewModel.swift */, 62E632E8267D3FF50063E547 /* SeasonItemViewModel.swift */, 62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */, + 62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */, ); path = ViewModels; sourceTree = ""; @@ -730,6 +734,7 @@ 62E632E1267D30CA0063E547 /* LibraryViewModel.swift in Sources */, 535870A82669D8AE00D05A09 /* StringExtensions.swift in Sources */, 62E632ED267D410B0063E547 /* SeriesItemViewModel.swift in Sources */, + 62E632F0267D43320063E547 /* LibraryFilterViewModel.swift in Sources */, 53ABFDE6267974EF00886593 /* SettingsViewModel.swift in Sources */, 6267B3D826710B9800A7371D /* CollectionExtensions.swift in Sources */, 62E632E7267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */, @@ -800,6 +805,7 @@ 62E632E0267D30CA0063E547 /* LibraryViewModel.swift in Sources */, 62EC352F267666A5000E9F2D /* SessionManager.swift in Sources */, 62E632E3267D3BA60063E547 /* MovieItemViewModel.swift in Sources */, + 62E632EF267D43320063E547 /* LibraryFilterViewModel.swift in Sources */, 535870AD2669D8DD00D05A09 /* Typings.swift in Sources */, 62EC352C26766675000E9F2D /* ServerEnvironment.swift in Sources */, 6267B3DA2671138200A7371D /* ImageExtensions.swift in Sources */, diff --git a/JellyfinPlayer/LibraryFilterView.swift b/JellyfinPlayer/LibraryFilterView.swift index 335a93f3..76545130 100644 --- a/JellyfinPlayer/LibraryFilterView.swift +++ b/JellyfinPlayer/LibraryFilterView.swift @@ -5,75 +5,80 @@ * Copyright 2021 Aiden Vigue & Jellyfin Contributors */ -import SwiftUI import JellyfinAPI +import SwiftUI struct LibraryFilterView: View { - @Binding var filter: LibraryFilters + @Environment(\.presentationMode) + var presentationMode + @Binding + var filters: LibraryFilters + + @StateObject + var viewModel: LibraryFilterViewModel + + init(filters: Binding, enabledFilterType: [FilterType]) { + _filters = filters + _viewModel = StateObject(wrappedValue: .init(filters: filters.wrappedValue, enabledFilterType: enabledFilterType)) + } var body: some View { - EmptyView() - /* NavigationView { - LoadingView(isShowing: $isLoading) { + ZStack { Form { - Toggle("Only show unplayed items", isOn: $onlyUnplayed) - .onChange(of: onlyUnplayed) { value in - if value { - filter.filterTypes.append(.isUnplayed) - } else { - filter.filterTypes.removeAll { $0 == .isUnplayed } - } - } - MultiSelector(label: "Genres", - options: allGenres, - optionToString: { $0.name }, - selected: $selectedGenres) - .onChange(of: selectedGenres) { genres in - filter.genres = genres.map(\.id) - } - MultiSelector(label: "Parental Ratings", - options: allRatings, - optionToString: { $0.name }, - selected: $selectedRatings) - .onChange(of: selectedRatings) { ratings in - filter.officialRatings = ratings.map(\.id) - } - - Section(header: Text("Sort settings")) { - Picker("Sort by", selection: $sortBySelection) { - Text("Name").tag("SortName") - Text("Date Added").tag("DateCreated") - Text("Date Played").tag("DatePlayed") - Text("Date Released").tag("PremiereDate") - Text("Runtime").tag("Runtime") - }.onChange(of: sortBySelection) { value in - guard let sort = SortType(rawValue: value) else { return } - filter.sort = sort - } - Picker("Sort order", selection: $sortOrder) { - Text("Ascending").tag("Ascending") - Text("Descending").tag("Descending") - }.onChange(of: sortOrder) { order in - guard let asc = ASC(rawValue: order) else { return } - filter.asc = asc - } + if viewModel.enabledFilterType.contains(.genre) { + MultiSelector(label: "Genres", + options: viewModel.allGenres, + optionToString: { $0.name ?? "" }, + selected: $viewModel.modifyedFilters.withGenres) + } + if viewModel.enabledFilterType.contains(.filter) { + MultiSelector(label: "Filters", + options: viewModel.allItemFilters, + optionToString: { $0.localized }, + selected: $viewModel.modifyedFilters.filters) + } + if viewModel.enabledFilterType.contains(.tag) { + MultiSelector(label: "Tags", + options: viewModel.allTags, + optionToString: { $0 }, + selected: $viewModel.modifyedFilters.tags) + } + if viewModel.enabledFilterType.contains(.sortBy) { + MultiSelector(label: "Sort by", + options: viewModel.allSortBys, + optionToString: { $0.localized }, + selected: $viewModel.modifyedFilters.sortBy) + } + if viewModel.enabledFilterType.contains(.sortOrder) { + MultiSelector(label: "Sort Order", + options: viewModel.allSortOrders, + optionToString: { $0.localized }, + selected: $viewModel.modifyedFilters.sortOrder) } } - }.onAppear(perform: onAppear) - .navigationBarTitle("Filters", displayMode: .inline) - .toolbar { - ToolbarItemGroup(placement: .navigationBarLeading) { - Button { - presentationMode.wrappedValue.dismiss() - } label: { - HStack { - Text("Back").font(.callout) - } - } + if viewModel.isLoading { + ProgressView() + } + } + .navigationBarTitle("Filters", displayMode: .inline) + .toolbar { + ToolbarItemGroup(placement: .navigationBarLeading) { + Button { + presentationMode.wrappedValue.dismiss() + } label: { + Image(systemName: "xmark") } } + ToolbarItemGroup(placement: .navigationBarTrailing) { + Button { + self.filters = viewModel.modifyedFilters + presentationMode.wrappedValue.dismiss() + } label: { + Text("Apply") + } + } + } } - */ } } diff --git a/JellyfinPlayer/LibraryView.swift b/JellyfinPlayer/LibraryView.swift index 576207e2..7058518d 100644 --- a/JellyfinPlayer/LibraryView.swift +++ b/JellyfinPlayer/LibraryView.swift @@ -20,6 +20,8 @@ struct LibraryView: View { @State var isShowingSearchView = false + @State + var isShowingFilterView = false @State private var tracks: [GridItem] = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 125) @@ -109,6 +111,11 @@ struct LibraryView: View { Image(systemName: "chevron.right") } } + Button(action: { + isShowingFilterView = true + }) { + Image(systemName: "line.horizontal.3.decrease.circle") + } Button(action: { isShowingSearchView = true }) { @@ -116,6 +123,9 @@ struct LibraryView: View { } } } + .sheet(isPresented: $isShowingFilterView) { + LibraryFilterView(filters: $viewModel.filters, enabledFilterType: viewModel.enabledFilterType) + } .background( NavigationLink(destination: LibrarySearchView(viewModel: .init(parentID: viewModel.parentID)), isActive: $isShowingSearchView) { diff --git a/Shared/Extensions/MultiSelectorView.swift b/Shared/Extensions/MultiSelectorView.swift index 6746a107..5f8a2714 100644 --- a/Shared/Extensions/MultiSelectorView.swift +++ b/Shared/Extensions/MultiSelectorView.swift @@ -7,44 +7,44 @@ import SwiftUI -private struct MultiSelectionView: View { +private struct MultiSelectionView: View { let options: [Selectable] let optionToString: (Selectable) -> String let label: String - @Binding var selected: Set + @Binding var selected: Array var body: some View { List { - ForEach(options) { selectable in + ForEach(options, id: \.self) { selectable in Button(action: { toggleSelection(selectable: selectable) }) { HStack { Text(optionToString(selectable)).foregroundColor(Color.primary) Spacer() - if selected.contains { $0.id == selectable.id } { + if selected.contains { $0 == selectable } { Image(systemName: "checkmark").foregroundColor(.accentColor) } } - }.tag(selectable.id) + }.tag(selectable) } }.listStyle(GroupedListStyle()) } private func toggleSelection(selectable: Selectable) { - if let existingIndex = selected.firstIndex(where: { $0.id == selectable.id }) { + if let existingIndex = selected.firstIndex(where: { $0 == selectable }) { selected.remove(at: existingIndex) } else { - selected.insert(selectable) + selected.append(selectable) } } } -struct MultiSelector: View { +struct MultiSelector: View { let label: String let options: [Selectable] let optionToString: (Selectable) -> String - var selected: Binding> + var selected: Binding> private var formattedSelectedListString: String { ListFormatter.localizedString(byJoining: selected.wrappedValue.map { optionToString($0) }) diff --git a/Shared/Typings/Typings.swift b/Shared/Typings/Typings.swift index 87d62223..cb9e00d0 100644 --- a/Shared/Typings/Typings.swift +++ b/Shared/Typings/Typings.swift @@ -5,15 +5,16 @@ * Copyright 2021 Aiden Vigue & Jellyfin Contributors */ -import Foundation import Combine +import Foundation import JellyfinAPI struct LibraryFilters: Codable, Hashable { var filters: [ItemFilter] = [] var sortOrder: [APISortOrder] = [.descending] var withGenres: [NameGuidPair] = [] - var sortBy: [String] = ["SortName"] + var tags: [String] = [] + var sortBy: [SortBy] = [.name] } public enum SortBy: String, Codable, CaseIterable { @@ -22,3 +23,54 @@ public enum SortBy: String, Codable, CaseIterable { case name = "SortName" case dateAdded = "DateCreated" } + +extension SortBy { + var localized: String { + switch self { + case .productionYear: + return "Production year" + case .premiereDate: + return "Premiere date" + case .name: + return "Name" + case .dateAdded: + return "Date created" + } + } +} + +extension ItemFilter { + var localized: String { + switch self { + case .isFolder: + return "Is folder" + case .isNotFolder: + return "Is not folder" + case .isUnplayed: + return "Is unplayed" + case .isPlayed: + return "Is played" + case .isFavorite: + return "Is favorite" + case .isResumable: + return "Is resumable" + case .likes: + return "Likes" + case .dislikes: + return "Dislikes" + case .isFavoriteOrLikes: + return "Is favorite or likes" + } + } +} + +extension APISortOrder { + var localized: String { + switch self { + case .ascending: + return "Ascending" + case .descending: + return "Descending" + } + } +} diff --git a/Shared/ViewModels/ConnectToServerViewModel.swift b/Shared/ViewModels/ConnectToServerViewModel.swift index 88f2af3f..f81cd91f 100644 --- a/Shared/ViewModels/ConnectToServerViewModel.swift +++ b/Shared/ViewModels/ConnectToServerViewModel.swift @@ -37,7 +37,7 @@ final class ConnectToServerViewModel: ViewModel { if ServerEnvironment.current.server != nil { UserAPI.getPublicUsers() .sink(receiveCompletion: { completion in - self.HandleAPIRequestCompletion(completion: completion) + self.handleAPIRequestCompletion(completion: completion) }, receiveValue: { response in self.publicUsers = response self.isConnectedServer = true @@ -74,7 +74,7 @@ final class ConnectToServerViewModel: ViewModel { func login() { SessionManager.current.login(username: username, password: password) .sink(receiveCompletion: { completion in - self.HandleAPIRequestCompletion(completion: completion) + self.handleAPIRequestCompletion(completion: completion) }, receiveValue: { _ in }) diff --git a/Shared/ViewModels/EpisodeItemViewModel.swift b/Shared/ViewModels/EpisodeItemViewModel.swift index 66274f2b..552f6bc1 100644 --- a/Shared/ViewModels/EpisodeItemViewModel.swift +++ b/Shared/ViewModels/EpisodeItemViewModel.swift @@ -33,7 +33,7 @@ final class EpisodeItemViewModel: ViewModel { PlaystateAPI.markUnplayedItem(userId: SessionManager.current.user.user_id!, itemId: id) .trackActivity(loading) .sink(receiveCompletion: { [weak self] completion in - self?.HandleAPIRequestCompletion(completion: completion) + self?.handleAPIRequestCompletion(completion: completion) }, receiveValue: { [weak self] _ in self?.isWatched = false }) @@ -42,7 +42,7 @@ final class EpisodeItemViewModel: ViewModel { PlaystateAPI.markPlayedItem(userId: SessionManager.current.user.user_id!, itemId: id) .trackActivity(loading) .sink(receiveCompletion: { [weak self] completion in - self?.HandleAPIRequestCompletion(completion: completion) + self?.handleAPIRequestCompletion(completion: completion) }, receiveValue: { [weak self] _ in self?.isWatched = true }) @@ -56,7 +56,7 @@ final class EpisodeItemViewModel: ViewModel { UserLibraryAPI.unmarkFavoriteItem(userId: SessionManager.current.user.user_id!, itemId: id) .trackActivity(loading) .sink(receiveCompletion: { [weak self] completion in - self?.HandleAPIRequestCompletion(completion: completion) + self?.handleAPIRequestCompletion(completion: completion) }, receiveValue: { [weak self] _ in self?.isFavorited = false }) @@ -65,7 +65,7 @@ final class EpisodeItemViewModel: ViewModel { UserLibraryAPI.markFavoriteItem(userId: SessionManager.current.user.user_id!, itemId: id) .trackActivity(loading) .sink(receiveCompletion: { [weak self] completion in - self?.HandleAPIRequestCompletion(completion: completion) + self?.handleAPIRequestCompletion(completion: completion) }, receiveValue: { [weak self] _ in self?.isFavorited = true }) diff --git a/Shared/ViewModels/HomeViewModel.swift b/Shared/ViewModels/HomeViewModel.swift index 81b0778c..9f0d6e85 100644 --- a/Shared/ViewModels/HomeViewModel.swift +++ b/Shared/ViewModels/HomeViewModel.swift @@ -24,7 +24,7 @@ final class HomeViewModel: ViewModel { var nextUpItems = [BaseItemDto]() // temp - var recentFilterSet: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.descending], sortBy: ["DateCreated"]) + var recentFilterSet: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.descending], sortBy: [.dateAdded]) override init() { super.init() @@ -36,7 +36,7 @@ final class HomeViewModel: ViewModel { UserViewsAPI.getUserViews(userId: SessionManager.current.user.user_id!) .trackActivity(loading) .sink(receiveCompletion: { completion in - self.HandleAPIRequestCompletion(completion: completion) + self.handleAPIRequestCompletion(completion: completion) }, receiveValue: { response in response.items!.forEach { item in if item.collectionType == "movies" || item.collectionType == "tvshows" { @@ -47,7 +47,7 @@ final class HomeViewModel: ViewModel { UserAPI.getCurrentUser() .trackActivity(self.loading) .sink(receiveCompletion: { completion in - self.HandleAPIRequestCompletion(completion: completion) + self.handleAPIRequestCompletion(completion: completion) }, receiveValue: { response in self.libraries.forEach { library in if !(response.configuration?.latestItemsExcludes?.contains(library.id!))! { @@ -64,7 +64,7 @@ final class HomeViewModel: ViewModel { mediaTypes: ["Video"], imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb]) .trackActivity(loading) .sink(receiveCompletion: { completion in - self.HandleAPIRequestCompletion(completion: completion) + self.handleAPIRequestCompletion(completion: completion) }, receiveValue: { response in self.resumeItems = response.items ?? [] }) @@ -74,7 +74,7 @@ final class HomeViewModel: ViewModel { fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people]) .trackActivity(loading) .sink(receiveCompletion: { completion in - self.HandleAPIRequestCompletion(completion: completion) + self.handleAPIRequestCompletion(completion: completion) }, receiveValue: { response in self.nextUpItems = response.items ?? [] }) diff --git a/Shared/ViewModels/LatestMediaViewModel.swift b/Shared/ViewModels/LatestMediaViewModel.swift index e6732738..e9255a8f 100644 --- a/Shared/ViewModels/LatestMediaViewModel.swift +++ b/Shared/ViewModels/LatestMediaViewModel.swift @@ -37,7 +37,7 @@ final class LatestMediaViewModel: ViewModel { enableUserData: true, limit: 12) .trackActivity(loading) .sink(receiveCompletion: { [weak self] completion in - self?.HandleAPIRequestCompletion(completion: completion) + self?.handleAPIRequestCompletion(completion: completion) }, receiveValue: { [weak self] response in self?.items = response }) diff --git a/Shared/ViewModels/LibraryFilterViewModel.swift b/Shared/ViewModels/LibraryFilterViewModel.swift new file mode 100644 index 00000000..0b32e181 --- /dev/null +++ b/Shared/ViewModels/LibraryFilterViewModel.swift @@ -0,0 +1,61 @@ +// +/* + * 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 2021 Aiden Vigue & Jellyfin Contributors + */ + +import Combine +import Foundation +import JellyfinAPI + +enum FilterType { + case tag + case genre + case sortOrder + case sortBy + case filter +} + +final class LibraryFilterViewModel: ViewModel { + @Published + var modifyedFilters = LibraryFilters() + + @Published + var allGenres = [NameGuidPair]() + @Published + var allTags = [String]() + @Published + var allSortOrders = APISortOrder.allCases + @Published + var allSortBys = SortBy.allCases + @Published + var allItemFilters = ItemFilter.allCases + @Published + var enabledFilterType: [FilterType] + + init(filters: LibraryFilters? = nil, + enabledFilterType: [FilterType] = [.tag, .genre, .sortBy, .sortOrder, .filter]) { + self.enabledFilterType = enabledFilterType + super.init() + if let filters = filters { + self.modifyedFilters = filters + } + refresh() + } + + func refresh() { + FilterAPI.getQueryFilters(userId: SessionManager.current.user.user_id!) + .trackActivity(loading) + .sink(receiveCompletion: { [weak self] completion in + self?.handleAPIRequestCompletion(completion: completion) + }, receiveValue: { [weak self] quertFilters in + guard let self = self else { return } + self.allGenres = quertFilters.genres ?? [] + self.allTags = quertFilters.tags ?? [] + }) + .store(in: &cancellables) + } +} diff --git a/Shared/ViewModels/LibraryListViewModel.swift b/Shared/ViewModels/LibraryListViewModel.swift index 7dba1888..16efefe4 100644 --- a/Shared/ViewModels/LibraryListViewModel.swift +++ b/Shared/ViewModels/LibraryListViewModel.swift @@ -29,7 +29,7 @@ final class LibraryListViewModel: ViewModel { UserViewsAPI.getUserViews(userId: SessionManager.current.user.user_id!) .trackActivity(loading) .sink(receiveCompletion: { completion in - self.HandleAPIRequestCompletion(completion: completion) + self.handleAPIRequestCompletion(completion: completion) }, receiveValue: { response in self.libraries.append(contentsOf: response.items ?? []) }) diff --git a/Shared/ViewModels/LibrarySearchViewModel.swift b/Shared/ViewModels/LibrarySearchViewModel.swift index 1f46ee14..065b0e47 100644 --- a/Shared/ViewModels/LibrarySearchViewModel.swift +++ b/Shared/ViewModels/LibrarySearchViewModel.swift @@ -36,7 +36,7 @@ final class LibrarySearchViewModel: ViewModel { includeItemTypes: ["Movie", "Series"], sortBy: ["SortName"], enableUserData: true, enableImages: true) .trackActivity(loading) .sink(receiveCompletion: { [weak self] completion in - self?.HandleAPIRequestCompletion(completion: completion) + self?.handleAPIRequestCompletion(completion: completion) }, receiveValue: { [weak self] response in self?.items = response.items ?? [] }) diff --git a/Shared/ViewModels/LibraryViewModel.swift b/Shared/ViewModels/LibraryViewModel.swift index c0fff9c5..4e27e4af 100644 --- a/Shared/ViewModels/LibraryViewModel.swift +++ b/Shared/ViewModels/LibraryViewModel.swift @@ -30,13 +30,22 @@ final class LibraryViewModel: ViewModel { var isCanPreviousPaging = false // temp + @Published var filters: LibraryFilters + var enabledFilterType: [FilterType] { + if genre == nil { + return [.tag, .genre, .sortBy, .sortOrder, .filter] + } else { + return [.tag, .sortBy, .sortOrder, .filter] + } + } + init(parentID: String? = nil, person: BaseItemPerson? = nil, genre: NameGuidPair? = nil, studio: NameGuidPair? = nil, - filters: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], sortBy: ["SortName"])) + filters: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], sortBy: [.name])) { self.parentID = parentID self.person = person @@ -45,18 +54,30 @@ final class LibraryViewModel: ViewModel { self.filters = filters super.init() - refresh() + $filters + .sink(receiveValue: refresh(with:)) + .store(in: &cancellables) } - - func refresh() { + + func refresh(with filters: LibraryFilters) { let personIDs: [String] = [person].compactMap(\.?.id) let studioIDs: [String] = [studio].compactMap(\.?.id) - let genreIDs: [String] = [genre].compactMap(\.?.id) - - ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, startIndex: currentPage * 100, limit: 100, recursive: true, searchTerm: nil, sortOrder: filters.sortOrder, parentId: parentID, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], includeItemTypes: ["Movie", "Series"], filters: filters.filters, sortBy: filters.sortBy, enableUserData: true, personIds: personIDs, studioIds: studioIDs, genreIds: genreIDs, enableImages: true) + let genreIDs: [String] + if filters.withGenres.isEmpty { + genreIDs = [genre].compactMap(\.?.id) + } else { + genreIDs = filters.withGenres.compactMap(\.id) + } + let sortBy = filters.sortBy.map(\.rawValue) + + ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, startIndex: currentPage * 100, limit: 100, recursive: true, + searchTerm: nil, sortOrder: filters.sortOrder, parentId: parentID, + fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], + includeItemTypes: ["Movie", "Series"], filters: filters.filters, sortBy: sortBy, tags: filters.tags, + enableUserData: true, personIds: personIDs, studioIds: studioIDs, genreIds: genreIDs, enableImages: true) .trackActivity(loading) .sink(receiveCompletion: { [weak self] completion in - self?.HandleAPIRequestCompletion(completion: completion) + self?.handleAPIRequestCompletion(completion: completion) }, receiveValue: { [weak self] response in guard let self = self else { return } let totalPages = ceil(Double(response.totalRecordCount ?? 0) / 100.0) @@ -67,14 +88,14 @@ final class LibraryViewModel: ViewModel { }) .store(in: &cancellables) } - + func requestNextPage() { currentPage += 1 - refresh() + refresh(with: filters) } - + func requestPreviousPage() { currentPage -= 1 - refresh() + refresh(with: filters) } } diff --git a/Shared/ViewModels/MovieItemViewModel.swift b/Shared/ViewModels/MovieItemViewModel.swift index c3c5b4c3..82f45cea 100644 --- a/Shared/ViewModels/MovieItemViewModel.swift +++ b/Shared/ViewModels/MovieItemViewModel.swift @@ -33,7 +33,7 @@ final class MovieItemViewModel: ViewModel { PlaystateAPI.markUnplayedItem(userId: SessionManager.current.user.user_id!, itemId: id) .trackActivity(loading) .sink(receiveCompletion: { [weak self] completion in - self?.HandleAPIRequestCompletion(completion: completion) + self?.handleAPIRequestCompletion(completion: completion) }, receiveValue: { [weak self] _ in self?.isWatched = false }) @@ -42,7 +42,7 @@ final class MovieItemViewModel: ViewModel { PlaystateAPI.markPlayedItem(userId: SessionManager.current.user.user_id!, itemId: id) .trackActivity(loading) .sink(receiveCompletion: { [weak self] completion in - self?.HandleAPIRequestCompletion(completion: completion) + self?.handleAPIRequestCompletion(completion: completion) }, receiveValue: { [weak self] _ in self?.isWatched = true }) @@ -56,7 +56,7 @@ final class MovieItemViewModel: ViewModel { UserLibraryAPI.unmarkFavoriteItem(userId: SessionManager.current.user.user_id!, itemId: id) .trackActivity(loading) .sink(receiveCompletion: { [weak self] completion in - self?.HandleAPIRequestCompletion(completion: completion) + self?.handleAPIRequestCompletion(completion: completion) }, receiveValue: { [weak self] _ in self?.isFavorited = false }) @@ -65,7 +65,7 @@ final class MovieItemViewModel: ViewModel { UserLibraryAPI.markFavoriteItem(userId: SessionManager.current.user.user_id!, itemId: id) .trackActivity(loading) .sink(receiveCompletion: { [weak self] completion in - self?.HandleAPIRequestCompletion(completion: completion) + self?.handleAPIRequestCompletion(completion: completion) }, receiveValue: { [weak self] _ in self?.isFavorited = true }) diff --git a/Shared/ViewModels/SeasonItemViewModel.swift b/Shared/ViewModels/SeasonItemViewModel.swift index 278d71d0..91f01e72 100644 --- a/Shared/ViewModels/SeasonItemViewModel.swift +++ b/Shared/ViewModels/SeasonItemViewModel.swift @@ -31,7 +31,7 @@ final class SeasonItemViewModel: ViewModel { seasonId: item.id ?? "") .trackActivity(loading) .sink(receiveCompletion: { [weak self] completion in - self?.HandleAPIRequestCompletion(completion: completion) + self?.handleAPIRequestCompletion(completion: completion) }, receiveValue: { [weak self] response in self?.episodes = response.items ?? [] }) diff --git a/Shared/ViewModels/SeriesItemViewModel.swift b/Shared/ViewModels/SeriesItemViewModel.swift index abd7b2d4..9602ca62 100644 --- a/Shared/ViewModels/SeriesItemViewModel.swift +++ b/Shared/ViewModels/SeriesItemViewModel.swift @@ -29,7 +29,7 @@ final class SeriesItemViewModel: ViewModel { TvShowsAPI.getSeasons(seriesId: item.id ?? "", fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people]) .trackActivity(loading) .sink(receiveCompletion: { [weak self] completion in - self?.HandleAPIRequestCompletion(completion: completion) + self?.handleAPIRequestCompletion(completion: completion) }, receiveValue: { [weak self] response in self?.seasons = response.items ?? [] }) diff --git a/Shared/ViewModels/ViewModel.swift b/Shared/ViewModels/ViewModel.swift index 5039d991..6417aa6e 100644 --- a/Shared/ViewModels/ViewModel.swift +++ b/Shared/ViewModels/ViewModel.swift @@ -32,7 +32,7 @@ class ViewModel: ObservableObject { loading.loading.assign(to: \.isLoading, on: self).store(in: &cancellables) } - func HandleAPIRequestCompletion(completion: Subscribers.Completion) { + func handleAPIRequestCompletion(completion: Subscribers.Completion) { switch completion { case .finished: break