From 082ce29a44ed4a8f02bad1f32c5d740a28715f8e Mon Sep 17 00:00:00 2001 From: PangMo5 Date: Wed, 2 Jun 2021 13:00:20 +0900 Subject: [PATCH] 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()