more to mvc & gen. client

This commit is contained in:
Aiden Vigue 2021-06-08 17:04:33 -07:00
parent 85d79a4774
commit 93ef16a46d
11 changed files with 174 additions and 326 deletions

View File

@ -50,6 +50,8 @@
53A089D0264DA9DA00D57806 /* MovieItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A089CF264DA9DA00D57806 /* MovieItemView.swift */; }; 53A089D0264DA9DA00D57806 /* MovieItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A089CF264DA9DA00D57806 /* MovieItemView.swift */; };
53A431BD266B0FF20016769F /* JellyfinAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 53A431BC266B0FF20016769F /* JellyfinAPI */; }; 53A431BD266B0FF20016769F /* JellyfinAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 53A431BC266B0FF20016769F /* JellyfinAPI */; };
53A431BF266B0FFE0016769F /* JellyfinAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 53A431BE266B0FFE0016769F /* JellyfinAPI */; }; 53A431BF266B0FFE0016769F /* JellyfinAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 53A431BE266B0FFE0016769F /* JellyfinAPI */; };
53AD124D267029D60094A276 /* SeriesItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53987CA526572F0700E7EA70 /* SeriesItemView.swift */; };
53AD124E26702B8A0094A276 /* SeasonItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53987CA326572C1300E7EA70 /* SeasonItemView.swift */; };
53C4404E266C75C70049424C /* HandleAPIRequestCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53C4404D266C75C70049424C /* HandleAPIRequestCompletion.swift */; }; 53C4404E266C75C70049424C /* HandleAPIRequestCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53C4404D266C75C70049424C /* HandleAPIRequestCompletion.swift */; };
53C4404F266C75C70049424C /* HandleAPIRequestCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53C4404D266C75C70049424C /* HandleAPIRequestCompletion.swift */; }; 53C4404F266C75C70049424C /* HandleAPIRequestCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53C4404D266C75C70049424C /* HandleAPIRequestCompletion.swift */; };
53D5E3DD264B47EE00BADDC8 /* MobileVLCKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */; }; 53D5E3DD264B47EE00BADDC8 /* MobileVLCKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */; };
@ -119,7 +121,7 @@
535BAE9E2649E569005FA86D /* ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemView.swift; sourceTree = "<group>"; }; 535BAE9E2649E569005FA86D /* ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemView.swift; sourceTree = "<group>"; };
535BAEA4264A151C005FA86D /* VideoPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayer.swift; sourceTree = "<group>"; }; 535BAEA4264A151C005FA86D /* VideoPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayer.swift; sourceTree = "<group>"; };
5364F454266CA0DC0026ECBA /* APIExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIExtensions.swift; sourceTree = "<group>"; }; 5364F454266CA0DC0026ECBA /* APIExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIExtensions.swift; sourceTree = "<group>"; };
5377CBF1263B596A003A4E83 /* JellyfinPlayer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JellyfinPlayer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5377CBF1263B596A003A4E83 /* JellyfinPlayer iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "JellyfinPlayer iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
5377CBF4263B596A003A4E83 /* JellyfinPlayerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinPlayerApp.swift; sourceTree = "<group>"; }; 5377CBF4263B596A003A4E83 /* JellyfinPlayerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinPlayerApp.swift; sourceTree = "<group>"; };
5377CBF6263B596A003A4E83 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; }; 5377CBF6263B596A003A4E83 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
5377CBF8263B596B003A4E83 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 5377CBF8263B596B003A4E83 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@ -136,6 +138,7 @@
53987CA72657424A00E7EA70 /* EpisodeItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeItemView.swift; sourceTree = "<group>"; }; 53987CA72657424A00E7EA70 /* EpisodeItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeItemView.swift; sourceTree = "<group>"; };
539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; }; 539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
53A089CF264DA9DA00D57806 /* MovieItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieItemView.swift; sourceTree = "<group>"; }; 53A089CF264DA9DA00D57806 /* MovieItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieItemView.swift; sourceTree = "<group>"; };
53AD124C2670278D0094A276 /* JellyfinPlayer.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = JellyfinPlayer.entitlements; sourceTree = "<group>"; };
53C4404D266C75C70049424C /* HandleAPIRequestCompletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandleAPIRequestCompletion.swift; sourceTree = "<group>"; }; 53C4404D266C75C70049424C /* HandleAPIRequestCompletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandleAPIRequestCompletion.swift; sourceTree = "<group>"; };
53D5E3DA264B460200BADDC8 /* Cartfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Cartfile; sourceTree = "<group>"; }; 53D5E3DA264B460200BADDC8 /* Cartfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Cartfile; sourceTree = "<group>"; };
53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MobileVLCKit.xcframework; path = Carthage/Build/MobileVLCKit.xcframework; sourceTree = "<group>"; }; 53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MobileVLCKit.xcframework; path = Carthage/Build/MobileVLCKit.xcframework; sourceTree = "<group>"; };
@ -236,7 +239,7 @@
5377CBF2263B596A003A4E83 /* Products */ = { 5377CBF2263B596A003A4E83 /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
5377CBF1263B596A003A4E83 /* JellyfinPlayer.app */, 5377CBF1263B596A003A4E83 /* JellyfinPlayer iOS.app */,
535870602669D21600D05A09 /* JellyfinPlayer tvOS.app */, 535870602669D21600D05A09 /* JellyfinPlayer tvOS.app */,
); );
name = Products; name = Products;
@ -245,6 +248,7 @@
5377CBF3263B596A003A4E83 /* JellyfinPlayer */ = { 5377CBF3263B596A003A4E83 /* JellyfinPlayer */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
53AD124C2670278D0094A276 /* JellyfinPlayer.entitlements */,
5377CBF8263B596B003A4E83 /* Assets.xcassets */, 5377CBF8263B596B003A4E83 /* Assets.xcassets */,
5338F74D263B61370014BF09 /* ConnectToServerView.swift */, 5338F74D263B61370014BF09 /* ConnectToServerView.swift */,
5377CBF6263B596A003A4E83 /* ContentView.swift */, 5377CBF6263B596A003A4E83 /* ContentView.swift */,
@ -342,9 +346,9 @@
productReference = 535870602669D21600D05A09 /* JellyfinPlayer tvOS.app */; productReference = 535870602669D21600D05A09 /* JellyfinPlayer tvOS.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
}; };
5377CBF0263B596A003A4E83 /* JellyfinPlayer */ = { 5377CBF0263B596A003A4E83 /* JellyfinPlayer iOS */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 5377CC1B263B596B003A4E83 /* Build configuration list for PBXNativeTarget "JellyfinPlayer" */; buildConfigurationList = 5377CC1B263B596B003A4E83 /* Build configuration list for PBXNativeTarget "JellyfinPlayer iOS" */;
buildPhases = ( buildPhases = (
5377CBED263B596A003A4E83 /* Sources */, 5377CBED263B596A003A4E83 /* Sources */,
5377CBEE263B596A003A4E83 /* Frameworks */, 5377CBEE263B596A003A4E83 /* Frameworks */,
@ -356,7 +360,7 @@
); );
dependencies = ( dependencies = (
); );
name = JellyfinPlayer; name = "JellyfinPlayer iOS";
packageProductDependencies = ( packageProductDependencies = (
5338F756263B7E2E0014BF09 /* KeychainSwift */, 5338F756263B7E2E0014BF09 /* KeychainSwift */,
53352570265EA0A0006CCA86 /* Introspect */, 53352570265EA0A0006CCA86 /* Introspect */,
@ -364,7 +368,7 @@
53A431BC266B0FF20016769F /* JellyfinAPI */, 53A431BC266B0FF20016769F /* JellyfinAPI */,
); );
productName = JellyfinPlayer; productName = JellyfinPlayer;
productReference = 5377CBF1263B596A003A4E83 /* JellyfinPlayer.app */; productReference = 5377CBF1263B596A003A4E83 /* JellyfinPlayer iOS.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
}; };
/* End PBXNativeTarget section */ /* End PBXNativeTarget section */
@ -406,7 +410,7 @@
projectDirPath = ""; projectDirPath = "";
projectRoot = ""; projectRoot = "";
targets = ( targets = (
5377CBF0263B596A003A4E83 /* JellyfinPlayer */, 5377CBF0263B596A003A4E83 /* JellyfinPlayer iOS */,
5358705F2669D21600D05A09 /* JellyfinPlayer tvOS */, 5358705F2669D21600D05A09 /* JellyfinPlayer tvOS */,
); );
}; };
@ -467,6 +471,7 @@
53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */, 53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */,
5377CBFE263B596B003A4E83 /* PersistenceController.swift in Sources */, 5377CBFE263B596B003A4E83 /* PersistenceController.swift in Sources */,
5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */, 5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */,
53AD124E26702B8A0094A276 /* SeasonItemView.swift in Sources */,
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */, 535BAE9F2649E569005FA86D /* ItemView.swift in Sources */,
6225FCCB2663841E00E067F6 /* ParallaxHeader.swift in Sources */, 6225FCCB2663841E00E067F6 /* ParallaxHeader.swift in Sources */,
53F8377D265FF67C00F456B3 /* VideoPlayerSettingsView.swift in Sources */, 53F8377D265FF67C00F456B3 /* VideoPlayerSettingsView.swift in Sources */,
@ -485,6 +490,7 @@
5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */, 5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */,
539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */, 539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */,
5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */, 5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */,
53AD124D267029D60094A276 /* SeriesItemView.swift in Sources */,
5377CBF5263B596A003A4E83 /* JellyfinPlayerApp.swift in Sources */, 5377CBF5263B596A003A4E83 /* JellyfinPlayerApp.swift in Sources */,
53EE24E6265060780068F029 /* LibrarySearchView.swift in Sources */, 53EE24E6265060780068F029 /* LibrarySearchView.swift in Sources */,
53892772263C8C6F0035E14B /* LoadingView.swift in Sources */, 53892772263C8C6F0035E14B /* LoadingView.swift in Sources */,
@ -513,7 +519,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.0; MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = me.vigue.swiftfin.tv; PRODUCT_BUNDLE_IDENTIFIER = me.vigue.jellyfin;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos; SDKROOT = appletvos;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -541,7 +547,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.0; MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = me.vigue.swiftfin.tv; PRODUCT_BUNDLE_IDENTIFIER = me.vigue.jellyfin;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos; SDKROOT = appletvos;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -749,7 +755,7 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
5377CC1B263B596B003A4E83 /* Build configuration list for PBXNativeTarget "JellyfinPlayer" */ = { 5377CC1B263B596B003A4E83 /* Build configuration list for PBXNativeTarget "JellyfinPlayer iOS" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
5377CC1C263B596B003A4E83 /* Debug */, 5377CC1C263B596B003A4E83 /* Debug */,

View File

@ -15,8 +15,8 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "5377CBF0263B596A003A4E83" BlueprintIdentifier = "5377CBF0263B596A003A4E83"
BuildableName = "JellyfinPlayer.app" BuildableName = "JellyfinPlayer iOS.app"
BlueprintName = "JellyfinPlayer" BlueprintName = "JellyfinPlayer iOS"
ReferencedContainer = "container:JellyfinPlayer.xcodeproj"> ReferencedContainer = "container:JellyfinPlayer.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildActionEntry> </BuildActionEntry>
@ -45,8 +45,8 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "5377CBF0263B596A003A4E83" BlueprintIdentifier = "5377CBF0263B596A003A4E83"
BuildableName = "JellyfinPlayer.app" BuildableName = "JellyfinPlayer iOS.app"
BlueprintName = "JellyfinPlayer" BlueprintName = "JellyfinPlayer iOS"
ReferencedContainer = "container:JellyfinPlayer.xcodeproj"> ReferencedContainer = "container:JellyfinPlayer.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
@ -62,8 +62,8 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "5377CBF0263B596A003A4E83" BlueprintIdentifier = "5377CBF0263B596A003A4E83"
BuildableName = "JellyfinPlayer.app" BuildableName = "JellyfinPlayer iOS.app"
BlueprintName = "JellyfinPlayer" BlueprintName = "JellyfinPlayer iOS"
ReferencedContainer = "container:JellyfinPlayer.xcodeproj"> ReferencedContainer = "container:JellyfinPlayer.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>

View File

@ -35,6 +35,8 @@ struct ContentView: View {
@State private var showSettingsPopover: Bool = false @State private var showSettingsPopover: Bool = false
@State private var viewDidLoad: Bool = false @State private var viewDidLoad: Bool = false
@State private var loadState: Int = 2 @State private var loadState: Int = 2
private var recentFilterSet: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.descending], sortBy: ["DateCreated"])
func startup() { func startup() {
if(viewDidLoad == true) { if(viewDidLoad == true) {
@ -158,7 +160,7 @@ struct ContentView: View {
Spacer() Spacer()
NavigationLink(destination: LazyView { NavigationLink(destination: LazyView {
LibraryView(usingParentID: library_id, LibraryView(usingParentID: library_id,
title: library_names[library_id] ?? "") title: library_names[library_id] ?? "", usingFilters: recentFilterSet)
}) { }) {
Text("See All").font(.subheadline).fontWeight(.bold) Text("See All").font(.subheadline).fontWeight(.bold)
} }

View File

@ -25,21 +25,31 @@ struct ItemView: View {
@StateObject private var videoPlayerItem: VideoPlayerItem = VideoPlayerItem() @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 videoIsLoading: Bool = false; //This variable is only changed by the underlying VLC view.
@State private var isLoading: Bool = false; @State private var isLoading: Bool = false;
@State private var viewDidLoad: Bool = false;
init(item: BaseItemDto) { init(item: BaseItemDto) {
self.item = item self.item = item
} }
func onAppear() { func onAppear() {
isLoading = true; if(viewDidLoad) {
UserLibraryAPI.getItem(userId: globalData.user.user_id!, itemId: item.id!) return
.sink(receiveCompletion: { completion in }
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
}, receiveValue: { response in if(item.type == "Movie" || item.type == "Episode") {
isLoading = false isLoading = true;
fullItem = response UserLibraryAPI.getItem(userId: globalData.user.user_id!, itemId: item.id!)
}) .sink(receiveCompletion: { completion in
.store(in: &globalData.pendingAPIRequests) HandleAPIRequestCompletion(globalData: globalData, completion: completion)
}, receiveValue: { response in
isLoading = false
viewDidLoad = true
fullItem = response
})
.store(in: &globalData.pendingAPIRequests)
} else {
viewDidLoad = true
}
} }
var body: some View { var body: some View {
@ -60,15 +70,14 @@ struct ItemView: View {
ProgressView() ProgressView()
} else { } else {
VStack { VStack {
if(fullItem.type == "Movie") { if(item.type == "Movie") {
MovieItemView(item: fullItem) MovieItemView(item: fullItem)
} else if(fullItem.type == "Season") { } else if(item.type == "Season") {
EmptyView() EmptyView()
//SeasonItemView(item: fullItem) SeasonItemView(item: item)
} else if(fullItem.type == "Series") { } else if(item.type == "Series") {
EmptyView() SeriesItemView(item: item)
//SeriesItemView(item: fullItem) } else if(item.type == "Episode") {
} else if(fullItem.type == "Episode") {
EmptyView() EmptyView()
//EpisodeItemView(item: fullItem) //EpisodeItemView(item: fullItem)
} else { } else {

View File

@ -6,5 +6,7 @@
<true/> <true/>
<key>com.apple.security.network.client</key> <key>com.apple.security.network.client</key>
<true/> <true/>
<key>com.apple.developer.coremedia.hls.low-latency</key>
<true/>
</dict> </dict>
</plist> </plist>

View File

@ -40,11 +40,11 @@ struct LatestMediaView: View {
LazyHStack() { LazyHStack() {
Spacer().frame(width:16) Spacer().frame(width:16)
ForEach(items, id: \.id) { item in ForEach(items, id: \.id) { item in
if(item.type == "Series" || item.type == "Movie") { if(item.type == "Series" || item.type == "Movie" || item.type == "Episode") {
NavigationLink(destination: ItemView(item: item)) { NavigationLink(destination: ItemView(item: item)) {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Spacer().frame(height:10) Spacer().frame(height:10)
LazyImage(source: item.getPrimaryImage(baseURL: globalData.server.baseURI!, maxWidth: 100)) LazyImage(source: (item.type != "Episode" ? item.getPrimaryImage(baseURL: globalData.server.baseURI!, maxWidth: 100) : item.getSeriesPrimaryImage(baseURL: globalData.server.baseURI!, maxWidth: 100)))
.placeholderAndFailure { .placeholderAndFailure {
Image(uiImage: UIImage(blurHash: item.getPrimaryImageBlurHash(), size: CGSize(width: 16, height: 20))!) Image(uiImage: UIImage(blurHash: item.getPrimaryImageBlurHash(), size: CGSize(width: 16, height: 20))!)
.resizable() .resizable()
@ -54,11 +54,24 @@ struct LatestMediaView: View {
.frame(width: 100, height: 150) .frame(width: 100, height: 150)
.cornerRadius(10) .cornerRadius(10)
Spacer().frame(height:5) Spacer().frame(height:5)
Text(item.seasonName ?? item.name ?? "") Text(item.seriesName ?? item.name ?? "")
.font(.caption) .font(.caption)
.fontWeight(.semibold) .fontWeight(.semibold)
.foregroundColor(.primary) .foregroundColor(.primary)
.lineLimit(1) .lineLimit(1)
if(item.type == "Episode") {
Text("S\(String(item.parentIndexNumber ?? 0)):E\(String(item.indexNumber ?? 0))")
.font(.caption)
.fontWeight(.semibold)
.foregroundColor(.secondary)
.lineLimit(1)
} else {
Text(String(item.productionYear ?? 0))
.font(.caption)
.fontWeight(.semibold)
.foregroundColor(.secondary)
.lineLimit(1)
}
}.frame(width: 100) }.frame(width: 100)
Spacer().frame(width: 15) Spacer().frame(width: 15)
} }

View File

@ -14,6 +14,7 @@ struct LibraryListView: View {
@State var library_ids: [String] = ["favorites", "genres"] @State var library_ids: [String] = ["favorites", "genres"]
@State var library_names: [String: String] = ["favorites": "Favorites", "genres": "Genres"] @State var library_names: [String: String] = ["favorites": "Favorites", "genres": "Genres"]
var libraries: [String: String] = [:] //input libraries var libraries: [String: String] = [:] //input libraries
var withFavorites: LibraryFilters = LibraryFilters(filters: [.isFavorite], sortOrder: [.descending], sortBy: ["SortName"])
init(libraries: [String: String]) { init(libraries: [String: String]) {
self.libraries = libraries self.libraries = libraries
@ -34,7 +35,7 @@ struct LibraryListView: View {
switch key { switch key {
case "favorites": case "favorites":
NavigationLink(destination: LazyView { NavigationLink(destination: LazyView {
LibraryView(usingParentID: "", title: library_names[key] ?? "", filters: [.isFavorite]) LibraryView(usingParentID: "", title: library_names[key] ?? "", usingFilters: withFavorites)
}) { }) {
Text(library_names[key] ?? "") Text(library_names[key] ?? "")
} }

View File

@ -18,7 +18,7 @@ struct LibraryView: View {
var usingParentID: String = "" var usingParentID: String = ""
var title: String = "" var title: String = ""
var filters: [ItemFilter] = [] var filters: LibraryFilters = LibraryFilters()
var personId: String = "" var personId: String = ""
var genre: String = "" var genre: String = ""
var studio: String = "" var studio: String = ""
@ -31,10 +31,10 @@ struct LibraryView: View {
self.title = title self.title = title
} }
init(usingParentID: String, title: String, filters: [ItemFilter]) { init(usingParentID: String, title: String, usingFilters: LibraryFilters) {
self.usingParentID = usingParentID self.usingParentID = usingParentID
self.title = title self.title = title
self.filters = filters self.filters = usingFilters
} }
init(withPerson: BaseItemPerson) { init(withPerson: BaseItemPerson) {
@ -60,7 +60,7 @@ struct LibraryView: View {
isLoading = true isLoading = true
items = [] items = []
ItemsAPI.getItemsByUserId(userId: globalData.user.user_id!, startIndex: currentPage * 100, limit: 100, recursive: true, searchTerm: nil, sortOrder: [.ascending], parentId: (usingParentID != "" ? usingParentID : nil), 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) ItemsAPI.getItemsByUserId(userId: globalData.user.user_id!, startIndex: currentPage * 100, limit: 100, recursive: true, searchTerm: nil, sortOrder: filters.sortOrder, parentId: (usingParentID != "" ? usingParentID : nil), fields: [.parentId,.primaryImageAspectRatio,.basicSyncInfo], includeItemTypes: ["Movie","Series"], filters: filters.filters, sortBy: filters.sortBy, enableUserData: true, personIds: (personId == "" ? nil : [personId]), studioIds: (studio == "" ? nil : [studio]), genreIds: (genre == "" ? nil : [genre]), enableImages: true)
.sink(receiveCompletion: { completion in .sink(receiveCompletion: { completion in
HandleAPIRequestCompletion(globalData: globalData, completion: completion) HandleAPIRequestCompletion(globalData: globalData, completion: completion)
isLoading = false isLoading = false
@ -121,29 +121,36 @@ struct LibraryView: View {
}.onChange(of: orientationInfo.orientation) { _ in }.onChange(of: orientationInfo.orientation) { _ in
recalcTracks() recalcTracks()
} }
HStack() { if(totalPages > 1) {
Spacer()
HStack() { HStack() {
Button { Spacer()
currentPage = currentPage - 1 HStack() {
onAppear() Button {
} label: { currentPage = currentPage - 1
Image(systemName: "chevron.left") onAppear()
}.disabled(currentPage == 0) } label: {
Text("\(String(currentPage+1)) of \(String(totalPages))") Image(systemName: "chevron.left")
Button { .font(.system(size: 25))
currentPage = currentPage + 1 }.disabled(currentPage == 0)
onAppear() Text("Page \(String(currentPage+1)) of \(String(totalPages))")
} label: { .font(.headline)
Image(systemName: "chevron.right") .fontWeight(.semibold)
}.disabled(currentPage > totalPages - 1) Button {
currentPage = currentPage + 1
onAppear()
} label: {
Image(systemName: "chevron.right")
.font(.system(size: 25))
}.disabled(currentPage > totalPages - 1)
}
Spacer()
} }
Spacer()
} }
Spacer().frame(height: 16)
} }
} }
} else { } else {
Text("No results found :(") Text("No results.")
} }
} }
} }

View File

@ -12,168 +12,27 @@ import JellyfinAPI
struct SeasonItemView: View { struct SeasonItemView: View {
@EnvironmentObject var globalData: GlobalData @EnvironmentObject var globalData: GlobalData
@EnvironmentObject var orientationInfo: OrientationInfo @EnvironmentObject var orientationInfo: OrientationInfo
@State private var isLoading: Bool = true
var item: ResumeItem var item: BaseItemDto
@State private var episodes: [BaseItemDto] = [] @State private var episodes: [BaseItemDto] = []
@State private var isLoading: Bool = true
init(item: ResumeItem) { @State private var viewDidLoad: Bool = false
self.item = item
}
func onAppear() { func onAppear() {
let url = "/Users/\(globalData.user.user_id ?? "")/Items/\(item.Id)" if(viewDidLoad) {
return
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 responseItem = DetailItem()
responseItem.ProductionYear = json["ProductionYear"].int ?? 0
responseItem.Poster = json["ImageTags"]["Primary"].string ?? ""
responseItem.PosterBlurHash = json["ImageBlurHashes"]["Primary"][responseItem.Poster].string ?? ""
responseItem.Backdrop = json["BackdropImageTags"][0].string ?? ""
responseItem.BackdropBlurHash = json["ImageBlurHashes"]["Backdrop"][responseItem.Backdrop].string ?? ""
responseItem.Name = json["Name"].string ?? ""
responseItem.Type = json["Type"].string ?? ""
responseItem.IndexNumber = json["IndexNumber"].int ?? nil
responseItem.SeriesId = json["ParentId"].string ?? nil
responseItem.Id = item.Id
responseItem.Overview = json["Overview"].string ?? ""
responseItem.Tagline = json["Taglines"][0].string ?? ""
responseItem.SeriesName = json["SeriesName"].string ?? nil
responseItem.ParentId = json["ParentId"].string ?? ""
// People
responseItem.Directors = []
responseItem.Studios = []
responseItem.Writers = []
responseItem.Cast = []
responseItem.Genres = []
for (_, person): (String, JSON) in json["People"] {
if person["Type"].stringValue == "Director" {
responseItem.Directors.append(person["Name"].string ?? "")
} else if person["Type"].stringValue == "Writer" {
responseItem.Writers.append(person["Name"].string ?? "")
} else if person["Type"].stringValue == "Actor" {
let cast = CastMember()
cast.Name = person["Name"].string ?? ""
cast.Id = person["Id"].string ?? ""
let imageTag = person["PrimaryImageTag"].string ?? ""
cast.ImageBlurHash = person["ImageBlurHashes"]["Primary"][imageTag].string ?? ""
cast.Role = person["Role"].string ?? ""
cast
.Image =
URL(string: "\(globalData.server.baseURI ?? "")/Items/\(cast.Id)/Images/Primary?maxWidth=2000&quality=90&tag=\(imageTag)")!
responseItem.Cast.append(cast)
}
}
_fullItem.wrappedValue = responseItem
let url2 =
"/Shows/\(fullItem.SeriesId ?? "")/Episodes?SeasonId=\(item.Id)&UserId=\(globalData.user.user_id ?? "")&Fields=ItemCounts%2CPrimaryImageAspectRatio%2CBasicSyncInfo%2CCanDelete%2CMediaSourceCount%2COverview"
let request2 = RestRequest(method: .get, url: (globalData.server.baseURI ?? "") + url2)
request2.headerParameters["X-Emby-Authorization"] = globalData.authHeader
request2.contentType = "application/json"
request2.acceptType = "application/json"
request2.responseData { (result: Result<RestResponse<Data>, RestError>) in
switch result {
case let .success(response):
let body = response.body
do {
let jsonroot = try JSON(data: body)
for (_, json): (String, JSON) in jsonroot["Items"] {
let episode = DetailItem()
episode.ProductionYear = json["ProductionYear"].int ?? 0
episode.Poster = json["ImageTags"]["Primary"].string ?? ""
episode.PosterBlurHash = json["ImageBlurHashes"]["Primary"][fullItem.Poster].string ?? ""
episode.Backdrop = json["BackdropImageTags"][0].string ?? ""
episode.BackdropBlurHash = json["ImageBlurHashes"]["Backdrop"][fullItem.Backdrop].string ?? ""
episode.Name = json["Name"].string ?? ""
episode.Type = "Episode"
episode.IndexNumber = json["IndexNumber"].int ?? nil
episode.Id = json["Id"].string ?? ""
episode.ParentIndexNumber = json["ParentIndexNumber"].int ?? nil
episode.SeasonId = json["SeasonId"].string ?? nil
episode.SeriesId = json["SeriesId"].string ?? nil
episode.Overview = json["Overview"].string ?? ""
episode.SeriesName = json["SeriesName"].string ?? nil
episode.Progress = Double(json["UserData"]["PlaybackPositionTicks"].int ?? 0)
episode.OfficialRating = json["OfficialRating"].string ?? "PG-13"
episode.Watched = json["UserData"]["Played"].bool ?? false
episode.ParentId = episode.SeasonId ?? ""
episode.CommunityRating = String(json["CommunityRating"].float ?? 0.0)
var rI = ResumeItem()
rI.Name = episode.Name
rI.Id = episode.Id
rI.IndexNumber = episode.IndexNumber
rI.ParentIndexNumber = episode.ParentIndexNumber
rI.Image = episode.Poster
rI.ImageType = "Primary"
rI.BlurHash = episode.PosterBlurHash
rI.Type = "Episode"
rI.SeasonId = episode.SeasonId
rI.SeriesId = episode.SeriesId
rI.SeriesName = episode.SeriesName
rI.ProductionYear = episode.ProductionYear
episode.ResumeItem = rI
let seconds: Int = ((json["RunTimeTicks"].int ?? 0) / 10_000_000)
episode.RuntimeTicks = json["RunTimeTicks"].int ?? 0
let hours = (seconds / 3600)
let minutes = ((seconds - (hours * 3600)) / 60)
if hours != 0 {
episode.Runtime = "\(hours):\(String(minutes).leftPad(toWidth: 2, withString: "0"))"
} else {
episode.Runtime = "\(String(minutes).leftPad(toWidth: 2, withString: "0"))m"
}
if episode.Progress != 0 {
let remainingSecs = (Double(json["RunTimeTicks"].int ?? 0) - episode.Progress) / 10_000_000
let proghours = Int(remainingSecs / 3600)
let progminutes = Int((Int(remainingSecs) - (proghours * 3600)) / 60)
if proghours != 0 {
episode.ProgressStr = "\(proghours):\(String(progminutes).leftPad(toWidth: 2, withString: "0"))"
} else {
episode.ProgressStr = "\(String(progminutes).leftPad(toWidth: 2, withString: "0"))m"
}
}
_episodes.wrappedValue.append(episode)
}
_isLoading.wrappedValue = false
_hasAppearedOnce.wrappedValue = true
} catch {}
case let .failure(error):
debugPrint(error)
}
}
} catch {}
case let .failure(error):
debugPrint(error)
}
} }
}
TvShowsAPI.getEpisodes(seriesId: item.seriesId!, fields: [.primaryImageAspectRatio], seasonId: item.id!)
@Environment(\.verticalSizeClass) .sink(receiveCompletion: { completion in
var verticalSizeClass: UserInterfaceSizeClass? HandleAPIRequestCompletion(globalData: globalData, completion: completion)
@Environment(\.horizontalSizeClass) isLoading = false
var horizontalSizeClass: UserInterfaceSizeClass? }, receiveValue: { response in
viewDidLoad = true
var isPortrait: Bool { episodes = response.items ?? []
let result = verticalSizeClass == .regular && horizontalSizeClass == .compact })
return result .store(in: &globalData.pendingAPIRequests)
} }
@ViewBuilder @ViewBuilder
@ -181,11 +40,9 @@ struct SeasonItemView: View {
if isLoading { if isLoading {
EmptyView() EmptyView()
} else { } else {
LazyImage(source: URL(string: "\(globalData.server.baseURI ?? "")/Items/\(fullItem.SeriesId ?? "")/Images/Backdrop?maxWidth=550&quality=90&tag=\(item.SeasonImage ?? "")")) LazyImage(source: item.getBackdropImage(baseURL: globalData.server.baseURI!, maxWidth: 1500))
.placeholderAndFailure { .placeholderAndFailure {
Image(uiImage: UIImage(blurHash: item Image(uiImage: UIImage(blurHash: item.getBackdropImageBlurHash(),
.SeasonImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item
.SeasonImageBlurHash ?? "",
size: CGSize(width: 32, height: 32))!) size: CGSize(width: 32, height: 32))!)
.resizable() .resizable()
} }
@ -197,11 +54,9 @@ struct SeasonItemView: View {
var portraitHeaderOverlayView: some View { var portraitHeaderOverlayView: some View {
HStack(alignment: .bottom, spacing: 12) { HStack(alignment: .bottom, spacing: 12) {
LazyImage(source: URL(string: "\(globalData.server.baseURI ?? "")/Items/\(fullItem.Id)/Images/Primary?maxWidth=250&quality=90&tag=\(fullItem.Poster)")) LazyImage(source: item.getPrimaryImage(baseURL: globalData.server.baseURI!, maxWidth: 120))
.placeholderAndFailure { .placeholderAndFailure {
Image(uiImage: UIImage(blurHash: fullItem Image(uiImage: UIImage(blurHash: item.getPrimaryImageBlurHash(),
.PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem
.PosterBlurHash,
size: CGSize(width: 32, height: 32))!) size: CGSize(width: 32, height: 32))!)
.resizable() .resizable()
.frame(width: 120, height: 180) .frame(width: 120, height: 180)
@ -211,13 +66,13 @@ struct SeasonItemView: View {
.frame(width: 120, height: 180) .frame(width: 120, height: 180)
.cornerRadius(10) .cornerRadius(10)
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text(fullItem.Name).font(.headline) Text(item.name ?? "").font(.headline)
.fontWeight(.semibold) .fontWeight(.semibold)
.foregroundColor(.primary) .foregroundColor(.primary)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.offset(y: -4) .offset(y: -4)
if fullItem.ProductionYear != 0 { if item.productionYear != 0 {
Text(String(fullItem.ProductionYear)).font(.subheadline) Text(String(item.productionYear!)).font(.subheadline)
.fontWeight(.medium) .fontWeight(.medium)
.foregroundColor(.secondary) .foregroundColor(.secondary)
.lineLimit(1) .lineLimit(1)
@ -235,23 +90,20 @@ struct SeasonItemView: View {
overlayAlignment: .bottomLeading, overlayAlignment: .bottomLeading,
headerHeight: UIScreen.main.bounds.width * 0.5625) { headerHeight: UIScreen.main.bounds.width * 0.5625) {
LazyVStack(alignment: .leading) { LazyVStack(alignment: .leading) {
if fullItem.Tagline != "" { if !(item.taglines ?? []).isEmpty {
Text(fullItem.Tagline).font(.body).italic().padding(.top, 7) Text(item.taglines!.first!).font(.body).italic().padding(.top, 7)
.fixedSize(horizontal: false, vertical: true).padding(.leading, 16) .fixedSize(horizontal: false, vertical: true).padding(.leading, 16)
.padding(.trailing, 16) .padding(.trailing, 16)
} }
Text(fullItem.Overview).font(.footnote).padding(.top, 3) Text(item.overview ?? "").font(.footnote).padding(.top, 3)
.fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16) .fixedSize(horizontal: false, vertical: true).padding(.bottom, 3).padding(.leading, 16)
.padding(.trailing, 16) .padding(.trailing, 16)
ForEach(episodes, id: \.Id) { episode in ForEach(episodes, id: \.Id) { episode in
NavigationLink(destination: ItemView(item: episode.ResumeItem ?? ResumeItem())) { NavigationLink(destination: ItemView(item: episode)) {
HStack { HStack {
LazyImage(source: URL(string: "\(globalData.server.baseURI ?? "")/Items/\(episode.Id)/Images/Primary?maxWidth=300&quality=90&tag=\(episode.Poster)")) LazyImage(source: episode.getPrimaryImage(baseURL: globalData.server.baseURI!, maxWidth: 150))
.placeholderAndFailure { .placeholderAndFailure {
Image(uiImage: UIImage(blurHash: episode Image(uiImage: UIImage(blurHash: episode.getPrimaryImageBlurHash()))
.PosterBlurHash == "" ?
"W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem
.PosterBlurHash,
size: CGSize(width: 32, height: 32))!) size: CGSize(width: 32, height: 32))!)
.resizable() .resizable()
.frame(width: 150, height: 90) .frame(width: 150, height: 90)

View File

@ -6,87 +6,46 @@
*/ */
import SwiftUI import SwiftUI
import SwiftyRequest
import SwiftyJSON
import NukeUI import NukeUI
import JellyfinAPI
struct SeriesItemView: View { struct SeriesItemView: View {
@EnvironmentObject var globalData: GlobalData @EnvironmentObject private var globalData: GlobalData
@EnvironmentObject private var orientationInfo: OrientationInfo
var item: BaseItemDto;
@State private var seasons: [BaseItemDto] = [];
@State private var isLoading: Bool = true; @State private var isLoading: Bool = true;
var item: ResumeItem; @State private var viewDidLoad: Bool = false;
@State private var items: [ResumeItem] = [];
@State private var hasAppearedOnce: Bool = false;
func onAppear() { func onAppear() {
recalcTracks() recalcTracks()
if(hasAppearedOnce) { if(viewDidLoad) {
return; return;
} }
_isLoading.wrappedValue = true; isLoading = true
let url = "/Shows/\(item.Id )/Seasons?userId=\(globalData.user.user_id ?? "")&Fields=ItemCount"
let request = RestRequest(method: .get, url: (globalData.server.baseURI ?? "") + url) TvShowsAPI.getSeasons(seriesId: item.id ?? "")
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader .sink(receiveCompletion: { completion in
request.contentType = "application/json" HandleAPIRequestCompletion(globalData: globalData, completion: completion)
request.acceptType = "application/json" }, receiveValue: { response in
isLoading = false
request.responseData() { (result: Result<RestResponse<Data>, RestError>) in viewDidLoad = true
switch result { seasons = response.items ?? []
case .success(let response): })
let body = response.body .store(in: &globalData.pendingAPIRequests)
do {
let json = try JSON(data: body)
for (_,item):(String, JSON) in json["Items"] {
// Do something you want
var itemObj = ResumeItem()
itemObj.Type = "Season"
itemObj.Id = item["Id"].string ?? ""
itemObj.ProductionYear = item["ProductionYear"].int ?? 0
itemObj.ItemBadge = item["UserData"]["UnplayedItemCount"].int ?? 0
itemObj.Image = item["ImageTags"]["Primary"].string ?? ""
if(itemObj.Image == "") {
itemObj.Image = item["ParentBackdropImageTags"][0].string ?? ""
}
itemObj.ImageType = "Primary"
itemObj.SeasonImage = item["ParentBackdropImageTags"][0].string ?? ""
itemObj.SeasonImageType = "Backdrop"
itemObj.SeasonImageBlurHash = item["ImageBlurHashes"]["Backdrop"][itemObj.SeasonImage ?? ""].string ?? ""
itemObj.BlurHash = item["ImageBlurHashes"]["Primary"][itemObj.Image].string ?? ""
itemObj.SeriesName = item["SeriesName"].string ?? ""
itemObj.Name = item["Name"].string ?? ""
_items.wrappedValue.append(itemObj)
}
} catch {
}
break
case .failure(let error):
debugPrint(error)
break
}
_isLoading.wrappedValue = false;
_hasAppearedOnce.wrappedValue = true;
}
} }
@Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass?
@Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass?
var isPortrait: Bool { //MARK: Grid tracks
let result = verticalSizeClass == .regular && horizontalSizeClass == .compact
return result
}
func recalcTracks() { func recalcTracks() {
let trkCnt: Int = Int(floor(UIScreen.main.bounds.size.width / 125)); let trkCnt: Int = Int(floor(UIScreen.main.bounds.size.width / 125));
_tracks.wrappedValue = [] tracks = []
for _ in (0..<trkCnt) for _ in (0..<trkCnt)
{ {
_tracks.wrappedValue.append(GridItem.init(.flexible())) tracks.append(GridItem.init(.flexible()))
} }
} }
@State private var tracks: [GridItem] = [] @State private var tracks: [GridItem] = []
var body: some View { var body: some View {
@ -94,43 +53,26 @@ struct SeriesItemView: View {
ScrollView(.vertical) { ScrollView(.vertical) {
Spacer().frame(height: 16) Spacer().frame(height: 16)
LazyVGrid(columns: tracks) { LazyVGrid(columns: tracks) {
ForEach(items, id: \.Id) { item in ForEach(seasons, id: \.id) { season in
NavigationLink(destination: ItemView(item: item )) { NavigationLink(destination: ItemView(item: season )) {
VStack(alignment: .leading) { VStack(alignment: .leading) {
LazyImage(source: URL(string: "\(globalData.server.baseURI ?? "")/Items/\(item.Id)/Images/\(item.ImageType)?maxWidth=250&quality=90&tag=\(item.Image)")) LazyImage(source: season.getPrimaryImage(baseURL: globalData.server.baseURI!, maxWidth: 100))
.placeholderAndFailure { .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))!) Image(uiImage: UIImage(blurHash: season.getPrimaryImageBlurHash(), size: CGSize(width: 32, height: 32))!)
.resizable() .resizable()
.frame(width: 100, height: 150) .frame(width: 100, height: 150)
.cornerRadius(10) .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
)
.frame(width:100, height: 150) .frame(width:100, height: 150)
.cornerRadius(10) .cornerRadius(10)
.shadow(radius: 5) .shadow(radius: 5)
Text(item.Name) Text(season.name ?? "")
.font(.caption) .font(.caption)
.fontWeight(.semibold) .fontWeight(.semibold)
.foregroundColor(.primary) .foregroundColor(.primary)
.lineLimit(1) .lineLimit(1)
if(item.ProductionYear != 0) { if(season.productionYear != 0) {
Text(String(item.ProductionYear)) Text(String(season.productionYear!))
.foregroundColor(.secondary) .foregroundColor(.secondary)
.font(.caption) .font(.caption)
.fontWeight(.medium) .fontWeight(.medium)
@ -139,13 +81,13 @@ struct SeriesItemView: View {
} }
} }
Spacer().frame(height: 2) Spacer().frame(height: 2)
}.onChange(of: isPortrait) { ip in }.onChange(of: orientationInfo.orientation) { ip in
recalcTracks() recalcTracks()
} }
} }
} }
.overrideViewPreference(.unspecified) .overrideViewPreference(.unspecified)
.onAppear(perform: onAppear) .onAppear(perform: onAppear)
.navigationTitle(item.Name) .navigationTitle(item.name ?? "")
} }
} }

View File

@ -7,6 +7,20 @@
import Foundation import Foundation
import Combine import Combine
import JellyfinAPI
struct LibraryFilters: Codable, Hashable {
var filters: [ItemFilter] = []
var sortOrder: [SortOrder] = [.descending]
var sortBy: [String] = ["SortName"]
}
public enum SortBy: String, Codable, CaseIterable {
case productionYear = "ProductionYear"
case premiereDate = "PremiereDate"
case name = "SortName"
case dateAdded = "DateCreated"
}
class justSignedIn: ObservableObject { class justSignedIn: ObservableObject {
@Published var did: Bool = false @Published var did: Bool = false