From b38d788e34de2f6761a36a44512f59aaac14aedc Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Wed, 12 Jan 2022 13:14:39 -0700 Subject: [PATCH 1/5] implement remove from resume and play from beginning --- Shared/Generated/Strings.swift | 4 + Shared/ViewModels/EpisodesRowManager.swift | 16 ---- Shared/ViewModels/HomeViewModel.swift | 14 ++++ .../CollectionItemViewModel.swift | 2 +- .../VideoPlayerViewModel.swift | 18 ++++- .../EpisodesRowView/EpisodesRowCard.swift | 76 +++++++++--------- .../CinematicResumeCardView.swift | 13 ++- .../HomeCinematicView/HomeCinematicView.swift | 7 +- Swiftfin tvOS/Views/HomeView.swift | 8 +- .../CinematicItemViewTopRow.swift | 14 ++++ Swiftfin.xcodeproj/project.pbxproj | 8 +- Swiftfin/Views/ContinueWatchingView.swift | 7 ++ .../Landscape/ItemLandscapeMainView.swift | 14 ++++ .../ItemPortraitHeaderOverlayView.swift | 23 +++++- Translations/en.lproj/Localizable.strings | Bin 11176 -> 11352 bytes 15 files changed, 156 insertions(+), 68 deletions(-) diff --git a/Shared/Generated/Strings.swift b/Shared/Generated/Strings.swift index 7ab73115..f137dd28 100644 --- a/Shared/Generated/Strings.swift +++ b/Shared/Generated/Strings.swift @@ -222,6 +222,8 @@ internal enum L10n { internal static let playbackSettings = L10n.tr("Localizable", "playbackSettings") /// Playback Speed internal static let playbackSpeed = L10n.tr("Localizable", "playbackSpeed") + /// Play From Beginning + internal static let playFromBeginning = L10n.tr("Localizable", "playFromBeginning") /// Play Next internal static let playNext = L10n.tr("Localizable", "playNext") /// Play Next Item @@ -250,6 +252,8 @@ internal enum L10n { internal static let remove = L10n.tr("Localizable", "remove") /// Remove All Users internal static let removeAllUsers = L10n.tr("Localizable", "removeAllUsers") + /// Remove From Resume + internal static let removeFromResume = L10n.tr("Localizable", "removeFromResume") /// Reset internal static let reset = L10n.tr("Localizable", "reset") /// Reset App Settings diff --git a/Shared/ViewModels/EpisodesRowManager.swift b/Shared/ViewModels/EpisodesRowManager.swift index a31d989b..5bc12889 100644 --- a/Shared/ViewModels/EpisodesRowManager.swift +++ b/Shared/ViewModels/EpisodesRowManager.swift @@ -70,19 +70,3 @@ extension EpisodesRowManager { } } } - -final class SingleSeasonEpisodesRowViewModel: ViewModel { - - // TODO: Protocol these viewmodels for generalization instead of Season - - @ObservedObject - var seasonItemViewModel: SeasonItemViewModel - @Published - var episodes: [BaseItemDto] - - init(seasonItemViewModel: SeasonItemViewModel) { - self.seasonItemViewModel = seasonItemViewModel - self.episodes = seasonItemViewModel.episodes - super.init() - } -} diff --git a/Shared/ViewModels/HomeViewModel.swift b/Shared/ViewModels/HomeViewModel.swift index 0de66a2f..8ef6a462 100644 --- a/Shared/ViewModels/HomeViewModel.swift +++ b/Shared/ViewModels/HomeViewModel.swift @@ -184,6 +184,20 @@ final class HomeViewModel: ViewModel { }) .store(in: &cancellables) } + + func removeItemFromResume(_ item: BaseItemDto) { + guard let itemID = item.id, resumeItems.contains(where: { $0.id == itemID }) else { return } + + PlaystateAPI.markUnplayedItem(userId: SessionManager.main.currentLogin.user.id, + itemId: item.id!) + .sink(receiveCompletion: { [weak self] completion in + self?.handleAPIRequestError(completion: completion) + }, receiveValue: { _ in + self.refreshResumeItems() + self.refreshNextUpItems() + }) + .store(in: &cancellables) + } // MARK: Next Up Items diff --git a/Shared/ViewModels/ItemViewModel/CollectionItemViewModel.swift b/Shared/ViewModels/ItemViewModel/CollectionItemViewModel.swift index 5d094b9c..cd14f9ce 100644 --- a/Shared/ViewModels/ItemViewModel/CollectionItemViewModel.swift +++ b/Shared/ViewModels/ItemViewModel/CollectionItemViewModel.swift @@ -24,7 +24,7 @@ final class CollectionItemViewModel: ItemViewModel { private func getCollectionItems() { ItemsAPI.getItems(userId: SessionManager.main.currentLogin.user.id, parentId: item.id, - fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people]) + fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people]) .trackActivity(loading) .sink { [weak self] completion in self?.handleAPIRequestError(completion: completion) diff --git a/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift b/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift index 8b3562b4..8f59920f 100644 --- a/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift +++ b/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift @@ -100,7 +100,7 @@ final class VideoPlayerViewModel: ViewModel { // MARK: General - let item: BaseItemDto + private(set) var item: BaseItemDto let title: String let subtitle: String? let streamURL: URL @@ -235,6 +235,22 @@ final class VideoPlayerViewModel: ViewModel { } } +// MARK: Injected Values + +extension VideoPlayerViewModel { + + // Injects custom values that override certain settings + func injectCustomValues(startFromBeginning: Bool = false) { + + if startFromBeginning { + item.userData?.playbackPositionTicks = 0 + item.userData?.playedPercentage = 0 + sliderPercentage = 0 + sliderPercentageChanged(newValue: 0) + } + } +} + // MARK: Adjacent Items extension VideoPlayerViewModel { diff --git a/Swiftfin tvOS/Components/EpisodesRowView/EpisodesRowCard.swift b/Swiftfin tvOS/Components/EpisodesRowView/EpisodesRowCard.swift index 4e6f3772..f0fdba86 100644 --- a/Swiftfin tvOS/Components/EpisodesRowView/EpisodesRowCard.swift +++ b/Swiftfin tvOS/Components/EpisodesRowView/EpisodesRowCard.swift @@ -17,45 +17,47 @@ struct EpisodeRowCard: View { let episode: BaseItemDto var body: some View { - Button { - itemRouter.route(to: \.item, episode) - } label: { - HStack(alignment: .top) { - VStack(alignment: .leading) { + VStack { + Button { + itemRouter.route(to: \.item, episode) + } label: { + ImageView(src: episode.getBackdropImage(maxWidth: 550), + bh: episode.getBackdropImageBlurHash()) + .mask(Rectangle().frame(width: 550, height: 308)) + .frame(width: 550, height: 308) + } + .buttonStyle(CardButtonStyle()) + + VStack(alignment: .leading) { - ImageView(src: episode.getBackdropImage(maxWidth: 500), - bh: episode.getBackdropImageBlurHash()) - .mask(Rectangle().frame(width: 500, height: 280)) - .frame(width: 500, height: 280) + VStack(alignment: .leading) { + Text(episode.getEpisodeLocator() ?? "") + .font(.caption) + .foregroundColor(.secondary) + Text(episode.name ?? "") + .font(.footnote) + .padding(.bottom, 1) - VStack(alignment: .leading) { - Text(episode.getEpisodeLocator() ?? "") - .font(.caption) - .foregroundColor(.secondary) - Text(episode.name ?? "") - .font(.footnote) - .padding(.bottom, 1) + if episode.unaired { + Text(episode.airDateLabel ?? L10n.noOverviewAvailable) + .font(.caption) + .foregroundColor(.secondary) + .fontWeight(.light) + .lineLimit(3) + } else { + Text(episode.overview ?? "") + .font(.caption) + .fontWeight(.light) + .lineLimit(4) + .fixedSize(horizontal: false, vertical: true) + } + } - if episode.unaired { - Text(episode.airDateLabel ?? L10n.noOverviewAvailable) - .font(.caption) - .foregroundColor(.secondary) - .fontWeight(.light) - .lineLimit(3) - } else { - Text(episode.overview ?? "") - .font(.caption) - .fontWeight(.light) - .lineLimit(4) - } - } - .padding(.horizontal) - - Spacer() - } - .frame(width: 500) - } - } - .buttonStyle(PlainButtonStyle()) + Spacer() + } + .padding() + .frame(width: 550) + } + .focusSection() } } diff --git a/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift b/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift index 512b841b..da737552 100644 --- a/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift +++ b/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift @@ -13,6 +13,8 @@ struct CinematicResumeCardView: View { @EnvironmentObject var homeRouter: HomeCoordinator.Router + @ObservedObject + var viewModel: HomeViewModel let item: BaseItemDto var body: some View { @@ -53,8 +55,15 @@ struct CinematicResumeCardView: View { } .frame(width: 350, height: 210) } - .buttonStyle(CardButtonStyle()) - .padding(.top) + .buttonStyle(CardButtonStyle()) + .padding(.top) + .contextMenu { + Button(role: .destructive) { + viewModel.removeItemFromResume(item) + } label: { + L10n.removeFromResume.text + } + } } .padding(.vertical) } diff --git a/Swiftfin tvOS/Components/HomeCinematicView/HomeCinematicView.swift b/Swiftfin tvOS/Components/HomeCinematicView/HomeCinematicView.swift index b616f260..ac4527b2 100644 --- a/Swiftfin tvOS/Components/HomeCinematicView/HomeCinematicView.swift +++ b/Swiftfin tvOS/Components/HomeCinematicView/HomeCinematicView.swift @@ -33,6 +33,8 @@ struct HomeCinematicView: View { @FocusState var selectedItem: BaseItemDto? + @ObservedObject + var viewModel: HomeViewModel @State private var updatedSelectedItem: BaseItemDto? @State @@ -41,7 +43,8 @@ struct HomeCinematicView: View { private let items: [HomeCinematicViewItem] private let backgroundViewModel = DynamicCinematicBackgroundViewModel() - init(items: [HomeCinematicViewItem], forcedItemSubtitle: String? = nil) { + init(viewModel: HomeViewModel, items: [HomeCinematicViewItem], forcedItemSubtitle: String? = nil) { + self.viewModel = viewModel self.items = items self.forcedItemSubtitle = forcedItemSubtitle } @@ -99,7 +102,7 @@ struct HomeCinematicView: View { CinematicNextUpCardView(item: item.item, showOverlay: true) .focused($selectedItem, equals: item.item) case .resume: - CinematicResumeCardView(item: item.item) + CinematicResumeCardView(viewModel: viewModel, item: item.item) .focused($selectedItem, equals: item.item) case .plain: CinematicNextUpCardView(item: item.item, showOverlay: false) diff --git a/Swiftfin tvOS/Views/HomeView.swift b/Swiftfin tvOS/Views/HomeView.swift index b4ca465d..86f963e4 100644 --- a/Swiftfin tvOS/Views/HomeView.swift +++ b/Swiftfin tvOS/Views/HomeView.swift @@ -15,7 +15,7 @@ struct HomeView: View { @EnvironmentObject var homeRouter: HomeCoordinator.Router - @ObservedObject + @StateObject var viewModel = HomeViewModel() @Default(.showPosterLabels) var showPosterLabels @@ -32,7 +32,8 @@ struct HomeView: View { LazyVStack(alignment: .leading) { if viewModel.resumeItems.isEmpty { - HomeCinematicView(items: viewModel.latestAddedItems.map { .init(item: $0, type: .plain) }, + HomeCinematicView(viewModel: viewModel, + items: viewModel.latestAddedItems.map { .init(item: $0, type: .plain) }, forcedItemSubtitle: L10n.recentlyAdded) if !viewModel.nextUpItems.isEmpty { @@ -40,7 +41,8 @@ struct HomeView: View { .focusSection() } } else { - HomeCinematicView(items: viewModel.resumeItems.map { .init(item: $0, type: .resume) }) + HomeCinematicView(viewModel: viewModel, + items: viewModel.resumeItems.map { .init(item: $0, type: .resume) }) if !viewModel.nextUpItems.isEmpty { NextUpView(items: viewModel.nextUpItems) diff --git a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemViewTopRow.swift b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemViewTopRow.swift index 0c95276d..599bd140 100644 --- a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemViewTopRow.swift +++ b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemViewTopRow.swift @@ -78,6 +78,20 @@ struct CinematicItemViewTopRow: View { .cornerRadius(10) } .buttonStyle(CardButtonStyle()) + .contextMenu { + if viewModel.playButtonItem != nil, viewModel.item.userData?.playbackPositionTicks ?? 0 > 0 { + Button { + if let itemVideoPlayerViewModel = viewModel.itemVideoPlayerViewModel { + itemVideoPlayerViewModel.injectCustomValues(startFromBeginning: true) + itemRouter.route(to: \.videoPlayer, itemVideoPlayerViewModel) + } else { + LogManager.shared.log.error("Attempted to play item but no playback information available") + } + } label: { + Label(L10n.playFromBeginning, systemImage: "gobackward") + } + } + } } } diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 8ec7e1f9..b536f07b 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -2797,7 +2797,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2834,7 +2834,7 @@ CURRENT_PROJECT_VERSION = 66; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2865,7 +2865,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -2892,7 +2892,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/Swiftfin/Views/ContinueWatchingView.swift b/Swiftfin/Views/ContinueWatchingView.swift index 6dd6b04e..3b81ef21 100644 --- a/Swiftfin/Views/ContinueWatchingView.swift +++ b/Swiftfin/Views/ContinueWatchingView.swift @@ -81,6 +81,13 @@ struct ContinueWatchingView: View { } } } + .contextMenu { + Button(role: .destructive) { + viewModel.removeItemFromResume(item) + } label: { + L10n.removeFromResume.text + } + } } } .padding(.horizontal) diff --git a/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift b/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift index 62c2c103..89250904 100644 --- a/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift +++ b/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift @@ -50,6 +50,20 @@ struct ItemLandscapeMainView: View { .cornerRadius(10) } .disabled(viewModel.playButtonItem == nil || viewModel.itemVideoPlayerViewModel == nil) + .contextMenu { + if viewModel.playButtonItem != nil, viewModel.item.userData?.playbackPositionTicks ?? 0 > 0 { + Button { + if let itemVideoPlayerViewModel = viewModel.itemVideoPlayerViewModel { + itemVideoPlayerViewModel.injectCustomValues(startFromBeginning: true) + itemRouter.route(to: \.videoPlayer, itemVideoPlayerViewModel) + } else { + LogManager.shared.log.error("Attempted to play item but no playback information available") + } + } label: { + Label(L10n.playFromBeginning, systemImage: "gobackward") + } + } + } Spacer() } diff --git a/Swiftfin/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift b/Swiftfin/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift index fec028d8..462155d0 100644 --- a/Swiftfin/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift +++ b/Swiftfin/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift @@ -90,7 +90,11 @@ struct PortraitHeaderOverlayView: View { // MARK: Play Button { - self.itemRouter.route(to: \.videoPlayer, viewModel.itemVideoPlayerViewModel!) + if let itemVideoPlayerViewModel = viewModel.itemVideoPlayerViewModel { + itemRouter.route(to: \.videoPlayer, itemVideoPlayerViewModel) + } else { + LogManager.shared.log.error("Attempted to play item but no playback information available") + } } label: { HStack { Image(systemName: "play.fill") @@ -104,7 +108,22 @@ struct PortraitHeaderOverlayView: View { .frame(width: 130, height: 40) .background(viewModel.playButtonItem == nil ? Color(UIColor.secondarySystemFill) : Color.jellyfinPurple) .cornerRadius(10) - }.disabled(viewModel.playButtonItem == nil) + } + .disabled(viewModel.playButtonItem == nil) + .contextMenu { + if viewModel.playButtonItem != nil, viewModel.item.userData?.playbackPositionTicks ?? 0 > 0 { + Button { + if let itemVideoPlayerViewModel = viewModel.itemVideoPlayerViewModel { + itemVideoPlayerViewModel.injectCustomValues(startFromBeginning: true) + itemRouter.route(to: \.videoPlayer, itemVideoPlayerViewModel) + } else { + LogManager.shared.log.error("Attempted to play item but no playback information available") + } + } label: { + Label(L10n.playFromBeginning, systemImage: "gobackward") + } + } + } Spacer() diff --git a/Translations/en.lproj/Localizable.strings b/Translations/en.lproj/Localizable.strings index 4d839fe9db6c7d8d67f299f5c574cb78c8932172..2b05086166506f9a65048b53d7b8c5c2b4dd789f 100644 GIT binary patch delta 138 zcmZ1xej{ST3hl`^r1^y17>XG38FCqd7*ZLE8A^ey)X9HECGBD23dk}HN(|NvTtKpb mA%`K6p^^cn$_c1DogtGU4+ueI`s9Z)qDnAX1qKXxs7U}wMIKlH delta 7 OcmcZ+u_AoK3T*%oE(5aw From 5e9d3a1c9ce50c9b4e356d1005aa650f67f5481d Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Wed, 12 Jan 2022 13:19:59 -0700 Subject: [PATCH 2/5] remove team and cancel button for tvOS --- .../CinematicItemView/CinematicItemViewTopRow.swift | 6 ++++++ Swiftfin.xcodeproj/project.pbxproj | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemViewTopRow.swift b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemViewTopRow.swift index 599bd140..aa5df4a6 100644 --- a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemViewTopRow.swift +++ b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemViewTopRow.swift @@ -90,6 +90,12 @@ struct CinematicItemViewTopRow: View { } label: { Label(L10n.playFromBeginning, systemImage: "gobackward") } + + Button(role: .cancel) { + + } label: { + L10n.cancel.text + } } } } diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index b536f07b..8ec7e1f9 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -2797,7 +2797,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2834,7 +2834,7 @@ CURRENT_PROJECT_VERSION = 66; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2865,7 +2865,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -2892,7 +2892,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( From 2d16b2886b098afab257721c6534a6028bf9f1dd Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Wed, 12 Jan 2022 13:21:31 -0700 Subject: [PATCH 3/5] lint --- Shared/ViewModels/HomeViewModel.swift | 28 +++---- .../CollectionItemViewModel.swift | 2 +- .../VideoPlayerViewModel.swift | 22 +++--- .../EpisodesRowView/EpisodesRowCard.swift | 78 +++++++++---------- .../CinematicResumeCardView.swift | 22 +++--- .../HomeCinematicView/HomeCinematicView.swift | 10 +-- Swiftfin tvOS/Views/HomeView.swift | 6 +- .../CinematicItemViewTopRow.swift | 38 +++++---- Swiftfin/Views/ContinueWatchingView.swift | 14 ++-- .../Landscape/ItemLandscapeMainView.swift | 28 +++---- .../ItemPortraitHeaderOverlayView.swift | 40 +++++----- 11 files changed, 143 insertions(+), 145 deletions(-) diff --git a/Shared/ViewModels/HomeViewModel.swift b/Shared/ViewModels/HomeViewModel.swift index 8ef6a462..1ed0c40b 100644 --- a/Shared/ViewModels/HomeViewModel.swift +++ b/Shared/ViewModels/HomeViewModel.swift @@ -184,20 +184,20 @@ final class HomeViewModel: ViewModel { }) .store(in: &cancellables) } - - func removeItemFromResume(_ item: BaseItemDto) { - guard let itemID = item.id, resumeItems.contains(where: { $0.id == itemID }) else { return } - - PlaystateAPI.markUnplayedItem(userId: SessionManager.main.currentLogin.user.id, - itemId: item.id!) - .sink(receiveCompletion: { [weak self] completion in - self?.handleAPIRequestError(completion: completion) - }, receiveValue: { _ in - self.refreshResumeItems() - self.refreshNextUpItems() - }) - .store(in: &cancellables) - } + + func removeItemFromResume(_ item: BaseItemDto) { + guard let itemID = item.id, resumeItems.contains(where: { $0.id == itemID }) else { return } + + PlaystateAPI.markUnplayedItem(userId: SessionManager.main.currentLogin.user.id, + itemId: item.id!) + .sink(receiveCompletion: { [weak self] completion in + self?.handleAPIRequestError(completion: completion) + }, receiveValue: { _ in + self.refreshResumeItems() + self.refreshNextUpItems() + }) + .store(in: &cancellables) + } // MARK: Next Up Items diff --git a/Shared/ViewModels/ItemViewModel/CollectionItemViewModel.swift b/Shared/ViewModels/ItemViewModel/CollectionItemViewModel.swift index cd14f9ce..5d094b9c 100644 --- a/Shared/ViewModels/ItemViewModel/CollectionItemViewModel.swift +++ b/Shared/ViewModels/ItemViewModel/CollectionItemViewModel.swift @@ -24,7 +24,7 @@ final class CollectionItemViewModel: ItemViewModel { private func getCollectionItems() { ItemsAPI.getItems(userId: SessionManager.main.currentLogin.user.id, parentId: item.id, - fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people]) + fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people]) .trackActivity(loading) .sink { [weak self] completion in self?.handleAPIRequestError(completion: completion) diff --git a/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift b/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift index 8f59920f..59cf368a 100644 --- a/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift +++ b/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift @@ -238,17 +238,17 @@ final class VideoPlayerViewModel: ViewModel { // MARK: Injected Values extension VideoPlayerViewModel { - - // Injects custom values that override certain settings - func injectCustomValues(startFromBeginning: Bool = false) { - - if startFromBeginning { - item.userData?.playbackPositionTicks = 0 - item.userData?.playedPercentage = 0 - sliderPercentage = 0 - sliderPercentageChanged(newValue: 0) - } - } + + // Injects custom values that override certain settings + func injectCustomValues(startFromBeginning: Bool = false) { + + if startFromBeginning { + item.userData?.playbackPositionTicks = 0 + item.userData?.playedPercentage = 0 + sliderPercentage = 0 + sliderPercentageChanged(newValue: 0) + } + } } // MARK: Adjacent Items diff --git a/Swiftfin tvOS/Components/EpisodesRowView/EpisodesRowCard.swift b/Swiftfin tvOS/Components/EpisodesRowView/EpisodesRowCard.swift index f0fdba86..0f27d13d 100644 --- a/Swiftfin tvOS/Components/EpisodesRowView/EpisodesRowCard.swift +++ b/Swiftfin tvOS/Components/EpisodesRowView/EpisodesRowCard.swift @@ -17,47 +17,47 @@ struct EpisodeRowCard: View { let episode: BaseItemDto var body: some View { - VStack { - Button { - itemRouter.route(to: \.item, episode) - } label: { - ImageView(src: episode.getBackdropImage(maxWidth: 550), - bh: episode.getBackdropImageBlurHash()) - .mask(Rectangle().frame(width: 550, height: 308)) - .frame(width: 550, height: 308) - } - .buttonStyle(CardButtonStyle()) - - VStack(alignment: .leading) { + VStack { + Button { + itemRouter.route(to: \.item, episode) + } label: { + ImageView(src: episode.getBackdropImage(maxWidth: 550), + bh: episode.getBackdropImageBlurHash()) + .mask(Rectangle().frame(width: 550, height: 308)) + .frame(width: 550, height: 308) + } + .buttonStyle(CardButtonStyle()) - VStack(alignment: .leading) { - Text(episode.getEpisodeLocator() ?? "") - .font(.caption) - .foregroundColor(.secondary) - Text(episode.name ?? "") - .font(.footnote) - .padding(.bottom, 1) + VStack(alignment: .leading) { - if episode.unaired { - Text(episode.airDateLabel ?? L10n.noOverviewAvailable) - .font(.caption) - .foregroundColor(.secondary) - .fontWeight(.light) - .lineLimit(3) - } else { - Text(episode.overview ?? "") - .font(.caption) - .fontWeight(.light) - .lineLimit(4) - .fixedSize(horizontal: false, vertical: true) - } - } + VStack(alignment: .leading) { + Text(episode.getEpisodeLocator() ?? "") + .font(.caption) + .foregroundColor(.secondary) + Text(episode.name ?? "") + .font(.footnote) + .padding(.bottom, 1) - Spacer() - } - .padding() - .frame(width: 550) - } - .focusSection() + if episode.unaired { + Text(episode.airDateLabel ?? L10n.noOverviewAvailable) + .font(.caption) + .foregroundColor(.secondary) + .fontWeight(.light) + .lineLimit(3) + } else { + Text(episode.overview ?? "") + .font(.caption) + .fontWeight(.light) + .lineLimit(4) + .fixedSize(horizontal: false, vertical: true) + } + } + + Spacer() + } + .padding() + .frame(width: 550) + } + .focusSection() } } diff --git a/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift b/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift index da737552..8586b5b4 100644 --- a/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift +++ b/Swiftfin tvOS/Components/HomeCinematicView/CinematicResumeCardView.swift @@ -13,8 +13,8 @@ struct CinematicResumeCardView: View { @EnvironmentObject var homeRouter: HomeCoordinator.Router - @ObservedObject - var viewModel: HomeViewModel + @ObservedObject + var viewModel: HomeViewModel let item: BaseItemDto var body: some View { @@ -55,15 +55,15 @@ struct CinematicResumeCardView: View { } .frame(width: 350, height: 210) } - .buttonStyle(CardButtonStyle()) - .padding(.top) - .contextMenu { - Button(role: .destructive) { - viewModel.removeItemFromResume(item) - } label: { - L10n.removeFromResume.text - } - } + .buttonStyle(CardButtonStyle()) + .padding(.top) + .contextMenu { + Button(role: .destructive) { + viewModel.removeItemFromResume(item) + } label: { + L10n.removeFromResume.text + } + } } .padding(.vertical) } diff --git a/Swiftfin tvOS/Components/HomeCinematicView/HomeCinematicView.swift b/Swiftfin tvOS/Components/HomeCinematicView/HomeCinematicView.swift index ac4527b2..902220b0 100644 --- a/Swiftfin tvOS/Components/HomeCinematicView/HomeCinematicView.swift +++ b/Swiftfin tvOS/Components/HomeCinematicView/HomeCinematicView.swift @@ -33,8 +33,8 @@ struct HomeCinematicView: View { @FocusState var selectedItem: BaseItemDto? - @ObservedObject - var viewModel: HomeViewModel + @ObservedObject + var viewModel: HomeViewModel @State private var updatedSelectedItem: BaseItemDto? @State @@ -43,8 +43,8 @@ struct HomeCinematicView: View { private let items: [HomeCinematicViewItem] private let backgroundViewModel = DynamicCinematicBackgroundViewModel() - init(viewModel: HomeViewModel, items: [HomeCinematicViewItem], forcedItemSubtitle: String? = nil) { - self.viewModel = viewModel + init(viewModel: HomeViewModel, items: [HomeCinematicViewItem], forcedItemSubtitle: String? = nil) { + self.viewModel = viewModel self.items = items self.forcedItemSubtitle = forcedItemSubtitle } @@ -102,7 +102,7 @@ struct HomeCinematicView: View { CinematicNextUpCardView(item: item.item, showOverlay: true) .focused($selectedItem, equals: item.item) case .resume: - CinematicResumeCardView(viewModel: viewModel, item: item.item) + CinematicResumeCardView(viewModel: viewModel, item: item.item) .focused($selectedItem, equals: item.item) case .plain: CinematicNextUpCardView(item: item.item, showOverlay: false) diff --git a/Swiftfin tvOS/Views/HomeView.swift b/Swiftfin tvOS/Views/HomeView.swift index 86f963e4..1d341e2c 100644 --- a/Swiftfin tvOS/Views/HomeView.swift +++ b/Swiftfin tvOS/Views/HomeView.swift @@ -32,8 +32,8 @@ struct HomeView: View { LazyVStack(alignment: .leading) { if viewModel.resumeItems.isEmpty { - HomeCinematicView(viewModel: viewModel, - items: viewModel.latestAddedItems.map { .init(item: $0, type: .plain) }, + HomeCinematicView(viewModel: viewModel, + items: viewModel.latestAddedItems.map { .init(item: $0, type: .plain) }, forcedItemSubtitle: L10n.recentlyAdded) if !viewModel.nextUpItems.isEmpty { @@ -42,7 +42,7 @@ struct HomeView: View { } } else { HomeCinematicView(viewModel: viewModel, - items: viewModel.resumeItems.map { .init(item: $0, type: .resume) }) + items: viewModel.resumeItems.map { .init(item: $0, type: .resume) }) if !viewModel.nextUpItems.isEmpty { NextUpView(items: viewModel.nextUpItems) diff --git a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemViewTopRow.swift b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemViewTopRow.swift index aa5df4a6..c8c4bf9a 100644 --- a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemViewTopRow.swift +++ b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicItemViewTopRow.swift @@ -78,26 +78,24 @@ struct CinematicItemViewTopRow: View { .cornerRadius(10) } .buttonStyle(CardButtonStyle()) - .contextMenu { - if viewModel.playButtonItem != nil, viewModel.item.userData?.playbackPositionTicks ?? 0 > 0 { - Button { - if let itemVideoPlayerViewModel = viewModel.itemVideoPlayerViewModel { - itemVideoPlayerViewModel.injectCustomValues(startFromBeginning: true) - itemRouter.route(to: \.videoPlayer, itemVideoPlayerViewModel) - } else { - LogManager.shared.log.error("Attempted to play item but no playback information available") - } - } label: { - Label(L10n.playFromBeginning, systemImage: "gobackward") - } - - Button(role: .cancel) { - - } label: { - L10n.cancel.text - } - } - } + .contextMenu { + if viewModel.playButtonItem != nil, viewModel.item.userData?.playbackPositionTicks ?? 0 > 0 { + Button { + if let itemVideoPlayerViewModel = viewModel.itemVideoPlayerViewModel { + itemVideoPlayerViewModel.injectCustomValues(startFromBeginning: true) + itemRouter.route(to: \.videoPlayer, itemVideoPlayerViewModel) + } else { + LogManager.shared.log.error("Attempted to play item but no playback information available") + } + } label: { + Label(L10n.playFromBeginning, systemImage: "gobackward") + } + + Button(role: .cancel) {} label: { + L10n.cancel.text + } + } + } } } diff --git a/Swiftfin/Views/ContinueWatchingView.swift b/Swiftfin/Views/ContinueWatchingView.swift index 3b81ef21..8c5c04c2 100644 --- a/Swiftfin/Views/ContinueWatchingView.swift +++ b/Swiftfin/Views/ContinueWatchingView.swift @@ -81,13 +81,13 @@ struct ContinueWatchingView: View { } } } - .contextMenu { - Button(role: .destructive) { - viewModel.removeItemFromResume(item) - } label: { - L10n.removeFromResume.text - } - } + .contextMenu { + Button(role: .destructive) { + viewModel.removeItemFromResume(item) + } label: { + L10n.removeFromResume.text + } + } } } .padding(.horizontal) diff --git a/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift b/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift index 89250904..c8c783aa 100644 --- a/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift +++ b/Swiftfin/Views/ItemView/Landscape/ItemLandscapeMainView.swift @@ -50,20 +50,20 @@ struct ItemLandscapeMainView: View { .cornerRadius(10) } .disabled(viewModel.playButtonItem == nil || viewModel.itemVideoPlayerViewModel == nil) - .contextMenu { - if viewModel.playButtonItem != nil, viewModel.item.userData?.playbackPositionTicks ?? 0 > 0 { - Button { - if let itemVideoPlayerViewModel = viewModel.itemVideoPlayerViewModel { - itemVideoPlayerViewModel.injectCustomValues(startFromBeginning: true) - itemRouter.route(to: \.videoPlayer, itemVideoPlayerViewModel) - } else { - LogManager.shared.log.error("Attempted to play item but no playback information available") - } - } label: { - Label(L10n.playFromBeginning, systemImage: "gobackward") - } - } - } + .contextMenu { + if viewModel.playButtonItem != nil, viewModel.item.userData?.playbackPositionTicks ?? 0 > 0 { + Button { + if let itemVideoPlayerViewModel = viewModel.itemVideoPlayerViewModel { + itemVideoPlayerViewModel.injectCustomValues(startFromBeginning: true) + itemRouter.route(to: \.videoPlayer, itemVideoPlayerViewModel) + } else { + LogManager.shared.log.error("Attempted to play item but no playback information available") + } + } label: { + Label(L10n.playFromBeginning, systemImage: "gobackward") + } + } + } Spacer() } diff --git a/Swiftfin/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift b/Swiftfin/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift index 462155d0..b7480f12 100644 --- a/Swiftfin/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift +++ b/Swiftfin/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift @@ -90,11 +90,11 @@ struct PortraitHeaderOverlayView: View { // MARK: Play Button { - if let itemVideoPlayerViewModel = viewModel.itemVideoPlayerViewModel { - itemRouter.route(to: \.videoPlayer, itemVideoPlayerViewModel) - } else { - LogManager.shared.log.error("Attempted to play item but no playback information available") - } + if let itemVideoPlayerViewModel = viewModel.itemVideoPlayerViewModel { + itemRouter.route(to: \.videoPlayer, itemVideoPlayerViewModel) + } else { + LogManager.shared.log.error("Attempted to play item but no playback information available") + } } label: { HStack { Image(systemName: "play.fill") @@ -109,21 +109,21 @@ struct PortraitHeaderOverlayView: View { .background(viewModel.playButtonItem == nil ? Color(UIColor.secondarySystemFill) : Color.jellyfinPurple) .cornerRadius(10) } - .disabled(viewModel.playButtonItem == nil) - .contextMenu { - if viewModel.playButtonItem != nil, viewModel.item.userData?.playbackPositionTicks ?? 0 > 0 { - Button { - if let itemVideoPlayerViewModel = viewModel.itemVideoPlayerViewModel { - itemVideoPlayerViewModel.injectCustomValues(startFromBeginning: true) - itemRouter.route(to: \.videoPlayer, itemVideoPlayerViewModel) - } else { - LogManager.shared.log.error("Attempted to play item but no playback information available") - } - } label: { - Label(L10n.playFromBeginning, systemImage: "gobackward") - } - } - } + .disabled(viewModel.playButtonItem == nil) + .contextMenu { + if viewModel.playButtonItem != nil, viewModel.item.userData?.playbackPositionTicks ?? 0 > 0 { + Button { + if let itemVideoPlayerViewModel = viewModel.itemVideoPlayerViewModel { + itemVideoPlayerViewModel.injectCustomValues(startFromBeginning: true) + itemRouter.route(to: \.videoPlayer, itemVideoPlayerViewModel) + } else { + LogManager.shared.log.error("Attempted to play item but no playback information available") + } + } label: { + Label(L10n.playFromBeginning, systemImage: "gobackward") + } + } + } Spacer() From 359986570eff4784aac72d95da7d725417248bd9 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Wed, 12 Jan 2022 17:27:04 -0700 Subject: [PATCH 4/5] reimplement NukeUI --- Shared/Views/ImageView.swift | 15 ++++---- Swiftfin.xcodeproj/project.pbxproj | 21 ++++++++++-- .../xcshareddata/swiftpm/Package.resolved | 34 ++++++++++++++----- 3 files changed, 51 insertions(+), 19 deletions(-) diff --git a/Shared/Views/ImageView.swift b/Shared/Views/ImageView.swift index 8fb14bdf..911e932b 100644 --- a/Shared/Views/ImageView.swift +++ b/Shared/Views/ImageView.swift @@ -6,6 +6,7 @@ // Copyright (c) 2022 Jellyfin & Jellyfin Contributors // +import NukeUI import SwiftUI struct ImageView: View { @@ -40,17 +41,12 @@ struct ImageView: View { } var body: some View { - AsyncImage(url: source, transaction: Transaction(animation: .easeInOut)) { phase in - switch phase { - case let .success(image): + LazyImage(source: source) { state in + if let image = state.image { image - .resizable() - .aspectRatio(contentMode: .fill) - case .failure: + } else if state.error != nil { failureImage - default: - // TODO: remove once placeholder hash image fixed - + } else { #if os(tvOS) ZStack { Color.black.ignoresSafeArea() @@ -66,5 +62,6 @@ struct ImageView: View { #endif } } + .pipeline(ImagePipeline(configuration: .withDataCache)) } } diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 8ec7e1f9..fa5223e3 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -291,6 +291,7 @@ E131691726C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; }; E131691826C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; }; E131691926C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; }; + E1361DA7278FA7A300BEC523 /* NukeUI in Frameworks */ = {isa = PBXBuildFile; productRef = E1361DA6278FA7A300BEC523 /* NukeUI */; }; E1384944278036C70024FB48 /* VLCPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1384943278036C70024FB48 /* VLCPlayerViewController.swift */; }; E13849452780370B0024FB48 /* PlaybackSpeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C812B4277A8E5D00918266 /* PlaybackSpeed.swift */; }; E13DD3BF27163DD7009D4DAF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13DD3BE27163DD7009D4DAF /* AppDelegate.swift */; }; @@ -799,6 +800,7 @@ buildActionMask = 2147483647; files = ( E13DD3D327168E65009D4DAF /* Defaults in Frameworks */, + E1361DA7278FA7A300BEC523 /* NukeUI in Frameworks */, 53649AAD269CFAEA00A2D8B7 /* Puppy in Frameworks */, E10EAA4D277BB716000269ED /* Sliders in Frameworks */, 62C29E9C26D0FE4200C1D2E7 /* Stinsen in Frameworks */, @@ -1752,6 +1754,7 @@ E10EAA44277BB646000269ED /* JellyfinAPI */, E10EAA4C277BB716000269ED /* Sliders */, E1AE8E7B2789135A00FBDDAA /* Nuke */, + E1361DA6278FA7A300BEC523 /* NukeUI */, ); productName = JellyfinPlayer; productReference = 5377CBF1263B596A003A4E83 /* Swiftfin iOS.app */; @@ -1844,6 +1847,7 @@ E10EAA43277BB646000269ED /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */, E10EAA4B277BB716000269ED /* XCRemoteSwiftPackageReference "swiftui-sliders" */, E1AE8E7A2789135A00FBDDAA /* XCRemoteSwiftPackageReference "Nuke" */, + E1361DA5278FA7A300BEC523 /* XCRemoteSwiftPackageReference "NukeUI" */, ); productRefGroup = 5377CBF2263B596A003A4E83 /* Products */; projectDirPath = ""; @@ -3024,6 +3028,14 @@ minimumVersion = 1.0.0; }; }; + E1361DA5278FA7A300BEC523 /* XCRemoteSwiftPackageReference "NukeUI" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/kean/NukeUI"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.7.0; + }; + }; E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/JohnEstropia/CoreStore.git"; @@ -3044,8 +3056,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/kean/Nuke"; requirement = { - kind = upToNextMinorVersion; - minimumVersion = 9.6.0; + kind = upToNextMajorVersion; + minimumVersion = 10.0.0; }; }; E1C16B89271A2180009A5D25 /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = { @@ -3139,6 +3151,11 @@ package = E1267D42271A212C003C492E /* XCRemoteSwiftPackageReference "CombineExt" */; productName = CombineExt; }; + E1361DA6278FA7A300BEC523 /* NukeUI */ = { + isa = XCSwiftPackageProductDependency; + package = E1361DA5278FA7A300BEC523 /* XCRemoteSwiftPackageReference "NukeUI" */; + productName = NukeUI; + }; E13DD3C52716499E009D4DAF /* CoreStore */ = { isa = XCSwiftPackageProductDependency; package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */; diff --git a/Swiftfin.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Swiftfin.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3ed0ea18..00182b81 100644 --- a/Swiftfin.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Swiftfin.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -24,8 +24,8 @@ "repositoryURL": "https://github.com/CombineCommunity/CombineExt", "state": { "branch": null, - "revision": "0880829102152185190064fd17847a7c681d2127", - "version": "1.5.1" + "revision": "8ca006df5e3cc6bb176b70238e2b0014bbc3a235", + "version": "1.0.0" } }, { @@ -42,8 +42,17 @@ "repositoryURL": "https://github.com/sindresorhus/Defaults", "state": { "branch": null, - "revision": "55f3302c3ab30a8760f10042d0ebc0a6907f865a", - "version": "6.1.0" + "revision": "8a6e4a96fd38504a05903d136c85634b65fd7c4d", + "version": "6.0.0" + } + }, + { + "package": "Gifu", + "repositoryURL": "https://github.com/kaishin/Gifu", + "state": { + "branch": null, + "revision": "51f2eab32903e336f590c013267cfa4d7f8b06c4", + "version": "3.3.1" } }, { @@ -60,8 +69,17 @@ "repositoryURL": "https://github.com/kean/Nuke", "state": { "branch": null, - "revision": "7f73ceaeacd5df75a7994cd82e165ad9ff1815db", - "version": "9.6.1" + "revision": "6be3e778f1663b16dd645b7e8a0a01f73b5ed7f3", + "version": "10.6.1" + } + }, + { + "package": "NukeUI", + "repositoryURL": "https://github.com/kean/NukeUI", + "state": { + "branch": null, + "revision": "08e953d8d80b409bebcd95ba0635fdd748934ce0", + "version": "0.7.0" } }, { @@ -78,8 +96,8 @@ "repositoryURL": "https://github.com/sushichop/Puppy", "state": { "branch": null, - "revision": "95ce04b0e778b8d7c351876bc98bbf68328dfc9b", - "version": "0.3.1" + "revision": "dc82e65c749cee431ffbb8c0913680b61ccd7e08", + "version": "0.2.0" } }, { From 88295d78da08025916f2a9e99f91891d906e9334 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Wed, 12 Jan 2022 17:28:38 -0700 Subject: [PATCH 5/5] add NukeUI to tvOS project --- Swiftfin.xcodeproj/project.pbxproj | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index fa5223e3..e91d1ec5 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -281,6 +281,7 @@ E11B1B6E2718CDBA006DA3E8 /* JellyfinAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */; }; E11D224227378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */; }; E11D224327378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */; }; + E11D83AF278FA998006E9776 /* NukeUI in Frameworks */ = {isa = PBXBuildFile; productRef = E11D83AE278FA998006E9776 /* NukeUI */; }; E12186DE2718F1C50010884C /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = E12186DD2718F1C50010884C /* Defaults */; }; E1218C9E271A2CD600EA0737 /* CombineExt in Frameworks */ = {isa = PBXBuildFile; productRef = E1218C9D271A2CD600EA0737 /* CombineExt */; }; E122A9132788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */; }; @@ -780,6 +781,7 @@ buildActionMask = 2147483647; files = ( 53649AAF269CFAF600A2D8B7 /* Puppy in Frameworks */, + E11D83AF278FA998006E9776 /* NukeUI in Frameworks */, E1218C9E271A2CD600EA0737 /* CombineExt in Frameworks */, 6220D0C926D63F3700B8E046 /* Stinsen in Frameworks */, 535870912669D7A800D05A09 /* Introspect in Frameworks */, @@ -1716,6 +1718,7 @@ E1A9999A271A343C008E78C0 /* SwiftUICollection */, E178857C278037FD0094FBCF /* JellyfinAPI */, E1AE8E7D2789136D00FBDDAA /* Nuke */, + E11D83AE278FA998006E9776 /* NukeUI */, ); productName = "JellyfinPlayer tvOS"; productReference = 535870602669D21600D05A09 /* Swiftfin tvOS.app */; @@ -3141,6 +3144,11 @@ package = E10EAA4B277BB716000269ED /* XCRemoteSwiftPackageReference "swiftui-sliders" */; productName = Sliders; }; + E11D83AE278FA998006E9776 /* NukeUI */ = { + isa = XCSwiftPackageProductDependency; + package = E1361DA5278FA7A300BEC523 /* XCRemoteSwiftPackageReference "NukeUI" */; + productName = NukeUI; + }; E12186DD2718F1C50010884C /* Defaults */ = { isa = XCSwiftPackageProductDependency; package = E13DD3D127168E65009D4DAF /* XCRemoteSwiftPackageReference "Defaults" */;