From 137f0dbf13b13202c6a3d6dca136ae92268fec37 Mon Sep 17 00:00:00 2001 From: Joe Kribs Date: Wed, 19 Feb 2025 22:02:11 -0700 Subject: [PATCH 1/3] [iOS & tvOS] Fix Version Selection (#1429) * iOS + tvOS Versioning * hasMultipleVersions Co-authored-by: Ethan Pippin * v1 * Button Witdh to avoid overflow. Fix build issues. * Let instead of Var. * cleanup --------- Co-authored-by: Ethan Pippin --- .../ItemViewModel/ItemViewModel.swift | 6 ++ .../ActionButtons/ActionButton.swift | 1 + .../ActionButtons/ActionButtonHStack.swift | 16 +++-- .../ActionButtons/VersionMenu.swift | 59 +++++++++++++++++++ Swiftfin.xcodeproj/project.pbxproj | 4 ++ .../Components/ActionButtonHStack.swift | 2 +- 6 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 Swiftfin tvOS/Views/ItemView/Components/ActionButtons/VersionMenu.swift diff --git a/Shared/ViewModels/ItemViewModel/ItemViewModel.swift b/Shared/ViewModels/ItemViewModel/ItemViewModel.swift index a62ae0991..ada0c58f6 100644 --- a/Shared/ViewModels/ItemViewModel/ItemViewModel.swift +++ b/Shared/ViewModels/ItemViewModel/ItemViewModel.swift @@ -27,6 +27,7 @@ class ItemViewModel: ViewModel, Stateful { case replace(BaseItemDto) case toggleIsFavorite case toggleIsPlayed + case selectMediaSource(MediaSourceInfo) } // MARK: BackgroundState @@ -272,6 +273,11 @@ class ItemViewModel: ViewModel, Stateful { } .asAnyCancellable() + return state + case let .selectMediaSource(newSource): + + selectedMediaSource = newSource + return state } } diff --git a/Swiftfin tvOS/Views/ItemView/Components/ActionButtons/ActionButton.swift b/Swiftfin tvOS/Views/ItemView/Components/ActionButtons/ActionButton.swift index 0660a6f34..b03acaecd 100644 --- a/Swiftfin tvOS/Views/ItemView/Components/ActionButtons/ActionButton.swift +++ b/Swiftfin tvOS/Views/ItemView/Components/ActionButtons/ActionButton.swift @@ -56,6 +56,7 @@ extension ItemView { .labelStyle(.iconOnly) } } + .padding(0) .focused($isFocused) .buttonStyle(.card) } diff --git a/Swiftfin tvOS/Views/ItemView/Components/ActionButtons/ActionButtonHStack.swift b/Swiftfin tvOS/Views/ItemView/Components/ActionButtons/ActionButtonHStack.swift index 26a35e408..89e0ee539 100644 --- a/Swiftfin tvOS/Views/ItemView/Components/ActionButtons/ActionButtonHStack.swift +++ b/Swiftfin tvOS/Views/ItemView/Components/ActionButtons/ActionButtonHStack.swift @@ -21,7 +21,7 @@ extension ItemView { var viewModel: ItemViewModel @StateObject - var deleteViewModel: DeleteItemViewModel + private var deleteViewModel: DeleteItemViewModel // MARK: - Defaults @@ -73,7 +73,6 @@ extension ItemView { // MARK: - Body - /// Shrink to minWidth 100 (button) / 50 (menu) and 16 spacing to get 3 buttons + menu var body: some View { HStack(alignment: .center, spacing: 24) { @@ -88,7 +87,7 @@ extension ItemView { } .foregroundStyle(.purple) .environment(\.isSelected, viewModel.item.userData?.isPlayed ?? false) - .frame(minWidth: 140, maxWidth: .infinity) + .frame(minWidth: 80, maxWidth: .infinity) // MARK: - Toggle Favorite @@ -101,7 +100,14 @@ extension ItemView { } .foregroundStyle(.pink) .environment(\.isSelected, viewModel.item.userData?.isFavorite ?? false) - .frame(minWidth: 140, maxWidth: .infinity) + .frame(minWidth: 80, maxWidth: .infinity) + + // MARK: - Select Merged Version + + if let mediaSources = viewModel.playButtonItem?.mediaSources, mediaSources.count > 1 { + VersionMenu(viewModel: viewModel, mediaSources: mediaSources) + .frame(minWidth: 80, maxWidth: .infinity) + } // MARK: - Additional Menu Options @@ -118,7 +124,7 @@ extension ItemView { } } } - .frame(width: 70) + .frame(minWidth: 30, maxWidth: 50) } } .frame(height: 100) diff --git a/Swiftfin tvOS/Views/ItemView/Components/ActionButtons/VersionMenu.swift b/Swiftfin tvOS/Views/ItemView/Components/ActionButtons/VersionMenu.swift new file mode 100644 index 000000000..2ca3bd580 --- /dev/null +++ b/Swiftfin tvOS/Views/ItemView/Components/ActionButtons/VersionMenu.swift @@ -0,0 +1,59 @@ +// +// 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 JellyfinAPI +import SwiftUI + +extension ItemView { + + struct VersionMenu: View { + + // MARK: - Focus State + + @FocusState + private var isFocused: Bool + + @ObservedObject + var viewModel: ItemViewModel + + let mediaSources: [MediaSourceInfo] + + // MARK: - Body + + var body: some View { + Menu { + ForEach(mediaSources, id: \.hashValue) { mediaSource in + Button { + viewModel.send(.selectMediaSource(mediaSource)) + } label: { + if let selectedMediaSource = viewModel.selectedMediaSource, selectedMediaSource == mediaSource { + Label(selectedMediaSource.displayTitle, systemImage: "checkmark") + } else { + Text(mediaSource.displayTitle) + } + } + } + } label: { + ZStack { + RoundedRectangle(cornerRadius: 10) + .fill(isFocused ? Color.white : Color.white.opacity(0.5)) + + Label(L10n.version, systemImage: "list.dash") + .font(.title3) + .fontWeight(.semibold) + .foregroundStyle(.black) + .labelStyle(.iconOnly) + } + } + .focused($isFocused) + .scaleEffect(isFocused ? 1.20 : 1.0) + .animation(.easeInOut(duration: 0.15), value: isFocused) + .menuStyle(.borderlessButton) + } + } +} diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 722c42089..bd0043b37 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -225,6 +225,7 @@ 4ECF5D8B2D0A57EF00F066B1 /* DynamicDayOfWeek.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ECF5D892D0A57EF00F066B1 /* DynamicDayOfWeek.swift */; }; 4ED25CA12D07E3590010333C /* EditAccessScheduleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED25CA02D07E3520010333C /* EditAccessScheduleView.swift */; }; 4ED25CA42D07E4990010333C /* EditAccessScheduleRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ED25CA22D07E4990010333C /* EditAccessScheduleRow.swift */; }; + 4EDDB49C2D596E1200DA16E8 /* VersionMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EDDB49B2D596E0700DA16E8 /* VersionMenu.swift */; }; 4EE07CBB2D08B19700B0B636 /* ErrorMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE07CBA2D08B19100B0B636 /* ErrorMessage.swift */; }; 4EE07CBC2D08B19700B0B636 /* ErrorMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE07CBA2D08B19100B0B636 /* ErrorMessage.swift */; }; 4EE141692C8BABDF0045B661 /* ActiveSessionProgressSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE141682C8BABDF0045B661 /* ActiveSessionProgressSection.swift */; }; @@ -1441,6 +1442,7 @@ 4ED25CA02D07E3520010333C /* EditAccessScheduleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAccessScheduleView.swift; sourceTree = ""; }; 4ED25CA22D07E4990010333C /* EditAccessScheduleRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAccessScheduleRow.swift; sourceTree = ""; }; 4EDBDCD02CBDD6510033D347 /* SessionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionInfo.swift; sourceTree = ""; }; + 4EDDB49B2D596E0700DA16E8 /* VersionMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionMenu.swift; sourceTree = ""; }; 4EE07CBA2D08B19100B0B636 /* ErrorMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessage.swift; sourceTree = ""; }; 4EE141682C8BABDF0045B661 /* ActiveSessionProgressSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSessionProgressSection.swift; sourceTree = ""; }; 4EE766F42D131FB7009658F0 /* IdentifyItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentifyItemView.swift; sourceTree = ""; }; @@ -2490,6 +2492,7 @@ E1C926032887565C002A7A66 /* ActionButtonHStack.swift */, 4EF659E22CDD270B00E0BE5D /* ActionMenu.swift */, 4E97D1842D064B43004B89AD /* RefreshMetadataButton.swift */, + 4EDDB49B2D596E0700DA16E8 /* VersionMenu.swift */, ); path = ActionButtons; sourceTree = ""; @@ -5971,6 +5974,7 @@ E1CAF6602BA345830087D991 /* MediaViewModel.swift in Sources */, E19D41A82BEEDC5F0082B8B2 /* UserLocalSecurityViewModel.swift in Sources */, E111D8FA28D0400900400001 /* PagingLibraryView.swift in Sources */, + 4EDDB49C2D596E1200DA16E8 /* VersionMenu.swift in Sources */, E1EA9F6B28F8A79E00BEC442 /* VideoPlayerManager.swift in Sources */, BD0BA22F2AD6508C00306A8D /* DownloadVideoPlayerManager.swift in Sources */, 4E0A8FFC2CAF74D20014B047 /* TaskCompletionStatus.swift in Sources */, diff --git a/Swiftfin/Views/ItemView/Components/ActionButtonHStack.swift b/Swiftfin/Views/ItemView/Components/ActionButtonHStack.swift index f08905431..51e30cc01 100644 --- a/Swiftfin/Views/ItemView/Components/ActionButtonHStack.swift +++ b/Swiftfin/Views/ItemView/Components/ActionButtonHStack.swift @@ -80,7 +80,7 @@ extension ItemView { Menu { ForEach(mediaSources, id: \.hashValue) { mediaSource in Button { -// viewModel.selectedMediaSource = mediaSource + viewModel.send(.selectMediaSource(mediaSource)) } label: { if let selectedMediaSource = viewModel.selectedMediaSource, selectedMediaSource == mediaSource { Label(selectedMediaSource.displayTitle, systemImage: "checkmark") From 35b72b3a5a633d9206f83ba27cb306314af6fc17 Mon Sep 17 00:00:00 2001 From: Joe Kribs Date: Sun, 23 Feb 2025 11:53:20 -0700 Subject: [PATCH 2/3] [iOS & tvOS] Use `AboutView` for Series Poster Routing (#1438) * Move Series Button to the About Section IF the item is an episode. Otherwise, continue using that image as just an image. tvOS & iOS. * use posterbutton --------- Co-authored-by: Ethan Pippin --- Swiftfin tvOS/Components/PosterButton.swift | 1 + .../Components/AboutView/AboutView.swift | 9 +--- .../AboutView/Components/ImageCard.swift | 51 +++++++++++++++++++ .../EpisodeItemContentView.swift | 11 ---- Swiftfin.xcodeproj/project.pbxproj | 8 +++ .../Components/AboutView/AboutView.swift | 19 +------ .../AboutView/Components/ImageCard.swift | 50 ++++++++++++++++++ .../EpisodeItemContentView.swift | 15 ------ .../iPadOSEpisodeContentView.swift | 13 ----- 9 files changed, 113 insertions(+), 64 deletions(-) create mode 100644 Swiftfin tvOS/Views/ItemView/Components/AboutView/Components/ImageCard.swift create mode 100644 Swiftfin/Views/ItemView/Components/AboutView/Components/ImageCard.swift 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 bd0043b37..9ffbca6fd 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 */; }; @@ -1343,6 +1345,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 = ""; }; @@ -4772,6 +4776,7 @@ isa = PBXGroup; children = ( E12376AD2A33D680001F5B44 /* AboutView+Card.swift */, + 4E5EE5502D67CE9000982290 /* ImageCard.swift */, E1E750662A33E9B400B2C1EE /* MediaSourcesCard.swift */, E1E750652A33E9B400B2C1EE /* OverviewCard.swift */, E1E750672A33E9B400B2C1EE /* RatingsCard.swift */, @@ -5241,6 +5246,7 @@ isa = PBXGroup; children = ( E12376AF2A33D6AE001F5B44 /* AboutViewCard.swift */, + 4E5EE5522D67CFAB00982290 /* ImageCard.swift */, E1DABAFB2A270EE7008AC34A /* MediaSourcesCard.swift */, E1DABAF92A270E62008AC34A /* OverviewCard.swift */, E1DABAFD2A27B982008AC34A /* RatingsCard.swift */, @@ -5874,6 +5880,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 */, @@ -6890,6 +6897,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) } } From d2c5ac99859f349b039be1740a755fd4b83eb7e2 Mon Sep 17 00:00:00 2001 From: Daniel Chick Date: Tue, 25 Feb 2025 22:23:21 -0600 Subject: [PATCH 3/3] [iOS] Fix Clipping Add User Button (#1441) * Add what was removed * tvos --------- Co-authored-by: chickdan <=> Co-authored-by: Ethan Pippin --- .../Views/SelectUserView/Components/UserGridButton.swift | 2 ++ Swiftfin/Views/SelectUserView/Components/UserGridButton.swift | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Swiftfin tvOS/Views/SelectUserView/Components/UserGridButton.swift b/Swiftfin tvOS/Views/SelectUserView/Components/UserGridButton.swift index a9e0a715f..8301ce71b 100644 --- a/Swiftfin tvOS/Views/SelectUserView/Components/UserGridButton.swift +++ b/Swiftfin tvOS/Views/SelectUserView/Components/UserGridButton.swift @@ -63,6 +63,8 @@ extension SelectUserView { ), pipeline: .Swiftfin.local ) + .aspectRatio(1, contentMode: .fill) + .clipShape(.circle) .overlay { if isEditing { ZStack(alignment: .bottom) { diff --git a/Swiftfin/Views/SelectUserView/Components/UserGridButton.swift b/Swiftfin/Views/SelectUserView/Components/UserGridButton.swift index 19485597a..55266be0c 100644 --- a/Swiftfin/Views/SelectUserView/Components/UserGridButton.swift +++ b/Swiftfin/Views/SelectUserView/Components/UserGridButton.swift @@ -67,6 +67,8 @@ extension SelectUserView { pipeline: .Swiftfin.local ) } + .aspectRatio(1, contentMode: .fill) + .clipShape(.circle) .overlay { if isEditing { ZStack(alignment: .bottomTrailing) {