Merge pull request #351 from mshockwave/dev-folder-view
Basic support for folder-type library items
This commit is contained in:
commit
6bd07817f7
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 ?? ""))
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue