add LibraryViewModel
This commit is contained in:
parent
88ed1c4a3e
commit
72375ab731
|
@ -118,9 +118,11 @@
|
||||||
628B953A2670CE250091AF3B /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 628B95392670CE250091AF3B /* KeychainSwift */; };
|
628B953A2670CE250091AF3B /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 628B95392670CE250091AF3B /* KeychainSwift */; };
|
||||||
628B953C2670D2430091AF3B /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621338922660107500A81A2A /* StringExtensions.swift */; };
|
628B953C2670D2430091AF3B /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621338922660107500A81A2A /* StringExtensions.swift */; };
|
||||||
62E632DA267D2BC40063E547 /* LatestMediaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */; };
|
62E632DA267D2BC40063E547 /* LatestMediaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */; };
|
||||||
62E632DC267D2E130063E547 /* 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 */; };
|
62E632DD267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632DB267D2E130063E547 /* LibrarySearchViewModel.swift */; };
|
||||||
62E632DE267D2E170063E547 /* LatestMediaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632D9267D2BC40063E547 /* LatestMediaViewModel.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 */; };
|
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, ); }; };
|
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 */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRotationViewModifier.swift; sourceTree = "<group>"; };
|
||||||
|
@ -333,7 +336,8 @@
|
||||||
625CB57B2678CE1000530A6E /* ViewModel.swift */,
|
625CB57B2678CE1000530A6E /* ViewModel.swift */,
|
||||||
536D3D75267BA9BB0004248C /* MainTabViewModel.swift */,
|
536D3D75267BA9BB0004248C /* MainTabViewModel.swift */,
|
||||||
62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */,
|
62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */,
|
||||||
62E632DB267D2E130063E547 /* LibrarySearchviewModel.swift */,
|
62E632DB267D2E130063E547 /* LibrarySearchViewModel.swift */,
|
||||||
|
62E632DF267D30CA0063E547 /* LibraryViewModel.swift */,
|
||||||
);
|
);
|
||||||
path = ViewModels;
|
path = ViewModels;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -706,13 +710,14 @@
|
||||||
531690ED267ABF46005D8AB9 /* ContinueWatchingView.swift in Sources */,
|
531690ED267ABF46005D8AB9 /* ContinueWatchingView.swift in Sources */,
|
||||||
62EC3530267666A5000E9F2D /* SessionManager.swift in Sources */,
|
62EC3530267666A5000E9F2D /* SessionManager.swift in Sources */,
|
||||||
531690F7267ACC00005D8AB9 /* LandscapeItemElement.swift in Sources */,
|
531690F7267ACC00005D8AB9 /* LandscapeItemElement.swift in Sources */,
|
||||||
|
62E632E1267D30CA0063E547 /* LibraryViewModel.swift in Sources */,
|
||||||
535870A82669D8AE00D05A09 /* StringExtensions.swift in Sources */,
|
535870A82669D8AE00D05A09 /* StringExtensions.swift in Sources */,
|
||||||
53ABFDE6267974EF00886593 /* SettingsViewModel.swift in Sources */,
|
53ABFDE6267974EF00886593 /* SettingsViewModel.swift in Sources */,
|
||||||
6267B3D826710B9800A7371D /* CollectionExtensions.swift in Sources */,
|
6267B3D826710B9800A7371D /* CollectionExtensions.swift in Sources */,
|
||||||
535870A52669D8AE00D05A09 /* ParallaxHeader.swift in Sources */,
|
535870A52669D8AE00D05A09 /* ParallaxHeader.swift in Sources */,
|
||||||
531690F0267ABF72005D8AB9 /* NextUpView.swift in Sources */,
|
531690F0267ABF72005D8AB9 /* NextUpView.swift in Sources */,
|
||||||
535870A72669D8AE00D05A09 /* MultiSelectorView.swift in Sources */,
|
535870A72669D8AE00D05A09 /* MultiSelectorView.swift in Sources */,
|
||||||
62E632DD267D2E130063E547 /* LibrarySearchviewModel.swift in Sources */,
|
62E632DD267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */,
|
||||||
536D3D81267BDFC60004248C /* PortraitItemElement.swift in Sources */,
|
536D3D81267BDFC60004248C /* PortraitItemElement.swift in Sources */,
|
||||||
531690E5267ABD5C005D8AB9 /* MainTabView.swift in Sources */,
|
531690E5267ABD5C005D8AB9 /* MainTabView.swift in Sources */,
|
||||||
53ABFDE7267974EF00886593 /* ConnectToServerViewModel.swift in Sources */,
|
53ABFDE7267974EF00886593 /* ConnectToServerViewModel.swift in Sources */,
|
||||||
|
@ -741,7 +746,7 @@
|
||||||
621338932660107500A81A2A /* StringExtensions.swift in Sources */,
|
621338932660107500A81A2A /* StringExtensions.swift in Sources */,
|
||||||
53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */,
|
53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */,
|
||||||
625CB5732678C32A00530A6E /* HomeViewModel.swift in Sources */,
|
625CB5732678C32A00530A6E /* HomeViewModel.swift in Sources */,
|
||||||
62E632DC267D2E130063E547 /* LibrarySearchviewModel.swift in Sources */,
|
62E632DC267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */,
|
||||||
5377CBFE263B596B003A4E83 /* PersistenceController.swift in Sources */,
|
5377CBFE263B596B003A4E83 /* PersistenceController.swift in Sources */,
|
||||||
5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */,
|
5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */,
|
||||||
53AD124E26702B8A0094A276 /* SeasonItemView.swift in Sources */,
|
53AD124E26702B8A0094A276 /* SeasonItemView.swift in Sources */,
|
||||||
|
@ -769,6 +774,7 @@
|
||||||
53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */,
|
53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */,
|
||||||
621338B32660A07800A81A2A /* LazyView.swift in Sources */,
|
621338B32660A07800A81A2A /* LazyView.swift in Sources */,
|
||||||
531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */,
|
531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */,
|
||||||
|
62E632E0267D30CA0063E547 /* LibraryViewModel.swift in Sources */,
|
||||||
62EC352F267666A5000E9F2D /* SessionManager.swift in Sources */,
|
62EC352F267666A5000E9F2D /* SessionManager.swift in Sources */,
|
||||||
535870AD2669D8DD00D05A09 /* Typings.swift in Sources */,
|
535870AD2669D8DD00D05A09 /* Typings.swift in Sources */,
|
||||||
62EC352C26766675000E9F2D /* ServerEnvironment.swift in Sources */,
|
62EC352C26766675000E9F2D /* ServerEnvironment.swift in Sources */,
|
||||||
|
|
|
@ -174,7 +174,7 @@ struct EpisodeItemView: View {
|
||||||
Text("Genres:").font(.callout).fontWeight(.semibold)
|
Text("Genres:").font(.callout).fontWeight(.semibold)
|
||||||
ForEach(item.genreItems!, id: \.id) { genre in
|
ForEach(item.genreItems!, id: \.id) { genre in
|
||||||
NavigationLink(destination: LazyView {
|
NavigationLink(destination: LazyView {
|
||||||
LibraryView(withGenre: genre)
|
LibraryView(viewModel: .init(genre: genre), title: genre.name ?? "")
|
||||||
}) {
|
}) {
|
||||||
Text(genre.name ?? "").font(.footnote)
|
Text(genre.name ?? "").font(.footnote)
|
||||||
}
|
}
|
||||||
|
@ -191,7 +191,7 @@ struct EpisodeItemView: View {
|
||||||
ForEach(item.people!, id: \.self) { person in
|
ForEach(item.people!, id: \.self) { person in
|
||||||
if person.type! == "Actor" {
|
if person.type! == "Actor" {
|
||||||
NavigationLink(destination: LazyView {
|
NavigationLink(destination: LazyView {
|
||||||
LibraryView(withPerson: person)
|
LibraryView(viewModel: .init(person: person), title: person.name ?? "")
|
||||||
}) {
|
}) {
|
||||||
VStack {
|
VStack {
|
||||||
ImageView(src: person.getImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 100), bh: person.getBlurHash())
|
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)
|
Text("Studios:").font(.callout).fontWeight(.semibold)
|
||||||
ForEach(item.studios!, id: \.id) { studio in
|
ForEach(item.studios!, id: \.id) { studio in
|
||||||
NavigationLink(destination: LazyView {
|
NavigationLink(destination: LazyView {
|
||||||
LibraryView(withStudio: studio)
|
LibraryView(viewModel: .init(studio: studio), title: studio.name ?? "")
|
||||||
}) {
|
}) {
|
||||||
Text(studio.name ?? "").font(.footnote)
|
Text(studio.name ?? "").font(.footnote)
|
||||||
}
|
}
|
||||||
|
@ -343,7 +343,7 @@ struct EpisodeItemView: View {
|
||||||
Text("Genres:").font(.callout).fontWeight(.semibold)
|
Text("Genres:").font(.callout).fontWeight(.semibold)
|
||||||
ForEach(item.genreItems!, id: \.id) { genre in
|
ForEach(item.genreItems!, id: \.id) { genre in
|
||||||
NavigationLink(destination: LazyView {
|
NavigationLink(destination: LazyView {
|
||||||
LibraryView(withGenre: genre)
|
LibraryView(viewModel: .init(genre: genre), title: genre.name ?? "")
|
||||||
}) {
|
}) {
|
||||||
Text(genre.name ?? "").font(.footnote)
|
Text(genre.name ?? "").font(.footnote)
|
||||||
}
|
}
|
||||||
|
@ -362,7 +362,7 @@ struct EpisodeItemView: View {
|
||||||
ForEach(item.people!, id: \.self) { person in
|
ForEach(item.people!, id: \.self) { person in
|
||||||
if person.type! == "Actor" {
|
if person.type! == "Actor" {
|
||||||
NavigationLink(destination: LazyView {
|
NavigationLink(destination: LazyView {
|
||||||
LibraryView(withPerson: person)
|
LibraryView(viewModel: .init(person: person), title: person.name ?? "")
|
||||||
}) {
|
}) {
|
||||||
VStack {
|
VStack {
|
||||||
ImageView(src: person.getImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: 100), bh: person.getBlurHash())
|
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)
|
Text("Studios:").font(.callout).fontWeight(.semibold)
|
||||||
ForEach(item.studios!, id: \.id) { studio in
|
ForEach(item.studios!, id: \.id) { studio in
|
||||||
NavigationLink(destination: LazyView {
|
NavigationLink(destination: LazyView {
|
||||||
LibraryView(withStudio: studio)
|
LibraryView(viewModel: .init(studio: studio), title: studio.name ?? "")
|
||||||
}) {
|
}) {
|
||||||
Text(studio.name ?? "").font(.footnote)
|
Text(studio.name ?? "").font(.footnote)
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,8 +44,7 @@ struct HomeView: View {
|
||||||
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16))
|
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16))
|
||||||
Spacer()
|
Spacer()
|
||||||
NavigationLink(destination: LazyView {
|
NavigationLink(destination: LazyView {
|
||||||
LibraryView(usingParentID: libraryID,
|
LibraryView(viewModel: .init(parentID: libraryID, filters: viewModel.recentFilterSet), title: library?.name ?? "")
|
||||||
title: library?.name ?? "", usingFilters: viewModel.recentFilterSet)
|
|
||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("See All").font(.subheadline).fontWeight(.bold)
|
Text("See All").font(.subheadline).fontWeight(.bold)
|
||||||
|
|
|
@ -17,7 +17,7 @@ struct LibraryListView: View {
|
||||||
switch library.id {
|
switch library.id {
|
||||||
case "favorites":
|
case "favorites":
|
||||||
NavigationLink(destination: LazyView {
|
NavigationLink(destination: LazyView {
|
||||||
LibraryView(usingParentID: "", title: library.name ?? "", usingFilters: viewModel.withFavorites)
|
LibraryView(viewModel: .init(filters: viewModel.withFavorites), title: library.name ?? "")
|
||||||
}) {
|
}) {
|
||||||
Text(library.name ?? "")
|
Text(library.name ?? "")
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ struct LibraryListView: View {
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
NavigationLink(destination: LazyView {
|
NavigationLink(destination: LazyView {
|
||||||
LibraryView(usingParentID: library.id ?? "", title: library.name ?? "")
|
LibraryView(viewModel: .init(parentID: library.id), title: library.name ?? "")
|
||||||
}) {
|
}) {
|
||||||
Text(library.name ?? "")
|
Text(library.name ?? "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,9 +26,10 @@ struct LibrarySearchView: View {
|
||||||
VStack {
|
VStack {
|
||||||
Spacer().frame(height: 6)
|
Spacer().frame(height: 6)
|
||||||
SearchBar(text: $viewModel.searchQuery)
|
SearchBar(text: $viewModel.searchQuery)
|
||||||
ZStack {
|
|
||||||
ScrollView(.vertical) {
|
ScrollView(.vertical) {
|
||||||
if !viewModel.items.isEmpty {
|
if viewModel.isLoading {
|
||||||
|
ProgressView()
|
||||||
|
} else if !viewModel.items.isEmpty {
|
||||||
Spacer().frame(height: 16)
|
Spacer().frame(height: 16)
|
||||||
LazyVGrid(columns: tracks) {
|
LazyVGrid(columns: tracks) {
|
||||||
ForEach(viewModel.items, id: \.id) { item in
|
ForEach(viewModel.items, id: \.id) { item in
|
||||||
|
@ -55,18 +56,14 @@ struct LibrarySearchView: View {
|
||||||
}
|
}
|
||||||
Spacer().frame(height: 16)
|
Spacer().frame(height: 16)
|
||||||
}
|
}
|
||||||
|
.onRotate { _ in
|
||||||
|
recalcTracks()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Text("No results :(")
|
Text("No results :(")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if viewModel.isLoading {
|
|
||||||
ProgressView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.navigationBarTitle("Search", displayMode: .inline)
|
.navigationBarTitle("Search", displayMode: .inline)
|
||||||
.onRotate { _ in
|
|
||||||
recalcTracks()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,106 +6,38 @@
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import NukeUI
|
|
||||||
import JellyfinAPI
|
|
||||||
import Combine
|
import Combine
|
||||||
|
import JellyfinAPI
|
||||||
|
import NukeUI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct LibraryView: View {
|
struct LibraryView: View {
|
||||||
|
|
||||||
@StateObject
|
@StateObject
|
||||||
var tempViewModel = ViewModel()
|
var viewModel: LibraryViewModel
|
||||||
@State private var items: [BaseItemDto] = []
|
var title: String
|
||||||
@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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: tracks for grid
|
// 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() {
|
func recalcTracks() {
|
||||||
let trkCnt = Int(floor(UIScreen.main.bounds.size.width / 125))
|
tracks = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 125)
|
||||||
_tracks.wrappedValue = []
|
|
||||||
for _ in 0 ..< trkCnt {
|
|
||||||
_tracks.wrappedValue.append(GridItem(.flexible()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
Group {
|
||||||
if isLoading == true {
|
if viewModel.isLoading == true {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
} else {
|
} else if !viewModel.items.isEmpty {
|
||||||
if !items.isEmpty {
|
|
||||||
VStack {
|
VStack {
|
||||||
ScrollView(.vertical) {
|
ScrollView(.vertical) {
|
||||||
Spacer().frame(height: 16)
|
Spacer().frame(height: 16)
|
||||||
LazyVGrid(columns: tracks) {
|
LazyVGrid(columns: tracks) {
|
||||||
ForEach(items, id: \.id) { item in
|
ForEach(viewModel.items, id: \.id) { item in
|
||||||
NavigationLink(destination: ItemView(item: item)) {
|
NavigationLink(destination: ItemView(item: item)) {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
ImageView(src: item.getPrimaryImage(maxWidth: 100), bh: item.getPrimaryImageBlurHash())
|
ImageView(src: item.getPrimaryImage(maxWidth: 100), bh: item.getPrimaryImageBlurHash())
|
||||||
|
@ -130,27 +62,25 @@ struct LibraryView: View {
|
||||||
}.onRotate { _ in
|
}.onRotate { _ in
|
||||||
recalcTracks()
|
recalcTracks()
|
||||||
}
|
}
|
||||||
if totalPages > 1 {
|
if viewModel.isCanNextPaging || viewModel.isCanPreviousPaging {
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
HStack {
|
HStack {
|
||||||
Button {
|
Button {
|
||||||
currentPage = currentPage - 1
|
viewModel.requestPreviousPage()
|
||||||
onAppear()
|
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "chevron.left")
|
Image(systemName: "chevron.left")
|
||||||
.font(.system(size: 25))
|
.font(.system(size: 25))
|
||||||
}.disabled(currentPage == 0)
|
}.disabled(viewModel.isCanPreviousPaging)
|
||||||
Text("Page \(String(currentPage+1)) of \(String(totalPages))")
|
Text("Page \(String(viewModel.currentPage + 1)) of \(String(viewModel.totalPages))")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
Button {
|
Button {
|
||||||
currentPage = currentPage + 1
|
viewModel.requestNextPage()
|
||||||
onAppear()
|
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "chevron.right")
|
Image(systemName: "chevron.right")
|
||||||
.font(.system(size: 25))
|
.font(.system(size: 25))
|
||||||
}.disabled(currentPage > totalPages - 1)
|
}.disabled(viewModel.isCanNextPaging)
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
@ -162,34 +92,36 @@ struct LibraryView: View {
|
||||||
Text("No results.")
|
Text("No results.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.onAppear(perform: onAppear)
|
|
||||||
.navigationBarTitle(title, displayMode: .inline)
|
.navigationBarTitle(title, displayMode: .inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||||
if currentPage > 0 {
|
if viewModel.isCanPreviousPaging {
|
||||||
Button {
|
Button {
|
||||||
currentPage = currentPage - 1
|
viewModel.requestPreviousPage()
|
||||||
onAppear()
|
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "chevron.left")
|
Image(systemName: "chevron.left")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if currentPage < totalPages - 1 {
|
if viewModel.isCanNextPaging {
|
||||||
Button {
|
Button {
|
||||||
currentPage = currentPage + 1
|
viewModel.requestNextPage()
|
||||||
onAppear()
|
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "chevron.right")
|
Image(systemName: "chevron.right")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if usingParentID != "" {
|
Button(action: {
|
||||||
NavigationLink(destination: LibrarySearchView(viewModel: .init(parentID: usingParentID))) {
|
isShowingSearchView = true
|
||||||
|
}) {
|
||||||
Image(systemName: "magnifyingglass")
|
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)
|
Text("Genres:").font(.callout).fontWeight(.semibold)
|
||||||
ForEach(item.genreItems!, id: \.id) { genre in
|
ForEach(item.genreItems!, id: \.id) { genre in
|
||||||
NavigationLink(destination: LazyView {
|
NavigationLink(destination: LazyView {
|
||||||
LibraryView(withGenre: genre)
|
LibraryView(viewModel: .init(genre: genre), title: genre.name ?? "")
|
||||||
}) {
|
}) {
|
||||||
Text(genre.name ?? "").font(.footnote)
|
Text(genre.name ?? "").font(.footnote)
|
||||||
}
|
}
|
||||||
|
@ -204,7 +204,7 @@ struct MovieItemView: View {
|
||||||
ForEach(item.people!, id: \.self) { person in
|
ForEach(item.people!, id: \.self) { person in
|
||||||
if person.type! == "Actor" {
|
if person.type! == "Actor" {
|
||||||
NavigationLink(destination: LazyView {
|
NavigationLink(destination: LazyView {
|
||||||
LibraryView(withPerson: person)
|
LibraryView(viewModel: .init(person: person), title: person.name ?? "")
|
||||||
}) {
|
}) {
|
||||||
VStack {
|
VStack {
|
||||||
ImageView(src: person
|
ImageView(src: person
|
||||||
|
@ -234,7 +234,8 @@ struct MovieItemView: View {
|
||||||
Text("Studios:").font(.callout).fontWeight(.semibold)
|
Text("Studios:").font(.callout).fontWeight(.semibold)
|
||||||
ForEach(item.studios!, id: \.id) { studio in
|
ForEach(item.studios!, id: \.id) { studio in
|
||||||
NavigationLink(destination: LazyView {
|
NavigationLink(destination: LazyView {
|
||||||
LibraryView(withStudio: studio)
|
|
||||||
|
LibraryView(viewModel: .init(studio: studio), title: studio.name ?? "")
|
||||||
}) {
|
}) {
|
||||||
Text(studio.name ?? "").font(.footnote)
|
Text(studio.name ?? "").font(.footnote)
|
||||||
}
|
}
|
||||||
|
@ -362,7 +363,7 @@ struct MovieItemView: View {
|
||||||
Text("Genres:").font(.callout).fontWeight(.semibold)
|
Text("Genres:").font(.callout).fontWeight(.semibold)
|
||||||
ForEach(item.genreItems!, id: \.id) { genre in
|
ForEach(item.genreItems!, id: \.id) { genre in
|
||||||
NavigationLink(destination: LazyView {
|
NavigationLink(destination: LazyView {
|
||||||
LibraryView(withGenre: genre)
|
LibraryView(viewModel: .init(genre: genre), title: genre.name ?? "")
|
||||||
}) {
|
}) {
|
||||||
Text(genre.name ?? "").font(.footnote)
|
Text(genre.name ?? "").font(.footnote)
|
||||||
}
|
}
|
||||||
|
@ -381,7 +382,7 @@ struct MovieItemView: View {
|
||||||
ForEach(item.people!, id: \.self) { person in
|
ForEach(item.people!, id: \.self) { person in
|
||||||
if person.type! == "Actor" {
|
if person.type! == "Actor" {
|
||||||
NavigationLink(destination: LazyView {
|
NavigationLink(destination: LazyView {
|
||||||
LibraryView(withPerson: person)
|
LibraryView(viewModel: .init(person: person), title: person.name ?? "")
|
||||||
}) {
|
}) {
|
||||||
VStack {
|
VStack {
|
||||||
ImageView(src: person
|
ImageView(src: person
|
||||||
|
@ -413,7 +414,7 @@ struct MovieItemView: View {
|
||||||
Text("Studios:").font(.callout).fontWeight(.semibold)
|
Text("Studios:").font(.callout).fontWeight(.semibold)
|
||||||
ForEach(item.studios!, id: \.id) { studio in
|
ForEach(item.studios!, id: \.id) { studio in
|
||||||
NavigationLink(destination: LazyView {
|
NavigationLink(destination: LazyView {
|
||||||
LibraryView(withStudio: studio)
|
LibraryView(viewModel: .init(studio: studio), title: studio.name ?? "")
|
||||||
}) {
|
}) {
|
||||||
Text(studio.name ?? "").font(.footnote)
|
Text(studio.name ?? "").font(.footnote)
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,7 +139,7 @@ struct SeasonItemView: View {
|
||||||
Text("Studios:").font(.callout).fontWeight(.semibold)
|
Text("Studios:").font(.callout).fontWeight(.semibold)
|
||||||
ForEach(item.studios!, id: \.id) { studio in
|
ForEach(item.studios!, id: \.id) { studio in
|
||||||
NavigationLink(destination: LazyView {
|
NavigationLink(destination: LazyView {
|
||||||
LibraryView(withStudio: studio)
|
LibraryView(viewModel: .init(studio: studio), title: studio.name ?? "")
|
||||||
}) {
|
}) {
|
||||||
Text(studio.name ?? "").font(.footnote)
|
Text(studio.name ?? "").font(.footnote)
|
||||||
}
|
}
|
||||||
|
@ -232,7 +232,7 @@ struct SeasonItemView: View {
|
||||||
Text("Studios:").font(.callout).fontWeight(.semibold)
|
Text("Studios:").font(.callout).fontWeight(.semibold)
|
||||||
ForEach(item.studios!, id: \.id) { studio in
|
ForEach(item.studios!, id: \.id) { studio in
|
||||||
NavigationLink(destination: LazyView {
|
NavigationLink(destination: LazyView {
|
||||||
LibraryView(withStudio: studio)
|
LibraryView(viewModel: .init(studio: studio), title: studio.name ?? "")
|
||||||
}) {
|
}) {
|
||||||
Text(studio.name ?? "").font(.footnote)
|
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