Fix Folder Libraries (#555)
This commit is contained in:
parent
14f1219500
commit
2a3617bd47
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 ?? "--"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue