Merge pull request #351 from mshockwave/dev-folder-view

Basic support for folder-type library items
This commit is contained in:
Ethan Pippin 2022-01-28 11:01:18 -08:00 committed by GitHub
commit 6bd07817f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 61 additions and 92 deletions

View File

@ -120,8 +120,12 @@ public extension BaseItemDto {
} }
func getSeriesPrimaryImage(maxWidth: Int) -> URL { func getSeriesPrimaryImage(maxWidth: Int) -> URL {
guard let seriesId = seriesId else {
return getPrimaryImage(maxWidth: maxWidth)
}
let x = UIScreen.main.nativeScale * CGFloat(maxWidth) let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
let urlString = ImageAPI.getItemImageWithRequestBuilder(itemId: seriesId ?? "", let urlString = ImageAPI.getItemImageWithRequestBuilder(itemId: seriesId,
imageType: .primary, imageType: .primary,
maxWidth: Int(x), maxWidth: Int(x),
quality: 96, quality: 96,
@ -227,6 +231,7 @@ public extension BaseItemDto {
case series = "Series" case series = "Series"
case boxset = "BoxSet" case boxset = "BoxSet"
case collectionFolder = "CollectionFolder" case collectionFolder = "CollectionFolder"
case folder = "Folder"
case unknown case unknown
@ -249,7 +254,7 @@ public extension BaseItemDto {
func portraitHeaderViewURL(maxWidth: Int) -> URL { func portraitHeaderViewURL(maxWidth: Int) -> URL {
switch itemType { switch itemType {
case .movie, .season, .series, .boxset, .collectionFolder: case .movie, .season, .series, .boxset, .collectionFolder, .folder:
return getPrimaryImage(maxWidth: maxWidth) return getPrimaryImage(maxWidth: maxWidth)
case .episode: case .episode:
return getSeriesPrimaryImage(maxWidth: maxWidth) return getSeriesPrimaryImage(maxWidth: maxWidth)

View File

@ -324,6 +324,8 @@ internal enum L10n {
internal static var settings: String { return L10n.tr("Localizable", "settings") } internal static var settings: String { return L10n.tr("Localizable", "settings") }
/// Show Cast & Crew /// Show Cast & Crew
internal static var showCastAndCrew: String { return L10n.tr("Localizable", "showCastAndCrew") } internal static var showCastAndCrew: String { return L10n.tr("Localizable", "showCastAndCrew") }
/// Flatten Library Items
internal static var showFlattenView: String { return L10n.tr("Localizable", "showFlattenView") }
/// Show Missing Episodes /// Show Missing Episodes
internal static var showMissingEpisodes: String { return L10n.tr("Localizable", "showMissingEpisodes") } internal static var showMissingEpisodes: String { return L10n.tr("Localizable", "showMissingEpisodes") }
/// Show Missing Seasons /// Show Missing Seasons

View File

@ -41,6 +41,7 @@ extension Defaults.Keys {
// Customize settings // Customize settings
static let showPosterLabels = Key<Bool>("showPosterLabels", default: true, suite: SwiftfinStore.Defaults.generalSuite) static let showPosterLabels = Key<Bool>("showPosterLabels", default: true, suite: SwiftfinStore.Defaults.generalSuite)
static let showCastAndCrew = Key<Bool>("showCastAndCrew", default: true, suite: SwiftfinStore.Defaults.generalSuite) static let showCastAndCrew = Key<Bool>("showCastAndCrew", default: true, suite: SwiftfinStore.Defaults.generalSuite)
static let showFlattenView = Key<Bool>("showFlattenView", default: true, suite: SwiftfinStore.Defaults.generalSuite)
// Video player / overlay settings // Video player / overlay settings
static let overlayType = Key<OverlayType>("overlayType", default: .normal, suite: SwiftfinStore.Defaults.generalSuite) static let overlayType = Key<OverlayType>("overlayType", default: .normal, suite: SwiftfinStore.Defaults.generalSuite)

View File

@ -7,6 +7,7 @@
// //
import Combine import Combine
import Defaults
import Foundation import Foundation
import JellyfinAPI import JellyfinAPI
import SwiftUICollection import SwiftUICollection
@ -94,10 +95,20 @@ final class LibraryViewModel: ViewModel {
genreIDs = filters.withGenres.compactMap(\.id) genreIDs = filters.withGenres.compactMap(\.id)
} }
let sortBy = filters.sortBy.map(\.rawValue) let sortBy = filters.sortBy.map(\.rawValue)
let queryRecursive = Defaults[.showFlattenView] || filters.filters.contains(.isFavorite) ||
self.person != nil ||
self.genre != nil ||
self.studio != nil
let includeItemTypes: [String]
if filters.filters.contains(.isFavorite) {
includeItemTypes = ["Movie", "Series", "Season", "Episode", "BoxSet"]
} else {
includeItemTypes = ["Movie", "Series", "BoxSet"] + (Defaults[.showFlattenView] ? [] : ["Folder"])
}
ItemsAPI.getItemsByUserId(userId: SessionManager.main.currentLogin.user.id, startIndex: currentPage * pageItemSize, ItemsAPI.getItemsByUserId(userId: SessionManager.main.currentLogin.user.id, startIndex: currentPage * pageItemSize,
limit: pageItemSize, limit: pageItemSize,
recursive: true, recursive: queryRecursive,
searchTerm: nil, searchTerm: nil,
sortOrder: filters.sortOrder, sortOrder: filters.sortOrder,
parentId: parentID, parentId: parentID,
@ -110,9 +121,7 @@ final class LibraryViewModel: ViewModel {
.people, .people,
.chapters, .chapters,
], ],
includeItemTypes: filters.filters includeItemTypes: includeItemTypes,
.contains(.isFavorite) ? ["Movie", "Series", "Season", "Episode", "BoxSet"] :
["Movie", "Series", "BoxSet"],
filters: filters.filters, filters: filters.filters,
sortBy: sortBy, sortBy: sortBy,
tags: filters.tags, tags: filters.tags,

View File

@ -62,7 +62,7 @@ struct ItemView: View {
} else { } else {
SeriesItemView(viewModel: SeriesItemViewModel(item: item)) SeriesItemView(viewModel: SeriesItemViewModel(item: item))
} }
case .boxset: case .boxset, .folder:
CinematicCollectionItemView(viewModel: CollectionItemViewModel(item: item)) CinematicCollectionItemView(viewModel: CollectionItemViewModel(item: item))
default: default:
Text(L10n.notImplementedYetWithType(item.type ?? "")) Text(L10n.notImplementedYetWithType(item.type ?? ""))

View File

@ -21,39 +21,17 @@ struct LibraryListView: View {
@Default(.Experimental.liveTVAlphaEnabled) @Default(.Experimental.liveTVAlphaEnabled)
var liveTVAlphaEnabled var liveTVAlphaEnabled
let supportedCollectionTypes = ["movies", "tvshows", "boxsets", "livetv", "other"]
var body: some View { var body: some View {
ScrollView { ScrollView {
LazyVStack { LazyVStack {
if !viewModel.isLoading { if !viewModel.isLoading {
if let collectionLibraryItem = viewModel.libraries.first(where: { $0.collectionType == "boxsets" }) { ForEach(viewModel.libraries.filter { [self] library in
Button { let collectionType = library.collectionType ?? "other"
self.libraryListRouter.route(to: \.library, return self.supportedCollectionTypes.contains(collectionType)
(viewModel: LibraryViewModel(parentID: collectionLibraryItem.id), }, id: \.id) { library in
title: collectionLibraryItem.name ?? ""))
}
label: {
ZStack {
HStack {
Spacer()
VStack {
Text(collectionLibraryItem.name ?? "")
.foregroundColor(.white)
.font(.title2)
.fontWeight(.semibold)
}
Spacer()
}.padding(32)
}
.frame(minWidth: 100, maxWidth: .infinity)
.frame(height: 100)
}
.cornerRadius(10)
.shadow(radius: 5)
.padding(.bottom, 5)
}
ForEach(viewModel.libraries.filter { $0.collectionType != "boxsets" }, id: \.id) { library in
if library.collectionType == "livetv" { if library.collectionType == "livetv" {
if liveTVAlphaEnabled { if liveTVAlphaEnabled {
Button { Button {
@ -81,7 +59,8 @@ struct LibraryListView: View {
} }
} else { } else {
Button { Button {
self.libraryListRouter.route(to: \.library, (viewModel: LibraryViewModel(), title: library.name ?? "")) self.libraryListRouter.route(to: \.library,
(viewModel: LibraryViewModel(parentID: library.id), title: library.name ?? ""))
} }
label: { label: {
ZStack { ZStack {

View File

@ -56,7 +56,6 @@ struct LibraryView: View {
} cell: { _, cell in } cell: { _, cell in
GeometryReader { _ in GeometryReader { _ in
if let item = cell.item { if let item = cell.item {
if item.type != "Folder" {
Button { Button {
libraryRouter.route(to: \.modalItem, item) libraryRouter.route(to: \.modalItem, item)
} label: { } label: {
@ -68,7 +67,6 @@ struct LibraryView: View {
viewModel.requestNextPageAsync() viewModel.requestNextPageAsync()
} }
} }
}
} else if cell.loadingCell { } else if cell.loadingCell {
ProgressView() ProgressView()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center) .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)

View File

@ -15,6 +15,8 @@ struct CustomizeViewsSettings: View {
var showPosterLabels var showPosterLabels
@Default(.showCastAndCrew) @Default(.showCastAndCrew)
var showCastAndCrew var showCastAndCrew
@Default(.showFlattenView)
var showFlattenView
var body: some View { var body: some View {
Form { Form {
@ -24,6 +26,7 @@ struct CustomizeViewsSettings: View {
// TODO: Uncomment when cast and crew implemented in item views // TODO: Uncomment when cast and crew implemented in item views
// Toggle(L10n.showCastAndCrew, isOn: $showCastAndCrew) // Toggle(L10n.showCastAndCrew, isOn: $showCastAndCrew)
Toggle(L10n.showFlattenView, isOn: $showFlattenView)
} header: { } header: {
L10n.customize.text L10n.customize.text

View File

@ -51,7 +51,7 @@ private struct ItemView: View {
self.viewModel = EpisodeItemViewModel(item: item) self.viewModel = EpisodeItemViewModel(item: item)
case .series: case .series:
self.viewModel = SeriesItemViewModel(item: item) self.viewModel = SeriesItemViewModel(item: item)
case .boxset: case .boxset, .folder:
self.viewModel = CollectionItemViewModel(item: item) self.viewModel = CollectionItemViewModel(item: item)
default: default:
self.viewModel = ItemViewModel(item: item) self.viewModel = ItemViewModel(item: item)

View File

@ -16,6 +16,8 @@ struct LibraryListView: View {
@StateObject @StateObject
var viewModel = LibraryListViewModel() var viewModel = LibraryListViewModel()
let supportedCollectionTypes = ["movies", "tvshows", "boxsets", "other"]
var body: some View { var body: some View {
ScrollView { ScrollView {
LazyVStack { LazyVStack {
@ -42,38 +44,10 @@ struct LibraryListView: View {
.padding(.bottom, 5) .padding(.bottom, 5)
if !viewModel.isLoading { if !viewModel.isLoading {
ForEach(viewModel.libraries.filter { [self] library in
if let collectionsLibraryItem = viewModel.libraries.first(where: { $0.collectionType == "boxsets" }) { let collectionType = library.collectionType ?? "other"
Button { return self.supportedCollectionTypes.contains(collectionType)
libraryListRouter.route(to: \.library, }, id: \.id) { library in
(viewModel: LibraryViewModel(parentID: collectionsLibraryItem.id),
title: collectionsLibraryItem.name ?? ""))
} label: {
ZStack {
ImageView(src: collectionsLibraryItem.getPrimaryImage(maxWidth: 500),
bh: collectionsLibraryItem.getPrimaryImageBlurHash())
.opacity(0.4)
HStack {
Spacer()
VStack {
Text(collectionsLibraryItem.name ?? "")
.foregroundColor(.white)
.font(.title2)
.fontWeight(.semibold)
}
Spacer()
}.padding(32)
}.background(Color.black)
.frame(minWidth: 100, maxWidth: .infinity)
.frame(height: 100)
}
.cornerRadius(10)
.shadow(radius: 5)
.padding(.bottom, 5)
}
ForEach(viewModel.libraries, id: \.id) { library in
if library.collectionType ?? "" == "movies" || library.collectionType ?? "" == "tvshows" {
Button { Button {
libraryListRouter.route(to: \.library, libraryListRouter.route(to: \.library,
(viewModel: LibraryViewModel(parentID: library.id), (viewModel: LibraryViewModel(parentID: library.id),
@ -100,9 +74,6 @@ struct LibraryListView: View {
.cornerRadius(10) .cornerRadius(10)
.shadow(radius: 5) .shadow(radius: 5)
.padding(.bottom, 5) .padding(.bottom, 5)
} else {
EmptyView()
}
} }
} else { } else {
ProgressView() ProgressView()

View File

@ -45,13 +45,11 @@ struct LibraryView: View {
VStack { VStack {
LazyVGrid(columns: tracks) { LazyVGrid(columns: tracks) {
ForEach(viewModel.items, id: \.id) { item in ForEach(viewModel.items, id: \.id) { item in
if item.type != "Folder" {
PortraitItemButton(item: item) { item in PortraitItemButton(item: item) { item in
libraryRouter.route(to: \.item, item) libraryRouter.route(to: \.item, item)
} }
} }
} }
}
.ignoresSafeArea() .ignoresSafeArea()
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
.onRotate { _ in .onRotate { _ in

View File

@ -15,6 +15,8 @@ struct CustomizeViewsSettings: View {
var showPosterLabels var showPosterLabels
@Default(.showCastAndCrew) @Default(.showCastAndCrew)
var showCastAndCrew var showCastAndCrew
@Default(.showFlattenView)
var showFlattenView
var body: some View { var body: some View {
Form { Form {
@ -22,6 +24,7 @@ struct CustomizeViewsSettings: View {
Toggle(L10n.showPosterLabels, isOn: $showPosterLabels) Toggle(L10n.showPosterLabels, isOn: $showPosterLabels)
Toggle(L10n.showCastAndCrew, isOn: $showCastAndCrew) Toggle(L10n.showCastAndCrew, isOn: $showCastAndCrew)
Toggle(L10n.showFlattenView, isOn: $showFlattenView)
} header: { } header: {
L10n.customize.text L10n.customize.text