From 72375ab7312363f785e9c6936a80d49e3157f34a Mon Sep 17 00:00:00 2001 From: PangMo5 Date: Sat, 19 Jun 2021 05:34:39 +0900 Subject: [PATCH] add LibraryViewModel --- JellyfinPlayer.xcodeproj/project.pbxproj | 18 +- JellyfinPlayer/EpisodeItemView.swift | 12 +- JellyfinPlayer/HomeView.swift | 3 +- JellyfinPlayer/LibraryListView.swift | 4 +- JellyfinPlayer/LibrarySearchView.swift | 71 +++--- JellyfinPlayer/LibraryView.swift | 230 ++++++------------ JellyfinPlayer/MovieItemView.swift | 13 +- JellyfinPlayer/SeasonItemView.swift | 4 +- ...del.swift => LibrarySearchViewModel.swift} | 0 Shared/ViewModels/LibraryViewModel.swift | 80 ++++++ 10 files changed, 225 insertions(+), 210 deletions(-) rename Shared/ViewModels/{LibrarySearchviewModel.swift => LibrarySearchViewModel.swift} (100%) create mode 100644 Shared/ViewModels/LibraryViewModel.swift diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index f7a8da0f..e861ed79 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -118,9 +118,11 @@ 628B953A2670CE250091AF3B /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 628B95392670CE250091AF3B /* KeychainSwift */; }; 628B953C2670D2430091AF3B /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621338922660107500A81A2A /* StringExtensions.swift */; }; 62E632DA267D2BC40063E547 /* LatestMediaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */; }; - 62E632DC267D2E130063E547 /* LibrarySearchviewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632DB267D2E130063E547 /* LibrarySearchviewModel.swift */; }; - 62E632DD267D2E130063E547 /* LibrarySearchviewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632DB267D2E130063E547 /* LibrarySearchviewModel.swift */; }; + 62E632DC267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632DB267D2E130063E547 /* LibrarySearchViewModel.swift */; }; + 62E632DD267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632DB267D2E130063E547 /* LibrarySearchViewModel.swift */; }; 62E632DE267D2E170063E547 /* LatestMediaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */; }; + 62E632E0267D30CA0063E547 /* LibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632DF267D30CA0063E547 /* LibraryViewModel.swift */; }; + 62E632E1267D30CA0063E547 /* LibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632DF267D30CA0063E547 /* LibraryViewModel.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 */; }; @@ -270,7 +272,8 @@ 628B95362670CB800091AF3B /* JellyfinWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinWidget.swift; sourceTree = ""; }; 628B953B2670D1FC0091AF3B /* WidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetExtension.entitlements; sourceTree = ""; }; 62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestMediaViewModel.swift; sourceTree = ""; }; - 62E632DB267D2E130063E547 /* LibrarySearchviewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchviewModel.swift; sourceTree = ""; }; + 62E632DB267D2E130063E547 /* LibrarySearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchViewModel.swift; sourceTree = ""; }; + 62E632DF267D30CA0063E547 /* LibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryViewModel.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 = ""; }; @@ -333,7 +336,8 @@ 625CB57B2678CE1000530A6E /* ViewModel.swift */, 536D3D75267BA9BB0004248C /* MainTabViewModel.swift */, 62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */, - 62E632DB267D2E130063E547 /* LibrarySearchviewModel.swift */, + 62E632DB267D2E130063E547 /* LibrarySearchViewModel.swift */, + 62E632DF267D30CA0063E547 /* LibraryViewModel.swift */, ); path = ViewModels; sourceTree = ""; @@ -706,13 +710,14 @@ 531690ED267ABF46005D8AB9 /* ContinueWatchingView.swift in Sources */, 62EC3530267666A5000E9F2D /* SessionManager.swift in Sources */, 531690F7267ACC00005D8AB9 /* LandscapeItemElement.swift in Sources */, + 62E632E1267D30CA0063E547 /* LibraryViewModel.swift in Sources */, 535870A82669D8AE00D05A09 /* StringExtensions.swift in Sources */, 53ABFDE6267974EF00886593 /* SettingsViewModel.swift in Sources */, 6267B3D826710B9800A7371D /* CollectionExtensions.swift in Sources */, 535870A52669D8AE00D05A09 /* ParallaxHeader.swift in Sources */, 531690F0267ABF72005D8AB9 /* NextUpView.swift in Sources */, 535870A72669D8AE00D05A09 /* MultiSelectorView.swift in Sources */, - 62E632DD267D2E130063E547 /* LibrarySearchviewModel.swift in Sources */, + 62E632DD267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */, 536D3D81267BDFC60004248C /* PortraitItemElement.swift in Sources */, 531690E5267ABD5C005D8AB9 /* MainTabView.swift in Sources */, 53ABFDE7267974EF00886593 /* ConnectToServerViewModel.swift in Sources */, @@ -741,7 +746,7 @@ 621338932660107500A81A2A /* StringExtensions.swift in Sources */, 53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */, 625CB5732678C32A00530A6E /* HomeViewModel.swift in Sources */, - 62E632DC267D2E130063E547 /* LibrarySearchviewModel.swift in Sources */, + 62E632DC267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */, 5377CBFE263B596B003A4E83 /* PersistenceController.swift in Sources */, 5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */, 53AD124E26702B8A0094A276 /* SeasonItemView.swift in Sources */, @@ -769,6 +774,7 @@ 53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */, 621338B32660A07800A81A2A /* LazyView.swift in Sources */, 531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */, + 62E632E0267D30CA0063E547 /* LibraryViewModel.swift in Sources */, 62EC352F267666A5000E9F2D /* SessionManager.swift in Sources */, 535870AD2669D8DD00D05A09 /* Typings.swift in Sources */, 62EC352C26766675000E9F2D /* ServerEnvironment.swift in Sources */, diff --git a/JellyfinPlayer/EpisodeItemView.swift b/JellyfinPlayer/EpisodeItemView.swift index b1ca2046..7b1dc8e9 100644 --- a/JellyfinPlayer/EpisodeItemView.swift +++ b/JellyfinPlayer/EpisodeItemView.swift @@ -174,7 +174,7 @@ struct EpisodeItemView: View { Text("Genres:").font(.callout).fontWeight(.semibold) ForEach(item.genreItems!, id: \.id) { genre in NavigationLink(destination: LazyView { - LibraryView(withGenre: genre) + LibraryView(viewModel: .init(genre: genre), title: genre.name ?? "") }) { Text(genre.name ?? "").font(.footnote) } @@ -191,7 +191,7 @@ struct EpisodeItemView: View { ForEach(item.people!, id: \.self) { person in if person.type! == "Actor" { NavigationLink(destination: LazyView { - LibraryView(withPerson: person) + LibraryView(viewModel: .init(person: person), title: person.name ?? "") }) { VStack { ImageView(src: person.getImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 100), bh: person.getBlurHash()) @@ -219,7 +219,7 @@ struct EpisodeItemView: View { Text("Studios:").font(.callout).fontWeight(.semibold) ForEach(item.studios!, id: \.id) { studio in NavigationLink(destination: LazyView { - LibraryView(withStudio: studio) + LibraryView(viewModel: .init(studio: studio), title: studio.name ?? "") }) { Text(studio.name ?? "").font(.footnote) } @@ -343,7 +343,7 @@ struct EpisodeItemView: View { Text("Genres:").font(.callout).fontWeight(.semibold) ForEach(item.genreItems!, id: \.id) { genre in NavigationLink(destination: LazyView { - LibraryView(withGenre: genre) + LibraryView(viewModel: .init(genre: genre), title: genre.name ?? "") }) { Text(genre.name ?? "").font(.footnote) } @@ -362,7 +362,7 @@ struct EpisodeItemView: View { ForEach(item.people!, id: \.self) { person in if person.type! == "Actor" { NavigationLink(destination: LazyView { - LibraryView(withPerson: person) + LibraryView(viewModel: .init(person: person), title: person.name ?? "") }) { VStack { ImageView(src: person.getImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 100), bh: person.getBlurHash()) @@ -390,7 +390,7 @@ struct EpisodeItemView: View { Text("Studios:").font(.callout).fontWeight(.semibold) ForEach(item.studios!, id: \.id) { studio in NavigationLink(destination: LazyView { - LibraryView(withStudio: studio) + LibraryView(viewModel: .init(studio: studio), title: studio.name ?? "") }) { Text(studio.name ?? "").font(.footnote) } diff --git a/JellyfinPlayer/HomeView.swift b/JellyfinPlayer/HomeView.swift index ee5e0512..779a7389 100644 --- a/JellyfinPlayer/HomeView.swift +++ b/JellyfinPlayer/HomeView.swift @@ -44,8 +44,7 @@ struct HomeView: View { .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16)) Spacer() NavigationLink(destination: LazyView { - LibraryView(usingParentID: libraryID, - title: library?.name ?? "", usingFilters: viewModel.recentFilterSet) + LibraryView(viewModel: .init(parentID: libraryID, filters: viewModel.recentFilterSet), title: library?.name ?? "") }) { HStack { Text("See All").font(.subheadline).fontWeight(.bold) diff --git a/JellyfinPlayer/LibraryListView.swift b/JellyfinPlayer/LibraryListView.swift index 681a8234..0fecac3e 100644 --- a/JellyfinPlayer/LibraryListView.swift +++ b/JellyfinPlayer/LibraryListView.swift @@ -17,7 +17,7 @@ struct LibraryListView: View { switch library.id { case "favorites": NavigationLink(destination: LazyView { - LibraryView(usingParentID: "", title: library.name ?? "", usingFilters: viewModel.withFavorites) + LibraryView(viewModel: .init(filters: viewModel.withFavorites), title: library.name ?? "") }) { Text(library.name ?? "") } @@ -29,7 +29,7 @@ struct LibraryListView: View { } default: NavigationLink(destination: LazyView { - LibraryView(usingParentID: library.id ?? "", title: library.name ?? "") + LibraryView(viewModel: .init(parentID: library.id), title: library.name ?? "") }) { Text(library.name ?? "") } diff --git a/JellyfinPlayer/LibrarySearchView.swift b/JellyfinPlayer/LibrarySearchView.swift index c2b8c380..56c5ffca 100644 --- a/JellyfinPlayer/LibrarySearchView.swift +++ b/JellyfinPlayer/LibrarySearchView.swift @@ -17,7 +17,7 @@ struct LibrarySearchView: View { @State private var tracks: [GridItem] = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 125) - + func recalcTracks() { tracks = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 125) } @@ -26,47 +26,44 @@ struct LibrarySearchView: View { VStack { Spacer().frame(height: 6) SearchBar(text: $viewModel.searchQuery) - ZStack { - ScrollView(.vertical) { - if !viewModel.items.isEmpty { - Spacer().frame(height: 16) - LazyVGrid(columns: tracks) { - ForEach(viewModel.items, id: \.id) { item in - NavigationLink(destination: ItemView(item: item)) { - VStack(alignment: .leading) { - ImageView(src: item.getPrimaryImage(maxWidth: 100), bh: item.getPrimaryImageBlurHash()) - .frame(width: 100, height: 150) - .cornerRadius(10) - Text(item.name ?? "") - .font(.caption) - .fontWeight(.semibold) - .foregroundColor(.primary) - .lineLimit(1) - if item.productionYear != nil { - Text(String(item.productionYear!)) - .foregroundColor(.secondary) - .font(.caption) - .fontWeight(.medium) - } else { - Text(item.type ?? "") - } - }.frame(width: 100) - } - } - Spacer().frame(height: 16) - } - } else { - Text("No results :(") - } - } + ScrollView(.vertical) { if viewModel.isLoading { ProgressView() + } else if !viewModel.items.isEmpty { + Spacer().frame(height: 16) + LazyVGrid(columns: tracks) { + ForEach(viewModel.items, id: \.id) { item in + NavigationLink(destination: ItemView(item: item)) { + VStack(alignment: .leading) { + ImageView(src: item.getPrimaryImage(maxWidth: 100), bh: item.getPrimaryImageBlurHash()) + .frame(width: 100, height: 150) + .cornerRadius(10) + Text(item.name ?? "") + .font(.caption) + .fontWeight(.semibold) + .foregroundColor(.primary) + .lineLimit(1) + if item.productionYear != nil { + Text(String(item.productionYear!)) + .foregroundColor(.secondary) + .font(.caption) + .fontWeight(.medium) + } else { + Text(item.type ?? "") + } + }.frame(width: 100) + } + } + Spacer().frame(height: 16) + } + .onRotate { _ in + recalcTracks() + } + } else { + Text("No results :(") } } } .navigationBarTitle("Search", displayMode: .inline) - .onRotate { _ in - recalcTracks() - } } } diff --git a/JellyfinPlayer/LibraryView.swift b/JellyfinPlayer/LibraryView.swift index c6c00f9f..576207e2 100644 --- a/JellyfinPlayer/LibraryView.swift +++ b/JellyfinPlayer/LibraryView.swift @@ -6,190 +6,122 @@ * Copyright 2021 Aiden Vigue & Jellyfin Contributors */ -import SwiftUI -import NukeUI -import JellyfinAPI import Combine +import JellyfinAPI +import NukeUI +import SwiftUI struct LibraryView: View { - @StateObject - var tempViewModel = ViewModel() - @State private var items: [BaseItemDto] = [] - @State private var isLoading: Bool = false - - private var usingParentID: String = "" - private var title: String = "" - private var filters: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], sortBy: ["SortName"]) - private var personId: String = "" - private var genre: String = "" - private var studio: String = "" - - @State private var totalPages: Int = 0 - @State private var currentPage: Int = 0 - @State private var isSearching: String? = "" - @State private var viewDidLoad: Bool = false - - init(usingParentID: String, title: String) { - self.usingParentID = usingParentID - self.title = title - } - - init(usingParentID: String, title: String, usingFilters: LibraryFilters) { - self.usingParentID = usingParentID - self.title = title - self.filters = usingFilters - } - - init(withPerson: BaseItemPerson) { - self.usingParentID = "" - self.title = withPerson.name ?? "" - self.personId = withPerson.id! - } - - init(withGenre: NameGuidPair) { - self.usingParentID = "" - self.title = withGenre.name ?? "" - self.genre = withGenre.id! - } - - init(withStudio: NameGuidPair) { - self.usingParentID = "" - self.title = withStudio.name ?? "" - self.studio = withStudio.id! - } - - func onAppear() { - recalcTracks() - - if viewDidLoad { - return - } - - isLoading = true - items = [] - - DispatchQueue.global(qos: .userInitiated).async { - ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, startIndex: currentPage * 100, limit: 100, recursive: true, searchTerm: nil, sortOrder: filters.sortOrder, parentId: (usingParentID != "" ? usingParentID : nil), fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], includeItemTypes: ["Movie", "Series"], filters: filters.filters, sortBy: filters.sortBy, enableUserData: true, personIds: (personId == "" ? nil : [personId]), studioIds: (studio == "" ? nil : [studio]), genreIds: (genre == "" ? nil : [genre]), enableImages: true) - .sink(receiveCompletion: { completion in - print(completion) - isLoading = false - }, receiveValue: { response in - let x = ceil(Double(response.totalRecordCount!) / 100.0) - totalPages = Int(x) - items = response.items ?? [] - isLoading = false - viewDidLoad = true - }) - .store(in: &tempViewModel.cancellables) - } - } + var viewModel: LibraryViewModel + var title: String // MARK: tracks for grid - @State private var tracks: [GridItem] = [] + + @State + var isShowingSearchView = false + + @State + private var tracks: [GridItem] = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 125) + func recalcTracks() { - let trkCnt = Int(floor(UIScreen.main.bounds.size.width / 125)) - _tracks.wrappedValue = [] - for _ in 0 ..< trkCnt { - _tracks.wrappedValue.append(GridItem(.flexible())) - } + tracks = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 125) } var body: some View { - ZStack { - if isLoading == true { + Group { + if viewModel.isLoading == true { ProgressView() - } else { - if !items.isEmpty { - VStack { - ScrollView(.vertical) { - Spacer().frame(height: 16) - LazyVGrid(columns: tracks) { - ForEach(items, id: \.id) { item in - NavigationLink(destination: ItemView(item: item)) { - VStack(alignment: .leading) { - ImageView(src: item.getPrimaryImage(maxWidth: 100), bh: item.getPrimaryImageBlurHash()) - .frame(width: 100, height: 150) - .cornerRadius(10) - Text(item.name ?? "") - .font(.caption) - .fontWeight(.semibold) - .foregroundColor(.primary) - .lineLimit(1) - if item.productionYear != nil { - Text(String(item.productionYear!)) - .foregroundColor(.secondary) - .font(.caption) - .fontWeight(.medium) - } else { - Text(item.type ?? "") - } - }.frame(width: 100) - } - } - }.onRotate { _ in - recalcTracks() - } - if totalPages > 1 { - HStack { - Spacer() - HStack { - Button { - currentPage = currentPage - 1 - onAppear() - } label: { - Image(systemName: "chevron.left") - .font(.system(size: 25)) - }.disabled(currentPage == 0) - Text("Page \(String(currentPage+1)) of \(String(totalPages))") - .font(.headline) + } else if !viewModel.items.isEmpty { + VStack { + ScrollView(.vertical) { + Spacer().frame(height: 16) + LazyVGrid(columns: tracks) { + ForEach(viewModel.items, id: \.id) { item in + NavigationLink(destination: ItemView(item: item)) { + VStack(alignment: .leading) { + ImageView(src: item.getPrimaryImage(maxWidth: 100), bh: item.getPrimaryImageBlurHash()) + .frame(width: 100, height: 150) + .cornerRadius(10) + Text(item.name ?? "") + .font(.caption) .fontWeight(.semibold) - Button { - currentPage = currentPage + 1 - onAppear() - } label: { - Image(systemName: "chevron.right") - .font(.system(size: 25)) - }.disabled(currentPage > totalPages - 1) - } - Spacer() + .foregroundColor(.primary) + .lineLimit(1) + if item.productionYear != nil { + Text(String(item.productionYear!)) + .foregroundColor(.secondary) + .font(.caption) + .fontWeight(.medium) + } else { + Text(item.type ?? "") + } + }.frame(width: 100) } } - Spacer().frame(height: 16) + }.onRotate { _ in + recalcTracks() } + if viewModel.isCanNextPaging || viewModel.isCanPreviousPaging { + HStack { + Spacer() + HStack { + Button { + viewModel.requestPreviousPage() + } label: { + Image(systemName: "chevron.left") + .font(.system(size: 25)) + }.disabled(viewModel.isCanPreviousPaging) + Text("Page \(String(viewModel.currentPage + 1)) of \(String(viewModel.totalPages))") + .font(.headline) + .fontWeight(.semibold) + Button { + viewModel.requestNextPage() + } label: { + Image(systemName: "chevron.right") + .font(.system(size: 25)) + }.disabled(viewModel.isCanNextPaging) + } + Spacer() + } + } + Spacer().frame(height: 16) } - } else { - Text("No results.") } + } else { + Text("No results.") } } - .onAppear(perform: onAppear) .navigationBarTitle(title, displayMode: .inline) .toolbar { ToolbarItemGroup(placement: .navigationBarTrailing) { - if currentPage > 0 { + if viewModel.isCanPreviousPaging { Button { - currentPage = currentPage - 1 - onAppear() + viewModel.requestPreviousPage() } label: { Image(systemName: "chevron.left") } } - if currentPage < totalPages - 1 { + if viewModel.isCanNextPaging { Button { - currentPage = currentPage + 1 - onAppear() + viewModel.requestNextPage() } label: { Image(systemName: "chevron.right") } } - if usingParentID != "" { - NavigationLink(destination: LibrarySearchView(viewModel: .init(parentID: usingParentID))) { - Image(systemName: "magnifyingglass") - } + Button(action: { + isShowingSearchView = true + }) { + Image(systemName: "magnifyingglass") } } } + .background( + NavigationLink(destination: LibrarySearchView(viewModel: .init(parentID: viewModel.parentID)), + isActive: $isShowingSearchView) { + EmptyView() + } + ) } } diff --git a/JellyfinPlayer/MovieItemView.swift b/JellyfinPlayer/MovieItemView.swift index 73248081..8c6e3623 100644 --- a/JellyfinPlayer/MovieItemView.swift +++ b/JellyfinPlayer/MovieItemView.swift @@ -187,7 +187,7 @@ struct MovieItemView: View { Text("Genres:").font(.callout).fontWeight(.semibold) ForEach(item.genreItems!, id: \.id) { genre in NavigationLink(destination: LazyView { - LibraryView(withGenre: genre) + LibraryView(viewModel: .init(genre: genre), title: genre.name ?? "") }) { Text(genre.name ?? "").font(.footnote) } @@ -204,7 +204,7 @@ struct MovieItemView: View { ForEach(item.people!, id: \.self) { person in if person.type! == "Actor" { NavigationLink(destination: LazyView { - LibraryView(withPerson: person) + LibraryView(viewModel: .init(person: person), title: person.name ?? "") }) { VStack { ImageView(src: person @@ -234,7 +234,8 @@ struct MovieItemView: View { Text("Studios:").font(.callout).fontWeight(.semibold) ForEach(item.studios!, id: \.id) { studio in NavigationLink(destination: LazyView { - LibraryView(withStudio: studio) + + LibraryView(viewModel: .init(studio: studio), title: studio.name ?? "") }) { Text(studio.name ?? "").font(.footnote) } @@ -362,7 +363,7 @@ struct MovieItemView: View { Text("Genres:").font(.callout).fontWeight(.semibold) ForEach(item.genreItems!, id: \.id) { genre in NavigationLink(destination: LazyView { - LibraryView(withGenre: genre) + LibraryView(viewModel: .init(genre: genre), title: genre.name ?? "") }) { Text(genre.name ?? "").font(.footnote) } @@ -381,7 +382,7 @@ struct MovieItemView: View { ForEach(item.people!, id: \.self) { person in if person.type! == "Actor" { NavigationLink(destination: LazyView { - LibraryView(withPerson: person) + LibraryView(viewModel: .init(person: person), title: person.name ?? "") }) { VStack { ImageView(src: person @@ -413,7 +414,7 @@ struct MovieItemView: View { Text("Studios:").font(.callout).fontWeight(.semibold) ForEach(item.studios!, id: \.id) { studio in NavigationLink(destination: LazyView { - LibraryView(withStudio: studio) + LibraryView(viewModel: .init(studio: studio), title: studio.name ?? "") }) { Text(studio.name ?? "").font(.footnote) } diff --git a/JellyfinPlayer/SeasonItemView.swift b/JellyfinPlayer/SeasonItemView.swift index fedf22c4..3810b433 100644 --- a/JellyfinPlayer/SeasonItemView.swift +++ b/JellyfinPlayer/SeasonItemView.swift @@ -139,7 +139,7 @@ struct SeasonItemView: View { Text("Studios:").font(.callout).fontWeight(.semibold) ForEach(item.studios!, id: \.id) { studio in NavigationLink(destination: LazyView { - LibraryView(withStudio: studio) + LibraryView(viewModel: .init(studio: studio), title: studio.name ?? "") }) { Text(studio.name ?? "").font(.footnote) } @@ -232,7 +232,7 @@ struct SeasonItemView: View { Text("Studios:").font(.callout).fontWeight(.semibold) ForEach(item.studios!, id: \.id) { studio in NavigationLink(destination: LazyView { - LibraryView(withStudio: studio) + LibraryView(viewModel: .init(studio: studio), title: studio.name ?? "") }) { Text(studio.name ?? "").font(.footnote) } diff --git a/Shared/ViewModels/LibrarySearchviewModel.swift b/Shared/ViewModels/LibrarySearchViewModel.swift similarity index 100% rename from Shared/ViewModels/LibrarySearchviewModel.swift rename to Shared/ViewModels/LibrarySearchViewModel.swift diff --git a/Shared/ViewModels/LibraryViewModel.swift b/Shared/ViewModels/LibraryViewModel.swift new file mode 100644 index 00000000..c0fff9c5 --- /dev/null +++ b/Shared/ViewModels/LibraryViewModel.swift @@ -0,0 +1,80 @@ +// +/* + * 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 + +final class LibraryViewModel: ViewModel { + var parentID: String? + var person: BaseItemPerson? + var genre: NameGuidPair? + var studio: NameGuidPair? + + @Published + var items = [BaseItemDto]() + + @Published + var totalPages = 0 + @Published + var currentPage = 0 + @Published + var isCanNextPaging = false + @Published + var isCanPreviousPaging = false + + // temp + var filters: LibraryFilters + + init(parentID: String? = nil, + person: BaseItemPerson? = nil, + genre: NameGuidPair? = nil, + studio: NameGuidPair? = nil, + filters: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], sortBy: ["SortName"])) + { + self.parentID = parentID + self.person = person + self.genre = genre + self.studio = studio + self.filters = filters + super.init() + + refresh() + } + + func refresh() { + 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) + .trackActivity(loading) + .sink(receiveCompletion: { [weak self] completion in + self?.HandleAPIRequestCompletion(completion: completion) + }, receiveValue: { [weak self] response in + guard let self = self else { return } + let totalPages = ceil(Double(response.totalRecordCount ?? 0) / 100.0) + self.totalPages = Int(totalPages) + self.isCanPreviousPaging = self.currentPage > 0 + self.isCanNextPaging = self.currentPage < self.totalPages - 1 + self.items = response.items ?? [] + }) + .store(in: &cancellables) + } + + func requestNextPage() { + currentPage += 1 + refresh() + } + + func requestPreviousPage() { + currentPage -= 1 + refresh() + } +}