diff --git a/Swiftfin tvOS/Components/PosterButton.swift b/Swiftfin tvOS/Components/PosterButton.swift index 6ad58f255..a484710bd 100644 --- a/Swiftfin tvOS/Components/PosterButton.swift +++ b/Swiftfin tvOS/Components/PosterButton.swift @@ -57,6 +57,7 @@ struct PosterButton: View { ) } } + .accessibilityIgnoresInvertColors() imageOverlay() .eraseToAnyView() diff --git a/Swiftfin tvOS/Views/ItemView/Components/AboutView/AboutView.swift b/Swiftfin tvOS/Views/ItemView/Components/AboutView/AboutView.swift index b84a0fe26..b87140ef5 100644 --- a/Swiftfin tvOS/Views/ItemView/Components/AboutView/AboutView.swift +++ b/Swiftfin tvOS/Views/ItemView/Components/AboutView/AboutView.swift @@ -26,14 +26,7 @@ extension ItemView { ScrollView(.horizontal) { HStack(alignment: .top, spacing: 30) { - PosterButton(item: viewModel.item, type: .portrait) - .content { - EmptyView() - } - .imageOverlay { - EmptyView() - } - .frame(height: 405) + ImageCard(viewModel: viewModel) OverviewCard(item: viewModel.item) diff --git a/Swiftfin tvOS/Views/ItemView/Components/AboutView/Components/ImageCard.swift b/Swiftfin tvOS/Views/ItemView/Components/AboutView/Components/ImageCard.swift new file mode 100644 index 000000000..f285929bd --- /dev/null +++ b/Swiftfin tvOS/Views/ItemView/Components/AboutView/Components/ImageCard.swift @@ -0,0 +1,51 @@ +// +// 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) 2025 Jellyfin & Jellyfin Contributors +// + +import Defaults +import JellyfinAPI +import SwiftUI + +extension ItemView.AboutView { + + struct ImageCard: View { + + // MARK: - Environment & Observed Objects + + @EnvironmentObject + private var router: ItemCoordinator.Router + + @ObservedObject + var viewModel: ItemViewModel + + // MARK: - Body + + var body: some View { + PosterButton(item: viewModel.item, type: .portrait) + .content { EmptyView() } + .imageOverlay { EmptyView() } + .onSelect(onSelect) + .frame(height: 405) + } + + // MARK: - On Select + + // Switch case to allow other funcitonality if we need to expand this beyond episode > series + private func onSelect() { + switch viewModel.item.type { + case .episode: + if let episodeViewModel = viewModel as? EpisodeItemViewModel, + let seriesItem = episodeViewModel.seriesItem + { + router.route(to: \.item, seriesItem) + } + default: + break + } + } + } +} diff --git a/Swiftfin tvOS/Views/ItemView/EpisodeItemView/EpisodeItemContentView.swift b/Swiftfin tvOS/Views/ItemView/EpisodeItemView/EpisodeItemContentView.swift index 4bfdf9331..73725ec0e 100644 --- a/Swiftfin tvOS/Views/ItemView/EpisodeItemView/EpisodeItemContentView.swift +++ b/Swiftfin tvOS/Views/ItemView/EpisodeItemView/EpisodeItemContentView.swift @@ -29,17 +29,6 @@ extension EpisodeItemView { ItemView.CastAndCrewHStack(people: castAndCrew) } - if let seriesItem = viewModel.seriesItem { - PosterHStack( - title: L10n.series, - type: .portrait, - items: [seriesItem] - ) - .onSelect { item in - router.route(to: \.item, item) - } - } - ItemView.AboutView(viewModel: viewModel) } .background { diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 722c42089..b8418f69b 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -106,6 +106,8 @@ 4E556AB02D036F6900733377 /* UserPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E556AAF2D036F5E00733377 /* UserPermissions.swift */; }; 4E556AB12D036F6900733377 /* UserPermissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E556AAF2D036F5E00733377 /* UserPermissions.swift */; }; 4E5E48E52AB59806003F1B48 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */; }; + 4E5EE5512D67CE9500982290 /* ImageCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5EE5502D67CE9000982290 /* ImageCard.swift */; }; + 4E5EE5532D67CFAB00982290 /* ImageCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5EE5522D67CFAB00982290 /* ImageCard.swift */; }; 4E63B9FA2C8A5BEF00C25378 /* AdminDashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E63B9F42C8A5BEF00C25378 /* AdminDashboardView.swift */; }; 4E63B9FC2C8A5C3E00C25378 /* ActiveSessionsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E63B9FB2C8A5C3E00C25378 /* ActiveSessionsViewModel.swift */; }; 4E656C302D0798AA00F993F3 /* ParentalRating.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E656C2F2D0798A900F993F3 /* ParentalRating.swift */; }; @@ -1342,6 +1344,8 @@ 4E5508722D13AFE3002A5345 /* UserProfileImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileImage.swift; sourceTree = ""; }; 4E556AAF2D036F5E00733377 /* UserPermissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPermissions.swift; sourceTree = ""; }; 4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeViewsSettings.swift; sourceTree = ""; }; + 4E5EE5502D67CE9000982290 /* ImageCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCard.swift; sourceTree = ""; }; + 4E5EE5522D67CFAB00982290 /* ImageCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCard.swift; sourceTree = ""; }; 4E63B9F42C8A5BEF00C25378 /* AdminDashboardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdminDashboardView.swift; sourceTree = ""; }; 4E63B9FB2C8A5C3E00C25378 /* ActiveSessionsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActiveSessionsViewModel.swift; sourceTree = ""; }; 4E656C2F2D0798A900F993F3 /* ParentalRating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParentalRating.swift; sourceTree = ""; }; @@ -4769,6 +4773,7 @@ isa = PBXGroup; children = ( E12376AD2A33D680001F5B44 /* AboutView+Card.swift */, + 4E5EE5502D67CE9000982290 /* ImageCard.swift */, E1E750662A33E9B400B2C1EE /* MediaSourcesCard.swift */, E1E750652A33E9B400B2C1EE /* OverviewCard.swift */, E1E750672A33E9B400B2C1EE /* RatingsCard.swift */, @@ -5238,6 +5243,7 @@ isa = PBXGroup; children = ( E12376AF2A33D6AE001F5B44 /* AboutViewCard.swift */, + 4E5EE5522D67CFAB00982290 /* ImageCard.swift */, E1DABAFB2A270EE7008AC34A /* MediaSourcesCard.swift */, E1DABAF92A270E62008AC34A /* OverviewCard.swift */, E1DABAFD2A27B982008AC34A /* RatingsCard.swift */, @@ -5871,6 +5877,7 @@ E1DC983E296DEB9B00982F06 /* UnwatchedIndicator.swift in Sources */, 4E2AC4BF2C6C48D200DD600D /* CustomDeviceProfileAction.swift in Sources */, 4EBE06472C7E9509004A6C03 /* PlaybackCompatibility.swift in Sources */, + 4E5EE5532D67CFAB00982290 /* ImageCard.swift in Sources */, 4E661A2B2CEFE6F400025C99 /* Video3DFormat.swift in Sources */, E107BB9427880A8F00354E07 /* CollectionItemViewModel.swift in Sources */, 53ABFDE9267974EF00886593 /* HomeViewModel.swift in Sources */, @@ -6886,6 +6893,7 @@ 4E35CE662CBED8B600DBD886 /* ServerTicks.swift in Sources */, E1D3043528D1763100587289 /* SeeAllButton.swift in Sources */, 4E73E2A62C41CFD3002D2A78 /* PlaybackBitrateTestSize.swift in Sources */, + 4E5EE5512D67CE9500982290 /* ImageCard.swift in Sources */, E172D3B22BACA569007B4647 /* EpisodeContent.swift in Sources */, 4E1A39342D56C84200BAC1C7 /* ItemViewAttributes.swift in Sources */, 4EC1C8522C7FDFA300E2879E /* PlaybackDeviceProfile.swift in Sources */, diff --git a/Swiftfin/Views/ItemView/Components/AboutView/AboutView.swift b/Swiftfin/Views/ItemView/Components/AboutView/AboutView.swift index df20f6629..91831d9f6 100644 --- a/Swiftfin/Views/ItemView/Components/AboutView/AboutView.swift +++ b/Swiftfin/Views/ItemView/Components/AboutView/AboutView.swift @@ -106,22 +106,6 @@ extension ItemView { return CGSize(width: width, height: height) } - @ViewBuilder - private var imageView: some View { - ZStack { - Color.clear - - ImageView( - viewModel.item.type == .episode ? viewModel.item.seriesImageSource(.primary, maxWidth: 300) : viewModel - .item.imageSource(.primary, maxWidth: 300) - ) - .accessibilityIgnoresInvertColors() - } - .posterStyle(.portrait) - .posterShadow() - .frame(width: UIDevice.isPad ? padImageWidth : phoneImageWidth) - } - var body: some View { VStack(alignment: .leading) { L10n.about.text @@ -136,7 +120,8 @@ extension ItemView { ) { item in switch item { case .image: - imageView + ImageCard(viewModel: viewModel) + .frame(width: UIDevice.isPad ? padImageWidth : phoneImageWidth) case .overview: OverviewCard(item: viewModel.item) .frame(width: cardSize.width, height: cardSize.height) diff --git a/Swiftfin/Views/ItemView/Components/AboutView/Components/ImageCard.swift b/Swiftfin/Views/ItemView/Components/AboutView/Components/ImageCard.swift new file mode 100644 index 000000000..f5a99283a --- /dev/null +++ b/Swiftfin/Views/ItemView/Components/AboutView/Components/ImageCard.swift @@ -0,0 +1,50 @@ +// +// 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) 2025 Jellyfin & Jellyfin Contributors +// + +import Defaults +import JellyfinAPI +import SwiftUI + +extension ItemView.AboutView { + + struct ImageCard: View { + + // MARK: - Environment & Observed Objects + + @EnvironmentObject + private var router: ItemCoordinator.Router + + @ObservedObject + var viewModel: ItemViewModel + + // MARK: - Body + + var body: some View { + PosterButton(item: viewModel.item, type: .portrait) + .content { EmptyView() } + .imageOverlay { EmptyView() } + .onSelect(onSelect) + } + + // MARK: - On Select + + // Switch case to allow other funcitonality if we need to expand this beyond episode > series + private func onSelect() { + switch viewModel.item.type { + case .episode: + if let episodeViewModel = viewModel as? EpisodeItemViewModel, + let seriesItem = episodeViewModel.seriesItem + { + router.route(to: \.item, seriesItem) + } + default: + break + } + } + } +} diff --git a/Swiftfin/Views/ItemView/iOS/EpisodeItemView/EpisodeItemContentView.swift b/Swiftfin/Views/ItemView/iOS/EpisodeItemView/EpisodeItemContentView.swift index 965c192b1..950844393 100644 --- a/Swiftfin/Views/ItemView/iOS/EpisodeItemView/EpisodeItemContentView.swift +++ b/Swiftfin/Views/ItemView/iOS/EpisodeItemView/EpisodeItemContentView.swift @@ -78,21 +78,6 @@ extension EpisodeItemView { RowDivider() } - // MARK: Series - - // TODO: have different way to get to series item - // - about view poster? - if let seriesItem = viewModel.seriesItem { - PosterHStack( - title: L10n.series, - type: .portrait, - items: [seriesItem] - ) - .onSelect { item in - router.route(to: \.item, item) - } - } - ItemView.AboutView(viewModel: viewModel) } } diff --git a/Swiftfin/Views/ItemView/iPadOS/EpisodeItemView/iPadOSEpisodeContentView.swift b/Swiftfin/Views/ItemView/iPadOS/EpisodeItemView/iPadOSEpisodeContentView.swift index 215062090..0a31f87cc 100644 --- a/Swiftfin/Views/ItemView/iPadOS/EpisodeItemView/iPadOSEpisodeContentView.swift +++ b/Swiftfin/Views/ItemView/iPadOS/EpisodeItemView/iPadOSEpisodeContentView.swift @@ -48,19 +48,6 @@ extension iPadOSEpisodeItemView { RowDivider() } - // MARK: Series - - if let seriesItem = viewModel.seriesItem { - PosterHStack( - title: L10n.series, - type: .portrait, - items: [seriesItem] - ) - .onSelect { item in - router.route(to: \.item, item) - } - } - ItemView.AboutView(viewModel: viewModel) } }