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 */; };
|
||||
53A431BD266B0FF20016769F /* JellyfinAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 53A431BC266B0FF20016769F /* 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 */; };
|
||||
53C4404F266C75C70049424C /* HandleAPIRequestCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53C4404D266C75C70049424C /* HandleAPIRequestCompletion.swift */; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -136,6 +138,7 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -236,7 +239,7 @@
|
|||
5377CBF2263B596A003A4E83 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5377CBF1263B596A003A4E83 /* JellyfinPlayer.app */,
|
||||
5377CBF1263B596A003A4E83 /* JellyfinPlayer iOS.app */,
|
||||
535870602669D21600D05A09 /* JellyfinPlayer tvOS.app */,
|
||||
);
|
||||
name = Products;
|
||||
|
@ -245,6 +248,7 @@
|
|||
5377CBF3263B596A003A4E83 /* JellyfinPlayer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
53AD124C2670278D0094A276 /* JellyfinPlayer.entitlements */,
|
||||
5377CBF8263B596B003A4E83 /* Assets.xcassets */,
|
||||
5338F74D263B61370014BF09 /* ConnectToServerView.swift */,
|
||||
5377CBF6263B596A003A4E83 /* ContentView.swift */,
|
||||
|
@ -342,9 +346,9 @@
|
|||
productReference = 535870602669D21600D05A09 /* JellyfinPlayer tvOS.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
5377CBF0263B596A003A4E83 /* JellyfinPlayer */ = {
|
||||
5377CBF0263B596A003A4E83 /* JellyfinPlayer iOS */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 5377CC1B263B596B003A4E83 /* Build configuration list for PBXNativeTarget "JellyfinPlayer" */;
|
||||
buildConfigurationList = 5377CC1B263B596B003A4E83 /* Build configuration list for PBXNativeTarget "JellyfinPlayer iOS" */;
|
||||
buildPhases = (
|
||||
5377CBED263B596A003A4E83 /* Sources */,
|
||||
5377CBEE263B596A003A4E83 /* Frameworks */,
|
||||
|
@ -356,7 +360,7 @@
|
|||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = JellyfinPlayer;
|
||||
name = "JellyfinPlayer iOS";
|
||||
packageProductDependencies = (
|
||||
5338F756263B7E2E0014BF09 /* KeychainSwift */,
|
||||
53352570265EA0A0006CCA86 /* Introspect */,
|
||||
|
@ -364,7 +368,7 @@
|
|||
53A431BC266B0FF20016769F /* JellyfinAPI */,
|
||||
);
|
||||
productName = JellyfinPlayer;
|
||||
productReference = 5377CBF1263B596A003A4E83 /* JellyfinPlayer.app */;
|
||||
productReference = 5377CBF1263B596A003A4E83 /* JellyfinPlayer iOS.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
@ -406,7 +410,7 @@
|
|||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
5377CBF0263B596A003A4E83 /* JellyfinPlayer */,
|
||||
5377CBF0263B596A003A4E83 /* JellyfinPlayer iOS */,
|
||||
5358705F2669D21600D05A09 /* JellyfinPlayer tvOS */,
|
||||
);
|
||||
};
|
||||
|
@ -467,6 +471,7 @@
|
|||
53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */,
|
||||
5377CBFE263B596B003A4E83 /* PersistenceController.swift in Sources */,
|
||||
5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */,
|
||||
53AD124E26702B8A0094A276 /* SeasonItemView.swift in Sources */,
|
||||
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */,
|
||||
6225FCCB2663841E00E067F6 /* ParallaxHeader.swift in Sources */,
|
||||
53F8377D265FF67C00F456B3 /* VideoPlayerSettingsView.swift in Sources */,
|
||||
|
@ -485,6 +490,7 @@
|
|||
5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */,
|
||||
539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */,
|
||||
5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */,
|
||||
53AD124D267029D60094A276 /* SeriesItemView.swift in Sources */,
|
||||
5377CBF5263B596A003A4E83 /* JellyfinPlayerApp.swift in Sources */,
|
||||
53EE24E6265060780068F029 /* LibrarySearchView.swift in Sources */,
|
||||
53892772263C8C6F0035E14B /* LoadingView.swift in Sources */,
|
||||
|
@ -513,7 +519,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.vigue.swiftfin.tv;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.vigue.jellyfin;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = appletvos;
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
@ -541,7 +547,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.vigue.swiftfin.tv;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.vigue.jellyfin;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = appletvos;
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
@ -749,7 +755,7 @@
|
|||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
5377CC1B263B596B003A4E83 /* Build configuration list for PBXNativeTarget "JellyfinPlayer" */ = {
|
||||
5377CC1B263B596B003A4E83 /* Build configuration list for PBXNativeTarget "JellyfinPlayer iOS" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
5377CC1C263B596B003A4E83 /* Debug */,
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "5377CBF0263B596A003A4E83"
|
||||
BuildableName = "JellyfinPlayer.app"
|
||||
BlueprintName = "JellyfinPlayer"
|
||||
BuildableName = "JellyfinPlayer iOS.app"
|
||||
BlueprintName = "JellyfinPlayer iOS"
|
||||
ReferencedContainer = "container:JellyfinPlayer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
|
@ -45,8 +45,8 @@
|
|||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "5377CBF0263B596A003A4E83"
|
||||
BuildableName = "JellyfinPlayer.app"
|
||||
BlueprintName = "JellyfinPlayer"
|
||||
BuildableName = "JellyfinPlayer iOS.app"
|
||||
BlueprintName = "JellyfinPlayer iOS"
|
||||
ReferencedContainer = "container:JellyfinPlayer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
|
@ -62,8 +62,8 @@
|
|||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "5377CBF0263B596A003A4E83"
|
||||
BuildableName = "JellyfinPlayer.app"
|
||||
BlueprintName = "JellyfinPlayer"
|
||||
BuildableName = "JellyfinPlayer iOS.app"
|
||||
BlueprintName = "JellyfinPlayer iOS"
|
||||
ReferencedContainer = "container:JellyfinPlayer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
|
|
|
@ -35,6 +35,8 @@ struct ContentView: View {
|
|||
@State private var showSettingsPopover: Bool = false
|
||||
@State private var viewDidLoad: Bool = false
|
||||
@State private var loadState: Int = 2
|
||||
|
||||
private var recentFilterSet: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.descending], sortBy: ["DateCreated"])
|
||||
|
||||
func startup() {
|
||||
if(viewDidLoad == true) {
|
||||
|
@ -158,7 +160,7 @@ struct ContentView: View {
|
|||
Spacer()
|
||||
NavigationLink(destination: LazyView {
|
||||
LibraryView(usingParentID: library_id,
|
||||
title: library_names[library_id] ?? "")
|
||||
title: library_names[library_id] ?? "", usingFilters: recentFilterSet)
|
||||
}) {
|
||||
Text("See All").font(.subheadline).fontWeight(.bold)
|
||||
}
|
||||
|
|
|
@ -25,21 +25,31 @@ struct ItemView: View {
|
|||
@StateObject private var videoPlayerItem: VideoPlayerItem = VideoPlayerItem()
|
||||
@State private var videoIsLoading: Bool = false; //This variable is only changed by the underlying VLC view.
|
||||
@State private var isLoading: Bool = false;
|
||||
@State private var viewDidLoad: Bool = false;
|
||||
|
||||
init(item: BaseItemDto) {
|
||||
self.item = item
|
||||
}
|
||||
|
||||
func onAppear() {
|
||||
isLoading = true;
|
||||
UserLibraryAPI.getItem(userId: globalData.user.user_id!, itemId: item.id!)
|
||||
.sink(receiveCompletion: { completion in
|
||||
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
|
||||
}, receiveValue: { response in
|
||||
isLoading = false
|
||||
fullItem = response
|
||||
})
|
||||
.store(in: &globalData.pendingAPIRequests)
|
||||
if(viewDidLoad) {
|
||||
return
|
||||
}
|
||||
|
||||
if(item.type == "Movie" || item.type == "Episode") {
|
||||
isLoading = true;
|
||||
UserLibraryAPI.getItem(userId: globalData.user.user_id!, itemId: item.id!)
|
||||
.sink(receiveCompletion: { completion in
|
||||
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
|
||||
}, receiveValue: { response in
|
||||
isLoading = false
|
||||
viewDidLoad = true
|
||||
fullItem = response
|
||||
})
|
||||
.store(in: &globalData.pendingAPIRequests)
|
||||
} else {
|
||||
viewDidLoad = true
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
@ -60,15 +70,14 @@ struct ItemView: View {
|
|||
ProgressView()
|
||||
} else {
|
||||
VStack {
|
||||
if(fullItem.type == "Movie") {
|
||||
if(item.type == "Movie") {
|
||||
MovieItemView(item: fullItem)
|
||||
} else if(fullItem.type == "Season") {
|
||||
} else if(item.type == "Season") {
|
||||
EmptyView()
|
||||
//SeasonItemView(item: fullItem)
|
||||
} else if(fullItem.type == "Series") {
|
||||
EmptyView()
|
||||
//SeriesItemView(item: fullItem)
|
||||
} else if(fullItem.type == "Episode") {
|
||||
SeasonItemView(item: item)
|
||||
} else if(item.type == "Series") {
|
||||
SeriesItemView(item: item)
|
||||
} else if(item.type == "Episode") {
|
||||
EmptyView()
|
||||
//EpisodeItemView(item: fullItem)
|
||||
} else {
|
||||
|
|
|
@ -6,5 +6,7 @@
|
|||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.coremedia.hls.low-latency</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -40,11 +40,11 @@ struct LatestMediaView: View {
|
|||
LazyHStack() {
|
||||
Spacer().frame(width:16)
|
||||
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)) {
|
||||
VStack(alignment: .leading) {
|
||||
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 {
|
||||
Image(uiImage: UIImage(blurHash: item.getPrimaryImageBlurHash(), size: CGSize(width: 16, height: 20))!)
|
||||
.resizable()
|
||||
|
@ -54,11 +54,24 @@ struct LatestMediaView: View {
|
|||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
Spacer().frame(height:5)
|
||||
Text(item.seasonName ?? item.name ?? "")
|
||||
Text(item.seriesName ?? item.name ?? "")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.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)
|
||||
Spacer().frame(width: 15)
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ struct LibraryListView: View {
|
|||
@State var library_ids: [String] = ["favorites", "genres"]
|
||||
@State var library_names: [String: String] = ["favorites": "Favorites", "genres": "Genres"]
|
||||
var libraries: [String: String] = [:] //input libraries
|
||||
var withFavorites: LibraryFilters = LibraryFilters(filters: [.isFavorite], sortOrder: [.descending], sortBy: ["SortName"])
|
||||
|
||||
init(libraries: [String: String]) {
|
||||
self.libraries = libraries
|
||||
|
@ -34,7 +35,7 @@ struct LibraryListView: View {
|
|||
switch key {
|
||||
case "favorites":
|
||||
NavigationLink(destination: LazyView {
|
||||
LibraryView(usingParentID: "", title: library_names[key] ?? "", filters: [.isFavorite])
|
||||
LibraryView(usingParentID: "", title: library_names[key] ?? "", usingFilters: withFavorites)
|
||||
}) {
|
||||
Text(library_names[key] ?? "")
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ struct LibraryView: View {
|
|||
|
||||
var usingParentID: String = ""
|
||||
var title: String = ""
|
||||
var filters: [ItemFilter] = []
|
||||
var filters: LibraryFilters = LibraryFilters()
|
||||
var personId: String = ""
|
||||
var genre: String = ""
|
||||
var studio: String = ""
|
||||
|
@ -31,10 +31,10 @@ struct LibraryView: View {
|
|||
self.title = title
|
||||
}
|
||||
|
||||
init(usingParentID: String, title: String, filters: [ItemFilter]) {
|
||||
init(usingParentID: String, title: String, usingFilters: LibraryFilters) {
|
||||
self.usingParentID = usingParentID
|
||||
self.title = title
|
||||
self.filters = filters
|
||||
self.filters = usingFilters
|
||||
}
|
||||
|
||||
init(withPerson: BaseItemPerson) {
|
||||
|
@ -60,7 +60,7 @@ struct LibraryView: View {
|
|||
isLoading = true
|
||||
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
|
||||
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
|
||||
isLoading = false
|
||||
|
@ -121,29 +121,36 @@ struct LibraryView: View {
|
|||
}.onChange(of: orientationInfo.orientation) { _ in
|
||||
recalcTracks()
|
||||
}
|
||||
HStack() {
|
||||
Spacer()
|
||||
if(totalPages > 1) {
|
||||
HStack() {
|
||||
Button {
|
||||
currentPage = currentPage - 1
|
||||
onAppear()
|
||||
} label: {
|
||||
Image(systemName: "chevron.left")
|
||||
}.disabled(currentPage == 0)
|
||||
Text("\(String(currentPage+1)) of \(String(totalPages))")
|
||||
Button {
|
||||
currentPage = currentPage + 1
|
||||
onAppear()
|
||||
} label: {
|
||||
Image(systemName: "chevron.right")
|
||||
}.disabled(currentPage > totalPages - 1)
|
||||
Spacer()
|
||||
HStack() {
|
||||
Button {
|
||||
currentPage = currentPage - 1
|
||||
onAppear()
|
||||
} label: {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 25))
|
||||
}.disabled(currentPage == 0)
|
||||
Text("Page \(String(currentPage+1)) of \(String(totalPages))")
|
||||
.font(.headline)
|
||||
.fontWeight(.semibold)
|
||||
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 {
|
||||
Text("No results found :(")
|
||||
Text("No results.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,168 +12,27 @@ import JellyfinAPI
|
|||
struct SeasonItemView: View {
|
||||
@EnvironmentObject var globalData: GlobalData
|
||||
@EnvironmentObject var orientationInfo: OrientationInfo
|
||||
@State private var isLoading: Bool = true
|
||||
|
||||
var item: ResumeItem
|
||||
var item: BaseItemDto
|
||||
|
||||
@State private var episodes: [BaseItemDto] = []
|
||||
|
||||
init(item: ResumeItem) {
|
||||
self.item = item
|
||||
}
|
||||
|
||||
@State private var isLoading: Bool = true
|
||||
@State private var viewDidLoad: Bool = false
|
||||
|
||||
func onAppear() {
|
||||
let url = "/Users/\(globalData.user.user_id ?? "")/Items/\(item.Id)"
|
||||
|
||||
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)
|
||||
}
|
||||
if(viewDidLoad) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@Environment(\.verticalSizeClass)
|
||||
var verticalSizeClass: UserInterfaceSizeClass?
|
||||
@Environment(\.horizontalSizeClass)
|
||||
var horizontalSizeClass: UserInterfaceSizeClass?
|
||||
|
||||
var isPortrait: Bool {
|
||||
let result = verticalSizeClass == .regular && horizontalSizeClass == .compact
|
||||
return result
|
||||
|
||||
TvShowsAPI.getEpisodes(seriesId: item.seriesId!, fields: [.primaryImageAspectRatio], seasonId: item.id!)
|
||||
.sink(receiveCompletion: { completion in
|
||||
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
|
||||
isLoading = false
|
||||
}, receiveValue: { response in
|
||||
viewDidLoad = true
|
||||
episodes = response.items ?? []
|
||||
})
|
||||
.store(in: &globalData.pendingAPIRequests)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
|
@ -181,11 +40,9 @@ struct SeasonItemView: View {
|
|||
if isLoading {
|
||||
EmptyView()
|
||||
} 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 {
|
||||
Image(uiImage: UIImage(blurHash: item
|
||||
.SeasonImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : item
|
||||
.SeasonImageBlurHash ?? "",
|
||||
Image(uiImage: UIImage(blurHash: item.getBackdropImageBlurHash(),
|
||||
size: CGSize(width: 32, height: 32))!)
|
||||
.resizable()
|
||||
}
|
||||
|
@ -197,11 +54,9 @@ struct SeasonItemView: View {
|
|||
|
||||
var portraitHeaderOverlayView: some View {
|
||||
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 {
|
||||
Image(uiImage: UIImage(blurHash: fullItem
|
||||
.PosterBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem
|
||||
.PosterBlurHash,
|
||||
Image(uiImage: UIImage(blurHash: item.getPrimaryImageBlurHash(),
|
||||
size: CGSize(width: 32, height: 32))!)
|
||||
.resizable()
|
||||
.frame(width: 120, height: 180)
|
||||
|
@ -211,13 +66,13 @@ struct SeasonItemView: View {
|
|||
.frame(width: 120, height: 180)
|
||||
.cornerRadius(10)
|
||||
VStack(alignment: .leading) {
|
||||
Text(fullItem.Name).font(.headline)
|
||||
Text(item.name ?? "").font(.headline)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.offset(y: -4)
|
||||
if fullItem.ProductionYear != 0 {
|
||||
Text(String(fullItem.ProductionYear)).font(.subheadline)
|
||||
if item.productionYear != 0 {
|
||||
Text(String(item.productionYear!)).font(.subheadline)
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
|
@ -235,23 +90,20 @@ struct SeasonItemView: View {
|
|||
overlayAlignment: .bottomLeading,
|
||||
headerHeight: UIScreen.main.bounds.width * 0.5625) {
|
||||
LazyVStack(alignment: .leading) {
|
||||
if fullItem.Tagline != "" {
|
||||
Text(fullItem.Tagline).font(.body).italic().padding(.top, 7)
|
||||
if !(item.taglines ?? []).isEmpty {
|
||||
Text(item.taglines!.first!).font(.body).italic().padding(.top, 7)
|
||||
.fixedSize(horizontal: false, vertical: true).padding(.leading, 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)
|
||||
.padding(.trailing, 16)
|
||||
ForEach(episodes, id: \.Id) { episode in
|
||||
NavigationLink(destination: ItemView(item: episode.ResumeItem ?? ResumeItem())) {
|
||||
NavigationLink(destination: ItemView(item: episode)) {
|
||||
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 {
|
||||
Image(uiImage: UIImage(blurHash: episode
|
||||
.PosterBlurHash == "" ?
|
||||
"W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem
|
||||
.PosterBlurHash,
|
||||
Image(uiImage: UIImage(blurHash: episode.getPrimaryImageBlurHash()))
|
||||
size: CGSize(width: 32, height: 32))!)
|
||||
.resizable()
|
||||
.frame(width: 150, height: 90)
|
||||
|
|
|
@ -6,87 +6,46 @@
|
|||
*/
|
||||
|
||||
import SwiftUI
|
||||
import SwiftyRequest
|
||||
import SwiftyJSON
|
||||
import NukeUI
|
||||
import JellyfinAPI
|
||||
|
||||
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;
|
||||
var item: ResumeItem;
|
||||
@State private var items: [ResumeItem] = [];
|
||||
@State private var hasAppearedOnce: Bool = false;
|
||||
@State private var viewDidLoad: Bool = false;
|
||||
|
||||
func onAppear() {
|
||||
recalcTracks()
|
||||
if(hasAppearedOnce) {
|
||||
if(viewDidLoad) {
|
||||
return;
|
||||
}
|
||||
_isLoading.wrappedValue = true;
|
||||
let url = "/Shows/\(item.Id )/Seasons?userId=\(globalData.user.user_id ?? "")&Fields=ItemCount"
|
||||
isLoading = true
|
||||
|
||||
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 .success(let response):
|
||||
let body = response.body
|
||||
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;
|
||||
}
|
||||
TvShowsAPI.getSeasons(seriesId: item.id ?? "")
|
||||
.sink(receiveCompletion: { completion in
|
||||
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
|
||||
}, receiveValue: { response in
|
||||
isLoading = false
|
||||
viewDidLoad = true
|
||||
seasons = response.items ?? []
|
||||
})
|
||||
.store(in: &globalData.pendingAPIRequests)
|
||||
}
|
||||
|
||||
@Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass?
|
||||
@Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass?
|
||||
|
||||
var isPortrait: Bool {
|
||||
let result = verticalSizeClass == .regular && horizontalSizeClass == .compact
|
||||
return result
|
||||
}
|
||||
|
||||
//MARK: Grid tracks
|
||||
func recalcTracks() {
|
||||
let trkCnt: Int = Int(floor(UIScreen.main.bounds.size.width / 125));
|
||||
_tracks.wrappedValue = []
|
||||
tracks = []
|
||||
for _ in (0..<trkCnt)
|
||||
{
|
||||
_tracks.wrappedValue.append(GridItem.init(.flexible()))
|
||||
tracks.append(GridItem.init(.flexible()))
|
||||
}
|
||||
}
|
||||
|
||||
@State private var tracks: [GridItem] = []
|
||||
|
||||
var body: some View {
|
||||
|
@ -94,43 +53,26 @@ struct SeriesItemView: View {
|
|||
ScrollView(.vertical) {
|
||||
Spacer().frame(height: 16)
|
||||
LazyVGrid(columns: tracks) {
|
||||
ForEach(items, id: \.Id) { item in
|
||||
NavigationLink(destination: ItemView(item: item )) {
|
||||
ForEach(seasons, id: \.id) { season in
|
||||
NavigationLink(destination: ItemView(item: season )) {
|
||||
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 {
|
||||
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()
|
||||
.frame(width: 100, height: 150)
|
||||
.cornerRadius(10)
|
||||
}.overlay(
|
||||
ZStack {
|
||||
if(item.ItemBadge == 0) {
|
||||
Image(systemName: "checkmark")
|
||||
.font(.caption)
|
||||
.padding(3)
|
||||
.foregroundColor(.white)
|
||||
} else {
|
||||
Text("\(String(item.ItemBadge ?? 0))")
|
||||
.font(.caption)
|
||||
.padding(3)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
}.background(Color.black)
|
||||
.opacity(0.8)
|
||||
.cornerRadius(10.0)
|
||||
.padding(3), alignment: .topTrailing
|
||||
)
|
||||
}
|
||||
.frame(width:100, height: 150)
|
||||
.cornerRadius(10)
|
||||
.shadow(radius: 5)
|
||||
Text(item.Name)
|
||||
Text(season.name ?? "")
|
||||
.font(.caption)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
if(item.ProductionYear != 0) {
|
||||
Text(String(item.ProductionYear))
|
||||
if(season.productionYear != 0) {
|
||||
Text(String(season.productionYear!))
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
|
@ -139,13 +81,13 @@ struct SeriesItemView: View {
|
|||
}
|
||||
}
|
||||
Spacer().frame(height: 2)
|
||||
}.onChange(of: isPortrait) { ip in
|
||||
}.onChange(of: orientationInfo.orientation) { ip in
|
||||
recalcTracks()
|
||||
}
|
||||
}
|
||||
}
|
||||
.overrideViewPreference(.unspecified)
|
||||
.onAppear(perform: onAppear)
|
||||
.navigationTitle(item.Name)
|
||||
.navigationTitle(item.name ?? "")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,20 @@
|
|||
|
||||
import Foundation
|
||||
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 {
|
||||
@Published var did: Bool = false
|
||||
|
|
Loading…
Reference in New Issue