Simplify item view creation

This commit is contained in:
Ethan Pippin 2021-08-27 23:31:13 -06:00
parent 7a79b78e38
commit 2dbb0bd890
18 changed files with 521 additions and 175 deletions

View File

@ -78,8 +78,8 @@
53649AB2269D019100A2D8B7 /* LogManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53649AB0269CFB1900A2D8B7 /* LogManager.swift */; };
53649AB3269D3F5B00A2D8B7 /* LogManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53649AB0269CFB1900A2D8B7 /* LogManager.swift */; };
53649AB5269D423A00A2D8B7 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = 53649AB4269D423A00A2D8B7 /* Puppy */; };
5364F455266CA0DC0026ECBA /* APIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* APIExtensions.swift */; };
5364F456266CA0DC0026ECBA /* APIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* APIExtensions.swift */; };
5364F455266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* BaseItemPersonExtensions.swift */; };
5364F456266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* BaseItemPersonExtensions.swift */; };
536D3D74267BA8170004248C /* BackgroundManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536D3D73267BA8170004248C /* BackgroundManager.swift */; };
536D3D76267BA9BB0004248C /* MainTabViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536D3D75267BA9BB0004248C /* MainTabViewModel.swift */; };
536D3D78267BD5C30004248C /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB57B2678CE1000530A6E /* ViewModel.swift */; };
@ -188,7 +188,6 @@
625CB57A2678C4A400530A6E /* ActivityIndicator in Frameworks */ = {isa = PBXBuildFile; productRef = 625CB5792678C4A400530A6E /* ActivityIndicator */; };
6260FFF926A09754003FA968 /* CombineExt in Frameworks */ = {isa = PBXBuildFile; productRef = 6260FFF826A09754003FA968 /* CombineExt */; };
6261A0E026A0AB710072EF1C /* CombineExt in Frameworks */ = {isa = PBXBuildFile; productRef = 6261A0DF26A0AB710072EF1C /* CombineExt */; };
6267B3D42671024A00A7371D /* APIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* APIExtensions.swift */; };
6267B3D626710B8900A7371D /* CollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6267B3D526710B8900A7371D /* CollectionExtensions.swift */; };
6267B3D726710B9700A7371D /* CollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6267B3D526710B8900A7371D /* CollectionExtensions.swift */; };
6267B3D826710B9800A7371D /* CollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6267B3D526710B8900A7371D /* CollectionExtensions.swift */; };
@ -242,6 +241,20 @@
E173DA5026D048D600CC4EB7 /* ServerDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */; };
E173DA5226D04AAF00CC4EB7 /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA5126D04AAF00CC4EB7 /* ColorExtension.swift */; };
E173DA5426D050F500CC4EB7 /* ServerDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA5326D050F500CC4EB7 /* ServerDetailViewModel.swift */; };
E1AD104A26D94822003E4A08 /* DetailItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD104926D94822003E4A08 /* DetailItem.swift */; };
E1AD104B26D94822003E4A08 /* DetailItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD104926D94822003E4A08 /* DetailItem.swift */; };
E1AD104D26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD104C26D96CE3003E4A08 /* BaseItemDtoExtensions.swift */; };
E1AD104E26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD104C26D96CE3003E4A08 /* BaseItemDtoExtensions.swift */; };
E1AD105426D97161003E4A08 /* BaseItemDtoExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD104C26D96CE3003E4A08 /* BaseItemDtoExtensions.swift */; };
E1AD105626D981CE003E4A08 /* PortraitHStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD105526D981CE003E4A08 /* PortraitHStackView.swift */; };
E1AD105726D981CE003E4A08 /* PortraitHStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD105526D981CE003E4A08 /* PortraitHStackView.swift */; };
E1AD105926D9A543003E4A08 /* LazyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621338B22660A07800A81A2A /* LazyView.swift */; };
E1AD105C26D9ABDD003E4A08 /* PillHStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD105B26D9ABDD003E4A08 /* PillHStackView.swift */; };
E1AD105D26D9ABDD003E4A08 /* PillHStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD105B26D9ABDD003E4A08 /* PillHStackView.swift */; };
E1AD105F26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD105E26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift */; };
E1AD106026D9ADDD003E4A08 /* NameGUIDPairExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD105E26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift */; };
E1AD106226D9B7CD003E4A08 /* PortraitHeaderOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD106126D9B7CD003E4A08 /* PortraitHeaderOverlayView.swift */; };
E1AD106326D9B7CD003E4A08 /* PortraitHeaderOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD106126D9B7CD003E4A08 /* PortraitHeaderOverlayView.swift */; };
E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; };
E1F0204F26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; };
E1FCD08826C35A0D007C8DCF /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FCD08726C35A0D007C8DCF /* NetworkError.swift */; };
@ -347,7 +360,7 @@
5362E4C6267D40F4000E2F71 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
5362E4C8267D40F7000E2F71 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
53649AB0269CFB1900A2D8B7 /* LogManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogManager.swift; sourceTree = "<group>"; };
5364F454266CA0DC0026ECBA /* APIExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIExtensions.swift; sourceTree = "<group>"; };
5364F454266CA0DC0026ECBA /* BaseItemPersonExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseItemPersonExtensions.swift; sourceTree = "<group>"; };
536D3D73267BA8170004248C /* BackgroundManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundManager.swift; sourceTree = "<group>"; };
536D3D75267BA9BB0004248C /* MainTabViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabViewModel.swift; sourceTree = "<group>"; };
536D3D7E267BDF100004248C /* LatestMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestMediaView.swift; sourceTree = "<group>"; };
@ -446,6 +459,12 @@
E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailView.swift; sourceTree = "<group>"; };
E173DA5126D04AAF00CC4EB7 /* ColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtension.swift; sourceTree = "<group>"; };
E173DA5326D050F500CC4EB7 /* ServerDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailViewModel.swift; sourceTree = "<group>"; };
E1AD104926D94822003E4A08 /* DetailItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailItem.swift; sourceTree = "<group>"; };
E1AD104C26D96CE3003E4A08 /* BaseItemDtoExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseItemDtoExtensions.swift; sourceTree = "<group>"; };
E1AD105526D981CE003E4A08 /* PortraitHStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortraitHStackView.swift; sourceTree = "<group>"; };
E1AD105B26D9ABDD003E4A08 /* PillHStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillHStackView.swift; sourceTree = "<group>"; };
E1AD105E26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NameGUIDPairExtensions.swift; sourceTree = "<group>"; };
E1AD106126D9B7CD003E4A08 /* PortraitHeaderOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortraitHeaderOverlayView.swift; sourceTree = "<group>"; };
E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerJumpLength.swift; sourceTree = "<group>"; };
E1FCD08726C35A0D007C8DCF /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = "<group>"; };
E1FCD09526C47118007C8DCF /* ErrorMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessage.swift; sourceTree = "<group>"; };
@ -658,7 +677,11 @@
535870AB2669D8D300D05A09 /* Objects */ = {
isa = PBXGroup;
children = (
E1AD105326D96F5A003E4A08 /* Views */,
62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */,
53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */,
535870AC2669D8DD00D05A09 /* Typings.swift */,
E1AD104926D94822003E4A08 /* DetailItem.swift */,
E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */,
);
path = Objects;
@ -898,6 +921,9 @@
53F866422687A45400DCD1D7 /* Components */ = {
isa = PBXGroup;
children = (
E1AD105B26D9ABDD003E4A08 /* PillHStackView.swift */,
E1AD106126D9B7CD003E4A08 /* PortraitHeaderOverlayView.swift */,
E1AD105526D981CE003E4A08 /* PortraitHStackView.swift */,
53F866432687A45F00DCD1D7 /* PortraitItemView.swift */,
);
path = Components;
@ -906,21 +932,13 @@
621338912660106C00A81A2A /* Extensions */ = {
isa = PBXGroup;
children = (
53DE4BD1267098F300739748 /* SearchBarView.swift */,
E173DA5126D04AAF00CC4EB7 /* ColorExtension.swift */,
53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */,
531AC8BE26750DE20091C7EB /* ImageView.swift */,
5364F454266CA0DC0026ECBA /* APIExtensions.swift */,
5389277B263CC3DB0035E14B /* BlurHashDecode.swift */,
621338B22660A07800A81A2A /* LazyView.swift */,
53E4E648263F725B00F67C6B /* MultiSelectorView.swift */,
6225FCCA2663841E00E067F6 /* ParallaxHeader.swift */,
621338922660107500A81A2A /* StringExtensions.swift */,
6267B3D526710B8900A7371D /* CollectionExtensions.swift */,
6267B3D92671138200A7371D /* ImageExtensions.swift */,
62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */,
E173DA5126D04AAF00CC4EB7 /* ColorExtension.swift */,
62CB3F4A2685BB77003D0A6F /* DefaultsExtension.swift */,
624C21742685CF60007F1390 /* SearchablePickerView.swift */,
6267B3D92671138200A7371D /* ImageExtensions.swift */,
E1AD105226D96D5F003E4A08 /* JellyfinAPIExtensions */,
621338922660107500A81A2A /* StringExtensions.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -940,10 +958,10 @@
62EC352A26766657000E9F2D /* Singleton */ = {
isa = PBXGroup;
children = (
62EC352B26766675000E9F2D /* ServerEnvironment.swift */,
62EC352E267666A5000E9F2D /* SessionManager.swift */,
536D3D73267BA8170004248C /* BackgroundManager.swift */,
53649AB0269CFB1900A2D8B7 /* LogManager.swift */,
62EC352B26766675000E9F2D /* ServerEnvironment.swift */,
62EC352E267666A5000E9F2D /* SessionManager.swift */,
);
path = Singleton;
sourceTree = "<group>";
@ -968,12 +986,35 @@
path = Pods;
sourceTree = "<group>";
};
E1AD105226D96D5F003E4A08 /* JellyfinAPIExtensions */ = {
isa = PBXGroup;
children = (
5364F454266CA0DC0026ECBA /* BaseItemPersonExtensions.swift */,
E1AD104C26D96CE3003E4A08 /* BaseItemDtoExtensions.swift */,
E1AD105E26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift */,
);
path = JellyfinAPIExtensions;
sourceTree = "<group>";
};
E1AD105326D96F5A003E4A08 /* Views */ = {
isa = PBXGroup;
children = (
531AC8BE26750DE20091C7EB /* ImageView.swift */,
621338B22660A07800A81A2A /* LazyView.swift */,
53E4E648263F725B00F67C6B /* MultiSelectorView.swift */,
6225FCCA2663841E00E067F6 /* ParallaxHeader.swift */,
624C21742685CF60007F1390 /* SearchablePickerView.swift */,
53DE4BD1267098F300739748 /* SearchBarView.swift */,
);
path = Views;
sourceTree = "<group>";
};
E1FCD08E26C466F3007C8DCF /* Errors */ = {
isa = PBXGroup;
children = (
E1FCD08726C35A0D007C8DCF /* NetworkError.swift */,
E131691626C583BC0074BFEE /* LogConstructor.swift */,
E1FCD09526C47118007C8DCF /* ErrorMessage.swift */,
E131691626C583BC0074BFEE /* LogConstructor.swift */,
E1FCD08726C35A0D007C8DCF /* NetworkError.swift */,
);
path = Errors;
sourceTree = "<group>";
@ -1346,6 +1387,7 @@
E1FCD08926C35A0D007C8DCF /* NetworkError.swift in Sources */,
531690ED267ABF46005D8AB9 /* ContinueWatchingView.swift in Sources */,
62EC3530267666A5000E9F2D /* SessionManager.swift in Sources */,
E1AD104B26D94822003E4A08 /* DetailItem.swift in Sources */,
53272539268C20100035FBF1 /* EpisodeItemView.swift in Sources */,
531690F7267ACC00005D8AB9 /* LandscapeItemElement.swift in Sources */,
62E632E1267D30CA0063E547 /* LibraryViewModel.swift in Sources */,
@ -1359,15 +1401,18 @@
62E632F4267D54030063E547 /* DetailItemViewModel.swift in Sources */,
6267B3D826710B9800A7371D /* CollectionExtensions.swift in Sources */,
62E632E7267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */,
E1AD106326D9B7CD003E4A08 /* PortraitHeaderOverlayView.swift in Sources */,
E100720726BDABC100CE3E31 /* MediaPlayButtonRowView.swift in Sources */,
535870A52669D8AE00D05A09 /* ParallaxHeader.swift in Sources */,
53272532268BF09D0035FBF1 /* MediaViewActionButton.swift in Sources */,
531690F0267ABF72005D8AB9 /* NextUpView.swift in Sources */,
535870A72669D8AE00D05A09 /* MultiSelectorView.swift in Sources */,
E1AD104E26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */,
62E632DD267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */,
536D3D81267BDFC60004248C /* PortraitItemElement.swift in Sources */,
531690E5267ABD5C005D8AB9 /* MainTabView.swift in Sources */,
5310695B2684E7EE00CFFDBA /* AudioView.swift in Sources */,
E1AD106026D9ADDD003E4A08 /* NameGUIDPairExtensions.swift in Sources */,
5398514726B64E4100101B49 /* SearchBarView.swift in Sources */,
091B5A8D268315D400D78B61 /* ServerDiscovery.swift in Sources */,
53ABFDE7267974EF00886593 /* ConnectToServerViewModel.swift in Sources */,
@ -1391,9 +1436,11 @@
536D3D74267BA8170004248C /* BackgroundManager.swift in Sources */,
535870632669D21600D05A09 /* JellyfinPlayer_tvOSApp.swift in Sources */,
53ABFDE4267974EF00886593 /* LibraryListViewModel.swift in Sources */,
5364F456266CA0DC0026ECBA /* APIExtensions.swift in Sources */,
5364F456266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */,
E1AD105D26D9ABDD003E4A08 /* PillHStackView.swift in Sources */,
531690FA267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift in Sources */,
E131691826C583BC0074BFEE /* LogConstructor.swift in Sources */,
E1AD105726D981CE003E4A08 /* PortraitHStackView.swift in Sources */,
535870A32669D89F00D05A09 /* Model.xcdatamodeld in Sources */,
09389CC826819B4600AE350E /* VideoPlayerModel.swift in Sources */,
);
@ -1403,7 +1450,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5364F455266CA0DC0026ECBA /* APIExtensions.swift in Sources */,
5364F455266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */,
621338932660107500A81A2A /* StringExtensions.swift in Sources */,
53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */,
62E632EC267D410B0063E547 /* SeriesItemViewModel.swift in Sources */,
@ -1412,6 +1459,7 @@
5377CBFE263B596B003A4E83 /* PersistenceController.swift in Sources */,
5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */,
53F866442687A45F00DCD1D7 /* PortraitItemView.swift in Sources */,
E1AD105626D981CE003E4A08 /* PortraitHStackView.swift in Sources */,
53AD124E26702B8A0094A276 /* SeasonItemView.swift in Sources */,
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */,
6225FCCB2663841E00E067F6 /* ParallaxHeader.swift in Sources */,
@ -1426,6 +1474,7 @@
62133890265F83A900A81A2A /* LibraryListView.swift in Sources */,
0959A5FD2686D29800C7C9A9 /* VideoUpNextView.swift in Sources */,
62E632DA267D2BC40063E547 /* LatestMediaViewModel.swift in Sources */,
E1AD105C26D9ABDD003E4A08 /* PillHStackView.swift in Sources */,
625CB56F2678C23300530A6E /* HomeView.swift in Sources */,
E173DA5226D04AAF00CC4EB7 /* ColorExtension.swift in Sources */,
53892770263C25230035E14B /* NextUpView.swift in Sources */,
@ -1447,9 +1496,11 @@
625CB56A2678B71200530A6E /* SplashViewModel.swift in Sources */,
62E632F3267D54030063E547 /* DetailItemViewModel.swift in Sources */,
53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */,
E1AD106226D9B7CD003E4A08 /* PortraitHeaderOverlayView.swift in Sources */,
53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */,
621338B32660A07800A81A2A /* LazyView.swift in Sources */,
531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */,
E1AD104A26D94822003E4A08 /* DetailItem.swift in Sources */,
62E632E0267D30CA0063E547 /* LibraryViewModel.swift in Sources */,
624C21752685CF60007F1390 /* SearchablePickerView.swift in Sources */,
E173DA5026D048D600CC4EB7 /* ServerDetailView.swift in Sources */,
@ -1457,7 +1508,9 @@
62E632E3267D3BA60063E547 /* MovieItemViewModel.swift in Sources */,
091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */,
62E632EF267D43320063E547 /* LibraryFilterViewModel.swift in Sources */,
E1AD104D26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */,
535870AD2669D8DD00D05A09 /* Typings.swift in Sources */,
E1AD105F26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift in Sources */,
62EC352C26766675000E9F2D /* ServerEnvironment.swift in Sources */,
6267B3DA2671138200A7371D /* ImageExtensions.swift in Sources */,
62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */,
@ -1481,11 +1534,12 @@
files = (
53649AB3269D3F5B00A2D8B7 /* LogManager.swift in Sources */,
62EC353126766848000E9F2D /* ServerEnvironment.swift in Sources */,
6267B3D42671024A00A7371D /* APIExtensions.swift in Sources */,
6267B3D726710B9700A7371D /* CollectionExtensions.swift in Sources */,
628B953C2670D2430091AF3B /* StringExtensions.swift in Sources */,
6267B3DB2671139400A7371D /* ImageExtensions.swift in Sources */,
E1AD105926D9A543003E4A08 /* LazyView.swift in Sources */,
628B95372670CB800091AF3B /* JellyfinWidget.swift in Sources */,
E1AD105426D97161003E4A08 /* BaseItemDtoExtensions.swift in Sources */,
6228B1C22670EB010067FD35 /* PersistenceController.swift in Sources */,
E1FCD09A26C4F35A007C8DCF /* ErrorMessage.swift in Sources */,
628B95272670CABD0091AF3B /* NextUpWidget.swift in Sources */,

View File

@ -0,0 +1,60 @@
//
/*
* 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 2021 Aiden Vigue & Jellyfin Contributors
*/
import SwiftUI
protocol PillStackable {
var title: String { get }
}
struct PillHStackView<NavigationView: View, ItemType: PillStackable>: View {
let title: String
let items: [ItemType]
let navigationView: (ItemType) -> NavigationView
var body: some View {
VStack(alignment: .leading) {
Text(title)
.font(.callout)
.fontWeight(.semibold)
.padding(.top, 3)
.padding(.leading, 16)
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(items, id: \.title) { item in
NavigationLink(destination: LazyView {
navigationView(item)
}) {
ZStack {
Color(UIColor.secondarySystemBackground)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.cornerRadius(10)
Text(item.title)
.font(.caption)
.fontWeight(.semibold)
.foregroundColor(.white)
.fixedSize()
.padding(.leading, 10)
.padding(.trailing, 10)
.padding(.top, 10)
.padding(.bottom, 10)
}
.fixedSize()
}
}
}
.padding(.leading, 16)
.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
}
}
}
}

View File

@ -0,0 +1,76 @@
//
/*
* 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 2021 Aiden Vigue & Jellyfin Contributors
*/
import SwiftUI
public protocol PortraitImageStackable {
func imageURLContsructor(maxWidth: Int) -> URL
var title: String { get }
var description: String? { get }
var blurHash: String { get }
}
struct PortraitImageHStackView<NavigationView: View, ItemType: PortraitImageStackable>: View {
let title: String
let items: [ItemType]
let maxWidth: Int
let navigationView: (ItemType) -> NavigationView
var body: some View {
VStack(alignment: .leading) {
Text(title)
.font(.callout)
.fontWeight(.semibold)
.padding(.top, 3)
.padding(.leading, 16)
ScrollView(.horizontal, showsIndicators: false) {
VStack {
Spacer().frame(height: 8)
HStack {
Spacer().frame(width: 16)
ForEach(items, id: \.title) { item in
NavigationLink(
destination: LazyView {
navigationView(item)
},
label: {
VStack {
ImageView(src: item.imageURLContsructor(maxWidth: maxWidth),
bh: item.blurHash)
.frame(width: 100, height: CGFloat(maxWidth))
.cornerRadius(10)
.shadow(radius: 4, y: 2)
Text(item.title)
.font(.footnote)
.fontWeight(.regular)
.lineLimit(1)
.frame(width: 100)
.foregroundColor(.primary)
if let description = item.description {
Text(description)
.font(.caption)
.fontWeight(.medium)
.lineLimit(1)
.frame(width: 100)
.foregroundColor(.secondary)
}
}
})
}
Spacer().frame(width: UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
}
}
}.padding(.top, -3)
}
}
}

View File

@ -0,0 +1,132 @@
//
/*
* 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 2021 Aiden Vigue & Jellyfin Contributors
*/
import SwiftUI
import JellyfinAPI
struct PortraitHeaderOverlayView: View {
@EnvironmentObject var viewModel: DetailItemViewModel
let item: BaseItemDto
var body: some View {
VStack(alignment: .leading) {
HStack(alignment: .bottom, spacing: 12) {
ImageView(src: item.portraitHeaderViewURL(maxWidth: 130))
.frame(width: 130, height: 195)
.cornerRadius(10)
VStack(alignment: .leading, spacing: 1) {
Spacer()
Text(item.name ?? "")
.font(.headline)
.fontWeight(.semibold)
.foregroundColor(.primary)
.fixedSize(horizontal: false, vertical: true)
.offset(y: 5)
Text(item.getItemRuntime())
.font(.subheadline)
.fontWeight(.medium)
.foregroundColor(.secondary)
.lineLimit(1)
.padding(.top, 10)
HStack {
if let productionYear = item.productionYear {
Text(String(productionYear))
.font(.subheadline)
.fontWeight(.medium)
.foregroundColor(.secondary)
.lineLimit(1)
}
if let officialRating = item.officialRating {
Text(officialRating)
.font(.subheadline)
.fontWeight(.semibold)
.foregroundColor(.secondary)
.lineLimit(1)
.padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4))
.overlay(RoundedRectangle(cornerRadius: 2)
.stroke(Color.secondary, lineWidth: 1))
}
}
}
.padding(.bottom, UIDevice.current.userInterfaceIdiom == .pad ? 98 : 30)
}
HStack {
// Play button
Button {
()
// self.playbackInfo.itemToPlay = item
// self.playbackInfo.shouldShowPlayer = true
} label: {
HStack {
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
Text(item.getItemProgressString() == "" ? "Play" : item.getItemProgressString())
.foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
}
.frame(width: 130, height: 40)
.background(Color.jellyfinPurple)
.cornerRadius(10)
}
Spacer()
Button {
print("Heart")
} label: {
Image(systemName: "heart").foregroundColor(.primary)
.font(.system(size: 20))
}
Button {
print("Check")
} label: {
Image(systemName: "checkmark.circle").foregroundColor(.primary)
.font(.system(size: 20))
}
// Button {
// updateFavoriteState()
// ()
// } label: {
// if viewModel.isFavorited {
// Image(systemName: "heart.fill").foregroundColor(Color(UIColor.systemRed))
// .font(.system(size: 20))
// } else {
// Image(systemName: "heart").foregroundColor(Color.primary)
// .font(.system(size: 20))
// }
// }
// .disabled(viewModel.isLoading)
// Button {
// viewModel.updateWatchState()
// ()
// } label: {
// if viewModel.isWatched {
// Image(systemName: "checkmark.circle.fill").foregroundColor(Color.primary)
// .font(.system(size: 20))
// } else {
// Image(systemName: "checkmark.circle").foregroundColor(Color.primary)
// .font(.system(size: 20))
// }
// }
// .disabled(viewModel.isLoading)
// }
}.padding(.top, 8)
}
.padding(.horizontal, 16)
.padding(.bottom, UIDevice.current.userInterfaceIdiom == .pad ? -189 : -64)
}
}

View File

@ -9,7 +9,9 @@
import SwiftUI
import JellyfinAPI
struct PortraitItemView: View {
var item: BaseItemDto
var body: some View {

View File

@ -34,118 +34,15 @@ struct ItemView: View {
}
var portraitHeaderOverlayView: some View {
VStack(alignment: .leading) {
HStack(alignment: .bottom, spacing: 12) {
ImageView(src: item.getPrimaryImage(maxWidth: 130))
.frame(width: 130, height: 195)
.cornerRadius(10)
VStack(alignment: .leading) {
Spacer()
Text(item.name ?? "").font(.headline)
.fontWeight(.semibold)
.foregroundColor(.primary)
.lineLimit(1)
.offset(y: 5)
HStack {
if item.productionYear != nil {
Text(String(item.productionYear ?? 0)).font(.subheadline)
.fontWeight(.medium)
.foregroundColor(.secondary)
.lineLimit(1)
}
Text(item.getItemRuntime()).font(.subheadline)
.fontWeight(.medium)
.foregroundColor(.secondary)
.lineLimit(1)
if item.officialRating != nil {
Text(item.officialRating!).font(.subheadline)
.fontWeight(.semibold)
.foregroundColor(.secondary)
.lineLimit(1)
.padding(EdgeInsets(top: 1, leading: 4, bottom: 1, trailing: 4))
.overlay(RoundedRectangle(cornerRadius: 2)
.stroke(Color.secondary, lineWidth: 1))
}
}
.padding(.top, 1)
}
.padding(.bottom, UIDevice.current.userInterfaceIdiom == .pad ? 98 : 30)
}
HStack {
// Play button
Button {
()
// self.playbackInfo.itemToPlay = item
// self.playbackInfo.shouldShowPlayer = true
} label: {
HStack {
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
Text(item.getItemProgressString() == "" ? "Play" : item.getItemProgressString())
.foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
}
.frame(width: 130, height: 40)
.background(Color.jellyfinPurple)
.cornerRadius(10)
}
Spacer()
Button {
print("Heart")
} label: {
Image(systemName: "heart").foregroundColor(.primary)
.font(.system(size: 20))
}
Button {
print("Check")
} label: {
Image(systemName: "checkmark.circle").foregroundColor(.primary)
.font(.system(size: 20))
}
// Button {
// updateFavoriteState()
// ()
// } label: {
// if viewModel.isFavorited {
// Image(systemName: "heart.fill").foregroundColor(Color(UIColor.systemRed))
// .font(.system(size: 20))
// } else {
// Image(systemName: "heart").foregroundColor(Color.primary)
// .font(.system(size: 20))
// }
// }
// .disabled(viewModel.isLoading)
// Button {
// viewModel.updateWatchState()
// ()
// } label: {
// if viewModel.isWatched {
// Image(systemName: "checkmark.circle.fill").foregroundColor(Color.primary)
// .font(.system(size: 20))
// } else {
// Image(systemName: "checkmark.circle").foregroundColor(Color.primary)
// .font(.system(size: 20))
// }
// }
// .disabled(viewModel.isLoading)
// }
}.padding(.top, 8)
}
.padding(.horizontal, 16)
.padding(.bottom, UIDevice.current.userInterfaceIdiom == .pad ? -189 : -64)
PortraitHeaderOverlayView(item: item)
}
var body: some View {
VStack {
NavigationLink(destination: LoadingViewNoBlur(isShowing: $videoIsLoading) { VLCPlayerWithControls(item: videoPlayerItem.itemToPlay, loadBinding: $videoIsLoading, pBinding: _videoPlayerItem.projectedValue.shouldShowPlayer)
NavigationLink(destination: LoadingViewNoBlur(isShowing: $videoIsLoading) {
VLCPlayerWithControls(item: videoPlayerItem.itemToPlay,
loadBinding: $videoIsLoading,
pBinding: _videoPlayerItem.projectedValue.shouldShowPlayer)
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.statusBar(hidden: true)
@ -158,29 +55,53 @@ struct ItemView: View {
ParallaxHeaderScrollView(header: portraitHeaderView,
staticOverlayView: portraitHeaderOverlayView,
overlayAlignment: .bottomLeading,
headerHeight: UIDevice.current.userInterfaceIdiom == .pad ? 350 : UIScreen.main.bounds
.width * 0.5625) {
headerHeight: UIDevice.current.userInterfaceIdiom == .pad ? 350 : UIScreen.main.bounds.width * 0.5625) {
VStack {
Spacer()
.frame(height: UIDevice.current.userInterfaceIdiom == .pad ? 135 : 40)
.padding(.bottom, UIDevice.current.userInterfaceIdiom == .pad ? 54 : 24)
// MARK: Overview
Text(item.overview ?? "")
.font(.footnote)
.padding(.top, 3)
.fixedSize(horizontal: false, vertical: true)
.padding(.bottom, 3)
.padding(.leading, 16)
.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
if item.type == "Movie" {
// MovieItemView(viewModel: .init(item: item))
Text("Movie")
} else if item.type == "Season" {
SeasonItemView(viewModel: .init(item: item))
} else if item.type == "Series" {
SeriesItemView(viewModel: .init(item: item))
} else if item.type == "Episode" {
EpisodeItemView(viewModel: .init(item: item))
} else {
Text("Type: \(item.type ?? "") not implemented yet :(")
// MARK: Genres
PillHStackView(title: "Genres", items: item.genreItems ?? []) { genre in
LibraryView(viewModel: .init(genre: genre), title: genre.title)
}
// MARK: Studios
if !(item.studios ?? []).isEmpty {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
Text("Studios:").font(.callout).fontWeight(.semibold)
ForEach(item.studios!, id: \.id) { studio in
NavigationLink(destination: LazyView {
LibraryView(viewModel: .init(studio: studio), title: studio.name ?? "")
}) {
Text(studio.name ?? "").font(.footnote)
}
}
}
.padding(.leading, 16)
.padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 16 : 55)
}
}
// MARK: Cast
PortraitImageHStackView(title: "Cast",
items: item.people!,
maxWidth: 150) { person in
LibraryView(viewModel: .init(person: person), title: person.title)
}
// MARK: More Like This
}
.introspectTabBarController { (UITabBarController) in
UITabBarController.tabBar.isHidden = false

View File

@ -1,9 +1,11 @@
/* 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 2021 Aiden Vigue & Jellyfin Contributors
*/
//
/*
* 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 2021 Aiden Vigue & Jellyfin Contributors
*/
import Foundation
import JellyfinAPI
@ -11,7 +13,8 @@ import UIKit
// 001fC^ = dark grey plain blurhash
extension BaseItemDto {
public extension BaseItemDto {
// MARK: Images
func getSeriesBackdropImageBlurHash() -> String {
@ -149,27 +152,33 @@ extension BaseItemDto {
return "\(String(progminutes))m"
}
}
}
func round(_ value: Double, toNearest: Double) -> Double {
return round(value / toNearest) * toNearest
}
extension BaseItemPerson {
func getImage(baseURL: String, maxWidth: Int) -> URL {
let imageType = "Primary"
let imageTag = primaryImageTag ?? ""
let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
let urlString = "\(baseURL)/Items/\(id ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=85&tag=\(imageTag)"
return URL(string: urlString)!
// MARK: ItemType
enum ItemType: String {
case movie = "Movie"
case season = "Season"
case episode = "Episode"
case series = "Series"
case unknown
}
func getBlurHash() -> String {
let rawImgURL = getImage(baseURL: "", maxWidth: 1).absoluteString
let imgTag = rawImgURL.components(separatedBy: "&tag=")[1]
return imageBlurHashes?.primary?[imgTag] ?? "001fC^"
var itemType: ItemType {
guard let originalType = self.type, let knownType = ItemType(rawValue: originalType) else { return .unknown }
return knownType
}
// MARK: PortraitHeaderViewURL
func portraitHeaderViewURL(maxWidth: Int) -> URL {
switch self.itemType {
case .movie, .season, .series:
return getPrimaryImage(maxWidth: maxWidth)
case .episode:
return getSeriesPrimaryImage(maxWidth: maxWidth)
case .unknown:
return getPrimaryImage(maxWidth: maxWidth)
}
}
}

View File

@ -0,0 +1,47 @@
/* 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 2021 Aiden Vigue & Jellyfin Contributors
*/
import Foundation
import JellyfinAPI
import UIKit
extension BaseItemPerson {
func getImage(baseURL: String, maxWidth: Int) -> URL {
let imageType = "Primary"
let imageTag = primaryImageTag ?? ""
let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
let urlString = "\(baseURL)/Items/\(id ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=85&tag=\(imageTag)"
return URL(string: urlString)!
}
func getBlurHash() -> String {
let rawImgURL = getImage(baseURL: "", maxWidth: 1).absoluteString
let imgTag = rawImgURL.components(separatedBy: "&tag=")[1]
return imageBlurHashes?.primary?[imgTag] ?? "001fC^"
}
}
extension BaseItemPerson: PortraitImageStackable {
public func imageURLContsructor(maxWidth: Int) -> URL {
return self.getImage(baseURL: ServerEnvironment.current.server.baseURI!, maxWidth: maxWidth)
}
public var title: String {
return self.name ?? ""
}
public var description: String? {
return self.role
}
public var blurHash: String {
return self.getBlurHash()
}
}

View File

@ -0,0 +1,17 @@
//
/*
* 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 2021 Aiden Vigue & Jellyfin Contributors
*/
import Foundation
import JellyfinAPI
extension NameGuidPair: PillStackable {
var title: String {
return self.name ?? ""
}
}

View File

@ -0,0 +1,28 @@
//
/*
* 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 2021 Aiden Vigue & Jellyfin Contributors
*/
import Foundation
import JellyfinAPI
enum DetailItemType: String {
case movie = "Movie"
case season = "Season"
case series = "Series"
case episode = "Episode"
}
struct DetailItem {
let baseItem: BaseItemDto
let type: DetailItemType
}