Fix Folder Libraries (#555)

This commit is contained in:
Ethan Pippin 2022-09-03 23:19:45 -06:00 committed by GitHub
parent 14f1219500
commit 2a3617bd47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 78 additions and 39 deletions

View File

@ -48,6 +48,8 @@ final class LibraryCoordinator: NavigationCoordinatable {
var item = makeItem var item = makeItem
@Route(.modal) @Route(.modal)
var filter = makeFilter var filter = makeFilter
@Route(.push)
var library = makeLibrary
#endif #endif
private let parameters: Parameters private let parameters: Parameters
@ -77,5 +79,9 @@ final class LibraryCoordinator: NavigationCoordinatable {
func makeFilter(parameters: FilterCoordinator.Parameters) -> NavigationViewCoordinator<FilterCoordinator> { func makeFilter(parameters: FilterCoordinator.Parameters) -> NavigationViewCoordinator<FilterCoordinator> {
NavigationViewCoordinator(FilterCoordinator(parameters: parameters)) NavigationViewCoordinator(FilterCoordinator(parameters: parameters))
} }
func makeLibrary(parameters: LibraryCoordinator.Parameters) -> LibraryCoordinator {
LibraryCoordinator(parameters: parameters)
}
#endif #endif
} }

View File

@ -86,26 +86,26 @@ final class LibraryViewModel: ViewModel {
} }
} }
var recursive = true
let includeItemTypes: [BaseItemKind] let includeItemTypes: [BaseItemKind]
if filters.filters.contains(ItemFilter.isFavorite.filter) { if filters.filters.contains(ItemFilter.isFavorite.filter) {
includeItemTypes = [.movie, .boxSet, .series, .season, .episode] includeItemTypes = [.movie, .boxSet, .series, .season, .episode]
} else if type == .folders { } else if type == .folders {
includeItemTypes = [.collectionFolder] recursive = false
includeItemTypes = [.movie, .boxSet, .series, .folder, .collectionFolder]
} else { } else {
includeItemTypes = [.movie, .series, .boxSet] includeItemTypes = [.movie, .boxSet, .series]
} }
let excludedIDs: [String]? var excludedIDs: [String]?
if filters.sortBy.first == SortBy.random.filter { if filters.sortBy.first == SortBy.random.filter {
excludedIDs = items.compactMap(\.id) excludedIDs = items.compactMap(\.id)
} else {
excludedIDs = nil
} }
let genreIDs = filters.genres.compactMap(\.id) let genreIDs = filters.genres.compactMap(\.id)
let sortBy: [String] = filters.sortBy.map(\.filterName) let sortBy: [String] = filters.sortBy.map(\.filterName).appending("IsFolder")
let sortOrder = filters.sortOrder.map { SortOrder(rawValue: $0.filterName) ?? .ascending } let sortOrder = filters.sortOrder.map { SortOrder(rawValue: $0.filterName) ?? .ascending }
let itemFilters: [ItemFilter] = filters.filters.compactMap { .init(rawValue: $0.filterName) } let itemFilters: [ItemFilter] = filters.filters.compactMap { .init(rawValue: $0.filterName) }
let tags: [String] = filters.tags.map(\.filterName) let tags: [String] = filters.tags.map(\.filterName)
@ -115,7 +115,7 @@ final class LibraryViewModel: ViewModel {
excludeItemIds: excludedIDs, excludeItemIds: excludedIDs,
startIndex: currentPage * pageItemSize, startIndex: currentPage * pageItemSize,
limit: pageItemSize, limit: pageItemSize,
recursive: true, recursive: recursive,
sortOrder: sortOrder, sortOrder: sortOrder,
parentId: libraryID, parentId: libraryID,
fields: ItemFields.allCases, fields: ItemFields.allCases,

View File

@ -14,10 +14,10 @@ struct PosterButton<Item: Poster, Content: View, ImageOverlay: View, ContextMenu
private var type: PosterType private var type: PosterType
private var itemScale: CGFloat private var itemScale: CGFloat
private var horizontalAlignment: HorizontalAlignment private var horizontalAlignment: HorizontalAlignment
private var content: (Item) -> Content private var content: () -> Content
private var imageOverlay: (Item) -> ImageOverlay private var imageOverlay: () -> ImageOverlay
private var contextMenu: (Item) -> ContextMenu private var contextMenu: () -> ContextMenu
private var onSelect: (Item) -> Void private var onSelect: () -> Void
private var singleImage: Bool private var singleImage: Bool
private var itemWidth: CGFloat { private var itemWidth: CGFloat {
@ -29,10 +29,10 @@ struct PosterButton<Item: Poster, Content: View, ImageOverlay: View, ContextMenu
type: PosterType, type: PosterType,
itemScale: CGFloat, itemScale: CGFloat,
horizontalAlignment: HorizontalAlignment, horizontalAlignment: HorizontalAlignment,
@ViewBuilder content: @escaping (Item) -> Content, @ViewBuilder content: @escaping () -> Content,
@ViewBuilder imageOverlay: @escaping (Item) -> ImageOverlay, @ViewBuilder imageOverlay: @escaping () -> ImageOverlay,
@ViewBuilder contextMenu: @escaping (Item) -> ContextMenu, @ViewBuilder contextMenu: @escaping () -> ContextMenu,
onSelect: @escaping (Item) -> Void, onSelect: @escaping () -> Void,
singleImage: Bool singleImage: Bool
) { ) {
self.item = item self.item = item
@ -49,7 +49,7 @@ struct PosterButton<Item: Poster, Content: View, ImageOverlay: View, ContextMenu
var body: some View { var body: some View {
VStack(alignment: horizontalAlignment) { VStack(alignment: horizontalAlignment) {
Button { Button {
onSelect(item) onSelect()
} label: { } label: {
Group { Group {
switch type { switch type {
@ -60,17 +60,17 @@ struct PosterButton<Item: Poster, Content: View, ImageOverlay: View, ContextMenu
} }
} }
.overlay { .overlay {
imageOverlay(item) imageOverlay()
.posterStyle(type: type, width: itemWidth) .posterStyle(type: type, width: itemWidth)
} }
} }
.contextMenu(menuItems: { .contextMenu(menuItems: {
contextMenu(item) contextMenu()
}) })
.posterStyle(type: type, width: itemWidth) .posterStyle(type: type, width: itemWidth)
.posterShadow() .posterShadow()
content(item) content()
} }
.frame(width: itemWidth) .frame(width: itemWidth)
} }
@ -86,10 +86,10 @@ extension PosterButton where Content == PosterButtonDefaultContentView<Item>,
type: type, type: type,
itemScale: 1, itemScale: 1,
horizontalAlignment: .leading, horizontalAlignment: .leading,
content: { PosterButtonDefaultContentView(item: $0) }, content: { PosterButtonDefaultContentView(item: item) },
imageOverlay: { _ in EmptyView() }, imageOverlay: { EmptyView() },
contextMenu: { _ in EmptyView() }, contextMenu: { EmptyView() },
onSelect: { _ in }, onSelect: {},
singleImage: singleImage singleImage: singleImage
) )
} }
@ -109,7 +109,7 @@ extension PosterButton {
} }
@ViewBuilder @ViewBuilder
func content<C: View>(@ViewBuilder _ content: @escaping (Item) -> C) -> PosterButton<Item, C, ImageOverlay, ContextMenu> { func content<C: View>(@ViewBuilder _ content: @escaping () -> C) -> PosterButton<Item, C, ImageOverlay, ContextMenu> {
PosterButton<Item, C, ImageOverlay, ContextMenu>( PosterButton<Item, C, ImageOverlay, ContextMenu>(
item: item, item: item,
type: type, type: type,
@ -124,7 +124,7 @@ extension PosterButton {
} }
@ViewBuilder @ViewBuilder
func imageOverlay<O: View>(@ViewBuilder _ imageOverlay: @escaping (Item) -> O) -> PosterButton<Item, Content, O, ContextMenu> { func imageOverlay<O: View>(@ViewBuilder _ imageOverlay: @escaping () -> O) -> PosterButton<Item, Content, O, ContextMenu> {
PosterButton<Item, Content, O, ContextMenu>( PosterButton<Item, Content, O, ContextMenu>(
item: item, item: item,
type: type, type: type,
@ -139,7 +139,7 @@ extension PosterButton {
} }
@ViewBuilder @ViewBuilder
func contextMenu<M: View>(@ViewBuilder _ contextMenu: @escaping (Item) -> M) -> PosterButton<Item, Content, ImageOverlay, M> { func contextMenu<M: View>(@ViewBuilder _ contextMenu: @escaping () -> M) -> PosterButton<Item, Content, ImageOverlay, M> {
PosterButton<Item, Content, ImageOverlay, M>( PosterButton<Item, Content, ImageOverlay, M>(
item: item, item: item,
type: type, type: type,
@ -153,7 +153,7 @@ extension PosterButton {
) )
} }
func onSelect(_ action: @escaping (Item) -> Void) -> Self { func onSelect(_ action: @escaping () -> Void) -> Self {
var copy = self var copy = self
copy.onSelect = action copy.onSelect = action
return copy return copy

View File

@ -65,9 +65,9 @@ struct PosterHStack<Item: Poster, Content: View, ImageOverlay: View, ContextMenu
ForEach(items, id: \.hashValue) { item in ForEach(items, id: \.hashValue) { item in
PosterButton(item: item, type: type) PosterButton(item: item, type: type)
.scaleItem(itemScale) .scaleItem(itemScale)
.imageOverlay(imageOverlay) .imageOverlay { imageOverlay(item) }
.contextMenu(contextMenu) .contextMenu { contextMenu(item) }
.onSelect(onSelect) .onSelect { onSelect(item) }
} }
} }
.padding(.horizontal) .padding(.horizontal)

View File

@ -22,7 +22,7 @@ struct EpisodeCard<RowManager: EpisodesRowManager>: View {
var body: some View { var body: some View {
PosterButton(item: episode, type: .landscape, singleImage: true) PosterButton(item: episode, type: .landscape, singleImage: true)
.scaleItem(1.2) .scaleItem(1.2)
.imageOverlay { _ in .imageOverlay {
if let progress = episode.progress { if let progress = episode.progress {
LandscapePosterProgressBar( LandscapePosterProgressBar(
title: progress, title: progress,
@ -40,7 +40,7 @@ struct EpisodeCard<RowManager: EpisodesRowManager>: View {
} }
} }
} }
.content { _ in .content {
Button { Button {
router.route(to: \.item, episode) router.route(to: \.item, episode)
} label: { } label: {
@ -78,7 +78,7 @@ struct EpisodeCard<RowManager: EpisodesRowManager>: View {
} }
} }
} }
.onSelect { _ in .onSelect {
episode.createVideoPlayerViewModel() episode.createVideoPlayerViewModel()
.sink { completion in .sink { completion in
self.viewModel.handleAPIRequestError(completion: completion) self.viewModel.handleAPIRequestError(completion: completion)

View File

@ -45,7 +45,8 @@ struct ItemView: View {
case .person: case .person:
LibraryView(viewModel: .init(parent: item, type: .person)) LibraryView(viewModel: .init(parent: item, type: .person))
case .collectionFolder: case .collectionFolder:
LibraryView(viewModel: .init(parent: item, type: .folders)) Text("Here")
// LibraryView(viewModel: .init(parent: item, type: .folders))
default: default:
Text(L10n.notImplementedYetWithType(item.type ?? "--")) Text(L10n.notImplementedYetWithType(item.type ?? "--"))
} }

View File

@ -15,10 +15,11 @@ struct LibraryItemRow: View {
private var router: LibraryCoordinator.Router private var router: LibraryCoordinator.Router
let item: BaseItemDto let item: BaseItemDto
private var onSelect: () -> Void
var body: some View { var body: some View {
Button { Button {
router.route(to: \.item, item) onSelect()
} label: { } label: {
HStack(alignment: .bottom) { HStack(alignment: .bottom) {
ImageView(item.portraitPosterImageSource(maxWidth: 60)) ImageView(item.portraitPosterImageSource(maxWidth: 60))
@ -55,3 +56,16 @@ struct LibraryItemRow: View {
} }
} }
} }
extension LibraryItemRow {
init(item: BaseItemDto) {
self.item = item
self.onSelect = {}
}
func onSelect(_ action: @escaping () -> Void) -> Self {
var copy = self
copy.onSelect = action
return copy
}
}

View File

@ -8,6 +8,7 @@
import CollectionView import CollectionView
import Defaults import Defaults
import JellyfinAPI
import SwiftUI import SwiftUI
struct LibraryView: View { struct LibraryView: View {
@ -40,10 +41,27 @@ struct LibraryView: View {
} }
} }
private func baseItemOnSelect(_ item: BaseItemDto) {
if let baseParent = viewModel.parent as? BaseItemDto {
if baseParent.collectionType == "folders" {
router.route(to: \.library, .init(parent: item, type: .folders, filters: .init()))
} else if item.type == .folder {
router.route(to: \.library, .init(parent: item, type: .library, filters: .init()))
} else {
router.route(to: \.item, item)
}
} else {
router.route(to: \.item, item)
}
}
@ViewBuilder @ViewBuilder
private var libraryListView: some View { private var libraryListView: some View {
CollectionView(items: viewModel.items) { _, item, _ in CollectionView(items: viewModel.items) { _, item, _ in
LibraryItemRow(item: item) LibraryItemRow(item: item)
.onSelect {
baseItemOnSelect(item)
}
.padding() .padding()
} }
.layout { _, layoutEnvironment in .layout { _, layoutEnvironment in
@ -65,8 +83,8 @@ struct LibraryView: View {
CollectionView(items: viewModel.items) { _, item, _ in CollectionView(items: viewModel.items) { _, item, _ in
PosterButton(item: item, type: libraryGridPosterType) PosterButton(item: item, type: libraryGridPosterType)
.scaleItem(libraryGridPosterType == .landscape && UIDevice.isPhone ? 0.85 : 1) .scaleItem(libraryGridPosterType == .landscape && UIDevice.isPhone ? 0.85 : 1)
.onSelect { item in .onSelect {
router.route(to: \.item, item) baseItemOnSelect(item)
} }
} }
.layout { _, layoutEnvironment in .layout { _, layoutEnvironment in

View File

@ -30,7 +30,7 @@ struct MediaView: View {
CollectionView(items: viewModel.libraryItems) { _, item, _ in CollectionView(items: viewModel.libraryItems) { _, item, _ in
PosterButton(item: item, type: .landscape) PosterButton(item: item, type: .landscape)
.scaleItem(UIDevice.isPhone ? 0.85 : 1) .scaleItem(UIDevice.isPhone ? 0.85 : 1)
.onSelect { _ in .onSelect {
switch item.library.collectionType { switch item.library.collectionType {
case "favorites": case "favorites":
router.route(to: \.library, .init(parent: item.library, type: .library, filters: .favorites)) router.route(to: \.library, .init(parent: item.library, type: .library, filters: .favorites))
@ -42,7 +42,7 @@ struct MediaView: View {
router.route(to: \.library, .init(parent: item.library, type: .library, filters: .init())) router.route(to: \.library, .init(parent: item.library, type: .library, filters: .init()))
} }
} }
.imageOverlay { _ in .imageOverlay {
ZStack { ZStack {
Color.black Color.black
.opacity(0.5) .opacity(0.5)