more to mvc & gen. client
This commit is contained in:
parent
85d79a4774
commit
93ef16a46d
|
@ -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 */,
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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] ?? "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 ?? "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue