diff --git a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift index 9a4dcf1d..2cc57295 100644 --- a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift +++ b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift @@ -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) diff --git a/Shared/Generated/Strings.swift b/Shared/Generated/Strings.swift index 561ba3af..854d717b 100644 --- a/Shared/Generated/Strings.swift +++ b/Shared/Generated/Strings.swift @@ -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 diff --git a/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift b/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift index 3cf1480c..c94abfc8 100644 --- a/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift +++ b/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift @@ -41,6 +41,7 @@ extension Defaults.Keys { // Customize settings static let showPosterLabels = Key("showPosterLabels", default: true, suite: SwiftfinStore.Defaults.generalSuite) static let showCastAndCrew = Key("showCastAndCrew", default: true, suite: SwiftfinStore.Defaults.generalSuite) + static let showFlattenView = Key("showFlattenView", default: true, suite: SwiftfinStore.Defaults.generalSuite) // Video player / overlay settings static let overlayType = Key("overlayType", default: .normal, suite: SwiftfinStore.Defaults.generalSuite) diff --git a/Shared/ViewModels/LibraryViewModel.swift b/Shared/ViewModels/LibraryViewModel.swift index 12581b39..0a9bd1db 100644 --- a/Shared/ViewModels/LibraryViewModel.swift +++ b/Shared/ViewModels/LibraryViewModel.swift @@ -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, diff --git a/Swiftfin tvOS/Views/ItemView/ItemView.swift b/Swiftfin tvOS/Views/ItemView/ItemView.swift index 0d78e0dc..1667deec 100644 --- a/Swiftfin tvOS/Views/ItemView/ItemView.swift +++ b/Swiftfin tvOS/Views/ItemView/ItemView.swift @@ -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 ?? "")) diff --git a/Swiftfin tvOS/Views/LibraryListView.swift b/Swiftfin tvOS/Views/LibraryListView.swift index d4e5436d..9001544d 100644 --- a/Swiftfin tvOS/Views/LibraryListView.swift +++ b/Swiftfin tvOS/Views/LibraryListView.swift @@ -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 { diff --git a/Swiftfin tvOS/Views/LibraryView.swift b/Swiftfin tvOS/Views/LibraryView.swift index 6ee1fe63..5c1f397b 100644 --- a/Swiftfin tvOS/Views/LibraryView.swift +++ b/Swiftfin tvOS/Views/LibraryView.swift @@ -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 { diff --git a/Swiftfin tvOS/Views/SettingsView/CustomizeViewsSettings.swift b/Swiftfin tvOS/Views/SettingsView/CustomizeViewsSettings.swift index 65764e6f..78ccb5b6 100644 --- a/Swiftfin tvOS/Views/SettingsView/CustomizeViewsSettings.swift +++ b/Swiftfin tvOS/Views/SettingsView/CustomizeViewsSettings.swift @@ -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 diff --git a/Swiftfin/Views/ItemView/ItemView.swift b/Swiftfin/Views/ItemView/ItemView.swift index 2433b1dc..4090aa3d 100644 --- a/Swiftfin/Views/ItemView/ItemView.swift +++ b/Swiftfin/Views/ItemView/ItemView.swift @@ -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) diff --git a/Swiftfin/Views/LibraryListView.swift b/Swiftfin/Views/LibraryListView.swift index 26c25fb6..a7ebe468 100644 --- a/Swiftfin/Views/LibraryListView.swift +++ b/Swiftfin/Views/LibraryListView.swift @@ -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() } diff --git a/Swiftfin/Views/LibraryView.swift b/Swiftfin/Views/LibraryView.swift index d2efe0f3..57fa4ede 100644 --- a/Swiftfin/Views/LibraryView.swift +++ b/Swiftfin/Views/LibraryView.swift @@ -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) } } } diff --git a/Swiftfin/Views/SettingsView/CustomizeViewsSettings.swift b/Swiftfin/Views/SettingsView/CustomizeViewsSettings.swift index 2cbe816d..e443a738 100644 --- a/Swiftfin/Views/SettingsView/CustomizeViewsSettings.swift +++ b/Swiftfin/Views/SettingsView/CustomizeViewsSettings.swift @@ -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 diff --git a/Translations/en.lproj/Localizable.strings b/Translations/en.lproj/Localizable.strings index 454c8e7e..abc407ba 100644 Binary files a/Translations/en.lproj/Localizable.strings and b/Translations/en.lproj/Localizable.strings differ