diff --git a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift index 8c353cb7..9a4dcf1d 100644 --- a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift +++ b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift @@ -92,6 +92,16 @@ public extension BaseItemDto { return URL(string: urlString)! } + func getThumbImage(maxWidth: Int) -> URL { + let x = UIScreen.main.nativeScale * CGFloat(maxWidth) + + let urlString = ImageAPI.getItemImageWithRequestBuilder(itemId: id ?? "", + imageType: .thumb, + maxWidth: Int(x), + quality: 96).URLString + return URL(string: urlString)! + } + func getEpisodeLocator() -> String? { if let seasonNo = parentIndexNumber, let episodeNo = indexNumber { return L10n.seasonAndEpisode(String(seasonNo), String(episodeNo)) @@ -119,6 +129,16 @@ public extension BaseItemDto { return URL(string: urlString)! } + func getSeriesThumbImage(maxWidth: Int) -> URL { + let x = UIScreen.main.nativeScale * CGFloat(maxWidth) + let urlString = ImageAPI.getItemImageWithRequestBuilder(itemId: seriesId ?? "", + imageType: .thumb, + maxWidth: Int(x), + quality: 96, + tag: seriesPrimaryImageTag).URLString + return URL(string: urlString)! + } + func getPrimaryImage(maxWidth: Int) -> URL { let imageType = ImageType.primary var imageTag = imageTags?[ImageType.primary.rawValue] ?? "" diff --git a/Shared/Views/ImageView.swift b/Shared/Views/ImageView.swift index 49a32be0..9dad2701 100644 --- a/Shared/Views/ImageView.swift +++ b/Shared/Views/ImageView.swift @@ -10,26 +10,52 @@ import NukeUI import SwiftUI struct ImageView: View { - private let source: URL + + @State + private var sources: [URL] + private var currentURL: URL? { sources.first } + private let blurhash: String private let failureInitials: String init(src: URL, bh: String = "001fC^", failureInitials: String = "") { - self.source = src + self.sources = [src] self.blurhash = bh self.failureInitials = failureInitials } - // TODO: fix placeholder hash image + init(sources: [URL], bh: String = "001fC^", failureInitials: String = "") { + assert(!sources.isEmpty, "Must supply at least one source") + + self.sources = sources + self.blurhash = bh + self.failureInitials = failureInitials + } + + // TODO: fix placeholder hash view @ViewBuilder - private var placeholderImage: some View { - Image(uiImage: UIImage(blurHash: blurhash, size: CGSize(width: 8, height: 8)) ?? - UIImage(blurHash: "001fC^", size: CGSize(width: 8, height: 8))!) - .resizable() + private func placeholderView() -> some View { +// Image(uiImage: UIImage(blurHash: blurhash, size: CGSize(width: 8, height: 8)) ?? +// UIImage(blurHash: "001fC^", size: CGSize(width: 8, height: 8))!) +// .resizable() + + #if os(tvOS) + ZStack { + Color.black.ignoresSafeArea() + + ProgressView() + } + #else + ZStack { + Color.gray.ignoresSafeArea() + + ProgressView() + } + #endif } @ViewBuilder - private var failureImage: some View { + private func failureImage() -> some View { ZStack { Rectangle() .foregroundColor(Color(UIColor.darkGray)) @@ -42,27 +68,20 @@ struct ImageView: View { } var body: some View { - LazyImage(source: source) { state in - if let image = state.image { - image - } else if state.error != nil { - failureImage - } else { - #if os(tvOS) - ZStack { - Color.black.ignoresSafeArea() - - ProgressView() - } - #else - ZStack { - Color.gray.ignoresSafeArea() - - ProgressView() - } - #endif + if let u = currentURL { + LazyImage(source: u) { state in + if let image = state.image { + image + } else if state.error != nil { + failureImage().onAppear { sources.removeFirst() } + } else { + placeholderView() + } } + .pipeline(ImagePipeline(configuration: .withDataCache)) + .id(u) + } else { + failureImage() } - .pipeline(ImagePipeline(configuration: .withDataCache)) } } diff --git a/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift b/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift index 568ed84a..d21d2c71 100644 --- a/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift +++ b/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift @@ -24,10 +24,17 @@ struct CinematicNextUpCardView: View { ZStack(alignment: .bottomLeading) { if item.itemType == .episode { - ImageView(src: item.getSeriesBackdropImage(maxWidth: 350)) + ImageView(sources: [ + item.getSeriesThumbImage(maxWidth: 350), + item.getSeriesBackdropImage(maxWidth: 350), + ]) .frame(width: 350, height: 210) } else { - ImageView(src: item.getBackdropImage(maxWidth: 350)) + ImageView(sources: [ + item.getThumbImage(maxWidth: 350), + item.getBackdropImage(maxWidth: 350), + ], + bh: item.getBackdropImageBlurHash()) .frame(width: 350, height: 210) } diff --git a/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift b/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift index 8586b5b4..a129a4df 100644 --- a/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift +++ b/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift @@ -25,10 +25,17 @@ struct CinematicResumeCardView: View { ZStack(alignment: .bottom) { if item.itemType == .episode { - ImageView(src: item.getSeriesBackdropImage(maxWidth: 350)) + ImageView(sources: [ + item.getSeriesThumbImage(maxWidth: 350), + item.getSeriesBackdropImage(maxWidth: 350), + ]) .frame(width: 350, height: 210) } else { - ImageView(src: item.getBackdropImage(maxWidth: 350)) + ImageView(sources: [ + item.getThumbImage(maxWidth: 350), + item.getBackdropImage(maxWidth: 350), + ], + bh: item.getBackdropImageBlurHash()) .frame(width: 350, height: 210) } diff --git a/Swiftfin/Views/ContinueWatchingView.swift b/Swiftfin/Views/ContinueWatchingView.swift index cd32af82..1c33c743 100644 --- a/Swiftfin/Views/ContinueWatchingView.swift +++ b/Swiftfin/Views/ContinueWatchingView.swift @@ -27,9 +27,22 @@ struct ContinueWatchingView: View { VStack(alignment: .leading) { ZStack { - ImageView(src: item.getBackdropImage(maxWidth: 320), bh: item.getBackdropImageBlurHash()) - .frame(width: 320, height: 180) - .accessibilityIgnoresInvertColors() + Group { + if item.itemType == .episode { + ImageView(sources: [ + item.getSeriesThumbImage(maxWidth: 320), + item.getSeriesBackdropImage(maxWidth: 320), + ]) + .frame(width: 320, height: 180) + } else { + ImageView(sources: [ + item.getThumbImage(maxWidth: 320), + item.getBackdropImage(maxWidth: 320), + ]) + .frame(width: 320, height: 180) + } + } + .accessibilityIgnoresInvertColors() HStack { VStack { diff --git a/Swiftfin/Views/ItemView/ItemViewDetailsView.swift b/Swiftfin/Views/ItemView/ItemViewDetailsView.swift index a91c2058..ffd3847c 100644 --- a/Swiftfin/Views/ItemView/ItemViewDetailsView.swift +++ b/Swiftfin/Views/ItemView/ItemViewDetailsView.swift @@ -48,6 +48,8 @@ struct ItemViewDetailsView: View { L10n.file.text .font(.subheadline) Text(viewModel.selectedVideoPlayerViewModel?.filename ?? "--") + .lineLimit(nil) + .fixedSize(horizontal: false, vertical: true) .font(.subheadline) .foregroundColor(Color.secondary) } @@ -67,6 +69,8 @@ struct ItemViewDetailsView: View { Text(mediaItem.title) .font(.subheadline) Text(mediaItem.content) + .lineLimit(nil) + .fixedSize(horizontal: false, vertical: true) .font(.subheadline) .foregroundColor(Color.secondary) }