From 4d40b0c083fa78a90337273b83b44f450c3d3d9b Mon Sep 17 00:00:00 2001 From: Shav Kinderlehrer Date: Sun, 7 Jan 2024 16:41:45 -0500 Subject: Use backdrop blurhash + rename LibraryIconView --- Jel.xcodeproj/project.pbxproj | 8 +- Jel/Views/Dashboard/DashboardLibraryView.swift | 4 +- .../Dashboard/DashboardSectionTitleView.swift | 1 - Jel/Views/Dashboard/DashboardView.swift | 10 +- Jel/Views/Library/Item/ItemHeaderView.swift | 6 +- Jel/Views/Library/Item/ItemInfoView.swift | 6 +- Jel/Views/Library/ItemIconView.swift | 106 +++++++++++++++++++++ Jel/Views/Library/LibraryDetailView.swift | 2 +- Jel/Views/Library/LibraryIconView.swift | 94 ------------------ 9 files changed, 121 insertions(+), 116 deletions(-) create mode 100644 Jel/Views/Library/ItemIconView.swift delete mode 100644 Jel/Views/Library/LibraryIconView.swift diff --git a/Jel.xcodeproj/project.pbxproj b/Jel.xcodeproj/project.pbxproj index 54d2e60..f84170a 100644 --- a/Jel.xcodeproj/project.pbxproj +++ b/Jel.xcodeproj/project.pbxproj @@ -18,7 +18,7 @@ 3D13F96F2B38A32500E91913 /* StickyHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D13F96E2B38A32500E91913 /* StickyHeaderView.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 */; }; + 3D41D1FA2B2CAE0000E58234 /* ItemIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D41D1F92B2CAE0000E58234 /* ItemIconView.swift */; }; 3D4C15722B3CAA670035373E /* DashboardSectionTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D4C15712B3CAA670035373E /* DashboardSectionTitleView.swift */; }; 3D7709392B29139700199889 /* Pulse in Frameworks */ = {isa = PBXBuildFile; productRef = 3D7709382B29139700199889 /* Pulse */; }; 3D77093B2B29139700199889 /* PulseUI in Frameworks */ = {isa = PBXBuildFile; productRef = 3D77093A2B29139700199889 /* PulseUI */; }; @@ -86,7 +86,7 @@ 3D13F96E2B38A32500E91913 /* StickyHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyHeaderView.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 = ""; }; + 3D41D1F92B2CAE0000E58234 /* ItemIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemIconView.swift; sourceTree = ""; }; 3D4C15712B3CAA670035373E /* DashboardSectionTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardSectionTitleView.swift; sourceTree = ""; }; 3D8AB2A42B36440D005BD7D0 /* BlurHashDecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = ""; }; 3D8AB2A72B366353005BD7D0 /* LibraryDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryDetailView.swift; sourceTree = ""; }; @@ -204,7 +204,7 @@ isa = PBXGroup; children = ( 3D13F95D2B375DAC00E91913 /* Item */, - 3D41D1F92B2CAE0000E58234 /* LibraryIconView.swift */, + 3D41D1F92B2CAE0000E58234 /* ItemIconView.swift */, 3D8AB2A72B366353005BD7D0 /* LibraryDetailView.swift */, ); path = Library; @@ -465,7 +465,7 @@ 3D8AB2A82B366353005BD7D0 /* LibraryDetailView.swift in Sources */, 3DDD67932B293BC40026781E /* DashboardView.swift in Sources */, 3D13F9612B37637500E91913 /* ItemMediaView.swift in Sources */, - 3D41D1FA2B2CAE0000E58234 /* LibraryIconView.swift in Sources */, + 3D41D1FA2B2CAE0000E58234 /* ItemIconView.swift in Sources */, 3D8AB2A52B36440D005BD7D0 /* BlurHashDecode.swift in Sources */, 3DC6BA2D2B2A422300416B9F /* SettingsController.swift in Sources */, 3DAFA8E82B38AFED00D71AD1 /* ItemInfoView.swift in Sources */, diff --git a/Jel/Views/Dashboard/DashboardLibraryView.swift b/Jel/Views/Dashboard/DashboardLibraryView.swift index 7526dce..2a8dee1 100644 --- a/Jel/Views/Dashboard/DashboardLibraryView.swift +++ b/Jel/Views/Dashboard/DashboardLibraryView.swift @@ -28,12 +28,12 @@ struct DashboardLibraryView: View { NavigationLink { LibraryDetailView(library: library) } label: { - LibraryIconView(library: library, height: 150) + ItemIconView(item: library, height: 150) .setAspectRatio(library.primaryImageAspectRatio) } .buttonStyle(PlainButtonStyle()) } - } + }.padding(.horizontal) } } .onAppear { diff --git a/Jel/Views/Dashboard/DashboardSectionTitleView.swift b/Jel/Views/Dashboard/DashboardSectionTitleView.swift index 2456dc2..608458a 100644 --- a/Jel/Views/Dashboard/DashboardSectionTitleView.swift +++ b/Jel/Views/Dashboard/DashboardSectionTitleView.swift @@ -21,7 +21,6 @@ struct DashboardSectionTitleView: View { .bold() Spacer() } - .padding(.top) } } diff --git a/Jel/Views/Dashboard/DashboardView.swift b/Jel/Views/Dashboard/DashboardView.swift index 279f056..5ad25f8 100644 --- a/Jel/Views/Dashboard/DashboardView.swift +++ b/Jel/Views/Dashboard/DashboardView.swift @@ -20,17 +20,9 @@ struct DashboardView: View { } } header: { DashboardSectionTitleView("Libraries") - } - - Section { - NavigationStack { - DashboardLibraryView() - } - } header: { - DashboardSectionTitleView("Next up") + .padding([.top, .leading]) } } - .padding() } .navigationTitle("Home") .toolbar { diff --git a/Jel/Views/Library/Item/ItemHeaderView.swift b/Jel/Views/Library/Item/ItemHeaderView.swift index 3930beb..b8a708a 100644 --- a/Jel/Views/Library/Item/ItemHeaderView.swift +++ b/Jel/Views/Library/Item/ItemHeaderView.swift @@ -21,18 +21,18 @@ struct ItemHeaderView: View { var body: some View { ZStack(alignment: .bottom) { StickyHeaderView(minHeight: 300) { - LibraryIconView(library: item, imageType: "Backdrop", contentMode: .fill) + ItemIconView(item: item, imageType: "Backdrop", contentMode: .fill) .setCornerRadius(0) .mask(overlayGradient) .background { - LibraryIconView(library: item, imageType: "Backdrop", contentMode: .fill) + ItemIconView(item: item, imageType: "Backdrop", contentMode: .fill) .setCornerRadius(0) .blur(radius: 50) } } HStack { - LibraryIconView(library: item, imageType: "Logo", width: 200, height: 100, placeHolder: AnyView(Text(item.name ?? "Unknown").font(.title).bold().truncationMode(.middle))) + ItemIconView(item: item, imageType: "Logo", width: 200, height: 100, placeHolder: AnyView(Text(item.name ?? "Unknown").font(.title).bold().truncationMode(.middle))) .setCornerRadius(0) .shadow(radius: 10) .frame(alignment: .leading) diff --git a/Jel/Views/Library/Item/ItemInfoView.swift b/Jel/Views/Library/Item/ItemInfoView.swift index f682898..022fa71 100644 --- a/Jel/Views/Library/Item/ItemInfoView.swift +++ b/Jel/Views/Library/Item/ItemInfoView.swift @@ -21,8 +21,10 @@ struct ItemInfoView: View { Text(item.genres?.first ?? "---") .shadow(color: .black, radius: 1) } - Text(item.getRuntime() ?? "-:--") - .shadow(color: .black, radius: 1) + if item.type == .movie { + Text(item.getRuntime() ?? "-:--") + .shadow(color: .black, radius: 1) + } } .font(.caption) } diff --git a/Jel/Views/Library/ItemIconView.swift b/Jel/Views/Library/ItemIconView.swift new file mode 100644 index 0000000..ec3952c --- /dev/null +++ b/Jel/Views/Library/ItemIconView.swift @@ -0,0 +1,106 @@ +// +// ItemIconView.swift +// Jel +// +// Created by zerocool on 12/15/23. +// + +import SwiftUI +import JellyfinKit +import NukeUI + +struct ItemIconView: View { + @EnvironmentObject var jellyfinClient: JellyfinClientController + + var item: BaseItemDto + + var imageType: String = "Primary" + var width: CGFloat? + var height: CGFloat? + + @State var blurHashImage: UIImage = UIImage() + @State var imageUrl: URL? + @State var contentMode: ContentMode = .fit + + @State var placeHolder: AnyView? + + var shouldShowCaption: Bool = false + var imageCornerRadius: CGFloat = 5 + var body: some View { + VStack(alignment: .leading) { + LazyImage(url: imageUrl) {state in + if let image = state.image { + image + .resizable() + .aspectRatio(contentMode: contentMode) + } else { + if let content = placeHolder { + content + } else { + Image(uiImage: blurHashImage) + .resizable() + .aspectRatio(contentMode: .fill) + } + } + } + .frame(width: width, height: height) + .clipShape(RoundedRectangle(cornerRadius: imageCornerRadius)) + .onAppear { + let blurhash = getBlurHash(imageType: imageType) + blurHashImage = UIImage(blurHash: blurhash, size: CGSize(width: 32, height: 32)) ?? UIImage() + + let imageId = item.id ?? "" + let request = Paths.getItemImage(itemID: imageId, imageType: imageType) + imageUrl = jellyfinClient.getUrl()?.appending(path: request.url?.absoluteString ?? "") + } + + if shouldShowCaption { + Text(item.name ?? "Unknown") + .font(.subheadline) + } + } + } + + private func getBlurHash(imageType: String) -> String { + switch imageType { + case "Primary": + return item.imageBlurHashes?.primary?[item.imageTags?[imageType] ?? ""] ?? "" + case "Backdrop": + return item.imageBlurHashes?.backdrop?[item.backdropImageTags?[0] ?? ""] ?? "" + default: + return "" + } + } + + func showCaption(_ showCaption: Bool = true) -> Self { + var copy = self + copy.shouldShowCaption = showCaption + return copy + } + + func setCornerRadius(_ cornerRadius: CGFloat = 5) -> Self { + var copy = self + copy.imageCornerRadius = cornerRadius + return copy + } + + func setAspectRatio(_ aspectRatio: Double?) -> Self { + var copy = self + if aspectRatio == nil { + return copy + } + + if let newWidth = copy.width { + copy.height = newWidth / aspectRatio! + } + if let newHeight = copy.height { + copy.width = newHeight * aspectRatio! + } + + return copy + } +} + +//#Preview { +// LibraryIconView(library: BaseItemDto()) +//} diff --git a/Jel/Views/Library/LibraryDetailView.swift b/Jel/Views/Library/LibraryDetailView.swift index 1dc0c9c..ece4e74 100644 --- a/Jel/Views/Library/LibraryDetailView.swift +++ b/Jel/Views/Library/LibraryDetailView.swift @@ -39,7 +39,7 @@ struct LibraryDetailView: View { NavigationLink { ItemView(item: item) } label: { - LibraryIconView(library: item, imageType: "Primary", width: 150) + ItemIconView(item: item, imageType: "Primary", width: 150) .showCaption() .setAspectRatio(item.primaryImageAspectRatio ?? 0.6) .padding() diff --git a/Jel/Views/Library/LibraryIconView.swift b/Jel/Views/Library/LibraryIconView.swift deleted file mode 100644 index 800d488..0000000 --- a/Jel/Views/Library/LibraryIconView.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// LibraryIconView.swift -// Jel -// -// Created by zerocool on 12/15/23. -// - -import SwiftUI -import JellyfinKit -import NukeUI - -struct LibraryIconView: View { - @EnvironmentObject var jellyfinClient: JellyfinClientController - - var library: BaseItemDto - - var imageType: String = "Primary" - var width: CGFloat? - var height: CGFloat? - - @State var blurHashImage: UIImage = UIImage() - @State var imageUrl: URL? - @State var contentMode: ContentMode = .fit - - @State var placeHolder: AnyView? - - var shouldShowCaption: Bool = false - var imageCornerRadius: CGFloat = 5 - var body: some View { - VStack(alignment: .leading) { - LazyImage(url: imageUrl) {state in - if let image = state.image { - image - .resizable() - .aspectRatio(contentMode: contentMode) - } else { - if let content = placeHolder { - content - } else { - Image(uiImage: blurHashImage) - .resizable() - .aspectRatio(contentMode: .fill) - } - } - } - .frame(width: width, height: height) - .clipShape(RoundedRectangle(cornerRadius: imageCornerRadius)) - .onAppear { - let blurhash = library.imageBlurHashes?.primary?[library.imageTags?[imageType] ?? ""] ?? "" - blurHashImage = UIImage(blurHash: blurhash, size: CGSize(width: 32, height: 32)) ?? UIImage() - - let imageId = library.id ?? "" - let request = Paths.getItemImage(itemID: imageId, imageType: imageType) - imageUrl = jellyfinClient.getUrl()?.appending(path: request.url?.absoluteString ?? "") - } - - if shouldShowCaption { - Text(library.name ?? "Unknown") - .font(.subheadline) - } - } - } - func showCaption(_ showCaption: Bool = true) -> Self { - var copy = self - copy.shouldShowCaption = showCaption - return copy - } - - func setCornerRadius(_ cornerRadius: CGFloat = 5) -> Self { - var copy = self - copy.imageCornerRadius = cornerRadius - return copy - } - - func setAspectRatio(_ aspectRatio: Double?) -> Self { - var copy = self - if aspectRatio == nil { - return copy - } - - if let newWidth = copy.width { - copy.height = newWidth / aspectRatio! - } - if let newHeight = copy.height { - copy.width = newHeight * aspectRatio! - } - - return copy - } -} - -//#Preview { -// LibraryIconView(library: BaseItemDto()) -//} -- cgit v1.2.3