MovieItemView using gen. api
This commit is contained in:
parent
56ec13499b
commit
de5e79f6ce
|
@ -11,7 +11,6 @@
|
|||
53313B90265EEA6D00947AA3 /* VideoPlayer.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53313B8F265EEA6D00947AA3 /* VideoPlayer.storyboard */; };
|
||||
53352571265EA0A0006CCA86 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 53352570265EA0A0006CCA86 /* Introspect */; };
|
||||
5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5338F74D263B61370014BF09 /* ConnectToServerView.swift */; };
|
||||
5338F754263B65E10014BF09 /* SwiftyRequest in Frameworks */ = {isa = PBXBuildFile; productRef = 5338F753263B65E10014BF09 /* SwiftyRequest */; };
|
||||
5338F757263B7E2E0014BF09 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 5338F756263B7E2E0014BF09 /* KeychainSwift */; };
|
||||
535870632669D21600D05A09 /* JellyfinPlayer_tvOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535870622669D21600D05A09 /* JellyfinPlayer_tvOSApp.swift */; };
|
||||
535870652669D21600D05A09 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535870642669D21600D05A09 /* ContentView.swift */; };
|
||||
|
@ -20,14 +19,8 @@
|
|||
5358706C2669D21700D05A09 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5358706B2669D21700D05A09 /* Persistence.swift */; };
|
||||
5358706F2669D21700D05A09 /* JellyfinPlayer_tvOS.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 5358706D2669D21700D05A09 /* JellyfinPlayer_tvOS.xcdatamodeld */; };
|
||||
5358707E2669D64F00D05A09 /* bitrates.json in Resources */ = {isa = PBXBuildFile; fileRef = AE8C3158265D6F90008AA076 /* bitrates.json */; };
|
||||
5358708B2669D7A800D05A09 /* SwiftyRequest in Frameworks */ = {isa = PBXBuildFile; productRef = 5358708A2669D7A800D05A09 /* SwiftyRequest */; };
|
||||
5358708D2669D7A800D05A09 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 5358708C2669D7A800D05A09 /* KeychainSwift */; };
|
||||
5358708F2669D7A800D05A09 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 5358708E2669D7A800D05A09 /* SwiftyJSON */; };
|
||||
535870912669D7A800D05A09 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 535870902669D7A800D05A09 /* Introspect */; };
|
||||
535870932669D7A800D05A09 /* CombineMoya in Frameworks */ = {isa = PBXBuildFile; productRef = 535870922669D7A800D05A09 /* CombineMoya */; };
|
||||
535870952669D7A800D05A09 /* Moya in Frameworks */ = {isa = PBXBuildFile; productRef = 535870942669D7A800D05A09 /* Moya */; };
|
||||
535870972669D7A800D05A09 /* ReactiveMoya in Frameworks */ = {isa = PBXBuildFile; productRef = 535870962669D7A800D05A09 /* ReactiveMoya */; };
|
||||
535870992669D7A800D05A09 /* RxMoya in Frameworks */ = {isa = PBXBuildFile; productRef = 535870982669D7A800D05A09 /* RxMoya */; };
|
||||
5358709B2669D7A800D05A09 /* NukeUI in Frameworks */ = {isa = PBXBuildFile; productRef = 5358709A2669D7A800D05A09 /* NukeUI */; };
|
||||
5358709D2669D82900D05A09 /* TVVLCKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5358709C2669D82900D05A09 /* TVVLCKit.framework */; };
|
||||
5358709E2669D82900D05A09 /* TVVLCKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5358709C2669D82900D05A09 /* TVVLCKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
|
@ -52,11 +45,7 @@
|
|||
5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5389276D263C25100035E14B /* ContinueWatchingView.swift */; };
|
||||
53892770263C25230035E14B /* NextUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5389276F263C25230035E14B /* NextUpView.swift */; };
|
||||
53892772263C8C6F0035E14B /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53892771263C8C6F0035E14B /* LoadingView.swift */; };
|
||||
5389277A263CBFE70035E14B /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 53892779263CBFE70035E14B /* SwiftyJSON */; };
|
||||
5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5389277B263CC3DB0035E14B /* BlurHashDecode.swift */; };
|
||||
53987CA426572C1300E7EA70 /* SeasonItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53987CA326572C1300E7EA70 /* SeasonItemView.swift */; };
|
||||
53987CA626572F0700E7EA70 /* SeriesItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53987CA526572F0700E7EA70 /* SeriesItemView.swift */; };
|
||||
53987CA82657424A00E7EA70 /* EpisodeItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53987CA72657424A00E7EA70 /* EpisodeItemView.swift */; };
|
||||
539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */; };
|
||||
53A089D0264DA9DA00D57806 /* MovieItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A089CF264DA9DA00D57806 /* MovieItemView.swift */; };
|
||||
53A431BD266B0FF20016769F /* JellyfinAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 53A431BC266B0FF20016769F /* JellyfinAPI */; };
|
||||
|
@ -66,25 +55,16 @@
|
|||
53D5E3DD264B47EE00BADDC8 /* MobileVLCKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */; };
|
||||
53D5E3DE264B47EE00BADDC8 /* MobileVLCKit.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DF641D263D9C0600A7CD1A /* LibraryView.swift */; };
|
||||
53E4E647263F6CF100F67C6B /* LibraryFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E4E646263F6CF100F67C6B /* LibraryFilterView.swift */; };
|
||||
53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E4E648263F725B00F67C6B /* MultiSelectorView.swift */; };
|
||||
53EE24E6265060780068F029 /* LibrarySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EE24E5265060780068F029 /* LibrarySearchView.swift */; };
|
||||
53F8377D265FF67C00F456B3 /* VideoPlayerSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53F8377C265FF67C00F456B3 /* VideoPlayerSettingsView.swift */; };
|
||||
53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FF7F29263CF3F500585C35 /* LatestMediaView.swift */; };
|
||||
6213388E265F777C00A81A2A /* LibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6213388D265F777C00A81A2A /* LibraryViewModel.swift */; };
|
||||
62133890265F83A900A81A2A /* LibraryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6213388F265F83A900A81A2A /* LibraryListView.swift */; };
|
||||
621338932660107500A81A2A /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621338922660107500A81A2A /* StringExtensions.swift */; };
|
||||
62133895266096EF00A81A2A /* LibraryListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62133894266096EF00A81A2A /* LibraryListViewModel.swift */; };
|
||||
621338B32660A07800A81A2A /* LazyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621338B22660A07800A81A2A /* LazyView.swift */; };
|
||||
621C638026672A30004216EA /* NukeUI in Frameworks */ = {isa = PBXBuildFile; productRef = 621C637F26672A30004216EA /* NukeUI */; };
|
||||
621C638226676728004216EA /* NukeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621C638126676728004216EA /* NukeExtensions.swift */; };
|
||||
6225FCCB2663841E00E067F6 /* ParallaxHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6225FCCA2663841E00E067F6 /* ParallaxHeader.swift */; };
|
||||
6273DD43265F4195009C1D0B /* Moya in Frameworks */ = {isa = PBXBuildFile; productRef = 6273DD42265F4195009C1D0B /* Moya */; };
|
||||
6273DD45265F4195009C1D0B /* CombineMoya in Frameworks */ = {isa = PBXBuildFile; productRef = 6273DD44265F4195009C1D0B /* CombineMoya */; };
|
||||
6273DD48265F41B3009C1D0B /* JellyfinAPIOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6273DD47265F41B3009C1D0B /* JellyfinAPIOld.swift */; };
|
||||
6273DD4E265F47B2009C1D0B /* LibrarySearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6273DD4D265F47B2009C1D0B /* LibrarySearchViewModel.swift */; };
|
||||
AE8C3154265D60BF008AA076 /* SettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE8C3153265D60BF008AA076 /* SettingsModel.swift */; };
|
||||
AE8C3156265D616A008AA076 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE8C3155265D616A008AA076 /* SettingsViewModel.swift */; };
|
||||
AE8C3159265D6F90008AA076 /* bitrates.json in Resources */ = {isa = PBXBuildFile; fileRef = AE8C3158265D6F90008AA076 /* bitrates.json */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
|
@ -125,7 +105,6 @@
|
|||
/* Begin PBXFileReference section */
|
||||
53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceProfileBuilder.swift; sourceTree = "<group>"; };
|
||||
53313B8F265EEA6D00947AA3 /* VideoPlayer.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = VideoPlayer.storyboard; sourceTree = "<group>"; };
|
||||
5333A68A266BDDC10044FD6B /* JellyfinPlayer.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = JellyfinPlayer.entitlements; sourceTree = "<group>"; };
|
||||
5338F74D263B61370014BF09 /* ConnectToServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerView.swift; sourceTree = "<group>"; };
|
||||
535870602669D21600D05A09 /* JellyfinPlayer tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "JellyfinPlayer tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
535870622669D21600D05A09 /* JellyfinPlayer_tvOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinPlayer_tvOSApp.swift; sourceTree = "<group>"; };
|
||||
|
@ -166,17 +145,11 @@
|
|||
53EE24E5265060780068F029 /* LibrarySearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchView.swift; sourceTree = "<group>"; };
|
||||
53F8377C265FF67C00F456B3 /* VideoPlayerSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerSettingsView.swift; sourceTree = "<group>"; };
|
||||
53FF7F29263CF3F500585C35 /* LatestMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestMediaView.swift; sourceTree = "<group>"; };
|
||||
6213388D265F777C00A81A2A /* LibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryViewModel.swift; sourceTree = "<group>"; };
|
||||
6213388F265F83A900A81A2A /* LibraryListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryListView.swift; sourceTree = "<group>"; };
|
||||
621338922660107500A81A2A /* StringExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = "<group>"; };
|
||||
62133894266096EF00A81A2A /* LibraryListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryListViewModel.swift; sourceTree = "<group>"; };
|
||||
621338B22660A07800A81A2A /* LazyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyView.swift; sourceTree = "<group>"; };
|
||||
621C638126676728004216EA /* NukeExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NukeExtensions.swift; sourceTree = "<group>"; };
|
||||
6225FCCA2663841E00E067F6 /* ParallaxHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParallaxHeader.swift; sourceTree = "<group>"; };
|
||||
6273DD47265F41B3009C1D0B /* JellyfinAPIOld.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinAPIOld.swift; sourceTree = "<group>"; };
|
||||
6273DD4D265F47B2009C1D0B /* LibrarySearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchViewModel.swift; sourceTree = "<group>"; };
|
||||
AE8C3153265D60BF008AA076 /* SettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModel.swift; sourceTree = "<group>"; };
|
||||
AE8C3155265D616A008AA076 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
|
||||
AE8C3158265D6F90008AA076 /* bitrates.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitrates.json; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
|
@ -185,17 +158,11 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
535870972669D7A800D05A09 /* ReactiveMoya in Frameworks */,
|
||||
53A431BF266B0FFE0016769F /* JellyfinAPI in Frameworks */,
|
||||
5358708F2669D7A800D05A09 /* SwiftyJSON in Frameworks */,
|
||||
535870912669D7A800D05A09 /* Introspect in Frameworks */,
|
||||
535870952669D7A800D05A09 /* Moya in Frameworks */,
|
||||
5358708D2669D7A800D05A09 /* KeychainSwift in Frameworks */,
|
||||
5358709D2669D82900D05A09 /* TVVLCKit.framework in Frameworks */,
|
||||
535870932669D7A800D05A09 /* CombineMoya in Frameworks */,
|
||||
5358709B2669D7A800D05A09 /* NukeUI in Frameworks */,
|
||||
535870992669D7A800D05A09 /* RxMoya in Frameworks */,
|
||||
5358708B2669D7A800D05A09 /* SwiftyRequest in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -204,14 +171,10 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5338F757263B7E2E0014BF09 /* KeychainSwift in Frameworks */,
|
||||
6273DD45265F4195009C1D0B /* CombineMoya in Frameworks */,
|
||||
6273DD43265F4195009C1D0B /* Moya in Frameworks */,
|
||||
53D5E3DD264B47EE00BADDC8 /* MobileVLCKit.xcframework in Frameworks */,
|
||||
5338F754263B65E10014BF09 /* SwiftyRequest in Frameworks */,
|
||||
53352571265EA0A0006CCA86 /* Introspect in Frameworks */,
|
||||
621C638026672A30004216EA /* NukeUI in Frameworks */,
|
||||
53A431BD266B0FF20016769F /* JellyfinAPI in Frameworks */,
|
||||
5389277A263CBFE70035E14B /* SwiftyJSON in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -244,8 +207,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
621338912660106C00A81A2A /* Extensions */,
|
||||
535870AB2669D8D300D05A09 /* Typings */,
|
||||
AE8C3157265D6F5E008AA076 /* Resources */,
|
||||
535870AB2669D8D300D05A09 /* Typings */,
|
||||
);
|
||||
path = Shared;
|
||||
sourceTree = "<group>";
|
||||
|
@ -261,12 +224,12 @@
|
|||
5377CBE8263B596A003A4E83 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
535870752669D60C00D05A09 /* Shared */,
|
||||
53D5E3DA264B460200BADDC8 /* Cartfile */,
|
||||
53D5E3DB264B47EE00BADDC8 /* Frameworks */,
|
||||
5377CBF3263B596A003A4E83 /* JellyfinPlayer */,
|
||||
535870612669D21600D05A09 /* JellyfinPlayer tvOS */,
|
||||
5377CBF2263B596A003A4E83 /* Products */,
|
||||
53D5E3DB264B47EE00BADDC8 /* Frameworks */,
|
||||
535870752669D60C00D05A09 /* Shared */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
@ -282,31 +245,31 @@
|
|||
5377CBF3263B596A003A4E83 /* JellyfinPlayer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5333A68A266BDDC10044FD6B /* JellyfinPlayer.entitlements */,
|
||||
6273DD46265F419B009C1D0B /* APIs */,
|
||||
AE8C3152265D607B008AA076 /* ViewModels */,
|
||||
AE8C3151265D6075008AA076 /* Models */,
|
||||
AE8C3150265D5FE1008AA076 /* Views */,
|
||||
5377CBF4263B596A003A4E83 /* JellyfinPlayerApp.swift */,
|
||||
5377CC02263B596B003A4E83 /* Info.plist */,
|
||||
5377CBF6263B596A003A4E83 /* ContentView.swift */,
|
||||
5377CBF8263B596B003A4E83 /* Assets.xcassets */,
|
||||
5338F74D263B61370014BF09 /* ConnectToServerView.swift */,
|
||||
5377CBF6263B596A003A4E83 /* ContentView.swift */,
|
||||
5389276D263C25100035E14B /* ContinueWatchingView.swift */,
|
||||
53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */,
|
||||
53987CA72657424A00E7EA70 /* EpisodeItemView.swift */,
|
||||
5377CC02263B596B003A4E83 /* Info.plist */,
|
||||
535BAE9E2649E569005FA86D /* ItemView.swift */,
|
||||
5377CBF4263B596A003A4E83 /* JellyfinPlayerApp.swift */,
|
||||
53FF7F29263CF3F500585C35 /* LatestMediaView.swift */,
|
||||
53E4E646263F6CF100F67C6B /* LibraryFilterView.swift */,
|
||||
6213388F265F83A900A81A2A /* LibraryListView.swift */,
|
||||
53EE24E5265060780068F029 /* LibrarySearchView.swift */,
|
||||
53DF641D263D9C0600A7CD1A /* LibraryView.swift */,
|
||||
53892771263C8C6F0035E14B /* LoadingView.swift */,
|
||||
53A089CF264DA9DA00D57806 /* MovieItemView.swift */,
|
||||
5389276F263C25230035E14B /* NextUpView.swift */,
|
||||
5377CBFD263B596B003A4E83 /* PersistenceController.swift */,
|
||||
5377CBFA263B596B003A4E83 /* Preview Content */,
|
||||
5338F74D263B61370014BF09 /* ConnectToServerView.swift */,
|
||||
5389276D263C25100035E14B /* ContinueWatchingView.swift */,
|
||||
5389276F263C25230035E14B /* NextUpView.swift */,
|
||||
53FF7F29263CF3F500585C35 /* LatestMediaView.swift */,
|
||||
53DF641D263D9C0600A7CD1A /* LibraryView.swift */,
|
||||
53E4E646263F6CF100F67C6B /* LibraryFilterView.swift */,
|
||||
535BAE9E2649E569005FA86D /* ItemView.swift */,
|
||||
53A089CF264DA9DA00D57806 /* MovieItemView.swift */,
|
||||
53EE24E5265060780068F029 /* LibrarySearchView.swift */,
|
||||
53987CA326572C1300E7EA70 /* SeasonItemView.swift */,
|
||||
53987CA526572F0700E7EA70 /* SeriesItemView.swift */,
|
||||
53987CA72657424A00E7EA70 /* EpisodeItemView.swift */,
|
||||
53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */,
|
||||
6213388F265F83A900A81A2A /* LibraryListView.swift */,
|
||||
539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */,
|
||||
535BAEA4264A151C005FA86D /* VideoPlayer.swift */,
|
||||
53313B8F265EEA6D00947AA3 /* VideoPlayer.storyboard */,
|
||||
53F8377C265FF67C00F456B3 /* VideoPlayerSettingsView.swift */,
|
||||
);
|
||||
path = JellyfinPlayer;
|
||||
sourceTree = "<group>";
|
||||
|
@ -331,62 +294,23 @@
|
|||
621338912660106C00A81A2A /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5389277B263CC3DB0035E14B /* BlurHashDecode.swift */,
|
||||
53E4E648263F725B00F67C6B /* MultiSelectorView.swift */,
|
||||
621338B22660A07800A81A2A /* LazyView.swift */,
|
||||
621338922660107500A81A2A /* StringExtensions.swift */,
|
||||
6225FCCA2663841E00E067F6 /* ParallaxHeader.swift */,
|
||||
621C638126676728004216EA /* NukeExtensions.swift */,
|
||||
53C4404D266C75C70049424C /* HandleAPIRequestCompletion.swift */,
|
||||
5364F454266CA0DC0026ECBA /* APIExtensions.swift */,
|
||||
5389277B263CC3DB0035E14B /* BlurHashDecode.swift */,
|
||||
53C4404D266C75C70049424C /* HandleAPIRequestCompletion.swift */,
|
||||
621338B22660A07800A81A2A /* LazyView.swift */,
|
||||
53E4E648263F725B00F67C6B /* MultiSelectorView.swift */,
|
||||
621C638126676728004216EA /* NukeExtensions.swift */,
|
||||
6225FCCA2663841E00E067F6 /* ParallaxHeader.swift */,
|
||||
621338922660107500A81A2A /* StringExtensions.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6273DD46265F419B009C1D0B /* APIs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6273DD47265F41B3009C1D0B /* JellyfinAPIOld.swift */,
|
||||
);
|
||||
path = APIs;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
AE8C3150265D5FE1008AA076 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
53892771263C8C6F0035E14B /* LoadingView.swift */,
|
||||
535BAEA4264A151C005FA86D /* VideoPlayer.swift */,
|
||||
53313B8F265EEA6D00947AA3 /* VideoPlayer.storyboard */,
|
||||
53F8377C265FF67C00F456B3 /* VideoPlayerSettingsView.swift */,
|
||||
539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
AE8C3151265D6075008AA076 /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
AE8C3153265D60BF008AA076 /* SettingsModel.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
AE8C3152265D607B008AA076 /* ViewModels */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6273DD4D265F47B2009C1D0B /* LibrarySearchViewModel.swift */,
|
||||
6213388D265F777C00A81A2A /* LibraryViewModel.swift */,
|
||||
62133894266096EF00A81A2A /* LibraryListViewModel.swift */,
|
||||
AE8C3155265D616A008AA076 /* SettingsViewModel.swift */,
|
||||
);
|
||||
path = ViewModels;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
AE8C3157265D6F5E008AA076 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5377CBFF263B596B003A4E83 /* Model.xcdatamodeld */,
|
||||
AE8C3158265D6F90008AA076 /* bitrates.json */,
|
||||
5377CBFF263B596B003A4E83 /* Model.xcdatamodeld */,
|
||||
);
|
||||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
|
@ -409,14 +333,8 @@
|
|||
);
|
||||
name = "JellyfinPlayer tvOS";
|
||||
packageProductDependencies = (
|
||||
5358708A2669D7A800D05A09 /* SwiftyRequest */,
|
||||
5358708C2669D7A800D05A09 /* KeychainSwift */,
|
||||
5358708E2669D7A800D05A09 /* SwiftyJSON */,
|
||||
535870902669D7A800D05A09 /* Introspect */,
|
||||
535870922669D7A800D05A09 /* CombineMoya */,
|
||||
535870942669D7A800D05A09 /* Moya */,
|
||||
535870962669D7A800D05A09 /* ReactiveMoya */,
|
||||
535870982669D7A800D05A09 /* RxMoya */,
|
||||
5358709A2669D7A800D05A09 /* NukeUI */,
|
||||
53A431BE266B0FFE0016769F /* JellyfinAPI */,
|
||||
);
|
||||
|
@ -440,12 +358,8 @@
|
|||
);
|
||||
name = JellyfinPlayer;
|
||||
packageProductDependencies = (
|
||||
5338F753263B65E10014BF09 /* SwiftyRequest */,
|
||||
5338F756263B7E2E0014BF09 /* KeychainSwift */,
|
||||
53892779263CBFE70035E14B /* SwiftyJSON */,
|
||||
53352570265EA0A0006CCA86 /* Introspect */,
|
||||
6273DD42265F4195009C1D0B /* Moya */,
|
||||
6273DD44265F4195009C1D0B /* CombineMoya */,
|
||||
621C637F26672A30004216EA /* NukeUI */,
|
||||
53A431BC266B0FF20016769F /* JellyfinAPI */,
|
||||
);
|
||||
|
@ -483,11 +397,8 @@
|
|||
);
|
||||
mainGroup = 5377CBE8263B596A003A4E83;
|
||||
packageReferences = (
|
||||
5338F752263B65E10014BF09 /* XCRemoteSwiftPackageReference "SwiftyRequest" */,
|
||||
5338F755263B7E2E0014BF09 /* XCRemoteSwiftPackageReference "keychain-swift" */,
|
||||
53892778263CBFE70035E14B /* XCRemoteSwiftPackageReference "SwiftyJSON" */,
|
||||
5335256F265EA0A0006CCA86 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */,
|
||||
6273DD41265F4195009C1D0B /* XCRemoteSwiftPackageReference "Moya" */,
|
||||
621C637E26672A30004216EA /* XCRemoteSwiftPackageReference "NukeUI" */,
|
||||
53A431BB266B0FF20016769F /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */,
|
||||
);
|
||||
|
@ -559,31 +470,21 @@
|
|||
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */,
|
||||
6225FCCB2663841E00E067F6 /* ParallaxHeader.swift in Sources */,
|
||||
53F8377D265FF67C00F456B3 /* VideoPlayerSettingsView.swift in Sources */,
|
||||
53987CA426572C1300E7EA70 /* SeasonItemView.swift in Sources */,
|
||||
53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */,
|
||||
62133890265F83A900A81A2A /* LibraryListView.swift in Sources */,
|
||||
AE8C3154265D60BF008AA076 /* SettingsModel.swift in Sources */,
|
||||
53892770263C25230035E14B /* NextUpView.swift in Sources */,
|
||||
535BAEA5264A151C005FA86D /* VideoPlayer.swift in Sources */,
|
||||
5377CC01263B596B003A4E83 /* Model.xcdatamodeld in Sources */,
|
||||
53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */,
|
||||
53A089D0264DA9DA00D57806 /* MovieItemView.swift in Sources */,
|
||||
53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */,
|
||||
53E4E647263F6CF100F67C6B /* LibraryFilterView.swift in Sources */,
|
||||
6213388E265F777C00A81A2A /* LibraryViewModel.swift in Sources */,
|
||||
621338B32660A07800A81A2A /* LazyView.swift in Sources */,
|
||||
62133895266096EF00A81A2A /* LibraryListViewModel.swift in Sources */,
|
||||
535870AD2669D8DD00D05A09 /* Typings.swift in Sources */,
|
||||
6273DD48265F41B3009C1D0B /* JellyfinAPIOld.swift in Sources */,
|
||||
53C4404E266C75C70049424C /* HandleAPIRequestCompletion.swift in Sources */,
|
||||
5377CBF7263B596A003A4E83 /* ContentView.swift in Sources */,
|
||||
53987CA82657424A00E7EA70 /* EpisodeItemView.swift in Sources */,
|
||||
5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */,
|
||||
53987CA626572F0700E7EA70 /* SeriesItemView.swift in Sources */,
|
||||
539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */,
|
||||
AE8C3156265D616A008AA076 /* SettingsViewModel.swift in Sources */,
|
||||
5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */,
|
||||
6273DD4E265F47B2009C1D0B /* LibrarySearchViewModel.swift in Sources */,
|
||||
5377CBF5263B596A003A4E83 /* JellyfinPlayerApp.swift in Sources */,
|
||||
53EE24E6265060780068F029 /* LibrarySearchView.swift in Sources */,
|
||||
53892772263C8C6F0035E14B /* LoadingView.swift in Sources */,
|
||||
|
@ -868,14 +769,6 @@
|
|||
minimumVersion = 0.1.3;
|
||||
};
|
||||
};
|
||||
5338F752263B65E10014BF09 /* XCRemoteSwiftPackageReference "SwiftyRequest" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/Kitura/SwiftyRequest";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 3.2.200;
|
||||
};
|
||||
};
|
||||
5338F755263B7E2E0014BF09 /* XCRemoteSwiftPackageReference "keychain-swift" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/evgenyneu/keychain-swift";
|
||||
|
@ -884,14 +777,6 @@
|
|||
minimumVersion = 19.0.0;
|
||||
};
|
||||
};
|
||||
53892778263CBFE70035E14B /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/SwiftyJSON/SwiftyJSON";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 5.0.1;
|
||||
};
|
||||
};
|
||||
53A431BB266B0FF20016769F /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/jellyfin/jellyfin-sdk-swift";
|
||||
|
@ -908,14 +793,6 @@
|
|||
kind = branch;
|
||||
};
|
||||
};
|
||||
6273DD41265F4195009C1D0B /* XCRemoteSwiftPackageReference "Moya" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/Moya/Moya";
|
||||
requirement = {
|
||||
kind = exactVersion;
|
||||
version = "15.0.0-alpha.1";
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
|
@ -924,66 +801,26 @@
|
|||
package = 5335256F265EA0A0006CCA86 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */;
|
||||
productName = Introspect;
|
||||
};
|
||||
5338F753263B65E10014BF09 /* SwiftyRequest */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5338F752263B65E10014BF09 /* XCRemoteSwiftPackageReference "SwiftyRequest" */;
|
||||
productName = SwiftyRequest;
|
||||
};
|
||||
5338F756263B7E2E0014BF09 /* KeychainSwift */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5338F755263B7E2E0014BF09 /* XCRemoteSwiftPackageReference "keychain-swift" */;
|
||||
productName = KeychainSwift;
|
||||
};
|
||||
5358708A2669D7A800D05A09 /* SwiftyRequest */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5338F752263B65E10014BF09 /* XCRemoteSwiftPackageReference "SwiftyRequest" */;
|
||||
productName = SwiftyRequest;
|
||||
};
|
||||
5358708C2669D7A800D05A09 /* KeychainSwift */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5338F755263B7E2E0014BF09 /* XCRemoteSwiftPackageReference "keychain-swift" */;
|
||||
productName = KeychainSwift;
|
||||
};
|
||||
5358708E2669D7A800D05A09 /* SwiftyJSON */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 53892778263CBFE70035E14B /* XCRemoteSwiftPackageReference "SwiftyJSON" */;
|
||||
productName = SwiftyJSON;
|
||||
};
|
||||
535870902669D7A800D05A09 /* Introspect */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5335256F265EA0A0006CCA86 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */;
|
||||
productName = Introspect;
|
||||
};
|
||||
535870922669D7A800D05A09 /* CombineMoya */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 6273DD41265F4195009C1D0B /* XCRemoteSwiftPackageReference "Moya" */;
|
||||
productName = CombineMoya;
|
||||
};
|
||||
535870942669D7A800D05A09 /* Moya */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 6273DD41265F4195009C1D0B /* XCRemoteSwiftPackageReference "Moya" */;
|
||||
productName = Moya;
|
||||
};
|
||||
535870962669D7A800D05A09 /* ReactiveMoya */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 6273DD41265F4195009C1D0B /* XCRemoteSwiftPackageReference "Moya" */;
|
||||
productName = ReactiveMoya;
|
||||
};
|
||||
535870982669D7A800D05A09 /* RxMoya */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 6273DD41265F4195009C1D0B /* XCRemoteSwiftPackageReference "Moya" */;
|
||||
productName = RxMoya;
|
||||
};
|
||||
5358709A2669D7A800D05A09 /* NukeUI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 621C637E26672A30004216EA /* XCRemoteSwiftPackageReference "NukeUI" */;
|
||||
productName = NukeUI;
|
||||
};
|
||||
53892779263CBFE70035E14B /* SwiftyJSON */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 53892778263CBFE70035E14B /* XCRemoteSwiftPackageReference "SwiftyJSON" */;
|
||||
productName = SwiftyJSON;
|
||||
};
|
||||
53A431BC266B0FF20016769F /* JellyfinAPI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 53A431BB266B0FF20016769F /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */;
|
||||
|
@ -999,16 +836,6 @@
|
|||
package = 621C637E26672A30004216EA /* XCRemoteSwiftPackageReference "NukeUI" */;
|
||||
productName = NukeUI;
|
||||
};
|
||||
6273DD42265F4195009C1D0B /* Moya */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 6273DD41265F4195009C1D0B /* XCRemoteSwiftPackageReference "Moya" */;
|
||||
productName = Moya;
|
||||
};
|
||||
6273DD44265F4195009C1D0B /* CombineMoya */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 6273DD41265F4195009C1D0B /* XCRemoteSwiftPackageReference "Moya" */;
|
||||
productName = CombineMoya;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
|
||||
/* Begin XCVersionGroup section */
|
||||
|
|
|
@ -1,15 +1,6 @@
|
|||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "Alamofire",
|
||||
"repositoryURL": "https://github.com/Alamofire/Alamofire.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "f96b619bcb2383b43d898402283924b80e2c4bae",
|
||||
"version": "5.4.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "AnyCodable",
|
||||
"repositoryURL": "https://github.com/Flight-School/AnyCodable",
|
||||
|
@ -19,24 +10,6 @@
|
|||
"version": "0.6.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "async-http-client",
|
||||
"repositoryURL": "https://github.com/swift-server/async-http-client.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "037b70291941fe43de668066eb6fb802c5e181d2",
|
||||
"version": "1.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "CircuitBreaker",
|
||||
"repositoryURL": "https://github.com/Kitura/CircuitBreaker.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "915cd4ed17500784cf5bcbf2ef54a76830884c86",
|
||||
"version": "5.0.200"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Gifu",
|
||||
"repositoryURL": "https://github.com/kaishin/Gifu",
|
||||
|
@ -64,24 +37,6 @@
|
|||
"version": "19.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "LoggerAPI",
|
||||
"repositoryURL": "https://github.com/Kitura/LoggerAPI.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "e82d34eab3f0b05391082b11ea07d3b70d2f65bb",
|
||||
"version": "1.9.200"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Moya",
|
||||
"repositoryURL": "https://github.com/Moya/Moya",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "e5a28fb62dd5ff4e17b7025643366550044a40b0",
|
||||
"version": "15.0.0-alpha.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Nuke",
|
||||
"repositoryURL": "https://github.com/kean/Nuke.git",
|
||||
|
@ -100,60 +55,6 @@
|
|||
"version": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "ReactiveSwift",
|
||||
"repositoryURL": "https://github.com/Moya/ReactiveSwift.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "f195d82bb30e412e70446e2b4a77e1b514099e88",
|
||||
"version": "6.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "RxSwift",
|
||||
"repositoryURL": "https://github.com/ReactiveX/RxSwift.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "254617dd7fae0c45319ba5fbea435bf4d0e15b5d",
|
||||
"version": "5.1.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "swift-log",
|
||||
"repositoryURL": "https://github.com/apple/swift-log.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "5d66f7ba25daf4f94100e7022febf3c75e37a6c7",
|
||||
"version": "1.4.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "swift-nio",
|
||||
"repositoryURL": "https://github.com/apple/swift-nio.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "d161bf658780b209c185994528e7e24376cf7283",
|
||||
"version": "2.29.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "swift-nio-extras",
|
||||
"repositoryURL": "https://github.com/apple/swift-nio-extras.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "de1c80ad1fdff1ba772bcef6b392c3ef735f39a6",
|
||||
"version": "1.8.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "swift-nio-ssl",
|
||||
"repositoryURL": "https://github.com/apple/swift-nio-ssl.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "6363cdf6d2fb863e82434f3c4618f4e896e37569",
|
||||
"version": "2.13.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Introspect",
|
||||
"repositoryURL": "https://github.com/siteline/SwiftUI-Introspect",
|
||||
|
@ -162,24 +63,6 @@
|
|||
"revision": "2e09be8af614401bc9f87d40093ec19ce56ccaf2",
|
||||
"version": "0.1.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "SwiftyJSON",
|
||||
"repositoryURL": "https://github.com/SwiftyJSON/SwiftyJSON",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "b3dcd7dbd0d488e1a7077cb33b00f2083e382f07",
|
||||
"version": "5.0.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "SwiftyRequest",
|
||||
"repositoryURL": "https://github.com/Kitura/SwiftyRequest",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "2c543777a5088bed811503a68551a4b4eceac198",
|
||||
"version": "3.2.200"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -1,148 +0,0 @@
|
|||
/* JellyfinPlayer/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 Moya
|
||||
|
||||
enum ImageType: String {
|
||||
case primary = "Primary"
|
||||
case backdrop = "Backdrop"
|
||||
case thumb = "Thumb"
|
||||
case banner = "Banner"
|
||||
}
|
||||
|
||||
enum Field: String {
|
||||
case primaryImageAspectRatio = "PrimaryImageAspectRatio"
|
||||
case basicSyncInfo = "BasicSyncInfo"
|
||||
}
|
||||
|
||||
enum ItemType: String {
|
||||
case movie = "Movie"
|
||||
case series = "Series"
|
||||
}
|
||||
|
||||
enum SortType: String {
|
||||
case name = "SortName"
|
||||
case dateCreated = "DateCreated"
|
||||
case datePlayed = "DatePlayed"
|
||||
case premiereDate = "PremiereDate"
|
||||
case runtime = "Runtime"
|
||||
}
|
||||
|
||||
enum ASC: String {
|
||||
case descending = "Descending"
|
||||
case ascending = "Ascending"
|
||||
}
|
||||
|
||||
enum FilterType: String {
|
||||
case isFavorite = "IsFavorite"
|
||||
case isUnplayed = "IsUnplayed"
|
||||
}
|
||||
|
||||
struct Filter {
|
||||
var imageTypes: [ImageType] = [.primary, .backdrop, .thumb, .banner]
|
||||
var fields: [Field] = [.primaryImageAspectRatio, .basicSyncInfo]
|
||||
var itemTypes: [ItemType] = [.movie, .series]
|
||||
var filterTypes = [FilterType]()
|
||||
var sort: SortType? = .dateCreated
|
||||
var asc: ASC? = .descending
|
||||
var parentID: String?
|
||||
var imageTypeLimit: Int? = 1
|
||||
var recursive = true
|
||||
var genres = [String]()
|
||||
var personIds = [String]()
|
||||
var officialRatings = [String]()
|
||||
}
|
||||
|
||||
extension Filter {
|
||||
var toParamters: [String: Any] {
|
||||
var parameters = [String: Any]()
|
||||
parameters["EnableImageTypes"] = imageTypes.map(\.rawValue).joined(separator: ",")
|
||||
parameters["Fields"] = fields.map(\.rawValue).joined(separator: ",")
|
||||
parameters["Filters"] = filterTypes.map(\.rawValue).joined(separator: ",")
|
||||
parameters["ImageTypeLimit"] = imageTypeLimit
|
||||
parameters["IncludeItemTypes"] = itemTypes.map(\.rawValue).joined(separator: ",")
|
||||
parameters["ParentId"] = parentID
|
||||
parameters["Recursive"] = recursive
|
||||
parameters["SortBy"] = sort?.rawValue
|
||||
parameters["SortOrder"] = asc?.rawValue
|
||||
parameters["Genres"] = genres.joined(separator: ",")
|
||||
parameters["PersonIds"] = personIds.joined(separator: ",")
|
||||
parameters["OfficialRatings"] = officialRatings.joined(separator: ",")
|
||||
return parameters
|
||||
}
|
||||
}
|
||||
|
||||
enum JellyfinAPIOld {
|
||||
case items(globalData: GlobalData, filter: Filter, page: Int)
|
||||
case search(globalData: GlobalData, filter: Filter, searchQuery: String, page: Int)
|
||||
}
|
||||
|
||||
extension JellyfinAPIOld: TargetType {
|
||||
|
||||
var baseURL: URL {
|
||||
switch self {
|
||||
case let .items(global, _, _),
|
||||
let .search(global, _, _, _):
|
||||
return URL(string: global.server.baseURI ?? "")!
|
||||
}
|
||||
}
|
||||
|
||||
var path: String {
|
||||
switch self {
|
||||
case let .items(global, _, _),
|
||||
let .search(global, _, _, _):
|
||||
return "/Users/\(global.user.user_id ?? "")/Items"
|
||||
}
|
||||
}
|
||||
|
||||
var method: Moya.Method {
|
||||
switch self {
|
||||
case .items, .search:
|
||||
return .get
|
||||
}
|
||||
}
|
||||
|
||||
var sampleData: Data {
|
||||
"{".data(using: .utf8)!
|
||||
}
|
||||
|
||||
var task: Task {
|
||||
switch self {
|
||||
case let .search(_, filter, searchQuery, page):
|
||||
var parameters = filter.toParamters
|
||||
parameters["searchTerm"] = searchQuery
|
||||
parameters["StartIndex"] = (page - 1) * 100
|
||||
parameters["Limit"] = 100
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.jellyfin)
|
||||
case let .items(_, filter, page):
|
||||
var parameters = filter.toParamters
|
||||
parameters["StartIndex"] = (page - 1) * 100
|
||||
parameters["Limit"] = 100
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.jellyfin)
|
||||
}
|
||||
}
|
||||
|
||||
var headers: [String: String]? {
|
||||
switch self {
|
||||
case let .items(global, _, _),
|
||||
let .search(global, _, _, _):
|
||||
var headers = [String: String]()
|
||||
headers["Content-Type"] = "application/json"
|
||||
headers["Accept"] = "application/json"
|
||||
headers["X-Emby-Authorization"] = global.authHeader
|
||||
return headers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension URLEncoding {
|
||||
|
||||
static var jellyfin: URLEncoding {
|
||||
URLEncoding(destination: .methodDependent, arrayEncoding: .noBrackets, boolEncoding: .literal)
|
||||
}
|
||||
}
|
|
@ -88,14 +88,18 @@ struct ConnectToServerView: View {
|
|||
deviceName = deviceName.removeRegexMatches(pattern: "[^\\w\\s]");
|
||||
|
||||
let authHeader = "MediaBrowser Client=\"SwiftFin\", Device=\"\(deviceName)\", DeviceId=\"\(serverSkipped ? reauthDeviceID : userUUID.uuidString)\", Version=\"\(appVersion ?? "0.0.1")\"";
|
||||
print(authHeader)
|
||||
|
||||
JellyfinAPI.customHeaders["X-Emby-Authorization"] = authHeader
|
||||
|
||||
UserAPI.authenticateUser(userId: username, pw: password)
|
||||
let x: AuthenticateUserByName = AuthenticateUserByName(username: username, pw: password, password: nil)
|
||||
|
||||
UserAPI.authenticateUserByName(authenticateUserByName: x)
|
||||
.sink(receiveCompletion: { completion in
|
||||
isWorking = false
|
||||
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
|
||||
}, receiveValue: { response in
|
||||
isWorking = true
|
||||
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Server")
|
||||
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
|
||||
|
||||
|
@ -133,6 +137,8 @@ struct ConnectToServerView: View {
|
|||
globalData.authHeader = authHeader
|
||||
_rootIsActive.wrappedValue = false
|
||||
jsi.did = true
|
||||
print("logged in")
|
||||
isWorking = false
|
||||
}
|
||||
} catch {
|
||||
print("Couldn't store objects to CoreData")
|
||||
|
@ -291,6 +297,9 @@ struct ConnectToServerView: View {
|
|||
.frame(width: 60, height: 60)
|
||||
.cornerRadius(30.0)
|
||||
.shadow(radius: 6)
|
||||
.onAppear(perform: {
|
||||
print("\(uri)/Users/\(publicUser.id!)/Images/Primary?width=200&quality=80&tag=\(publicUser.primaryImageTag!)")
|
||||
})
|
||||
} else {
|
||||
Image(systemName: "person.fill")
|
||||
.foregroundColor(Color(red: 1, green: 1, blue: 1).opacity(0.8))
|
||||
|
|
|
@ -34,6 +34,7 @@ struct ContentView: View {
|
|||
@State private var libraryPrefillID: String = ""
|
||||
@State private var showSettingsPopover: Bool = false
|
||||
@State private var viewDidLoad: Bool = false
|
||||
@State private var loadState: Int = 2
|
||||
|
||||
func startup() {
|
||||
if(viewDidLoad == true) {
|
||||
|
@ -85,21 +86,31 @@ struct ContentView: View {
|
|||
UserAPI.getCurrentUser()
|
||||
.sink(receiveCompletion: { completion in
|
||||
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
|
||||
loadState = loadState - 1
|
||||
}, receiveValue: { response in
|
||||
libraries = response.configuration?.orderedViews ?? []
|
||||
librariesShowRecentlyAdded = libraries.filter { element in
|
||||
return !(response.configuration?.latestItemsExcludes?.contains(element))!
|
||||
}
|
||||
|
||||
if(loadState == 0) {
|
||||
isLoading = false
|
||||
}
|
||||
})
|
||||
.store(in: &globalData.pendingAPIRequests)
|
||||
|
||||
UserViewsAPI.getUserViews(userId: globalData.user.user_id ?? "")
|
||||
.sink(receiveCompletion: { completion in
|
||||
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
|
||||
loadState = loadState - 1
|
||||
}, receiveValue: { response in
|
||||
response.items?.forEach({ item in
|
||||
library_names[item.id ?? ""] = item.name
|
||||
})
|
||||
|
||||
if(loadState == 0) {
|
||||
isLoading = false
|
||||
}
|
||||
})
|
||||
.store(in: &globalData.pendingAPIRequests)
|
||||
|
||||
|
@ -110,8 +121,6 @@ struct ContentView: View {
|
|||
if defaults.integer(forKey: "OutOfNetworkBandwidth") == 0 {
|
||||
defaults.setValue(40_000_000, forKey: "OutOfNetworkBandwidth")
|
||||
}
|
||||
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,61 +141,67 @@ struct ContentView: View {
|
|||
} else {
|
||||
if !jsi.did {
|
||||
LoadingView(isShowing: $isLoading) {
|
||||
TabView(selection: $tabSelection) {
|
||||
NavigationView {
|
||||
VStack(alignment: .leading) {
|
||||
ScrollView {
|
||||
Spacer().frame(height: orientationInfo.orientation == .portrait ? 0 : 16)
|
||||
ContinueWatchingView()
|
||||
NextUpView()
|
||||
ForEach(librariesShowRecentlyAdded, id: \.self) { library_id in
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Text("Latest \(library_names[library_id] ?? "")").font(.title2).fontWeight(.bold)
|
||||
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16))
|
||||
Spacer()
|
||||
NavigationLink(destination: LazyView {
|
||||
LibraryView(viewModel: .init(filter: Filter(parentID: library_id)),
|
||||
title: library_names[library_id] ?? "")
|
||||
}) {
|
||||
Text("See All").font(.subheadline).fontWeight(.bold)
|
||||
VStack() {
|
||||
if(loadState == 0) {
|
||||
TabView(selection: $tabSelection) {
|
||||
NavigationView {
|
||||
VStack(alignment: .leading) {
|
||||
ScrollView {
|
||||
Spacer().frame(height: orientationInfo.orientation == .portrait ? 0 : 16)
|
||||
ContinueWatchingView()
|
||||
NextUpView()
|
||||
ForEach(librariesShowRecentlyAdded, id: \.self) { library_id in
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Text("Latest \(library_names[library_id] ?? "")").font(.title2).fontWeight(.bold)
|
||||
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16))
|
||||
Spacer()
|
||||
NavigationLink(destination: LazyView {
|
||||
LibraryView(usingParentID: library_id,
|
||||
title: library_names[library_id] ?? "")
|
||||
}) {
|
||||
Text("See All").font(.subheadline).fontWeight(.bold)
|
||||
}
|
||||
}.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
|
||||
LatestMediaView(usingParentID: library_id)
|
||||
}.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
|
||||
}
|
||||
Spacer().frame(height: UIDevice.current.userInterfaceIdiom == .phone ? 20 : 30)
|
||||
}
|
||||
.navigationTitle("Home")
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
showSettingsPopover = true
|
||||
} label: {
|
||||
Image(systemName: "gear")
|
||||
}
|
||||
}.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
|
||||
LatestMediaView(usingLibraryID: library_id)
|
||||
}.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
|
||||
}
|
||||
Spacer().frame(height: UIDevice.current.userInterfaceIdiom == .phone ? 20 : 30)
|
||||
}
|
||||
.navigationTitle("Home")
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
showSettingsPopover = true
|
||||
} label: {
|
||||
Image(systemName: "gear")
|
||||
}
|
||||
}
|
||||
.fullScreenCover(isPresented: $showSettingsPopover) {
|
||||
SettingsView(viewModel: SettingsViewModel(), close: $showSettingsPopover)
|
||||
}
|
||||
}
|
||||
}
|
||||
.fullScreenCover(isPresented: $showSettingsPopover) {
|
||||
SettingsView(viewModel: SettingsViewModel(), close: $showSettingsPopover)
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.tabItem {
|
||||
Text("Home")
|
||||
Image(systemName: "house")
|
||||
}
|
||||
.tag("Home")
|
||||
NavigationView {
|
||||
LibraryListView(libraries: library_names)
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.tabItem {
|
||||
Text("All Media")
|
||||
Image(systemName: "folder")
|
||||
}
|
||||
.tag("All Media")
|
||||
}
|
||||
} else {
|
||||
Text("Loading...")
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.tabItem {
|
||||
Text("Home")
|
||||
Image(systemName: "house")
|
||||
}
|
||||
.tag("Home")
|
||||
NavigationView {
|
||||
LibraryListView(viewModel: .init(libraryNames: library_names, libraryIDs: libraries))
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
.tabItem {
|
||||
Text("All Media")
|
||||
Image(systemName: "folder")
|
||||
}
|
||||
.tag("All Media")
|
||||
}
|
||||
}
|
||||
.environmentObject(globalData)
|
||||
|
|
|
@ -52,7 +52,7 @@ struct ContinueWatchingView: View {
|
|||
LazyHStack() {
|
||||
Spacer().frame(width:14)
|
||||
ForEach(items, id: \.id) { item in
|
||||
NavigationLink(destination: EmptyView()) {
|
||||
NavigationLink(destination: ItemView(item: item)) {
|
||||
VStack(alignment: .leading) {
|
||||
Spacer().frame(height: 10)
|
||||
LazyImage(source: item.getBackdropImage(baseURL: globalData.server.baseURI ?? "", maxWidth: 320))
|
||||
|
|
|
@ -17,53 +17,79 @@ class VideoPlayerItem: ObservableObject {
|
|||
}
|
||||
|
||||
struct ItemView: View {
|
||||
private var item: BaseItemDto;
|
||||
@StateObject private var videoPlayerItem: VideoPlayerItem = VideoPlayerItem()
|
||||
@EnvironmentObject private var globalData: GlobalData
|
||||
|
||||
@State private var isLoading: Bool = false; //This variable is only changed by the underlying VLC view.
|
||||
@State private var fullItem: BaseItemDto = BaseItemDto();
|
||||
private var item: BaseItemDto;
|
||||
|
||||
@StateObject private var videoPlayerItem: VideoPlayerItem = VideoPlayerItem()
|
||||
@State private var videoIsLoading: Bool = false; //This variable is only changed by the underlying VLC view.
|
||||
@State private var isLoading: Bool = false;
|
||||
|
||||
init(item: BaseItemDto) {
|
||||
self.item = item;
|
||||
self.item = item
|
||||
}
|
||||
|
||||
func onAppear() {
|
||||
isLoading = true;
|
||||
UserLibraryAPI.getItem(userId: globalData.user.user_id!, itemId: item.id!)
|
||||
.sink(receiveCompletion: { completion in
|
||||
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
|
||||
}, receiveValue: { response in
|
||||
isLoading = false
|
||||
fullItem = response
|
||||
})
|
||||
.store(in: &globalData.pendingAPIRequests)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if(videoPlayerItem.shouldShowPlayer) {
|
||||
LoadingViewNoBlur(isShowing: $isLoading) {
|
||||
VLCPlayerWithControls(item: playback.itemToPlay, loadBinding: $isLoading, pBinding: _videoPlayerItem.projectedValue.shouldShowPlayer)
|
||||
}.navigationBarHidden(true)
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.statusBar(hidden: true)
|
||||
.prefersHomeIndicatorAutoHidden(true)
|
||||
.preferredColorScheme(.dark)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
.overrideViewPreference(.unspecified)
|
||||
.supportedOrientations(.landscape)
|
||||
} else {
|
||||
Group {
|
||||
if(item.Type == "Movie") {
|
||||
MovieItemView(item: self.item)
|
||||
} else if(item.Type == "Season") {
|
||||
SeasonItemView(item: self.item)
|
||||
} else if(item.Type == "Series") {
|
||||
SeriesItemView(item: self.item)
|
||||
} else if(item.Type == "Episode") {
|
||||
EpisodeItemView(item: self.item)
|
||||
VStack {
|
||||
if(videoPlayerItem.shouldShowPlayer) {
|
||||
LoadingViewNoBlur(isShowing: $videoIsLoading) {
|
||||
VLCPlayerWithControls(item: videoPlayerItem.itemToPlay, loadBinding: $videoIsLoading, pBinding: _videoPlayerItem.projectedValue.shouldShowPlayer)
|
||||
}.navigationBarHidden(true)
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.statusBar(hidden: true)
|
||||
.prefersHomeIndicatorAutoHidden(true)
|
||||
.preferredColorScheme(.dark)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
.overrideViewPreference(.unspecified)
|
||||
.supportedOrientations(.landscape)
|
||||
} else {
|
||||
if(isLoading) {
|
||||
ProgressView()
|
||||
} else {
|
||||
Text("Type: \(item.Type) not implemented yet :(")
|
||||
VStack {
|
||||
if(fullItem.type == "Movie") {
|
||||
MovieItemView(item: fullItem)
|
||||
} else if(fullItem.type == "Season") {
|
||||
EmptyView()
|
||||
//SeasonItemView(item: fullItem)
|
||||
} else if(fullItem.type == "Series") {
|
||||
EmptyView()
|
||||
//SeriesItemView(item: fullItem)
|
||||
} else if(fullItem.type == "Episode") {
|
||||
EmptyView()
|
||||
//EpisodeItemView(item: fullItem)
|
||||
} else {
|
||||
Text("Type: \(fullItem.type ?? "") not implemented yet :(")
|
||||
}
|
||||
}
|
||||
.introspectTabBarController { (UITabBarController) in
|
||||
UITabBarController.tabBar.isHidden = false
|
||||
}
|
||||
.navigationBarHidden(false)
|
||||
.navigationBarBackButtonHidden(false)
|
||||
.statusBar(hidden: false)
|
||||
.prefersHomeIndicatorAutoHidden(false)
|
||||
.preferredColorScheme(.none)
|
||||
.edgesIgnoringSafeArea([])
|
||||
.overrideViewPreference(.unspecified)
|
||||
.supportedOrientations(.allButUpsideDown)
|
||||
.environmentObject(videoPlayerItem)
|
||||
}
|
||||
}
|
||||
.introspectTabBarController { (UITabBarController) in
|
||||
UITabBarController.tabBar.isHidden = false
|
||||
}
|
||||
.navigationBarHidden(false)
|
||||
.navigationBarBackButtonHidden(false)
|
||||
.statusBar(hidden: false)
|
||||
.prefersHomeIndicatorAutoHidden(false)
|
||||
.preferredColorScheme(.none)
|
||||
.edgesIgnoringSafeArea([])
|
||||
.overrideViewPreference(.unspecified)
|
||||
.supportedOrientations(.allButUpsideDown)
|
||||
.environmentObject(playback)
|
||||
}
|
||||
.onAppear(perform: onAppear)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,8 @@ struct LatestMediaView: View {
|
|||
private var library_id: String = "";
|
||||
@State private var viewDidLoad: Bool = false;
|
||||
|
||||
init(usingLibraryID: String) {
|
||||
library_id = usingLibraryID;
|
||||
init(usingParentID: String) {
|
||||
library_id = usingParentID;
|
||||
}
|
||||
|
||||
func onAppear() {
|
||||
|
@ -41,7 +41,7 @@ struct LatestMediaView: View {
|
|||
Spacer().frame(width:16)
|
||||
ForEach(items, id: \.id) { item in
|
||||
if(item.type == "Series" || item.type == "Movie") {
|
||||
NavigationLink(destination: EmptyView()) {
|
||||
NavigationLink(destination: ItemView(item: item)) {
|
||||
VStack(alignment: .leading) {
|
||||
Spacer().frame(height:10)
|
||||
LazyImage(source: item.getPrimaryImage(baseURL: globalData.server.baseURI!, maxWidth: 100))
|
||||
|
|
|
@ -6,99 +6,15 @@
|
|||
*/
|
||||
|
||||
import SwiftUI
|
||||
import SwiftyJSON
|
||||
import SwiftyRequest
|
||||
|
||||
struct Genre: Hashable, Identifiable {
|
||||
var name: String
|
||||
var id: String { name }
|
||||
}
|
||||
import JellyfinAPI
|
||||
|
||||
struct LibraryFilterView: View {
|
||||
@Environment(\.presentationMode)
|
||||
var presentationMode
|
||||
@Environment(\.managedObjectContext)
|
||||
private var viewContext
|
||||
@EnvironmentObject
|
||||
var globalData: GlobalData
|
||||
|
||||
@State
|
||||
var library: String
|
||||
|
||||
@Binding
|
||||
var filter: Filter
|
||||
@State
|
||||
private var isLoading: Bool = true
|
||||
@State
|
||||
private var onlyUnplayed: Bool = false
|
||||
@State
|
||||
private var allGenres: [Genre] = []
|
||||
@State
|
||||
private var selectedGenres: Set<Genre> = []
|
||||
|
||||
@State
|
||||
private var allRatings: [Genre] = []
|
||||
@State
|
||||
private var selectedRatings: Set<Genre> = []
|
||||
@State
|
||||
private var sortBySelection: String = "SortName"
|
||||
@State
|
||||
private var sortOrder: String = "Descending"
|
||||
@State
|
||||
private var viewDidLoad: Bool = false
|
||||
|
||||
func onAppear() {
|
||||
if _viewDidLoad.wrappedValue == true {
|
||||
return
|
||||
}
|
||||
_viewDidLoad.wrappedValue = true
|
||||
if filter.filterTypes.contains(.isUnplayed) {
|
||||
_onlyUnplayed.wrappedValue = true
|
||||
}
|
||||
if !filter.genres.isEmpty {
|
||||
_selectedGenres.wrappedValue = Set(filter.genres.map { Genre(name: $0) })
|
||||
}
|
||||
if !filter.officialRatings.isEmpty {
|
||||
_selectedRatings.wrappedValue = Set(filter.officialRatings.map { Genre(name: $0) })
|
||||
}
|
||||
_sortBySelection.wrappedValue = filter.sort?.rawValue ?? sortBySelection
|
||||
_sortOrder.wrappedValue = filter.asc?.rawValue ?? sortOrder
|
||||
|
||||
_allGenres.wrappedValue = []
|
||||
let url = "/Items/Filters?UserId=\(globalData.user.user_id ?? "")&ParentId=\(library)"
|
||||
let request = RestRequest(method: .get, url: (globalData.server.baseURI ?? "") + url)
|
||||
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
|
||||
request.contentType = "application/json"
|
||||
request.acceptType = "application/json"
|
||||
|
||||
request.responseData { (result: Result<RestResponse<Data>, RestError>) in
|
||||
switch result {
|
||||
case let .success(response):
|
||||
let body = response.body
|
||||
do {
|
||||
let json = try JSON(data: body)
|
||||
let arr = json["Genres"].arrayObject as? [String] ?? []
|
||||
for genreName in arr {
|
||||
// print(genreName)
|
||||
let genre = Genre(name: genreName)
|
||||
allGenres.append(genre)
|
||||
}
|
||||
|
||||
let arr2 = json["OfficialRatings"].arrayObject as? [String] ?? []
|
||||
for genreName in arr2 {
|
||||
// print(genreName)
|
||||
let genre = Genre(name: genreName)
|
||||
allRatings.append(genre)
|
||||
}
|
||||
} catch {}
|
||||
case let .failure(error):
|
||||
debugPrint(error)
|
||||
}
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
@EnvironmentObject var globalData: GlobalData
|
||||
@Binding var filter: Filter
|
||||
|
||||
var body: some View {
|
||||
EmptyView()
|
||||
/*
|
||||
NavigationView {
|
||||
LoadingView(isShowing: $isLoading) {
|
||||
Form {
|
||||
|
@ -159,5 +75,6 @@ struct LibraryFilterView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,34 +9,59 @@ import Foundation
|
|||
import SwiftUI
|
||||
|
||||
struct LibraryListView: View {
|
||||
@Environment(\.managedObjectContext)
|
||||
private var viewContext
|
||||
@EnvironmentObject
|
||||
var globalData: GlobalData
|
||||
@StateObject
|
||||
var viewModel: LibraryListViewModel
|
||||
|
||||
@EnvironmentObject var globalData: GlobalData
|
||||
|
||||
@State var library_ids: [String] = ["favorites", "genres"]
|
||||
@State var library_names: [String: String] = ["favorites": "Favorites", "genres": "Genres"]
|
||||
var libraries: [String: String] = [:] //input libraries
|
||||
|
||||
init(libraries: [String: String]) {
|
||||
self.libraries = libraries
|
||||
print(libraries)
|
||||
}
|
||||
|
||||
func onAppear() {
|
||||
if(library_ids.count == 2) {
|
||||
libraries.forEach() { k,v in
|
||||
print("\(k): \(v)")
|
||||
_library_ids.wrappedValue.append(k)
|
||||
_library_names.wrappedValue[k] = v
|
||||
}
|
||||
print(library_ids)
|
||||
print(library_names)
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List(viewModel.libraryIDs, id: \.self) { id in
|
||||
switch id {
|
||||
case "favorites":
|
||||
NavigationLink(destination: LazyView { LibraryView(viewModel: .init(filter: Filter(filterTypes: [.isFavorite])),
|
||||
title: viewModel.libraryNames[id] ?? "") }) {
|
||||
Text(viewModel.libraryNames[id] ?? "").foregroundColor(Color.primary)
|
||||
}
|
||||
case "genres":
|
||||
Text(viewModel.libraryNames[id] ?? "").foregroundColor(Color.primary)
|
||||
default:
|
||||
NavigationLink(destination: LazyView { LibraryView(viewModel: .init(filter: Filter(parentID: id)),
|
||||
title: viewModel.libraryNames[id] ?? "") }) {
|
||||
Text(viewModel.libraryNames[id] ?? "").foregroundColor(Color.primary)
|
||||
}
|
||||
List(library_ids, id: \.self) { key in
|
||||
switch key {
|
||||
case "favorites":
|
||||
NavigationLink(destination: LazyView {
|
||||
LibraryView(usingParentID: "", title: library_names[key] ?? "", filters: [.isFavorite])
|
||||
}) {
|
||||
Text(library_names[key] ?? "")
|
||||
}
|
||||
case "genres":
|
||||
NavigationLink(destination: LazyView {
|
||||
EmptyView()
|
||||
}) {
|
||||
Text(library_names[key] ?? "")
|
||||
}
|
||||
default:
|
||||
NavigationLink(destination: LazyView {
|
||||
LibraryView(usingParentID: key, title: library_names[key] ?? "")
|
||||
}) {
|
||||
Text(library_names[key] ?? "")
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("All Media")
|
||||
.onAppear(perform: onAppear)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
NavigationLink(destination: LazyView { LibrarySearchView(viewModel: .init(filter: .init())) }) {
|
||||
NavigationLink(destination: LazyView {
|
||||
LibrarySearchView(usingParentID: "")
|
||||
}) {
|
||||
Image(systemName: "magnifyingglass")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,37 +6,39 @@
|
|||
*/
|
||||
|
||||
import SwiftUI
|
||||
import SwiftyJSON
|
||||
import SwiftyRequest
|
||||
import NukeUI
|
||||
import JellyfinAPI
|
||||
|
||||
struct LibrarySearchView: View {
|
||||
@Environment(\.managedObjectContext)
|
||||
private var viewContext
|
||||
@EnvironmentObject
|
||||
var globalData: GlobalData
|
||||
@StateObject
|
||||
var viewModel: LibrarySearchViewModel
|
||||
@EnvironmentObject var globalData: GlobalData
|
||||
@EnvironmentObject var orientationInfo: OrientationInfo
|
||||
|
||||
@State
|
||||
private var tracks: [GridItem] = []
|
||||
|
||||
@Environment(\.verticalSizeClass)
|
||||
var verticalSizeClass: UserInterfaceSizeClass?
|
||||
@Environment(\.horizontalSizeClass)
|
||||
var horizontalSizeClass: UserInterfaceSizeClass?
|
||||
@State private var items: [BaseItemDto] = []
|
||||
@State private var searchQuery: String = ""
|
||||
@State private var isLoading: Bool = false
|
||||
|
||||
var usingParentID: String
|
||||
|
||||
func onAppear() {
|
||||
guard viewModel.globalData != globalData else { return }
|
||||
recalcTracks()
|
||||
viewModel.globalData = globalData
|
||||
requestSearch(query: "")
|
||||
}
|
||||
|
||||
var isPortrait: Bool {
|
||||
let result = verticalSizeClass == .regular && horizontalSizeClass == .compact
|
||||
return result
|
||||
|
||||
func requestSearch(query: String) {
|
||||
isLoading = true
|
||||
ItemsAPI.getItems(userId: globalData.user.user_id!, searchTerm: query, parentId: usingParentID)
|
||||
.sink(receiveCompletion: { completion in
|
||||
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
|
||||
}, receiveValue: { response in
|
||||
items = response.items!
|
||||
})
|
||||
.store(in: &globalData.pendingAPIRequests)
|
||||
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
|
||||
//MARK: tracks for grid
|
||||
@State private var tracks: [GridItem] = []
|
||||
func recalcTracks() {
|
||||
let trkCnt = Int(floor(UIScreen.main.bounds.size.width / 125))
|
||||
_tracks.wrappedValue = []
|
||||
|
@ -47,96 +49,56 @@ struct LibrarySearchView: View {
|
|||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
VStack {
|
||||
Spacer().frame(height: 6)
|
||||
TextField("Search", text: $viewModel.searchQuery, onEditingChanged: { _ in
|
||||
print("changed")
|
||||
})
|
||||
.padding(.horizontal, 10)
|
||||
.foregroundColor(Color.secondary)
|
||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
ScrollView(.vertical) {
|
||||
LazyVGrid(columns: tracks) {
|
||||
ForEach(viewModel.items, id: \.Id) { item in
|
||||
NavigationLink(destination: ItemView(item: item)) {
|
||||
ResumeItemGridCell(item: item)
|
||||
if(isLoading == true) {
|
||||
ProgressView()
|
||||
}
|
||||
if(!items.isEmpty) {
|
||||
VStack {
|
||||
Spacer().frame(height: 6)
|
||||
TextField("Search", text: $searchQuery)
|
||||
.padding(.horizontal, 10)
|
||||
.foregroundColor(Color.secondary)
|
||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
ScrollView(.vertical) {
|
||||
LazyVGrid(columns: tracks) {
|
||||
ForEach(items, id: \.id) { item in
|
||||
NavigationLink(destination: ItemView(item: item)) {
|
||||
VStack(alignment: .leading) {
|
||||
LazyImage(source: item.getPrimaryImage(baseURL: globalData.server.baseURI!, maxWidth: 100))
|
||||
.placeholderAndFailure {
|
||||
Image(uiImage: UIImage(blurHash: item.getPrimaryImageBlurHash(),
|
||||
size: CGSize(width: 32, height: 32))!)
|
||||
.resizable()
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
Text(item.name!)
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
Text(String(item.productionYear!))
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
}.frame(width: 100)
|
||||
}
|
||||
}
|
||||
}.onChange(of: orientationInfo.orientation) { _ in
|
||||
recalcTracks()
|
||||
}
|
||||
}.onChange(of: isPortrait) { _ in
|
||||
recalcTracks()
|
||||
}
|
||||
}
|
||||
}
|
||||
if viewModel.isLoading {
|
||||
ProgressView()
|
||||
} else if viewModel.items.isEmpty {
|
||||
Text("Empty Response")
|
||||
} else {
|
||||
Text("No results found :(")
|
||||
}
|
||||
}
|
||||
.onAppear(perform: onAppear)
|
||||
.navigationBarTitle("Search", displayMode: .inline)
|
||||
}
|
||||
}
|
||||
|
||||
struct ResumeItemGridCell: View {
|
||||
@EnvironmentObject
|
||||
var globalData: GlobalData
|
||||
|
||||
var item: ResumeItem
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
if item.Type == "Movie" {
|
||||
LazyImage(source: URL(string: "\(globalData.server.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)"))
|
||||
.placeholderAndFailure {
|
||||
Image(uiImage: UIImage(blurHash: item
|
||||
.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item
|
||||
.BlurHash,
|
||||
size: CGSize(width: 32, height: 32))!)
|
||||
.resizable()
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
} else {
|
||||
LazyImage(source: URL(string: "\(globalData.server.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?fillWidth=300&fillHeight=450&quality=90&tag=\(item.Image)"))
|
||||
.placeholderAndFailure {
|
||||
Image(uiImage: UIImage(blurHash: item
|
||||
.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item
|
||||
.BlurHash,
|
||||
size: CGSize(width: 32, height: 32))!)
|
||||
.resizable()
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10).overlay(ZStack {
|
||||
if item.ItemBadge == 0 {
|
||||
Image(systemName: "checkmark")
|
||||
.font(.caption)
|
||||
.padding(3)
|
||||
.foregroundColor(.white)
|
||||
} else {
|
||||
Text("\(String(item.ItemBadge ?? 0))")
|
||||
.font(.caption)
|
||||
.padding(3)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
}.background(Color.black)
|
||||
.opacity(0.8)
|
||||
.cornerRadius(10.0)
|
||||
.padding(3), alignment: .topTrailing)
|
||||
}
|
||||
Text(item.Name)
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
Text(String(item.ProductionYear))
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
}.frame(width: 100)
|
||||
.onChange(of: searchQuery) { query in
|
||||
requestSearch(query: query)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,46 +7,69 @@
|
|||
|
||||
import SwiftUI
|
||||
import NukeUI
|
||||
import JellyfinAPI
|
||||
|
||||
struct LibraryView: View {
|
||||
@Environment(\.managedObjectContext)
|
||||
private var viewContext
|
||||
@EnvironmentObject
|
||||
var globalData: GlobalData
|
||||
@StateObject
|
||||
var viewModel: LibraryViewModel
|
||||
|
||||
@State
|
||||
private var showFiltersPopover: Bool = false
|
||||
@State
|
||||
private var showingSearchView: Bool = false
|
||||
|
||||
private var title: String
|
||||
|
||||
@State
|
||||
private var tracks: [GridItem] = []
|
||||
|
||||
init(viewModel: LibraryViewModel, title: String) {
|
||||
_viewModel = StateObject(wrappedValue: viewModel)
|
||||
@EnvironmentObject var globalData: GlobalData
|
||||
@EnvironmentObject var orientationInfo: OrientationInfo
|
||||
|
||||
@State private var items: [BaseItemDto] = []
|
||||
@State private var isLoading: Bool = false
|
||||
|
||||
var usingParentID: String = ""
|
||||
var title: String = ""
|
||||
var filters: [ItemFilter] = []
|
||||
var personId: String = ""
|
||||
var genre: String = ""
|
||||
var studio: String = ""
|
||||
|
||||
init(usingParentID: String, title: String) {
|
||||
self.usingParentID = usingParentID
|
||||
self.title = title
|
||||
}
|
||||
|
||||
|
||||
init(usingParentID: String, title: String, filters: [ItemFilter]) {
|
||||
self.usingParentID = usingParentID
|
||||
self.title = title
|
||||
self.filters = filters
|
||||
}
|
||||
|
||||
init(withPerson: BaseItemPerson) {
|
||||
self.usingParentID = ""
|
||||
self.title = withPerson.name ?? ""
|
||||
self.personId = withPerson.id!
|
||||
}
|
||||
|
||||
init(withGenre: NameGuidPair) {
|
||||
self.usingParentID = ""
|
||||
self.title = withGenre.name ?? ""
|
||||
self.genre = withGenre.id ?? ""
|
||||
}
|
||||
|
||||
init(withStudio: NameGuidPair) {
|
||||
self.usingParentID = ""
|
||||
self.title = withStudio.name ?? ""
|
||||
self.studio = withStudio.id ?? ""
|
||||
}
|
||||
|
||||
func onAppear() {
|
||||
guard viewModel.globalData != globalData else { return }
|
||||
recalcTracks()
|
||||
viewModel.globalData = globalData
|
||||
isLoading = true
|
||||
items = []
|
||||
|
||||
ItemsAPI.getItemsByUserId(userId: globalData.user.user_id!, limit: 100, recursive: true, searchTerm: nil, sortOrder: [.ascending], fields: [.parentId,.primaryImageAspectRatio,.basicSyncInfo], includeItemTypes: ["Movie","Series"], filters: filters, enableUserData: true, personIds: (personId == "" ? nil : [personId]), studioIds: (studio == "" ? nil : [studio]), genreIds: (genre == "" ? nil : [genre]), enableImages: true)
|
||||
.sink(receiveCompletion: { completion in
|
||||
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
|
||||
isLoading = false
|
||||
}, receiveValue: { response in
|
||||
items = response.items ?? []
|
||||
isLoading = false
|
||||
})
|
||||
.store(in: &globalData.pendingAPIRequests)
|
||||
}
|
||||
|
||||
@Environment(\.verticalSizeClass)
|
||||
var verticalSizeClass: UserInterfaceSizeClass?
|
||||
@Environment(\.horizontalSizeClass)
|
||||
var horizontalSizeClass: UserInterfaceSizeClass?
|
||||
|
||||
var isPortrait: Bool {
|
||||
let result = verticalSizeClass == .regular && horizontalSizeClass == .compact
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
//MARK: tracks for grid
|
||||
@State private var tracks: [GridItem] = []
|
||||
func recalcTracks() {
|
||||
let trkCnt = Int(floor(UIScreen.main.bounds.size.width / 125))
|
||||
_tracks.wrappedValue = []
|
||||
|
@ -57,136 +80,50 @@ struct LibraryView: View {
|
|||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
ScrollView(.vertical) {
|
||||
Spacer().frame(height: 16)
|
||||
LazyVGrid(columns: tracks) {
|
||||
ForEach(viewModel.items, id: \.Id) { item in
|
||||
NavigationLink(destination: ItemView(item: item)) {
|
||||
ItemGridView(item: item)
|
||||
if(isLoading == true) {
|
||||
ProgressView()
|
||||
} else {
|
||||
if(!items.isEmpty) {
|
||||
VStack {
|
||||
ScrollView(.vertical) {
|
||||
Spacer().frame(height: 16)
|
||||
LazyVGrid(columns: tracks) {
|
||||
ForEach(items, id: \.id) { item in
|
||||
NavigationLink(destination: ItemView(item: item)) {
|
||||
VStack(alignment: .leading) {
|
||||
LazyImage(source: item.getPrimaryImage(baseURL: globalData.server.baseURI!, maxWidth: 100))
|
||||
.placeholderAndFailure {
|
||||
Image(uiImage: UIImage(blurHash: item.getPrimaryImageBlurHash(),
|
||||
size: CGSize(width: 32, height: 32))!)
|
||||
.resizable()
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
Text(item.name ?? "")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
Text(String(item.productionYear ?? 0))
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
}.frame(width: 100)
|
||||
}
|
||||
}
|
||||
}.onChange(of: orientationInfo.orientation) { _ in
|
||||
recalcTracks()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text("No results found :(")
|
||||
}
|
||||
HStack() {
|
||||
Spacer()
|
||||
Button {
|
||||
viewModel.requestPreviousPage()
|
||||
} label: {
|
||||
Image(systemName: "chevron.left").font(.system(size: 25))
|
||||
}.disabled(viewModel.isHiddenPreviousButton)
|
||||
Text("\(viewModel.page) of \(viewModel.totalPages)")
|
||||
Button {
|
||||
viewModel.requestNextPage()
|
||||
} label: {
|
||||
Image(systemName: "chevron.right").font(.system(size: 25))
|
||||
}.disabled(viewModel.isHiddenNextButton)
|
||||
Spacer()
|
||||
}
|
||||
Spacer().frame(height: 16)
|
||||
}
|
||||
.onChange(of: isPortrait) { _ in
|
||||
recalcTracks()
|
||||
}
|
||||
if viewModel.isLoading {
|
||||
ProgressView()
|
||||
} else if viewModel.items.isEmpty {
|
||||
Text("Empty Response")
|
||||
}
|
||||
}
|
||||
.onAppear(perform: onAppear)
|
||||
.navigationTitle(title)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
if !viewModel.isHiddenPreviousButton {
|
||||
Button {
|
||||
viewModel.requestPreviousPage()
|
||||
} label: {
|
||||
Image(systemName: "chevron.left")
|
||||
}
|
||||
}
|
||||
if !viewModel.isHiddenNextButton {
|
||||
Button {
|
||||
viewModel.requestNextPage()
|
||||
} label: {
|
||||
Image(systemName: "chevron.right")
|
||||
}
|
||||
}
|
||||
NavigationLink(destination: LazyView { LibrarySearchView(viewModel: .init(filter: viewModel.filter)) }) {
|
||||
Image(systemName: "magnifyingglass")
|
||||
}
|
||||
Button {
|
||||
showFiltersPopover = true
|
||||
} label: {
|
||||
Image(systemName: "line.horizontal.3.decrease")
|
||||
}
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: self.$showFiltersPopover) {
|
||||
LibraryFilterView(library: viewModel.filter.parentID ?? "", filter: $viewModel.filter)
|
||||
.environmentObject(self.globalData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension LibraryView {
|
||||
struct ItemGridView: View {
|
||||
@EnvironmentObject
|
||||
var globalData: GlobalData
|
||||
var item: ResumeItem
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
if item.Type == "Movie" {
|
||||
LazyImage(source: URL(string: "\(globalData.server.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)"))
|
||||
.placeholderAndFailure {
|
||||
Image(uiImage: UIImage(blurHash: item
|
||||
.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item
|
||||
.BlurHash,
|
||||
size: CGSize(width: 16, height: 16))!)
|
||||
.resizable()
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
} else {
|
||||
LazyImage(source: URL(string: "\(globalData.server.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=80&tag=\(item.Image)"))
|
||||
.placeholderAndFailure {
|
||||
Image(uiImage: UIImage(blurHash: item
|
||||
.BlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item
|
||||
.BlurHash,
|
||||
size: CGSize(width: 16, height: 16))!)
|
||||
.resizable()
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10).overlay(ZStack {
|
||||
if item.ItemBadge == 0 {
|
||||
Image(systemName: "checkmark")
|
||||
.font(.caption)
|
||||
.padding(3)
|
||||
.foregroundColor(.white)
|
||||
} else {
|
||||
Text("\(String(item.ItemBadge ?? 0))")
|
||||
.font(.caption)
|
||||
.padding(3)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
}.background(Color.black)
|
||||
.opacity(0.8)
|
||||
.cornerRadius(10.0)
|
||||
.padding(3), alignment: .topTrailing)
|
||||
}
|
||||
Text(item.Name)
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
Text(String(item.ProductionYear))
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
}.frame(width: 100)
|
||||
}
|
||||
.navigationBarTitle(title, displayMode: .inline)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
/* JellyfinPlayer/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
|
||||
|
||||
struct UserSettings: Decodable {
|
||||
var LocalMaxBitrate: Int;
|
||||
var RemoteMaxBitrate: Int;
|
||||
var AutoSelectSubtitles: Bool;
|
||||
var AutoSelectSubtitlesLangcode: String;
|
||||
var SubtitlePositionOffset: Int;
|
||||
var SubtitleFontName: String;
|
||||
}
|
||||
|
||||
struct Bitrates: Codable, Hashable {
|
||||
public var name: String
|
||||
public var value: Int
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -41,7 +41,7 @@ struct NextUpView: View {
|
|||
LazyHStack() {
|
||||
Spacer().frame(width:16)
|
||||
ForEach(items, id: \.id) { item in
|
||||
NavigationLink(destination: EmptyView()) {
|
||||
NavigationLink(destination: ItemView(item: item)) {
|
||||
VStack(alignment: .leading) {
|
||||
LazyImage(source: item.getSeriesPrimaryImage(baseURL: globalData.server.baseURI!, maxWidth: 100))
|
||||
.placeholderAndFailure {
|
||||
|
|
|
@ -7,34 +7,22 @@
|
|||
|
||||
import NukeUI
|
||||
import SwiftUI
|
||||
import SwiftyJSON
|
||||
import SwiftyRequest
|
||||
import JellyfinAPI
|
||||
|
||||
struct SeasonItemView: View {
|
||||
@EnvironmentObject
|
||||
var globalData: GlobalData
|
||||
@EnvironmentObject
|
||||
var orientationInfo: OrientationInfo
|
||||
@State
|
||||
private var isLoading: Bool = true
|
||||
@EnvironmentObject var globalData: GlobalData
|
||||
@EnvironmentObject var orientationInfo: OrientationInfo
|
||||
@State private var isLoading: Bool = true
|
||||
|
||||
var item: ResumeItem
|
||||
@State
|
||||
var fullItem = DetailItem()
|
||||
@State
|
||||
var episodes: [DetailItem] = []
|
||||
@State
|
||||
private var progressString: String = ""
|
||||
@State
|
||||
private var hasAppearedOnce: Bool = false
|
||||
|
||||
@State private var episodes: [BaseItemDto] = []
|
||||
|
||||
init(item: ResumeItem) {
|
||||
self.item = item
|
||||
}
|
||||
|
||||
func loadData() {
|
||||
if hasAppearedOnce {
|
||||
return
|
||||
}
|
||||
func onAppear() {
|
||||
let url = "/Users/\(globalData.user.user_id ?? "")/Items/\(item.Id)"
|
||||
|
||||
let request = RestRequest(method: .get, url: (globalData.server.baseURI ?? "") + url)
|
||||
|
@ -483,7 +471,7 @@ struct SeasonItemView: View {
|
|||
LoadingView(isShowing: $isLoading) {
|
||||
innerBody
|
||||
}
|
||||
.onAppear(perform: loadData)
|
||||
.onAppear(perform: onAppear)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationTitle("\(item.Name) - \(item.SeriesName ?? "")")
|
||||
}
|
||||
|
|
|
@ -121,3 +121,36 @@ struct SettingsView: View {
|
|||
}.onAppear(perform: onAppear)
|
||||
}
|
||||
}
|
||||
|
||||
struct UserSettings: Decodable {
|
||||
var LocalMaxBitrate: Int;
|
||||
var RemoteMaxBitrate: Int;
|
||||
var AutoSelectSubtitles: Bool;
|
||||
var AutoSelectSubtitlesLangcode: String;
|
||||
var SubtitlePositionOffset: Int;
|
||||
var SubtitleFontName: String;
|
||||
}
|
||||
|
||||
struct Bitrates: Codable, Hashable {
|
||||
public var name: String
|
||||
public var value: Int
|
||||
}
|
||||
|
||||
final class SettingsViewModel: ObservableObject {
|
||||
var bitrates: [Bitrates] = []
|
||||
|
||||
init() {
|
||||
let url = Bundle.main.url(forResource: "bitrates", withExtension: "json")!
|
||||
|
||||
do {
|
||||
let jsonData = try Data(contentsOf: url, options: .mappedIfSafe)
|
||||
do {
|
||||
self.bitrates = try JSONDecoder().decode([Bitrates].self, from: jsonData)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -291,6 +291,24 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
|||
self.sendPlayReport()
|
||||
playbackItem = item;
|
||||
}
|
||||
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
mediaPlayer.media = VLCMedia(url: playbackItem.videoUrl)
|
||||
mediaPlayer.play()
|
||||
mediaPlayer.jumpForward(Int32(manifest.userData?.playbackPositionTicks ?? 0/10000000))
|
||||
mediaPlayer.pause()
|
||||
subtitleTrackArray.forEach() { sub in
|
||||
if(sub.id != -1 && sub.delivery == .external && sub.codec != "subrip") {
|
||||
print("adding subs for id: \(sub.id) w/ url: \(sub.url)")
|
||||
mediaPlayer.addPlaybackSlave(sub.url, type: .subtitle, enforce: false)
|
||||
}
|
||||
}
|
||||
delegate?.showLoadingView(self)
|
||||
while(mediaPlayer.numberOfSubtitlesTracks != subtitleTrackArray.count - 1) {}
|
||||
mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack;
|
||||
mediaPlayer.pause()
|
||||
mediaPlayer.play()
|
||||
}
|
||||
})
|
||||
.store(in: &globalData.pendingAPIRequests)
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
/* JellyfinPlayer/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 Combine
|
||||
import CombineMoya
|
||||
import Foundation
|
||||
import Moya
|
||||
import SwiftyJSON
|
||||
|
||||
final class LibraryListViewModel: ObservableObject {
|
||||
fileprivate var provider =
|
||||
MoyaProvider<JellyfinAPIOld>()
|
||||
|
||||
@Published
|
||||
var libraryIDs = [String]()
|
||||
@Published
|
||||
var libraryNames = [String: String]()
|
||||
|
||||
fileprivate var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(libraryNames: [String: String], libraryIDs: [String]) {
|
||||
self.libraryIDs = libraryIDs
|
||||
self.libraryNames = libraryNames
|
||||
refresh()
|
||||
}
|
||||
|
||||
func refresh() {
|
||||
libraryIDs.append("favorites")
|
||||
libraryNames["favorites"] = "Favorites"
|
||||
|
||||
libraryIDs.append("genres")
|
||||
libraryNames["genres"] = "Genres - WIP"
|
||||
}
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
/* JellyfinPlayer/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 Combine
|
||||
import CombineMoya
|
||||
import Foundation
|
||||
import Moya
|
||||
import SwiftyJSON
|
||||
|
||||
final class LibrarySearchViewModel: ObservableObject {
|
||||
fileprivate var provider = MoyaProvider<JellyfinAPIOld>()
|
||||
|
||||
var filter: Filter
|
||||
|
||||
@Published
|
||||
var items = [ResumeItem]()
|
||||
|
||||
@Published
|
||||
var searchQuery = ""
|
||||
@Published
|
||||
var isLoading: Bool = true
|
||||
|
||||
var page = 1
|
||||
|
||||
var globalData = GlobalData() {
|
||||
didSet {
|
||||
injectEnvironmentData()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(filter: Filter) {
|
||||
self.filter = filter
|
||||
}
|
||||
|
||||
fileprivate func injectEnvironmentData() {
|
||||
cancellables.removeAll()
|
||||
|
||||
$searchQuery
|
||||
.debounce(for: 0.25, scheduler: DispatchQueue.main)
|
||||
.sink(receiveValue: requestSearch(query:))
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
fileprivate func requestSearch(query: String) {
|
||||
isLoading = true
|
||||
provider.requestPublisher(.search(globalData: globalData, filter: filter, searchQuery: query, page: page))
|
||||
// .map(ResumeItem.self) TO DO
|
||||
.print()
|
||||
.sink(receiveCompletion: { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.isLoading = false
|
||||
}, receiveValue: { [weak self] response in
|
||||
guard let self = self else { return }
|
||||
let body = response.data
|
||||
var innerItems = [ResumeItem]()
|
||||
do {
|
||||
let json = try JSON(data: body)
|
||||
for (_, item): (String, JSON) in json["Items"] {
|
||||
// Do something you want
|
||||
var itemObj = ResumeItem()
|
||||
itemObj.Type = item["Type"].string ?? ""
|
||||
if itemObj.Type == "Series" {
|
||||
itemObj.ItemBadge = item["UserData"]["UnplayedItemCount"].int ?? 0
|
||||
itemObj.Image = item["ImageTags"]["Primary"].string ?? ""
|
||||
itemObj.ImageType = "Primary"
|
||||
itemObj.BlurHash = item["ImageBlurHashes"]["Primary"][itemObj.Image].string ?? ""
|
||||
itemObj.Name = item["Name"].string ?? ""
|
||||
itemObj.Type = item["Type"].string ?? ""
|
||||
itemObj.IndexNumber = nil
|
||||
itemObj.Id = item["Id"].string ?? ""
|
||||
itemObj.ParentIndexNumber = nil
|
||||
itemObj.SeasonId = nil
|
||||
itemObj.SeriesId = nil
|
||||
itemObj.SeriesName = nil
|
||||
itemObj.ProductionYear = item["ProductionYear"].int ?? 0
|
||||
} else {
|
||||
itemObj.ProductionYear = item["ProductionYear"].int ?? 0
|
||||
itemObj.Image = item["ImageTags"]["Primary"].string ?? ""
|
||||
itemObj.ImageType = "Primary"
|
||||
itemObj.BlurHash = item["ImageBlurHashes"]["Primary"][itemObj.Image].string ?? ""
|
||||
itemObj.Name = item["Name"].string ?? ""
|
||||
itemObj.Type = item["Type"].string ?? ""
|
||||
itemObj.IndexNumber = item["IndexNumber"].int ?? nil
|
||||
itemObj.Id = item["Id"].string ?? ""
|
||||
itemObj.ParentIndexNumber = item["ParentIndexNumber"].int ?? nil
|
||||
itemObj.SeasonId = item["SeasonId"].string ?? nil
|
||||
itemObj.SeriesId = item["SeriesId"].string ?? nil
|
||||
itemObj.SeriesName = item["SeriesName"].string ?? nil
|
||||
}
|
||||
itemObj.Watched = item["UserData"]["Played"].bool ?? false
|
||||
|
||||
innerItems.append(itemObj)
|
||||
}
|
||||
} catch {}
|
||||
self.items = innerItems
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
|
@ -1,157 +0,0 @@
|
|||
/* JellyfinPlayer/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 Combine
|
||||
import CombineMoya
|
||||
import Foundation
|
||||
import Moya
|
||||
import SwiftyJSON
|
||||
|
||||
final class LibraryViewModel: ObservableObject {
|
||||
fileprivate var provider =
|
||||
MoyaProvider<JellyfinAPIOld>()
|
||||
|
||||
@Published
|
||||
var filter: Filter
|
||||
|
||||
@Published
|
||||
var items = [ResumeItem]()
|
||||
|
||||
@Published
|
||||
var isLoading: Bool = true
|
||||
|
||||
@Published
|
||||
var isHiddenPreviousButton = true
|
||||
@Published
|
||||
var isHiddenNextButton = true
|
||||
|
||||
@Published
|
||||
var totalPages = 1
|
||||
|
||||
@Published
|
||||
var page = 1
|
||||
|
||||
var globalData = GlobalData() {
|
||||
didSet {
|
||||
injectEnvironmentData()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(filter: Filter = Filter()) {
|
||||
self.filter = filter
|
||||
}
|
||||
|
||||
fileprivate func injectEnvironmentData() {
|
||||
cancellables.removeAll()
|
||||
|
||||
$filter
|
||||
.sink(receiveValue: requestInitItems(_:))
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func requestNextPage() {
|
||||
page += 1
|
||||
requestItems(filter)
|
||||
}
|
||||
|
||||
func requestPreviousPage() {
|
||||
page -= 1
|
||||
requestItems(filter)
|
||||
}
|
||||
|
||||
func requestInitItems(_ filter: Filter) {
|
||||
page = 1
|
||||
requestItems(filter)
|
||||
}
|
||||
|
||||
fileprivate func requestItems(_ filter: Filter) {
|
||||
isLoading = true
|
||||
provider.requestPublisher(.items(globalData: globalData, filter: filter, page: page))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.map { response -> ([ResumeItem], Int) in
|
||||
let body = response.data
|
||||
var totalCount = 0
|
||||
var innerItems = [ResumeItem]()
|
||||
do {
|
||||
let json = try JSON(data: body)
|
||||
totalCount = json["TotalRecordCount"].int ?? 0
|
||||
for (_, item): (String, JSON) in json["Items"] {
|
||||
// Do something you want
|
||||
var itemObj = ResumeItem()
|
||||
itemObj.Type = item["Type"].string ?? ""
|
||||
if itemObj.Type == "Series" {
|
||||
itemObj.ItemBadge = item["UserData"]["UnplayedItemCount"].int ?? 0
|
||||
itemObj.Image = item["ImageTags"]["Primary"].string ?? ""
|
||||
itemObj.ImageType = "Primary"
|
||||
itemObj.BlurHash = item["ImageBlurHashes"]["Primary"][itemObj.Image].string ?? ""
|
||||
itemObj.Name = item["Name"].string ?? ""
|
||||
itemObj.Type = item["Type"].string ?? ""
|
||||
itemObj.IndexNumber = nil
|
||||
itemObj.Id = item["Id"].string ?? ""
|
||||
itemObj.ParentIndexNumber = nil
|
||||
itemObj.SeasonId = nil
|
||||
itemObj.SeriesId = nil
|
||||
itemObj.SeriesName = nil
|
||||
itemObj.ProductionYear = item["ProductionYear"].int ?? 0
|
||||
} else {
|
||||
itemObj.ProductionYear = item["ProductionYear"].int ?? 0
|
||||
itemObj.Image = item["ImageTags"]["Primary"].string ?? ""
|
||||
itemObj.ImageType = "Primary"
|
||||
itemObj.BlurHash = item["ImageBlurHashes"]["Primary"][itemObj.Image].string ?? ""
|
||||
itemObj.Name = item["Name"].string ?? ""
|
||||
itemObj.Type = item["Type"].string ?? ""
|
||||
itemObj.IndexNumber = item["IndexNumber"].int ?? nil
|
||||
itemObj.Id = item["Id"].string ?? ""
|
||||
itemObj.ParentIndexNumber = item["ParentIndexNumber"].int ?? nil
|
||||
itemObj.SeasonId = item["SeasonId"].string ?? nil
|
||||
itemObj.SeriesId = item["SeriesId"].string ?? nil
|
||||
itemObj.SeriesName = item["SeriesName"].string ?? nil
|
||||
}
|
||||
itemObj.Watched = item["UserData"]["Played"].bool ?? false
|
||||
|
||||
innerItems.append(itemObj)
|
||||
}
|
||||
} catch {}
|
||||
return (innerItems, totalCount)
|
||||
}
|
||||
.sink(receiveCompletion: { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.isLoading = false
|
||||
}, receiveValue: { [weak self] items, count in
|
||||
guard let self = self else { return }
|
||||
print(count)
|
||||
print(items.count)
|
||||
print(self.page)
|
||||
|
||||
self.totalPages = Int(Float(Double(count)/100.0).rounded(.up))
|
||||
|
||||
if(count > 100) {
|
||||
self.isHiddenPreviousButton = true
|
||||
self.isHiddenNextButton = true
|
||||
|
||||
if(self.page > 1) {
|
||||
self.isHiddenPreviousButton = false
|
||||
}
|
||||
|
||||
if(self.page * 100 < count) {
|
||||
self.isHiddenNextButton = false
|
||||
}
|
||||
} else {
|
||||
self.isHiddenPreviousButton = true
|
||||
self.isHiddenNextButton = true
|
||||
}
|
||||
|
||||
print(self.isHiddenPreviousButton)
|
||||
print(self.isHiddenNextButton)
|
||||
|
||||
self.items = items
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
/* JellyfinPlayer/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
|
||||
|
||||
final class SettingsViewModel: ObservableObject {
|
||||
var bitrates: [Bitrates] = []
|
||||
|
||||
init() {
|
||||
let url = Bundle.main.url(forResource: "bitrates", withExtension: "json")!
|
||||
|
||||
do {
|
||||
let jsonData = try Data(contentsOf: url, options: .mappedIfSafe)
|
||||
do {
|
||||
self.bitrates = try JSONDecoder().decode([Bitrates].self, from: jsonData)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,8 @@ import UIKit
|
|||
//001fC^ = dark grey plain blurhash
|
||||
|
||||
extension BaseItemDto {
|
||||
|
||||
//MARK: Images
|
||||
func getSeriesPrimaryImageBlurHash() -> String {
|
||||
let rawImgURL = self.getSeriesPrimaryImage(baseURL: "", maxWidth: 1).absoluteString;
|
||||
let imgTag = rawImgURL.components(separatedBy: "&tag=")[1];
|
||||
|
@ -78,4 +80,50 @@ extension BaseItemDto {
|
|||
let urlString = "\(baseURL)/Items/\(self.id ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=85&tag=\(imageTag)"
|
||||
return URL(string: urlString)!
|
||||
}
|
||||
|
||||
//MARK: Calculations
|
||||
func getItemRuntime() -> String {
|
||||
let seconds: Int = Int(self.runTimeTicks!) / 10_000_000
|
||||
let hours = (seconds / 3600)
|
||||
let minutes = ((seconds - (hours * 3600)) / 60)
|
||||
if hours != 0 {
|
||||
return "\(hours):\(String(minutes).leftPad(toWidth: 2, withString: "0"))"
|
||||
} else {
|
||||
return "\(String(minutes).leftPad(toWidth: 2, withString: "0"))m"
|
||||
}
|
||||
}
|
||||
|
||||
func getItemProgressString() -> String {
|
||||
if(self.userData?.playbackPositionTicks == nil || self.userData?.playbackPositionTicks == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
let remainingSecs = Int(self.runTimeTicks! - (self.userData?.playbackPositionTicks!)!) / 10_000_000
|
||||
let proghours = Int(remainingSecs / 3600)
|
||||
let progminutes = Int((Int(remainingSecs) - (proghours * 3600)) / 60)
|
||||
if proghours != 0 {
|
||||
return "\(proghours):\(String(progminutes).leftPad(toWidth: 2, withString: "0"))"
|
||||
} else {
|
||||
return "\(String(progminutes).leftPad(toWidth: 2, withString: "0"))m"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension BaseItemPerson {
|
||||
func getImage(baseURL: String, maxWidth: Int) -> URL {
|
||||
let imageType = "Primary";
|
||||
let imageTag = self.primaryImageTag ?? ""
|
||||
|
||||
let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
|
||||
|
||||
let urlString = "\(baseURL)/Items/\(self.id ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=85&tag=\(imageTag)"
|
||||
return URL(string: urlString)!
|
||||
}
|
||||
|
||||
func getBlurHash() -> String {
|
||||
let rawImgURL = self.getImage(baseURL: "", maxWidth: 1).absoluteString;
|
||||
let imgTag = rawImgURL.components(separatedBy: "&tag=")[1];
|
||||
|
||||
return self.imageBlurHashes?.primary?[imgTag] ?? "001fC^";
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue