Cleanup `ItemView`s (#1543)
* wip * wip * Update ItemView.swift * cleanup, fix images * cleanup * Update Package.resolved * Update Localizable.strings
This commit is contained in:
parent
4402227ea2
commit
8be5df69b0
|
@ -17,7 +17,7 @@ import UIKit
|
||||||
extension BaseItemDto: Displayable {
|
extension BaseItemDto: Displayable {
|
||||||
|
|
||||||
var displayTitle: String {
|
var displayTitle: String {
|
||||||
name ?? .emptyDash
|
name ?? L10n.unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,7 +268,6 @@ extension BaseItemDto {
|
||||||
album
|
album
|
||||||
case .episode:
|
case .episode:
|
||||||
seriesName
|
seriesName
|
||||||
case .program: nil
|
|
||||||
default:
|
default:
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -872,8 +872,6 @@ internal enum L10n {
|
||||||
internal static let liveTVPrograms = L10n.tr("Localizable", "liveTVPrograms", fallback: "Live TV programs")
|
internal static let liveTVPrograms = L10n.tr("Localizable", "liveTVPrograms", fallback: "Live TV programs")
|
||||||
/// Live TV recording management
|
/// Live TV recording management
|
||||||
internal static let liveTVRecordingManagement = L10n.tr("Localizable", "liveTVRecordingManagement", fallback: "Live TV recording management")
|
internal static let liveTVRecordingManagement = L10n.tr("Localizable", "liveTVRecordingManagement", fallback: "Live TV recording management")
|
||||||
/// Live TV recording management
|
|
||||||
internal static let liveTvRecordingManagement = L10n.tr("Localizable", "liveTvRecordingManagement", fallback: "Live TV recording management")
|
|
||||||
/// Loading user failed
|
/// Loading user failed
|
||||||
internal static let loadingUserFailed = L10n.tr("Localizable", "loadingUserFailed", fallback: "Loading user failed")
|
internal static let loadingUserFailed = L10n.tr("Localizable", "loadingUserFailed", fallback: "Loading user failed")
|
||||||
/// Local
|
/// Local
|
||||||
|
|
|
@ -943,23 +943,13 @@
|
||||||
E18D6AA62BAA96F000A0D167 /* CollectionHStack in Frameworks */ = {isa = PBXBuildFile; productRef = E18D6AA52BAA96F000A0D167 /* CollectionHStack */; };
|
E18D6AA62BAA96F000A0D167 /* CollectionHStack in Frameworks */ = {isa = PBXBuildFile; productRef = E18D6AA52BAA96F000A0D167 /* CollectionHStack */; };
|
||||||
E18E01AB288746AF0022598C /* PillHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01A5288746AF0022598C /* PillHStack.swift */; };
|
E18E01AB288746AF0022598C /* PillHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01A5288746AF0022598C /* PillHStack.swift */; };
|
||||||
E18E01AD288746AF0022598C /* DotHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01A7288746AF0022598C /* DotHStack.swift */; };
|
E18E01AD288746AF0022598C /* DotHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01A7288746AF0022598C /* DotHStack.swift */; };
|
||||||
E18E01DA288747230022598C /* iPadOSEpisodeContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01B6288747230022598C /* iPadOSEpisodeContentView.swift */; };
|
|
||||||
E18E01DB288747230022598C /* iPadOSEpisodeItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01B7288747230022598C /* iPadOSEpisodeItemView.swift */; };
|
|
||||||
E18E01DC288747230022598C /* iPadOSCinematicScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01B9288747230022598C /* iPadOSCinematicScrollView.swift */; };
|
E18E01DC288747230022598C /* iPadOSCinematicScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01B9288747230022598C /* iPadOSCinematicScrollView.swift */; };
|
||||||
E18E01DD288747230022598C /* iPadOSSeriesItemContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01BB288747230022598C /* iPadOSSeriesItemContentView.swift */; };
|
|
||||||
E18E01DE288747230022598C /* iPadOSSeriesItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01BC288747230022598C /* iPadOSSeriesItemView.swift */; };
|
|
||||||
E18E01DF288747230022598C /* iPadOSMovieItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01BE288747230022598C /* iPadOSMovieItemView.swift */; };
|
|
||||||
E18E01E0288747230022598C /* iPadOSMovieItemContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01BF288747230022598C /* iPadOSMovieItemContentView.swift */; };
|
|
||||||
E18E01E1288747230022598C /* EpisodeItemContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01C2288747230022598C /* EpisodeItemContentView.swift */; };
|
E18E01E1288747230022598C /* EpisodeItemContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01C2288747230022598C /* EpisodeItemContentView.swift */; };
|
||||||
E18E01E2288747230022598C /* EpisodeItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01C3288747230022598C /* EpisodeItemView.swift */; };
|
|
||||||
E18E01E3288747230022598C /* CompactPortraitScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01C5288747230022598C /* CompactPortraitScrollView.swift */; };
|
E18E01E3288747230022598C /* CompactPortraitScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01C5288747230022598C /* CompactPortraitScrollView.swift */; };
|
||||||
E18E01E4288747230022598C /* CompactLogoScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01C6288747230022598C /* CompactLogoScrollView.swift */; };
|
E18E01E4288747230022598C /* CompactLogoScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01C6288747230022598C /* CompactLogoScrollView.swift */; };
|
||||||
E18E01E5288747230022598C /* CinematicScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01C7288747230022598C /* CinematicScrollView.swift */; };
|
E18E01E5288747230022598C /* CinematicScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01C7288747230022598C /* CinematicScrollView.swift */; };
|
||||||
E18E01E6288747230022598C /* CollectionItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01C9288747230022598C /* CollectionItemView.swift */; };
|
|
||||||
E18E01E7288747230022598C /* CollectionItemContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01CA288747230022598C /* CollectionItemContentView.swift */; };
|
E18E01E7288747230022598C /* CollectionItemContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01CA288747230022598C /* CollectionItemContentView.swift */; };
|
||||||
E18E01E8288747230022598C /* SeriesItemContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01CC288747230022598C /* SeriesItemContentView.swift */; };
|
E18E01E8288747230022598C /* SeriesItemContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01CC288747230022598C /* SeriesItemContentView.swift */; };
|
||||||
E18E01E9288747230022598C /* SeriesItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01CD288747230022598C /* SeriesItemView.swift */; };
|
|
||||||
E18E01EA288747230022598C /* MovieItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01CF288747230022598C /* MovieItemView.swift */; };
|
|
||||||
E18E01EB288747230022598C /* MovieItemContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01D0288747230022598C /* MovieItemContentView.swift */; };
|
E18E01EB288747230022598C /* MovieItemContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01D0288747230022598C /* MovieItemContentView.swift */; };
|
||||||
E18E01EE288747230022598C /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01D5288747230022598C /* AboutView.swift */; };
|
E18E01EE288747230022598C /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01D5288747230022598C /* AboutView.swift */; };
|
||||||
E18E01F0288747230022598C /* AttributeHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01D7288747230022598C /* AttributeHStack.swift */; };
|
E18E01F0288747230022598C /* AttributeHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18E01D7288747230022598C /* AttributeHStack.swift */; };
|
||||||
|
@ -1000,6 +990,7 @@
|
||||||
E193D5502719430400900D82 /* ServerDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E193D54F2719430400900D82 /* ServerDetailView.swift */; };
|
E193D5502719430400900D82 /* ServerDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E193D54F2719430400900D82 /* ServerDetailView.swift */; };
|
||||||
E193D5512719432400900D82 /* ServerConnectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA5326D050F500CC4EB7 /* ServerConnectionViewModel.swift */; };
|
E193D5512719432400900D82 /* ServerConnectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA5326D050F500CC4EB7 /* ServerConnectionViewModel.swift */; };
|
||||||
E193D553271943D500900D82 /* tvOSMainTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E193D552271943D500900D82 /* tvOSMainTabCoordinator.swift */; };
|
E193D553271943D500900D82 /* tvOSMainTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E193D552271943D500900D82 /* tvOSMainTabCoordinator.swift */; };
|
||||||
|
E19523752DD8F18B00442F15 /* SimpleScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19523742DD8F18B00442F15 /* SimpleScrollView.swift */; };
|
||||||
E19D41A72BEEDC450082B8B2 /* UserLocalSecurityViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19D41A62BEEDC450082B8B2 /* UserLocalSecurityViewModel.swift */; };
|
E19D41A72BEEDC450082B8B2 /* UserLocalSecurityViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19D41A62BEEDC450082B8B2 /* UserLocalSecurityViewModel.swift */; };
|
||||||
E19D41A82BEEDC5F0082B8B2 /* UserLocalSecurityViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19D41A62BEEDC450082B8B2 /* UserLocalSecurityViewModel.swift */; };
|
E19D41A82BEEDC5F0082B8B2 /* UserLocalSecurityViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19D41A62BEEDC450082B8B2 /* UserLocalSecurityViewModel.swift */; };
|
||||||
E19D41AA2BF077130082B8B2 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19D41A92BF077130082B8B2 /* Keychain.swift */; };
|
E19D41AA2BF077130082B8B2 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19D41A92BF077130082B8B2 /* Keychain.swift */; };
|
||||||
|
@ -1250,8 +1241,6 @@
|
||||||
E1F5CF062CB09EA000607465 /* CurrentDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F5CF042CB09EA000607465 /* CurrentDate.swift */; };
|
E1F5CF062CB09EA000607465 /* CurrentDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F5CF042CB09EA000607465 /* CurrentDate.swift */; };
|
||||||
E1F5CF082CB0A04500607465 /* Text.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F5CF072CB0A04500607465 /* Text.swift */; };
|
E1F5CF082CB0A04500607465 /* Text.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F5CF072CB0A04500607465 /* Text.swift */; };
|
||||||
E1F5CF092CB0A04500607465 /* Text.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F5CF072CB0A04500607465 /* Text.swift */; };
|
E1F5CF092CB0A04500607465 /* Text.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F5CF072CB0A04500607465 /* Text.swift */; };
|
||||||
E1FA891B289A302300176FEB /* iPadOSCollectionItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FA891A289A302300176FEB /* iPadOSCollectionItemView.swift */; };
|
|
||||||
E1FA891E289A305D00176FEB /* iPadOSCollectionItemContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FA891D289A305D00176FEB /* iPadOSCollectionItemContentView.swift */; };
|
|
||||||
E1FAD1C62A0375BA007F5521 /* UDPBroadcast in Frameworks */ = {isa = PBXBuildFile; productRef = E1FAD1C52A0375BA007F5521 /* UDPBroadcast */; };
|
E1FAD1C62A0375BA007F5521 /* UDPBroadcast in Frameworks */ = {isa = PBXBuildFile; productRef = E1FAD1C52A0375BA007F5521 /* UDPBroadcast */; };
|
||||||
E1FCD08826C35A0D007C8DCF /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FCD08726C35A0D007C8DCF /* NetworkError.swift */; };
|
E1FCD08826C35A0D007C8DCF /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FCD08726C35A0D007C8DCF /* NetworkError.swift */; };
|
||||||
E1FCD08926C35A0D007C8DCF /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FCD08726C35A0D007C8DCF /* NetworkError.swift */; };
|
E1FCD08926C35A0D007C8DCF /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FCD08726C35A0D007C8DCF /* NetworkError.swift */; };
|
||||||
|
@ -1937,23 +1926,13 @@
|
||||||
E18CE0B328A22EDA0092E7F1 /* RepeatingTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepeatingTimer.swift; sourceTree = "<group>"; };
|
E18CE0B328A22EDA0092E7F1 /* RepeatingTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepeatingTimer.swift; sourceTree = "<group>"; };
|
||||||
E18E01A5288746AF0022598C /* PillHStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PillHStack.swift; sourceTree = "<group>"; };
|
E18E01A5288746AF0022598C /* PillHStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PillHStack.swift; sourceTree = "<group>"; };
|
||||||
E18E01A7288746AF0022598C /* DotHStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DotHStack.swift; sourceTree = "<group>"; };
|
E18E01A7288746AF0022598C /* DotHStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DotHStack.swift; sourceTree = "<group>"; };
|
||||||
E18E01B6288747230022598C /* iPadOSEpisodeContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = iPadOSEpisodeContentView.swift; sourceTree = "<group>"; };
|
|
||||||
E18E01B7288747230022598C /* iPadOSEpisodeItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = iPadOSEpisodeItemView.swift; sourceTree = "<group>"; };
|
|
||||||
E18E01B9288747230022598C /* iPadOSCinematicScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = iPadOSCinematicScrollView.swift; sourceTree = "<group>"; };
|
E18E01B9288747230022598C /* iPadOSCinematicScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = iPadOSCinematicScrollView.swift; sourceTree = "<group>"; };
|
||||||
E18E01BB288747230022598C /* iPadOSSeriesItemContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = iPadOSSeriesItemContentView.swift; sourceTree = "<group>"; };
|
|
||||||
E18E01BC288747230022598C /* iPadOSSeriesItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = iPadOSSeriesItemView.swift; sourceTree = "<group>"; };
|
|
||||||
E18E01BE288747230022598C /* iPadOSMovieItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = iPadOSMovieItemView.swift; sourceTree = "<group>"; };
|
|
||||||
E18E01BF288747230022598C /* iPadOSMovieItemContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = iPadOSMovieItemContentView.swift; sourceTree = "<group>"; };
|
|
||||||
E18E01C2288747230022598C /* EpisodeItemContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EpisodeItemContentView.swift; sourceTree = "<group>"; };
|
E18E01C2288747230022598C /* EpisodeItemContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EpisodeItemContentView.swift; sourceTree = "<group>"; };
|
||||||
E18E01C3288747230022598C /* EpisodeItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EpisodeItemView.swift; sourceTree = "<group>"; };
|
|
||||||
E18E01C5288747230022598C /* CompactPortraitScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompactPortraitScrollView.swift; sourceTree = "<group>"; };
|
E18E01C5288747230022598C /* CompactPortraitScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompactPortraitScrollView.swift; sourceTree = "<group>"; };
|
||||||
E18E01C6288747230022598C /* CompactLogoScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompactLogoScrollView.swift; sourceTree = "<group>"; };
|
E18E01C6288747230022598C /* CompactLogoScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompactLogoScrollView.swift; sourceTree = "<group>"; };
|
||||||
E18E01C7288747230022598C /* CinematicScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CinematicScrollView.swift; sourceTree = "<group>"; };
|
E18E01C7288747230022598C /* CinematicScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CinematicScrollView.swift; sourceTree = "<group>"; };
|
||||||
E18E01C9288747230022598C /* CollectionItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionItemView.swift; sourceTree = "<group>"; };
|
|
||||||
E18E01CA288747230022598C /* CollectionItemContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionItemContentView.swift; sourceTree = "<group>"; };
|
E18E01CA288747230022598C /* CollectionItemContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionItemContentView.swift; sourceTree = "<group>"; };
|
||||||
E18E01CC288747230022598C /* SeriesItemContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeriesItemContentView.swift; sourceTree = "<group>"; };
|
E18E01CC288747230022598C /* SeriesItemContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeriesItemContentView.swift; sourceTree = "<group>"; };
|
||||||
E18E01CD288747230022598C /* SeriesItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeriesItemView.swift; sourceTree = "<group>"; };
|
|
||||||
E18E01CF288747230022598C /* MovieItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MovieItemView.swift; sourceTree = "<group>"; };
|
|
||||||
E18E01D0288747230022598C /* MovieItemContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MovieItemContentView.swift; sourceTree = "<group>"; };
|
E18E01D0288747230022598C /* MovieItemContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MovieItemContentView.swift; sourceTree = "<group>"; };
|
||||||
E18E01D5288747230022598C /* AboutView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
|
E18E01D5288747230022598C /* AboutView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
|
||||||
E18E01D7288747230022598C /* AttributeHStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttributeHStack.swift; sourceTree = "<group>"; };
|
E18E01D7288747230022598C /* AttributeHStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttributeHStack.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1975,6 +1954,7 @@
|
||||||
E193D548271941CC00900D82 /* UserSignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSignInView.swift; sourceTree = "<group>"; };
|
E193D548271941CC00900D82 /* UserSignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSignInView.swift; sourceTree = "<group>"; };
|
||||||
E193D54F2719430400900D82 /* ServerDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailView.swift; sourceTree = "<group>"; };
|
E193D54F2719430400900D82 /* ServerDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailView.swift; sourceTree = "<group>"; };
|
||||||
E193D552271943D500900D82 /* tvOSMainTabCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = tvOSMainTabCoordinator.swift; sourceTree = "<group>"; };
|
E193D552271943D500900D82 /* tvOSMainTabCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = tvOSMainTabCoordinator.swift; sourceTree = "<group>"; };
|
||||||
|
E19523742DD8F18B00442F15 /* SimpleScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleScrollView.swift; sourceTree = "<group>"; };
|
||||||
E19D41A62BEEDC450082B8B2 /* UserLocalSecurityViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserLocalSecurityViewModel.swift; sourceTree = "<group>"; };
|
E19D41A62BEEDC450082B8B2 /* UserLocalSecurityViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserLocalSecurityViewModel.swift; sourceTree = "<group>"; };
|
||||||
E19D41A92BF077130082B8B2 /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
|
E19D41A92BF077130082B8B2 /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
|
||||||
E19D41AB2BF288110082B8B2 /* ServerCheckView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerCheckView.swift; sourceTree = "<group>"; };
|
E19D41AB2BF288110082B8B2 /* ServerCheckView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerCheckView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -2152,8 +2132,6 @@
|
||||||
E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerJumpLength.swift; sourceTree = "<group>"; };
|
E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerJumpLength.swift; sourceTree = "<group>"; };
|
||||||
E1F5CF042CB09EA000607465 /* CurrentDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentDate.swift; sourceTree = "<group>"; };
|
E1F5CF042CB09EA000607465 /* CurrentDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentDate.swift; sourceTree = "<group>"; };
|
||||||
E1F5CF072CB0A04500607465 /* Text.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Text.swift; sourceTree = "<group>"; };
|
E1F5CF072CB0A04500607465 /* Text.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Text.swift; sourceTree = "<group>"; };
|
||||||
E1FA891A289A302300176FEB /* iPadOSCollectionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iPadOSCollectionItemView.swift; sourceTree = "<group>"; };
|
|
||||||
E1FA891D289A305D00176FEB /* iPadOSCollectionItemContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iPadOSCollectionItemContentView.swift; sourceTree = "<group>"; };
|
|
||||||
E1FCD08726C35A0D007C8DCF /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = "<group>"; };
|
E1FCD08726C35A0D007C8DCF /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = "<group>"; };
|
||||||
E1FE28C82DC16B2B00E1A23E /* RedrawOnNotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedrawOnNotificationView.swift; sourceTree = "<group>"; };
|
E1FE28C82DC16B2B00E1A23E /* RedrawOnNotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedrawOnNotificationView.swift; sourceTree = "<group>"; };
|
||||||
E1FE69A628C29B720021BC93 /* ProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = "<group>"; };
|
E1FE69A628C29B720021BC93 /* ProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = "<group>"; };
|
||||||
|
@ -4655,10 +4633,13 @@
|
||||||
E14F7D0A26DB3714007C3AE6 /* ItemView */ = {
|
E14F7D0A26DB3714007C3AE6 /* ItemView */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
535BAE9E2649E569005FA86D /* ItemView.swift */,
|
E18E01CA288747230022598C /* CollectionItemContentView.swift */,
|
||||||
E18E01D4288747230022598C /* Components */,
|
E18E01D4288747230022598C /* Components */,
|
||||||
E18E01C0288747230022598C /* iOS */,
|
E18E01C2288747230022598C /* EpisodeItemContentView.swift */,
|
||||||
E18E01B4288747230022598C /* iPadOS */,
|
535BAE9E2649E569005FA86D /* ItemView.swift */,
|
||||||
|
E18E01D0288747230022598C /* MovieItemContentView.swift */,
|
||||||
|
E18E01C4288747230022598C /* ScrollViews */,
|
||||||
|
E18E01CC288747230022598C /* SeriesItemContentView.swift */,
|
||||||
);
|
);
|
||||||
path = ItemView;
|
path = ItemView;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -4918,111 +4899,18 @@
|
||||||
path = Components;
|
path = Components;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
E18E01B4288747230022598C /* iPadOS */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
E1FA891C289A302600176FEB /* CollectionItemView */,
|
|
||||||
E18E01B5288747230022598C /* EpisodeItemView */,
|
|
||||||
E18E01BD288747230022598C /* MovieItemView */,
|
|
||||||
E18E01B8288747230022598C /* ScrollViews */,
|
|
||||||
E18E01BA288747230022598C /* SeriesItemView */,
|
|
||||||
);
|
|
||||||
path = iPadOS;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
E18E01B5288747230022598C /* EpisodeItemView */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
E18E01B6288747230022598C /* iPadOSEpisodeContentView.swift */,
|
|
||||||
E18E01B7288747230022598C /* iPadOSEpisodeItemView.swift */,
|
|
||||||
);
|
|
||||||
path = EpisodeItemView;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
E18E01B8288747230022598C /* ScrollViews */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
E18E01B9288747230022598C /* iPadOSCinematicScrollView.swift */,
|
|
||||||
);
|
|
||||||
path = ScrollViews;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
E18E01BA288747230022598C /* SeriesItemView */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
E18E01BB288747230022598C /* iPadOSSeriesItemContentView.swift */,
|
|
||||||
E18E01BC288747230022598C /* iPadOSSeriesItemView.swift */,
|
|
||||||
);
|
|
||||||
path = SeriesItemView;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
E18E01BD288747230022598C /* MovieItemView */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
E18E01BF288747230022598C /* iPadOSMovieItemContentView.swift */,
|
|
||||||
E18E01BE288747230022598C /* iPadOSMovieItemView.swift */,
|
|
||||||
);
|
|
||||||
path = MovieItemView;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
E18E01C0288747230022598C /* iOS */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
E18E01C8288747230022598C /* CollectionItemView */,
|
|
||||||
E18E01C1288747230022598C /* EpisodeItemView */,
|
|
||||||
E18E01CE288747230022598C /* MovieItemView */,
|
|
||||||
E18E01C4288747230022598C /* ScrollViews */,
|
|
||||||
E18E01CB288747230022598C /* SeriesItemView */,
|
|
||||||
);
|
|
||||||
path = iOS;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
E18E01C1288747230022598C /* EpisodeItemView */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
E18E01C2288747230022598C /* EpisodeItemContentView.swift */,
|
|
||||||
E18E01C3288747230022598C /* EpisodeItemView.swift */,
|
|
||||||
);
|
|
||||||
path = EpisodeItemView;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
E18E01C4288747230022598C /* ScrollViews */ = {
|
E18E01C4288747230022598C /* ScrollViews */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E18E01C5288747230022598C /* CompactPortraitScrollView.swift */,
|
|
||||||
E18E01C6288747230022598C /* CompactLogoScrollView.swift */,
|
|
||||||
E18E01C7288747230022598C /* CinematicScrollView.swift */,
|
E18E01C7288747230022598C /* CinematicScrollView.swift */,
|
||||||
|
E18E01C6288747230022598C /* CompactLogoScrollView.swift */,
|
||||||
|
E18E01C5288747230022598C /* CompactPortraitScrollView.swift */,
|
||||||
|
E18E01B9288747230022598C /* iPadOSCinematicScrollView.swift */,
|
||||||
|
E19523742DD8F18B00442F15 /* SimpleScrollView.swift */,
|
||||||
);
|
);
|
||||||
path = ScrollViews;
|
path = ScrollViews;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
E18E01C8288747230022598C /* CollectionItemView */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
E18E01CA288747230022598C /* CollectionItemContentView.swift */,
|
|
||||||
E18E01C9288747230022598C /* CollectionItemView.swift */,
|
|
||||||
);
|
|
||||||
path = CollectionItemView;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
E18E01CB288747230022598C /* SeriesItemView */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
E18E01CC288747230022598C /* SeriesItemContentView.swift */,
|
|
||||||
E18E01CD288747230022598C /* SeriesItemView.swift */,
|
|
||||||
);
|
|
||||||
path = SeriesItemView;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
E18E01CE288747230022598C /* MovieItemView */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
E18E01D0288747230022598C /* MovieItemContentView.swift */,
|
|
||||||
E18E01CF288747230022598C /* MovieItemView.swift */,
|
|
||||||
);
|
|
||||||
path = MovieItemView;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
E18E01D4288747230022598C /* Components */ = {
|
E18E01D4288747230022598C /* Components */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -5518,15 +5406,6 @@
|
||||||
path = MediaSourceInfo;
|
path = MediaSourceInfo;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
E1FA891C289A302600176FEB /* CollectionItemView */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
E1FA891D289A305D00176FEB /* iPadOSCollectionItemContentView.swift */,
|
|
||||||
E1FA891A289A302300176FEB /* iPadOSCollectionItemView.swift */,
|
|
||||||
);
|
|
||||||
path = CollectionItemView;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
E1FCD08E26C466F3007C8DCF /* Errors */ = {
|
E1FCD08E26C466F3007C8DCF /* Errors */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -6466,7 +6345,6 @@
|
||||||
6220D0C026D61C5000B8E046 /* ItemCoordinator.swift in Sources */,
|
6220D0C026D61C5000B8E046 /* ItemCoordinator.swift in Sources */,
|
||||||
4EC2B1A22CC96F6600D866BE /* ServerUsersViewModel.swift in Sources */,
|
4EC2B1A22CC96F6600D866BE /* ServerUsersViewModel.swift in Sources */,
|
||||||
E1B90C6A2BBE68D5007027C8 /* OffsetScrollView.swift in Sources */,
|
E1B90C6A2BBE68D5007027C8 /* OffsetScrollView.swift in Sources */,
|
||||||
E18E01DB288747230022598C /* iPadOSEpisodeItemView.swift in Sources */,
|
|
||||||
4E661A292CEFE68200025C99 /* Video3DFormatPicker.swift in Sources */,
|
4E661A292CEFE68200025C99 /* Video3DFormatPicker.swift in Sources */,
|
||||||
E13DD3F227179378009D4DAF /* UserSignInCoordinator.swift in Sources */,
|
E13DD3F227179378009D4DAF /* UserSignInCoordinator.swift in Sources */,
|
||||||
621338932660107500A81A2A /* String.swift in Sources */,
|
621338932660107500A81A2A /* String.swift in Sources */,
|
||||||
|
@ -6513,7 +6391,6 @@
|
||||||
C46DD8D22A8DC1F60046A504 /* LiveVideoPlayerCoordinator.swift in Sources */,
|
C46DD8D22A8DC1F60046A504 /* LiveVideoPlayerCoordinator.swift in Sources */,
|
||||||
4EB538C32CE3E21800EB72D5 /* SyncPlaySection.swift in Sources */,
|
4EB538C32CE3E21800EB72D5 /* SyncPlaySection.swift in Sources */,
|
||||||
E18ACA8B2A14301800BB4F35 /* ScalingButtonStyle.swift in Sources */,
|
E18ACA8B2A14301800BB4F35 /* ScalingButtonStyle.swift in Sources */,
|
||||||
E18E01DF288747230022598C /* iPadOSMovieItemView.swift in Sources */,
|
|
||||||
E168BD13289A4162001A6922 /* ContinueWatchingView.swift in Sources */,
|
E168BD13289A4162001A6922 /* ContinueWatchingView.swift in Sources */,
|
||||||
E154966E296CA2EF00C4EF88 /* LogManager.swift in Sources */,
|
E154966E296CA2EF00C4EF88 /* LogManager.swift in Sources */,
|
||||||
62C29EA126D102A500C1D2E7 /* iOSMainTabCoordinator.swift in Sources */,
|
62C29EA126D102A500C1D2E7 /* iOSMainTabCoordinator.swift in Sources */,
|
||||||
|
@ -6585,7 +6462,6 @@
|
||||||
4E37F6162D17C1860022AADD /* RemoteImageInfoViewModel.swift in Sources */,
|
4E37F6162D17C1860022AADD /* RemoteImageInfoViewModel.swift in Sources */,
|
||||||
4E10C8172CC0455A0012CC9F /* CompatibilitiesSection.swift in Sources */,
|
4E10C8172CC0455A0012CC9F /* CompatibilitiesSection.swift in Sources */,
|
||||||
4EE7670A2D135CBA009658F0 /* RemoteSearchResultView.swift in Sources */,
|
4EE7670A2D135CBA009658F0 /* RemoteSearchResultView.swift in Sources */,
|
||||||
E1FA891B289A302300176FEB /* iPadOSCollectionItemView.swift in Sources */,
|
|
||||||
E14E9DF12BCF7A99004E3371 /* ItemLetter.swift in Sources */,
|
E14E9DF12BCF7A99004E3371 /* ItemLetter.swift in Sources */,
|
||||||
E1B5861229E32EEF00E45D6E /* Sequence.swift in Sources */,
|
E1B5861229E32EEF00E45D6E /* Sequence.swift in Sources */,
|
||||||
E11895B32893844A0042947B /* BackgroundParallaxHeaderModifier.swift in Sources */,
|
E11895B32893844A0042947B /* BackgroundParallaxHeaderModifier.swift in Sources */,
|
||||||
|
@ -6634,7 +6510,6 @@
|
||||||
E1EA09692BED78BB004CDE76 /* UserAccessPolicy.swift in Sources */,
|
E1EA09692BED78BB004CDE76 /* UserAccessPolicy.swift in Sources */,
|
||||||
4E656C302D0798AA00F993F3 /* ParentalRating.swift in Sources */,
|
4E656C302D0798AA00F993F3 /* ParentalRating.swift in Sources */,
|
||||||
E18E0204288749200022598C /* RowDivider.swift in Sources */,
|
E18E0204288749200022598C /* RowDivider.swift in Sources */,
|
||||||
E18E01DA288747230022598C /* iPadOSEpisodeContentView.swift in Sources */,
|
|
||||||
E1CB75752C80EAFA00217C76 /* ArrayBuilder.swift in Sources */,
|
E1CB75752C80EAFA00217C76 /* ArrayBuilder.swift in Sources */,
|
||||||
E1047E2327E5880000CB0D4A /* SystemImageContentView.swift in Sources */,
|
E1047E2327E5880000CB0D4A /* SystemImageContentView.swift in Sources */,
|
||||||
E1C8CE5B28FE512400DF5D7B /* CGPoint.swift in Sources */,
|
E1C8CE5B28FE512400DF5D7B /* CGPoint.swift in Sources */,
|
||||||
|
@ -6738,6 +6613,7 @@
|
||||||
E18E01E4288747230022598C /* CompactLogoScrollView.swift in Sources */,
|
E18E01E4288747230022598C /* CompactLogoScrollView.swift in Sources */,
|
||||||
E15D63EF2BD6DFC200AA665D /* SystemImageable.swift in Sources */,
|
E15D63EF2BD6DFC200AA665D /* SystemImageable.swift in Sources */,
|
||||||
E1002B642793CEE800E47059 /* ChapterInfo.swift in Sources */,
|
E1002B642793CEE800E47059 /* ChapterInfo.swift in Sources */,
|
||||||
|
E19523752DD8F18B00442F15 /* SimpleScrollView.swift in Sources */,
|
||||||
4E661A012CEFE39D00025C99 /* EditMetadataView.swift in Sources */,
|
4E661A012CEFE39D00025C99 /* EditMetadataView.swift in Sources */,
|
||||||
4E45939E2D04E20000E277E1 /* ItemImagesViewModel.swift in Sources */,
|
4E45939E2D04E20000E277E1 /* ItemImagesViewModel.swift in Sources */,
|
||||||
C46DD8E52A8FA6510046A504 /* LiveTopBarView.swift in Sources */,
|
C46DD8E52A8FA6510046A504 /* LiveTopBarView.swift in Sources */,
|
||||||
|
@ -6771,7 +6647,6 @@
|
||||||
E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */,
|
E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */,
|
||||||
E1A3E4CB2BB74EFD005C59F8 /* EpisodeHStack.swift in Sources */,
|
E1A3E4CB2BB74EFD005C59F8 /* EpisodeHStack.swift in Sources */,
|
||||||
E1E0BEB729EF450B0002E8D3 /* UIGestureRecognizer.swift in Sources */,
|
E1E0BEB729EF450B0002E8D3 /* UIGestureRecognizer.swift in Sources */,
|
||||||
E1FA891E289A305D00176FEB /* iPadOSCollectionItemContentView.swift in Sources */,
|
|
||||||
E12CC1AE28D0FAEA00678D5D /* NextUpLibraryViewModel.swift in Sources */,
|
E12CC1AE28D0FAEA00678D5D /* NextUpLibraryViewModel.swift in Sources */,
|
||||||
E1549666296CA2EF00C4EF88 /* Notifications.swift in Sources */,
|
E1549666296CA2EF00C4EF88 /* Notifications.swift in Sources */,
|
||||||
4EE141692C8BABDF0045B661 /* ActiveSessionProgressSection.swift in Sources */,
|
4EE141692C8BABDF0045B661 /* ActiveSessionProgressSection.swift in Sources */,
|
||||||
|
@ -6784,7 +6659,6 @@
|
||||||
E1DA656F28E78C9900592A73 /* EpisodeSelector.swift in Sources */,
|
E1DA656F28E78C9900592A73 /* EpisodeSelector.swift in Sources */,
|
||||||
4E9654492D99C553006CB024 /* CollectionType.swift in Sources */,
|
4E9654492D99C553006CB024 /* CollectionType.swift in Sources */,
|
||||||
4E8F74A22CE03C9000CC8969 /* ItemEditorCoordinator.swift in Sources */,
|
4E8F74A22CE03C9000CC8969 /* ItemEditorCoordinator.swift in Sources */,
|
||||||
E18E01E0288747230022598C /* iPadOSMovieItemContentView.swift in Sources */,
|
|
||||||
E1A7F0DF2BD4EC7400620DDD /* Dictionary.swift in Sources */,
|
E1A7F0DF2BD4EC7400620DDD /* Dictionary.swift in Sources */,
|
||||||
4E0195E42CE0467B007844F4 /* ItemSection.swift in Sources */,
|
4E0195E42CE0467B007844F4 /* ItemSection.swift in Sources */,
|
||||||
E10231442BCF8A51009D71FC /* ChannelProgram.swift in Sources */,
|
E10231442BCF8A51009D71FC /* ChannelProgram.swift in Sources */,
|
||||||
|
@ -6820,7 +6694,6 @@
|
||||||
E1FE28C92DC16B2B00E1A23E /* RedrawOnNotificationView.swift in Sources */,
|
E1FE28C92DC16B2B00E1A23E /* RedrawOnNotificationView.swift in Sources */,
|
||||||
E13DD3FC2717EAE8009D4DAF /* SelectUserView.swift in Sources */,
|
E13DD3FC2717EAE8009D4DAF /* SelectUserView.swift in Sources */,
|
||||||
4EF36F642D962A430065BB79 /* ItemSortBy.swift in Sources */,
|
4EF36F642D962A430065BB79 /* ItemSortBy.swift in Sources */,
|
||||||
E18E01DE288747230022598C /* iPadOSSeriesItemView.swift in Sources */,
|
|
||||||
6220D0CC26D640C400B8E046 /* AppURLHandler.swift in Sources */,
|
6220D0CC26D640C400B8E046 /* AppURLHandler.swift in Sources */,
|
||||||
E1A3E4CF2BB7E02B005C59F8 /* DelayedProgressView.swift in Sources */,
|
E1A3E4CF2BB7E02B005C59F8 /* DelayedProgressView.swift in Sources */,
|
||||||
E1EA09672BED6815004CDE76 /* UserSignInSecurityView.swift in Sources */,
|
E1EA09672BED6815004CDE76 /* UserSignInSecurityView.swift in Sources */,
|
||||||
|
@ -6829,7 +6702,6 @@
|
||||||
E18A8E8028D6083700333B9A /* MediaSourceInfo+ItemVideoPlayerViewModel.swift in Sources */,
|
E18A8E8028D6083700333B9A /* MediaSourceInfo+ItemVideoPlayerViewModel.swift in Sources */,
|
||||||
E18E01DC288747230022598C /* iPadOSCinematicScrollView.swift in Sources */,
|
E18E01DC288747230022598C /* iPadOSCinematicScrollView.swift in Sources */,
|
||||||
E101ECD52CD40489001EA89E /* DeviceDetailViewModel.swift in Sources */,
|
E101ECD52CD40489001EA89E /* DeviceDetailViewModel.swift in Sources */,
|
||||||
E18E01E2288747230022598C /* EpisodeItemView.swift in Sources */,
|
|
||||||
4E35CE5C2CBED3F300DBD886 /* TimeRow.swift in Sources */,
|
4E35CE5C2CBED3F300DBD886 /* TimeRow.swift in Sources */,
|
||||||
4E3A24DC2CFE35D50083A72C /* NameInput.swift in Sources */,
|
4E3A24DC2CFE35D50083A72C /* NameInput.swift in Sources */,
|
||||||
4E35CE5D2CBED3F300DBD886 /* TriggerTypeRow.swift in Sources */,
|
4E35CE5D2CBED3F300DBD886 /* TriggerTypeRow.swift in Sources */,
|
||||||
|
@ -6906,7 +6778,6 @@
|
||||||
BD0BA22B2AD6503B00306A8D /* OnlineVideoPlayerManager.swift in Sources */,
|
BD0BA22B2AD6503B00306A8D /* OnlineVideoPlayerManager.swift in Sources */,
|
||||||
E1BDF2F529524E6400CC0294 /* PlayNextItemActionButton.swift in Sources */,
|
E1BDF2F529524E6400CC0294 /* PlayNextItemActionButton.swift in Sources */,
|
||||||
BD3957772C112AD30078CEF8 /* SliderSection.swift in Sources */,
|
BD3957772C112AD30078CEF8 /* SliderSection.swift in Sources */,
|
||||||
E18E01DD288747230022598C /* iPadOSSeriesItemContentView.swift in Sources */,
|
|
||||||
E14EA1692BF7330A00DE757A /* UserProfileImageViewModel.swift in Sources */,
|
E14EA1692BF7330A00DE757A /* UserProfileImageViewModel.swift in Sources */,
|
||||||
4EB1A8CE2C9B2D0800F43898 /* ActiveSessionRow.swift in Sources */,
|
4EB1A8CE2C9B2D0800F43898 /* ActiveSessionRow.swift in Sources */,
|
||||||
E1D5C39B28DF993400CDBEFB /* ThumbSlider.swift in Sources */,
|
E1D5C39B28DF993400CDBEFB /* ThumbSlider.swift in Sources */,
|
||||||
|
@ -6915,7 +6786,6 @@
|
||||||
E1FE69AA28C29CC20021BC93 /* LandscapePosterProgressBar.swift in Sources */,
|
E1FE69AA28C29CC20021BC93 /* LandscapePosterProgressBar.swift in Sources */,
|
||||||
E1C925F72887504B002A7A66 /* PanDirectionGestureRecognizer.swift in Sources */,
|
E1C925F72887504B002A7A66 /* PanDirectionGestureRecognizer.swift in Sources */,
|
||||||
E1CB758C2C80F9EC00217C76 /* CodecProfile.swift in Sources */,
|
E1CB758C2C80F9EC00217C76 /* CodecProfile.swift in Sources */,
|
||||||
E18E01E9288747230022598C /* SeriesItemView.swift in Sources */,
|
|
||||||
E15756342936851D00976E1F /* NativeVideoPlayerSettingsView.swift in Sources */,
|
E15756342936851D00976E1F /* NativeVideoPlayerSettingsView.swift in Sources */,
|
||||||
E1D4BF7C2719D05000A11E64 /* AppSettingsView.swift in Sources */,
|
E1D4BF7C2719D05000A11E64 /* AppSettingsView.swift in Sources */,
|
||||||
4E16FD512C0183DB00110147 /* LetterPickerButton.swift in Sources */,
|
4E16FD512C0183DB00110147 /* LetterPickerButton.swift in Sources */,
|
||||||
|
@ -6998,7 +6868,6 @@
|
||||||
4EF36F662D9649050065BB79 /* SessionInfoDto.swift in Sources */,
|
4EF36F662D9649050065BB79 /* SessionInfoDto.swift in Sources */,
|
||||||
E170D0E4294CC8AB0017224C /* VideoPlayer+KeyCommands.swift in Sources */,
|
E170D0E4294CC8AB0017224C /* VideoPlayer+KeyCommands.swift in Sources */,
|
||||||
4EC1C8692C808FBB00E2879E /* CustomDeviceProfileSettingsView.swift in Sources */,
|
4EC1C8692C808FBB00E2879E /* CustomDeviceProfileSettingsView.swift in Sources */,
|
||||||
E18E01E6288747230022598C /* CollectionItemView.swift in Sources */,
|
|
||||||
E15D4F0A2B1BD88900442DB8 /* Edge.swift in Sources */,
|
E15D4F0A2B1BD88900442DB8 /* Edge.swift in Sources */,
|
||||||
6220D0BA26D6092100B8E046 /* FilterCoordinator.swift in Sources */,
|
6220D0BA26D6092100B8E046 /* FilterCoordinator.swift in Sources */,
|
||||||
E1E5D54C2783E27200692DFE /* ExperimentalSettingsView.swift in Sources */,
|
E1E5D54C2783E27200692DFE /* ExperimentalSettingsView.swift in Sources */,
|
||||||
|
@ -7032,7 +6901,6 @@
|
||||||
5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */,
|
5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */,
|
||||||
E1D8429529346C6400D1041A /* BasicStepper.swift in Sources */,
|
E1D8429529346C6400D1041A /* BasicStepper.swift in Sources */,
|
||||||
4EECA4ED2D2C89D70080A863 /* UserProfileImageCropView.swift in Sources */,
|
4EECA4ED2D2C89D70080A863 /* UserProfileImageCropView.swift in Sources */,
|
||||||
E18E01EA288747230022598C /* MovieItemView.swift in Sources */,
|
|
||||||
6220D0B726D5EE1100B8E046 /* SearchCoordinator.swift in Sources */,
|
6220D0B726D5EE1100B8E046 /* SearchCoordinator.swift in Sources */,
|
||||||
E164A7F42BE4736300A54B18 /* SignOutIntervalSection.swift in Sources */,
|
E164A7F42BE4736300A54B18 /* SignOutIntervalSection.swift in Sources */,
|
||||||
4E8F74AF2CE03E2E00CC8969 /* RefreshMetadataButton.swift in Sources */,
|
4E8F74AF2CE03E2E00CC8969 /* RefreshMetadataButton.swift in Sources */,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"originHash" : "44d87a45bd21720bc457afc9350e1268808e629c432ce75d0e6c4c26ce3b67ce",
|
"originHash" : "66bff9f26defe8d2dfa92b4e65d0ae348e3b586d0fbb7de49c9c937459e6b55c",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "blurhashkit",
|
"identity" : "blurhashkit",
|
||||||
|
@ -24,6 +24,7 @@
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/LePips/CollectionHStack",
|
"location" : "https://github.com/LePips/CollectionHStack",
|
||||||
"state" : {
|
"state" : {
|
||||||
|
"branch" : "main",
|
||||||
"revision" : "03dc666e8b20ec216fda60f55ccc0eeaabbc5fad"
|
"revision" : "03dc666e8b20ec216fda60f55ccc0eeaabbc5fad"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -32,6 +33,7 @@
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/LePips/CollectionVGrid",
|
"location" : "https://github.com/LePips/CollectionVGrid",
|
||||||
"state" : {
|
"state" : {
|
||||||
|
"branch" : "main",
|
||||||
"revision" : "70db2318ce64d49aa8b536e0623b96cb323fbdf1"
|
"revision" : "70db2318ce64d49aa8b536e0623b96cb323fbdf1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,9 +10,9 @@ import BlurHashKit
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension CollectionItemView {
|
extension ItemView {
|
||||||
|
|
||||||
struct ContentView: View {
|
struct CollectionItemContentView: View {
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var router: ItemCoordinator.Router
|
private var router: ItemCoordinator.Router
|
||||||
|
@ -21,9 +21,12 @@ extension CollectionItemView {
|
||||||
var viewModel: CollectionItemViewModel
|
var viewModel: CollectionItemViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 20) {
|
SeparatorVStack(alignment: .leading) {
|
||||||
|
RowDivider()
|
||||||
|
.padding(.vertical, 10)
|
||||||
|
} content: {
|
||||||
|
|
||||||
// MARK: Items
|
// MARK: - Items
|
||||||
|
|
||||||
ForEach(viewModel.collectionItems.elements, id: \.key) { element in
|
ForEach(viewModel.collectionItems.elements, id: \.key) { element in
|
||||||
if element.value.isNotEmpty {
|
if element.value.isNotEmpty {
|
||||||
|
@ -46,8 +49,6 @@ extension CollectionItemView {
|
||||||
.onSelect { item in
|
.onSelect { item in
|
||||||
router.route(to: \.item, item)
|
router.route(to: \.item, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,24 +56,18 @@ extension CollectionItemView {
|
||||||
|
|
||||||
if let genres = viewModel.item.itemGenres, genres.isNotEmpty {
|
if let genres = viewModel.item.itemGenres, genres.isNotEmpty {
|
||||||
ItemView.GenresHStack(genres: genres)
|
ItemView.GenresHStack(genres: genres)
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Studios
|
// MARK: Studios
|
||||||
|
|
||||||
if let studios = viewModel.item.studios, studios.isNotEmpty {
|
if let studios = viewModel.item.studios, studios.isNotEmpty {
|
||||||
ItemView.StudiosHStack(studios: studios)
|
ItemView.StudiosHStack(studios: studios)
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Similar
|
// MARK: Similar
|
||||||
|
|
||||||
if viewModel.similarItems.isNotEmpty {
|
if viewModel.similarItems.isNotEmpty {
|
||||||
ItemView.SimilarItemsHStack(items: viewModel.similarItems)
|
ItemView.SimilarItemsHStack(items: viewModel.similarItems)
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemView.AboutView(viewModel: viewModel)
|
ItemView.AboutView(viewModel: viewModel)
|
|
@ -6,12 +6,13 @@
|
||||||
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors
|
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import BlurHashKit
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension iPadOSEpisodeItemView {
|
extension ItemView {
|
||||||
|
|
||||||
struct ContentView: View {
|
struct EpisodeItemContentView: View {
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var router: ItemCoordinator.Router
|
private var router: ItemCoordinator.Router
|
||||||
|
@ -20,22 +21,21 @@ extension iPadOSEpisodeItemView {
|
||||||
var viewModel: EpisodeItemViewModel
|
var viewModel: EpisodeItemViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
SeparatorVStack(alignment: .leading) {
|
||||||
|
RowDivider()
|
||||||
|
.padding(.vertical, 10)
|
||||||
|
} content: {
|
||||||
|
|
||||||
// MARK: Genres
|
// MARK: Genres
|
||||||
|
|
||||||
if let genres = viewModel.item.itemGenres, genres.isNotEmpty {
|
if let genres = viewModel.item.itemGenres, genres.isNotEmpty {
|
||||||
ItemView.GenresHStack(genres: genres)
|
ItemView.GenresHStack(genres: genres)
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Studios
|
// MARK: Studios
|
||||||
|
|
||||||
if let studios = viewModel.item.studios, studios.isNotEmpty {
|
if let studios = viewModel.item.studios, studios.isNotEmpty {
|
||||||
ItemView.StudiosHStack(studios: studios)
|
ItemView.StudiosHStack(studios: studios)
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Cast and Crew
|
// MARK: Cast and Crew
|
||||||
|
@ -44,8 +44,6 @@ extension iPadOSEpisodeItemView {
|
||||||
castAndCrew.isNotEmpty
|
castAndCrew.isNotEmpty
|
||||||
{
|
{
|
||||||
ItemView.CastAndCrewHStack(people: castAndCrew)
|
ItemView.CastAndCrewHStack(people: castAndCrew)
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemView.AboutView(viewModel: viewModel)
|
ItemView.AboutView(viewModel: viewModel)
|
|
@ -6,15 +6,22 @@
|
||||||
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors
|
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Defaults
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
// TODO: try to make views simpler so there isn't one per media type, but per view type
|
|
||||||
// - basic (episodes, collection) vs more fancy (rest)
|
|
||||||
// - think about future for other media types
|
|
||||||
|
|
||||||
struct ItemView: View {
|
struct ItemView: View {
|
||||||
|
|
||||||
|
protocol ScrollContainerView: View {
|
||||||
|
|
||||||
|
associatedtype Content: View
|
||||||
|
|
||||||
|
init(viewModel: ItemViewModel, content: @escaping () -> Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Default(.Customization.itemViewType)
|
||||||
|
private var itemViewType
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var router: ItemCoordinator.Router
|
private var router: ItemCoordinator.Router
|
||||||
|
|
||||||
|
@ -24,7 +31,7 @@ struct ItemView: View {
|
||||||
private var deleteViewModel: DeleteItemViewModel
|
private var deleteViewModel: DeleteItemViewModel
|
||||||
|
|
||||||
@State
|
@State
|
||||||
private var showConfirmationDialog = false
|
private var isPresentingConfirmationDialog = false
|
||||||
@State
|
@State
|
||||||
private var isPresentingEventAlert = false
|
private var isPresentingEventAlert = false
|
||||||
@State
|
@State
|
||||||
|
@ -73,51 +80,58 @@ struct ItemView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var padView: some View {
|
private var scrollContentView: some View {
|
||||||
switch viewModel.item.type {
|
switch viewModel.item.type {
|
||||||
case .boxSet:
|
case .boxSet:
|
||||||
iPadOSCollectionItemView(viewModel: viewModel as! CollectionItemViewModel)
|
CollectionItemContentView(viewModel: viewModel as! CollectionItemViewModel)
|
||||||
case .episode:
|
case .episode:
|
||||||
iPadOSEpisodeItemView(viewModel: viewModel as! EpisodeItemViewModel)
|
EpisodeItemContentView(viewModel: viewModel as! EpisodeItemViewModel)
|
||||||
case .movie:
|
case .movie:
|
||||||
iPadOSMovieItemView(viewModel: viewModel as! MovieItemViewModel)
|
MovieItemContentView(viewModel: viewModel as! MovieItemViewModel)
|
||||||
case .series:
|
case .series:
|
||||||
iPadOSSeriesItemView(viewModel: viewModel as! SeriesItemViewModel)
|
SeriesItemContentView(viewModel: viewModel as! SeriesItemViewModel)
|
||||||
default:
|
default:
|
||||||
Text(L10n.notImplementedYetWithType(viewModel.item.type ?? "--"))
|
Text(L10n.notImplementedYetWithType(viewModel.item.type ?? "--"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
// TODO: break out into pad vs phone views based on item type
|
||||||
private var phoneView: some View {
|
private func scrollContainerView<Content: View>(
|
||||||
switch viewModel.item.type {
|
viewModel: ItemViewModel,
|
||||||
case .boxSet:
|
content: @escaping () -> Content
|
||||||
CollectionItemView(viewModel: viewModel as! CollectionItemViewModel)
|
) -> any ScrollContainerView {
|
||||||
case .episode:
|
|
||||||
EpisodeItemView(viewModel: viewModel as! EpisodeItemViewModel)
|
|
||||||
case .movie:
|
|
||||||
MovieItemView(viewModel: viewModel as! MovieItemViewModel)
|
|
||||||
case .series:
|
|
||||||
SeriesItemView(viewModel: viewModel as! SeriesItemViewModel)
|
|
||||||
default:
|
|
||||||
Text(L10n.notImplementedYetWithType(viewModel.item.type ?? "--"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private var contentView: some View {
|
|
||||||
if UIDevice.isPad {
|
if UIDevice.isPad {
|
||||||
padView
|
return iPadOSCinematicScrollView(viewModel: viewModel, content: content)
|
||||||
} else {
|
|
||||||
phoneView
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if viewModel.item.type == .movie || viewModel.item.type == .series {
|
||||||
|
switch itemViewType {
|
||||||
|
case .compactPoster:
|
||||||
|
return CompactPosterScrollView(viewModel: viewModel, content: content)
|
||||||
|
case .compactLogo:
|
||||||
|
return CompactLogoScrollView(viewModel: viewModel, content: content)
|
||||||
|
case .cinematic:
|
||||||
|
return CinematicScrollView(viewModel: viewModel, content: content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SimpleScrollView(viewModel: viewModel, content: content)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var innerBody: some View {
|
||||||
|
scrollContainerView(viewModel: viewModel) {
|
||||||
|
scrollContentView
|
||||||
|
}
|
||||||
|
.eraseToAnyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
switch viewModel.state {
|
switch viewModel.state {
|
||||||
case .content:
|
case .content:
|
||||||
contentView
|
innerBody
|
||||||
.navigationTitle(viewModel.item.displayTitle)
|
.navigationTitle(viewModel.item.displayTitle)
|
||||||
case let .error(error):
|
case let .error(error):
|
||||||
ErrorView(error: error)
|
ErrorView(error: error)
|
||||||
|
@ -143,14 +157,14 @@ struct ItemView: View {
|
||||||
if canDelete {
|
if canDelete {
|
||||||
Section {
|
Section {
|
||||||
Button(L10n.delete, systemImage: "trash", role: .destructive) {
|
Button(L10n.delete, systemImage: "trash", role: .destructive) {
|
||||||
showConfirmationDialog = true
|
isPresentingConfirmationDialog = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.confirmationDialog(
|
.confirmationDialog(
|
||||||
L10n.deleteItemConfirmationMessage,
|
L10n.deleteItemConfirmationMessage,
|
||||||
isPresented: $showConfirmationDialog,
|
isPresented: $isPresentingConfirmationDialog,
|
||||||
titleVisibility: .visible
|
titleVisibility: .visible
|
||||||
) {
|
) {
|
||||||
Button(L10n.confirm, role: .destructive) {
|
Button(L10n.confirm, role: .destructive) {
|
||||||
|
|
|
@ -10,30 +10,29 @@ import Defaults
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension MovieItemView {
|
extension ItemView {
|
||||||
|
|
||||||
struct ContentView: View {
|
struct MovieItemContentView: View {
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var viewModel: MovieItemViewModel
|
var viewModel: MovieItemViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
SeparatorVStack(alignment: .leading) {
|
||||||
|
RowDivider()
|
||||||
|
.padding(.vertical, 10)
|
||||||
|
} content: {
|
||||||
|
|
||||||
// MARK: Genres
|
// MARK: Genres
|
||||||
|
|
||||||
if let genres = viewModel.item.itemGenres, genres.isNotEmpty {
|
if let genres = viewModel.item.itemGenres, genres.isNotEmpty {
|
||||||
ItemView.GenresHStack(genres: genres)
|
ItemView.GenresHStack(genres: genres)
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Studios
|
// MARK: Studios
|
||||||
|
|
||||||
if let studios = viewModel.item.studios, studios.isNotEmpty {
|
if let studios = viewModel.item.studios, studios.isNotEmpty {
|
||||||
ItemView.StudiosHStack(studios: studios)
|
ItemView.StudiosHStack(studios: studios)
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Cast and Crew
|
// MARK: Cast and Crew
|
||||||
|
@ -42,29 +41,22 @@ extension MovieItemView {
|
||||||
castAndCrew.isNotEmpty
|
castAndCrew.isNotEmpty
|
||||||
{
|
{
|
||||||
ItemView.CastAndCrewHStack(people: castAndCrew)
|
ItemView.CastAndCrewHStack(people: castAndCrew)
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Special Features
|
// MARK: Special Features
|
||||||
|
|
||||||
if viewModel.specialFeatures.isNotEmpty {
|
if viewModel.specialFeatures.isNotEmpty {
|
||||||
ItemView.SpecialFeaturesHStack(items: viewModel.specialFeatures)
|
ItemView.SpecialFeaturesHStack(items: viewModel.specialFeatures)
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Similar
|
// MARK: Similar
|
||||||
|
|
||||||
if viewModel.similarItems.isNotEmpty {
|
if viewModel.similarItems.isNotEmpty {
|
||||||
ItemView.SimilarItemsHStack(items: viewModel.similarItems)
|
ItemView.SimilarItemsHStack(items: viewModel.similarItems)
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemView.AboutView(viewModel: viewModel)
|
ItemView.AboutView(viewModel: viewModel)
|
||||||
}
|
}
|
||||||
.animation(.linear(duration: 0.2), value: viewModel.item)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -12,7 +12,7 @@ import SwiftUI
|
||||||
|
|
||||||
extension ItemView {
|
extension ItemView {
|
||||||
|
|
||||||
struct CinematicScrollView<Content: View>: View {
|
struct CinematicScrollView<Content: View>: ScrollContainerView {
|
||||||
|
|
||||||
@Default(.Customization.CinematicItemViewType.usePrimaryImage)
|
@Default(.Customization.CinematicItemViewType.usePrimaryImage)
|
||||||
private var usePrimaryImage
|
private var usePrimaryImage
|
||||||
|
@ -21,12 +21,29 @@ extension ItemView {
|
||||||
private var router: ItemCoordinator.Router
|
private var router: ItemCoordinator.Router
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var viewModel: ItemViewModel
|
private var viewModel: ItemViewModel
|
||||||
|
|
||||||
@State
|
private let blurHashBottomEdgeColor: Color
|
||||||
private var blurHashBottomEdgeColor: Color = .secondarySystemFill
|
private let content: Content
|
||||||
|
|
||||||
let content: () -> Content
|
init(
|
||||||
|
viewModel: ItemViewModel,
|
||||||
|
content: @escaping () -> Content
|
||||||
|
) {
|
||||||
|
if let backdropBlurHash = viewModel.item.blurHash(.backdrop) {
|
||||||
|
let bottomRGB = BlurHash(string: backdropBlurHash)!.averageLinearRGB
|
||||||
|
blurHashBottomEdgeColor = Color(
|
||||||
|
red: Double(bottomRGB.0),
|
||||||
|
green: Double(bottomRGB.1),
|
||||||
|
blue: Double(bottomRGB.2)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
blurHashBottomEdgeColor = Color.secondarySystemFill
|
||||||
|
}
|
||||||
|
|
||||||
|
self.content = content()
|
||||||
|
self.viewModel = viewModel
|
||||||
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var headerView: some View {
|
private var headerView: some View {
|
||||||
|
@ -37,16 +54,6 @@ extension ItemView {
|
||||||
.aspectRatio(usePrimaryImage ? (2 / 3) : 1.77, contentMode: .fill)
|
.aspectRatio(usePrimaryImage ? (2 / 3) : 1.77, contentMode: .fill)
|
||||||
.frame(height: UIScreen.main.bounds.height * 0.6)
|
.frame(height: UIScreen.main.bounds.height * 0.6)
|
||||||
.bottomEdgeGradient(bottomColor: blurHashBottomEdgeColor)
|
.bottomEdgeGradient(bottomColor: blurHashBottomEdgeColor)
|
||||||
.onAppear {
|
|
||||||
if let headerBlurHash = viewModel.item.blurHash(.backdrop) {
|
|
||||||
let bottomRGB = BlurHash(string: headerBlurHash)!.averageLinearRGB
|
|
||||||
blurHashBottomEdgeColor = Color(
|
|
||||||
red: Double(bottomRGB.0),
|
|
||||||
green: Double(bottomRGB.1),
|
|
||||||
blue: Double(bottomRGB.2)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
@ -75,7 +82,7 @@ extension ItemView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} content: {
|
} content: {
|
||||||
content()
|
content
|
||||||
.edgePadding(.vertical)
|
.edgePadding(.vertical)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,7 +94,7 @@ extension ItemView.CinematicScrollView {
|
||||||
struct OverlayView: View {
|
struct OverlayView: View {
|
||||||
|
|
||||||
@Default(.Customization.CinematicItemViewType.usePrimaryImage)
|
@Default(.Customization.CinematicItemViewType.usePrimaryImage)
|
||||||
private var cinematicItemViewTypeUsePrimaryImage
|
private var usePrimaryImage
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var router: ItemCoordinator.Router
|
private var router: ItemCoordinator.Router
|
||||||
|
@ -97,7 +104,7 @@ extension ItemView.CinematicScrollView {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
VStack(alignment: .center, spacing: 10) {
|
VStack(alignment: .center, spacing: 10) {
|
||||||
if !cinematicItemViewTypeUsePrimaryImage {
|
if !usePrimaryImage {
|
||||||
ImageView(viewModel.item.imageURL(.logo, maxHeight: 100))
|
ImageView(viewModel.item.imageURL(.logo, maxHeight: 100))
|
||||||
.placeholder { _ in
|
.placeholder { _ in
|
||||||
EmptyView()
|
EmptyView()
|
||||||
|
@ -130,16 +137,17 @@ extension ItemView.CinematicScrollView {
|
||||||
.foregroundColor(Color(UIColor.lightGray))
|
.foregroundColor(Color(UIColor.lightGray))
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
|
|
||||||
if viewModel.presentPlayButton {
|
Group {
|
||||||
ItemView.PlayButton(viewModel: viewModel)
|
if viewModel.presentPlayButton {
|
||||||
.frame(maxWidth: 300)
|
ItemView.PlayButton(viewModel: viewModel)
|
||||||
.frame(height: 50)
|
.frame(height: 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemView.ActionButtonHStack(viewModel: viewModel)
|
ItemView.ActionButtonHStack(viewModel: viewModel)
|
||||||
.font(.title)
|
.font(.title)
|
||||||
.frame(maxWidth: 300)
|
.foregroundColor(.white)
|
||||||
.foregroundColor(.white)
|
}
|
||||||
|
.frame(maxWidth: 300)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
|
|
@ -11,20 +11,35 @@ import SwiftUI
|
||||||
|
|
||||||
extension ItemView {
|
extension ItemView {
|
||||||
|
|
||||||
struct CompactLogoScrollView<Content: View>: View {
|
struct CompactLogoScrollView<Content: View>: ScrollContainerView {
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var router: ItemCoordinator.Router
|
private var router: ItemCoordinator.Router
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var viewModel: ItemViewModel
|
private var viewModel: ItemViewModel
|
||||||
|
|
||||||
@State
|
private let blurHashBottomEdgeColor: Color
|
||||||
private var scrollViewOffset: CGFloat = 0
|
private let content: Content
|
||||||
@State
|
|
||||||
private var blurHashBottomEdgeColor: Color = .secondarySystemFill
|
|
||||||
|
|
||||||
let content: () -> Content
|
init(
|
||||||
|
viewModel: ItemViewModel,
|
||||||
|
content: @escaping () -> Content
|
||||||
|
) {
|
||||||
|
if let backdropBlurHash = viewModel.item.blurHash(.backdrop) {
|
||||||
|
let bottomRGB = BlurHash(string: backdropBlurHash)!.averageLinearRGB
|
||||||
|
blurHashBottomEdgeColor = Color(
|
||||||
|
red: Double(bottomRGB.0),
|
||||||
|
green: Double(bottomRGB.1),
|
||||||
|
blue: Double(bottomRGB.2)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
blurHashBottomEdgeColor = Color.secondarySystemFill
|
||||||
|
}
|
||||||
|
|
||||||
|
self.content = content()
|
||||||
|
self.viewModel = viewModel
|
||||||
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var headerView: some View {
|
private var headerView: some View {
|
||||||
|
@ -32,16 +47,6 @@ extension ItemView {
|
||||||
.aspectRatio(1.77, contentMode: .fill)
|
.aspectRatio(1.77, contentMode: .fill)
|
||||||
.frame(height: UIScreen.main.bounds.height * 0.35)
|
.frame(height: UIScreen.main.bounds.height * 0.35)
|
||||||
.bottomEdgeGradient(bottomColor: blurHashBottomEdgeColor)
|
.bottomEdgeGradient(bottomColor: blurHashBottomEdgeColor)
|
||||||
.onAppear {
|
|
||||||
if let backdropBlurHash = viewModel.item.blurHash(.backdrop) {
|
|
||||||
let bottomRGB = BlurHash(string: backdropBlurHash)!.averageLinearRGB
|
|
||||||
blurHashBottomEdgeColor = Color(
|
|
||||||
red: Double(bottomRGB.0),
|
|
||||||
green: Double(bottomRGB.1),
|
|
||||||
blue: Double(bottomRGB.2)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
@ -78,7 +83,7 @@ extension ItemView {
|
||||||
|
|
||||||
RowDivider()
|
RowDivider()
|
||||||
|
|
||||||
content()
|
content
|
||||||
}
|
}
|
||||||
.edgePadding(.vertical)
|
.edgePadding(.vertical)
|
||||||
}
|
}
|
||||||
|
@ -131,14 +136,17 @@ extension ItemView.CompactLogoScrollView {
|
||||||
|
|
||||||
ItemView.AttributesHStack(viewModel: viewModel)
|
ItemView.AttributesHStack(viewModel: viewModel)
|
||||||
|
|
||||||
if viewModel.presentPlayButton {
|
Group {
|
||||||
ItemView.PlayButton(viewModel: viewModel)
|
if viewModel.presentPlayButton {
|
||||||
.frame(height: 50)
|
ItemView.PlayButton(viewModel: viewModel)
|
||||||
}
|
.frame(height: 50)
|
||||||
|
}
|
||||||
|
|
||||||
ItemView.ActionButtonHStack(viewModel: viewModel)
|
ItemView.ActionButtonHStack(viewModel: viewModel)
|
||||||
.font(.title)
|
.font(.title)
|
||||||
.foregroundColor(.white)
|
.foregroundStyle(.white)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: 300)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .bottom)
|
.frame(maxWidth: .infinity, alignment: .bottom)
|
||||||
}
|
}
|
|
@ -11,27 +11,34 @@ import SwiftUI
|
||||||
|
|
||||||
extension ItemView {
|
extension ItemView {
|
||||||
|
|
||||||
struct CompactPosterScrollView<Content: View>: View {
|
struct CompactPosterScrollView<Content: View>: ScrollContainerView {
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var router: ItemCoordinator.Router
|
private var router: ItemCoordinator.Router
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var viewModel: ItemViewModel
|
private var viewModel: ItemViewModel
|
||||||
|
|
||||||
@State
|
private let blurHashBottomEdgeColor: Color
|
||||||
private var scrollViewOffset: CGFloat = 0
|
private let content: Content
|
||||||
@State
|
|
||||||
private var blurHashBottomEdgeColor: Color = .secondarySystemFill
|
|
||||||
|
|
||||||
let content: () -> Content
|
init(
|
||||||
|
viewModel: ItemViewModel,
|
||||||
|
content: @escaping () -> Content
|
||||||
|
) {
|
||||||
|
if let backdropBlurHash = viewModel.item.blurHash(.backdrop) {
|
||||||
|
let bottomRGB = BlurHash(string: backdropBlurHash)!.averageLinearRGB
|
||||||
|
blurHashBottomEdgeColor = Color(
|
||||||
|
red: Double(bottomRGB.0),
|
||||||
|
green: Double(bottomRGB.1),
|
||||||
|
blue: Double(bottomRGB.2)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
blurHashBottomEdgeColor = Color.secondarySystemFill
|
||||||
|
}
|
||||||
|
|
||||||
private var topOpacity: CGFloat {
|
self.content = content()
|
||||||
let start = UIScreen.main.bounds.height * 0.20
|
self.viewModel = viewModel
|
||||||
let end = UIScreen.main.bounds.height * 0.4
|
|
||||||
let diff = end - start
|
|
||||||
let opacity = clamp((scrollViewOffset - start) / diff, min: 0, max: 1)
|
|
||||||
return opacity
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
@ -40,16 +47,6 @@ extension ItemView {
|
||||||
.aspectRatio(1.77, contentMode: .fill)
|
.aspectRatio(1.77, contentMode: .fill)
|
||||||
.frame(height: UIScreen.main.bounds.height * 0.35)
|
.frame(height: UIScreen.main.bounds.height * 0.35)
|
||||||
.bottomEdgeGradient(bottomColor: blurHashBottomEdgeColor)
|
.bottomEdgeGradient(bottomColor: blurHashBottomEdgeColor)
|
||||||
.onAppear {
|
|
||||||
if let backdropBlurHash = viewModel.item.blurHash(.backdrop) {
|
|
||||||
let bottomRGB = BlurHash(string: backdropBlurHash)!.averageLinearRGB
|
|
||||||
blurHashBottomEdgeColor = Color(
|
|
||||||
red: Double(bottomRGB.0),
|
|
||||||
green: Double(bottomRGB.1),
|
|
||||||
blue: Double(bottomRGB.2)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
@ -87,7 +84,7 @@ extension ItemView {
|
||||||
|
|
||||||
RowDivider()
|
RowDivider()
|
||||||
|
|
||||||
content()
|
content
|
||||||
}
|
}
|
||||||
.edgePadding(.vertical)
|
.edgePadding(.vertical)
|
||||||
}
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
//
|
||||||
|
// 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 BlurHashKit
|
||||||
|
import JellyfinAPI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension ItemView {
|
||||||
|
|
||||||
|
struct SimpleScrollView<Content: View>: ScrollContainerView {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: ItemCoordinator.Router
|
||||||
|
|
||||||
|
@ObservedObject
|
||||||
|
private var viewModel: ItemViewModel
|
||||||
|
|
||||||
|
private let content: Content
|
||||||
|
|
||||||
|
init(
|
||||||
|
viewModel: ItemViewModel,
|
||||||
|
@ViewBuilder content: () -> Content
|
||||||
|
) {
|
||||||
|
self.content = content()
|
||||||
|
self.viewModel = viewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var shelfView: some View {
|
||||||
|
VStack(alignment: .center, spacing: 10) {
|
||||||
|
if let parentTitle = viewModel.item.parentTitle {
|
||||||
|
Text(parentTitle)
|
||||||
|
.font(.headline)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.lineLimit(2)
|
||||||
|
.padding(.horizontal)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(viewModel.item.displayTitle)
|
||||||
|
.font(.title2)
|
||||||
|
.fontWeight(.bold)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.lineLimit(2)
|
||||||
|
.padding(.horizontal)
|
||||||
|
|
||||||
|
DotHStack {
|
||||||
|
if let seasonEpisodeLabel = viewModel.item.seasonEpisodeLabel {
|
||||||
|
Text(seasonEpisodeLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let productionYear = viewModel.item.premiereDateYear {
|
||||||
|
Text(productionYear)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let runtime = viewModel.item.runTimeLabel {
|
||||||
|
Text(runtime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.padding(.horizontal)
|
||||||
|
|
||||||
|
ItemView.AttributesHStack(viewModel: viewModel, alignment: .center)
|
||||||
|
|
||||||
|
Group {
|
||||||
|
if viewModel.presentPlayButton {
|
||||||
|
ItemView.PlayButton(viewModel: viewModel)
|
||||||
|
.frame(height: 50)
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemView.ActionButtonHStack(viewModel: viewModel)
|
||||||
|
.font(.title)
|
||||||
|
.foregroundStyle(.primary)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: 300)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var imageType: ImageType {
|
||||||
|
if viewModel.item.type == .episode {
|
||||||
|
return .primary
|
||||||
|
} else {
|
||||||
|
return .backdrop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var header: some View {
|
||||||
|
VStack(alignment: .center) {
|
||||||
|
ImageView(viewModel.item.imageSource(imageType, maxWidth: 600))
|
||||||
|
.placeholder { source in
|
||||||
|
if let blurHash = source.blurHash {
|
||||||
|
BlurHashView(blurHash: blurHash, size: .Square(length: 8))
|
||||||
|
} else {
|
||||||
|
Color.secondarySystemFill
|
||||||
|
.opacity(0.75)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.failure {
|
||||||
|
SystemImageContentView(systemName: viewModel.item.systemImage)
|
||||||
|
}
|
||||||
|
.frame(maxHeight: 300)
|
||||||
|
.posterStyle(.landscape)
|
||||||
|
.posterShadow()
|
||||||
|
.padding(.horizontal)
|
||||||
|
|
||||||
|
shelfView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ScrollView(showsIndicators: false) {
|
||||||
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
|
||||||
|
header
|
||||||
|
|
||||||
|
// MARK: Overview
|
||||||
|
|
||||||
|
ItemView.OverviewView(item: viewModel.item)
|
||||||
|
.overviewLineLimit(4)
|
||||||
|
.padding(.horizontal)
|
||||||
|
|
||||||
|
RowDivider()
|
||||||
|
|
||||||
|
// MARK: Genres
|
||||||
|
|
||||||
|
content
|
||||||
|
.edgePadding(.bottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@
|
||||||
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors
|
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import JellyfinAPI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
// TODO: remove rest occurrences of `UIDevice.main` sizings
|
// TODO: remove rest occurrences of `UIDevice.main` sizings
|
||||||
|
@ -17,33 +18,38 @@ import SwiftUI
|
||||||
|
|
||||||
extension ItemView {
|
extension ItemView {
|
||||||
|
|
||||||
struct iPadOSCinematicScrollView<Content: View>: View {
|
struct iPadOSCinematicScrollView<Content: View>: ScrollContainerView {
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var viewModel: ItemViewModel
|
private var viewModel: ItemViewModel
|
||||||
|
|
||||||
@State
|
@State
|
||||||
private var globalSize: CGSize = .zero
|
private var globalSize: CGSize = .zero
|
||||||
|
|
||||||
let content: () -> Content
|
private let content: Content
|
||||||
|
|
||||||
@ViewBuilder
|
init(
|
||||||
private var headerView: some View {
|
viewModel: ItemViewModel,
|
||||||
Group {
|
@ViewBuilder content: () -> Content
|
||||||
if viewModel.item.type == .episode {
|
) {
|
||||||
ImageView(viewModel.item.imageSource(.primary, maxWidth: 1920))
|
self.content = content()
|
||||||
} else {
|
self.viewModel = viewModel
|
||||||
ImageView(viewModel.item.imageSource(.backdrop, maxWidth: 1920))
|
}
|
||||||
}
|
|
||||||
|
private var imageType: ImageType {
|
||||||
|
if viewModel.item.type == .episode {
|
||||||
|
return .primary
|
||||||
|
} else {
|
||||||
|
return .backdrop
|
||||||
}
|
}
|
||||||
.aspectRatio(1.77, contentMode: .fill)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
OffsetScrollView(
|
OffsetScrollView(
|
||||||
headerHeight: globalSize.isLandscape ? 0.75 : 0.6
|
headerHeight: globalSize.isLandscape ? 0.75 : 0.6
|
||||||
) {
|
) {
|
||||||
headerView
|
ImageView(viewModel.item.imageSource(.backdrop, maxWidth: 1920))
|
||||||
|
.aspectRatio(1.77, contentMode: .fill)
|
||||||
} overlay: {
|
} overlay: {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
@ -65,7 +71,7 @@ extension ItemView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} content: {
|
} content: {
|
||||||
content()
|
content
|
||||||
.edgePadding(.vertical)
|
.edgePadding(.vertical)
|
||||||
}
|
}
|
||||||
.trackingSize($globalSize)
|
.trackingSize($globalSize)
|
|
@ -10,15 +10,18 @@ import Defaults
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension SeriesItemView {
|
extension ItemView {
|
||||||
|
|
||||||
struct ContentView: View {
|
struct SeriesItemContentView: View {
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var viewModel: SeriesItemViewModel
|
var viewModel: SeriesItemViewModel
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 20) {
|
SeparatorVStack(alignment: .leading) {
|
||||||
|
RowDivider()
|
||||||
|
.padding(.vertical, 10)
|
||||||
|
} content: {
|
||||||
|
|
||||||
// MARK: Episodes
|
// MARK: Episodes
|
||||||
|
|
||||||
|
@ -30,16 +33,12 @@ extension SeriesItemView {
|
||||||
|
|
||||||
if let genres = viewModel.item.itemGenres, genres.isNotEmpty {
|
if let genres = viewModel.item.itemGenres, genres.isNotEmpty {
|
||||||
ItemView.GenresHStack(genres: genres)
|
ItemView.GenresHStack(genres: genres)
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Studios
|
// MARK: Studios
|
||||||
|
|
||||||
if let studios = viewModel.item.studios, studios.isNotEmpty {
|
if let studios = viewModel.item.studios, studios.isNotEmpty {
|
||||||
ItemView.StudiosHStack(studios: studios)
|
ItemView.StudiosHStack(studios: studios)
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Cast and Crew
|
// MARK: Cast and Crew
|
||||||
|
@ -48,24 +47,18 @@ extension SeriesItemView {
|
||||||
castAndCrew.isNotEmpty
|
castAndCrew.isNotEmpty
|
||||||
{
|
{
|
||||||
ItemView.CastAndCrewHStack(people: castAndCrew)
|
ItemView.CastAndCrewHStack(people: castAndCrew)
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Special Features
|
// MARK: Special Features
|
||||||
|
|
||||||
if viewModel.specialFeatures.isNotEmpty {
|
if viewModel.specialFeatures.isNotEmpty {
|
||||||
ItemView.SpecialFeaturesHStack(items: viewModel.specialFeatures)
|
ItemView.SpecialFeaturesHStack(items: viewModel.specialFeatures)
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Similar
|
// MARK: Similar
|
||||||
|
|
||||||
if viewModel.similarItems.isNotEmpty {
|
if viewModel.similarItems.isNotEmpty {
|
||||||
ItemView.SimilarItemsHStack(items: viewModel.similarItems)
|
ItemView.SimilarItemsHStack(items: viewModel.similarItems)
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemView.AboutView(viewModel: viewModel)
|
ItemView.AboutView(viewModel: viewModel)
|
|
@ -1,37 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
||||||
|
|
||||||
struct CollectionItemView: View {
|
|
||||||
|
|
||||||
@Default(.Customization.itemViewType)
|
|
||||||
private var itemViewType
|
|
||||||
|
|
||||||
@ObservedObject
|
|
||||||
var viewModel: CollectionItemViewModel
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
switch itemViewType {
|
|
||||||
case .compactPoster:
|
|
||||||
ItemView.CompactPosterScrollView(viewModel: viewModel) {
|
|
||||||
ContentView(viewModel: viewModel)
|
|
||||||
}
|
|
||||||
case .compactLogo:
|
|
||||||
ItemView.CompactLogoScrollView(viewModel: viewModel) {
|
|
||||||
ContentView(viewModel: viewModel)
|
|
||||||
}
|
|
||||||
case .cinematic:
|
|
||||||
ItemView.CinematicScrollView(viewModel: viewModel) {
|
|
||||||
ContentView(viewModel: viewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,144 +0,0 @@
|
||||||
//
|
|
||||||
// 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 BlurHashKit
|
|
||||||
import JellyfinAPI
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
extension EpisodeItemView {
|
|
||||||
|
|
||||||
struct ContentView: View {
|
|
||||||
|
|
||||||
@EnvironmentObject
|
|
||||||
private var router: ItemCoordinator.Router
|
|
||||||
|
|
||||||
@ObservedObject
|
|
||||||
var viewModel: EpisodeItemViewModel
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
|
||||||
|
|
||||||
VStack(alignment: .center) {
|
|
||||||
ImageView(viewModel.item.imageSource(.primary, maxWidth: 600))
|
|
||||||
.placeholder { source in
|
|
||||||
if let blurHash = source.blurHash {
|
|
||||||
BlurHashView(blurHash: blurHash, size: .Square(length: 8))
|
|
||||||
} else {
|
|
||||||
Color.secondarySystemFill
|
|
||||||
.opacity(0.75)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.failure {
|
|
||||||
SystemImageContentView(systemName: viewModel.item.systemImage)
|
|
||||||
}
|
|
||||||
.frame(maxHeight: 300)
|
|
||||||
.posterStyle(.landscape)
|
|
||||||
.posterShadow()
|
|
||||||
.padding(.horizontal)
|
|
||||||
|
|
||||||
ShelfView(viewModel: viewModel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Overview
|
|
||||||
|
|
||||||
ItemView.OverviewView(item: viewModel.item)
|
|
||||||
.overviewLineLimit(4)
|
|
||||||
.padding(.horizontal)
|
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
|
|
||||||
// MARK: Genres
|
|
||||||
|
|
||||||
if let genres = viewModel.item.itemGenres, genres.isNotEmpty {
|
|
||||||
ItemView.GenresHStack(genres: genres)
|
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Studios
|
|
||||||
|
|
||||||
if let studios = viewModel.item.studios, studios.isNotEmpty {
|
|
||||||
ItemView.StudiosHStack(studios: studios)
|
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Cast and Crew
|
|
||||||
|
|
||||||
if let castAndCrew = viewModel.item.people,
|
|
||||||
castAndCrew.isNotEmpty
|
|
||||||
{
|
|
||||||
ItemView.CastAndCrewHStack(people: castAndCrew)
|
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemView.AboutView(viewModel: viewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension EpisodeItemView.ContentView {
|
|
||||||
|
|
||||||
struct ShelfView: View {
|
|
||||||
|
|
||||||
@EnvironmentObject
|
|
||||||
private var router: ItemCoordinator.Router
|
|
||||||
|
|
||||||
@ObservedObject
|
|
||||||
var viewModel: EpisodeItemViewModel
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .center, spacing: 10) {
|
|
||||||
Text(viewModel.item.seriesName ?? .emptyDash)
|
|
||||||
.font(.headline)
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.lineLimit(2)
|
|
||||||
.padding(.horizontal)
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
|
|
||||||
Text(viewModel.item.displayTitle)
|
|
||||||
.font(.title2)
|
|
||||||
.fontWeight(.bold)
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.lineLimit(2)
|
|
||||||
.padding(.horizontal)
|
|
||||||
|
|
||||||
DotHStack {
|
|
||||||
if let seasonEpisodeLabel = viewModel.item.seasonEpisodeLabel {
|
|
||||||
Text(seasonEpisodeLabel)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let productionYear = viewModel.item.premiereDateYear {
|
|
||||||
Text(productionYear)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let runtime = viewModel.item.runTimeLabel {
|
|
||||||
Text(runtime)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
.padding(.horizontal)
|
|
||||||
|
|
||||||
ItemView.AttributesHStack(viewModel: viewModel, alignment: .center)
|
|
||||||
|
|
||||||
ItemView.PlayButton(viewModel: viewModel)
|
|
||||||
.frame(maxWidth: 300)
|
|
||||||
.frame(height: 50)
|
|
||||||
|
|
||||||
ItemView.ActionButtonHStack(viewModel: viewModel)
|
|
||||||
.font(.title)
|
|
||||||
.frame(maxWidth: 300)
|
|
||||||
.foregroundStyle(.primary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
||||||
|
|
||||||
struct EpisodeItemView: View {
|
|
||||||
|
|
||||||
@ObservedObject
|
|
||||||
var viewModel: EpisodeItemViewModel
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ScrollView(showsIndicators: false) {
|
|
||||||
ContentView(viewModel: viewModel)
|
|
||||||
.edgePadding(.bottom)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
||||||
|
|
||||||
struct MovieItemView: View {
|
|
||||||
|
|
||||||
@Default(.Customization.itemViewType)
|
|
||||||
private var itemViewType
|
|
||||||
|
|
||||||
@ObservedObject
|
|
||||||
var viewModel: MovieItemViewModel
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
switch itemViewType {
|
|
||||||
case .compactPoster:
|
|
||||||
ItemView.CompactPosterScrollView(viewModel: viewModel) {
|
|
||||||
ContentView(viewModel: viewModel)
|
|
||||||
}
|
|
||||||
case .compactLogo:
|
|
||||||
ItemView.CompactLogoScrollView(viewModel: viewModel) {
|
|
||||||
ContentView(viewModel: viewModel)
|
|
||||||
}
|
|
||||||
case .cinematic:
|
|
||||||
ItemView.CinematicScrollView(viewModel: viewModel) {
|
|
||||||
ContentView(viewModel: viewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
||||||
|
|
||||||
struct SeriesItemView: View {
|
|
||||||
|
|
||||||
@Default(.Customization.itemViewType)
|
|
||||||
private var itemViewType
|
|
||||||
|
|
||||||
@ObservedObject
|
|
||||||
var viewModel: SeriesItemViewModel
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
switch itemViewType {
|
|
||||||
case .compactPoster:
|
|
||||||
ItemView.CompactPosterScrollView(viewModel: viewModel) {
|
|
||||||
ContentView(viewModel: viewModel)
|
|
||||||
}
|
|
||||||
case .compactLogo:
|
|
||||||
ItemView.CompactLogoScrollView(viewModel: viewModel) {
|
|
||||||
ContentView(viewModel: viewModel)
|
|
||||||
}
|
|
||||||
case .cinematic:
|
|
||||||
ItemView.CinematicScrollView(viewModel: viewModel) {
|
|
||||||
ContentView(viewModel: viewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
//
|
|
||||||
// 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 iPadOSCollectionItemView {
|
|
||||||
|
|
||||||
struct ContentView: View {
|
|
||||||
|
|
||||||
@EnvironmentObject
|
|
||||||
private var router: ItemCoordinator.Router
|
|
||||||
|
|
||||||
@ObservedObject
|
|
||||||
var viewModel: CollectionItemViewModel
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading, spacing: 20) {
|
|
||||||
|
|
||||||
// MARK: Items
|
|
||||||
|
|
||||||
ForEach(viewModel.collectionItems.elements, id: \.key) { element in
|
|
||||||
if element.value.isNotEmpty {
|
|
||||||
PosterHStack(
|
|
||||||
title: element.key.pluralDisplayTitle,
|
|
||||||
type: .portrait,
|
|
||||||
items: element.value
|
|
||||||
)
|
|
||||||
.trailing {
|
|
||||||
SeeAllButton()
|
|
||||||
.onSelect {
|
|
||||||
let viewModel = ItemLibraryViewModel(
|
|
||||||
title: viewModel.item.displayTitle,
|
|
||||||
id: viewModel.item.id,
|
|
||||||
element.value
|
|
||||||
)
|
|
||||||
router.route(to: \.library, viewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onSelect { item in
|
|
||||||
router.route(to: \.item, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Genres
|
|
||||||
|
|
||||||
if let genres = viewModel.item.itemGenres, genres.isNotEmpty {
|
|
||||||
ItemView.GenresHStack(genres: genres)
|
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Studios
|
|
||||||
|
|
||||||
if let studios = viewModel.item.studios, studios.isNotEmpty {
|
|
||||||
ItemView.StudiosHStack(studios: studios)
|
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Similar
|
|
||||||
|
|
||||||
if viewModel.similarItems.isNotEmpty {
|
|
||||||
ItemView.SimilarItemsHStack(items: viewModel.similarItems)
|
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemView.AboutView(viewModel: viewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
||||||
|
|
||||||
struct iPadOSCollectionItemView: View {
|
|
||||||
|
|
||||||
@ObservedObject
|
|
||||||
var viewModel: CollectionItemViewModel
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ItemView.iPadOSCinematicScrollView(viewModel: viewModel) {
|
|
||||||
ContentView(viewModel: viewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
||||||
|
|
||||||
struct iPadOSEpisodeItemView: View {
|
|
||||||
|
|
||||||
@ObservedObject
|
|
||||||
var viewModel: EpisodeItemViewModel
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ItemView.iPadOSCinematicScrollView(viewModel: viewModel) {
|
|
||||||
ContentView(viewModel: viewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
//
|
|
||||||
// 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 iPadOSMovieItemView {
|
|
||||||
|
|
||||||
struct ContentView: View {
|
|
||||||
|
|
||||||
@ObservedObject
|
|
||||||
var viewModel: MovieItemViewModel
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
|
||||||
|
|
||||||
// MARK: Genres
|
|
||||||
|
|
||||||
if let genres = viewModel.item.itemGenres, genres.isNotEmpty {
|
|
||||||
ItemView.GenresHStack(genres: genres)
|
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Studios
|
|
||||||
|
|
||||||
if let studios = viewModel.item.studios, studios.isNotEmpty {
|
|
||||||
ItemView.StudiosHStack(studios: studios)
|
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Cast and Crew
|
|
||||||
|
|
||||||
if let castAndCrew = viewModel.item.people,
|
|
||||||
castAndCrew.isNotEmpty
|
|
||||||
{
|
|
||||||
ItemView.CastAndCrewHStack(people: castAndCrew)
|
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Special Features
|
|
||||||
|
|
||||||
if viewModel.specialFeatures.isNotEmpty {
|
|
||||||
ItemView.SpecialFeaturesHStack(items: viewModel.specialFeatures)
|
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Similar
|
|
||||||
|
|
||||||
if viewModel.similarItems.isNotEmpty {
|
|
||||||
ItemView.SimilarItemsHStack(items: viewModel.similarItems)
|
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemView.AboutView(viewModel: viewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
||||||
|
|
||||||
struct iPadOSMovieItemView: View {
|
|
||||||
|
|
||||||
@ObservedObject
|
|
||||||
var viewModel: MovieItemViewModel
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ItemView.iPadOSCinematicScrollView(viewModel: viewModel) {
|
|
||||||
ContentView(viewModel: viewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
//
|
|
||||||
// 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 iPadOSSeriesItemView {
|
|
||||||
|
|
||||||
struct ContentView: View {
|
|
||||||
|
|
||||||
@ObservedObject
|
|
||||||
var viewModel: SeriesItemViewModel
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
|
||||||
|
|
||||||
// MARK: Episodes
|
|
||||||
|
|
||||||
SeriesEpisodeSelector(viewModel: viewModel)
|
|
||||||
|
|
||||||
// MARK: Genres
|
|
||||||
|
|
||||||
if let genres = viewModel.item.itemGenres, genres.isNotEmpty {
|
|
||||||
ItemView.GenresHStack(genres: genres)
|
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Studios
|
|
||||||
|
|
||||||
if let studios = viewModel.item.studios, studios.isNotEmpty {
|
|
||||||
ItemView.StudiosHStack(studios: studios)
|
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Cast and Crew
|
|
||||||
|
|
||||||
if let castAndCrew = viewModel.item.people,
|
|
||||||
castAndCrew.isNotEmpty
|
|
||||||
{
|
|
||||||
ItemView.CastAndCrewHStack(people: castAndCrew)
|
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Special Features
|
|
||||||
|
|
||||||
if viewModel.specialFeatures.isNotEmpty {
|
|
||||||
ItemView.SpecialFeaturesHStack(items: viewModel.specialFeatures)
|
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Similar
|
|
||||||
|
|
||||||
if viewModel.similarItems.isNotEmpty {
|
|
||||||
ItemView.SimilarItemsHStack(items: viewModel.similarItems)
|
|
||||||
|
|
||||||
RowDivider()
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemView.AboutView(viewModel: viewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
//
|
|
||||||
// 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
|
|
||||||
|
|
||||||
struct iPadOSSeriesItemView: View {
|
|
||||||
|
|
||||||
@ObservedObject
|
|
||||||
var viewModel: SeriesItemViewModel
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ItemView.iPadOSCinematicScrollView(viewModel: viewModel) {
|
|
||||||
ContentView(viewModel: viewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
Loading…
Reference in New Issue