mirror of
https://github.com/jellyfin/Swiftfin.git
synced 2025-02-24 09:12:19 +00:00
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 {
|
||||
guard let seriesId = seriesId else {
|
||||
return getPrimaryImage(maxWidth: maxWidth)
|
||||
}
|
||||
|
||||
let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
|
||||
let urlString = ImageAPI.getItemImageWithRequestBuilder(itemId: seriesId ?? "",
|
||||
let urlString = ImageAPI.getItemImageWithRequestBuilder(itemId: seriesId,
|
||||
imageType: .primary,
|
||||
maxWidth: Int(x),
|
||||
quality: 96,
|
||||
@ -227,6 +231,7 @@ public extension BaseItemDto {
|
||||
case series = "Series"
|
||||
case boxset = "BoxSet"
|
||||
case collectionFolder = "CollectionFolder"
|
||||
case folder = "Folder"
|
||||
|
||||
case unknown
|
||||
|
||||
@ -249,7 +254,7 @@ public extension BaseItemDto {
|
||||
|
||||
func portraitHeaderViewURL(maxWidth: Int) -> URL {
|
||||
switch itemType {
|
||||
case .movie, .season, .series, .boxset, .collectionFolder:
|
||||
case .movie, .season, .series, .boxset, .collectionFolder, .folder:
|
||||
return getPrimaryImage(maxWidth: maxWidth)
|
||||
case .episode:
|
||||
return getSeriesPrimaryImage(maxWidth: maxWidth)
|
||||
|
@ -324,6 +324,8 @@ internal enum L10n {
|
||||
internal static var settings: String { return L10n.tr("Localizable", "settings") }
|
||||
/// Show Cast & Crew
|
||||
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
|
||||
internal static var showMissingEpisodes: String { return L10n.tr("Localizable", "showMissingEpisodes") }
|
||||
/// Show Missing Seasons
|
||||
|
@ -41,6 +41,7 @@ extension Defaults.Keys {
|
||||
// Customize settings
|
||||
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 showFlattenView = Key<Bool>("showFlattenView", default: true, suite: SwiftfinStore.Defaults.generalSuite)
|
||||
|
||||
// Video player / overlay settings
|
||||
static let overlayType = Key<OverlayType>("overlayType", default: .normal, suite: SwiftfinStore.Defaults.generalSuite)
|
||||
|
@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Defaults
|
||||
import Foundation
|
||||
import JellyfinAPI
|
||||
import SwiftUICollection
|
||||
@ -94,10 +95,20 @@ final class LibraryViewModel: ViewModel {
|
||||
genreIDs = filters.withGenres.compactMap(\.id)
|
||||
}
|
||||
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,
|
||||
limit: pageItemSize,
|
||||
recursive: true,
|
||||
recursive: queryRecursive,
|
||||
searchTerm: nil,
|
||||
sortOrder: filters.sortOrder,
|
||||
parentId: parentID,
|
||||
@ -110,9 +121,7 @@ final class LibraryViewModel: ViewModel {
|
||||
.people,
|
||||
.chapters,
|
||||
],
|
||||
includeItemTypes: filters.filters
|
||||
.contains(.isFavorite) ? ["Movie", "Series", "Season", "Episode", "BoxSet"] :
|
||||
["Movie", "Series", "BoxSet"],
|
||||
includeItemTypes: includeItemTypes,
|
||||
filters: filters.filters,
|
||||
sortBy: sortBy,
|
||||
tags: filters.tags,
|
||||
|
@ -62,7 +62,7 @@ struct ItemView: View {
|
||||
} else {
|
||||
SeriesItemView(viewModel: SeriesItemViewModel(item: item))
|
||||
}
|
||||
case .boxset:
|
||||
case .boxset, .folder:
|
||||
CinematicCollectionItemView(viewModel: CollectionItemViewModel(item: item))
|
||||
default:
|
||||
Text(L10n.notImplementedYetWithType(item.type ?? ""))
|
||||
|
@ -21,39 +21,17 @@ struct LibraryListView: View {
|
||||
@Default(.Experimental.liveTVAlphaEnabled)
|
||||
var liveTVAlphaEnabled
|
||||
|
||||
let supportedCollectionTypes = ["movies", "tvshows", "boxsets", "livetv", "other"]
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
LazyVStack {
|
||||
if !viewModel.isLoading {
|
||||
|
||||
if let collectionLibraryItem = viewModel.libraries.first(where: { $0.collectionType == "boxsets" }) {
|
||||
Button {
|
||||
self.libraryListRouter.route(to: \.library,
|
||||
(viewModel: LibraryViewModel(parentID: collectionLibraryItem.id),
|
||||
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
|
||||
ForEach(viewModel.libraries.filter { [self] library in
|
||||
let collectionType = library.collectionType ?? "other"
|
||||
return self.supportedCollectionTypes.contains(collectionType)
|
||||
}, id: \.id) { library in
|
||||
if library.collectionType == "livetv" {
|
||||
if liveTVAlphaEnabled {
|
||||
Button {
|
||||
@ -81,7 +59,8 @@ struct LibraryListView: View {
|
||||
}
|
||||
} else {
|
||||
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: {
|
||||
ZStack {
|
||||
|
@ -56,17 +56,15 @@ struct LibraryView: View {
|
||||
} cell: { _, cell in
|
||||
GeometryReader { _ in
|
||||
if let item = cell.item {
|
||||
if item.type != "Folder" {
|
||||
Button {
|
||||
libraryRouter.route(to: \.modalItem, item)
|
||||
} label: {
|
||||
PortraitItemElement(item: item)
|
||||
}
|
||||
.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||
.onAppear {
|
||||
if item == viewModel.items.last && viewModel.hasNextPage {
|
||||
viewModel.requestNextPageAsync()
|
||||
}
|
||||
Button {
|
||||
libraryRouter.route(to: \.modalItem, item)
|
||||
} label: {
|
||||
PortraitItemElement(item: item)
|
||||
}
|
||||
.buttonStyle(PlainNavigationLinkButtonStyle())
|
||||
.onAppear {
|
||||
if item == viewModel.items.last && viewModel.hasNextPage {
|
||||
viewModel.requestNextPageAsync()
|
||||
}
|
||||
}
|
||||
} else if cell.loadingCell {
|
||||
|
@ -15,6 +15,8 @@ struct CustomizeViewsSettings: View {
|
||||
var showPosterLabels
|
||||
@Default(.showCastAndCrew)
|
||||
var showCastAndCrew
|
||||
@Default(.showFlattenView)
|
||||
var showFlattenView
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
@ -24,6 +26,7 @@ struct CustomizeViewsSettings: View {
|
||||
|
||||
// TODO: Uncomment when cast and crew implemented in item views
|
||||
// Toggle(L10n.showCastAndCrew, isOn: $showCastAndCrew)
|
||||
Toggle(L10n.showFlattenView, isOn: $showFlattenView)
|
||||
|
||||
} header: {
|
||||
L10n.customize.text
|
||||
|
@ -51,7 +51,7 @@ private struct ItemView: View {
|
||||
self.viewModel = EpisodeItemViewModel(item: item)
|
||||
case .series:
|
||||
self.viewModel = SeriesItemViewModel(item: item)
|
||||
case .boxset:
|
||||
case .boxset, .folder:
|
||||
self.viewModel = CollectionItemViewModel(item: item)
|
||||
default:
|
||||
self.viewModel = ItemViewModel(item: item)
|
||||
|
@ -16,6 +16,8 @@ struct LibraryListView: View {
|
||||
@StateObject
|
||||
var viewModel = LibraryListViewModel()
|
||||
|
||||
let supportedCollectionTypes = ["movies", "tvshows", "boxsets", "other"]
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
LazyVStack {
|
||||
@ -42,21 +44,23 @@ struct LibraryListView: View {
|
||||
.padding(.bottom, 5)
|
||||
|
||||
if !viewModel.isLoading {
|
||||
|
||||
if let collectionsLibraryItem = viewModel.libraries.first(where: { $0.collectionType == "boxsets" }) {
|
||||
ForEach(viewModel.libraries.filter { [self] library in
|
||||
let collectionType = library.collectionType ?? "other"
|
||||
return self.supportedCollectionTypes.contains(collectionType)
|
||||
}, id: \.id) { library in
|
||||
Button {
|
||||
libraryListRouter.route(to: \.library,
|
||||
(viewModel: LibraryViewModel(parentID: collectionsLibraryItem.id),
|
||||
title: collectionsLibraryItem.name ?? ""))
|
||||
(viewModel: LibraryViewModel(parentID: library.id),
|
||||
title: library.name ?? ""))
|
||||
} label: {
|
||||
ZStack {
|
||||
ImageView(src: collectionsLibraryItem.getPrimaryImage(maxWidth: 500),
|
||||
bh: collectionsLibraryItem.getPrimaryImageBlurHash())
|
||||
ImageView(src: library.getPrimaryImage(maxWidth: 500), bh: library.getPrimaryImageBlurHash())
|
||||
.opacity(0.4)
|
||||
.accessibilityIgnoresInvertColors()
|
||||
HStack {
|
||||
Spacer()
|
||||
VStack {
|
||||
Text(collectionsLibraryItem.name ?? "")
|
||||
Text(library.name ?? "")
|
||||
.foregroundColor(.white)
|
||||
.font(.title2)
|
||||
.fontWeight(.semibold)
|
||||
@ -71,39 +75,6 @@ struct LibraryListView: View {
|
||||
.shadow(radius: 5)
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
|
||||
ForEach(viewModel.libraries, id: \.id) { library in
|
||||
if library.collectionType ?? "" == "movies" || library.collectionType ?? "" == "tvshows" {
|
||||
Button {
|
||||
libraryListRouter.route(to: \.library,
|
||||
(viewModel: LibraryViewModel(parentID: library.id),
|
||||
title: library.name ?? ""))
|
||||
} label: {
|
||||
ZStack {
|
||||
ImageView(src: library.getPrimaryImage(maxWidth: 500), bh: library.getPrimaryImageBlurHash())
|
||||
.opacity(0.4)
|
||||
.accessibilityIgnoresInvertColors()
|
||||
HStack {
|
||||
Spacer()
|
||||
VStack {
|
||||
Text(library.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)
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ProgressView()
|
||||
}
|
||||
|
@ -45,10 +45,8 @@ struct LibraryView: View {
|
||||
VStack {
|
||||
LazyVGrid(columns: tracks) {
|
||||
ForEach(viewModel.items, id: \.id) { item in
|
||||
if item.type != "Folder" {
|
||||
PortraitItemButton(item: item) { item in
|
||||
libraryRouter.route(to: \.item, item)
|
||||
}
|
||||
PortraitItemButton(item: item) { item in
|
||||
libraryRouter.route(to: \.item, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ struct CustomizeViewsSettings: View {
|
||||
var showPosterLabels
|
||||
@Default(.showCastAndCrew)
|
||||
var showCastAndCrew
|
||||
@Default(.showFlattenView)
|
||||
var showFlattenView
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
@ -22,6 +24,7 @@ struct CustomizeViewsSettings: View {
|
||||
|
||||
Toggle(L10n.showPosterLabels, isOn: $showPosterLabels)
|
||||
Toggle(L10n.showCastAndCrew, isOn: $showCastAndCrew)
|
||||
Toggle(L10n.showFlattenView, isOn: $showFlattenView)
|
||||
|
||||
} header: {
|
||||
L10n.customize.text
|
||||
|
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user