add LibraryViewModel
This commit is contained in:
parent
88ed1c4a3e
commit
72375ab731
|
@ -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 */,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 ?? "")
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue