Proper Library Handling (#543)

This commit is contained in:
Ethan Pippin 2022-08-29 20:29:24 -06:00 committed by GitHub
parent 3b755adf87
commit ce38efb3ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 77 additions and 78 deletions

View File

@ -36,10 +36,10 @@ final class MovieLibrariesCoordinator: NavigationCoordinatable {
}
func makeLibrary(library: BaseItemDto) -> LibraryCoordinator {
LibraryCoordinator(viewModel: LibraryViewModel(parentID: library.id), title: library.title)
LibraryCoordinator(viewModel: LibraryViewModel(library: library), title: library.title)
}
func makeRootLibrary(library: BaseItemDto) -> LibraryCoordinator {
LibraryCoordinator(viewModel: LibraryViewModel(parentID: library.id), title: library.title)
LibraryCoordinator(viewModel: LibraryViewModel(library: library), title: library.title)
}
}

View File

@ -36,10 +36,10 @@ final class TVLibrariesCoordinator: NavigationCoordinatable {
}
func makeLibrary(library: BaseItemDto) -> LibraryCoordinator {
LibraryCoordinator(viewModel: LibraryViewModel(parentID: library.id), title: library.title)
LibraryCoordinator(viewModel: LibraryViewModel(library: library), title: library.title)
}
func makeRootLibrary(library: BaseItemDto) -> LibraryCoordinator {
LibraryCoordinator(viewModel: LibraryViewModel(parentID: library.id), title: library.title)
LibraryCoordinator(viewModel: LibraryViewModel(library: library), title: library.title)
}
}

View File

@ -35,7 +35,7 @@ extension BaseItemDto: Poster {
var showTitle: Bool {
switch type {
case .episode, .series, .movie, .boxSet:
case .episode, .series, .movie, .boxSet, .collectionFolder:
return Defaults[.Customization.showPosterLabels]
default:
return true

View File

@ -9,6 +9,8 @@
import JellyfinAPI
import SwiftUI
// TODO: Look at something better that possibly doesn't depend on the viewmodel
// and accomodates favorites and liveTV better
struct LibraryItem: Equatable, Poster {
var library: BaseItemDto
@ -29,4 +31,12 @@ struct LibraryItem: Equatable, Poster {
lhs.library == rhs.library &&
lhs.viewModel.libraryImages[lhs.library.id ?? ""] == rhs.viewModel.libraryImages[rhs.library.id ?? ""]
}
static func favorites(viewModel: MediaViewModel) -> LibraryItem {
.init(library: .init(name: L10n.favorites, collectionType: "favorites"), viewModel: viewModel)
}
static func liveTV(viewModel: MediaViewModel) -> LibraryItem {
.init(library: .init(name: "LiveTV", collectionType: "liveTV"), viewModel: viewModel)
}
}

View File

@ -28,7 +28,6 @@ extension Defaults.Keys {
static let outOfNetworkBandwidth = Key<Int>("OutOfNetworkBandwidth", default: 40_000_000, suite: .generalSuite)
enum Customization {
static let showFlattenView = Key<Bool>("showFlattenView", default: true, suite: .generalSuite)
static let itemViewType = Key<ItemViewType>("itemViewType", default: .compactLogo, suite: .generalSuite)
static let showPosterLabels = Key<Bool>("showPosterLabels", default: true, suite: .generalSuite)

View File

@ -11,28 +11,24 @@ import Defaults
import JellyfinAPI
import UIKit
// TODO: Look at refactoring
final class LibraryViewModel: ViewModel {
@Default(.Customization.Library.gridPosterType)
var libraryGridPosterType
@Published
var items: [BaseItemDto] = []
@Published
var totalPages = 0
@Published
var currentPage = 0
@Published
var hasNextPage = false
// temp
private var currentPage = 0
private var hasNextPage = true
@Published
var filters: LibraryFilters
var parentID: String?
var person: BaseItemPerson?
var genre: NameGuidPair?
var studio: NameGuidPair?
@Default(.Customization.Library.gridPosterType)
private var libraryGridPosterType
let library: BaseItemDto?
let person: BaseItemPerson?
let genre: NameGuidPair?
let studio: NameGuidPair?
private var pageItemSize: Int {
let height = libraryGridPosterType == .portrait ? libraryGridPosterType.width * 1.5 : libraryGridPosterType.width / 1.77
@ -48,13 +44,13 @@ final class LibraryViewModel: ViewModel {
}
init(
parentID: String? = nil,
library: BaseItemDto? = nil,
person: BaseItemPerson? = nil,
genre: NameGuidPair? = nil,
studio: NameGuidPair? = nil,
filters: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], sortBy: [.name])
) {
self.parentID = parentID
self.library = library
self.person = person
self.genre = genre
self.studio = studio
@ -78,31 +74,33 @@ final class LibraryViewModel: ViewModel {
let personIDs: [String] = [person].compactMap(\.?.id)
let studioIDs: [String] = [studio].compactMap(\.?.id)
let genreIDs: [String]
if filters.withGenres.isEmpty {
genreIDs = [genre].compactMap(\.?.id)
} else {
genreIDs = filters.withGenres.compactMap(\.id)
}
let sortBy = filters.sortBy.map(\.rawValue)
let queryRecursive = Defaults[.Customization.showFlattenView] || filters.filters.contains(.isFavorite) ||
self.person != nil ||
self.genre != nil ||
self.studio != nil
let includeItemTypes: [BaseItemKind]
if filters.filters.contains(.isFavorite) {
includeItemTypes = [.movie, .series, .season, .episode, .boxSet]
includeItemTypes = [.movie, .boxSet, .series, .season, .episode]
} else if library?.collectionType == "folders" {
includeItemTypes = [.collectionFolder]
} else {
includeItemTypes = [.movie, .series, .boxSet] + (Defaults[.Customization.showFlattenView] ? [] : [.folder])
includeItemTypes = [.movie, .series, .boxSet]
}
ItemsAPI.getItemsByUserId(
userId: SessionManager.main.currentLogin.user.id,
startIndex: currentPage * pageItemSize,
limit: pageItemSize,
recursive: queryRecursive,
recursive: true,
searchTerm: nil,
sortOrder: filters.sortOrder.compactMap { SortOrder(rawValue: $0.rawValue) },
parentId: parentID,
parentId: library?.id,
fields: ItemFields.allCases,
includeItemTypes: includeItemTypes,
filters: filters.filters,
@ -118,18 +116,18 @@ final class LibraryViewModel: ViewModel {
.sink(receiveCompletion: { [weak self] completion in
self?.handleAPIRequestError(completion: completion)
}, receiveValue: { [weak self] response in
guard !(response.items?.isEmpty ?? false) else {
self?.hasNextPage = false
return
}
guard let self = self else { return }
let totalPages = ceil(Double(response.totalRecordCount ?? 0) / Double(self.pageItemSize))
self.totalPages = Int(totalPages)
self.hasNextPage = self.currentPage < self.totalPages - 1
self.items.append(contentsOf: response.items ?? [])
self?.items.append(contentsOf: response.items ?? [])
})
.store(in: &cancellables)
}
func requestNextPageAsync() {
guard hasNextPage else { return }
currentPage += 1
requestItemsAsync(with: filters)
}

View File

@ -13,15 +13,21 @@ import JellyfinAPI
final class MediaViewModel: ViewModel {
@Published
var libraries: [BaseItemDto] = []
private var libraries: [LibraryItem] = []
@Published
var libraryImages: [String: [ImageSource]] = [:]
private var supportedLibraries: [String] {
["movies", "tvshows", "unknown"]
.appending("livetv", if: Defaults[.Experimental.liveTVAlphaEnabled])
@Default(.Experimental.liveTVAlphaEnabled)
private var liveTVEnabled
var libraryItems: [LibraryItem] {
[.init(library: .init(name: L10n.favorites, collectionType: "favorites"), viewModel: self)]
.appending(.init(library: .init(name: "LiveTV", collectionType: "liveTV"), viewModel: self), if: liveTVEnabled)
.appending(libraries)
}
private static let supportedCollectionTypes: [String] = ["boxsets", "folders", "movies", "tvshows", "unknown"]
override init() {
super.init()
@ -31,15 +37,16 @@ final class MediaViewModel: ViewModel {
func requestLibraries() {
UserViewsAPI.getUserViews(userId: SessionManager.main.currentLogin.user.id)
.trackActivity(loading)
.sink(receiveCompletion: { completion in
self.handleAPIRequestError(completion: completion)
}, receiveValue: { response in
guard let items = response.items else { return }
self.libraries = items.filter { self.supportedLibraries.contains($0.collectionType ?? "unknown") }
self.libraries.forEach {
let filteredLibraries = items.filter { Self.supportedCollectionTypes.contains($0.collectionType ?? "unknown") }
filteredLibraries.forEach {
self.getRandomItemImageSource(with: nil, id: $0.id, key: $0.id ?? "")
}
self.libraries = filteredLibraries.map { .init(library: $0, viewModel: self) }
})
.store(in: &cancellables)
}

View File

@ -22,7 +22,7 @@ struct LatestInLibraryView: View {
Button {
router.route(to: \.library, (
viewModel: .init(
parentID: viewModel.library.id!,
library: viewModel.library,
filters: LibraryFilters(
filters: [],
sortOrder: [.descending],

View File

@ -13,25 +13,25 @@ import SwiftUI
struct MediaView: View {
@EnvironmentObject
private var tabRouter: MainCoordinator.Router
@EnvironmentObject
private var router: MediaCoordinator.Router
@ObservedObject
var viewModel: MediaViewModel
private var libraryItems: [LibraryItem] {
[LibraryItem(library: .init(name: L10n.favorites, id: "favorites"), viewModel: viewModel)] +
viewModel.libraries.map { LibraryItem(library: $0, viewModel: viewModel) }
}
var body: some View {
CollectionView(items: libraryItems) { _, item, _ in
CollectionView(items: viewModel.libraryItems) { _, item, _ in
PosterButton(item: item, type: .landscape)
.scaleItem(0.8)
.onSelect { _ in
if item.library.id == "favorites" {
switch item.library.collectionType {
case "favorites":
router.route(to: \.library, (viewModel: .init(filters: .favorites), title: ""))
} else {
router.route(to: \.library, (viewModel: .init(parentID: item.library.id), title: ""))
case "liveTV":
tabRouter.root(\.liveTV)
default:
router.route(to: \.library, (viewModel: .init(library: item.library), title: ""))
}
}
.imageOverlay { _ in

View File

@ -13,8 +13,6 @@ struct CustomizeViewsSettings: View {
@Default(.Customization.showPosterLabels)
var showPosterLabels
@Default(.Customization.showFlattenView)
var showFlattenView
var body: some View {
Form {
@ -22,8 +20,6 @@ struct CustomizeViewsSettings: View {
Toggle(L10n.showPosterLabels, isOn: $showPosterLabels)
Toggle(L10n.showFlattenView, isOn: $showFlattenView)
} header: {
L10n.customize.text
}

View File

@ -24,7 +24,7 @@ struct LatestInLibraryView: View {
PosterHStack(title: L10n.latestWithString(viewModel.library.displayName), type: latestInLibraryPosterType, items: viewModel.items)
.trailing {
Button {
let libraryViewModel = LibraryViewModel(parentID: viewModel.library.id, filters: HomeViewModel.recentFilterSet)
let libraryViewModel = LibraryViewModel(library: viewModel.library, filters: HomeViewModel.recentFilterSet)
homeRouter.route(to: \.library, (viewModel: libraryViewModel, title: viewModel.library.displayName))
} label: {
HStack {

View File

@ -44,6 +44,8 @@ struct ItemView: View {
}
case .person:
LibraryView(viewModel: .init(person: .init(id: item.id)))
case .collectionFolder:
LibraryView(viewModel: .init(library: item))
default:
Text(L10n.notImplementedYetWithType(item.type ?? "--"))
}

View File

@ -125,7 +125,7 @@ struct LibraryView: View {
.route(to: \.filter, (
filters: $viewModel.filters,
enabledFilterType: viewModel.enabledFilterType,
parentId: viewModel.parentID ?? ""
parentId: viewModel.library?.id ?? ""
))
} label: {
Image(systemName: "line.horizontal.3.decrease.circle")

View File

@ -7,7 +7,6 @@
//
import CollectionView
import Defaults
import JellyfinAPI
import Stinsen
import SwiftUI
@ -18,14 +17,6 @@ struct MediaView: View {
private var router: MediaCoordinator.Router
@ObservedObject
var viewModel: MediaViewModel
@Default(.Experimental.liveTVAlphaEnabled)
var liveTVEnabled
private var libraryItems: [LibraryItem] {
[LibraryItem(library: .init(name: L10n.favorites, id: "favorites"), viewModel: viewModel)]
.appending(.init(library: .init(name: "LiveTV", id: "liveTV"), viewModel: viewModel), if: liveTVEnabled)
.appending(viewModel.libraries.map { LibraryItem(library: $0, viewModel: viewModel) })
}
private var gridLayout: NSCollectionLayoutSection.GridLayoutMode {
if UIDevice.isPhone {
@ -36,16 +27,17 @@ struct MediaView: View {
}
var body: some View {
CollectionView(items: libraryItems) { _, item, _ in
CollectionView(items: viewModel.libraryItems) { _, item, _ in
PosterButton(item: item, type: .landscape)
.scaleItem(UIDevice.isPhone ? 0.9 : 1)
.onSelect { _ in
if item.library.id == "favorites" {
switch item.library.collectionType {
case "favorites":
router.route(to: \.library, (viewModel: .init(filters: .favorites), title: ""))
} else if item.library.id == "liveTV" {
case "liveTV":
router.route(to: \.liveTV)
} else {
router.route(to: \.library, (viewModel: .init(parentID: item.library.id), title: ""))
default:
router.route(to: \.library, (viewModel: .init(library: item.library), title: ""))
}
}
.imageOverlay { _ in

View File

@ -11,8 +11,6 @@ import SwiftUI
struct CustomizeViewsSettings: View {
@Default(.Customization.showFlattenView)
var showFlattenView
@Default(.Customization.itemViewType)
var itemViewType
@ -45,9 +43,6 @@ struct CustomizeViewsSettings: View {
var body: some View {
List {
Section {
Toggle(L10n.showFlattenView, isOn: $showFlattenView)
Picker(L10n.items, selection: $itemViewType) {
ForEach(ItemViewType.allCases, id: \.self) { type in
Text(type.localizedName).tag(type.rawValue)