From 53bdc33ac7e97e1c51dd27c8cb1943c7608b49d6 Mon Sep 17 00:00:00 2001 From: Shav Kinderlehrer Date: Sat, 23 Dec 2023 14:15:01 -0500 Subject: [PATCH] Use Nuke to load images + start movieView --- Jel.xcodeproj/project.pbxproj | 41 +++++++++++++ .../xcshareddata/swiftpm/Package.resolved | 9 +++ .../Dashboard/DashboardLibraryView.swift | 2 +- Jel/Views/Dashboard/DashboardView.swift | 1 + Jel/Views/Library/Item/ItemMovieView.swift | 44 ++++++++++++++ Jel/Views/Library/Item/ItemView.swift | 25 ++++++++ Jel/Views/Library/LibraryDetailView.swift | 9 ++- Jel/Views/Library/LibraryIconView.swift | 57 +++++++++---------- 8 files changed, 155 insertions(+), 33 deletions(-) create mode 100644 Jel/Views/Library/Item/ItemMovieView.swift create mode 100644 Jel/Views/Library/Item/ItemView.swift diff --git a/Jel.xcodeproj/project.pbxproj b/Jel.xcodeproj/project.pbxproj index 020a200..9a1e3cd 100644 --- a/Jel.xcodeproj/project.pbxproj +++ b/Jel.xcodeproj/project.pbxproj @@ -9,6 +9,10 @@ /* Begin PBXBuildFile section */ 3D1015D92B27F57400F5C29A /* AddServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1015D82B27F57400F5C29A /* AddServerView.swift */; }; 3D1015E42B28000E00F5C29A /* AuthStateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1015E32B28000E00F5C29A /* AuthStateController.swift */; }; + 3D13F95A2B375A9E00E91913 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = 3D13F9592B375A9E00E91913 /* Nuke */; }; + 3D13F95C2B375A9E00E91913 /* NukeUI in Frameworks */ = {isa = PBXBuildFile; productRef = 3D13F95B2B375A9E00E91913 /* NukeUI */; }; + 3D13F95F2B375DB800E91913 /* ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D13F95E2B375DB800E91913 /* ItemView.swift */; }; + 3D13F9612B37637500E91913 /* ItemMovieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D13F9602B37637500E91913 /* ItemMovieView.swift */; }; 3D16FC3C2B2CDFB500E6D8B3 /* DashboardLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D16FC3B2B2CDFB500E6D8B3 /* DashboardLibraryView.swift */; }; 3D41D1F52B2C962500E58234 /* AppearancePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D41D1F42B2C962500E58234 /* AppearancePicker.swift */; }; 3D41D1FA2B2CAE0000E58234 /* LibraryIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D41D1F92B2CAE0000E58234 /* LibraryIconView.swift */; }; @@ -67,6 +71,8 @@ 3D1015D42B27F49000F5C29A /* JellyfinKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = JellyfinKit; path = ../JellyfinKit; sourceTree = ""; }; 3D1015D82B27F57400F5C29A /* AddServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddServerView.swift; sourceTree = ""; }; 3D1015E32B28000E00F5C29A /* AuthStateController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthStateController.swift; sourceTree = ""; }; + 3D13F95E2B375DB800E91913 /* ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemView.swift; sourceTree = ""; }; + 3D13F9602B37637500E91913 /* ItemMovieView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemMovieView.swift; sourceTree = ""; }; 3D16FC3B2B2CDFB500E6D8B3 /* DashboardLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardLibraryView.swift; sourceTree = ""; }; 3D41D1F42B2C962500E58234 /* AppearancePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearancePicker.swift; sourceTree = ""; }; 3D41D1F92B2CAE0000E58234 /* LibraryIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryIconView.swift; sourceTree = ""; }; @@ -98,9 +104,11 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3D13F95C2B375A9E00E91913 /* NukeUI in Frameworks */, 3D77093B2B29139700199889 /* PulseUI in Frameworks */, 3D7709392B29139700199889 /* Pulse in Frameworks */, 3D9064592B27E4C70063DD2A /* JellyfinKit in Frameworks */, + 3D13F95A2B375A9E00E91913 /* Nuke in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -152,9 +160,19 @@ path = Models; sourceTree = ""; }; + 3D13F95D2B375DAC00E91913 /* Item */ = { + isa = PBXGroup; + children = ( + 3D13F95E2B375DB800E91913 /* ItemView.swift */, + 3D13F9602B37637500E91913 /* ItemMovieView.swift */, + ); + path = Item; + sourceTree = ""; + }; 3D8AB2A62B366309005BD7D0 /* Library */ = { isa = PBXGroup; children = ( + 3D13F95D2B375DAC00E91913 /* Item */, 3D41D1F92B2CAE0000E58234 /* LibraryIconView.swift */, 3D8AB2A72B366353005BD7D0 /* LibraryDetailView.swift */, ); @@ -271,6 +289,8 @@ 3D9064582B27E4C70063DD2A /* JellyfinKit */, 3D7709382B29139700199889 /* Pulse */, 3D77093A2B29139700199889 /* PulseUI */, + 3D13F9592B375A9E00E91913 /* Nuke */, + 3D13F95B2B375A9E00E91913 /* NukeUI */, ); productName = Jel; productReference = 3D9063C72B279A310063DD2A /* Jel.app */; @@ -346,6 +366,7 @@ mainGroup = 3D9063BE2B279A310063DD2A; packageReferences = ( 3D7709372B29139700199889 /* XCRemoteSwiftPackageReference "Pulse" */, + 3D13F9582B375A9E00E91913 /* XCRemoteSwiftPackageReference "Nuke" */, ); productRefGroup = 3D9063C82B279A310063DD2A /* Products */; projectDirPath = ""; @@ -397,12 +418,14 @@ 3D91FDC92B28C62800919017 /* SignInView.swift in Sources */, 3D8AB2A82B366353005BD7D0 /* LibraryDetailView.swift in Sources */, 3DDD67932B293BC40026781E /* DashboardView.swift in Sources */, + 3D13F9612B37637500E91913 /* ItemMovieView.swift in Sources */, 3D41D1FA2B2CAE0000E58234 /* LibraryIconView.swift in Sources */, 3D8AB2A52B36440D005BD7D0 /* BlurHashDecode.swift in Sources */, 3DC6BA2D2B2A422300416B9F /* SettingsController.swift in Sources */, 3D91FDCB2B28CA2500919017 /* SignInToServerView.swift in Sources */, 3D16FC3C2B2CDFB500E6D8B3 /* DashboardLibraryView.swift in Sources */, 3D1015E42B28000E00F5C29A /* AuthStateController.swift in Sources */, + 3D13F95F2B375DB800E91913 /* ItemView.swift in Sources */, 3DDD67962B29E28B0026781E /* SettingsView.swift in Sources */, 3D41D1F52B2C962500E58234 /* AppearancePicker.swift in Sources */, ); @@ -769,6 +792,14 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 3D13F9582B375A9E00E91913 /* XCRemoteSwiftPackageReference "Nuke" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/kean/Nuke"; + requirement = { + kind = upToNextMinorVersion; + minimumVersion = 12.2.0; + }; + }; 3D7709372B29139700199889 /* XCRemoteSwiftPackageReference "Pulse" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/kean/Pulse"; @@ -780,6 +811,16 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 3D13F9592B375A9E00E91913 /* Nuke */ = { + isa = XCSwiftPackageProductDependency; + package = 3D13F9582B375A9E00E91913 /* XCRemoteSwiftPackageReference "Nuke" */; + productName = Nuke; + }; + 3D13F95B2B375A9E00E91913 /* NukeUI */ = { + isa = XCSwiftPackageProductDependency; + package = 3D13F9582B375A9E00E91913 /* XCRemoteSwiftPackageReference "Nuke" */; + productName = NukeUI; + }; 3D7709382B29139700199889 /* Pulse */ = { isa = XCSwiftPackageProductDependency; package = 3D7709372B29139700199889 /* XCRemoteSwiftPackageReference "Pulse" */; diff --git a/Jel.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Jel.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e1e75ae..ac5b537 100644 --- a/Jel.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Jel.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -10,6 +10,15 @@ "version": "2.1.6" } }, + { + "package": "Nuke", + "repositoryURL": "https://github.com/kean/Nuke", + "state": { + "branch": null, + "revision": "1694798e876113d44f6ec6ead965d7286695981d", + "version": "12.2.0" + } + }, { "package": "Pulse", "repositoryURL": "https://github.com/kean/Pulse", diff --git a/Jel/Views/Dashboard/DashboardLibraryView.swift b/Jel/Views/Dashboard/DashboardLibraryView.swift index 00f0e68..2e0cd0e 100644 --- a/Jel/Views/Dashboard/DashboardLibraryView.swift +++ b/Jel/Views/Dashboard/DashboardLibraryView.swift @@ -21,7 +21,7 @@ struct DashboardLibraryView: View { .progressViewStyle(.circular) } ScrollView(.horizontal, showsIndicators: false) { - HStack { + LazyHStack { ForEach(libraries) {library in if library.collectionType == "movies" || library.collectionType == "tvshows" { NavigationLink { diff --git a/Jel/Views/Dashboard/DashboardView.swift b/Jel/Views/Dashboard/DashboardView.swift index 50d3538..a29d2f5 100644 --- a/Jel/Views/Dashboard/DashboardView.swift +++ b/Jel/Views/Dashboard/DashboardView.swift @@ -16,6 +16,7 @@ struct DashboardView: View { VStack { DashboardLibraryView() } + .navigationTitle("Home") .toolbar { ToolbarItem(placement: .topBarTrailing) { Button { diff --git a/Jel/Views/Library/Item/ItemMovieView.swift b/Jel/Views/Library/Item/ItemMovieView.swift new file mode 100644 index 0000000..6a29654 --- /dev/null +++ b/Jel/Views/Library/Item/ItemMovieView.swift @@ -0,0 +1,44 @@ +// +// ItemMovieView.swift +// Jel +// +// Created by zerocool on 12/23/23. +// + +import SwiftUI +import JellyfinKit + +struct ItemMovieView: View { + @EnvironmentObject var jellyfinClient: JellyfinClientController + @StateObject var authState: AuthStateController = AuthStateController.shared + + @State var item: BaseItemDto + @State var loading: Bool = true + var body: some View { + VStack { + Text(item.name ?? "Unknown") + .font(.title) + + Text(item.taglines?[0] ?? "Unknown") + .font(.headline) + Text(item.overview ?? "Unknown") + } + .navigationTitle(item.name ?? "Unknown") + .navigationBarTitleDisplayMode(.inline) + .redacted(reason: loading ? .placeholder : []) + .onAppear { + Task { + do { + let request = Paths.getItem(userID: authState.userId ?? "", itemID: item.id ?? "") + item = try await jellyfinClient.send(request).value + loading = false + } catch { + } + } + } + } +} + +#Preview { + ItemMovieView(item: BaseItemDto()) +} diff --git a/Jel/Views/Library/Item/ItemView.swift b/Jel/Views/Library/Item/ItemView.swift new file mode 100644 index 0000000..d3446af --- /dev/null +++ b/Jel/Views/Library/Item/ItemView.swift @@ -0,0 +1,25 @@ +// +// ItemView.swift +// Jel +// +// Created by zerocool on 12/23/23. +// + +import SwiftUI +import JellyfinKit + +struct ItemView: View { + @State var item: BaseItemDto + var body: some View { + switch item.type { + case .movie: + ItemMovieView(item: item) + default: + Text("Unkown media") + } + } +} + +#Preview { + ItemView(item: BaseItemDto()) +} diff --git a/Jel/Views/Library/LibraryDetailView.swift b/Jel/Views/Library/LibraryDetailView.swift index 37ab613..4b140df 100644 --- a/Jel/Views/Library/LibraryDetailView.swift +++ b/Jel/Views/Library/LibraryDetailView.swift @@ -28,8 +28,13 @@ struct LibraryDetailView: View { ScrollView { LazyVGrid(columns: columns) { ForEach(items ?? []) {item in - LibraryIconView(library: item, imageType: "Primary", height: 150) - .padding() + NavigationLink { + ItemView(item: item) + } label: { + LibraryIconView(library: item, imageType: "Primary", width: 170) + .padding() + } + .buttonStyle(PlainButtonStyle()) } } } diff --git a/Jel/Views/Library/LibraryIconView.swift b/Jel/Views/Library/LibraryIconView.swift index 6a7d5ae..71c6e14 100644 --- a/Jel/Views/Library/LibraryIconView.swift +++ b/Jel/Views/Library/LibraryIconView.swift @@ -7,49 +7,46 @@ import SwiftUI import JellyfinKit +import NukeUI struct LibraryIconView: View { @EnvironmentObject var jellyfinClient: JellyfinClientController - @State var library: BaseItemDto + var library: BaseItemDto - @State var loadingImage: Bool = true - @State var imageType: String = "Primary" + var imageType: String = "Primary" var width: CGFloat? var height: CGFloat? @State var blurHashImage: UIImage = UIImage() @State var imageUrl: URL? var body: some View { - AsyncImage(url: imageUrl) {image in - VStack { - image - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: width, height: height) - .clipShape(RoundedRectangle(cornerRadius: 3)) - Text(library.name ?? "Unknown") - .font(.subheadline) + VStack { + LazyImage(url: imageUrl) {state in + if let image = state.image { + image + .resizable() + } else if state.error != nil { + Color.red + } else { + Image(uiImage: blurHashImage) + .resizable() + } } - } placeholder: { - VStack { - Image(uiImage: blurHashImage) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: width, height: height) - .clipShape(RoundedRectangle(cornerRadius: 3)) - Text(library.name ?? "Unknown") - .font(.subheadline) - } - } - - .onAppear { - let blurhash = library.imageBlurHashes?.primary?[library.imageTags?[imageType] ?? ""] ?? "" - blurHashImage = UIImage(blurHash: blurhash, size: CGSize(width: 32, height: 32)) ?? UIImage() + .aspectRatio(contentMode: .fit) + .frame(width: width, height: height) + .clipShape(RoundedRectangle(cornerRadius: 5)) - let imageId = library.id ?? "" - let request = Paths.getItemImage(itemID: imageId, imageType: imageType) - imageUrl = jellyfinClient.getUrl()?.appending(path: request.url?.absoluteString ?? "") + Text(library.name ?? "Unknown") + .font(.subheadline) + .onAppear { + let blurhash = library.imageBlurHashes?.primary?[library.imageTags?[imageType] ?? ""] ?? "" + blurHashImage = UIImage(blurHash: blurhash, size: CGSize(width: 16, height: 16)) ?? UIImage() + + let imageId = library.id ?? "" + let request = Paths.getItemImage(itemID: imageId, imageType: imageType) + imageUrl = jellyfinClient.getUrl()?.appending(path: request.url?.absoluteString ?? "") + } } } }