From 42ea9ad1cf455ccd271ccd7ae61846cf02afc971 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 18 Mar 2022 21:24:12 -0600 Subject: [PATCH 1/7] Update ImageView.swift --- Shared/Views/ImageView.swift | 154 +++++++++++++++++++++-------------- 1 file changed, 92 insertions(+), 62 deletions(-) diff --git a/Shared/Views/ImageView.swift b/Shared/Views/ImageView.swift index 2f06ae04..1ef3f305 100644 --- a/Shared/Views/ImageView.swift +++ b/Shared/Views/ImageView.swift @@ -6,70 +6,100 @@ // Copyright (c) 2022 Jellyfin & Jellyfin Contributors // +import Nuke import NukeUI import SwiftUI +import UIKit -// TODO: update multiple sources so that multiple blurhashes can be taken, clean up +// TODO: Fix 100+ inits -struct ImageView: View { - - @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.sources = [src] - self.blurhash = bh - self.failureInitials = failureInitials - } - - 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 placeholderView: some View { - Image(uiImage: UIImage(blurHash: blurhash, size: CGSize(width: 12, height: 12)) ?? - UIImage(blurHash: "001fC^", size: CGSize(width: 12, height: 12))!) - .resizable() - } - - @ViewBuilder - private func failureImage() -> some View { - ZStack { - Rectangle() - .foregroundColor(Color(UIColor.darkGray)) - - Text(failureInitials) - .font(.largeTitle) - .foregroundColor(.secondary) - .accessibilityHidden(true) - } - } - - var body: some View { - if let u = currentURL { - LazyImage(source: u) { state in - if let image = state.image { - image - } else if state.error != nil { - placeholderView.onAppear { sources.removeFirst() } - } else { - placeholderView - } - } - .pipeline(ImagePipeline(configuration: .withDataCache)) - .id(u) - } else { - failureImage() - } - } +struct ImageViewSource { + let url: URL? + let blurHash: String? + + init(url: URL? = nil, blurHash: String? = nil) { + self.url = url + self.blurHash = blurHash + } +} + +struct DefaultFailureView: View { + + var body: some View { + Color.secondary + } +} + +struct ImageView: View { + + @State + private var sources: [ImageViewSource] + private var currentURL: URL? { sources.first?.url } + private var currentBlurHash: String? { sources.first?.blurHash } + private var failureView: FailureView + + init(_ source: URL?, blurHash: String? = nil, @ViewBuilder failureView: () -> FailureView) { + let imageViewSource = ImageViewSource(url: source, blurHash: blurHash) + _sources = State(initialValue: [imageViewSource]) + self.failureView = failureView() + } + + init(_ source: ImageViewSource, @ViewBuilder failureView: () -> FailureView) { + _sources = State(initialValue: [source]) + self.failureView = failureView() + } + + init(_ sources: [ImageViewSource], @ViewBuilder failureView: () -> FailureView) { + _sources = State(initialValue: sources) + self.failureView = failureView() + } + + @ViewBuilder + private var placeholderView: some View { + if let currentBlurHash = currentBlurHash { + BlurHashView(blurHash: currentBlurHash) + .id(currentBlurHash) + } else { + Color.secondary + } + } + + var body: some View { + + if let currentURL = currentURL { + LazyImage(source: currentURL) { state in + if let image = state.image { + image + } else if state.error != nil { + placeholderView.onAppear { sources.removeFirst() } + } else { + placeholderView + } + } + .pipeline(ImagePipeline(configuration: .withDataCache)) + .id(currentURL) + } else { + failureView + } + } +} + +extension ImageView where FailureView == DefaultFailureView { + init(_ source: URL?, blurHash: String? = nil) { + let imageViewSource = ImageViewSource(url: source, blurHash: blurHash) + self.init(imageViewSource, failureView: { DefaultFailureView() }) + } + + init(_ source: ImageViewSource) { + self.init(source, failureView: { DefaultFailureView() }) + } + + init(_ sources: [ImageViewSource]) { + self.init(sources, failureView: { DefaultFailureView() }) + } + + init(sources: [URL]) { + let imageViewSources = sources.compactMap { ImageViewSource(url: $0, blurHash: nil) } + self.init(imageViewSources, failureView: { DefaultFailureView() }) + } } From 9e10395f80987e2481e712da5bef488bc0b28d59 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 18 Mar 2022 21:38:33 -0600 Subject: [PATCH 2/7] update inits --- Shared/Views/BlurHashView.swift | 61 +++++++++++++++++++ Shared/Views/LiveTVChannelItemElement.swift | 2 +- .../EpisodesRowView/EpisodesRowCard.swift | 4 +- .../CinematicNextUpCardView.swift | 2 +- .../CinematicResumeCardView.swift | 2 +- .../Components/LandscapeItemElement.swift | 4 +- .../Components/PortraitItemElement.swift | 4 +- .../Components/PortraitItemsRowView.swift | 2 +- .../Components/PublicUserButton.swift | 2 +- .../ContinueWatchingCard.swift | 4 +- .../CinematicCollectionItemView.swift | 4 +- .../CinematicEpisodeItemView.swift | 4 +- .../CinematicItemAboutView.swift | 2 +- .../CinematicMovieItemView.swift | 4 +- .../CinematicSeasonItemView.swift | 2 +- .../CinematicSeriesItemView.swift | 2 +- .../CompactItemView/EpisodeItemView.swift | 2 +- .../CompactItemView/MovieItemView.swift | 2 +- .../CompactItemView/SeasonItemView.swift | 2 +- .../CompactItemView/SeriesItemView.swift | 2 +- Swiftfin tvOS/Views/LatestMediaView.swift | 2 +- .../Views/NextUpView/NextUpCard.swift | 4 +- .../Overlays/SmallMenuOverlay.swift | 2 +- Swiftfin.xcodeproj/project.pbxproj | 6 ++ .../xcshareddata/swiftpm/Package.resolved | 10 +-- .../EpisodesRowView/EpisodeRowCard.swift | 4 +- Swiftfin/Components/PortraitHStackView.swift | 4 +- Swiftfin/Components/PortraitItemButton.swift | 4 +- .../Landscape/ItemLandscapeMainView.swift | 8 +-- .../ItemPortraitHeaderOverlayView.swift | 4 +- .../Portrait/ItemPortraitMainView.swift | 4 +- Swiftfin/Views/LibraryListView.swift | 2 +- .../VLCPlayerChapterOverlayView.swift | 2 +- 33 files changed, 118 insertions(+), 51 deletions(-) create mode 100644 Shared/Views/BlurHashView.swift diff --git a/Shared/Views/BlurHashView.swift b/Shared/Views/BlurHashView.swift new file mode 100644 index 00000000..9e8d3805 --- /dev/null +++ b/Shared/Views/BlurHashView.swift @@ -0,0 +1,61 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2022 Jellyfin & Jellyfin Contributors +// + +import SwiftUI +import UIKit + +struct BlurHashView: UIViewRepresentable { + + let blurHash: String + + func makeUIView(context: Context) -> UIBlurHashView { + return UIBlurHashView(blurHash) + } + + func updateUIView(_ uiView: UIBlurHashView, context: Context) {} +} + +class UIBlurHashView: UIView { + + private let imageView: UIImageView + + init(_ blurHash: String) { + let imageView = UIImageView() + imageView.translatesAutoresizingMaskIntoConstraints = false + self.imageView = imageView + + super.init(frame: .zero) + + computeBlurHashImageAsync(blurHash: blurHash) { blurImage in + DispatchQueue.main.async { + self.imageView.image = blurImage + self.imageView.setNeedsDisplay() + } + } + + addSubview(imageView) + + NSLayoutConstraint.activate([ + imageView.topAnchor.constraint(equalTo: topAnchor), + imageView.bottomAnchor.constraint(equalTo: bottomAnchor), + imageView.leftAnchor.constraint(equalTo: leftAnchor), + imageView.rightAnchor.constraint(equalTo: rightAnchor), + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func computeBlurHashImageAsync(blurHash: String, _ completion: @escaping (UIImage?) -> Void) { + DispatchQueue.global(qos: .utility).async { + let image = UIImage(blurHash: blurHash, size: .Circle(radius: 12)) + completion(image) + } + } +} diff --git a/Shared/Views/LiveTVChannelItemElement.swift b/Shared/Views/LiveTVChannelItemElement.swift index f9472a50..f8671a25 100644 --- a/Shared/Views/LiveTVChannelItemElement.swift +++ b/Shared/Views/LiveTVChannelItemElement.swift @@ -29,7 +29,7 @@ struct LiveTVChannelItemElement: View { .font(.footnote) .frame(alignment: .trailing) }.frame(alignment: .top) - ImageView(src: channel.getPrimaryImage(maxWidth: 125)) + ImageView(channel.getPrimaryImage(maxWidth: 125)) .frame(width: 125, alignment: .center) .offset(x: 0, y: -32) Text(channel.name ?? "?") diff --git a/Swiftfin tvOS/Components/EpisodesRowView/EpisodesRowCard.swift b/Swiftfin tvOS/Components/EpisodesRowView/EpisodesRowCard.swift index 0f27d13d..5d00f1e2 100644 --- a/Swiftfin tvOS/Components/EpisodesRowView/EpisodesRowCard.swift +++ b/Swiftfin tvOS/Components/EpisodesRowView/EpisodesRowCard.swift @@ -21,8 +21,8 @@ struct EpisodeRowCard: View { Button { itemRouter.route(to: \.item, episode) } label: { - ImageView(src: episode.getBackdropImage(maxWidth: 550), - bh: episode.getBackdropImageBlurHash()) + ImageView(episode.getBackdropImage(maxWidth: 550), + blurHash: episode.getBackdropImageBlurHash()) .mask(Rectangle().frame(width: 550, height: 308)) .frame(width: 550, height: 308) } diff --git a/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift b/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift index d21d2c71..c31a89f9 100644 --- a/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift +++ b/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift @@ -34,7 +34,7 @@ struct CinematicNextUpCardView: View { item.getThumbImage(maxWidth: 350), item.getBackdropImage(maxWidth: 350), ], - bh: item.getBackdropImageBlurHash()) + blurHash: item.getBackdropImageBlurHash()) .frame(width: 350, height: 210) } diff --git a/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift b/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift index a129a4df..21914da3 100644 --- a/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift +++ b/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift @@ -35,7 +35,7 @@ struct CinematicResumeCardView: View { item.getThumbImage(maxWidth: 350), item.getBackdropImage(maxWidth: 350), ], - bh: item.getBackdropImageBlurHash()) + blurHash: item.getBackdropImageBlurHash()) .frame(width: 350, height: 210) } diff --git a/Swiftfin tvOS/Components/LandscapeItemElement.swift b/Swiftfin tvOS/Components/LandscapeItemElement.swift index efae06e9..a5217b96 100644 --- a/Swiftfin tvOS/Components/LandscapeItemElement.swift +++ b/Swiftfin tvOS/Components/LandscapeItemElement.swift @@ -46,9 +46,9 @@ struct LandscapeItemElement: View { var body: some View { VStack { - ImageView(src: item.type == "Episode" && !(inSeasonView ?? false) ? item.getSeriesBackdropImage(maxWidth: 445) : item + ImageView(item.type == "Episode" && !(inSeasonView ?? false) ? item.getSeriesBackdropImage(maxWidth: 445) : item .getBackdropImage(maxWidth: 445), - bh: item.type == "Episode" ? item.getSeriesBackdropImageBlurHash() : item.getBackdropImageBlurHash()) + blurHash: item.type == "Episode" ? item.getSeriesBackdropImageBlurHash() : item.getBackdropImageBlurHash()) .frame(width: 445, height: 250) .cornerRadius(10) .ignoresSafeArea() diff --git a/Swiftfin tvOS/Components/PortraitItemElement.swift b/Swiftfin tvOS/Components/PortraitItemElement.swift index 044ca526..1f7d6323 100644 --- a/Swiftfin tvOS/Components/PortraitItemElement.swift +++ b/Swiftfin tvOS/Components/PortraitItemElement.swift @@ -21,8 +21,8 @@ struct PortraitItemElement: View { var body: some View { VStack { - ImageView(src: item.type == "Episode" ? item.getSeriesPrimaryImage(maxWidth: 200) : item.getPrimaryImage(maxWidth: 200), - bh: item.type == "Episode" ? item.getSeriesPrimaryImageBlurHash() : item.getPrimaryImageBlurHash()) + ImageView(item.type == "Episode" ? item.getSeriesPrimaryImage(maxWidth: 200) : item.getPrimaryImage(maxWidth: 200), + blurHash: item.type == "Episode" ? item.getSeriesPrimaryImageBlurHash() : item.getPrimaryImageBlurHash()) .frame(width: 200, height: 300) .cornerRadius(10) .shadow(radius: focused ? 10.0 : 0) diff --git a/Swiftfin tvOS/Components/PortraitItemsRowView.swift b/Swiftfin tvOS/Components/PortraitItemsRowView.swift index 6d0c43d7..a3e89660 100644 --- a/Swiftfin tvOS/Components/PortraitItemsRowView.swift +++ b/Swiftfin tvOS/Components/PortraitItemsRowView.swift @@ -45,7 +45,7 @@ struct PortraitItemsRowView: View { Button { selectedAction(item) } label: { - ImageView(src: item.portraitHeaderViewURL(maxWidth: 257)) + ImageView(item.portraitHeaderViewURL(maxWidth: 257)) .frame(width: 257, height: 380) } .frame(height: 380) diff --git a/Swiftfin tvOS/Components/PublicUserButton.swift b/Swiftfin tvOS/Components/PublicUserButton.swift index 4b07dfe9..1f4868d9 100644 --- a/Swiftfin tvOS/Components/PublicUserButton.swift +++ b/Swiftfin tvOS/Components/PublicUserButton.swift @@ -20,7 +20,7 @@ struct PublicUserButton: View { var body: some View { VStack { if publicUser.primaryImageTag != nil { - ImageView(src: URL(string: "\(SessionManager.main.currentLogin.server.currentURI)/Users/\(publicUser.id ?? "")/Images/Primary?width=500&quality=80&tag=\(publicUser.primaryImageTag!)")!) + ImageView(URL(string: "\(SessionManager.main.currentLogin.server.currentURI)/Users/\(publicUser.id ?? "")/Images/Primary?width=500&quality=80&tag=\(publicUser.primaryImageTag!)")!) .frame(width: 250, height: 250) .cornerRadius(125.0) } else { diff --git a/Swiftfin tvOS/Views/ContinueWatchingView/ContinueWatchingCard.swift b/Swiftfin tvOS/Views/ContinueWatchingView/ContinueWatchingCard.swift index daac3470..2b7cf408 100644 --- a/Swiftfin tvOS/Views/ContinueWatchingView/ContinueWatchingCard.swift +++ b/Swiftfin tvOS/Views/ContinueWatchingView/ContinueWatchingCard.swift @@ -23,10 +23,10 @@ struct ContinueWatchingCard: View { ZStack(alignment: .bottom) { if item.itemType == .episode { - ImageView(src: item.getSeriesBackdropImage(maxWidth: 500)) + ImageView(item.getSeriesBackdropImage(maxWidth: 500)) .frame(width: 500, height: 281.25) } else { - ImageView(src: item.getBackdropImage(maxWidth: 500)) + ImageView(item.getBackdropImage(maxWidth: 500)) .frame(width: 500, height: 281.25) } diff --git a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicCollectionItemView.swift b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicCollectionItemView.swift index c17bfdf9..d82fb6a7 100644 --- a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicCollectionItemView.swift +++ b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicCollectionItemView.swift @@ -24,8 +24,8 @@ struct CinematicCollectionItemView: View { var body: some View { ZStack { - ImageView(src: viewModel.item.getBackdropImage(maxWidth: 1920), - bh: viewModel.item.getBackdropImageBlurHash()) + ImageView(viewModel.item.getBackdropImage(maxWidth: 1920), + blurHash: viewModel.item.getBackdropImageBlurHash()) .ignoresSafeArea() ScrollView { diff --git a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicEpisodeItemView.swift b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicEpisodeItemView.swift index 8ce92dd3..13d1360f 100644 --- a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicEpisodeItemView.swift +++ b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicEpisodeItemView.swift @@ -32,8 +32,8 @@ struct CinematicEpisodeItemView: View { var body: some View { ZStack { - ImageView(src: viewModel.item.getBackdropImage(maxWidth: 1920), - bh: viewModel.item.getBackdropImageBlurHash()) + ImageView(viewModel.item.getBackdropImage(maxWidth: 1920), + blurHash: viewModel.item.getBackdropImageBlurHash()) .frame(height: UIScreen.main.bounds.height - 10) .ignoresSafeArea() diff --git a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemAboutView.swift b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemAboutView.swift index 6e6230b3..151b0e20 100644 --- a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemAboutView.swift +++ b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemAboutView.swift @@ -17,7 +17,7 @@ struct CinematicItemAboutView: View { var body: some View { HStack(alignment: .top, spacing: 10) { - ImageView(src: viewModel.item.portraitHeaderViewURL(maxWidth: 257)) + ImageView(viewModel.item.portraitHeaderViewURL(maxWidth: 257)) .portraitPoster(width: 257) ZStack(alignment: .topLeading) { diff --git a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicMovieItemView.swift b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicMovieItemView.swift index b8982cb0..efd04c5a 100644 --- a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicMovieItemView.swift +++ b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicMovieItemView.swift @@ -24,8 +24,8 @@ struct CinematicMovieItemView: View { var body: some View { ZStack { - ImageView(src: viewModel.item.getBackdropImage(maxWidth: 1920), - bh: viewModel.item.getBackdropImageBlurHash()) + ImageView(viewModel.item.getBackdropImage(maxWidth: 1920), + blurHash: viewModel.item.getBackdropImageBlurHash()) .ignoresSafeArea() ScrollView { diff --git a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicSeasonItemView.swift b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicSeasonItemView.swift index e6e18eeb..aec16c2a 100644 --- a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicSeasonItemView.swift +++ b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicSeasonItemView.swift @@ -23,7 +23,7 @@ struct CinematicSeasonItemView: View { var body: some View { ZStack { - ImageView(src: viewModel.item.getBackdropImage(maxWidth: 1920), bh: viewModel.item.getBackdropImageBlurHash()) + ImageView(viewModel.item.getBackdropImage(maxWidth: 1920), blurHash: viewModel.item.getBackdropImageBlurHash()) .ignoresSafeArea() ScrollView { diff --git a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicSeriesItemView.swift b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicSeriesItemView.swift index 0d2fb619..06930f7a 100644 --- a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicSeriesItemView.swift +++ b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicSeriesItemView.swift @@ -23,7 +23,7 @@ struct CinematicSeriesItemView: View { var body: some View { ZStack { - ImageView(src: viewModel.item.getBackdropImage(maxWidth: 1920), bh: viewModel.item.getBackdropImageBlurHash()) + ImageView(viewModel.item.getBackdropImage(maxWidth: 1920), blurHash: viewModel.item.getBackdropImageBlurHash()) .ignoresSafeArea() ScrollView { diff --git a/Swiftfin tvOS/Views/ItemView/CompactItemView/EpisodeItemView.swift b/Swiftfin tvOS/Views/ItemView/CompactItemView/EpisodeItemView.swift index b6fc94b3..e50c2698 100644 --- a/Swiftfin tvOS/Views/ItemView/CompactItemView/EpisodeItemView.swift +++ b/Swiftfin tvOS/Views/ItemView/CompactItemView/EpisodeItemView.swift @@ -45,7 +45,7 @@ struct EpisodeItemView: View { var body: some View { ZStack { - ImageView(src: viewModel.item.getBackdropImage(maxWidth: 1920), bh: viewModel.item.getBackdropImageBlurHash()) + ImageView(viewModel.item.getBackdropImage(maxWidth: 1920), blurHash: viewModel.item.getBackdropImageBlurHash()) .opacity(0.4) .ignoresSafeArea() LazyVStack(alignment: .leading) { diff --git a/Swiftfin tvOS/Views/ItemView/CompactItemView/MovieItemView.swift b/Swiftfin tvOS/Views/ItemView/CompactItemView/MovieItemView.swift index cb19aba6..e52498e5 100644 --- a/Swiftfin tvOS/Views/ItemView/CompactItemView/MovieItemView.swift +++ b/Swiftfin tvOS/Views/ItemView/CompactItemView/MovieItemView.swift @@ -50,7 +50,7 @@ struct MovieItemView: View { var body: some View { ZStack { - ImageView(src: viewModel.item.getBackdropImage(maxWidth: 1920), bh: viewModel.item.getBackdropImageBlurHash()) + ImageView(viewModel.item.getBackdropImage(maxWidth: 1920), blurHash: viewModel.item.getBackdropImageBlurHash()) .opacity(0.4) .ignoresSafeArea() ScrollView { diff --git a/Swiftfin tvOS/Views/ItemView/CompactItemView/SeasonItemView.swift b/Swiftfin tvOS/Views/ItemView/CompactItemView/SeasonItemView.swift index 33a40736..b01aba83 100644 --- a/Swiftfin tvOS/Views/ItemView/CompactItemView/SeasonItemView.swift +++ b/Swiftfin tvOS/Views/ItemView/CompactItemView/SeasonItemView.swift @@ -25,7 +25,7 @@ struct SeasonItemView: View { var body: some View { ZStack { - ImageView(src: viewModel.item.getSeriesBackdropImage(maxWidth: 1920), bh: viewModel.item.getSeriesBackdropImageBlurHash()) + ImageView(viewModel.item.getSeriesBackdropImage(maxWidth: 1920), blurHash: viewModel.item.getSeriesBackdropImageBlurHash()) .opacity(0.4) .ignoresSafeArea() ScrollView { diff --git a/Swiftfin tvOS/Views/ItemView/CompactItemView/SeriesItemView.swift b/Swiftfin tvOS/Views/ItemView/CompactItemView/SeriesItemView.swift index 53ac05bb..b9752602 100644 --- a/Swiftfin tvOS/Views/ItemView/CompactItemView/SeriesItemView.swift +++ b/Swiftfin tvOS/Views/ItemView/CompactItemView/SeriesItemView.swift @@ -53,7 +53,7 @@ struct SeriesItemView: View { var body: some View { ZStack { - ImageView(src: viewModel.item.getBackdropImage(maxWidth: 1920), bh: viewModel.item.getBackdropImageBlurHash()) + ImageView(viewModel.item.getBackdropImage(maxWidth: 1920), blurHash: viewModel.item.getBackdropImageBlurHash()) .opacity(0.4) .ignoresSafeArea() ScrollView { diff --git a/Swiftfin tvOS/Views/LatestMediaView.swift b/Swiftfin tvOS/Views/LatestMediaView.swift index c2cc6f87..07a3af34 100644 --- a/Swiftfin tvOS/Views/LatestMediaView.swift +++ b/Swiftfin tvOS/Views/LatestMediaView.swift @@ -34,7 +34,7 @@ struct LatestMediaView: View { Button { homeRouter.route(to: \.modalItem, item) } label: { - ImageView(src: item.portraitHeaderViewURL(maxWidth: 257)) + ImageView(item.portraitHeaderViewURL(maxWidth: 257)) .frame(width: 257, height: 380) } .frame(height: 380) diff --git a/Swiftfin tvOS/Views/NextUpView/NextUpCard.swift b/Swiftfin tvOS/Views/NextUpView/NextUpCard.swift index 8e3e9083..27ffcc68 100644 --- a/Swiftfin tvOS/Views/NextUpView/NextUpCard.swift +++ b/Swiftfin tvOS/Views/NextUpView/NextUpCard.swift @@ -21,10 +21,10 @@ struct NextUpCard: View { homeRouter.route(to: \.modalItem, item) } label: { if item.itemType == .episode { - ImageView(src: item.getSeriesBackdropImage(maxWidth: 500)) + ImageView(item.getSeriesBackdropImage(maxWidth: 500)) .frame(width: 500, height: 281.25) } else { - ImageView(src: item.getBackdropImage(maxWidth: 500)) + ImageView(item.getBackdropImage(maxWidth: 500)) .frame(width: 500, height: 281.25) } } diff --git a/Swiftfin tvOS/Views/VideoPlayer/Overlays/SmallMenuOverlay.swift b/Swiftfin tvOS/Views/VideoPlayer/Overlays/SmallMenuOverlay.swift index 010cf779..78af9836 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/Overlays/SmallMenuOverlay.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/Overlays/SmallMenuOverlay.swift @@ -329,7 +329,7 @@ struct SmallMediaStreamSelectionView: View { Button { viewModel.playerOverlayDelegate?.didSelectChapter(viewModel.chapters[chapterIndex]) } label: { - ImageView(src: chapterImages[chapterIndex]) + ImageView(chapterImages[chapterIndex]) .cornerRadius(10) .frame(width: 350, height: 210) } diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index cb21589b..4975c6ff 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -292,6 +292,8 @@ E103A6A5278A82E500820EC7 /* HomeCinematicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E103A6A4278A82E500820EC7 /* HomeCinematicView.swift */; }; E103A6A7278AB6D700820EC7 /* CinematicResumeCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E103A6A6278AB6D700820EC7 /* CinematicResumeCardView.swift */; }; E103A6A9278AB6FF00820EC7 /* CinematicNextUpCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E103A6A8278AB6FF00820EC7 /* CinematicNextUpCardView.swift */; }; + E1047E2027E584AF00CB0D4A /* BlurHashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1047E1F27E584AF00CB0D4A /* BlurHashView.swift */; }; + E1047E2127E584AF00CB0D4A /* BlurHashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1047E1F27E584AF00CB0D4A /* BlurHashView.swift */; }; E107BB9327880A8F00354E07 /* CollectionItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E107BB9227880A8F00354E07 /* CollectionItemViewModel.swift */; }; E107BB9427880A8F00354E07 /* CollectionItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E107BB9227880A8F00354E07 /* CollectionItemViewModel.swift */; }; E107BB972788104100354E07 /* CinematicCollectionItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E107BB952788104100354E07 /* CinematicCollectionItemView.swift */; }; @@ -760,6 +762,7 @@ E103A6A4278A82E500820EC7 /* HomeCinematicView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCinematicView.swift; sourceTree = ""; }; E103A6A6278AB6D700820EC7 /* CinematicResumeCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicResumeCardView.swift; sourceTree = ""; }; E103A6A8278AB6FF00820EC7 /* CinematicNextUpCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicNextUpCardView.swift; sourceTree = ""; }; + E1047E1F27E584AF00CB0D4A /* BlurHashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashView.swift; sourceTree = ""; }; E107BB9227880A8F00354E07 /* CollectionItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionItemViewModel.swift; sourceTree = ""; }; E107BB952788104100354E07 /* CinematicCollectionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicCollectionItemView.swift; sourceTree = ""; }; E10C0940278B8DAB009DBF93 /* PortraitItemSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortraitItemSize.swift; sourceTree = ""; }; @@ -1748,6 +1751,7 @@ E1AD105326D96F5A003E4A08 /* Views */ = { isa = PBXGroup; children = ( + E1047E1F27E584AF00CB0D4A /* BlurHashView.swift */, 531AC8BE26750DE20091C7EB /* ImageView.swift */, 621338B22660A07800A81A2A /* LazyView.swift */, C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */, @@ -2243,6 +2247,7 @@ C4BE078C272844AF003F4AD1 /* LiveTVChannelsView.swift in Sources */, E1D4BF852719D25A00A11E64 /* TrackLanguage.swift in Sources */, 53272532268BF09D0035FBF1 /* MediaViewActionButton.swift in Sources */, + E1047E2127E584AF00CB0D4A /* BlurHashView.swift in Sources */, 531690F0267ABF72005D8AB9 /* NextUpView.swift in Sources */, E193D53427193F7F00900D82 /* HomeCoordinator.swift in Sources */, E193D5502719430400900D82 /* ServerDetailView.swift in Sources */, @@ -2390,6 +2395,7 @@ E1EBCB42278BD174009FE6E9 /* TruncatedTextView.swift in Sources */, 62133890265F83A900A81A2A /* LibraryListView.swift in Sources */, 62C29EA326D1030F00C1D2E7 /* ConnectToServerCoodinator.swift in Sources */, + E1047E2027E584AF00CB0D4A /* BlurHashView.swift in Sources */, 62E632DA267D2BC40063E547 /* LatestMediaViewModel.swift in Sources */, E1AD105C26D9ABDD003E4A08 /* PillHStackView.swift in Sources */, 625CB56F2678C23300530A6E /* HomeView.swift in Sources */, diff --git a/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index fac5a82a..c3e87965 100644 --- a/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/kaishin/Gifu", "state" : { - "revision" : "51f2eab32903e336f590c013267cfa4d7f8b06c4", - "version" : "3.3.1" + "revision" : "0ffe24744cc3d82ab9edece53670d0352c6d5507", + "version" : "3.3.0" } }, { @@ -66,7 +66,7 @@ { "identity" : "nuke", "kind" : "remoteSourceControl", - "location" : "https://github.com/kean/Nuke", + "location" : "https://github.com/kean/Nuke.git", "state" : { "revision" : "78fa963b8491fc520791d8c2a509f1b8593d8aae", "version" : "10.7.1" @@ -140,8 +140,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/siteline/SwiftUI-Introspect", "state" : { - "revision" : "f2616860a41f9d9932da412a8978fec79c06fe24", - "version" : "0.1.4" + "revision" : "2e09be8af614401bc9f87d40093ec19ce56ccaf2", + "version" : "0.1.3" } }, { diff --git a/Swiftfin/Components/EpisodesRowView/EpisodeRowCard.swift b/Swiftfin/Components/EpisodesRowView/EpisodeRowCard.swift index ef5de88f..f18e2604 100644 --- a/Swiftfin/Components/EpisodesRowView/EpisodeRowCard.swift +++ b/Swiftfin/Components/EpisodesRowView/EpisodeRowCard.swift @@ -23,8 +23,8 @@ struct EpisodeRowCard: View { HStack(alignment: .top) { VStack(alignment: .leading) { - ImageView(src: episode.getBackdropImage(maxWidth: 200), - bh: episode.getBackdropImageBlurHash()) + ImageView(episode.getBackdropImage(maxWidth: 200), + blurHash: episode.getBackdropImageBlurHash()) .mask(Rectangle().frame(width: 200, height: 112).cornerRadius(10)) .frame(width: 200, height: 112) .overlay { diff --git a/Swiftfin/Components/PortraitHStackView.swift b/Swiftfin/Components/PortraitHStackView.swift index cbfb01f5..de120c3c 100644 --- a/Swiftfin/Components/PortraitHStackView.swift +++ b/Swiftfin/Components/PortraitHStackView.swift @@ -43,8 +43,8 @@ struct PortraitImageHStackView: View { selectedAction(item) } label: { VStack(alignment: horizontalAlignment) { - ImageView(src: item.imageURLConstructor(maxWidth: Int(maxWidth)), - bh: item.blurHash, + ImageView(item.imageURLConstructor(maxWidth: Int(maxWidth)), + blurHash: item.blurHash, failureInitials: item.failureInitials) .portraitPoster(width: maxWidth) .shadow(radius: 4, y: 2) diff --git a/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift b/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift index 8ef58c0e..14001bcc 100644 --- a/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift +++ b/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift @@ -24,8 +24,8 @@ struct ItemLandscapeMainView: View { // MARK: Sidebar Image VStack { - ImageView(src: viewModel.item.portraitHeaderViewURL(maxWidth: 130), - bh: viewModel.item.getPrimaryImageBlurHash()) + ImageView(viewModel.item.portraitHeaderViewURL(maxWidth: 130), + blurHash: viewModel.item.getPrimaryImageBlurHash()) .frame(width: 130, height: 195) .cornerRadius(10) .accessibilityIgnoresInvertColors() @@ -95,8 +95,8 @@ struct ItemLandscapeMainView: View { ZStack { // MARK: Backdrop - ImageView(src: viewModel.item.getBackdropImage(maxWidth: 200), - bh: viewModel.item.getBackdropImageBlurHash()) + ImageView(viewModel.item.getBackdropImage(maxWidth: 200), + blurHash: viewModel.item.getBackdropImageBlurHash()) .opacity(0.3) .edgesIgnoringSafeArea(.all) .blur(radius: 8) diff --git a/Swiftfin/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift b/Swiftfin/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift index 18eddbf5..d9cba8eb 100644 --- a/Swiftfin/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift +++ b/Swiftfin/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift @@ -24,8 +24,8 @@ struct PortraitHeaderOverlayView: View { // MARK: Portrait Image - ImageView(src: viewModel.item.portraitHeaderViewURL(maxWidth: 130), - bh: viewModel.item.getPrimaryImageBlurHash()) + ImageView(viewModel.item.portraitHeaderViewURL(maxWidth: 130), + blurHash: viewModel.item.getPrimaryImageBlurHash()) .portraitPoster(width: 130) .accessibilityIgnoresInvertColors() diff --git a/Swiftfin/Views/ItemView/Portrait/ItemPortraitMainView.swift b/Swiftfin/Views/ItemView/Portrait/ItemPortraitMainView.swift index fbb4692b..da47995c 100644 --- a/Swiftfin/Views/ItemView/Portrait/ItemPortraitMainView.swift +++ b/Swiftfin/Views/ItemView/Portrait/ItemPortraitMainView.swift @@ -19,8 +19,8 @@ struct ItemPortraitMainView: View { // MARK: portraitHeaderView var portraitHeaderView: some View { - ImageView(src: viewModel.item.getBackdropImage(maxWidth: Int(UIScreen.main.bounds.width)), - bh: viewModel.item.getBackdropImageBlurHash()) + ImageView(viewModel.item.getBackdropImage(maxWidth: Int(UIScreen.main.bounds.width)), + blurHash: viewModel.item.getBackdropImageBlurHash()) .opacity(0.4) .blur(radius: 2.0) .accessibilityIgnoresInvertColors() diff --git a/Swiftfin/Views/LibraryListView.swift b/Swiftfin/Views/LibraryListView.swift index a7ebe468..eedbe6c1 100644 --- a/Swiftfin/Views/LibraryListView.swift +++ b/Swiftfin/Views/LibraryListView.swift @@ -54,7 +54,7 @@ struct LibraryListView: View { title: library.name ?? "")) } label: { ZStack { - ImageView(src: library.getPrimaryImage(maxWidth: 500), bh: library.getPrimaryImageBlurHash()) + ImageView(library.getPrimaryImage(maxWidth: 500), blurHash: library.getPrimaryImageBlurHash()) .opacity(0.4) .accessibilityIgnoresInvertColors() HStack { diff --git a/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerChapterOverlayView.swift b/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerChapterOverlayView.swift index f0887f6b..0ffc67e6 100644 --- a/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerChapterOverlayView.swift +++ b/Swiftfin/Views/VideoPlayer/Overlays/VLCPlayerChapterOverlayView.swift @@ -48,7 +48,7 @@ struct VLCPlayerChapterOverlayView: View { Button { viewModel.playerOverlayDelegate?.didSelectChapter(viewModel.chapters[chapterIndex]) } label: { - ImageView(src: chapterImages[chapterIndex]) + ImageView(chapterImages[chapterIndex]) .cornerRadius(10) .frame(width: 150, height: 100) .overlay { From 24dbb78fcfbda7f8495d5d03936407f802093c84 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 18 Mar 2022 21:45:52 -0600 Subject: [PATCH 3/7] fix failureinitials --- Shared/Views/InitialFailureView.swift | 30 +++++++++++++++++++ Swiftfin.xcodeproj/project.pbxproj | 6 ++++ Swiftfin/Components/PortraitHStackView.swift | 4 ++- Swiftfin/Components/PortraitItemButton.swift | 4 ++- .../VideoPlayer/VLCPlayerViewController.swift | 4 +-- 5 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 Shared/Views/InitialFailureView.swift diff --git a/Shared/Views/InitialFailureView.swift b/Shared/Views/InitialFailureView.swift new file mode 100644 index 00000000..9af50ae6 --- /dev/null +++ b/Shared/Views/InitialFailureView.swift @@ -0,0 +1,30 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2022 Jellyfin & Jellyfin Contributors +// + +import SwiftUI + +struct InitialFailureView: View { + + let initials: String + + init(_ initials: String) { + self.initials = initials + } + + var body: some View { + ZStack { + Rectangle() + .foregroundColor(Color(UIColor.darkGray)) + + Text(initials) + .font(.largeTitle) + .foregroundColor(.secondary) + .accessibilityHidden(true) + } + } +} diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 4975c6ff..52af3a75 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -294,6 +294,8 @@ E103A6A9278AB6FF00820EC7 /* CinematicNextUpCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E103A6A8278AB6FF00820EC7 /* CinematicNextUpCardView.swift */; }; E1047E2027E584AF00CB0D4A /* BlurHashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1047E1F27E584AF00CB0D4A /* BlurHashView.swift */; }; E1047E2127E584AF00CB0D4A /* BlurHashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1047E1F27E584AF00CB0D4A /* BlurHashView.swift */; }; + E1047E2327E5880000CB0D4A /* InitialFailureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1047E2227E5880000CB0D4A /* InitialFailureView.swift */; }; + E1047E2427E5880000CB0D4A /* InitialFailureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1047E2227E5880000CB0D4A /* InitialFailureView.swift */; }; E107BB9327880A8F00354E07 /* CollectionItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E107BB9227880A8F00354E07 /* CollectionItemViewModel.swift */; }; E107BB9427880A8F00354E07 /* CollectionItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E107BB9227880A8F00354E07 /* CollectionItemViewModel.swift */; }; E107BB972788104100354E07 /* CinematicCollectionItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E107BB952788104100354E07 /* CinematicCollectionItemView.swift */; }; @@ -763,6 +765,7 @@ E103A6A6278AB6D700820EC7 /* CinematicResumeCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicResumeCardView.swift; sourceTree = ""; }; E103A6A8278AB6FF00820EC7 /* CinematicNextUpCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicNextUpCardView.swift; sourceTree = ""; }; E1047E1F27E584AF00CB0D4A /* BlurHashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashView.swift; sourceTree = ""; }; + E1047E2227E5880000CB0D4A /* InitialFailureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitialFailureView.swift; sourceTree = ""; }; E107BB9227880A8F00354E07 /* CollectionItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionItemViewModel.swift; sourceTree = ""; }; E107BB952788104100354E07 /* CinematicCollectionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicCollectionItemView.swift; sourceTree = ""; }; E10C0940278B8DAB009DBF93 /* PortraitItemSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortraitItemSize.swift; sourceTree = ""; }; @@ -1753,6 +1756,7 @@ children = ( E1047E1F27E584AF00CB0D4A /* BlurHashView.swift */, 531AC8BE26750DE20091C7EB /* ImageView.swift */, + E1047E2227E5880000CB0D4A /* InitialFailureView.swift */, 621338B22660A07800A81A2A /* LazyView.swift */, C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */, 53E4E648263F725B00F67C6B /* MultiSelectorView.swift */, @@ -2263,6 +2267,7 @@ 62E632DD267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */, 536D3D81267BDFC60004248C /* PortraitItemElement.swift in Sources */, 5D1603FD278A40DB00D22B99 /* SubtitleSize.swift in Sources */, + E1047E2427E5880000CB0D4A /* InitialFailureView.swift in Sources */, E1AA33232782648000F6439C /* OverlaySliderColor.swift in Sources */, E103A6A7278AB6D700820EC7 /* CinematicResumeCardView.swift in Sources */, 62E1DCC4273CE19800C9AE76 /* URLExtensions.swift in Sources */, @@ -2399,6 +2404,7 @@ 62E632DA267D2BC40063E547 /* LatestMediaViewModel.swift in Sources */, E1AD105C26D9ABDD003E4A08 /* PillHStackView.swift in Sources */, 625CB56F2678C23300530A6E /* HomeView.swift in Sources */, + E1047E2327E5880000CB0D4A /* InitialFailureView.swift in Sources */, E1CEFBF527914C7700F60429 /* CustomizeViewsSettings.swift in Sources */, E1C812BC277A8E5D00918266 /* PlaybackSpeed.swift in Sources */, E1E5D5492783CDD700692DFE /* OverlaySettingsView.swift in Sources */, diff --git a/Swiftfin/Components/PortraitHStackView.swift b/Swiftfin/Components/PortraitHStackView.swift index de120c3c..e0f76706 100644 --- a/Swiftfin/Components/PortraitHStackView.swift +++ b/Swiftfin/Components/PortraitHStackView.swift @@ -45,7 +45,9 @@ struct PortraitImageHStackView: View { VStack(alignment: horizontalAlignment) { ImageView(item.imageURLConstructor(maxWidth: Int(maxWidth)), blurHash: item.blurHash, - failureInitials: item.failureInitials) + failureView: { + InitialFailureView(item.failureInitials) + }) .portraitPoster(width: maxWidth) .shadow(radius: 4, y: 2) .accessibilityIgnoresInvertColors() diff --git a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift index cd28bb00..9407d2b2 100644 --- a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift @@ -755,7 +755,7 @@ extension VLCPlayerViewController { extension VLCPlayerViewController: VLCMediaPlayerDelegate { // MARK: mediaPlayerStateChanged - func mediaPlayerStateChanged(_ aNotification: Notification!) { + func mediaPlayerStateChanged(_ aNotification: Notification) { // Don't show buffering if paused, usually here while scrubbing if vlcMediaPlayer.state == .buffering, viewModel.playerState == .paused { return @@ -774,7 +774,7 @@ extension VLCPlayerViewController: VLCMediaPlayerDelegate { // MARK: mediaPlayerTimeChanged - func mediaPlayerTimeChanged(_ aNotification: Notification!) { + func mediaPlayerTimeChanged(_ aNotification: Notification) { if !viewModel.sliderIsScrubbing { viewModel.sliderPercentage = Double(vlcMediaPlayer.position) } From 2d5f1a2c1980e87d76d77a9699c7e8b9cc4b6fe6 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 18 Mar 2022 21:59:25 -0600 Subject: [PATCH 4/7] update notifications --- .../MainCoordinator/iOSMainCoordinator.swift | 14 ++-- Shared/Singleton/SessionManager.swift | 8 +- .../SwiftfinNotificationCenter.swift | 75 +++++++++++++++---- Shared/ViewModels/HomeViewModel.swift | 5 +- .../ItemViewModel/ItemViewModel.swift | 9 +-- Shared/ViewModels/ServerDetailViewModel.swift | 3 +- Shared/ViewModels/ServerListViewModel.swift | 3 +- Shared/ViewModels/UserListViewModel.swift | 6 +- .../VideoPlayerViewModel.swift | 3 +- Swiftfin/AppURLHandler/AppURLHandler.swift | 2 +- 10 files changed, 80 insertions(+), 48 deletions(-) diff --git a/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift b/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift index 7e47fa65..d1690c2f 100644 --- a/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift +++ b/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift @@ -45,12 +45,10 @@ final class MainCoordinator: NavigationCoordinatable { barAppearance.tintColor = UIColor(Color.jellyfinPurple) // Notification setup for state - let nc = SwiftfinNotificationCenter.main - nc.addObserver(self, selector: #selector(didLogIn), name: SwiftfinNotificationCenter.Keys.didSignIn, object: nil) - nc.addObserver(self, selector: #selector(didLogOut), name: SwiftfinNotificationCenter.Keys.didSignOut, object: nil) - nc.addObserver(self, selector: #selector(processDeepLink), name: SwiftfinNotificationCenter.Keys.processDeepLink, object: nil) - nc.addObserver(self, selector: #selector(didChangeServerCurrentURI), - name: SwiftfinNotificationCenter.Keys.didChangeServerCurrentURI, object: nil) + Notifications[.didSignIn].subscribe(self, selector: #selector(didSignIn)) + Notifications[.didSignOut].subscribe(self, selector: #selector(didSignOut)) + Notifications[.processDeepLink].subscribe(self, selector: #selector(processDeepLink(_:))) + Notifications[.didChangeServerCurrentURI].subscribe(self, selector: #selector(didChangeServerCurrentURI(_:))) Defaults.publisher(.appAppearance) .sink { _ in @@ -60,13 +58,13 @@ final class MainCoordinator: NavigationCoordinatable { } @objc - func didLogIn() { + func didSignIn() { LogManager.shared.log.info("Received `didSignIn` from SwiftfinNotificationCenter.") root(\.mainTab) } @objc - func didLogOut() { + func didSignOut() { LogManager.shared.log.info("Received `didSignOut` from SwiftfinNotificationCenter.") root(\.serverList) } diff --git a/Shared/Singleton/SessionManager.swift b/Shared/Singleton/SessionManager.swift index abba3f1d..a6ddd435 100644 --- a/Shared/Singleton/SessionManager.swift +++ b/Shared/Singleton/SessionManager.swift @@ -240,7 +240,7 @@ final class SessionManager { Defaults[.lastServerUserID] = user.id currentLogin = (server: currentServer.state, user: currentUser.state) - SwiftfinNotificationCenter.main.post(name: SwiftfinNotificationCenter.Keys.didSignIn, object: nil) + Notifications[.didSignIn].post() }) .map { _, user, _ in user.state @@ -255,7 +255,7 @@ final class SessionManager { Defaults[.lastServerUserID] = user.id setAuthHeader(with: user.accessToken) currentLogin = (server: server, user: user) - SwiftfinNotificationCenter.main.post(name: SwiftfinNotificationCenter.Keys.didSignIn, object: nil) + Notifications[.didSignIn].post() } // MARK: logout @@ -265,7 +265,7 @@ final class SessionManager { JellyfinAPI.basePath = "" setAuthHeader(with: "") Defaults[.lastServerUserID] = nil - SwiftfinNotificationCenter.main.post(name: SwiftfinNotificationCenter.Keys.didSignOut, object: nil) + Notifications[.didSignOut].post() } // MARK: purge @@ -278,7 +278,7 @@ final class SessionManager { delete(server: server) } - SwiftfinNotificationCenter.main.post(name: SwiftfinNotificationCenter.Keys.didPurge, object: nil) + Notifications[.didPurge].post() } // MARK: delete user diff --git a/Shared/Singleton/SwiftfinNotificationCenter.swift b/Shared/Singleton/SwiftfinNotificationCenter.swift index cbf99bcd..61fcfbc9 100644 --- a/Shared/Singleton/SwiftfinNotificationCenter.swift +++ b/Shared/Singleton/SwiftfinNotificationCenter.swift @@ -8,20 +8,63 @@ import Foundation -enum SwiftfinNotificationCenter { - - static let main: NotificationCenter = { - NotificationCenter() - }() - - enum Keys { - static let didSignIn = Notification.Name("didSignIn") - static let didSignOut = Notification.Name("didSignOut") - static let processDeepLink = Notification.Name("processDeepLink") - static let didPurge = Notification.Name("didPurge") - static let didChangeServerCurrentURI = Notification.Name("didChangeCurrentLoginURI") - - // Send with an item id to check if current item for item views - static let didSendStopReport = Notification.Name("didSendStopReport") - } +class SwiftfinNotification { + + private let notificationName: Notification.Name + + fileprivate init(_ notificationName: Notification.Name) { + self.notificationName = notificationName + } + + func post(object: Any? = nil) { + Notifications.main.post(name: notificationName, object: object) + } + + func subscribe(_ observer: Any, selector: Selector) { + Notifications.main.addObserver(observer, selector: selector, name: notificationName, object: nil) + } + + func unsubscribe(_ observer: Any) { + Notifications.main.removeObserver(self, name: notificationName, object: nil) + } +} + +enum Notifications { + + static let main: NotificationCenter = { + NotificationCenter() + }() + + final class Key { + public typealias NotificationKey = Notifications.Key + + public let key: String + public let underlyingNotification: SwiftfinNotification + + public init(_ key: String) { + self.key = key + self.underlyingNotification = SwiftfinNotification(Notification.Name(key)) + } + } + + static subscript(key: Key) -> SwiftfinNotification { + return key.underlyingNotification + } + + static func unsubscribe(_ observer: Any) { + main.removeObserver(observer) + } +} + +extension Notifications.Key { + + static let didSignIn = NotificationKey("didSignIn") + static let didSignOut = NotificationKey("didSignOut") + static let processDeepLink = NotificationKey("processDeepLink") + static let didPurge = NotificationKey("didPurge") + static let didChangeServerCurrentURI = NotificationKey("didChangeCurrentLoginURI") + static let toggleOfflineMode = NotificationKey("toggleOfflineMode") + static let didDeleteOfflineItem = NotificationKey("didDeleteOfflineItem") + static let didAddDownload = NotificationKey("didAddDownload") + static let didSendStopReport = NotificationKey("didSendStopReport") } diff --git a/Shared/ViewModels/HomeViewModel.swift b/Shared/ViewModels/HomeViewModel.swift index 9df68555..7b93a9b0 100644 --- a/Shared/ViewModels/HomeViewModel.swift +++ b/Shared/ViewModels/HomeViewModel.swift @@ -34,9 +34,8 @@ final class HomeViewModel: ViewModel { // Nov. 6, 2021 // This is a workaround since Stinsen doesn't have the ability to rebuild a root at the time of writing. // See ServerDetailViewModel.swift for feature request issue - let nc = SwiftfinNotificationCenter.main - nc.addObserver(self, selector: #selector(didSignIn), name: SwiftfinNotificationCenter.Keys.didSignIn, object: nil) - nc.addObserver(self, selector: #selector(didSignOut), name: SwiftfinNotificationCenter.Keys.didSignOut, object: nil) + Notifications[.didSignIn].subscribe(self, selector: #selector(didSignIn)) + Notifications[.didSignOut].subscribe(self, selector: #selector(didSignOut)) } @objc diff --git a/Shared/ViewModels/ItemViewModel/ItemViewModel.swift b/Shared/ViewModels/ItemViewModel/ItemViewModel.swift index 0b493a24..7d9b8c6c 100644 --- a/Shared/ViewModels/ItemViewModel/ItemViewModel.swift +++ b/Shared/ViewModels/ItemViewModel/ItemViewModel.swift @@ -54,11 +54,8 @@ class ItemViewModel: ViewModel { super.init() getSimilarItems() - - SwiftfinNotificationCenter.main.addObserver(self, - selector: #selector(receivedStopReport(_:)), - name: SwiftfinNotificationCenter.Keys.didSendStopReport, - object: nil) + + Notifications[.didSendStopReport].subscribe(self, selector: #selector(receivedStopReport(_:))) refreshItemVideoPlayerViewModel(for: item) } @@ -72,7 +69,7 @@ class ItemViewModel: ViewModel { } else { // Remove if necessary. Note that this cannot be in deinit as // holding as an observer won't allow the object to be deinit-ed - SwiftfinNotificationCenter.main.removeObserver(self) + Notifications.unsubscribe(self) } } diff --git a/Shared/ViewModels/ServerDetailViewModel.swift b/Shared/ViewModels/ServerDetailViewModel.swift index b821c01f..08030106 100644 --- a/Shared/ViewModels/ServerDetailViewModel.swift +++ b/Shared/ViewModels/ServerDetailViewModel.swift @@ -25,8 +25,7 @@ class ServerDetailViewModel: ViewModel { } receiveValue: { newServerState in self.server = newServerState - let nc = SwiftfinNotificationCenter.main - nc.post(name: SwiftfinNotificationCenter.Keys.didChangeServerCurrentURI, object: newServerState) + Notifications[.didChangeServerCurrentURI].post(object: newServerState) } .store(in: &cancellables) } diff --git a/Shared/ViewModels/ServerListViewModel.swift b/Shared/ViewModels/ServerListViewModel.swift index 721c05dc..7691e100 100644 --- a/Shared/ViewModels/ServerListViewModel.swift +++ b/Shared/ViewModels/ServerListViewModel.swift @@ -20,8 +20,7 @@ class ServerListViewModel: ObservableObject { // This is a workaround since Stinsen doesn't have the ability to rebuild a root at the time of writing. // Feature request issue: https://github.com/rundfunk47/stinsen/issues/33 // Go to each MainCoordinator and implement the rebuild of the root when receiving the notification - let nc = SwiftfinNotificationCenter.main - nc.addObserver(self, selector: #selector(didPurge), name: SwiftfinNotificationCenter.Keys.didPurge, object: nil) + Notifications[.didPurge].subscribe(self, selector: #selector(didPurge)) } func fetchServers() { diff --git a/Shared/ViewModels/UserListViewModel.swift b/Shared/ViewModels/UserListViewModel.swift index c3a11919..258555da 100644 --- a/Shared/ViewModels/UserListViewModel.swift +++ b/Shared/ViewModels/UserListViewModel.swift @@ -20,10 +20,8 @@ class UserListViewModel: ViewModel { self.server = server super.init() - - let nc = SwiftfinNotificationCenter.main - nc.addObserver(self, selector: #selector(didChangeCurrentLoginURI), name: SwiftfinNotificationCenter.Keys.didChangeServerCurrentURI, - object: nil) + + Notifications[.didChangeServerCurrentURI].subscribe(self, selector: #selector(didChangeCurrentLoginURI(_:))) } @objc diff --git a/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift b/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift index bcbe6a11..1325c0e6 100644 --- a/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift +++ b/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift @@ -585,8 +585,7 @@ extension VideoPlayerViewModel { self.handleAPIRequestError(completion: completion) } receiveValue: { _ in LogManager.shared.log.debug("Stop report sent for item: \(self.item.id ?? "No ID")") - SwiftfinNotificationCenter.main.post(name: SwiftfinNotificationCenter.Keys.didSendStopReport, - object: self.item.id) + Notifications[.didSendStopReport].post(object: self.item.id) } .store(in: &cancellables) } diff --git a/Swiftfin/AppURLHandler/AppURLHandler.swift b/Swiftfin/AppURLHandler/AppURLHandler.swift index 412a4c84..6c091372 100644 --- a/Swiftfin/AppURLHandler/AppURLHandler.swift +++ b/Swiftfin/AppURLHandler/AppURLHandler.swift @@ -81,7 +81,7 @@ extension AppURLHandler { // It would be nice if the ItemViewModel could be initialized to id later. getItem(userID: userID, itemID: itemID) { item in guard let item = item else { return } - SwiftfinNotificationCenter.main.post(name: SwiftfinNotificationCenter.Keys.processDeepLink, object: DeepLink.item(item)) + Notifications[.processDeepLink].post(object: DeepLink.item(item)) } return true From a467f0cbd7019526ba96556823adc1ec4458e1de Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 18 Mar 2022 22:05:08 -0600 Subject: [PATCH 5/7] lint --- .../MainCoordinator/iOSMainCoordinator.swift | 8 +- Shared/Singleton/SessionManager.swift | 8 +- .../SwiftfinNotificationCenter.swift | 100 ++++++------ Shared/ViewModels/HomeViewModel.swift | 4 +- .../ItemViewModel/ItemViewModel.swift | 6 +- Shared/ViewModels/ServerDetailViewModel.swift | 2 +- Shared/ViewModels/ServerListViewModel.swift | 2 +- Shared/ViewModels/UserListViewModel.swift | 4 +- .../VideoPlayerViewModel.swift | 2 +- Shared/Views/BlurHashView.swift | 91 +++++------ Shared/Views/ImageView.swift | 146 +++++++++--------- Shared/Views/InitialFailureView.swift | 34 ++-- Swiftfin tvOS/Views/LibraryListView.swift | 50 +++--- Swiftfin/AppURLHandler/AppURLHandler.swift | 2 +- Swiftfin/Components/PortraitHStackView.swift | 12 +- Swiftfin/Components/PortraitItemButton.swift | 12 +- .../VideoPlayer/VLCPlayerViewController.swift | 4 +- 17 files changed, 244 insertions(+), 243 deletions(-) diff --git a/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift b/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift index d1690c2f..7db2b6eb 100644 --- a/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift +++ b/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift @@ -45,10 +45,10 @@ final class MainCoordinator: NavigationCoordinatable { barAppearance.tintColor = UIColor(Color.jellyfinPurple) // Notification setup for state - Notifications[.didSignIn].subscribe(self, selector: #selector(didSignIn)) - Notifications[.didSignOut].subscribe(self, selector: #selector(didSignOut)) - Notifications[.processDeepLink].subscribe(self, selector: #selector(processDeepLink(_:))) - Notifications[.didChangeServerCurrentURI].subscribe(self, selector: #selector(didChangeServerCurrentURI(_:))) + Notifications[.didSignIn].subscribe(self, selector: #selector(didSignIn)) + Notifications[.didSignOut].subscribe(self, selector: #selector(didSignOut)) + Notifications[.processDeepLink].subscribe(self, selector: #selector(processDeepLink(_:))) + Notifications[.didChangeServerCurrentURI].subscribe(self, selector: #selector(didChangeServerCurrentURI(_:))) Defaults.publisher(.appAppearance) .sink { _ in diff --git a/Shared/Singleton/SessionManager.swift b/Shared/Singleton/SessionManager.swift index a6ddd435..2d3fffd2 100644 --- a/Shared/Singleton/SessionManager.swift +++ b/Shared/Singleton/SessionManager.swift @@ -240,7 +240,7 @@ final class SessionManager { Defaults[.lastServerUserID] = user.id currentLogin = (server: currentServer.state, user: currentUser.state) - Notifications[.didSignIn].post() + Notifications[.didSignIn].post() }) .map { _, user, _ in user.state @@ -255,7 +255,7 @@ final class SessionManager { Defaults[.lastServerUserID] = user.id setAuthHeader(with: user.accessToken) currentLogin = (server: server, user: user) - Notifications[.didSignIn].post() + Notifications[.didSignIn].post() } // MARK: logout @@ -265,7 +265,7 @@ final class SessionManager { JellyfinAPI.basePath = "" setAuthHeader(with: "") Defaults[.lastServerUserID] = nil - Notifications[.didSignOut].post() + Notifications[.didSignOut].post() } // MARK: purge @@ -278,7 +278,7 @@ final class SessionManager { delete(server: server) } - Notifications[.didPurge].post() + Notifications[.didPurge].post() } // MARK: delete user diff --git a/Shared/Singleton/SwiftfinNotificationCenter.swift b/Shared/Singleton/SwiftfinNotificationCenter.swift index 61fcfbc9..23ec8c4d 100644 --- a/Shared/Singleton/SwiftfinNotificationCenter.swift +++ b/Shared/Singleton/SwiftfinNotificationCenter.swift @@ -9,62 +9,62 @@ import Foundation class SwiftfinNotification { - - private let notificationName: Notification.Name - - fileprivate init(_ notificationName: Notification.Name) { - self.notificationName = notificationName - } - - func post(object: Any? = nil) { - Notifications.main.post(name: notificationName, object: object) - } - - func subscribe(_ observer: Any, selector: Selector) { - Notifications.main.addObserver(observer, selector: selector, name: notificationName, object: nil) - } - - func unsubscribe(_ observer: Any) { - Notifications.main.removeObserver(self, name: notificationName, object: nil) - } + + private let notificationName: Notification.Name + + fileprivate init(_ notificationName: Notification.Name) { + self.notificationName = notificationName + } + + func post(object: Any? = nil) { + Notifications.main.post(name: notificationName, object: object) + } + + func subscribe(_ observer: Any, selector: Selector) { + Notifications.main.addObserver(observer, selector: selector, name: notificationName, object: nil) + } + + func unsubscribe(_ observer: Any) { + Notifications.main.removeObserver(self, name: notificationName, object: nil) + } } enum Notifications { - - static let main: NotificationCenter = { - NotificationCenter() - }() - - final class Key { - public typealias NotificationKey = Notifications.Key - - public let key: String - public let underlyingNotification: SwiftfinNotification - public init(_ key: String) { - self.key = key - self.underlyingNotification = SwiftfinNotification(Notification.Name(key)) - } - } + static let main: NotificationCenter = { + NotificationCenter() + }() - static subscript(key: Key) -> SwiftfinNotification { - return key.underlyingNotification - } - - static func unsubscribe(_ observer: Any) { - main.removeObserver(observer) - } + final class Key { + public typealias NotificationKey = Notifications.Key + + public let key: String + public let underlyingNotification: SwiftfinNotification + + public init(_ key: String) { + self.key = key + self.underlyingNotification = SwiftfinNotification(Notification.Name(key)) + } + } + + static subscript(key: Key) -> SwiftfinNotification { + key.underlyingNotification + } + + static func unsubscribe(_ observer: Any) { + main.removeObserver(observer) + } } extension Notifications.Key { - - static let didSignIn = NotificationKey("didSignIn") - static let didSignOut = NotificationKey("didSignOut") - static let processDeepLink = NotificationKey("processDeepLink") - static let didPurge = NotificationKey("didPurge") - static let didChangeServerCurrentURI = NotificationKey("didChangeCurrentLoginURI") - static let toggleOfflineMode = NotificationKey("toggleOfflineMode") - static let didDeleteOfflineItem = NotificationKey("didDeleteOfflineItem") - static let didAddDownload = NotificationKey("didAddDownload") - static let didSendStopReport = NotificationKey("didSendStopReport") + + static let didSignIn = NotificationKey("didSignIn") + static let didSignOut = NotificationKey("didSignOut") + static let processDeepLink = NotificationKey("processDeepLink") + static let didPurge = NotificationKey("didPurge") + static let didChangeServerCurrentURI = NotificationKey("didChangeCurrentLoginURI") + static let toggleOfflineMode = NotificationKey("toggleOfflineMode") + static let didDeleteOfflineItem = NotificationKey("didDeleteOfflineItem") + static let didAddDownload = NotificationKey("didAddDownload") + static let didSendStopReport = NotificationKey("didSendStopReport") } diff --git a/Shared/ViewModels/HomeViewModel.swift b/Shared/ViewModels/HomeViewModel.swift index 7b93a9b0..3d4dccba 100644 --- a/Shared/ViewModels/HomeViewModel.swift +++ b/Shared/ViewModels/HomeViewModel.swift @@ -34,8 +34,8 @@ final class HomeViewModel: ViewModel { // Nov. 6, 2021 // This is a workaround since Stinsen doesn't have the ability to rebuild a root at the time of writing. // See ServerDetailViewModel.swift for feature request issue - Notifications[.didSignIn].subscribe(self, selector: #selector(didSignIn)) - Notifications[.didSignOut].subscribe(self, selector: #selector(didSignOut)) + Notifications[.didSignIn].subscribe(self, selector: #selector(didSignIn)) + Notifications[.didSignOut].subscribe(self, selector: #selector(didSignOut)) } @objc diff --git a/Shared/ViewModels/ItemViewModel/ItemViewModel.swift b/Shared/ViewModels/ItemViewModel/ItemViewModel.swift index 7d9b8c6c..b147763f 100644 --- a/Shared/ViewModels/ItemViewModel/ItemViewModel.swift +++ b/Shared/ViewModels/ItemViewModel/ItemViewModel.swift @@ -54,8 +54,8 @@ class ItemViewModel: ViewModel { super.init() getSimilarItems() - - Notifications[.didSendStopReport].subscribe(self, selector: #selector(receivedStopReport(_:))) + + Notifications[.didSendStopReport].subscribe(self, selector: #selector(receivedStopReport(_:))) refreshItemVideoPlayerViewModel(for: item) } @@ -69,7 +69,7 @@ class ItemViewModel: ViewModel { } else { // Remove if necessary. Note that this cannot be in deinit as // holding as an observer won't allow the object to be deinit-ed - Notifications.unsubscribe(self) + Notifications.unsubscribe(self) } } diff --git a/Shared/ViewModels/ServerDetailViewModel.swift b/Shared/ViewModels/ServerDetailViewModel.swift index 08030106..69d0ded3 100644 --- a/Shared/ViewModels/ServerDetailViewModel.swift +++ b/Shared/ViewModels/ServerDetailViewModel.swift @@ -25,7 +25,7 @@ class ServerDetailViewModel: ViewModel { } receiveValue: { newServerState in self.server = newServerState - Notifications[.didChangeServerCurrentURI].post(object: newServerState) + Notifications[.didChangeServerCurrentURI].post(object: newServerState) } .store(in: &cancellables) } diff --git a/Shared/ViewModels/ServerListViewModel.swift b/Shared/ViewModels/ServerListViewModel.swift index 7691e100..86b7cd59 100644 --- a/Shared/ViewModels/ServerListViewModel.swift +++ b/Shared/ViewModels/ServerListViewModel.swift @@ -20,7 +20,7 @@ class ServerListViewModel: ObservableObject { // This is a workaround since Stinsen doesn't have the ability to rebuild a root at the time of writing. // Feature request issue: https://github.com/rundfunk47/stinsen/issues/33 // Go to each MainCoordinator and implement the rebuild of the root when receiving the notification - Notifications[.didPurge].subscribe(self, selector: #selector(didPurge)) + Notifications[.didPurge].subscribe(self, selector: #selector(didPurge)) } func fetchServers() { diff --git a/Shared/ViewModels/UserListViewModel.swift b/Shared/ViewModels/UserListViewModel.swift index 258555da..7d087323 100644 --- a/Shared/ViewModels/UserListViewModel.swift +++ b/Shared/ViewModels/UserListViewModel.swift @@ -20,8 +20,8 @@ class UserListViewModel: ViewModel { self.server = server super.init() - - Notifications[.didChangeServerCurrentURI].subscribe(self, selector: #selector(didChangeCurrentLoginURI(_:))) + + Notifications[.didChangeServerCurrentURI].subscribe(self, selector: #selector(didChangeCurrentLoginURI(_:))) } @objc diff --git a/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift b/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift index 1325c0e6..738eeadc 100644 --- a/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift +++ b/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift @@ -585,7 +585,7 @@ extension VideoPlayerViewModel { self.handleAPIRequestError(completion: completion) } receiveValue: { _ in LogManager.shared.log.debug("Stop report sent for item: \(self.item.id ?? "No ID")") - Notifications[.didSendStopReport].post(object: self.item.id) + Notifications[.didSendStopReport].post(object: self.item.id) } .store(in: &cancellables) } diff --git a/Shared/Views/BlurHashView.swift b/Shared/Views/BlurHashView.swift index 9e8d3805..7eac6d5b 100644 --- a/Shared/Views/BlurHashView.swift +++ b/Shared/Views/BlurHashView.swift @@ -10,52 +10,53 @@ import SwiftUI import UIKit struct BlurHashView: UIViewRepresentable { - - let blurHash: String - - func makeUIView(context: Context) -> UIBlurHashView { - return UIBlurHashView(blurHash) - } - - func updateUIView(_ uiView: UIBlurHashView, context: Context) {} + + let blurHash: String + + func makeUIView(context: Context) -> UIBlurHashView { + UIBlurHashView(blurHash) + } + + func updateUIView(_ uiView: UIBlurHashView, context: Context) {} } class UIBlurHashView: UIView { - - private let imageView: UIImageView - - init(_ blurHash: String) { - let imageView = UIImageView() - imageView.translatesAutoresizingMaskIntoConstraints = false - self.imageView = imageView - - super.init(frame: .zero) - - computeBlurHashImageAsync(blurHash: blurHash) { blurImage in - DispatchQueue.main.async { - self.imageView.image = blurImage - self.imageView.setNeedsDisplay() - } - } - - addSubview(imageView) - - NSLayoutConstraint.activate([ - imageView.topAnchor.constraint(equalTo: topAnchor), - imageView.bottomAnchor.constraint(equalTo: bottomAnchor), - imageView.leftAnchor.constraint(equalTo: leftAnchor), - imageView.rightAnchor.constraint(equalTo: rightAnchor), - ]) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func computeBlurHashImageAsync(blurHash: String, _ completion: @escaping (UIImage?) -> Void) { - DispatchQueue.global(qos: .utility).async { - let image = UIImage(blurHash: blurHash, size: .Circle(radius: 12)) - completion(image) - } - } + + private let imageView: UIImageView + + init(_ blurHash: String) { + let imageView = UIImageView() + imageView.translatesAutoresizingMaskIntoConstraints = false + self.imageView = imageView + + super.init(frame: .zero) + + computeBlurHashImageAsync(blurHash: blurHash) { blurImage in + DispatchQueue.main.async { + self.imageView.image = blurImage + self.imageView.setNeedsDisplay() + } + } + + addSubview(imageView) + + NSLayoutConstraint.activate([ + imageView.topAnchor.constraint(equalTo: topAnchor), + imageView.bottomAnchor.constraint(equalTo: bottomAnchor), + imageView.leftAnchor.constraint(equalTo: leftAnchor), + imageView.rightAnchor.constraint(equalTo: rightAnchor), + ]) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func computeBlurHashImageAsync(blurHash: String, _ completion: @escaping (UIImage?) -> Void) { + DispatchQueue.global(qos: .utility).async { + let image = UIImage(blurHash: blurHash, size: .Circle(radius: 12)) + completion(image) + } + } } diff --git a/Shared/Views/ImageView.swift b/Shared/Views/ImageView.swift index 1ef3f305..c932cd97 100644 --- a/Shared/Views/ImageView.swift +++ b/Shared/Views/ImageView.swift @@ -14,92 +14,92 @@ import UIKit // TODO: Fix 100+ inits struct ImageViewSource { - let url: URL? - let blurHash: String? - - init(url: URL? = nil, blurHash: String? = nil) { - self.url = url - self.blurHash = blurHash - } + let url: URL? + let blurHash: String? + + init(url: URL? = nil, blurHash: String? = nil) { + self.url = url + self.blurHash = blurHash + } } struct DefaultFailureView: View { - - var body: some View { - Color.secondary - } + + var body: some View { + Color.secondary + } } struct ImageView: View { - @State - private var sources: [ImageViewSource] - private var currentURL: URL? { sources.first?.url } - private var currentBlurHash: String? { sources.first?.blurHash } - private var failureView: FailureView - - init(_ source: URL?, blurHash: String? = nil, @ViewBuilder failureView: () -> FailureView) { - let imageViewSource = ImageViewSource(url: source, blurHash: blurHash) - _sources = State(initialValue: [imageViewSource]) - self.failureView = failureView() - } + @State + private var sources: [ImageViewSource] + private var currentURL: URL? { sources.first?.url } + private var currentBlurHash: String? { sources.first?.blurHash } + private var failureView: FailureView - init(_ source: ImageViewSource, @ViewBuilder failureView: () -> FailureView) { - _sources = State(initialValue: [source]) - self.failureView = failureView() - } + init(_ source: URL?, blurHash: String? = nil, @ViewBuilder failureView: () -> FailureView) { + let imageViewSource = ImageViewSource(url: source, blurHash: blurHash) + _sources = State(initialValue: [imageViewSource]) + self.failureView = failureView() + } - init(_ sources: [ImageViewSource], @ViewBuilder failureView: () -> FailureView) { - _sources = State(initialValue: sources) - self.failureView = failureView() - } + init(_ source: ImageViewSource, @ViewBuilder failureView: () -> FailureView) { + _sources = State(initialValue: [source]) + self.failureView = failureView() + } - @ViewBuilder - private var placeholderView: some View { - if let currentBlurHash = currentBlurHash { - BlurHashView(blurHash: currentBlurHash) - .id(currentBlurHash) - } else { - Color.secondary - } - } + init(_ sources: [ImageViewSource], @ViewBuilder failureView: () -> FailureView) { + _sources = State(initialValue: sources) + self.failureView = failureView() + } - var body: some View { - - if let currentURL = currentURL { - LazyImage(source: currentURL) { state in - if let image = state.image { - image - } else if state.error != nil { - placeholderView.onAppear { sources.removeFirst() } - } else { - placeholderView - } - } - .pipeline(ImagePipeline(configuration: .withDataCache)) - .id(currentURL) - } else { - failureView - } - } + @ViewBuilder + private var placeholderView: some View { + if let currentBlurHash = currentBlurHash { + BlurHashView(blurHash: currentBlurHash) + .id(currentBlurHash) + } else { + Color.secondary + } + } + + var body: some View { + + if let currentURL = currentURL { + LazyImage(source: currentURL) { state in + if let image = state.image { + image + } else if state.error != nil { + placeholderView.onAppear { sources.removeFirst() } + } else { + placeholderView + } + } + .pipeline(ImagePipeline(configuration: .withDataCache)) + .id(currentURL) + } else { + failureView + } + } } extension ImageView where FailureView == DefaultFailureView { - init(_ source: URL?, blurHash: String? = nil) { - let imageViewSource = ImageViewSource(url: source, blurHash: blurHash) - self.init(imageViewSource, failureView: { DefaultFailureView() }) - } - - init(_ source: ImageViewSource) { - self.init(source, failureView: { DefaultFailureView() }) - } + init(_ source: URL?, blurHash: String? = nil) { + let imageViewSource = ImageViewSource(url: source, blurHash: blurHash) + self.init(imageViewSource, failureView: { DefaultFailureView() }) + } - init(_ sources: [ImageViewSource]) { - self.init(sources, failureView: { DefaultFailureView() }) - } - - init(sources: [URL]) { - let imageViewSources = sources.compactMap { ImageViewSource(url: $0, blurHash: nil) } - self.init(imageViewSources, failureView: { DefaultFailureView() }) - } + init(_ source: ImageViewSource) { + self.init(source, failureView: { DefaultFailureView() }) + } + + init(_ sources: [ImageViewSource]) { + self.init(sources, failureView: { DefaultFailureView() }) + } + + init(sources: [URL]) { + let imageViewSources = sources.compactMap { ImageViewSource(url: $0, blurHash: nil) } + self.init(imageViewSources, failureView: { DefaultFailureView() }) + } } diff --git a/Shared/Views/InitialFailureView.swift b/Shared/Views/InitialFailureView.swift index 9af50ae6..8a9bed09 100644 --- a/Shared/Views/InitialFailureView.swift +++ b/Shared/Views/InitialFailureView.swift @@ -9,22 +9,22 @@ import SwiftUI struct InitialFailureView: View { - - let initials: String - - init(_ initials: String) { - self.initials = initials - } - - var body: some View { - ZStack { - Rectangle() - .foregroundColor(Color(UIColor.darkGray)) - Text(initials) - .font(.largeTitle) - .foregroundColor(.secondary) - .accessibilityHidden(true) - } - } + let initials: String + + init(_ initials: String) { + self.initials = initials + } + + var body: some View { + ZStack { + Rectangle() + .foregroundColor(Color(UIColor.darkGray)) + + Text(initials) + .font(.largeTitle) + .foregroundColor(.secondary) + .accessibilityHidden(true) + } + } } diff --git a/Swiftfin tvOS/Views/LibraryListView.swift b/Swiftfin tvOS/Views/LibraryListView.swift index 4760e74e..9001544d 100644 --- a/Swiftfin tvOS/Views/LibraryListView.swift +++ b/Swiftfin tvOS/Views/LibraryListView.swift @@ -38,6 +38,31 @@ struct LibraryListView: View { self.mainCoordinator.root(\.liveTV) } label: { + ZStack { + HStack { + Spacer() + VStack { + Text(library.name ?? "") + .foregroundColor(.white) + .font(.title2) + .fontWeight(.semibold) + } + Spacer() + }.padding(32) + } + .frame(minWidth: 100, maxWidth: .infinity) + .frame(height: 100) + } + .cornerRadius(10) + .shadow(radius: 5) + .padding(.bottom, 5) + } + } else { + Button { + self.libraryListRouter.route(to: \.library, + (viewModel: LibraryViewModel(parentID: library.id), title: library.name ?? "")) + } + label: { ZStack { HStack { Spacer() @@ -56,31 +81,6 @@ struct LibraryListView: View { .cornerRadius(10) .shadow(radius: 5) .padding(.bottom, 5) - } - } else { - Button { - self.libraryListRouter.route(to: \.library, - (viewModel: LibraryViewModel(parentID: library.id), title: library.name ?? "")) - } - label: { - ZStack { - HStack { - Spacer() - VStack { - Text(library.name ?? "") - .foregroundColor(.white) - .font(.title2) - .fontWeight(.semibold) - } - Spacer() - }.padding(32) - } - .frame(minWidth: 100, maxWidth: .infinity) - .frame(height: 100) - } - .cornerRadius(10) - .shadow(radius: 5) - .padding(.bottom, 5) } } } else { diff --git a/Swiftfin/AppURLHandler/AppURLHandler.swift b/Swiftfin/AppURLHandler/AppURLHandler.swift index 6c091372..7db83b2c 100644 --- a/Swiftfin/AppURLHandler/AppURLHandler.swift +++ b/Swiftfin/AppURLHandler/AppURLHandler.swift @@ -81,7 +81,7 @@ extension AppURLHandler { // It would be nice if the ItemViewModel could be initialized to id later. getItem(userID: userID, itemID: itemID) { item in guard let item = item else { return } - Notifications[.processDeepLink].post(object: DeepLink.item(item)) + Notifications[.processDeepLink].post(object: DeepLink.item(item)) } return true diff --git a/Swiftfin/Components/PortraitHStackView.swift b/Swiftfin/Components/PortraitHStackView.swift index e0f76706..a9aa57c0 100644 --- a/Swiftfin/Components/PortraitHStackView.swift +++ b/Swiftfin/Components/PortraitHStackView.swift @@ -45,12 +45,12 @@ struct PortraitImageHStackView: View { VStack(alignment: horizontalAlignment) { ImageView(item.imageURLConstructor(maxWidth: Int(maxWidth)), blurHash: item.blurHash, - failureView: { - InitialFailureView(item.failureInitials) - }) - .portraitPoster(width: maxWidth) - .shadow(radius: 4, y: 2) - .accessibilityIgnoresInvertColors() + failureView: { + InitialFailureView(item.failureInitials) + }) + .portraitPoster(width: maxWidth) + .shadow(radius: 4, y: 2) + .accessibilityIgnoresInvertColors() if item.showTitle { Text(item.title) diff --git a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift index 9407d2b2..6f7e2173 100644 --- a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift @@ -755,7 +755,7 @@ extension VLCPlayerViewController { extension VLCPlayerViewController: VLCMediaPlayerDelegate { // MARK: mediaPlayerStateChanged - func mediaPlayerStateChanged(_ aNotification: Notification) { + func mediaPlayerStateChanged(_ aNotification: Notification) { // Don't show buffering if paused, usually here while scrubbing if vlcMediaPlayer.state == .buffering, viewModel.playerState == .paused { return @@ -774,7 +774,7 @@ extension VLCPlayerViewController: VLCMediaPlayerDelegate { // MARK: mediaPlayerTimeChanged - func mediaPlayerTimeChanged(_ aNotification: Notification) { + func mediaPlayerTimeChanged(_ aNotification: Notification) { if !viewModel.sliderIsScrubbing { viewModel.sliderPercentage = Double(vlcMediaPlayer.position) } From 9441de86a81eb4eaec53a90cd58dffcefea49695 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 18 Mar 2022 22:13:27 -0600 Subject: [PATCH 6/7] fix tvos --- .../MainCoordinator/tvOSMainCoordinator.swift | 9 ++-- .../CinematicNextUpCardView.swift | 9 ++-- .../CinematicResumeCardView.swift | 9 ++-- Swiftfin tvOS/Views/LibraryListView.swift | 50 +++++++++---------- .../LiveTVPlayerViewController.swift | 4 +- .../VideoPlayer/VLCPlayerViewController.swift | 4 +- 6 files changed, 41 insertions(+), 44 deletions(-) diff --git a/Shared/Coordinators/MainCoordinator/tvOSMainCoordinator.swift b/Shared/Coordinators/MainCoordinator/tvOSMainCoordinator.swift index 632b7790..8e1d6390 100644 --- a/Shared/Coordinators/MainCoordinator/tvOSMainCoordinator.swift +++ b/Shared/Coordinators/MainCoordinator/tvOSMainCoordinator.swift @@ -40,19 +40,18 @@ final class MainCoordinator: NavigationCoordinatable { DataLoader.sharedUrlCache.diskCapacity = 1000 * 1024 * 1024 // 1000MB disk // Notification setup for state - let nc = SwiftfinNotificationCenter.main - nc.addObserver(self, selector: #selector(didLogIn), name: SwiftfinNotificationCenter.Keys.didSignIn, object: nil) - nc.addObserver(self, selector: #selector(didLogOut), name: SwiftfinNotificationCenter.Keys.didSignOut, object: nil) + Notifications[.didSignIn].subscribe(self, selector: #selector(didSignIn)) + Notifications[.didSignOut].subscribe(self, selector: #selector(didSignOut)) } @objc - func didLogIn() { + func didSignIn() { LogManager.shared.log.info("Received `didSignIn` from NSNotificationCenter.") root(\.mainTab) } @objc - func didLogOut() { + func didSignOut() { LogManager.shared.log.info("Received `didSignOut` from NSNotificationCenter.") root(\.serverList) } diff --git a/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift b/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift index c31a89f9..fa95e274 100644 --- a/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift +++ b/Swiftfin tvOS/Components/HomeCinematicView/CinematicNextUpCardView.swift @@ -30,11 +30,10 @@ struct CinematicNextUpCardView: View { ]) .frame(width: 350, height: 210) } else { - ImageView(sources: [ - item.getThumbImage(maxWidth: 350), - item.getBackdropImage(maxWidth: 350), - ], - blurHash: item.getBackdropImageBlurHash()) + ImageView([ + .init(url: item.getThumbImage(maxWidth: 350)), + .init(url: item.getBackdropImage(maxWidth: 350), blurHash: item.getBackdropImageBlurHash()), + ]) .frame(width: 350, height: 210) } diff --git a/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift b/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift index 21914da3..8aa188c5 100644 --- a/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift +++ b/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift @@ -31,11 +31,10 @@ struct CinematicResumeCardView: View { ]) .frame(width: 350, height: 210) } else { - ImageView(sources: [ - item.getThumbImage(maxWidth: 350), - item.getBackdropImage(maxWidth: 350), - ], - blurHash: item.getBackdropImageBlurHash()) + ImageView([ + .init(url: item.getThumbImage(maxWidth: 350)), + .init(url: item.getBackdropImage(maxWidth: 350), blurHash: item.getBackdropImageBlurHash()), + ]) .frame(width: 350, height: 210) } diff --git a/Swiftfin tvOS/Views/LibraryListView.swift b/Swiftfin tvOS/Views/LibraryListView.swift index 9001544d..4760e74e 100644 --- a/Swiftfin tvOS/Views/LibraryListView.swift +++ b/Swiftfin tvOS/Views/LibraryListView.swift @@ -38,31 +38,6 @@ struct LibraryListView: View { self.mainCoordinator.root(\.liveTV) } label: { - ZStack { - HStack { - Spacer() - VStack { - Text(library.name ?? "") - .foregroundColor(.white) - .font(.title2) - .fontWeight(.semibold) - } - Spacer() - }.padding(32) - } - .frame(minWidth: 100, maxWidth: .infinity) - .frame(height: 100) - } - .cornerRadius(10) - .shadow(radius: 5) - .padding(.bottom, 5) - } - } else { - Button { - self.libraryListRouter.route(to: \.library, - (viewModel: LibraryViewModel(parentID: library.id), title: library.name ?? "")) - } - label: { ZStack { HStack { Spacer() @@ -81,6 +56,31 @@ struct LibraryListView: View { .cornerRadius(10) .shadow(radius: 5) .padding(.bottom, 5) + } + } else { + Button { + self.libraryListRouter.route(to: \.library, + (viewModel: LibraryViewModel(parentID: library.id), title: library.name ?? "")) + } + label: { + ZStack { + HStack { + Spacer() + VStack { + Text(library.name ?? "") + .foregroundColor(.white) + .font(.title2) + .fontWeight(.semibold) + } + Spacer() + }.padding(32) + } + .frame(minWidth: 100, maxWidth: .infinity) + .frame(height: 100) + } + .cornerRadius(10) + .shadow(radius: 5) + .padding(.bottom, 5) } } } else { diff --git a/Swiftfin tvOS/Views/VideoPlayer/LiveTVPlayerViewController.swift b/Swiftfin tvOS/Views/VideoPlayer/LiveTVPlayerViewController.swift index d97dbb55..e726e723 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/LiveTVPlayerViewController.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/LiveTVPlayerViewController.swift @@ -700,7 +700,7 @@ extension LiveTVPlayerViewController: VLCMediaPlayerDelegate { // MARK: mediaPlayerStateChanged - func mediaPlayerStateChanged(_ aNotification: Notification!) { + func mediaPlayerStateChanged(_ aNotification: Notification) { // Don't show buffering if paused, usually here while scrubbing if vlcMediaPlayer.state == .buffering && viewModel.playerState == .paused { @@ -720,7 +720,7 @@ extension LiveTVPlayerViewController: VLCMediaPlayerDelegate { // MARK: mediaPlayerTimeChanged - func mediaPlayerTimeChanged(_ aNotification: Notification!) { + func mediaPlayerTimeChanged(_ aNotification: Notification) { if !viewModel.sliderIsScrubbing { viewModel.sliderPercentage = Double(vlcMediaPlayer.position) diff --git a/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift b/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift index 058bd4a9..36382579 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift @@ -700,7 +700,7 @@ extension VLCPlayerViewController: VLCMediaPlayerDelegate { // MARK: mediaPlayerStateChanged - func mediaPlayerStateChanged(_ aNotification: Notification!) { + func mediaPlayerStateChanged(_ aNotification: Notification) { // Don't show buffering if paused, usually here while scrubbing if vlcMediaPlayer.state == .buffering && viewModel.playerState == .paused { @@ -720,7 +720,7 @@ extension VLCPlayerViewController: VLCMediaPlayerDelegate { // MARK: mediaPlayerTimeChanged - func mediaPlayerTimeChanged(_ aNotification: Notification!) { + func mediaPlayerTimeChanged(_ aNotification: Notification) { if !viewModel.sliderIsScrubbing { viewModel.sliderPercentage = Double(vlcMediaPlayer.position) From 0d69ae592ac67be20c2f39bd59762754626d1335 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Sat, 19 Mar 2022 09:54:45 -0600 Subject: [PATCH 7/7] Update BlurHashView.swift --- Shared/Views/BlurHashView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Shared/Views/BlurHashView.swift b/Shared/Views/BlurHashView.swift index 7eac6d5b..c5f608d6 100644 --- a/Shared/Views/BlurHashView.swift +++ b/Shared/Views/BlurHashView.swift @@ -31,7 +31,8 @@ class UIBlurHashView: UIView { super.init(frame: .zero) - computeBlurHashImageAsync(blurHash: blurHash) { blurImage in + computeBlurHashImageAsync(blurHash: blurHash) { [weak self] blurImage in + guard let self = self else { return } DispatchQueue.main.async { self.imageView.image = blurImage self.imageView.setNeedsDisplay()