From 082ce29a44ed4a8f02bad1f32c5d740a28715f8e Mon Sep 17 00:00:00 2001 From: PangMo5 Date: Wed, 2 Jun 2021 13:00:20 +0900 Subject: [PATCH 1/3] WIP --- JellyfinPlayer.xcodeproj/project.pbxproj | 26 +- .../xcshareddata/swiftpm/Package.resolved | 45 ++- JellyfinPlayer/ConnectToServerView.swift | 7 +- JellyfinPlayer/ContentView.swift | 15 +- JellyfinPlayer/ContinueWatchingView.swift | 8 +- JellyfinPlayer/EpisodeItemView.swift | 35 +- JellyfinPlayer/LatestMediaView.swift | 8 +- JellyfinPlayer/LibrarySearchView.swift | 10 +- JellyfinPlayer/LibraryView.swift | 10 +- JellyfinPlayer/MovieItemView.swift | 32 +- JellyfinPlayer/NextUpView.swift | 5 +- JellyfinPlayer/SeasonItemView.swift | 378 +++++++++--------- JellyfinPlayer/SeriesItemView.swift | 5 +- 13 files changed, 295 insertions(+), 289 deletions(-) diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 5d110182..e97ae5d5 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -28,7 +28,6 @@ 53892777263CBB000035E14B /* JellyApiTypings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53892776263CBB000035E14B /* JellyApiTypings.swift */; }; 5389277A263CBFE70035E14B /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 53892779263CBFE70035E14B /* SwiftyJSON */; }; 5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5389277B263CC3DB0035E14B /* BlurHashDecode.swift */; }; - 538CD954263E3DC100BB5AF0 /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 538CD953263E3DC100BB5AF0 /* SDWebImageSwiftUI */; }; 53987CA426572C1300E7EA70 /* SeasonItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53987CA326572C1300E7EA70 /* SeasonItemView.swift */; }; 53987CA626572F0700E7EA70 /* SeriesItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53987CA526572F0700E7EA70 /* SeriesItemView.swift */; }; 53987CA82657424A00E7EA70 /* EpisodeItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53987CA72657424A00E7EA70 /* EpisodeItemView.swift */; }; @@ -47,6 +46,7 @@ 621338932660107500A81A2A /* String++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621338922660107500A81A2A /* String++.swift */; }; 62133895266096EF00A81A2A /* LibraryListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62133894266096EF00A81A2A /* LibraryListViewModel.swift */; }; 621338B32660A07800A81A2A /* LazyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621338B22660A07800A81A2A /* LazyView.swift */; }; + 621C638026672A30004216EA /* NukeUI in Frameworks */ = {isa = PBXBuildFile; productRef = 621C637F26672A30004216EA /* NukeUI */; }; 6225FCCB2663841E00E067F6 /* ParallaxHeaderScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6225FCCA2663841E00E067F6 /* ParallaxHeaderScrollView.swift */; }; 6273DD43265F4195009C1D0B /* Moya in Frameworks */ = {isa = PBXBuildFile; productRef = 6273DD42265F4195009C1D0B /* Moya */; }; 6273DD45265F4195009C1D0B /* CombineMoya in Frameworks */ = {isa = PBXBuildFile; productRef = 6273DD44265F4195009C1D0B /* CombineMoya */; }; @@ -130,13 +130,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 538CD954263E3DC100BB5AF0 /* SDWebImageSwiftUI in Frameworks */, 5338F757263B7E2E0014BF09 /* KeychainSwift in Frameworks */, 6273DD45265F4195009C1D0B /* CombineMoya in Frameworks */, 6273DD43265F4195009C1D0B /* Moya in Frameworks */, 53D5E3DD264B47EE00BADDC8 /* MobileVLCKit.xcframework in Frameworks */, 5338F754263B65E10014BF09 /* SwiftyRequest in Frameworks */, 53352571265EA0A0006CCA86 /* Introspect in Frameworks */, + 621C638026672A30004216EA /* NukeUI in Frameworks */, 5302F82A2658791C00647A2E /* Sentry in Frameworks */, 5389277A263CBFE70035E14B /* SwiftyJSON in Frameworks */, ); @@ -295,11 +295,11 @@ 5338F753263B65E10014BF09 /* SwiftyRequest */, 5338F756263B7E2E0014BF09 /* KeychainSwift */, 53892779263CBFE70035E14B /* SwiftyJSON */, - 538CD953263E3DC100BB5AF0 /* SDWebImageSwiftUI */, 5302F8292658791C00647A2E /* Sentry */, 53352570265EA0A0006CCA86 /* Introspect */, 6273DD42265F4195009C1D0B /* Moya */, 6273DD44265F4195009C1D0B /* CombineMoya */, + 621C637F26672A30004216EA /* NukeUI */, ); productName = JellyfinPlayer; productReference = 5377CBF1263B596A003A4E83 /* JellyfinPlayer.app */; @@ -335,10 +335,10 @@ 5338F752263B65E10014BF09 /* XCRemoteSwiftPackageReference "SwiftyRequest" */, 5338F755263B7E2E0014BF09 /* XCRemoteSwiftPackageReference "keychain-swift" */, 53892778263CBFE70035E14B /* XCRemoteSwiftPackageReference "SwiftyJSON" */, - 538CD952263E3DC100BB5AF0 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */, 5302F8282658791C00647A2E /* XCRemoteSwiftPackageReference "sentry-cocoa" */, 5335256F265EA0A0006CCA86 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */, 6273DD41265F4195009C1D0B /* XCRemoteSwiftPackageReference "Moya" */, + 621C637E26672A30004216EA /* XCRemoteSwiftPackageReference "NukeUI" */, ); productRefGroup = 5377CBF2263B596A003A4E83 /* Products */; projectDirPath = ""; @@ -535,7 +535,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 32; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = 9R8RREG67J; + DEVELOPMENT_TEAM = 4BHXT8RHFR; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -562,7 +562,7 @@ CURRENT_PROJECT_VERSION = 32; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = 9R8RREG67J; + DEVELOPMENT_TEAM = 4BHXT8RHFR; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -644,12 +644,12 @@ minimumVersion = 5.0.1; }; }; - 538CD952263E3DC100BB5AF0 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */ = { + 621C637E26672A30004216EA /* XCRemoteSwiftPackageReference "NukeUI" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/SDWebImage/SDWebImageSwiftUI"; + repositoryURL = "https://github.com/kean/NukeUI"; requirement = { - kind = upToNextMajorVersion; - minimumVersion = 2.0.2; + branch = main; + kind = branch; }; }; 6273DD41265F4195009C1D0B /* XCRemoteSwiftPackageReference "Moya" */ = { @@ -688,10 +688,10 @@ package = 53892778263CBFE70035E14B /* XCRemoteSwiftPackageReference "SwiftyJSON" */; productName = SwiftyJSON; }; - 538CD953263E3DC100BB5AF0 /* SDWebImageSwiftUI */ = { + 621C637F26672A30004216EA /* NukeUI */ = { isa = XCSwiftPackageProductDependency; - package = 538CD952263E3DC100BB5AF0 /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */; - productName = SDWebImageSwiftUI; + package = 621C637E26672A30004216EA /* XCRemoteSwiftPackageReference "NukeUI" */; + productName = NukeUI; }; 6273DD42265F4195009C1D0B /* Moya */ = { isa = XCSwiftPackageProductDependency; diff --git a/JellyfinPlayer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/JellyfinPlayer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 45cf41c3..0793b3b3 100644 --- a/JellyfinPlayer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/JellyfinPlayer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -28,6 +28,15 @@ "version": "5.0.200" } }, + { + "package": "Gifu", + "repositoryURL": "https://github.com/kaishin/Gifu", + "state": { + "branch": null, + "revision": "0ffe24744cc3d82ab9edece53670d0352c6d5507", + "version": "3.3.0" + } + }, { "package": "KeychainSwift", "repositoryURL": "https://github.com/evgenyneu/keychain-swift", @@ -55,6 +64,24 @@ "version": "15.0.0-alpha.1" } }, + { + "package": "Nuke", + "repositoryURL": "https://github.com/kean/Nuke.git", + "state": { + "branch": null, + "revision": "2775239e10e23c0b70c5544b98c2af7f65c2bbd9", + "version": "10.0.1" + } + }, + { + "package": "NukeUI", + "repositoryURL": "https://github.com/kean/NukeUI", + "state": { + "branch": "main", + "revision": "27dcb9065a18450ba47ecd46a913a74646d95144", + "version": null + } + }, { "package": "ReactiveSwift", "repositoryURL": "https://github.com/Moya/ReactiveSwift.git", @@ -73,24 +100,6 @@ "version": "5.1.2" } }, - { - "package": "SDWebImage", - "repositoryURL": "https://github.com/SDWebImage/SDWebImage.git", - "state": { - "branch": null, - "revision": "76dd4b49110b8624317fc128e7fa0d8a252018bc", - "version": "5.11.1" - } - }, - { - "package": "SDWebImageSwiftUI", - "repositoryURL": "https://github.com/SDWebImage/SDWebImageSwiftUI", - "state": { - "branch": null, - "revision": "cd8625b7cf11a97698e180d28bb7d5d357196678", - "version": "2.0.2" - } - }, { "package": "Sentry", "repositoryURL": "https://github.com/getsentry/sentry-cocoa", diff --git a/JellyfinPlayer/ConnectToServerView.swift b/JellyfinPlayer/ConnectToServerView.swift index 62a804a7..28495931 100644 --- a/JellyfinPlayer/ConnectToServerView.swift +++ b/JellyfinPlayer/ConnectToServerView.swift @@ -12,7 +12,7 @@ import SwiftyJSON import CoreData import KeychainSwift import Sentry -import SDWebImageSwiftUI +import NukeUI class publicUser: ObservableObject { @Published var username: String = ""; @@ -320,9 +320,8 @@ struct ConnectToServerView: View { Text(pubuser.username).font(.subheadline).fontWeight(.semibold) Spacer() if(pubuser.primaryImageTag != "") { - WebImage(url: URL(string: "\(uri)/Users/\(pubuser.id)/Images/Primary?width=200&quality=80&tag=\(pubuser.primaryImageTag)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size - .aspectRatio(contentMode: .fill) + LazyImage(source: URL(string: "\(uri)/Users/\(pubuser.id)/Images/Primary?width=200&quality=80&tag=\(pubuser.primaryImageTag)")) + .contentMode(.aspectFill) .frame(width: 60, height: 60) .cornerRadius(30.0) .shadow(radius: 6) diff --git a/JellyfinPlayer/ContentView.swift b/JellyfinPlayer/ContentView.swift index 1c628616..dd2f81e0 100644 --- a/JellyfinPlayer/ContentView.swift +++ b/JellyfinPlayer/ContentView.swift @@ -8,10 +8,10 @@ import SwiftUI import KeychainSwift -import SDWebImageSwiftUI import Sentry import SwiftyJSON import SwiftyRequest +import Nuke struct ContentView: View { @Environment(\.managedObjectContext) @@ -75,12 +75,15 @@ struct ContentView: View { options.releaseName = "ios-" + (Bundle.main.infoDictionary?["CFBundleVersion"] as! String) options.enableOutOfMemoryTracking = true } + + ImageCache.shared.costLimit = 125 * 1024 * 1024 // 125MB memory + DataLoader.sharedUrlCache.diskCapacity = 1000 * 1024 * 1024 // 1000MB disk - let cache = SDImageCache(namespace: "tiny") - cache.config.maxMemoryCost = 125 * 1024 * 1024 // 125MB memory - cache.config.maxDiskSize = 1000 * 1024 * 1024 // 1000MB disk - SDImageCachesManager.shared.addCache(cache) - SDWebImageManager.defaultImageCache = SDImageCachesManager.shared +// let cache = SDImageCache(namespace: "tiny") +// cache.config.maxMemoryCost = 125 * 1024 * 1024 // 125MB memory +// cache.config.maxDiskSize = 1000 * 1024 * 1024 // 1000MB disk +// SDImageCachesManager.shared.addCache(cache) +// SDWebImageManager.defaultImageCache = SDImageCachesManager.shared _libraries.wrappedValue = [] _library_names.wrappedValue = [:] diff --git a/JellyfinPlayer/ContinueWatchingView.swift b/JellyfinPlayer/ContinueWatchingView.swift index 20bd9468..9c78236c 100644 --- a/JellyfinPlayer/ContinueWatchingView.swift +++ b/JellyfinPlayer/ContinueWatchingView.swift @@ -8,7 +8,7 @@ import SwiftUI import SwiftyRequest import SwiftyJSON -import SDWebImageSwiftUI +import NukeUI struct CustomShape: Shape { let radius: CGFloat @@ -115,8 +115,7 @@ struct ContinueWatchingView: View { VStack(alignment: .leading) { Spacer().frame(height: 10) if(item.Type == "Episode") { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=550&quality=80&tag=\(item.Image)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=550&quality=80&tag=\(item.Image)")) .placeholder { Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 48, height: 32))!) .resizable() @@ -144,8 +143,7 @@ struct ContinueWatchingView: View { .padding(0), alignment: .bottomLeading ) } else { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=550&quality=80&tag=\(item.Image)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=550&quality=80&tag=\(item.Image)")) .placeholder { Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 48, height: 32))!) .resizable() diff --git a/JellyfinPlayer/EpisodeItemView.swift b/JellyfinPlayer/EpisodeItemView.swift index 51db820f..2f504fe4 100644 --- a/JellyfinPlayer/EpisodeItemView.swift +++ b/JellyfinPlayer/EpisodeItemView.swift @@ -5,10 +5,10 @@ // Created by Aiden Vigue on 5/13/21. // -import SDWebImageSwiftUI import SwiftUI import SwiftyJSON import SwiftyRequest +import NukeUI struct EpisodeItemView: View { @EnvironmentObject @@ -204,8 +204,7 @@ struct EpisodeItemView: View { var portraitHeaderView: some View { VStack { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.ParentBackdropItemId)/Images/Backdrop?maxWidth=550&quality=90&tag=\(fullItem.Backdrop)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.ParentBackdropItemId)/Images/Backdrop?maxWidth=550&quality=90&tag=\(fullItem.Backdrop)")) .placeholder { Image(uiImage: UIImage(blurHash: fullItem .BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem @@ -213,9 +212,8 @@ struct EpisodeItemView: View { size: CGSize(width: 32, height: 32))!) .resizable() } - + .contentMode(.aspectFill) .opacity(0.3) - .aspectRatio(contentMode: .fill) .shadow(radius: 5) } } @@ -223,8 +221,7 @@ struct EpisodeItemView: View { var portraitHeaderOverlayView: some View { VStack(alignment: .leading) { HStack(alignment: .bottom, spacing: 12) { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")) .placeholder { Image(uiImage: UIImage(blurHash: fullItem .PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : @@ -233,7 +230,8 @@ struct EpisodeItemView: View { .resizable() .frame(width: 120, height: 180) .cornerRadius(10) - }.aspectRatio(contentMode: .fill) + } + .contentMode(.aspectFill) .frame(width: 120, height: 180) .cornerRadius(10) VStack(alignment: .leading) { @@ -365,8 +363,7 @@ struct EpisodeItemView: View { ])), title: cast.Name) }) { VStack { - WebImage(url: cast.Image) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + LazyImage(source: cast.Image) .placeholder { Image(uiImage: UIImage(blurHash: cast .ImageBlurHash == "" ? @@ -379,7 +376,8 @@ struct EpisodeItemView: View { .frame(width: 100, height: 100) .cornerRadius(10) } - .aspectRatio(contentMode: .fill) + + .contentMode(.aspectFill) .frame(width: 100, height: 100) .cornerRadius(10) Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1) @@ -424,8 +422,7 @@ struct EpisodeItemView: View { } else { GeometryReader { geometry in ZStack { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.ParentBackdropItemId)/Images/Backdrop?maxWidth=\(String(Int(geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing)))&quality=80&tag=\(fullItem.Backdrop)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.ParentBackdropItemId)/Images/Backdrop?maxWidth=\(String(Int(geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing)))&quality=80&tag=\(fullItem.Backdrop)")) .placeholder { Image(uiImage: UIImage(blurHash: fullItem .BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem @@ -437,17 +434,16 @@ struct EpisodeItemView: View { height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets .bottom) } + .contentMode(.aspectFill) .opacity(0.3) - .aspectRatio(contentMode: .fill) .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom) .edgesIgnoringSafeArea(.all) .blur(radius: 2) HStack { VStack { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")) .placeholder { Image(uiImage: UIImage(blurHash: fullItem .PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : @@ -456,7 +452,7 @@ struct EpisodeItemView: View { .resizable() .frame(width: 120, height: 180) } - .aspectRatio(contentMode: .fill) + .contentMode(.fill) .frame(width: 120, height: 180) .cornerRadius(10) .shadow(radius: 5) @@ -585,8 +581,7 @@ struct EpisodeItemView: View { ])), title: cast.Name) }) { VStack { - WebImage(url: cast.Image) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + LazyImage(source: cast.Image) .placeholder { Image(uiImage: UIImage(blurHash: cast .ImageBlurHash == "" ? @@ -599,7 +594,7 @@ struct EpisodeItemView: View { .frame(width: 100, height: 100) .cornerRadius(10) } - .aspectRatio(contentMode: .fill) + .contentMode(.aspectFill) .frame(width: 100, height: 100) .cornerRadius(10) Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1) diff --git a/JellyfinPlayer/LatestMediaView.swift b/JellyfinPlayer/LatestMediaView.swift index 7170af52..7846d2be 100644 --- a/JellyfinPlayer/LatestMediaView.swift +++ b/JellyfinPlayer/LatestMediaView.swift @@ -8,7 +8,7 @@ import SwiftUI import SwiftyRequest import SwiftyJSON -import SDWebImageSwiftUI +import NukeUI struct LatestMediaView: View { @Environment(\.managedObjectContext) private var viewContext @@ -91,8 +91,7 @@ struct LatestMediaView: View { VStack(alignment: .leading) { if(item.Type == "Series") { Spacer().frame(height:10) - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)")) .placeholder { Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 16, height: 16))!) .resizable() @@ -122,8 +121,7 @@ struct LatestMediaView: View { ) } else { Spacer().frame(height:10) - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)")) .placeholder { Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 16, height: 16))!) .resizable() diff --git a/JellyfinPlayer/LibrarySearchView.swift b/JellyfinPlayer/LibrarySearchView.swift index fd418a91..5b1291b7 100644 --- a/JellyfinPlayer/LibrarySearchView.swift +++ b/JellyfinPlayer/LibrarySearchView.swift @@ -5,10 +5,10 @@ // Created by Aiden Vigue on 5/2/21. // -import SDWebImageSwiftUI import SwiftUI import SwiftyJSON import SwiftyRequest +import NukeUI struct LibrarySearchView: View { @Environment(\.managedObjectContext) @@ -68,7 +68,7 @@ struct LibrarySearchView: View { } } if viewModel.isLoading { - ActivityIndicator($viewModel.isLoading) + ProgressView() } else if viewModel.items.isEmpty { Text("Empty Response") } @@ -87,8 +87,7 @@ struct ResumeItemGridCell: View { var body: some View { VStack(alignment: .leading) { if item.Type == "Movie" { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)")) - .resizable() + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)")) .placeholder { Image(uiImage: UIImage(blurHash: item .BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item @@ -101,8 +100,7 @@ struct ResumeItemGridCell: View { .frame(width: 100, height: 150) .cornerRadius(10) } else { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)")) - .resizable() + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)")) .placeholder { Image(uiImage: UIImage(blurHash: item .BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item diff --git a/JellyfinPlayer/LibraryView.swift b/JellyfinPlayer/LibraryView.swift index 454ee0a5..eaea439e 100644 --- a/JellyfinPlayer/LibraryView.swift +++ b/JellyfinPlayer/LibraryView.swift @@ -5,8 +5,8 @@ // Created by Aiden Vigue on 5/1/21. // -import SDWebImageSwiftUI import SwiftUI +import NukeUI struct LibraryView: View { @Environment(\.managedObjectContext) @@ -87,7 +87,7 @@ struct LibraryView: View { recalcTracks() } if viewModel.isLoading { - ActivityIndicator($viewModel.isLoading) + ProgressView() } else if viewModel.items.isEmpty { Text("Empty Response") } @@ -136,8 +136,7 @@ extension LibraryView { var body: some View { VStack(alignment: .leading) { if item.Type == "Movie" { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)")) - .resizable() + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)")) .placeholder { Image(uiImage: UIImage(blurHash: item .BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item @@ -150,8 +149,7 @@ extension LibraryView { .frame(width: 100, height: 150) .cornerRadius(10) } else { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)")) - .resizable() + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)")) .placeholder { Image(uiImage: UIImage(blurHash: item .BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item diff --git a/JellyfinPlayer/MovieItemView.swift b/JellyfinPlayer/MovieItemView.swift index 969a4c40..528451bb 100644 --- a/JellyfinPlayer/MovieItemView.swift +++ b/JellyfinPlayer/MovieItemView.swift @@ -5,10 +5,10 @@ // Created by Aiden Vigue on 5/13/21. // -import SDWebImageSwiftUI import SwiftUI import SwiftyJSON import SwiftyRequest +import NukeUI class DetailItem: ObservableObject { @Published @@ -293,8 +293,7 @@ struct MovieItemView: View { } var portraitHeaderView: some View { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Backdrop?maxWidth=550&quality=90&tag=\(fullItem.Backdrop)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Backdrop?maxWidth=550&quality=90&tag=\(fullItem.Backdrop)")) .placeholder { Image(uiImage: UIImage(blurHash: fullItem .BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem @@ -302,17 +301,15 @@ struct MovieItemView: View { size: CGSize(width: 32, height: 32))!) .resizable() } - + .contentMode(.aspectFill) .opacity(0.3) - .aspectRatio(contentMode: .fill) .shadow(radius: 5) } var portraitHeaderOverlayView: some View { VStack(alignment: .leading) { HStack(alignment: .bottom, spacing: 12) { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")) .placeholder { Image(uiImage: UIImage(blurHash: fullItem .PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : @@ -321,7 +318,8 @@ struct MovieItemView: View { .resizable() .frame(width: 120, height: 180) .cornerRadius(10) - }.aspectRatio(contentMode: .fill) + } + .contentMode(.aspectFill) .frame(width: 120, height: 180) .cornerRadius(10) VStack(alignment: .leading) { @@ -454,8 +452,7 @@ struct MovieItemView: View { ])), title: cast.Name) }) { VStack { - WebImage(url: cast.Image) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + LazyImage(source: cast.Image) .placeholder { Image(uiImage: UIImage(blurHash: cast .ImageBlurHash == "" ? @@ -468,7 +465,7 @@ struct MovieItemView: View { .frame(width: 100, height: 100) .cornerRadius(10) } - .aspectRatio(contentMode: .fill) + .contentMode(.aspectFill) .frame(width: 100, height: 100) .cornerRadius(10) Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1) @@ -513,8 +510,7 @@ struct MovieItemView: View { } else { GeometryReader { geometry in ZStack { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Backdrop?maxWidth=\(String(Int(geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing)))&quality=80&tag=\(fullItem.Backdrop)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Backdrop?maxWidth=\(String(Int(geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing)))&quality=80&tag=\(fullItem.Backdrop)")) .placeholder { Image(uiImage: UIImage(blurHash: fullItem .BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem @@ -526,17 +522,16 @@ struct MovieItemView: View { height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets .bottom) } + .contentMode(.aspectFill) .opacity(0.3) - .aspectRatio(contentMode: .fill) .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom) .edgesIgnoringSafeArea(.all) .blur(radius: 2) HStack { VStack { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")) .placeholder { Image(uiImage: UIImage(blurHash: fullItem .PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : @@ -674,8 +669,7 @@ struct MovieItemView: View { ])), title: cast.Name) }) { VStack { - WebImage(url: cast.Image) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + LazyImage(source: cast.Image) .placeholder { Image(uiImage: UIImage(blurHash: cast .ImageBlurHash == "" ? @@ -688,7 +682,7 @@ struct MovieItemView: View { .frame(width: 100, height: 100) .cornerRadius(10) } - .aspectRatio(contentMode: .fill) + .contentMode(.aspectFill) .frame(width: 100, height: 100) .cornerRadius(10) Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1) diff --git a/JellyfinPlayer/NextUpView.swift b/JellyfinPlayer/NextUpView.swift index 7b6e7a83..4128d1c7 100644 --- a/JellyfinPlayer/NextUpView.swift +++ b/JellyfinPlayer/NextUpView.swift @@ -8,7 +8,7 @@ import SwiftUI import SwiftyRequest import SwiftyJSON -import SDWebImageSwiftUI +import NukeUI struct NextUpView: View { @Environment(\.managedObjectContext) private var viewContext @@ -79,8 +79,7 @@ struct NextUpView: View { NavigationLink(destination: ItemView(item: item)) { VStack(alignment: .leading) { Spacer().frame(height:10) - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.SeriesId ?? "")/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.SeriesId ?? "")/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)")) .placeholder { Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 16, height: 16))!) .resizable() diff --git a/JellyfinPlayer/SeasonItemView.swift b/JellyfinPlayer/SeasonItemView.swift index 55ff0a5d..dd6afd46 100644 --- a/JellyfinPlayer/SeasonItemView.swift +++ b/JellyfinPlayer/SeasonItemView.swift @@ -5,7 +5,7 @@ // Created by Aiden Vigue on 5/13/21. // -import SDWebImageSwiftUI +import NukeUI import SwiftUI import SwiftyJSON import SwiftyRequest @@ -186,8 +186,7 @@ struct SeasonItemView: View { } var portraitHeaderView: some View { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Backdrop?maxWidth=750&quality=80&tag=\(item.SeasonImage ?? "")")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Backdrop?maxWidth=750&quality=90&tag=\(item.SeasonImage ?? "")")) .placeholder { Image(uiImage: UIImage(blurHash: item .SeasonImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item @@ -195,15 +194,31 @@ struct SeasonItemView: View { size: CGSize(width: 32, height: 32))!) .resizable() } + .failure{ + Image(uiImage: UIImage(blurHash: item + .SeasonImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item + .SeasonImageBlurHash ?? "", + size: CGSize(width: 32, height: 32))!) + .resizable() + } + .contentMode(.aspectFill) + .onStart { print("onStart \($0.request.url)") } + .onProgress { _,_,_ in print("onProgress") } + .onSuccess { _ in print("onSuccess") } + .onFailure { error in + print(error.dataLoadingError?.localizedDescription) + print(error.description) + print(error.localizedDescription) + } + .onCompletion { _ in print("onCompletion") } + .frame(minWidth: 0, maxWidth: .infinity) .opacity(0.4) - .aspectRatio(contentMode: .fill) .shadow(radius: 5) } var portraitHeaderOverlayView: some View { HStack(alignment: .bottom, spacing: 12) { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")) .placeholder { Image(uiImage: UIImage(blurHash: fullItem .PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem @@ -212,7 +227,8 @@ struct SeasonItemView: View { .resizable() .frame(width: 120, height: 180) .cornerRadius(10) - }.aspectRatio(contentMode: .fill) + } + .contentMode(.aspectFill) .frame(width: 120, height: 180) .cornerRadius(10) VStack(alignment: .leading) { @@ -237,31 +253,155 @@ struct SeasonItemView: View { .padding(.bottom, -22) } - var body: some View { - VStack(alignment: .leading) { - LoadingView(isShowing: $isLoading) { - VStack(alignment: .leading) { - if orientationInfo.orientation == .portrait { - ParallaxHeaderScrollView(header: portraitHeaderView, - staticOverlayView: portraitHeaderOverlayView, - overlayAlignment: .bottomLeading, - headerHeight: UIScreen.main.bounds.width * 0.5625) { - VStack(alignment: .leading) { - Spacer() - .frame(height: 22) + @ViewBuilder + var innerBody: some View { + if orientationInfo.orientation == .portrait { + ParallaxHeaderScrollView(header: portraitHeaderView, + staticOverlayView: portraitHeaderOverlayView, + overlayAlignment: .bottomLeading, + headerHeight: UIScreen.main.bounds.width * 0.5625) { + LazyVStack(alignment: .leading) { + Spacer() + .frame(height: 22) + if fullItem.Tagline != "" { + Text(fullItem.Tagline).font(.body).italic().padding(.top, 7) + .fixedSize(horizontal: false, vertical: true).padding(.leading, 16) + .padding(.trailing, 16) + } + Text(fullItem.Overview).font(.footnote).padding(.top, 3) + .fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16) + .padding(.trailing, 16) + ForEach(episodes, id: \.Id) { episode in + NavigationLink(destination: ItemView(item: episode.ResumeItem ?? ResumeItem())) { + HStack { + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(episode.Id)/Images/Primary?maxWidth=300&quality=90&tag=\(episode.Poster)")) + .placeholder { + Image(uiImage: UIImage(blurHash: episode + .PosterBlurHash == "" ? + "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem + .PosterBlurHash, + size: CGSize(width: 32, height: 32))!) + .resizable() + .frame(width: 150, height: 90) + .cornerRadius(10) + } + .contentMode(.aspectFill) + .shadow(radius: 5) + .frame(width: 150, height: 90) + .cornerRadius(10) + .overlay(RoundedRectangle(cornerRadius: 10, style: .circular) + .fill(Color(red: 172 / 255, green: 92 / 255, blue: 195 / 255) + .opacity(0.4)) + .frame(width: CGFloat((episode.Progress / Double(episode.RuntimeTicks)) * + 150), + height: 90) + .padding(0), alignment: .bottomLeading) + VStack(alignment: .leading) { + HStack { + Text(episode.Name).font(.subheadline) + .fontWeight(.semibold) + .foregroundColor(.primary) + .fixedSize(horizontal: false, vertical: true) + .lineLimit(1) + Spacer() + Text(episode.Runtime).font(.subheadline) + .fontWeight(.medium) + .foregroundColor(.secondary) + .lineLimit(1) + } + Spacer() + Text(episode.Overview).font(.footnote).foregroundColor(.secondary) + .fixedSize(horizontal: false, vertical: true).lineLimit(4) + Spacer() + }.padding(.trailing, 20).offset(y: 2) + }.offset(x: 12, y: 0) + } + } + if !fullItem.Directors.isEmpty { + HStack { + Text("Directors:").font(.callout).fontWeight(.semibold) + Text(fullItem.Directors.joined(separator: ", ")).font(.footnote).lineLimit(1) + .foregroundColor(Color.secondary) + }.padding(.leading, 16).padding(.trailing, 16) + } + if !fullItem.Writers.isEmpty { + HStack { + Text("Writers:").font(.callout).fontWeight(.semibold) + Text(fullItem.Writers.joined(separator: ", ")).font(.footnote).lineLimit(1) + .foregroundColor(Color.secondary) + }.padding(.leading, 16).padding(.trailing, 16) + } + if !fullItem.Studios.isEmpty { + HStack { + Text("Studios:").font(.callout).fontWeight(.semibold) + Text(fullItem.Studios.joined(separator: ", ")).font(.footnote).lineLimit(1) + .foregroundColor(Color.secondary) + }.padding(.leading, 16).padding(.trailing, 16) + } + Spacer().frame(height: 3) + } + } + } else { + GeometryReader { geometry in + ZStack { + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Backdrop?maxWidth=\(String(Int(geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing)))&quality=80&tag=\(item.SeasonImage ?? "")")) + .placeholder { + Image(uiImage: UIImage(blurHash: item + .SeasonImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item + .SeasonImageBlurHash ?? "", + size: CGSize(width: 32, height: 32))!) + .resizable() + .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets + .trailing, + height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets + .bottom) + } + .contentMode(.aspectFill) + + .opacity(0.4) + .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, + height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom) + .edgesIgnoringSafeArea(.all) + .blur(radius: 2) + HStack { + VStack(alignment: .leading) { + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")) + .placeholder { + Image(uiImage: UIImage(blurHash: fullItem + .PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : + fullItem.PosterBlurHash, + size: CGSize(width: 32, height: 32))!) + .resizable() + .frame(width: 120, height: 180) + .cornerRadius(10) + } + .contentMode(.aspectFill) + .frame(width: 120, height: 180) + .cornerRadius(10) + Spacer().frame(height: 4) + if fullItem.ProductionYear != 0 { + Text(String(fullItem.ProductionYear)).font(.subheadline) + .fontWeight(.medium) + .foregroundColor(.secondary) + } + Spacer() + } + ScrollView { + LazyVStack(alignment: .leading) { if fullItem.Tagline != "" { - Text(fullItem.Tagline).font(.body).italic().padding(.top, 7) + Text(fullItem.Tagline).font(.body).italic().padding(.top, 3) .fixedSize(horizontal: false, vertical: true).padding(.leading, 16) .padding(.trailing, 16) } - Text(fullItem.Overview).font(.footnote).padding(.top, 3) - .fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16) - .padding(.trailing, 16) + if fullItem.Overview != "" { + Text(fullItem.Overview).font(.footnote).padding(.top, 3) + .fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16) + .padding(.trailing, 16) + } ForEach(episodes, id: \.Id) { episode in NavigationLink(destination: ItemView(item: episode.ResumeItem ?? ResumeItem())) { HStack { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(episode.Id)/Images/Primary?maxWidth=300&quality=90&tag=\(episode.Poster)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(episode.Id)/Images/Primary?maxWidth=300&quality=90&tag=\(episode.Poster)")) .placeholder { Image(uiImage: UIImage(blurHash: episode .PosterBlurHash == "" ? @@ -271,7 +411,8 @@ struct SeasonItemView: View { .resizable() .frame(width: 150, height: 90) .cornerRadius(10) - }.aspectRatio(contentMode: .fill) + } + .contentMode(.aspectFill) .shadow(radius: 5) .frame(width: 150, height: 90) .cornerRadius(10) @@ -289,11 +430,30 @@ struct SeasonItemView: View { .foregroundColor(.primary) .fixedSize(horizontal: false, vertical: true) .lineLimit(1) - Spacer() Text(episode.Runtime).font(.subheadline) .fontWeight(.medium) .foregroundColor(.secondary) .lineLimit(1) + if episode.OfficialRating != "" { + Text(episode.OfficialRating).font(.subheadline) + .fontWeight(.medium) + .foregroundColor(.secondary) + .lineLimit(1) + .padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4)) + .overlay(RoundedRectangle(cornerRadius: 2) + .stroke(Color.secondary, lineWidth: 1)) + } + if episode.CommunityRating != "" { + HStack { + Image(systemName: "star").foregroundColor(.secondary) + Text(episode.CommunityRating).font(.subheadline) + .fontWeight(.semibold) + .foregroundColor(.secondary) + .lineLimit(1) + .offset(x: -6, y: 0) + } + } + Spacer() } Spacer() Text(episode.Overview).font(.footnote).foregroundColor(.secondary) @@ -324,163 +484,19 @@ struct SeasonItemView: View { .foregroundColor(Color.secondary) }.padding(.leading, 16).padding(.trailing, 16) } - Spacer().frame(height: 3) - } - } - } else { - GeometryReader { geometry in - ZStack { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Backdrop?maxWidth=\(String(Int(geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing)))&quality=80&tag=\(item.SeasonImage ?? "")")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size - .placeholder { - Image(uiImage: UIImage(blurHash: item - .SeasonImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item - .SeasonImageBlurHash ?? "", - size: CGSize(width: 32, height: 32))!) - .resizable() - .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets - .trailing, - height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets - .bottom) - } - - .opacity(0.4) - .aspectRatio(contentMode: .fill) - .frame(width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, - height: geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom) - .edgesIgnoringSafeArea(.all) - .blur(radius: 2) - HStack { - VStack(alignment: .leading) { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size - .placeholder { - Image(uiImage: UIImage(blurHash: fullItem - .PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : - fullItem.PosterBlurHash, - size: CGSize(width: 32, height: 32))!) - .resizable() - .frame(width: 120, height: 180) - .cornerRadius(10) - }.aspectRatio(contentMode: .fill) - .frame(width: 120, height: 180) - .cornerRadius(10) - Spacer().frame(height: 4) - if fullItem.ProductionYear != 0 { - Text(String(fullItem.ProductionYear)).font(.subheadline) - .fontWeight(.medium) - .foregroundColor(.secondary) - } - Spacer() - } - ScrollView { - VStack(alignment: .leading) { - if fullItem.Tagline != "" { - Text(fullItem.Tagline).font(.body).italic().padding(.top, 3) - .fixedSize(horizontal: false, vertical: true).padding(.leading, 16) - .padding(.trailing, 16) - } - if fullItem.Overview != "" { - Text(fullItem.Overview).font(.footnote).padding(.top, 3) - .fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16) - .padding(.trailing, 16) - } - ForEach(episodes, id: \.Id) { episode in - NavigationLink(destination: ItemView(item: episode.ResumeItem ?? ResumeItem())) { - HStack { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(episode.Id)/Images/Primary?maxWidth=300&quality=90&tag=\(episode.Poster)")!) - .resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size - .placeholder { - Image(uiImage: UIImage(blurHash: episode - .PosterBlurHash == "" ? - "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem - .PosterBlurHash, - size: CGSize(width: 32, height: 32))!) - .resizable() - .frame(width: 150, height: 90) - .cornerRadius(10) - }.aspectRatio(contentMode: .fill) - .shadow(radius: 5) - .frame(width: 150, height: 90) - .cornerRadius(10) - .overlay(RoundedRectangle(cornerRadius: 10, style: .circular) - .fill(Color(red: 172 / 255, green: 92 / 255, blue: 195 / 255) - .opacity(0.4)) - .frame(width: CGFloat((episode.Progress / Double(episode.RuntimeTicks)) * - 150), - height: 90) - .padding(0), alignment: .bottomLeading) - VStack(alignment: .leading) { - HStack { - Text(episode.Name).font(.subheadline) - .fontWeight(.semibold) - .foregroundColor(.primary) - .fixedSize(horizontal: false, vertical: true) - .lineLimit(1) - Text(episode.Runtime).font(.subheadline) - .fontWeight(.medium) - .foregroundColor(.secondary) - .lineLimit(1) - if episode.OfficialRating != "" { - Text(episode.OfficialRating).font(.subheadline) - .fontWeight(.medium) - .foregroundColor(.secondary) - .lineLimit(1) - .padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4)) - .overlay(RoundedRectangle(cornerRadius: 2) - .stroke(Color.secondary, lineWidth: 1)) - } - if episode.CommunityRating != "" { - HStack { - Image(systemName: "star").foregroundColor(.secondary) - Text(episode.CommunityRating).font(.subheadline) - .fontWeight(.semibold) - .foregroundColor(.secondary) - .lineLimit(1) - .offset(x: -6, y: 0) - } - } - Spacer() - } - Spacer() - Text(episode.Overview).font(.footnote).foregroundColor(.secondary) - .fixedSize(horizontal: false, vertical: true).lineLimit(4) - Spacer() - }.padding(.trailing, 20).offset(y: 2) - }.offset(x: 12, y: 0) - } - } - if !fullItem.Directors.isEmpty { - HStack { - Text("Directors:").font(.callout).fontWeight(.semibold) - Text(fullItem.Directors.joined(separator: ", ")).font(.footnote).lineLimit(1) - .foregroundColor(Color.secondary) - }.padding(.leading, 16).padding(.trailing, 16) - } - if !fullItem.Writers.isEmpty { - HStack { - Text("Writers:").font(.callout).fontWeight(.semibold) - Text(fullItem.Writers.joined(separator: ", ")).font(.footnote).lineLimit(1) - .foregroundColor(Color.secondary) - }.padding(.leading, 16).padding(.trailing, 16) - } - if !fullItem.Studios.isEmpty { - HStack { - Text("Studios:").font(.callout).fontWeight(.semibold) - Text(fullItem.Studios.joined(separator: ", ")).font(.footnote).lineLimit(1) - .foregroundColor(Color.secondary) - }.padding(.leading, 16).padding(.trailing, 16) - } - Spacer().frame(height: 125) - }.frame(maxHeight: .infinity) - }.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) - }.padding(.top, 16).padding(.leading, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 0) - } - } - } + Spacer().frame(height: 125) + }.frame(maxHeight: .infinity) + }.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55) + }.padding(.top, 16).padding(.leading, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 0) } } } + } + + var body: some View { + LoadingView(isShowing: $isLoading) { + innerBody + } .onAppear(perform: loadData) .navigationBarTitleDisplayMode(.inline) .navigationTitle("\(item.Name) - \(item.SeriesName ?? "")") diff --git a/JellyfinPlayer/SeriesItemView.swift b/JellyfinPlayer/SeriesItemView.swift index 279959ab..f6294b73 100644 --- a/JellyfinPlayer/SeriesItemView.swift +++ b/JellyfinPlayer/SeriesItemView.swift @@ -8,7 +8,7 @@ import SwiftUI import SwiftyRequest import SwiftyJSON -import SDWebImageSwiftUI +import NukeUI struct SeriesItemView: View { @EnvironmentObject var globalData: GlobalData @@ -96,8 +96,7 @@ struct SeriesItemView: View { ForEach(items, id: \.Id) { item in NavigationLink(destination: ItemView(item: item )) { VStack(alignment: .leading) { - WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=90&tag=\(item.Image)")) - .resizable() + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=90&tag=\(item.Image)")) .placeholder { Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 32, height: 32))!) .resizable() From df0014592d91f5739a104d37af9026b67df1f724 Mon Sep 17 00:00:00 2001 From: PangMo5 Date: Wed, 2 Jun 2021 16:18:38 +0900 Subject: [PATCH 2/3] add LazyImage.placeholderAndFailure apply placeholderAndFailure too all LazyImage --- JellyfinPlayer.xcodeproj/project.pbxproj | 4 + JellyfinPlayer/ContinueWatchingView.swift | 4 +- JellyfinPlayer/EpisodeItemView.swift | 14 ++- JellyfinPlayer/Extensions/LazyImage++.swift | 22 ++++ JellyfinPlayer/LatestMediaView.swift | 4 +- JellyfinPlayer/LibrarySearchView.swift | 4 +- JellyfinPlayer/LibraryView.swift | 4 +- JellyfinPlayer/MovieItemView.swift | 12 +-- JellyfinPlayer/NextUpView.swift | 2 +- JellyfinPlayer/SeasonItemView.swift | 108 +++++++++----------- JellyfinPlayer/SeriesItemView.swift | 2 +- 11 files changed, 99 insertions(+), 81 deletions(-) create mode 100644 JellyfinPlayer/Extensions/LazyImage++.swift diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index e97ae5d5..89943dca 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ 62133895266096EF00A81A2A /* LibraryListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62133894266096EF00A81A2A /* LibraryListViewModel.swift */; }; 621338B32660A07800A81A2A /* LazyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621338B22660A07800A81A2A /* LazyView.swift */; }; 621C638026672A30004216EA /* NukeUI in Frameworks */ = {isa = PBXBuildFile; productRef = 621C637F26672A30004216EA /* NukeUI */; }; + 621C638226676728004216EA /* LazyImage++.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621C638126676728004216EA /* LazyImage++.swift */; }; 6225FCCB2663841E00E067F6 /* ParallaxHeaderScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6225FCCA2663841E00E067F6 /* ParallaxHeaderScrollView.swift */; }; 6273DD43265F4195009C1D0B /* Moya in Frameworks */ = {isa = PBXBuildFile; productRef = 6273DD42265F4195009C1D0B /* Moya */; }; 6273DD45265F4195009C1D0B /* CombineMoya in Frameworks */ = {isa = PBXBuildFile; productRef = 6273DD44265F4195009C1D0B /* CombineMoya */; }; @@ -117,6 +118,7 @@ 621338922660107500A81A2A /* String++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String++.swift"; sourceTree = ""; }; 62133894266096EF00A81A2A /* LibraryListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryListViewModel.swift; sourceTree = ""; }; 621338B22660A07800A81A2A /* LazyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyView.swift; sourceTree = ""; }; + 621C638126676728004216EA /* LazyImage++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LazyImage++.swift"; sourceTree = ""; }; 6225FCCA2663841E00E067F6 /* ParallaxHeaderScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParallaxHeaderScrollView.swift; sourceTree = ""; }; 6273DD47265F41B3009C1D0B /* JellyfinAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinAPI.swift; sourceTree = ""; }; 6273DD4D265F47B2009C1D0B /* LibrarySearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchViewModel.swift; sourceTree = ""; }; @@ -221,6 +223,7 @@ 621338B22660A07800A81A2A /* LazyView.swift */, 621338922660107500A81A2A /* String++.swift */, 6225FCCA2663841E00E067F6 /* ParallaxHeaderScrollView.swift */, + 621C638126676728004216EA /* LazyImage++.swift */, ); path = Extensions; sourceTree = ""; @@ -369,6 +372,7 @@ buildActionMask = 2147483647; files = ( 621338932660107500A81A2A /* String++.swift in Sources */, + 621C638226676728004216EA /* LazyImage++.swift in Sources */, 53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */, 5377CBFE263B596B003A4E83 /* PersistenceController.swift in Sources */, 5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */, diff --git a/JellyfinPlayer/ContinueWatchingView.swift b/JellyfinPlayer/ContinueWatchingView.swift index 9c78236c..8b6be46c 100644 --- a/JellyfinPlayer/ContinueWatchingView.swift +++ b/JellyfinPlayer/ContinueWatchingView.swift @@ -116,7 +116,7 @@ struct ContinueWatchingView: View { Spacer().frame(height: 10) if(item.Type == "Episode") { LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=550&quality=80&tag=\(item.Image)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 48, height: 32))!) .resizable() .frame(width: 320, height: 180) @@ -144,7 +144,7 @@ struct ContinueWatchingView: View { ) } else { LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=550&quality=80&tag=\(item.Image)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 48, height: 32))!) .resizable() .frame(width: 320, height: 180) diff --git a/JellyfinPlayer/EpisodeItemView.swift b/JellyfinPlayer/EpisodeItemView.swift index 2f504fe4..5fb2a308 100644 --- a/JellyfinPlayer/EpisodeItemView.swift +++ b/JellyfinPlayer/EpisodeItemView.swift @@ -203,9 +203,8 @@ struct EpisodeItemView: View { } var portraitHeaderView: some View { - VStack { LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.ParentBackdropItemId)/Images/Backdrop?maxWidth=550&quality=90&tag=\(fullItem.Backdrop)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: fullItem .BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem .BackdropBlurHash, @@ -215,14 +214,13 @@ struct EpisodeItemView: View { .contentMode(.aspectFill) .opacity(0.3) .shadow(radius: 5) - } } var portraitHeaderOverlayView: some View { VStack(alignment: .leading) { HStack(alignment: .bottom, spacing: 12) { LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: fullItem .PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.PosterBlurHash, @@ -364,7 +362,7 @@ struct EpisodeItemView: View { }) { VStack { LazyImage(source: cast.Image) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: cast .ImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : @@ -423,7 +421,7 @@ struct EpisodeItemView: View { GeometryReader { geometry in ZStack { LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.ParentBackdropItemId)/Images/Backdrop?maxWidth=\(String(Int(geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing)))&quality=80&tag=\(fullItem.Backdrop)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: fullItem .BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem .BackdropBlurHash, @@ -444,7 +442,7 @@ struct EpisodeItemView: View { HStack { VStack { LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: fullItem .PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.PosterBlurHash, @@ -582,7 +580,7 @@ struct EpisodeItemView: View { }) { VStack { LazyImage(source: cast.Image) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: cast .ImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : diff --git a/JellyfinPlayer/Extensions/LazyImage++.swift b/JellyfinPlayer/Extensions/LazyImage++.swift new file mode 100644 index 00000000..d124f6a7 --- /dev/null +++ b/JellyfinPlayer/Extensions/LazyImage++.swift @@ -0,0 +1,22 @@ +// +// LazyImage++.swift +// JellyfinPlayer +// +// Created by PangMo5 on 2021/06/02. +// + +import Foundation +import SwiftUI +import NukeUI + +extension LazyImage { + func placeholderAndFailure(@ViewBuilder _ content: () -> Content?) -> LazyImage { + placeholder { + content() + } + .failure { + content() + } + } + +} diff --git a/JellyfinPlayer/LatestMediaView.swift b/JellyfinPlayer/LatestMediaView.swift index 7846d2be..88004fdc 100644 --- a/JellyfinPlayer/LatestMediaView.swift +++ b/JellyfinPlayer/LatestMediaView.swift @@ -92,7 +92,7 @@ struct LatestMediaView: View { if(item.Type == "Series") { Spacer().frame(height:10) LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 16, height: 16))!) .resizable() .frame(width: 100, height: 150) @@ -122,7 +122,7 @@ struct LatestMediaView: View { } else { Spacer().frame(height:10) LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 16, height: 16))!) .resizable() .frame(width: 100, height: 150) diff --git a/JellyfinPlayer/LibrarySearchView.swift b/JellyfinPlayer/LibrarySearchView.swift index 5b1291b7..0fb7ae20 100644 --- a/JellyfinPlayer/LibrarySearchView.swift +++ b/JellyfinPlayer/LibrarySearchView.swift @@ -88,7 +88,7 @@ struct ResumeItemGridCell: View { VStack(alignment: .leading) { if item.Type == "Movie" { LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: item .BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item .BlurHash, @@ -101,7 +101,7 @@ struct ResumeItemGridCell: View { .cornerRadius(10) } else { LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: item .BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item .BlurHash, diff --git a/JellyfinPlayer/LibraryView.swift b/JellyfinPlayer/LibraryView.swift index eaea439e..9823aeb0 100644 --- a/JellyfinPlayer/LibraryView.swift +++ b/JellyfinPlayer/LibraryView.swift @@ -137,7 +137,7 @@ extension LibraryView { VStack(alignment: .leading) { if item.Type == "Movie" { LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: item .BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item .BlurHash, @@ -150,7 +150,7 @@ extension LibraryView { .cornerRadius(10) } else { LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: item .BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item .BlurHash, diff --git a/JellyfinPlayer/MovieItemView.swift b/JellyfinPlayer/MovieItemView.swift index 528451bb..452edfca 100644 --- a/JellyfinPlayer/MovieItemView.swift +++ b/JellyfinPlayer/MovieItemView.swift @@ -294,7 +294,7 @@ struct MovieItemView: View { var portraitHeaderView: some View { LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Backdrop?maxWidth=550&quality=90&tag=\(fullItem.Backdrop)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: fullItem .BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem .BackdropBlurHash, @@ -310,7 +310,7 @@ struct MovieItemView: View { VStack(alignment: .leading) { HStack(alignment: .bottom, spacing: 12) { LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: fullItem .PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.PosterBlurHash, @@ -453,7 +453,7 @@ struct MovieItemView: View { }) { VStack { LazyImage(source: cast.Image) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: cast .ImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : @@ -511,7 +511,7 @@ struct MovieItemView: View { GeometryReader { geometry in ZStack { LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Backdrop?maxWidth=\(String(Int(geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing)))&quality=80&tag=\(fullItem.Backdrop)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: fullItem .BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem .BackdropBlurHash, @@ -532,7 +532,7 @@ struct MovieItemView: View { HStack { VStack { LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: fullItem .PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.PosterBlurHash, @@ -670,7 +670,7 @@ struct MovieItemView: View { }) { VStack { LazyImage(source: cast.Image) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: cast .ImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : diff --git a/JellyfinPlayer/NextUpView.swift b/JellyfinPlayer/NextUpView.swift index 4128d1c7..23e08323 100644 --- a/JellyfinPlayer/NextUpView.swift +++ b/JellyfinPlayer/NextUpView.swift @@ -80,7 +80,7 @@ struct NextUpView: View { VStack(alignment: .leading) { Spacer().frame(height:10) LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.SeriesId ?? "")/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 16, height: 16))!) .resizable() .frame(width: 100, height: 150) diff --git a/JellyfinPlayer/SeasonItemView.swift b/JellyfinPlayer/SeasonItemView.swift index dd6afd46..97a8ccfc 100644 --- a/JellyfinPlayer/SeasonItemView.swift +++ b/JellyfinPlayer/SeasonItemView.swift @@ -18,7 +18,8 @@ struct SeasonItemView: View { @State private var isLoading: Bool = true var item: ResumeItem - var fullItem: DetailItem + @State + var fullItem = DetailItem() @State var episodes: [DetailItem] = [] @State @@ -28,7 +29,6 @@ struct SeasonItemView: View { init(item: ResumeItem) { self.item = item - self.fullItem = DetailItem() } func loadData() { @@ -48,32 +48,33 @@ struct SeasonItemView: View { let body = response.body do { let json = try JSON(data: body) - fullItem.ProductionYear = json["ProductionYear"].int ?? 0 - fullItem.Poster = json["ImageTags"]["Primary"].string ?? "" - fullItem.PosterBlurHash = json["ImageBlurHashes"]["Primary"][fullItem.Poster].string ?? "" - fullItem.Backdrop = json["BackdropImageTags"][0].string ?? "" - fullItem.BackdropBlurHash = json["ImageBlurHashes"]["Backdrop"][fullItem.Backdrop].string ?? "" - fullItem.Name = json["Name"].string ?? "" - fullItem.Type = json["Type"].string ?? "" - fullItem.IndexNumber = json["IndexNumber"].int ?? nil - fullItem.SeriesId = json["ParentId"].string ?? nil - fullItem.Id = item.Id - fullItem.Overview = json["Overview"].string ?? "" - fullItem.Tagline = json["Taglines"][0].string ?? "" - fullItem.SeriesName = json["SeriesName"].string ?? nil - fullItem.ParentId = json["ParentId"].string ?? "" + let responseItem = DetailItem() + responseItem.ProductionYear = json["ProductionYear"].int ?? 0 + responseItem.Poster = json["ImageTags"]["Primary"].string ?? "" + responseItem.PosterBlurHash = json["ImageBlurHashes"]["Primary"][responseItem.Poster].string ?? "" + responseItem.Backdrop = json["BackdropImageTags"][0].string ?? "" + responseItem.BackdropBlurHash = json["ImageBlurHashes"]["Backdrop"][responseItem.Backdrop].string ?? "" + responseItem.Name = json["Name"].string ?? "" + responseItem.Type = json["Type"].string ?? "" + responseItem.IndexNumber = json["IndexNumber"].int ?? nil + responseItem.SeriesId = json["ParentId"].string ?? nil + responseItem.Id = item.Id + responseItem.Overview = json["Overview"].string ?? "" + responseItem.Tagline = json["Taglines"][0].string ?? "" + responseItem.SeriesName = json["SeriesName"].string ?? nil + responseItem.ParentId = json["ParentId"].string ?? "" // People - fullItem.Directors = [] - fullItem.Studios = [] - fullItem.Writers = [] - fullItem.Cast = [] - fullItem.Genres = [] + responseItem.Directors = [] + responseItem.Studios = [] + responseItem.Writers = [] + responseItem.Cast = [] + responseItem.Genres = [] for (_, person): (String, JSON) in json["People"] { if person["Type"].stringValue == "Director" { - fullItem.Directors.append(person["Name"].string ?? "") + responseItem.Directors.append(person["Name"].string ?? "") } else if person["Type"].stringValue == "Writer" { - fullItem.Writers.append(person["Name"].string ?? "") + responseItem.Writers.append(person["Name"].string ?? "") } else if person["Type"].stringValue == "Actor" { let cast = CastMember() cast.Name = person["Name"].string ?? "" @@ -84,10 +85,15 @@ struct SeasonItemView: View { cast .Image = URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(cast.Id)/Images/Primary?maxWidth=2000&quality=90&tag=\(imageTag)")! - fullItem.Cast.append(cast) + responseItem.Cast.append(cast) } } + _fullItem.wrappedValue = responseItem + + print("!@#!@#@#!@#") + print(fullItem.SeriesId) + let url2 = "/Shows/\(fullItem.SeriesId ?? "")/Episodes?SeasonId=\(item.Id)&UserId=\(globalData.user?.user_id ?? "")&Fields=ItemCounts%2CPrimaryImageAspectRatio%2CBasicSyncInfo%2CCanDelete%2CMediaSourceCount%2COverview" let request2 = RestRequest(method: .get, url: (globalData.server?.baseURI ?? "") + url2) @@ -185,41 +191,29 @@ struct SeasonItemView: View { return result } + @ViewBuilder var portraitHeaderView: some View { - LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Backdrop?maxWidth=750&quality=90&tag=\(item.SeasonImage ?? "")")) - .placeholder { - Image(uiImage: UIImage(blurHash: item - .SeasonImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item - .SeasonImageBlurHash ?? "", - size: CGSize(width: 32, height: 32))!) - .resizable() - } - .failure{ - Image(uiImage: UIImage(blurHash: item - .SeasonImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item - .SeasonImageBlurHash ?? "", - size: CGSize(width: 32, height: 32))!) - .resizable() - } - .contentMode(.aspectFill) - .onStart { print("onStart \($0.request.url)") } - .onProgress { _,_,_ in print("onProgress") } - .onSuccess { _ in print("onSuccess") } - .onFailure { error in - print(error.dataLoadingError?.localizedDescription) - print(error.description) - print(error.localizedDescription) - } - .onCompletion { _ in print("onCompletion") } - .frame(minWidth: 0, maxWidth: .infinity) - .opacity(0.4) - .shadow(radius: 5) + if isLoading { + EmptyView() + } else { + LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Backdrop?maxWidth=550&quality=90&tag=\(item.SeasonImage ?? "")")) + .placeholderAndFailure { + Image(uiImage: UIImage(blurHash: item + .SeasonImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item + .SeasonImageBlurHash ?? "", + size: CGSize(width: 32, height: 32))!) + .resizable() + } + .contentMode(.aspectFill) + .opacity(0.4) + .shadow(radius: 5) + } } var portraitHeaderOverlayView: some View { HStack(alignment: .bottom, spacing: 12) { LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: fullItem .PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem .PosterBlurHash, @@ -275,7 +269,7 @@ struct SeasonItemView: View { NavigationLink(destination: ItemView(item: episode.ResumeItem ?? ResumeItem())) { HStack { LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(episode.Id)/Images/Primary?maxWidth=300&quality=90&tag=\(episode.Poster)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: episode .PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem @@ -345,7 +339,7 @@ struct SeasonItemView: View { GeometryReader { geometry in ZStack { LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Backdrop?maxWidth=\(String(Int(geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing)))&quality=80&tag=\(item.SeasonImage ?? "")")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: item .SeasonImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item .SeasonImageBlurHash ?? "", @@ -366,7 +360,7 @@ struct SeasonItemView: View { HStack { VStack(alignment: .leading) { LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: fullItem .PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.PosterBlurHash, @@ -402,7 +396,7 @@ struct SeasonItemView: View { NavigationLink(destination: ItemView(item: episode.ResumeItem ?? ResumeItem())) { HStack { LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(episode.Id)/Images/Primary?maxWidth=300&quality=90&tag=\(episode.Poster)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: episode .PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem diff --git a/JellyfinPlayer/SeriesItemView.swift b/JellyfinPlayer/SeriesItemView.swift index f6294b73..9689cf32 100644 --- a/JellyfinPlayer/SeriesItemView.swift +++ b/JellyfinPlayer/SeriesItemView.swift @@ -97,7 +97,7 @@ struct SeriesItemView: View { NavigationLink(destination: ItemView(item: item )) { VStack(alignment: .leading) { LazyImage(source: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=90&tag=\(item.Image)")) - .placeholder { + .placeholderAndFailure { Image(uiImage: UIImage(blurHash: (item.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item.BlurHash), size: CGSize(width: 32, height: 32))!) .resizable() .frame(width: 100, height: 150) From 9a6f859ff7d57c5036e2005c1e8b93a55fd20dbc Mon Sep 17 00:00:00 2001 From: Aiden Vigue Date: Wed, 2 Jun 2021 13:38:06 -0400 Subject: [PATCH 3/3] Small fixes --- JellyfinPlayer.xcodeproj/project.pbxproj | 4 ++-- JellyfinPlayer/ContentView.swift | 6 ------ JellyfinPlayer/SeasonItemView.swift | 3 --- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 89943dca..59089512 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -539,7 +539,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 32; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = 4BHXT8RHFR; + DEVELOPMENT_TEAM = 9R8RREG67J; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -566,7 +566,7 @@ CURRENT_PROJECT_VERSION = 32; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = 4BHXT8RHFR; + DEVELOPMENT_TEAM = 9R8RREG67J; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; diff --git a/JellyfinPlayer/ContentView.swift b/JellyfinPlayer/ContentView.swift index dd2f81e0..8cb02617 100644 --- a/JellyfinPlayer/ContentView.swift +++ b/JellyfinPlayer/ContentView.swift @@ -79,12 +79,6 @@ struct ContentView: View { ImageCache.shared.costLimit = 125 * 1024 * 1024 // 125MB memory DataLoader.sharedUrlCache.diskCapacity = 1000 * 1024 * 1024 // 1000MB disk -// let cache = SDImageCache(namespace: "tiny") -// cache.config.maxMemoryCost = 125 * 1024 * 1024 // 125MB memory -// cache.config.maxDiskSize = 1000 * 1024 * 1024 // 1000MB disk -// SDImageCachesManager.shared.addCache(cache) -// SDWebImageManager.defaultImageCache = SDImageCachesManager.shared - _libraries.wrappedValue = [] _library_names.wrappedValue = [:] _librariesShowRecentlyAdded.wrappedValue = [] diff --git a/JellyfinPlayer/SeasonItemView.swift b/JellyfinPlayer/SeasonItemView.swift index 97a8ccfc..995a0d69 100644 --- a/JellyfinPlayer/SeasonItemView.swift +++ b/JellyfinPlayer/SeasonItemView.swift @@ -91,9 +91,6 @@ struct SeasonItemView: View { _fullItem.wrappedValue = responseItem - print("!@#!@#@#!@#") - print(fullItem.SeriesId) - let url2 = "/Shows/\(fullItem.SeriesId ?? "")/Episodes?SeasonId=\(item.Id)&UserId=\(globalData.user?.user_id ?? "")&Fields=ItemCounts%2CPrimaryImageAspectRatio%2CBasicSyncInfo%2CCanDelete%2CMediaSourceCount%2COverview" let request2 = RestRequest(method: .get, url: (globalData.server?.baseURI ?? "") + url2)