From 7aa602f19dec3cf526c4550c5e63a8fc6dfac723 Mon Sep 17 00:00:00 2001 From: Shav Kinderlehrer Date: Wed, 27 Dec 2023 08:25:35 -0500 Subject: [PATCH] Implement auto aspect ratio for LibraryIconViews --- Jel.xcodeproj/project.pbxproj | 20 ++++-- .../Dashboard/DashboardLibraryView.swift | 1 + Jel/Views/Library/Item/ItemInfoView.swift | 4 +- ...temMovieView.swift => ItemMediaView.swift} | 4 +- .../Library/Item/Types/ItemMovieView.swift | 22 +++++++ Jel/Views/Library/LibraryDetailView.swift | 65 +++++++++---------- Jel/Views/Library/LibraryIconView.swift | 21 +++++- 7 files changed, 92 insertions(+), 45 deletions(-) rename Jel/Views/Library/Item/{ItemMovieView.swift => ItemMediaView.swift} (97%) create mode 100644 Jel/Views/Library/Item/Types/ItemMovieView.swift diff --git a/Jel.xcodeproj/project.pbxproj b/Jel.xcodeproj/project.pbxproj index c01594c..eba3696 100644 --- a/Jel.xcodeproj/project.pbxproj +++ b/Jel.xcodeproj/project.pbxproj @@ -12,7 +12,7 @@ 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 */; }; + 3D13F9612B37637500E91913 /* ItemMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D13F9602B37637500E91913 /* ItemMediaView.swift */; }; 3D13F9652B37EC7A00E91913 /* ItemHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D13F9642B37EC7A00E91913 /* ItemHeaderView.swift */; }; 3D13F9692B389FA300E91913 /* ViewOffsetKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D13F9682B389FA300E91913 /* ViewOffsetKey.swift */; }; 3D13F96F2B38A32500E91913 /* StickyHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D13F96E2B38A32500E91913 /* StickyHeaderView.swift */; }; @@ -37,6 +37,7 @@ 3DAFA8E82B38AFED00D71AD1 /* ItemInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAFA8E72B38AFED00D71AD1 /* ItemInfoView.swift */; }; 3DAFA8EA2B39039900D71AD1 /* JellyfinKitExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAFA8E92B39039900D71AD1 /* JellyfinKitExtensions.swift */; }; 3DAFA8EC2B394F9F00D71AD1 /* ViewConditionalMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAFA8EB2B394F9F00D71AD1 /* ViewConditionalMethod.swift */; }; + 3DAFA8EF2B3B707B00D71AD1 /* ItemMovieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAFA8EE2B3B707B00D71AD1 /* ItemMovieView.swift */; }; 3DC6BA2D2B2A422300416B9F /* SettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DC6BA2C2B2A422300416B9F /* SettingsController.swift */; }; 3DDD67932B293BC40026781E /* DashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DDD67922B293BC40026781E /* DashboardView.swift */; }; 3DDD67962B29E28B0026781E /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DDD67952B29E28B0026781E /* SettingsView.swift */; }; @@ -78,7 +79,7 @@ 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 = ""; }; + 3D13F9602B37637500E91913 /* ItemMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemMediaView.swift; sourceTree = ""; }; 3D13F9642B37EC7A00E91913 /* ItemHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemHeaderView.swift; sourceTree = ""; }; 3D13F9682B389FA300E91913 /* ViewOffsetKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewOffsetKey.swift; sourceTree = ""; }; 3D13F96E2B38A32500E91913 /* StickyHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyHeaderView.swift; sourceTree = ""; }; @@ -104,6 +105,7 @@ 3DAFA8E72B38AFED00D71AD1 /* ItemInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemInfoView.swift; sourceTree = ""; }; 3DAFA8E92B39039900D71AD1 /* JellyfinKitExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinKitExtensions.swift; sourceTree = ""; }; 3DAFA8EB2B394F9F00D71AD1 /* ViewConditionalMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewConditionalMethod.swift; sourceTree = ""; }; + 3DAFA8EE2B3B707B00D71AD1 /* ItemMovieView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemMovieView.swift; sourceTree = ""; }; 3DC0E5802B2832B9001CCE96 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3DC6BA2C2B2A422300416B9F /* SettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsController.swift; sourceTree = ""; }; 3DDD67922B293BC40026781E /* DashboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardView.swift; sourceTree = ""; }; @@ -179,8 +181,9 @@ 3D13F95D2B375DAC00E91913 /* Item */ = { isa = PBXGroup; children = ( + 3DAFA8ED2B3B707100D71AD1 /* Types */, 3D13F95E2B375DB800E91913 /* ItemView.swift */, - 3D13F9602B37637500E91913 /* ItemMovieView.swift */, + 3D13F9602B37637500E91913 /* ItemMediaView.swift */, 3DAFA8E72B38AFED00D71AD1 /* ItemInfoView.swift */, 3D13F9642B37EC7A00E91913 /* ItemHeaderView.swift */, ); @@ -276,6 +279,14 @@ path = SignIn; sourceTree = ""; }; + 3DAFA8ED2B3B707100D71AD1 /* Types */ = { + isa = PBXGroup; + children = ( + 3DAFA8EE2B3B707B00D71AD1 /* ItemMovieView.swift */, + ); + path = Types; + sourceTree = ""; + }; 3DDD67902B293B780026781E /* Dashboard */ = { isa = PBXGroup; children = ( @@ -447,9 +458,10 @@ 3D13F9692B389FA300E91913 /* ViewOffsetKey.swift in Sources */, 3D91FDCD2B2907E800919017 /* JellyfinDateFormatter.swift in Sources */, 3D91FDC92B28C62800919017 /* SignInView.swift in Sources */, + 3DAFA8EF2B3B707B00D71AD1 /* ItemMovieView.swift in Sources */, 3D8AB2A82B366353005BD7D0 /* LibraryDetailView.swift in Sources */, 3DDD67932B293BC40026781E /* DashboardView.swift in Sources */, - 3D13F9612B37637500E91913 /* ItemMovieView.swift in Sources */, + 3D13F9612B37637500E91913 /* ItemMediaView.swift in Sources */, 3D41D1FA2B2CAE0000E58234 /* LibraryIconView.swift in Sources */, 3D8AB2A52B36440D005BD7D0 /* BlurHashDecode.swift in Sources */, 3DC6BA2D2B2A422300416B9F /* SettingsController.swift in Sources */, diff --git a/Jel/Views/Dashboard/DashboardLibraryView.swift b/Jel/Views/Dashboard/DashboardLibraryView.swift index 57ffa50..4a68013 100644 --- a/Jel/Views/Dashboard/DashboardLibraryView.swift +++ b/Jel/Views/Dashboard/DashboardLibraryView.swift @@ -29,6 +29,7 @@ struct DashboardLibraryView: View { LibraryDetailView(library: library) } label: { LibraryIconView(library: library, height: 150) + .setAspectRatio(library.primaryImageAspectRatio) .padding() } .buttonStyle(PlainButtonStyle()) diff --git a/Jel/Views/Library/Item/ItemInfoView.swift b/Jel/Views/Library/Item/ItemInfoView.swift index d48dfef..103acf9 100644 --- a/Jel/Views/Library/Item/ItemInfoView.swift +++ b/Jel/Views/Library/Item/ItemInfoView.swift @@ -14,9 +14,9 @@ struct ItemInfoView: View { var body: some View { VStack(alignment: .leading) { HStack { - Text(item.genres?.first ?? "---") - Text("•") Text((item.productionYear != nil) ? String(item.productionYear!) : "---") + Text("•") + Text(item.genres?.first ?? "---") } Text(item.getRuntime() ?? "-:--") } diff --git a/Jel/Views/Library/Item/ItemMovieView.swift b/Jel/Views/Library/Item/ItemMediaView.swift similarity index 97% rename from Jel/Views/Library/Item/ItemMovieView.swift rename to Jel/Views/Library/Item/ItemMediaView.swift index eed083e..38c242d 100644 --- a/Jel/Views/Library/Item/ItemMovieView.swift +++ b/Jel/Views/Library/Item/ItemMediaView.swift @@ -1,5 +1,5 @@ // -// ItemMovieView.swift +// ItemMediaView.swift // Jel // // Created by zerocool on 12/23/23. @@ -8,7 +8,7 @@ import SwiftUI import JellyfinKit -struct ItemMovieView: View { +struct ItemMediaView: View { @EnvironmentObject var jellyfinClient: JellyfinClientController @StateObject var authState: AuthStateController = AuthStateController.shared diff --git a/Jel/Views/Library/Item/Types/ItemMovieView.swift b/Jel/Views/Library/Item/Types/ItemMovieView.swift new file mode 100644 index 0000000..22de82d --- /dev/null +++ b/Jel/Views/Library/Item/Types/ItemMovieView.swift @@ -0,0 +1,22 @@ +// +// ItemMovieView.swift +// Jel +// +// Created by zerocool on 12/26/23. +// + +import SwiftUI +import JellyfinKit + +struct ItemMovieView: View { + @State var item: BaseItemDto + var body: some View { + VStack { + ItemMediaView(item: item) + } + } +} + +//#Preview { +// ItemMovieView() +//} diff --git a/Jel/Views/Library/LibraryDetailView.swift b/Jel/Views/Library/LibraryDetailView.swift index 5d0e695..5b58a4e 100644 --- a/Jel/Views/Library/LibraryDetailView.swift +++ b/Jel/Views/Library/LibraryDetailView.swift @@ -34,55 +34,50 @@ struct LibraryDetailView: View { } ScrollView { LazyVGrid(columns: columns) { - if !searchText.isEmpty { - ForEach(searchResultItems ?? []) {item in - NavigationLink { - ItemView(item: item) - } label: { - LibraryIconView(library: item, imageType: "Primary", width: 170) - .padding() - } - .buttonStyle(PlainButtonStyle()) - } - } else { - ForEach(items ?? []) {item in - NavigationLink { - ItemView(item: item) - } label: { - LibraryIconView(library: item, imageType: "Primary", width: 170) - .padding() - } - .buttonStyle(PlainButtonStyle()) + // uses searchResultItems only if searchText is not empty + ForEach(!searchText.isEmpty ? (searchResultItems ?? items) ?? [] : items ?? []) {item in + NavigationLink { + ItemView(item: item) + } label: { + LibraryIconView(library: item, imageType: "Primary", width: 170) + .setAspectRatio(item.primaryImageAspectRatio ?? 0.6) + .padding() + } + .buttonStyle(PlainButtonStyle()) } } } .if(!loading) {view in view.searchable(text: $searchText) - .onChange(of: searchText) { - Task { - let parameters = Paths.GetParameters( - userID: AuthStateController.shared.userId, - searchTerm: searchText.lowercased(), - parentID: library.id - ) - searchResultHints = await jellyfinClient.search(parameters: parameters) - - searchResultItems = items?.filter { item in - for hint in searchResultHints?.searchHints ?? [] { - if hint.name == item.name { - return true + .onChange(of: searchText) { + Task { + let parameters = Paths.GetParameters( + userID: AuthStateController.shared.userId, + searchTerm: searchText.lowercased(), + parentID: library.id + ) + searchResultHints = await jellyfinClient.search(parameters: parameters) + + searchResultItems = items?.filter { item in + for hint in searchResultHints?.searchHints ?? [] { + if hint.name == item.name { + return true + } } + return false } - return false } } - } } .navigationTitle(library.name ?? "Unknown") .onAppear { Task { - let params = Paths.GetItemsParameters(userID: authState.userId, parentID: library.id) + let params = Paths.GetItemsParameters( + userID: authState.userId, + parentID: library.id, + fields: [.primaryImageAspectRatio] + ) let request = Paths.getItems(parameters: params) do { diff --git a/Jel/Views/Library/LibraryIconView.swift b/Jel/Views/Library/LibraryIconView.swift index a849446..9bd3182 100644 --- a/Jel/Views/Library/LibraryIconView.swift +++ b/Jel/Views/Library/LibraryIconView.swift @@ -32,21 +32,22 @@ struct LibraryIconView: View { if let image = state.image { image .resizable() + .aspectRatio(contentMode: contentMode) } else { if let content = placeHolder { content } else { Image(uiImage: blurHashImage) .resizable() + .aspectRatio(contentMode: .fill) } } } - .aspectRatio(contentMode: contentMode) .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: 16, height: 16)) ?? UIImage() + blurHashImage = UIImage(blurHash: blurhash, size: CGSize(width: 32, height: 32)) ?? UIImage() let imageId = library.id ?? "" let request = Paths.getItemImage(itemID: imageId, imageType: imageType) @@ -70,6 +71,22 @@ struct LibraryIconView: View { 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 {