add LibraryViewModel

This commit is contained in:
PangMo5 2021-06-19 05:34:39 +09:00
parent 88ed1c4a3e
commit 72375ab731
10 changed files with 225 additions and 210 deletions

View File

@ -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 = "<group>"; };
628B953B2670D1FC0091AF3B /* WidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WidgetExtension.entitlements; sourceTree = "<group>"; };
62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestMediaViewModel.swift; sourceTree = "<group>"; };
62E632DB267D2E130063E547 /* LibrarySearchviewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchviewModel.swift; sourceTree = "<group>"; };
62E632DB267D2E130063E547 /* LibrarySearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchViewModel.swift; sourceTree = "<group>"; };
62E632DF267D30CA0063E547 /* LibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryViewModel.swift; sourceTree = "<group>"; };
62EC352B26766675000E9F2D /* ServerEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerEnvironment.swift; sourceTree = "<group>"; };
62EC352E267666A5000E9F2D /* SessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = "<group>"; };
62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRotationViewModifier.swift; sourceTree = "<group>"; };
@ -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 = "<group>";
@ -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 */,

View File

@ -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)
}

View File

@ -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)

View File

@ -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 ?? "")
}

View File

@ -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()
}
}
}

View File

@ -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()
}
)
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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()
}
}