Generic Libraries (#583)
This commit is contained in:
parent
5299f5a9ce
commit
20e0789ce2
|
@ -0,0 +1,55 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import JellyfinAPI
|
||||||
|
import Stinsen
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
// TODO: See if this and LibraryCoordinator can be merged,
|
||||||
|
// along with all corresponding views
|
||||||
|
final class BasicLibraryCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
|
struct Parameters {
|
||||||
|
let title: String?
|
||||||
|
let viewModel: PagingLibraryViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
let stack = NavigationStack(initial: \BasicLibraryCoordinator.start)
|
||||||
|
|
||||||
|
@Root
|
||||||
|
var start = makeStart
|
||||||
|
@Route(.push)
|
||||||
|
var item = makeItem
|
||||||
|
@Route(.push)
|
||||||
|
var library = makeLibrary
|
||||||
|
|
||||||
|
private let parameters: Parameters
|
||||||
|
|
||||||
|
init(parameters: Parameters) {
|
||||||
|
self.parameters = parameters
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
func makeStart() -> some View {
|
||||||
|
BasicLibraryView(viewModel: parameters.viewModel)
|
||||||
|
#if !os(tvOS)
|
||||||
|
.if(parameters.title != nil) { view in
|
||||||
|
view.navigationTitle(parameters.title ?? .emptyDash)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeItem(item: BaseItemDto) -> NavigationViewCoordinator<ItemCoordinator> {
|
||||||
|
NavigationViewCoordinator(ItemCoordinator(item: item))
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeLibrary(parameters: LibraryCoordinator.Parameters) -> NavigationViewCoordinator<LibraryCoordinator> {
|
||||||
|
NavigationViewCoordinator(LibraryCoordinator(parameters: parameters))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import JellyfinAPI
|
||||||
|
import Stinsen
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
final class CastAndCrewLibraryCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
|
let stack = NavigationStack(initial: \CastAndCrewLibraryCoordinator.start)
|
||||||
|
|
||||||
|
@Root
|
||||||
|
var start = makeStart
|
||||||
|
@Route(.push)
|
||||||
|
var library = makeLibrary
|
||||||
|
|
||||||
|
let people: [BaseItemPerson]
|
||||||
|
|
||||||
|
init(people: [BaseItemPerson]) {
|
||||||
|
self.people = people
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
func makeStart() -> some View {
|
||||||
|
CastAndCrewLibraryView(people: people)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeLibrary(parameters: LibraryCoordinator.Parameters) -> LibraryCoordinator {
|
||||||
|
LibraryCoordinator(parameters: parameters)
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,11 +24,15 @@ final class HomeCoordinator: NavigationCoordinatable {
|
||||||
@Route(.modal)
|
@Route(.modal)
|
||||||
var item = makeItem
|
var item = makeItem
|
||||||
@Route(.modal)
|
@Route(.modal)
|
||||||
|
var basicLibrary = makeBasicLibrary
|
||||||
|
@Route(.modal)
|
||||||
var library = makeLibrary
|
var library = makeLibrary
|
||||||
#else
|
#else
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var item = makeItem
|
var item = makeItem
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
|
var basicLibrary = makeBasicLibrary
|
||||||
|
@Route(.push)
|
||||||
var library = makeLibrary
|
var library = makeLibrary
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -41,6 +45,10 @@ final class HomeCoordinator: NavigationCoordinatable {
|
||||||
NavigationViewCoordinator(ItemCoordinator(item: item))
|
NavigationViewCoordinator(ItemCoordinator(item: item))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeBasicLibrary(parameters: BasicLibraryCoordinator.Parameters) -> NavigationViewCoordinator<BasicLibraryCoordinator> {
|
||||||
|
NavigationViewCoordinator(BasicLibraryCoordinator(parameters: parameters))
|
||||||
|
}
|
||||||
|
|
||||||
func makeLibrary(parameters: LibraryCoordinator.Parameters) -> NavigationViewCoordinator<LibraryCoordinator> {
|
func makeLibrary(parameters: LibraryCoordinator.Parameters) -> NavigationViewCoordinator<LibraryCoordinator> {
|
||||||
NavigationViewCoordinator(LibraryCoordinator(parameters: parameters))
|
NavigationViewCoordinator(LibraryCoordinator(parameters: parameters))
|
||||||
}
|
}
|
||||||
|
@ -49,6 +57,10 @@ final class HomeCoordinator: NavigationCoordinatable {
|
||||||
ItemCoordinator(item: item)
|
ItemCoordinator(item: item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeBasicLibrary(parameters: BasicLibraryCoordinator.Parameters) -> BasicLibraryCoordinator {
|
||||||
|
BasicLibraryCoordinator(parameters: parameters)
|
||||||
|
}
|
||||||
|
|
||||||
func makeLibrary(parameters: LibraryCoordinator.Parameters) -> LibraryCoordinator {
|
func makeLibrary(parameters: LibraryCoordinator.Parameters) -> LibraryCoordinator {
|
||||||
LibraryCoordinator(parameters: parameters)
|
LibraryCoordinator(parameters: parameters)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,11 @@ final class ItemCoordinator: NavigationCoordinatable {
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var item = makeItem
|
var item = makeItem
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
|
var basicLibrary = makeBasicLibrary
|
||||||
|
@Route(.push)
|
||||||
var library = makeLibrary
|
var library = makeLibrary
|
||||||
|
@Route(.push)
|
||||||
|
var castAndCrew = makeCastAndCrew
|
||||||
@Route(.modal)
|
@Route(.modal)
|
||||||
var itemOverview = makeItemOverview
|
var itemOverview = makeItemOverview
|
||||||
@Route(.fullScreen)
|
@Route(.fullScreen)
|
||||||
|
@ -32,20 +36,24 @@ final class ItemCoordinator: NavigationCoordinatable {
|
||||||
self.itemDto = item
|
self.itemDto = item
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeLibrary(parameters: LibraryCoordinator.Parameters) -> LibraryCoordinator {
|
|
||||||
LibraryCoordinator(parameters: parameters)
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeItem(item: BaseItemDto) -> ItemCoordinator {
|
func makeItem(item: BaseItemDto) -> ItemCoordinator {
|
||||||
ItemCoordinator(item: item)
|
ItemCoordinator(item: item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeItemOverview(item: BaseItemDto) -> NavigationViewCoordinator<ItemOverviewCoordinator> {
|
func makeBasicLibrary(parameters: BasicLibraryCoordinator.Parameters) -> BasicLibraryCoordinator {
|
||||||
NavigationViewCoordinator(ItemOverviewCoordinator(item: itemDto))
|
BasicLibraryCoordinator(parameters: parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeSeason(item: BaseItemDto) -> ItemCoordinator {
|
func makeLibrary(parameters: LibraryCoordinator.Parameters) -> LibraryCoordinator {
|
||||||
ItemCoordinator(item: item)
|
LibraryCoordinator(parameters: parameters)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeCastAndCrew(people: [BaseItemPerson]) -> CastAndCrewLibraryCoordinator {
|
||||||
|
CastAndCrewLibraryCoordinator(people: people)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeItemOverview(item: BaseItemDto) -> NavigationViewCoordinator<ItemOverviewCoordinator> {
|
||||||
|
NavigationViewCoordinator(ItemOverviewCoordinator(item: itemDto))
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeVideoPlayer(viewModel: VideoPlayerViewModel) -> NavigationViewCoordinator<VideoPlayerCoordinator> {
|
func makeVideoPlayer(viewModel: VideoPlayerViewModel) -> NavigationViewCoordinator<VideoPlayerCoordinator> {
|
||||||
|
|
|
@ -48,10 +48,10 @@ final class LibraryCoordinator: NavigationCoordinatable {
|
||||||
#else
|
#else
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var item = makeItem
|
var item = makeItem
|
||||||
@Route(.modal)
|
|
||||||
var filter = makeFilter
|
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var library = makeLibrary
|
var library = makeLibrary
|
||||||
|
@Route(.modal)
|
||||||
|
var filter = makeFilter
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private let parameters: Parameters
|
private let parameters: Parameters
|
||||||
|
@ -63,9 +63,9 @@ final class LibraryCoordinator: NavigationCoordinatable {
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeStart() -> some View {
|
func makeStart() -> some View {
|
||||||
if let parent = parameters.parent {
|
if let parent = parameters.parent {
|
||||||
LibraryView(viewModel: .init(parent: parent, type: parameters.type, filters: parameters.filters))
|
LibraryView(viewModel: LibraryViewModel(parent: parent, type: parameters.type, filters: parameters.filters))
|
||||||
} else {
|
} else {
|
||||||
LibraryView(viewModel: .init(filters: parameters.filters))
|
LibraryView(viewModel: LibraryViewModel(filters: parameters.filters))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,12 +82,12 @@ final class LibraryCoordinator: NavigationCoordinatable {
|
||||||
ItemCoordinator(item: item)
|
ItemCoordinator(item: item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeFilter(parameters: FilterCoordinator.Parameters) -> NavigationViewCoordinator<FilterCoordinator> {
|
|
||||||
NavigationViewCoordinator(FilterCoordinator(parameters: parameters))
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeLibrary(parameters: LibraryCoordinator.Parameters) -> LibraryCoordinator {
|
func makeLibrary(parameters: LibraryCoordinator.Parameters) -> LibraryCoordinator {
|
||||||
LibraryCoordinator(parameters: parameters)
|
LibraryCoordinator(parameters: parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeFilter(parameters: FilterCoordinator.Parameters) -> NavigationViewCoordinator<FilterCoordinator> {
|
||||||
|
NavigationViewCoordinator(FilterCoordinator(parameters: parameters))
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import SwiftUI
|
||||||
final class MainTabCoordinator: TabCoordinatable {
|
final class MainTabCoordinator: TabCoordinatable {
|
||||||
var child = TabChild(startingItems: [
|
var child = TabChild(startingItems: [
|
||||||
\MainTabCoordinator.home,
|
\MainTabCoordinator.home,
|
||||||
\MainTabCoordinator.tv,
|
\MainTabCoordinator.tvShows,
|
||||||
\MainTabCoordinator.movies,
|
\MainTabCoordinator.movies,
|
||||||
\MainTabCoordinator.search,
|
\MainTabCoordinator.search,
|
||||||
\MainTabCoordinator.media,
|
\MainTabCoordinator.media,
|
||||||
|
@ -23,7 +23,7 @@ final class MainTabCoordinator: TabCoordinatable {
|
||||||
@Route(tabItem: makeHomeTab)
|
@Route(tabItem: makeHomeTab)
|
||||||
var home = makeHome
|
var home = makeHome
|
||||||
@Route(tabItem: makeTvTab)
|
@Route(tabItem: makeTvTab)
|
||||||
var tv = makeTv
|
var tvShows = makeTVShows
|
||||||
@Route(tabItem: makeMoviesTab)
|
@Route(tabItem: makeMoviesTab)
|
||||||
var movies = makeMovies
|
var movies = makeMovies
|
||||||
@Route(tabItem: makeSearchTab)
|
@Route(tabItem: makeSearchTab)
|
||||||
|
@ -45,8 +45,12 @@ final class MainTabCoordinator: TabCoordinatable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeTv() -> NavigationViewCoordinator<TVLibrariesCoordinator> {
|
func makeTVShows() -> NavigationViewCoordinator<BasicLibraryCoordinator> {
|
||||||
NavigationViewCoordinator(TVLibrariesCoordinator(viewModel: TVLibrariesViewModel(), title: L10n.tvShows))
|
let parameters = BasicLibraryCoordinator.Parameters(
|
||||||
|
title: nil,
|
||||||
|
viewModel: ItemTypeLibraryViewModel(itemTypes: [.series], filters: .init())
|
||||||
|
)
|
||||||
|
return NavigationViewCoordinator(BasicLibraryCoordinator(parameters: parameters))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
@ -57,8 +61,12 @@ final class MainTabCoordinator: TabCoordinatable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeMovies() -> NavigationViewCoordinator<MovieLibrariesCoordinator> {
|
func makeMovies() -> NavigationViewCoordinator<BasicLibraryCoordinator> {
|
||||||
NavigationViewCoordinator(MovieLibrariesCoordinator(viewModel: MovieLibrariesViewModel(), title: L10n.movies))
|
let parameters = BasicLibraryCoordinator.Parameters(
|
||||||
|
title: nil,
|
||||||
|
viewModel: ItemTypeLibraryViewModel(itemTypes: [.movie], filters: .init())
|
||||||
|
)
|
||||||
|
return NavigationViewCoordinator(BasicLibraryCoordinator(parameters: parameters))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
//
|
|
||||||
// Swiftfin is subject to the terms of the Mozilla Public
|
|
||||||
// License, v2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import JellyfinAPI
|
|
||||||
import Stinsen
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
final class MovieLibrariesCoordinator: NavigationCoordinatable {
|
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \MovieLibrariesCoordinator.start)
|
|
||||||
|
|
||||||
@Root
|
|
||||||
var start = makeStart
|
|
||||||
@Root
|
|
||||||
var rootLibrary = makeRootLibrary
|
|
||||||
@Route(.push)
|
|
||||||
var library = makeLibrary
|
|
||||||
|
|
||||||
let viewModel: MovieLibrariesViewModel
|
|
||||||
let title: String
|
|
||||||
|
|
||||||
init(viewModel: MovieLibrariesViewModel, title: String) {
|
|
||||||
self.viewModel = viewModel
|
|
||||||
self.title = title
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
func makeStart() -> some View {
|
|
||||||
MovieLibrariesView(viewModel: self.viewModel, title: title)
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeLibrary(library: BaseItemDto) -> LibraryCoordinator {
|
|
||||||
LibraryCoordinator(parameters: .init(parent: library, type: .library, filters: .init()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeRootLibrary(library: BaseItemDto) -> LibraryCoordinator {
|
|
||||||
LibraryCoordinator(parameters: .init(parent: library, type: .library, filters: .init()))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
//
|
|
||||||
// Swiftfin is subject to the terms of the Mozilla Public
|
|
||||||
// License, v2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import JellyfinAPI
|
|
||||||
import Stinsen
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
final class TVLibrariesCoordinator: NavigationCoordinatable {
|
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \TVLibrariesCoordinator.start)
|
|
||||||
|
|
||||||
@Root
|
|
||||||
var start = makeStart
|
|
||||||
@Root
|
|
||||||
var rootLibrary = makeRootLibrary
|
|
||||||
@Route(.push)
|
|
||||||
var library = makeLibrary
|
|
||||||
|
|
||||||
let viewModel: TVLibrariesViewModel
|
|
||||||
let title: String
|
|
||||||
|
|
||||||
init(viewModel: TVLibrariesViewModel, title: String) {
|
|
||||||
self.viewModel = viewModel
|
|
||||||
self.title = title
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
func makeStart() -> some View {
|
|
||||||
TVLibrariesView(viewModel: self.viewModel, title: title)
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeLibrary(library: BaseItemDto) -> LibraryCoordinator {
|
|
||||||
LibraryCoordinator(parameters: .init(parent: library, type: .library, filters: .init()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeRootLibrary(library: BaseItemDto) -> LibraryCoordinator {
|
|
||||||
LibraryCoordinator(parameters: .init(parent: library, type: .library, filters: .init()))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -25,3 +25,9 @@ extension Array {
|
||||||
self + contents
|
self + contents
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension ArraySlice {
|
||||||
|
var asArray: [Element] {
|
||||||
|
Array(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,12 @@ import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
extension BaseItemDto: Displayable {
|
||||||
|
var displayName: String {
|
||||||
|
name ?? .emptyDash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension BaseItemDto: Identifiable {}
|
extension BaseItemDto: Identifiable {}
|
||||||
extension BaseItemDto: LibraryParent {}
|
extension BaseItemDto: LibraryParent {}
|
||||||
|
|
||||||
|
@ -86,10 +92,6 @@ extension BaseItemDto {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
var displayName: String {
|
|
||||||
name ?? .emptyDash
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: ItemDetail
|
// MARK: ItemDetail
|
||||||
|
|
||||||
struct ItemDetail {
|
struct ItemDetail {
|
||||||
|
|
|
@ -10,8 +10,6 @@ import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
// MARK: PortraitImageStackable
|
|
||||||
|
|
||||||
extension BaseItemPerson: Poster {
|
extension BaseItemPerson: Poster {
|
||||||
|
|
||||||
var subtitle: String? {
|
var subtitle: String? {
|
||||||
|
|
|
@ -10,6 +10,14 @@ import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
extension BaseItemPerson: Displayable {
|
||||||
|
var displayName: String {
|
||||||
|
self.name ?? .emptyDash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension BaseItemPerson: LibraryParent {}
|
||||||
|
|
||||||
extension BaseItemPerson {
|
extension BaseItemPerson {
|
||||||
|
|
||||||
// MARK: First Role
|
// MARK: First Role
|
||||||
|
@ -50,11 +58,3 @@ extension BaseItemPerson {
|
||||||
return DisplayedType(rawValue: type) != nil
|
return DisplayedType(rawValue: type) != nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension BaseItemPerson: Displayable {
|
|
||||||
var displayName: String {
|
|
||||||
self.name ?? .emptyDash
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension BaseItemPerson: LibraryParent {}
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import SwiftUI
|
||||||
|
|
||||||
// TODO: Look at something better that possibly doesn't depend on the viewmodel
|
// TODO: Look at something better that possibly doesn't depend on the viewmodel
|
||||||
// and accomodates favorites and liveTV better
|
// and accomodates favorites and liveTV better
|
||||||
struct LibraryItem: Equatable, Poster {
|
struct MediaLibraryItem: Equatable, Poster {
|
||||||
|
|
||||||
var library: BaseItemDto
|
var library: BaseItemDto
|
||||||
var viewModel: MediaViewModel
|
var viewModel: MediaViewModel
|
||||||
|
@ -27,16 +27,16 @@ struct LibraryItem: Equatable, Poster {
|
||||||
viewModel.libraryImages[library.id ?? ""] ?? []
|
viewModel.libraryImages[library.id ?? ""] ?? []
|
||||||
}
|
}
|
||||||
|
|
||||||
static func == (lhs: LibraryItem, rhs: LibraryItem) -> Bool {
|
static func == (lhs: MediaLibraryItem, rhs: MediaLibraryItem) -> Bool {
|
||||||
lhs.library == rhs.library &&
|
lhs.library == rhs.library &&
|
||||||
lhs.viewModel.libraryImages[lhs.library.id ?? ""] == rhs.viewModel.libraryImages[rhs.library.id ?? ""]
|
lhs.viewModel.libraryImages[lhs.library.id ?? ""] == rhs.viewModel.libraryImages[rhs.library.id ?? ""]
|
||||||
}
|
}
|
||||||
|
|
||||||
static func favorites(viewModel: MediaViewModel) -> LibraryItem {
|
static func favorites(viewModel: MediaViewModel) -> MediaLibraryItem {
|
||||||
.init(library: .init(name: L10n.favorites, collectionType: "favorites"), viewModel: viewModel)
|
.init(library: .init(name: L10n.favorites, collectionType: "favorites"), viewModel: viewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func liveTV(viewModel: MediaViewModel) -> LibraryItem {
|
static func liveTV(viewModel: MediaViewModel) -> MediaLibraryItem {
|
||||||
.init(library: .init(name: "LiveTV", collectionType: "liveTV"), viewModel: viewModel)
|
.init(library: .init(name: "LiveTV", collectionType: "liveTV"), viewModel: viewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,12 +13,12 @@ import JellyfinAPI
|
||||||
|
|
||||||
final class HomeViewModel: ViewModel {
|
final class HomeViewModel: ViewModel {
|
||||||
|
|
||||||
@Published
|
|
||||||
var latestAddedItems: [BaseItemDto] = []
|
|
||||||
@Published
|
@Published
|
||||||
var resumeItems: [BaseItemDto] = []
|
var resumeItems: [BaseItemDto] = []
|
||||||
@Published
|
@Published
|
||||||
var nextUpItems: [BaseItemDto] = []
|
var hasNextUp: Bool = false
|
||||||
|
@Published
|
||||||
|
var hasRecentlyAdded: Bool = false
|
||||||
@Published
|
@Published
|
||||||
var librariesShowRecentlyAddedIDs: [String] = []
|
var librariesShowRecentlyAddedIDs: [String] = []
|
||||||
@Published
|
@Published
|
||||||
|
@ -44,7 +44,6 @@ final class HomeViewModel: ViewModel {
|
||||||
librariesShowRecentlyAddedIDs = []
|
librariesShowRecentlyAddedIDs = []
|
||||||
libraries = []
|
libraries = []
|
||||||
resumeItems = []
|
resumeItems = []
|
||||||
nextUpItems = []
|
|
||||||
|
|
||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
@ -119,36 +118,23 @@ final class HomeViewModel: ViewModel {
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Latest Added Items
|
// MARK: Recently Added Items
|
||||||
|
|
||||||
private func refreshLatestAddedItems() {
|
private func refreshLatestAddedItems() {
|
||||||
UserLibraryAPI.getLatestMedia(
|
UserLibraryAPI.getLatestMedia(
|
||||||
userId: SessionManager.main.currentLogin.user.id,
|
userId: SessionManager.main.currentLogin.user.id,
|
||||||
fields: [
|
|
||||||
.primaryImageAspectRatio,
|
|
||||||
.seriesPrimaryImage,
|
|
||||||
.seasonUserData,
|
|
||||||
.overview,
|
|
||||||
.genres,
|
|
||||||
.people,
|
|
||||||
.chapters,
|
|
||||||
],
|
|
||||||
includeItemTypes: [.movie, .series],
|
includeItemTypes: [.movie, .series],
|
||||||
enableImageTypes: [.primary, .backdrop, .thumb],
|
limit: 1
|
||||||
enableUserData: true,
|
|
||||||
limit: 20
|
|
||||||
)
|
)
|
||||||
.sink { completion in
|
.sink { completion in
|
||||||
switch completion {
|
switch completion {
|
||||||
case .finished: ()
|
case .finished: ()
|
||||||
case .failure:
|
case .failure:
|
||||||
self.nextUpItems = []
|
self.hasRecentlyAdded = false
|
||||||
self.handleAPIRequestError(completion: completion)
|
self.handleAPIRequestError(completion: completion)
|
||||||
}
|
}
|
||||||
} receiveValue: { items in
|
} receiveValue: { items in
|
||||||
LogManager.log.debug("Retrieved \(String(items.count)) resume items")
|
self.hasRecentlyAdded = items.count > 0
|
||||||
|
|
||||||
self.latestAddedItems = items
|
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
@ -207,30 +193,18 @@ final class HomeViewModel: ViewModel {
|
||||||
private func refreshNextUpItems() {
|
private func refreshNextUpItems() {
|
||||||
TvShowsAPI.getNextUp(
|
TvShowsAPI.getNextUp(
|
||||||
userId: SessionManager.main.currentLogin.user.id,
|
userId: SessionManager.main.currentLogin.user.id,
|
||||||
limit: 20,
|
limit: 1
|
||||||
fields: [
|
|
||||||
.primaryImageAspectRatio,
|
|
||||||
.seriesPrimaryImage,
|
|
||||||
.seasonUserData,
|
|
||||||
.overview,
|
|
||||||
.genres,
|
|
||||||
.people,
|
|
||||||
.chapters,
|
|
||||||
],
|
|
||||||
enableUserData: true
|
|
||||||
)
|
)
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
switch completion {
|
switch completion {
|
||||||
case .finished: ()
|
case .finished: ()
|
||||||
case .failure:
|
case .failure:
|
||||||
self.nextUpItems = []
|
self.hasNextUp = false
|
||||||
self.handleAPIRequestError(completion: completion)
|
self.handleAPIRequestError(completion: completion)
|
||||||
}
|
}
|
||||||
}, receiveValue: { response in
|
}, receiveValue: { response in
|
||||||
LogManager.log.debug("Retrieved \(String(response.items!.count)) nextup items")
|
self.hasNextUp = (response.items ?? []).count > 0
|
||||||
|
|
||||||
self.nextUpItems = response.items ?? []
|
|
||||||
})
|
})
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import Foundation
|
||||||
|
import JellyfinAPI
|
||||||
|
|
||||||
|
final class ItemTypeLibraryViewModel: PagingLibraryViewModel {
|
||||||
|
|
||||||
|
let itemTypes: [BaseItemKind]
|
||||||
|
let filterViewModel: FilterViewModel
|
||||||
|
|
||||||
|
init(itemTypes: [BaseItemKind], filters: ItemFilters) {
|
||||||
|
self.itemTypes = itemTypes
|
||||||
|
self.filterViewModel = .init(parent: nil, currentFilters: filters)
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
filterViewModel.$currentFilters
|
||||||
|
.sink { newFilters in
|
||||||
|
self.requestItems(with: newFilters, replaceCurrentItems: true)
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func requestItems(with filters: ItemFilters, replaceCurrentItems: Bool = false) {
|
||||||
|
|
||||||
|
if replaceCurrentItems {
|
||||||
|
self.items = []
|
||||||
|
self.currentPage = 0
|
||||||
|
self.hasNextPage = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let genreIDs = filters.genres.compactMap(\.id)
|
||||||
|
let sortBy: [String] = filters.sortBy.map(\.filterName).appending("IsFolder")
|
||||||
|
let sortOrder = filters.sortOrder.map { SortOrder(rawValue: $0.filterName) ?? .ascending }
|
||||||
|
let itemFilters: [ItemFilter] = filters.filters.compactMap { .init(rawValue: $0.filterName) }
|
||||||
|
let tags: [String] = filters.tags.map(\.filterName)
|
||||||
|
|
||||||
|
ItemsAPI.getItemsByUserId(
|
||||||
|
userId: SessionManager.main.currentLogin.user.id,
|
||||||
|
startIndex: currentPage * pageItemSize,
|
||||||
|
limit: pageItemSize,
|
||||||
|
recursive: true,
|
||||||
|
sortOrder: sortOrder,
|
||||||
|
fields: ItemFields.allCases,
|
||||||
|
includeItemTypes: itemTypes,
|
||||||
|
filters: itemFilters,
|
||||||
|
sortBy: sortBy,
|
||||||
|
tags: tags,
|
||||||
|
enableUserData: true,
|
||||||
|
genreIds: genreIDs
|
||||||
|
)
|
||||||
|
.trackActivity(loading)
|
||||||
|
.sink { [weak self] completion in
|
||||||
|
self?.handleAPIRequestError(completion: completion)
|
||||||
|
} receiveValue: { [weak self] response in
|
||||||
|
guard let items = response.items, !items.isEmpty else {
|
||||||
|
self?.hasNextPage = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self?.items.append(contentsOf: items)
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func _requestNextPage() {
|
||||||
|
requestItems(with: filterViewModel.currentFilters)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,46 +0,0 @@
|
||||||
//
|
|
||||||
// Swiftfin is subject to the terms of the Mozilla Public
|
|
||||||
// License, v2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
|
||||||
//
|
|
||||||
|
|
||||||
import Combine
|
|
||||||
import Foundation
|
|
||||||
import JellyfinAPI
|
|
||||||
|
|
||||||
final class LatestMediaViewModel: ViewModel {
|
|
||||||
|
|
||||||
@Published
|
|
||||||
var items = [BaseItemDto]()
|
|
||||||
|
|
||||||
let library: BaseItemDto
|
|
||||||
|
|
||||||
init(library: BaseItemDto) {
|
|
||||||
self.library = library
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
requestLatestMedia()
|
|
||||||
}
|
|
||||||
|
|
||||||
func requestLatestMedia() {
|
|
||||||
LogManager.log.debug("Requesting latest media for user id \(SessionManager.main.currentLogin.user.id)")
|
|
||||||
UserLibraryAPI.getLatestMedia(
|
|
||||||
userId: SessionManager.main.currentLogin.user.id,
|
|
||||||
parentId: library.id ?? "",
|
|
||||||
fields: ItemFields.allCases,
|
|
||||||
includeItemTypes: [.series, .movie],
|
|
||||||
enableUserData: true,
|
|
||||||
limit: 12
|
|
||||||
)
|
|
||||||
.trackActivity(loading)
|
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
|
||||||
self?.handleAPIRequestError(completion: completion)
|
|
||||||
}, receiveValue: { [weak self] response in
|
|
||||||
self?.items = response
|
|
||||||
LogManager.log.debug("Retrieved \(String(self?.items.count ?? 0)) items")
|
|
||||||
})
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,27 +7,26 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Defaults
|
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
// TODO: Look at refactoring
|
// TODO: Look at refactoring
|
||||||
final class LibraryViewModel: ViewModel {
|
final class LibraryViewModel: PagingLibraryViewModel {
|
||||||
|
|
||||||
@Default(.Customization.Library.gridPosterType)
|
|
||||||
private var libraryGridPosterType
|
|
||||||
|
|
||||||
@Published
|
|
||||||
var items: [BaseItemDto] = []
|
|
||||||
|
|
||||||
let filterViewModel: FilterViewModel
|
let filterViewModel: FilterViewModel
|
||||||
private var currentPage = 0
|
|
||||||
private var hasNextPage = true
|
|
||||||
|
|
||||||
let parent: LibraryParent?
|
let parent: LibraryParent?
|
||||||
let type: LibraryParentType
|
let type: LibraryParentType
|
||||||
|
|
||||||
|
var libraryCoordinatorParameters: LibraryCoordinator.Parameters {
|
||||||
|
if let parent = parent {
|
||||||
|
return .init(parent: parent, type: type, filters: filterViewModel.currentFilters)
|
||||||
|
} else {
|
||||||
|
return .init(filters: filterViewModel.currentFilters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init(filters: ItemFilters) {
|
init(filters: ItemFilters) {
|
||||||
self.parent = nil
|
self.parent = nil
|
||||||
self.type = .library
|
self.type = .library
|
||||||
|
@ -58,11 +57,6 @@ final class LibraryViewModel: ViewModel {
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var pageItemSize: Int {
|
|
||||||
let height = libraryGridPosterType == .portrait ? libraryGridPosterType.width * 1.5 : libraryGridPosterType.width / 1.77
|
|
||||||
return UIScreen.main.maxChildren(width: libraryGridPosterType.width, height: height)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func requestItems(with filters: ItemFilters, replaceCurrentItems: Bool = false) {
|
private func requestItems(with filters: ItemFilters, replaceCurrentItems: Bool = false) {
|
||||||
|
|
||||||
if replaceCurrentItems {
|
if replaceCurrentItems {
|
||||||
|
@ -156,9 +150,7 @@ final class LibraryViewModel: ViewModel {
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestNextPage() {
|
override func _requestNextPage() {
|
||||||
guard hasNextPage else { return }
|
|
||||||
currentPage += 1
|
|
||||||
requestItems(with: filterViewModel.currentFilters)
|
requestItems(with: filterViewModel.currentFilters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,14 +13,14 @@ import JellyfinAPI
|
||||||
final class MediaViewModel: ViewModel {
|
final class MediaViewModel: ViewModel {
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
private var libraries: [LibraryItem] = []
|
private var libraries: [MediaLibraryItem] = []
|
||||||
@Published
|
@Published
|
||||||
var libraryImages: [String: [ImageSource]] = [:]
|
var libraryImages: [String: [ImageSource]] = [:]
|
||||||
|
|
||||||
@Default(.Experimental.liveTVAlphaEnabled)
|
@Default(.Experimental.liveTVAlphaEnabled)
|
||||||
private var liveTVEnabled
|
private var liveTVEnabled
|
||||||
|
|
||||||
var libraryItems: [LibraryItem] {
|
var libraryItems: [MediaLibraryItem] {
|
||||||
[.init(library: .init(name: L10n.favorites, collectionType: "favorites"), viewModel: self)]
|
[.init(library: .init(name: L10n.favorites, collectionType: "favorites"), viewModel: self)]
|
||||||
.appending(.init(library: .init(name: "LiveTV", collectionType: "liveTV"), viewModel: self), if: liveTVEnabled)
|
.appending(.init(library: .init(name: "LiveTV", collectionType: "liveTV"), viewModel: self), if: liveTVEnabled)
|
||||||
.appending(libraries)
|
.appending(libraries)
|
||||||
|
|
|
@ -1,94 +0,0 @@
|
||||||
//
|
|
||||||
// Swiftfin is subject to the terms of the Mozilla Public
|
|
||||||
// License, v2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
|
||||||
//
|
|
||||||
|
|
||||||
import Combine
|
|
||||||
import Foundation
|
|
||||||
import JellyfinAPI
|
|
||||||
import Stinsen
|
|
||||||
import SwiftUICollection
|
|
||||||
|
|
||||||
final class MovieLibrariesViewModel: ViewModel {
|
|
||||||
|
|
||||||
@Published
|
|
||||||
var rows = [LibraryRow]()
|
|
||||||
@Published
|
|
||||||
var totalPages = 0
|
|
||||||
@Published
|
|
||||||
var currentPage = 0
|
|
||||||
@Published
|
|
||||||
var hasNextPage = false
|
|
||||||
@Published
|
|
||||||
var hasPreviousPage = false
|
|
||||||
|
|
||||||
private var libraries = [BaseItemDto]()
|
|
||||||
private let columns: Int
|
|
||||||
|
|
||||||
@RouterObject
|
|
||||||
private var router: MovieLibrariesCoordinator.Router?
|
|
||||||
|
|
||||||
init(columns: Int = 7) {
|
|
||||||
self.columns = columns
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
requestLibraries()
|
|
||||||
}
|
|
||||||
|
|
||||||
func requestLibraries() {
|
|
||||||
|
|
||||||
UserViewsAPI.getUserViews(userId: SessionManager.main.currentLogin.user.id)
|
|
||||||
.trackActivity(loading)
|
|
||||||
.sink(receiveCompletion: { completion in
|
|
||||||
self.handleAPIRequestError(completion: completion)
|
|
||||||
}, receiveValue: { response in
|
|
||||||
if let responseItems = response.items {
|
|
||||||
self.libraries = []
|
|
||||||
for library in responseItems {
|
|
||||||
if library.collectionType == "movies" {
|
|
||||||
self.libraries.append(library)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.rows = self.calculateRows()
|
|
||||||
if self.libraries.count == 1, let library = self.libraries.first {
|
|
||||||
// make this library the root of this stack
|
|
||||||
self.router?.coordinator.root(\.rootLibrary, library)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func calculateRows() -> [LibraryRow] {
|
|
||||||
guard !libraries.isEmpty else { return [] }
|
|
||||||
let rowCount = libraries.count / columns
|
|
||||||
var calculatedRows = [LibraryRow]()
|
|
||||||
for i in 0 ... rowCount {
|
|
||||||
let firstItemIndex = i * columns
|
|
||||||
var lastItemIndex = firstItemIndex + columns
|
|
||||||
if lastItemIndex > libraries.count {
|
|
||||||
lastItemIndex = libraries.count
|
|
||||||
}
|
|
||||||
|
|
||||||
var rowCells = [LibraryRowCell]()
|
|
||||||
for item in libraries[firstItemIndex ..< lastItemIndex] {
|
|
||||||
let newCell = LibraryRowCell(item: item)
|
|
||||||
rowCells.append(newCell)
|
|
||||||
}
|
|
||||||
if i == rowCount && hasNextPage {
|
|
||||||
var loadingCell = LibraryRowCell(item: nil)
|
|
||||||
loadingCell.loadingCell = true
|
|
||||||
rowCells.append(loadingCell)
|
|
||||||
}
|
|
||||||
|
|
||||||
calculatedRows.append(LibraryRow(
|
|
||||||
section: i,
|
|
||||||
items: rowCells
|
|
||||||
))
|
|
||||||
}
|
|
||||||
return calculatedRows
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import Foundation
|
||||||
|
import JellyfinAPI
|
||||||
|
|
||||||
|
final class NextUpLibraryViewModel: PagingLibraryViewModel {
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
_requestNextPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func _requestNextPage() {
|
||||||
|
|
||||||
|
TvShowsAPI.getNextUp(
|
||||||
|
userId: SessionManager.main.currentLogin.user.id,
|
||||||
|
startIndex: currentPage * pageItemSize,
|
||||||
|
limit: pageItemSize,
|
||||||
|
fields: [
|
||||||
|
.primaryImageAspectRatio,
|
||||||
|
.seriesPrimaryImage,
|
||||||
|
.seasonUserData,
|
||||||
|
.overview,
|
||||||
|
.genres,
|
||||||
|
.people,
|
||||||
|
.chapters,
|
||||||
|
],
|
||||||
|
enableUserData: true
|
||||||
|
)
|
||||||
|
.trackActivity(loading)
|
||||||
|
.sink { [weak self] completion in
|
||||||
|
self?.handleAPIRequestError(completion: completion)
|
||||||
|
} receiveValue: { [weak self] response in
|
||||||
|
guard let items = response.items, !items.isEmpty else {
|
||||||
|
self?.hasNextPage = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self?.items.append(contentsOf: items)
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import Defaults
|
||||||
|
import Foundation
|
||||||
|
import JellyfinAPI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class PagingLibraryViewModel: ViewModel {
|
||||||
|
|
||||||
|
@Default(.Customization.Library.gridPosterType)
|
||||||
|
private var libraryGridPosterType
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var items: [BaseItemDto] = []
|
||||||
|
|
||||||
|
var currentPage = 0
|
||||||
|
var hasNextPage = true
|
||||||
|
|
||||||
|
var pageItemSize: Int {
|
||||||
|
let height = libraryGridPosterType == .portrait ? libraryGridPosterType.width * 1.5 : libraryGridPosterType.width / 1.77
|
||||||
|
return UIScreen.main.maxChildren(width: libraryGridPosterType.width, height: height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestNextPage() {
|
||||||
|
guard hasNextPage else { return }
|
||||||
|
currentPage += 1
|
||||||
|
_requestNextPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
func _requestNextPage() {}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import Foundation
|
||||||
|
import JellyfinAPI
|
||||||
|
|
||||||
|
final class RecentlyAddedLibraryViewModel: PagingLibraryViewModel {
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
_requestNextPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func _requestNextPage() {
|
||||||
|
ItemsAPI.getItemsByUserId(
|
||||||
|
userId: SessionManager.main.currentLogin.user.id,
|
||||||
|
startIndex: currentPage * pageItemSize,
|
||||||
|
limit: pageItemSize,
|
||||||
|
recursive: true,
|
||||||
|
sortOrder: [.descending],
|
||||||
|
fields: ItemFields.allCases,
|
||||||
|
includeItemTypes: [.movie, .series],
|
||||||
|
sortBy: [SortBy.dateAdded.rawValue],
|
||||||
|
enableUserData: true
|
||||||
|
)
|
||||||
|
.trackActivity(loading)
|
||||||
|
.sink { [weak self] completion in
|
||||||
|
self?.handleAPIRequestError(completion: completion)
|
||||||
|
} receiveValue: { [weak self] response in
|
||||||
|
guard let items = response.items, !items.isEmpty else {
|
||||||
|
self?.hasNextPage = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self?.items.append(contentsOf: items)
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import JellyfinAPI
|
||||||
|
|
||||||
|
class StaticLibraryViewModel: PagingLibraryViewModel {
|
||||||
|
|
||||||
|
init(items: [BaseItemDto]) {
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.items = items
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,102 +0,0 @@
|
||||||
//
|
|
||||||
// Swiftfin is subject to the terms of the Mozilla Public
|
|
||||||
// License, v2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
|
||||||
//
|
|
||||||
|
|
||||||
import Combine
|
|
||||||
import Foundation
|
|
||||||
import JellyfinAPI
|
|
||||||
import Stinsen
|
|
||||||
import SwiftUICollection
|
|
||||||
|
|
||||||
typealias LibraryRow = CollectionRow<Int, LibraryRowCell>
|
|
||||||
|
|
||||||
struct LibraryRowCell: Hashable {
|
|
||||||
let id = UUID()
|
|
||||||
let item: BaseItemDto?
|
|
||||||
var loadingCell: Bool = false
|
|
||||||
}
|
|
||||||
|
|
||||||
final class TVLibrariesViewModel: ViewModel {
|
|
||||||
|
|
||||||
@Published
|
|
||||||
var rows = [LibraryRow]()
|
|
||||||
@Published
|
|
||||||
var totalPages = 0
|
|
||||||
@Published
|
|
||||||
var currentPage = 0
|
|
||||||
@Published
|
|
||||||
var hasNextPage = false
|
|
||||||
@Published
|
|
||||||
var hasPreviousPage = false
|
|
||||||
|
|
||||||
private var libraries = [BaseItemDto]()
|
|
||||||
private let columns: Int
|
|
||||||
|
|
||||||
@RouterObject
|
|
||||||
private var router: TVLibrariesCoordinator.Router?
|
|
||||||
|
|
||||||
init(columns: Int = 7) {
|
|
||||||
self.columns = columns
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
requestLibraries()
|
|
||||||
}
|
|
||||||
|
|
||||||
func requestLibraries() {
|
|
||||||
|
|
||||||
UserViewsAPI.getUserViews(userId: SessionManager.main.currentLogin.user.id)
|
|
||||||
.trackActivity(loading)
|
|
||||||
.sink(receiveCompletion: { completion in
|
|
||||||
self.handleAPIRequestError(completion: completion)
|
|
||||||
}, receiveValue: { response in
|
|
||||||
if let responseItems = response.items {
|
|
||||||
self.libraries = []
|
|
||||||
for library in responseItems {
|
|
||||||
if library.collectionType == "tvshows" {
|
|
||||||
self.libraries.append(library)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.rows = self.calculateRows()
|
|
||||||
if self.libraries.count == 1, let library = self.libraries.first {
|
|
||||||
// make this library the root of this stack
|
|
||||||
self.router?.coordinator.root(\.rootLibrary, library)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func calculateRows() -> [LibraryRow] {
|
|
||||||
guard !libraries.isEmpty else { return [] }
|
|
||||||
let rowCount = libraries.count / columns
|
|
||||||
var calculatedRows = [LibraryRow]()
|
|
||||||
for i in 0 ... rowCount {
|
|
||||||
let firstItemIndex = i * columns
|
|
||||||
var lastItemIndex = firstItemIndex + columns
|
|
||||||
if lastItemIndex > libraries.count {
|
|
||||||
lastItemIndex = libraries.count
|
|
||||||
}
|
|
||||||
|
|
||||||
var rowCells = [LibraryRowCell]()
|
|
||||||
for item in libraries[firstItemIndex ..< lastItemIndex] {
|
|
||||||
let newCell = LibraryRowCell(item: item)
|
|
||||||
rowCells.append(newCell)
|
|
||||||
}
|
|
||||||
if i == rowCount && hasNextPage {
|
|
||||||
var loadingCell = LibraryRowCell(item: nil)
|
|
||||||
loadingCell.loadingCell = true
|
|
||||||
rowCells.append(loadingCell)
|
|
||||||
}
|
|
||||||
|
|
||||||
calculatedRows.append(LibraryRow(
|
|
||||||
section: i,
|
|
||||||
items: rowCells
|
|
||||||
))
|
|
||||||
}
|
|
||||||
return calculatedRows
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,7 +11,14 @@ import JellyfinAPI
|
||||||
import Nuke
|
import Nuke
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct CinematicItemSelector<Item: Poster, TopContent: View, ItemContent: View, ItemImageOverlay: View, ItemContextMenu: View>: View {
|
struct CinematicItemSelector<
|
||||||
|
Item: Poster,
|
||||||
|
TopContent: View,
|
||||||
|
ItemContent: View,
|
||||||
|
ItemImageOverlay: View,
|
||||||
|
ItemContextMenu: View,
|
||||||
|
TrailingContent: View
|
||||||
|
>: View {
|
||||||
|
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
private var viewModel: CinematicBackgroundView.ViewModel = .init()
|
private var viewModel: CinematicBackgroundView.ViewModel = .init()
|
||||||
|
@ -20,6 +27,7 @@ struct CinematicItemSelector<Item: Poster, TopContent: View, ItemContent: View,
|
||||||
private var itemContent: (Item) -> ItemContent
|
private var itemContent: (Item) -> ItemContent
|
||||||
private var itemImageOverlay: (Item) -> ItemImageOverlay
|
private var itemImageOverlay: (Item) -> ItemImageOverlay
|
||||||
private var itemContextMenu: (Item) -> ItemContextMenu
|
private var itemContextMenu: (Item) -> ItemContextMenu
|
||||||
|
private var trailingContent: () -> TrailingContent
|
||||||
private var onSelect: (Item) -> Void
|
private var onSelect: (Item) -> Void
|
||||||
|
|
||||||
let items: [Item]
|
let items: [Item]
|
||||||
|
@ -62,6 +70,7 @@ struct CinematicItemSelector<Item: Poster, TopContent: View, ItemContent: View,
|
||||||
.content(itemContent)
|
.content(itemContent)
|
||||||
.imageOverlay(itemImageOverlay)
|
.imageOverlay(itemImageOverlay)
|
||||||
.contextMenu(itemContextMenu)
|
.contextMenu(itemContextMenu)
|
||||||
|
.trailing(trailingContent)
|
||||||
.onSelect(onSelect)
|
.onSelect(onSelect)
|
||||||
.onFocus { item in
|
.onFocus { item in
|
||||||
viewModel.select(item: item)
|
viewModel.select(item: item)
|
||||||
|
@ -169,7 +178,8 @@ struct CinematicItemSelector<Item: Poster, TopContent: View, ItemContent: View,
|
||||||
extension CinematicItemSelector where TopContent == EmptyView,
|
extension CinematicItemSelector where TopContent == EmptyView,
|
||||||
ItemContent == EmptyView,
|
ItemContent == EmptyView,
|
||||||
ItemImageOverlay == EmptyView,
|
ItemImageOverlay == EmptyView,
|
||||||
ItemContextMenu == EmptyView
|
ItemContextMenu == EmptyView,
|
||||||
|
TrailingContent == EmptyView
|
||||||
{
|
{
|
||||||
init(items: [Item]) {
|
init(items: [Item]) {
|
||||||
self.init(
|
self.init(
|
||||||
|
@ -177,6 +187,7 @@ extension CinematicItemSelector where TopContent == EmptyView,
|
||||||
itemContent: { _ in EmptyView() },
|
itemContent: { _ in EmptyView() },
|
||||||
itemImageOverlay: { _ in EmptyView() },
|
itemImageOverlay: { _ in EmptyView() },
|
||||||
itemContextMenu: { _ in EmptyView() },
|
itemContextMenu: { _ in EmptyView() },
|
||||||
|
trailingContent: { EmptyView() },
|
||||||
onSelect: { _ in },
|
onSelect: { _ in },
|
||||||
items: items
|
items: items
|
||||||
)
|
)
|
||||||
|
@ -187,12 +198,13 @@ extension CinematicItemSelector {
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func topContent<T: View>(@ViewBuilder _ content: @escaping (Item) -> T)
|
func topContent<T: View>(@ViewBuilder _ content: @escaping (Item) -> T)
|
||||||
-> CinematicItemSelector<Item, T, ItemContent, ItemImageOverlay, ItemContextMenu> {
|
-> CinematicItemSelector<Item, T, ItemContent, ItemImageOverlay, ItemContextMenu, TrailingContent> {
|
||||||
CinematicItemSelector<Item, T, ItemContent, ItemImageOverlay, ItemContextMenu>(
|
CinematicItemSelector<Item, T, ItemContent, ItemImageOverlay, ItemContextMenu, TrailingContent>(
|
||||||
topContent: content,
|
topContent: content,
|
||||||
itemContent: itemContent,
|
itemContent: itemContent,
|
||||||
itemImageOverlay: itemImageOverlay,
|
itemImageOverlay: itemImageOverlay,
|
||||||
itemContextMenu: itemContextMenu,
|
itemContextMenu: itemContextMenu,
|
||||||
|
trailingContent: trailingContent,
|
||||||
onSelect: onSelect,
|
onSelect: onSelect,
|
||||||
items: items
|
items: items
|
||||||
)
|
)
|
||||||
|
@ -200,12 +212,13 @@ extension CinematicItemSelector {
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func content<C: View>(@ViewBuilder _ content: @escaping (Item) -> C)
|
func content<C: View>(@ViewBuilder _ content: @escaping (Item) -> C)
|
||||||
-> CinematicItemSelector<Item, TopContent, C, ItemImageOverlay, ItemContextMenu> {
|
-> CinematicItemSelector<Item, TopContent, C, ItemImageOverlay, ItemContextMenu, TrailingContent> {
|
||||||
CinematicItemSelector<Item, TopContent, C, ItemImageOverlay, ItemContextMenu>(
|
CinematicItemSelector<Item, TopContent, C, ItemImageOverlay, ItemContextMenu, TrailingContent>(
|
||||||
topContent: topContent,
|
topContent: topContent,
|
||||||
itemContent: content,
|
itemContent: content,
|
||||||
itemImageOverlay: itemImageOverlay,
|
itemImageOverlay: itemImageOverlay,
|
||||||
itemContextMenu: itemContextMenu,
|
itemContextMenu: itemContextMenu,
|
||||||
|
trailingContent: trailingContent,
|
||||||
onSelect: onSelect,
|
onSelect: onSelect,
|
||||||
items: items
|
items: items
|
||||||
)
|
)
|
||||||
|
@ -213,12 +226,13 @@ extension CinematicItemSelector {
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func itemImageOverlay<O: View>(@ViewBuilder _ imageOverlay: @escaping (Item) -> O)
|
func itemImageOverlay<O: View>(@ViewBuilder _ imageOverlay: @escaping (Item) -> O)
|
||||||
-> CinematicItemSelector<Item, TopContent, ItemContent, O, ItemContextMenu> {
|
-> CinematicItemSelector<Item, TopContent, ItemContent, O, ItemContextMenu, TrailingContent> {
|
||||||
CinematicItemSelector<Item, TopContent, ItemContent, O, ItemContextMenu>(
|
CinematicItemSelector<Item, TopContent, ItemContent, O, ItemContextMenu, TrailingContent>(
|
||||||
topContent: topContent,
|
topContent: topContent,
|
||||||
itemContent: itemContent,
|
itemContent: itemContent,
|
||||||
itemImageOverlay: imageOverlay,
|
itemImageOverlay: imageOverlay,
|
||||||
itemContextMenu: itemContextMenu,
|
itemContextMenu: itemContextMenu,
|
||||||
|
trailingContent: trailingContent,
|
||||||
onSelect: onSelect,
|
onSelect: onSelect,
|
||||||
items: items
|
items: items
|
||||||
)
|
)
|
||||||
|
@ -226,12 +240,27 @@ extension CinematicItemSelector {
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func contextMenu<M: View>(@ViewBuilder _ contextMenu: @escaping (Item) -> M)
|
func contextMenu<M: View>(@ViewBuilder _ contextMenu: @escaping (Item) -> M)
|
||||||
-> CinematicItemSelector<Item, TopContent, ItemContent, ItemImageOverlay, M> {
|
-> CinematicItemSelector<Item, TopContent, ItemContent, ItemImageOverlay, M, TrailingContent> {
|
||||||
CinematicItemSelector<Item, TopContent, ItemContent, ItemImageOverlay, M>(
|
CinematicItemSelector<Item, TopContent, ItemContent, ItemImageOverlay, M, TrailingContent>(
|
||||||
topContent: topContent,
|
topContent: topContent,
|
||||||
itemContent: itemContent,
|
itemContent: itemContent,
|
||||||
itemImageOverlay: itemImageOverlay,
|
itemImageOverlay: itemImageOverlay,
|
||||||
itemContextMenu: contextMenu,
|
itemContextMenu: contextMenu,
|
||||||
|
trailingContent: trailingContent,
|
||||||
|
onSelect: onSelect,
|
||||||
|
items: items
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
func trailingContent<T: View>(@ViewBuilder _ content: @escaping () -> T)
|
||||||
|
-> CinematicItemSelector<Item, TopContent, ItemContent, ItemImageOverlay, ItemContextMenu, T> {
|
||||||
|
CinematicItemSelector<Item, TopContent, ItemContent, ItemImageOverlay, ItemContextMenu, T>(
|
||||||
|
topContent: topContent,
|
||||||
|
itemContent: itemContent,
|
||||||
|
itemImageOverlay: itemImageOverlay,
|
||||||
|
itemContextMenu: itemContextMenu,
|
||||||
|
trailingContent: content,
|
||||||
onSelect: onSelect,
|
onSelect: onSelect,
|
||||||
items: items
|
items: items
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import CollectionView
|
||||||
|
import Defaults
|
||||||
|
import JellyfinAPI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct PagingLibraryView: View {
|
||||||
|
|
||||||
|
@ObservedObject
|
||||||
|
var viewModel: PagingLibraryViewModel
|
||||||
|
private var onSelect: (BaseItemDto) -> Void
|
||||||
|
|
||||||
|
@Default(.Customization.Library.gridPosterType)
|
||||||
|
private var libraryPosterType
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
CollectionView(items: viewModel.items) { _, item, _ in
|
||||||
|
PosterButton(item: item, type: libraryPosterType)
|
||||||
|
.onSelect {
|
||||||
|
onSelect(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.layout { _, layoutEnvironment in
|
||||||
|
.grid(
|
||||||
|
layoutEnvironment: layoutEnvironment,
|
||||||
|
layoutMode: .fixedNumberOfColumns(7),
|
||||||
|
lineSpacing: 50
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.willReachEdge(insets: .init(top: 0, leading: 0, bottom: 600, trailing: 0)) { edge in
|
||||||
|
if !viewModel.isLoading && edge == .bottom {
|
||||||
|
viewModel.requestNextPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PagingLibraryView {
|
||||||
|
init(viewModel: PagingLibraryViewModel) {
|
||||||
|
self.viewModel = viewModel
|
||||||
|
self.onSelect = { _ in }
|
||||||
|
}
|
||||||
|
|
||||||
|
func onSelect(_ action: @escaping (BaseItemDto) -> Void) -> Self {
|
||||||
|
var copy = self
|
||||||
|
copy.onSelect = action
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,92 +0,0 @@
|
||||||
//
|
|
||||||
// Swiftfin is subject to the terms of the Mozilla Public
|
|
||||||
// License, v2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
|
||||||
//
|
|
||||||
|
|
||||||
import JellyfinAPI
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
// TODO: Transition to PosterButton`
|
|
||||||
struct PortraitItemElement: View {
|
|
||||||
|
|
||||||
@Environment(\.isFocused)
|
|
||||||
var envFocused: Bool
|
|
||||||
@State
|
|
||||||
var focused: Bool = false
|
|
||||||
@State
|
|
||||||
var backgroundURL: URL?
|
|
||||||
|
|
||||||
var item: BaseItemDto
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack {
|
|
||||||
ImageView(item.type == .episode ? item.seriesImageSource(.primary, maxWidth: 200) : item.imageSource(.primary, maxWidth: 200))
|
|
||||||
.frame(width: 200, height: 300)
|
|
||||||
.cornerRadius(10)
|
|
||||||
.shadow(radius: focused ? 10.0 : 0)
|
|
||||||
.shadow(radius: focused ? 10.0 : 0)
|
|
||||||
.overlay(
|
|
||||||
ZStack {
|
|
||||||
if item.userData?.isFavorite ?? false {
|
|
||||||
Image(systemName: "circle.fill")
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.opacity(0.6)
|
|
||||||
Image(systemName: "heart.fill")
|
|
||||||
.foregroundColor(Color(.systemRed))
|
|
||||||
.font(.system(size: 10))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(2)
|
|
||||||
.opacity(1),
|
|
||||||
alignment: .bottomLeading
|
|
||||||
)
|
|
||||||
.overlay(
|
|
||||||
ZStack {
|
|
||||||
if item.userData?.played ?? false {
|
|
||||||
Image(systemName: "circle.fill")
|
|
||||||
.foregroundColor(.white)
|
|
||||||
Image(systemName: "checkmark.circle.fill")
|
|
||||||
.foregroundColor(Color(.systemBlue))
|
|
||||||
} else {
|
|
||||||
if item.userData?.unplayedItemCount != nil {
|
|
||||||
Image(systemName: "circle.fill")
|
|
||||||
.foregroundColor(Color(.systemBlue))
|
|
||||||
Text(String(item.userData!.unplayedItemCount ?? 0))
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.font(.caption2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.padding(2)
|
|
||||||
.opacity(1),
|
|
||||||
alignment: .topTrailing
|
|
||||||
).opacity(1)
|
|
||||||
Text(item.title)
|
|
||||||
.frame(width: 200, height: 30, alignment: .center)
|
|
||||||
if item.type == .movie || item.type == .series {
|
|
||||||
Text("\(String(item.productionYear ?? 0)) • \(item.officialRating ?? "N/A")")
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
.font(.caption)
|
|
||||||
.fontWeight(.medium)
|
|
||||||
} else if item.type == .season {
|
|
||||||
Text("\(item.name ?? "") • \(String(item.productionYear ?? 0))")
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
.font(.caption)
|
|
||||||
.fontWeight(.medium)
|
|
||||||
} else {
|
|
||||||
Text(L10n.seasonAndEpisode(String(item.parentIndexNumber ?? 0), String(item.indexNumber ?? 0)))
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
.font(.caption)
|
|
||||||
.fontWeight(.medium)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onChange(of: envFocused) { envFocus in
|
|
||||||
withAnimation(.linear(duration: 0.15)) {
|
|
||||||
self.focused = envFocus
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.scaleEffect(focused ? 1.1 : 1)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,10 +17,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 onFocus: () -> Void
|
private var onFocus: () -> Void
|
||||||
private var singleImage: Bool
|
private var singleImage: Bool
|
||||||
|
|
||||||
|
@ -31,31 +31,37 @@ 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 {
|
||||||
case .portrait:
|
case .portrait:
|
||||||
ImageView(item.portraitPosterImageSource(maxWidth: itemWidth))
|
ImageView(item.portraitPosterImageSource(maxWidth: itemWidth))
|
||||||
|
.failure {
|
||||||
|
InitialFailureView(item.displayName.initials)
|
||||||
|
}
|
||||||
.posterStyle(type: type, width: itemWidth)
|
.posterStyle(type: type, width: itemWidth)
|
||||||
case .landscape:
|
case .landscape:
|
||||||
ImageView(item.landscapePosterImageSources(maxWidth: itemWidth, single: singleImage))
|
ImageView(item.landscapePosterImageSources(maxWidth: itemWidth, single: singleImage))
|
||||||
|
.failure {
|
||||||
|
InitialFailureView(item.displayName.initials)
|
||||||
|
}
|
||||||
.posterStyle(type: type, width: itemWidth)
|
.posterStyle(type: type, width: itemWidth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.overlay {
|
.overlay {
|
||||||
imageOverlay(item)
|
imageOverlay()
|
||||||
.posterStyle(type: type, width: itemWidth)
|
.posterStyle(type: type, width: itemWidth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(.card)
|
.buttonStyle(.card)
|
||||||
.contextMenu(menuItems: {
|
.contextMenu(menuItems: {
|
||||||
contextMenu(item)
|
contextMenu()
|
||||||
})
|
})
|
||||||
.posterShadow()
|
.posterShadow()
|
||||||
.focused($isFocused)
|
.focused($isFocused)
|
||||||
|
|
||||||
content(item)
|
content()
|
||||||
.zIndex(-1)
|
.zIndex(-1)
|
||||||
}
|
}
|
||||||
.frame(width: itemWidth)
|
.frame(width: itemWidth)
|
||||||
|
@ -76,10 +82,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: {},
|
||||||
onFocus: {},
|
onFocus: {},
|
||||||
singleImage: singleImage
|
singleImage: singleImage
|
||||||
)
|
)
|
||||||
|
@ -100,7 +106,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,
|
||||||
|
@ -116,7 +122,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,
|
||||||
|
@ -132,7 +138,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,
|
||||||
|
@ -147,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
|
||||||
|
|
|
@ -41,10 +41,10 @@ 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)
|
||||||
.content(content)
|
.content { content(item) }
|
||||||
.imageOverlay(imageOverlay)
|
.imageOverlay { imageOverlay(item) }
|
||||||
.contextMenu(contextMenu)
|
.contextMenu { contextMenu(item) }
|
||||||
.onSelect(onSelect)
|
.onSelect { onSelect(item) }
|
||||||
.onFocus { onFocus(item) }
|
.onFocus { onFocus(item) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SeeAllPoster: View {
|
||||||
|
|
||||||
|
private let type: PosterType
|
||||||
|
private var onSelect: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button {
|
||||||
|
onSelect()
|
||||||
|
} label: {
|
||||||
|
ZStack {
|
||||||
|
Color(UIColor.darkGray)
|
||||||
|
.opacity(0.5)
|
||||||
|
|
||||||
|
VStack(spacing: 20) {
|
||||||
|
Image(systemName: "chevron.right")
|
||||||
|
.font(.title)
|
||||||
|
|
||||||
|
L10n.seeAll.text
|
||||||
|
.font(.title3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.posterStyle(type: type, width: type.width)
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SeeAllPoster {
|
||||||
|
init(type: PosterType) {
|
||||||
|
self.type = type
|
||||||
|
self.onSelect = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
func onSelect(_ action: @escaping () -> Void) -> Self {
|
||||||
|
var copy = self
|
||||||
|
copy.onSelect = action
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import CollectionView
|
||||||
|
import Defaults
|
||||||
|
import JellyfinAPI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct BasicLibraryView: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: BasicLibraryCoordinator.Router
|
||||||
|
@ObservedObject
|
||||||
|
var viewModel: PagingLibraryViewModel
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var loadingView: some View {
|
||||||
|
ProgressView()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var noResultsView: some View {
|
||||||
|
L10n.noResults.text
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var libraryItemsView: some View {
|
||||||
|
PagingLibraryView(viewModel: viewModel)
|
||||||
|
.onSelect { item in
|
||||||
|
router.route(to: \.item, item)
|
||||||
|
}
|
||||||
|
.ignoresSafeArea()
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
if viewModel.isLoading && viewModel.items.isEmpty {
|
||||||
|
loadingView
|
||||||
|
} else if viewModel.items.isEmpty {
|
||||||
|
noResultsView
|
||||||
|
} else {
|
||||||
|
libraryItemsView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import CollectionView
|
||||||
|
import JellyfinAPI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct CastAndCrewLibraryView: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: CastAndCrewLibraryCoordinator.Router
|
||||||
|
let people: [BaseItemPerson]
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var noResultsView: some View {
|
||||||
|
L10n.noResults.text
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var libraryGridView: some View {
|
||||||
|
CollectionView(items: people) { _, person, _ in
|
||||||
|
PosterButton(item: person, type: .portrait)
|
||||||
|
.onSelect {
|
||||||
|
router.route(to: \.library, .init(parent: person, type: .person, filters: .init()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.layout { _, layoutEnvironment in
|
||||||
|
.grid(
|
||||||
|
layoutEnvironment: layoutEnvironment,
|
||||||
|
layoutMode: .fixedNumberOfColumns(7),
|
||||||
|
lineSpacing: 50
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.configure { configuration in
|
||||||
|
configuration.showsVerticalScrollIndicator = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
if people.isEmpty {
|
||||||
|
noResultsView
|
||||||
|
} else {
|
||||||
|
libraryGridView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ignoresSafeArea()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import JellyfinAPI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension HomeView {
|
||||||
|
|
||||||
|
struct CinematicRecentlyAddedView: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: HomeCoordinator.Router
|
||||||
|
@ObservedObject
|
||||||
|
var viewModel: ItemTypeLibraryViewModel
|
||||||
|
|
||||||
|
private func itemSelectorImageSource(for item: BaseItemDto) -> ImageSource {
|
||||||
|
if item.type == .episode {
|
||||||
|
return item.seriesImageSource(
|
||||||
|
.logo,
|
||||||
|
maxWidth: UIScreen.main.bounds.width * 0.4,
|
||||||
|
maxHeight: 200
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return item.imageSource(
|
||||||
|
.logo,
|
||||||
|
maxWidth: UIScreen.main.bounds.width * 0.4,
|
||||||
|
maxHeight: 200
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
CinematicItemSelector(items: viewModel.items.prefix(20).asArray)
|
||||||
|
.topContent { item in
|
||||||
|
ImageView(itemSelectorImageSource(for: item))
|
||||||
|
.resizingMode(.bottomLeft)
|
||||||
|
.placeholder {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
.failure {
|
||||||
|
Text(item.displayName)
|
||||||
|
.font(.largeTitle)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
}
|
||||||
|
.padding2(.leading)
|
||||||
|
}
|
||||||
|
.onSelect { item in
|
||||||
|
router.route(to: \.item, item)
|
||||||
|
}
|
||||||
|
.trailingContent {
|
||||||
|
SeeAllPoster(type: .landscape)
|
||||||
|
.onSelect {
|
||||||
|
router.route(to: \.basicLibrary, .init(title: L10n.recentlyAdded, viewModel: viewModel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import JellyfinAPI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension HomeView {
|
||||||
|
|
||||||
|
struct CinematicResumeView: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: HomeCoordinator.Router
|
||||||
|
@ObservedObject
|
||||||
|
var viewModel: HomeViewModel
|
||||||
|
|
||||||
|
private func itemSelectorImageSource(for item: BaseItemDto) -> ImageSource {
|
||||||
|
if item.type == .episode {
|
||||||
|
return item.seriesImageSource(
|
||||||
|
.logo,
|
||||||
|
maxWidth: UIScreen.main.bounds.width * 0.4,
|
||||||
|
maxHeight: 200
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return item.imageSource(
|
||||||
|
.logo,
|
||||||
|
maxWidth: UIScreen.main.bounds.width * 0.4,
|
||||||
|
maxHeight: 200
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
CinematicItemSelector(items: viewModel.resumeItems)
|
||||||
|
.topContent { item in
|
||||||
|
ImageView(itemSelectorImageSource(for: item))
|
||||||
|
.resizingMode(.bottomLeft)
|
||||||
|
.placeholder {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
.failure {
|
||||||
|
Text(item.displayName)
|
||||||
|
.font(.largeTitle)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
}
|
||||||
|
.padding2(.leading)
|
||||||
|
}
|
||||||
|
.content { item in
|
||||||
|
if let subtitle = item.subtitle {
|
||||||
|
Text(subtitle)
|
||||||
|
.font(.caption)
|
||||||
|
.fontWeight(.medium)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.lineLimit(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.itemImageOverlay { item in
|
||||||
|
LandscapePosterProgressBar(
|
||||||
|
title: item.progress ?? L10n.continue,
|
||||||
|
progress: (item.userData?.playedPercentage ?? 0) / 100
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.onSelect { item in
|
||||||
|
router.route(to: \.item, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import JellyfinAPI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension HomeView {
|
||||||
|
|
||||||
|
struct LatestInLibraryView: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: HomeCoordinator.Router
|
||||||
|
@StateObject
|
||||||
|
var viewModel: LibraryViewModel
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
PosterHStack(
|
||||||
|
title: L10n.latestWithString(viewModel.parent?.displayName ?? .emptyDash),
|
||||||
|
type: .portrait,
|
||||||
|
items: viewModel.items
|
||||||
|
)
|
||||||
|
.trailing {
|
||||||
|
SeeAllPoster(type: .portrait)
|
||||||
|
.onSelect {
|
||||||
|
router.route(to: \.library, viewModel.libraryCoordinatorParameters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onSelect { item in
|
||||||
|
router.route(to: \.item, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import Defaults
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension HomeView {
|
||||||
|
|
||||||
|
struct NextUpView: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: HomeCoordinator.Router
|
||||||
|
@ObservedObject
|
||||||
|
var viewModel: NextUpLibraryViewModel
|
||||||
|
|
||||||
|
@Default(.Customization.nextUpPosterType)
|
||||||
|
private var nextUpPosterType
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
PosterHStack(
|
||||||
|
title: L10n.nextUp,
|
||||||
|
type: nextUpPosterType,
|
||||||
|
items: viewModel.items.prefix(20).asArray
|
||||||
|
)
|
||||||
|
.trailing {
|
||||||
|
Button {
|
||||||
|
router.route(to: \.basicLibrary, .init(title: L10n.nextUp, viewModel: viewModel))
|
||||||
|
} label: {
|
||||||
|
HStack {
|
||||||
|
L10n.seeAll.text
|
||||||
|
Image(systemName: "chevron.right")
|
||||||
|
}
|
||||||
|
.font(.subheadline.bold())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onSelect { item in
|
||||||
|
router.route(to: \.item, item)
|
||||||
|
}
|
||||||
|
.trailing {
|
||||||
|
SeeAllPoster(type: nextUpPosterType)
|
||||||
|
.onSelect {
|
||||||
|
router.route(to: \.basicLibrary, .init(title: L10n.nextUp, viewModel: viewModel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import Defaults
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension HomeView {
|
||||||
|
|
||||||
|
struct RecentlyAddedView: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: HomeCoordinator.Router
|
||||||
|
@ObservedObject
|
||||||
|
var viewModel: ItemTypeLibraryViewModel
|
||||||
|
|
||||||
|
@Default(.Customization.recentlyAddedPosterType)
|
||||||
|
private var recentlyAddedPosterType
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
PosterHStack(
|
||||||
|
title: L10n.recentlyAdded,
|
||||||
|
type: recentlyAddedPosterType,
|
||||||
|
items: viewModel.items.prefix(20).asArray
|
||||||
|
)
|
||||||
|
.onSelect { item in
|
||||||
|
router.route(to: \.item, item)
|
||||||
|
}
|
||||||
|
.trailing {
|
||||||
|
SeeAllPoster(type: recentlyAddedPosterType)
|
||||||
|
.onSelect {
|
||||||
|
router.route(to: \.basicLibrary, .init(title: L10n.recentlyAdded, viewModel: viewModel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,111 +18,36 @@ extension HomeView {
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var viewModel: HomeViewModel
|
var viewModel: HomeViewModel
|
||||||
|
|
||||||
private func itemSelectorImageSource(for item: BaseItemDto) -> ImageSource {
|
|
||||||
if item.type == .episode {
|
|
||||||
return item.seriesImageSource(
|
|
||||||
.logo,
|
|
||||||
maxWidth: UIScreen.main.bounds.width * 0.4,
|
|
||||||
maxHeight: 200
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return item.imageSource(
|
|
||||||
.logo,
|
|
||||||
maxWidth: UIScreen.main.bounds.width * 0.4,
|
|
||||||
maxHeight: 200
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private var cinematicResumeItems: some View {
|
|
||||||
CinematicItemSelector(items: viewModel.resumeItems)
|
|
||||||
.topContent { item in
|
|
||||||
ImageView(itemSelectorImageSource(for: item))
|
|
||||||
.resizingMode(.bottomLeft)
|
|
||||||
.placeholder {
|
|
||||||
EmptyView()
|
|
||||||
}
|
|
||||||
.failure {
|
|
||||||
Text(item.displayName)
|
|
||||||
.font(.largeTitle)
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
}
|
|
||||||
.padding2(.leading)
|
|
||||||
}
|
|
||||||
.content { item in
|
|
||||||
if let subtitle = item.subtitle {
|
|
||||||
Text(subtitle)
|
|
||||||
.font(.caption)
|
|
||||||
.fontWeight(.medium)
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
.lineLimit(2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.itemImageOverlay { item in
|
|
||||||
LandscapePosterProgressBar(
|
|
||||||
title: item.progress ?? L10n.continue,
|
|
||||||
progress: (item.userData?.playedPercentage ?? 0) / 100
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.onSelect { item in
|
|
||||||
router.route(to: \.item, item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private var cinematicLatestAddedItems: some View {
|
|
||||||
CinematicItemSelector(items: viewModel.latestAddedItems)
|
|
||||||
.topContent { item in
|
|
||||||
ImageView(itemSelectorImageSource(for: item))
|
|
||||||
.resizingMode(.bottomLeft)
|
|
||||||
.placeholder {
|
|
||||||
EmptyView()
|
|
||||||
}
|
|
||||||
.failure {
|
|
||||||
Text(item.displayName)
|
|
||||||
.font(.largeTitle)
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
}
|
|
||||||
.padding2(.leading)
|
|
||||||
}
|
|
||||||
.onSelect { item in
|
|
||||||
router.route(to: \.item, item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
LazyVStack(alignment: .leading, spacing: 0) {
|
LazyVStack(alignment: .leading, spacing: 0) {
|
||||||
if viewModel.resumeItems.isEmpty {
|
|
||||||
cinematicLatestAddedItems
|
|
||||||
|
|
||||||
if !viewModel.nextUpItems.isEmpty {
|
if viewModel.resumeItems.isEmpty {
|
||||||
PosterHStack(title: L10n.nextUp, type: .portrait, items: viewModel.nextUpItems)
|
CinematicRecentlyAddedView(viewModel: .init(
|
||||||
.onSelect { item in
|
itemTypes: [.movie, .series],
|
||||||
router.route(to: \.item, item)
|
filters: .init(sortOrder: [APISortOrder.descending.filter], sortBy: [SortBy.dateAdded.filter])
|
||||||
}
|
))
|
||||||
|
|
||||||
|
if viewModel.hasNextUp {
|
||||||
|
NextUpView(viewModel: .init())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cinematicResumeItems
|
CinematicResumeView(viewModel: viewModel)
|
||||||
|
|
||||||
if !viewModel.nextUpItems.isEmpty {
|
if viewModel.hasNextUp {
|
||||||
PosterHStack(title: L10n.nextUp, type: .portrait, items: viewModel.nextUpItems)
|
NextUpView(viewModel: .init())
|
||||||
.onSelect { item in
|
|
||||||
router.route(to: \.item, item)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !viewModel.latestAddedItems.isEmpty {
|
if viewModel.hasRecentlyAdded {
|
||||||
PosterHStack(title: L10n.recentlyAdded, type: .portrait, items: viewModel.latestAddedItems)
|
RecentlyAddedView(viewModel: .init(
|
||||||
.onSelect { item in
|
itemTypes: [.movie, .series],
|
||||||
router.route(to: \.item, item)
|
filters: .init(sortOrder: [APISortOrder.descending.filter], sortBy: [SortBy.dateAdded.filter])
|
||||||
}
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ForEach(viewModel.libraries, id: \.self) { library in
|
ForEach(viewModel.libraries, id: \.self) { library in
|
||||||
LatestInLibraryView(viewModel: LatestMediaViewModel(library: library))
|
LatestInLibraryView(viewModel: .init(parent: library, type: .library, filters: .recent))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import JellyfinAPI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension ItemView {
|
extension ItemView {
|
||||||
|
|
||||||
struct CastAndCrewHStack: View {
|
struct CastAndCrewHStack: View {
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
|
@ -20,8 +21,14 @@ extension ItemView {
|
||||||
PosterHStack(
|
PosterHStack(
|
||||||
title: L10n.castAndCrew,
|
title: L10n.castAndCrew,
|
||||||
type: .portrait,
|
type: .portrait,
|
||||||
items: people
|
items: people.filter(\.isDisplayed).prefix(20).asArray
|
||||||
)
|
)
|
||||||
|
.trailing {
|
||||||
|
SeeAllPoster(type: .portrait)
|
||||||
|
.onSelect {
|
||||||
|
router.route(to: \.castAndCrew, people)
|
||||||
|
}
|
||||||
|
}
|
||||||
.onSelect { person in
|
.onSelect { person in
|
||||||
router.route(to: \.library, .init(parent: person, type: .person, filters: .init()))
|
router.route(to: \.library, .init(parent: person, type: .person, filters: .init()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import JellyfinAPI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension ItemView {
|
extension ItemView {
|
||||||
|
|
||||||
struct SimilarItemsHStack: View {
|
struct SimilarItemsHStack: View {
|
||||||
|
|
||||||
@Default(.Customization.similarPosterType)
|
@Default(.Customization.similarPosterType)
|
||||||
|
@ -26,6 +27,13 @@ extension ItemView {
|
||||||
type: similarPosterType,
|
type: similarPosterType,
|
||||||
items: items
|
items: items
|
||||||
)
|
)
|
||||||
|
.trailing {
|
||||||
|
SeeAllPoster(type: similarPosterType)
|
||||||
|
.onSelect {
|
||||||
|
let viewModel = StaticLibraryViewModel(items: items)
|
||||||
|
router.route(to: \.basicLibrary, .init(title: L10n.recommended, viewModel: viewModel))
|
||||||
|
}
|
||||||
|
}
|
||||||
.onSelect { item in
|
.onSelect { item in
|
||||||
router.route(to: \.item, item)
|
router.route(to: \.item, item)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ extension EpisodeItemView {
|
||||||
.frame(height: UIScreen.main.bounds.height - 150)
|
.frame(height: UIScreen.main.bounds.height - 150)
|
||||||
.padding(.bottom, 50)
|
.padding(.bottom, 50)
|
||||||
|
|
||||||
ItemView.CastAndCrewHStack(people: viewModel.item.people?.filter(\.isDisplayed) ?? [])
|
ItemView.CastAndCrewHStack(people: viewModel.item.people ?? [])
|
||||||
|
|
||||||
if let seriesItem = viewModel.seriesItem {
|
if let seriesItem = viewModel.seriesItem {
|
||||||
PosterHStack(title: L10n.series, type: .portrait, items: [seriesItem])
|
PosterHStack(title: L10n.series, type: .portrait, items: [seriesItem])
|
||||||
|
|
|
@ -25,7 +25,7 @@ extension MovieItemView {
|
||||||
.frame(height: UIScreen.main.bounds.height - 150)
|
.frame(height: UIScreen.main.bounds.height - 150)
|
||||||
.padding(.bottom, 50)
|
.padding(.bottom, 50)
|
||||||
|
|
||||||
ItemView.CastAndCrewHStack(people: viewModel.item.people?.filter(\.isDisplayed) ?? [])
|
ItemView.CastAndCrewHStack(people: viewModel.item.people ?? [])
|
||||||
|
|
||||||
ItemView.SimilarItemsHStack(items: viewModel.similarItems)
|
ItemView.SimilarItemsHStack(items: viewModel.similarItems)
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ extension SeriesItemView {
|
||||||
SeriesEpisodesView(viewModel: viewModel)
|
SeriesEpisodesView(viewModel: viewModel)
|
||||||
.environmentObject(focusGuide)
|
.environmentObject(focusGuide)
|
||||||
|
|
||||||
ItemView.CastAndCrewHStack(people: viewModel.item.people?.filter(\.isDisplayed) ?? [])
|
ItemView.CastAndCrewHStack(people: viewModel.item.people ?? [])
|
||||||
|
|
||||||
ItemView.SimilarItemsHStack(items: viewModel.similarItems)
|
ItemView.SimilarItemsHStack(items: viewModel.similarItems)
|
||||||
|
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
//
|
|
||||||
// Swiftfin is subject to the terms of the Mozilla Public
|
|
||||||
// License, v2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
|
||||||
//
|
|
||||||
|
|
||||||
import JellyfinAPI
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct LatestInLibraryView: View {
|
|
||||||
|
|
||||||
@EnvironmentObject
|
|
||||||
private var router: HomeCoordinator.Router
|
|
||||||
@StateObject
|
|
||||||
var viewModel: LatestMediaViewModel
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
PosterHStack(title: L10n.latestWithString(viewModel.library.displayName), type: .portrait, items: viewModel.items)
|
|
||||||
.trailing {
|
|
||||||
Button {
|
|
||||||
router.route(to: \.library, .init(parent: viewModel.library, type: .library, filters: .recent))
|
|
||||||
} label: {
|
|
||||||
ZStack {
|
|
||||||
Color(UIColor.darkGray)
|
|
||||||
.opacity(0.5)
|
|
||||||
|
|
||||||
VStack(spacing: 20) {
|
|
||||||
Image(systemName: "chevron.right")
|
|
||||||
.font(.title)
|
|
||||||
|
|
||||||
L10n.seeAll.text
|
|
||||||
.font(.title3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.posterStyle(type: .portrait, width: PosterType.portrait.width)
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
}
|
|
||||||
.onSelect { item in
|
|
||||||
router.route(to: \.item, item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,7 +8,6 @@
|
||||||
|
|
||||||
import CollectionView
|
import CollectionView
|
||||||
import Defaults
|
import Defaults
|
||||||
import Introspect
|
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
@ -18,11 +17,6 @@ struct LibraryView: View {
|
||||||
private var router: LibraryCoordinator.Router
|
private var router: LibraryCoordinator.Router
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var viewModel: LibraryViewModel
|
var viewModel: LibraryViewModel
|
||||||
@State
|
|
||||||
private var scrollViewOffset: CGPoint = .zero
|
|
||||||
|
|
||||||
@Default(.Customization.Library.gridPosterType)
|
|
||||||
private var libraryPosterType
|
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var loadingView: some View {
|
private var loadingView: some View {
|
||||||
|
@ -50,26 +44,11 @@ struct LibraryView: View {
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var libraryItemsView: some View {
|
private var libraryItemsView: some View {
|
||||||
CollectionView(items: viewModel.items) { _, item, _ in
|
PagingLibraryView(viewModel: viewModel)
|
||||||
PosterButton(item: item, type: libraryPosterType)
|
.onSelect { item in
|
||||||
.onSelect { item in
|
baseItemOnSelect(item)
|
||||||
baseItemOnSelect(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.layout { _, layoutEnvironment in
|
|
||||||
.grid(
|
|
||||||
layoutEnvironment: layoutEnvironment,
|
|
||||||
layoutMode: .fixedNumberOfColumns(7),
|
|
||||||
lineSpacing: 50
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.willReachEdge(insets: .init(top: 0, leading: 0, bottom: 600, trailing: 0)) { edge in
|
|
||||||
if !viewModel.isLoading && edge == .bottom {
|
|
||||||
viewModel.requestNextPage()
|
|
||||||
}
|
}
|
||||||
}
|
.ignoresSafeArea()
|
||||||
.scrollViewOffset($scrollViewOffset)
|
|
||||||
.ignoresSafeArea()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
|
@ -24,7 +24,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(1.12)
|
.scaleItem(1.12)
|
||||||
.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))
|
||||||
|
@ -36,7 +36,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)
|
||||||
|
|
|
@ -1,92 +0,0 @@
|
||||||
//
|
|
||||||
// Swiftfin is subject to the terms of the Mozilla Public
|
|
||||||
// License, v2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
|
||||||
//
|
|
||||||
|
|
||||||
import JellyfinAPI
|
|
||||||
import SwiftUI
|
|
||||||
import SwiftUICollection
|
|
||||||
|
|
||||||
struct MovieLibrariesView: View {
|
|
||||||
|
|
||||||
@EnvironmentObject
|
|
||||||
private var movieLibrariesRouter: MovieLibrariesCoordinator.Router
|
|
||||||
@StateObject
|
|
||||||
var viewModel: MovieLibrariesViewModel
|
|
||||||
var title: String
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
if viewModel.isLoading == true {
|
|
||||||
ProgressView()
|
|
||||||
} else if !viewModel.rows.isEmpty {
|
|
||||||
CollectionView(rows: viewModel.rows) { _, _ in
|
|
||||||
let itemSize = NSCollectionLayoutSize(
|
|
||||||
widthDimension: .fractionalWidth(1),
|
|
||||||
heightDimension: .fractionalHeight(1)
|
|
||||||
)
|
|
||||||
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
|
||||||
|
|
||||||
let groupSize = NSCollectionLayoutSize(
|
|
||||||
widthDimension: .absolute(200),
|
|
||||||
heightDimension: .absolute(300)
|
|
||||||
)
|
|
||||||
let group = NSCollectionLayoutGroup.horizontal(
|
|
||||||
layoutSize: groupSize,
|
|
||||||
subitems: [item]
|
|
||||||
)
|
|
||||||
|
|
||||||
let header =
|
|
||||||
NSCollectionLayoutBoundarySupplementaryItem(
|
|
||||||
layoutSize: NSCollectionLayoutSize(
|
|
||||||
widthDimension: .fractionalWidth(1),
|
|
||||||
heightDimension: .absolute(44)
|
|
||||||
),
|
|
||||||
elementKind: UICollectionView.elementKindSectionHeader,
|
|
||||||
alignment: .topLeading
|
|
||||||
)
|
|
||||||
|
|
||||||
let section = NSCollectionLayoutSection(group: group)
|
|
||||||
|
|
||||||
section.contentInsets = NSDirectionalEdgeInsets(top: 30, leading: 0, bottom: 80, trailing: 80)
|
|
||||||
section.interGroupSpacing = 48
|
|
||||||
section.orthogonalScrollingBehavior = .continuous
|
|
||||||
section.boundarySupplementaryItems = [header]
|
|
||||||
return section
|
|
||||||
} cell: { _, cell in
|
|
||||||
GeometryReader { _ in
|
|
||||||
if let item = cell.item {
|
|
||||||
if item.type != .folder {
|
|
||||||
Button {
|
|
||||||
self.movieLibrariesRouter.route(to: \.library, item)
|
|
||||||
} label: {
|
|
||||||
PortraitItemElement(item: item)
|
|
||||||
}
|
|
||||||
.buttonStyle(PlainNavigationLinkButtonStyle())
|
|
||||||
}
|
|
||||||
} else if cell.loadingCell {
|
|
||||||
ProgressView()
|
|
||||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} supplementaryView: { _, indexPath in
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
}.accessibilityIdentifier("\(indexPath.section).\(indexPath.row)")
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
.ignoresSafeArea(.all)
|
|
||||||
} else {
|
|
||||||
VStack {
|
|
||||||
L10n.noResults.text
|
|
||||||
Button {
|
|
||||||
print("movieLibraries reload")
|
|
||||||
} label: {
|
|
||||||
L10n.refresh.text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
//
|
|
||||||
// Swiftfin is subject to the terms of the Mozilla Public
|
|
||||||
// License, v2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
|
||||||
//
|
|
||||||
|
|
||||||
import JellyfinAPI
|
|
||||||
import SwiftUI
|
|
||||||
import SwiftUICollection
|
|
||||||
|
|
||||||
struct TVLibrariesView: View {
|
|
||||||
@EnvironmentObject
|
|
||||||
private var tvLibrariesRouter: TVLibrariesCoordinator.Router
|
|
||||||
@StateObject
|
|
||||||
var viewModel: TVLibrariesViewModel
|
|
||||||
var title: String
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
if viewModel.isLoading == true {
|
|
||||||
ProgressView()
|
|
||||||
} else if !viewModel.rows.isEmpty {
|
|
||||||
CollectionView(rows: viewModel.rows) { _, _ in
|
|
||||||
let itemSize = NSCollectionLayoutSize(
|
|
||||||
widthDimension: .fractionalWidth(1),
|
|
||||||
heightDimension: .fractionalHeight(1)
|
|
||||||
)
|
|
||||||
let item = NSCollectionLayoutItem(layoutSize: itemSize)
|
|
||||||
|
|
||||||
let groupSize = NSCollectionLayoutSize(
|
|
||||||
widthDimension: .absolute(200),
|
|
||||||
heightDimension: .absolute(300)
|
|
||||||
)
|
|
||||||
let group = NSCollectionLayoutGroup.horizontal(
|
|
||||||
layoutSize: groupSize,
|
|
||||||
subitems: [item]
|
|
||||||
)
|
|
||||||
|
|
||||||
let header =
|
|
||||||
NSCollectionLayoutBoundarySupplementaryItem(
|
|
||||||
layoutSize: NSCollectionLayoutSize(
|
|
||||||
widthDimension: .fractionalWidth(1),
|
|
||||||
heightDimension: .absolute(44)
|
|
||||||
),
|
|
||||||
elementKind: UICollectionView.elementKindSectionHeader,
|
|
||||||
alignment: .topLeading
|
|
||||||
)
|
|
||||||
|
|
||||||
let section = NSCollectionLayoutSection(group: group)
|
|
||||||
|
|
||||||
section.contentInsets = NSDirectionalEdgeInsets(top: 30, leading: 0, bottom: 80, trailing: 80)
|
|
||||||
section.interGroupSpacing = 48
|
|
||||||
section.orthogonalScrollingBehavior = .continuous
|
|
||||||
section.boundarySupplementaryItems = [header]
|
|
||||||
return section
|
|
||||||
} cell: { _, cell in
|
|
||||||
GeometryReader { _ in
|
|
||||||
if let item = cell.item {
|
|
||||||
if item.type != .folder {
|
|
||||||
Button {
|
|
||||||
self.tvLibrariesRouter.route(to: \.library, item)
|
|
||||||
} label: {
|
|
||||||
PortraitItemElement(item: item)
|
|
||||||
}
|
|
||||||
.buttonStyle(PlainNavigationLinkButtonStyle())
|
|
||||||
}
|
|
||||||
} else if cell.loadingCell {
|
|
||||||
ProgressView()
|
|
||||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} supplementaryView: { _, indexPath in
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
}.accessibilityIdentifier("\(indexPath.section).\(indexPath.row)")
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
.ignoresSafeArea(.all)
|
|
||||||
} else {
|
|
||||||
VStack {
|
|
||||||
L10n.noResults.text
|
|
||||||
Button {
|
|
||||||
print("tvLibraries reload")
|
|
||||||
} label: {
|
|
||||||
L10n.refresh.text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -39,7 +39,6 @@
|
||||||
5364F455266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* BaseItemPersonExtensions.swift */; };
|
5364F455266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* BaseItemPersonExtensions.swift */; };
|
||||||
5364F456266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* BaseItemPersonExtensions.swift */; };
|
5364F456266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5364F454266CA0DC0026ECBA /* BaseItemPersonExtensions.swift */; };
|
||||||
536D3D78267BD5C30004248C /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB57B2678CE1000530A6E /* ViewModel.swift */; };
|
536D3D78267BD5C30004248C /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB57B2678CE1000530A6E /* ViewModel.swift */; };
|
||||||
536D3D81267BDFC60004248C /* PortraitItemElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536D3D80267BDFC60004248C /* PortraitItemElement.swift */; };
|
|
||||||
5377CBF5263B596A003A4E83 /* JellyfinPlayerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5377CBF4263B596A003A4E83 /* JellyfinPlayerApp.swift */; };
|
5377CBF5263B596A003A4E83 /* JellyfinPlayerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5377CBF4263B596A003A4E83 /* JellyfinPlayerApp.swift */; };
|
||||||
5377CBF9263B596B003A4E83 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5377CBF8263B596B003A4E83 /* Assets.xcassets */; };
|
5377CBF9263B596B003A4E83 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5377CBF8263B596B003A4E83 /* Assets.xcassets */; };
|
||||||
5377CBFC263B596B003A4E83 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5377CBFB263B596B003A4E83 /* Preview Assets.xcassets */; };
|
5377CBFC263B596B003A4E83 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5377CBFB263B596B003A4E83 /* Preview Assets.xcassets */; };
|
||||||
|
@ -153,10 +152,8 @@
|
||||||
62C83B08288C6A630004ED0C /* FontPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C83B07288C6A630004ED0C /* FontPicker.swift */; };
|
62C83B08288C6A630004ED0C /* FontPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C83B07288C6A630004ED0C /* FontPicker.swift */; };
|
||||||
62E1DCC3273CE19800C9AE76 /* URLExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E1DCC2273CE19800C9AE76 /* URLExtensions.swift */; };
|
62E1DCC3273CE19800C9AE76 /* URLExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E1DCC2273CE19800C9AE76 /* URLExtensions.swift */; };
|
||||||
62E1DCC4273CE19800C9AE76 /* URLExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E1DCC2273CE19800C9AE76 /* URLExtensions.swift */; };
|
62E1DCC4273CE19800C9AE76 /* URLExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E1DCC2273CE19800C9AE76 /* URLExtensions.swift */; };
|
||||||
62E632DA267D2BC40063E547 /* LatestMediaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */; };
|
|
||||||
62E632DC267D2E130063E547 /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632DB267D2E130063E547 /* SearchViewModel.swift */; };
|
62E632DC267D2E130063E547 /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632DB267D2E130063E547 /* SearchViewModel.swift */; };
|
||||||
62E632DD267D2E130063E547 /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632DB267D2E130063E547 /* SearchViewModel.swift */; };
|
62E632DD267D2E130063E547 /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632DB267D2E130063E547 /* SearchViewModel.swift */; };
|
||||||
62E632DE267D2E170063E547 /* LatestMediaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */; };
|
|
||||||
62E632E0267D30CA0063E547 /* LibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632DF267D30CA0063E547 /* LibraryViewModel.swift */; };
|
62E632E0267D30CA0063E547 /* LibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632DF267D30CA0063E547 /* LibraryViewModel.swift */; };
|
||||||
62E632E1267D30CA0063E547 /* LibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632DF267D30CA0063E547 /* LibraryViewModel.swift */; };
|
62E632E1267D30CA0063E547 /* LibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632DF267D30CA0063E547 /* LibraryViewModel.swift */; };
|
||||||
62E632E3267D3BA60063E547 /* MovieItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632E2267D3BA60063E547 /* MovieItemViewModel.swift */; };
|
62E632E3267D3BA60063E547 /* MovieItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632E2267D3BA60063E547 /* MovieItemViewModel.swift */; };
|
||||||
|
@ -180,9 +177,7 @@
|
||||||
C400DB6D27FE8E65007B65FE /* LiveTVChannelItemWideElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = C400DB6C27FE8E65007B65FE /* LiveTVChannelItemWideElement.swift */; };
|
C400DB6D27FE8E65007B65FE /* LiveTVChannelItemWideElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = C400DB6C27FE8E65007B65FE /* LiveTVChannelItemWideElement.swift */; };
|
||||||
C409CE9C284EA6EA00CABC12 /* SwiftUICollection in Frameworks */ = {isa = PBXBuildFile; productRef = C409CE9B284EA6EA00CABC12 /* SwiftUICollection */; };
|
C409CE9C284EA6EA00CABC12 /* SwiftUICollection in Frameworks */ = {isa = PBXBuildFile; productRef = C409CE9B284EA6EA00CABC12 /* SwiftUICollection */; };
|
||||||
C409CE9E285044C800CABC12 /* SwiftUICollection in Frameworks */ = {isa = PBXBuildFile; productRef = C409CE9D285044C800CABC12 /* SwiftUICollection */; };
|
C409CE9E285044C800CABC12 /* SwiftUICollection in Frameworks */ = {isa = PBXBuildFile; productRef = C409CE9D285044C800CABC12 /* SwiftUICollection */; };
|
||||||
C40CD923271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD921271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift */; };
|
C40CD926271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD924271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift */; };
|
||||||
C40CD926271F8D1E000FB198 /* MovieLibrariesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD924271F8D1E000FB198 /* MovieLibrariesViewModel.swift */; };
|
|
||||||
C40CD929271F8DAB000FB198 /* MovieLibrariesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD927271F8DAB000FB198 /* MovieLibrariesView.swift */; };
|
|
||||||
C4464953281616AE00DDB461 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5377CBF8263B596B003A4E83 /* Assets.xcassets */; };
|
C4464953281616AE00DDB461 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5377CBF8263B596B003A4E83 /* Assets.xcassets */; };
|
||||||
C453497F279A2DA50045F1E2 /* LiveTVPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C453497E279A2DA50045F1E2 /* LiveTVPlayerViewController.swift */; };
|
C453497F279A2DA50045F1E2 /* LiveTVPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C453497E279A2DA50045F1E2 /* LiveTVPlayerViewController.swift */; };
|
||||||
C4534981279A3F140045F1E2 /* tvOSLiveTVOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4534980279A3F140045F1E2 /* tvOSLiveTVOverlay.swift */; };
|
C4534981279A3F140045F1E2 /* tvOSLiveTVOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4534980279A3F140045F1E2 /* tvOSLiveTVOverlay.swift */; };
|
||||||
|
@ -195,13 +190,10 @@
|
||||||
C45942CB27F6984100C54FE7 /* LiveTVPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45942CA27F6984100C54FE7 /* LiveTVPlayerViewController.swift */; };
|
C45942CB27F6984100C54FE7 /* LiveTVPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45942CA27F6984100C54FE7 /* LiveTVPlayerViewController.swift */; };
|
||||||
C45942CD27F6994A00C54FE7 /* LiveTVPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45942CC27F6994A00C54FE7 /* LiveTVPlayerView.swift */; };
|
C45942CD27F6994A00C54FE7 /* LiveTVPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45942CC27F6994A00C54FE7 /* LiveTVPlayerView.swift */; };
|
||||||
C45942D027F69C2400C54FE7 /* LiveTVChannelsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07872728448B003F4AD1 /* LiveTVChannelsCoordinator.swift */; };
|
C45942D027F69C2400C54FE7 /* LiveTVChannelsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07872728448B003F4AD1 /* LiveTVChannelsCoordinator.swift */; };
|
||||||
C45B29BB26FAC5B600CEF5E0 /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA5126D04AAF00CC4EB7 /* ColorExtension.swift */; };
|
C45B29BB26FAC5B600CEF5E0 /* ColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA5126D04AAF00CC4EB7 /* ColorExtensions.swift */; };
|
||||||
C4AE2C3027498D2300AE13CF /* LiveTVHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AE2C2F27498D2300AE13CF /* LiveTVHomeView.swift */; };
|
C4AE2C3027498D2300AE13CF /* LiveTVHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AE2C2F27498D2300AE13CF /* LiveTVHomeView.swift */; };
|
||||||
C4AE2C3227498D6A00AE13CF /* LiveTVProgramsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AE2C3127498D6A00AE13CF /* LiveTVProgramsView.swift */; };
|
C4AE2C3227498D6A00AE13CF /* LiveTVProgramsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AE2C3127498D6A00AE13CF /* LiveTVProgramsView.swift */; };
|
||||||
C4B9B91427E1921B0063535C /* LiveTVNativeVideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B9B91327E1921B0063535C /* LiveTVNativeVideoPlayerView.swift */; };
|
C4B9B91427E1921B0063535C /* LiveTVNativeVideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4B9B91327E1921B0063535C /* LiveTVNativeVideoPlayerView.swift */; };
|
||||||
C4BE0764271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE0762271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift */; };
|
|
||||||
C4BE0767271FC109003F4AD1 /* TVLibrariesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE0765271FC109003F4AD1 /* TVLibrariesViewModel.swift */; };
|
|
||||||
C4BE076A271FC164003F4AD1 /* TVLibrariesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE0768271FC164003F4AD1 /* TVLibrariesView.swift */; };
|
|
||||||
C4BE076F2720FEFF003F4AD1 /* PlainNavigationLinkButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */; };
|
C4BE076F2720FEFF003F4AD1 /* PlainNavigationLinkButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */; };
|
||||||
C4BE07722725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07702725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift */; };
|
C4BE07722725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07702725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift */; };
|
||||||
C4BE07742725EB66003F4AD1 /* LiveTVProgramsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07732725EB66003F4AD1 /* LiveTVProgramsView.swift */; };
|
C4BE07742725EB66003F4AD1 /* LiveTVProgramsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07732725EB66003F4AD1 /* LiveTVProgramsView.swift */; };
|
||||||
|
@ -233,6 +225,10 @@
|
||||||
E10EAA53277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */; };
|
E10EAA53277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */; };
|
||||||
E10EAA54277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */; };
|
E10EAA54277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */; };
|
||||||
E1101177281B1E8A006A3584 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = E1101176281B1E8A006A3584 /* Puppy */; };
|
E1101177281B1E8A006A3584 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = E1101176281B1E8A006A3584 /* Puppy */; };
|
||||||
|
E111D8F528D03B7500400001 /* PagingLibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E111D8F428D03B7500400001 /* PagingLibraryViewModel.swift */; };
|
||||||
|
E111D8F628D03B7500400001 /* PagingLibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E111D8F428D03B7500400001 /* PagingLibraryViewModel.swift */; };
|
||||||
|
E111D8F828D03BF900400001 /* PagingLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E111D8F728D03BF900400001 /* PagingLibraryView.swift */; };
|
||||||
|
E111D8FA28D0400900400001 /* PagingLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E111D8F928D0400900400001 /* PagingLibraryView.swift */; };
|
||||||
E113132B28BDB4B500930F75 /* NavBarDrawerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113132A28BDB4B500930F75 /* NavBarDrawerView.swift */; };
|
E113132B28BDB4B500930F75 /* NavBarDrawerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113132A28BDB4B500930F75 /* NavBarDrawerView.swift */; };
|
||||||
E113132F28BDB66A00930F75 /* NavBarDrawerModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113132E28BDB66A00930F75 /* NavBarDrawerModifier.swift */; };
|
E113132F28BDB66A00930F75 /* NavBarDrawerModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113132E28BDB66A00930F75 /* NavBarDrawerModifier.swift */; };
|
||||||
E113133228BDC72000930F75 /* FilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113133128BDC72000930F75 /* FilterView.swift */; };
|
E113133228BDC72000930F75 /* FilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113133128BDC72000930F75 /* FilterView.swift */; };
|
||||||
|
@ -267,6 +263,22 @@
|
||||||
E126F741278A656C00A522BF /* ServerStreamType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E126F740278A656C00A522BF /* ServerStreamType.swift */; };
|
E126F741278A656C00A522BF /* ServerStreamType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E126F740278A656C00A522BF /* ServerStreamType.swift */; };
|
||||||
E126F742278A656C00A522BF /* ServerStreamType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E126F740278A656C00A522BF /* ServerStreamType.swift */; };
|
E126F742278A656C00A522BF /* ServerStreamType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E126F740278A656C00A522BF /* ServerStreamType.swift */; };
|
||||||
E12B835F28C07D8500878399 /* LibraryParent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113133728BEADBA00930F75 /* LibraryParent.swift */; };
|
E12B835F28C07D8500878399 /* LibraryParent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113133728BEADBA00930F75 /* LibraryParent.swift */; };
|
||||||
|
E12CC1AE28D0FAEA00678D5D /* NextUpLibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12CC1AD28D0FAEA00678D5D /* NextUpLibraryViewModel.swift */; };
|
||||||
|
E12CC1AF28D0FAEA00678D5D /* NextUpLibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12CC1AD28D0FAEA00678D5D /* NextUpLibraryViewModel.swift */; };
|
||||||
|
E12CC1B128D1008F00678D5D /* NextUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12CC1B028D1008F00678D5D /* NextUpView.swift */; };
|
||||||
|
E12CC1B528D1124400678D5D /* BasicLibraryCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12CC1B428D1124400678D5D /* BasicLibraryCoordinator.swift */; };
|
||||||
|
E12CC1B628D1124400678D5D /* BasicLibraryCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12CC1B428D1124400678D5D /* BasicLibraryCoordinator.swift */; };
|
||||||
|
E12CC1B928D11A1D00678D5D /* BasicLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12CC1B828D11A1D00678D5D /* BasicLibraryView.swift */; };
|
||||||
|
E12CC1BB28D11E1000678D5D /* RecentlyAddedViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12CC1BA28D11E1000678D5D /* RecentlyAddedViewModel.swift */; };
|
||||||
|
E12CC1BC28D11E1000678D5D /* RecentlyAddedViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12CC1BA28D11E1000678D5D /* RecentlyAddedViewModel.swift */; };
|
||||||
|
E12CC1BE28D11F4500678D5D /* RecentlyAddedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12CC1BD28D11F4500678D5D /* RecentlyAddedView.swift */; };
|
||||||
|
E12CC1BF28D1260600678D5D /* ItemTypeLibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD924271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift */; };
|
||||||
|
E12CC1C128D12B0A00678D5D /* BasicLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12CC1C028D12B0A00678D5D /* BasicLibraryView.swift */; };
|
||||||
|
E12CC1C528D12D9B00678D5D /* SeeAllPoster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12CC1C428D12D9B00678D5D /* SeeAllPoster.swift */; };
|
||||||
|
E12CC1C728D12FD600678D5D /* CinematicRecentlyAddedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12CC1C628D12FD600678D5D /* CinematicRecentlyAddedView.swift */; };
|
||||||
|
E12CC1C928D132B800678D5D /* RecentlyAddedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12CC1C828D132B800678D5D /* RecentlyAddedView.swift */; };
|
||||||
|
E12CC1CB28D1333400678D5D /* CinematicResumeItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12CC1CA28D1333400678D5D /* CinematicResumeItemView.swift */; };
|
||||||
|
E12CC1CD28D135C700678D5D /* NextUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12CC1CC28D135C700678D5D /* NextUpView.swift */; };
|
||||||
E1347DB2279E3C6200BC6161 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = E1347DB1279E3C6200BC6161 /* Puppy */; };
|
E1347DB2279E3C6200BC6161 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = E1347DB1279E3C6200BC6161 /* Puppy */; };
|
||||||
E1347DB6279E3CA500BC6161 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = E1347DB5279E3CA500BC6161 /* Puppy */; };
|
E1347DB6279E3CA500BC6161 /* Puppy in Frameworks */ = {isa = PBXBuildFile; productRef = E1347DB5279E3CA500BC6161 /* Puppy */; };
|
||||||
E1384944278036C70024FB48 /* VLCPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1384943278036C70024FB48 /* VLCPlayerViewController.swift */; };
|
E1384944278036C70024FB48 /* VLCPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1384943278036C70024FB48 /* VLCPlayerViewController.swift */; };
|
||||||
|
@ -306,7 +318,6 @@
|
||||||
E13F05EC28BC9000003499D2 /* LibraryViewType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13F05EB28BC9000003499D2 /* LibraryViewType.swift */; };
|
E13F05EC28BC9000003499D2 /* LibraryViewType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13F05EB28BC9000003499D2 /* LibraryViewType.swift */; };
|
||||||
E13F05ED28BC9000003499D2 /* LibraryViewType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13F05EB28BC9000003499D2 /* LibraryViewType.swift */; };
|
E13F05ED28BC9000003499D2 /* LibraryViewType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13F05EB28BC9000003499D2 /* LibraryViewType.swift */; };
|
||||||
E13F05F128BC9016003499D2 /* LibraryItemRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13F05EF28BC9016003499D2 /* LibraryItemRow.swift */; };
|
E13F05F128BC9016003499D2 /* LibraryItemRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13F05EF28BC9016003499D2 /* LibraryItemRow.swift */; };
|
||||||
E13F05F228BC9016003499D2 /* LibraryItemRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13F05EF28BC9016003499D2 /* LibraryItemRow.swift */; };
|
|
||||||
E13F05F328BC9016003499D2 /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13F05F028BC9016003499D2 /* LibraryView.swift */; };
|
E13F05F328BC9016003499D2 /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13F05F028BC9016003499D2 /* LibraryView.swift */; };
|
||||||
E148128328C1443D003B8787 /* NameGUIDPairExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD105E26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift */; };
|
E148128328C1443D003B8787 /* NameGUIDPairExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD105E26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift */; };
|
||||||
E148128528C15472003B8787 /* APISortOrderExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E148128428C15472003B8787 /* APISortOrderExtensions.swift */; };
|
E148128528C15472003B8787 /* APISortOrderExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E148128428C15472003B8787 /* APISortOrderExtensions.swift */; };
|
||||||
|
@ -326,7 +337,7 @@
|
||||||
E1734D7C28B9577700C66367 /* CollectionView in Frameworks */ = {isa = PBXBuildFile; productRef = E1734D7B28B9577700C66367 /* CollectionView */; };
|
E1734D7C28B9577700C66367 /* CollectionView in Frameworks */ = {isa = PBXBuildFile; productRef = E1734D7B28B9577700C66367 /* CollectionView */; };
|
||||||
E1734D7E28B9578100C66367 /* CollectionView in Frameworks */ = {isa = PBXBuildFile; productRef = E1734D7D28B9578100C66367 /* CollectionView */; };
|
E1734D7E28B9578100C66367 /* CollectionView in Frameworks */ = {isa = PBXBuildFile; productRef = E1734D7D28B9578100C66367 /* CollectionView */; };
|
||||||
E173DA5026D048D600CC4EB7 /* ServerDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */; };
|
E173DA5026D048D600CC4EB7 /* ServerDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */; };
|
||||||
E173DA5226D04AAF00CC4EB7 /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA5126D04AAF00CC4EB7 /* ColorExtension.swift */; };
|
E173DA5226D04AAF00CC4EB7 /* ColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA5126D04AAF00CC4EB7 /* ColorExtensions.swift */; };
|
||||||
E173DA5426D050F500CC4EB7 /* ServerDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA5326D050F500CC4EB7 /* ServerDetailViewModel.swift */; };
|
E173DA5426D050F500CC4EB7 /* ServerDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA5326D050F500CC4EB7 /* ServerDetailViewModel.swift */; };
|
||||||
E176DE6D278E30D2001EFD8D /* EpisodeCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E176DE6C278E30D2001EFD8D /* EpisodeCard.swift */; };
|
E176DE6D278E30D2001EFD8D /* EpisodeCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E176DE6C278E30D2001EFD8D /* EpisodeCard.swift */; };
|
||||||
E178857D278037FD0094FBCF /* JellyfinAPI in Frameworks */ = {isa = PBXBuildFile; productRef = E178857C278037FD0094FBCF /* JellyfinAPI */; };
|
E178857D278037FD0094FBCF /* JellyfinAPI in Frameworks */ = {isa = PBXBuildFile; productRef = E178857C278037FD0094FBCF /* JellyfinAPI */; };
|
||||||
|
@ -481,6 +492,15 @@
|
||||||
E1CCF13128AC07EC006CAC9E /* PosterHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CCF13028AC07EC006CAC9E /* PosterHStack.swift */; };
|
E1CCF13128AC07EC006CAC9E /* PosterHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CCF13028AC07EC006CAC9E /* PosterHStack.swift */; };
|
||||||
E1CEFBF527914C7700F60429 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CEFBF427914C7700F60429 /* CustomizeViewsSettings.swift */; };
|
E1CEFBF527914C7700F60429 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CEFBF427914C7700F60429 /* CustomizeViewsSettings.swift */; };
|
||||||
E1CEFBF727914E6400F60429 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CEFBF627914E6400F60429 /* CustomizeViewsSettings.swift */; };
|
E1CEFBF727914E6400F60429 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CEFBF627914E6400F60429 /* CustomizeViewsSettings.swift */; };
|
||||||
|
E1D3043228D175CE00587289 /* StaticLibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D3043128D175CE00587289 /* StaticLibraryViewModel.swift */; };
|
||||||
|
E1D3043328D175CE00587289 /* StaticLibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D3043128D175CE00587289 /* StaticLibraryViewModel.swift */; };
|
||||||
|
E1D3043528D1763100587289 /* SeeAllButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D3043428D1763100587289 /* SeeAllButton.swift */; };
|
||||||
|
E1D3043A28D189C500587289 /* CastAndCrewLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D3043928D189C500587289 /* CastAndCrewLibraryView.swift */; };
|
||||||
|
E1D3043C28D18CD400587289 /* CastAndCrewLibraryCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D3043B28D18CD400587289 /* CastAndCrewLibraryCoordinator.swift */; };
|
||||||
|
E1D3043D28D18CD400587289 /* CastAndCrewLibraryCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D3043B28D18CD400587289 /* CastAndCrewLibraryCoordinator.swift */; };
|
||||||
|
E1D3043F28D18F5700587289 /* CastAndCrewLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D3043E28D18F5700587289 /* CastAndCrewLibraryView.swift */; };
|
||||||
|
E1D3044228D1976600587289 /* CastAndCrewItemRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D3044128D1976600587289 /* CastAndCrewItemRow.swift */; };
|
||||||
|
E1D3044428D1991900587289 /* LibraryViewTypeToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D3044328D1991900587289 /* LibraryViewTypeToggle.swift */; };
|
||||||
E1D4BF7C2719D05000A11E64 /* BasicAppSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF7B2719D05000A11E64 /* BasicAppSettingsView.swift */; };
|
E1D4BF7C2719D05000A11E64 /* BasicAppSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF7B2719D05000A11E64 /* BasicAppSettingsView.swift */; };
|
||||||
E1D4BF7E2719D1DD00A11E64 /* BasicAppSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF7D2719D1DC00A11E64 /* BasicAppSettingsViewModel.swift */; };
|
E1D4BF7E2719D1DD00A11E64 /* BasicAppSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF7D2719D1DC00A11E64 /* BasicAppSettingsViewModel.swift */; };
|
||||||
E1D4BF7F2719D1DD00A11E64 /* BasicAppSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF7D2719D1DC00A11E64 /* BasicAppSettingsViewModel.swift */; };
|
E1D4BF7F2719D1DD00A11E64 /* BasicAppSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF7D2719D1DC00A11E64 /* BasicAppSettingsViewModel.swift */; };
|
||||||
|
@ -500,8 +520,8 @@
|
||||||
E1E1643F28BB075C00323B0A /* SelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1643D28BB074000323B0A /* SelectorView.swift */; };
|
E1E1643F28BB075C00323B0A /* SelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1643D28BB074000323B0A /* SelectorView.swift */; };
|
||||||
E1E1644128BB301900323B0A /* ArrayExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1644028BB301900323B0A /* ArrayExtensions.swift */; };
|
E1E1644128BB301900323B0A /* ArrayExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1644028BB301900323B0A /* ArrayExtensions.swift */; };
|
||||||
E1E1644228BB301900323B0A /* ArrayExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1644028BB301900323B0A /* ArrayExtensions.swift */; };
|
E1E1644228BB301900323B0A /* ArrayExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1644028BB301900323B0A /* ArrayExtensions.swift */; };
|
||||||
E1E1644428BC60C600323B0A /* LibraryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1644328BC60C600323B0A /* LibraryItem.swift */; };
|
E1E1644428BC60C600323B0A /* MediaLibraryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1644328BC60C600323B0A /* MediaLibraryItem.swift */; };
|
||||||
E1E1644528BC60C600323B0A /* LibraryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1644328BC60C600323B0A /* LibraryItem.swift */; };
|
E1E1644528BC60C600323B0A /* MediaLibraryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1644328BC60C600323B0A /* MediaLibraryItem.swift */; };
|
||||||
E1E48CC9271E6D410021A2F9 /* RefreshHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E48CC8271E6D410021A2F9 /* RefreshHelper.swift */; };
|
E1E48CC9271E6D410021A2F9 /* RefreshHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E48CC8271E6D410021A2F9 /* RefreshHelper.swift */; };
|
||||||
E1E5D5442783BB5100692DFE /* ItemDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E5D5432783BB5100692DFE /* ItemDetailsView.swift */; };
|
E1E5D5442783BB5100692DFE /* ItemDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E5D5432783BB5100692DFE /* ItemDetailsView.swift */; };
|
||||||
E1E5D5492783CDD700692DFE /* OverlaySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E5D5472783CCF900692DFE /* OverlaySettingsView.swift */; };
|
E1E5D5492783CDD700692DFE /* OverlaySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E5D5472783CCF900692DFE /* OverlaySettingsView.swift */; };
|
||||||
|
@ -617,7 +637,6 @@
|
||||||
5362E4C8267D40F7000E2F71 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
|
5362E4C8267D40F7000E2F71 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
|
||||||
53649AB0269CFB1900A2D8B7 /* LogManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogManager.swift; sourceTree = "<group>"; };
|
53649AB0269CFB1900A2D8B7 /* LogManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogManager.swift; sourceTree = "<group>"; };
|
||||||
5364F454266CA0DC0026ECBA /* BaseItemPersonExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseItemPersonExtensions.swift; sourceTree = "<group>"; };
|
5364F454266CA0DC0026ECBA /* BaseItemPersonExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseItemPersonExtensions.swift; sourceTree = "<group>"; };
|
||||||
536D3D80267BDFC60004248C /* PortraitItemElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortraitItemElement.swift; sourceTree = "<group>"; };
|
|
||||||
5377CBF1263B596A003A4E83 /* Swiftfin iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Swiftfin iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
5377CBF1263B596A003A4E83 /* Swiftfin iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Swiftfin iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
5377CBF4263B596A003A4E83 /* JellyfinPlayerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinPlayerApp.swift; sourceTree = "<group>"; };
|
5377CBF4263B596A003A4E83 /* JellyfinPlayerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinPlayerApp.swift; sourceTree = "<group>"; };
|
||||||
5377CBF8263B596B003A4E83 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
5377CBF8263B596B003A4E83 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
@ -700,7 +719,6 @@
|
||||||
62C29EA726D103D500C1D2E7 /* MediaCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaCoordinator.swift; sourceTree = "<group>"; };
|
62C29EA726D103D500C1D2E7 /* MediaCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaCoordinator.swift; sourceTree = "<group>"; };
|
||||||
62C83B07288C6A630004ED0C /* FontPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontPicker.swift; sourceTree = "<group>"; };
|
62C83B07288C6A630004ED0C /* FontPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontPicker.swift; sourceTree = "<group>"; };
|
||||||
62E1DCC2273CE19800C9AE76 /* URLExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLExtensions.swift; sourceTree = "<group>"; };
|
62E1DCC2273CE19800C9AE76 /* URLExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLExtensions.swift; sourceTree = "<group>"; };
|
||||||
62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestMediaViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
62E632DB267D2E130063E547 /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = "<group>"; };
|
62E632DB267D2E130063E547 /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = "<group>"; };
|
||||||
62E632DF267D30CA0063E547 /* LibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryViewModel.swift; sourceTree = "<group>"; };
|
62E632DF267D30CA0063E547 /* LibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryViewModel.swift; sourceTree = "<group>"; };
|
||||||
62E632E2267D3BA60063E547 /* MovieItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieItemViewModel.swift; sourceTree = "<group>"; };
|
62E632E2267D3BA60063E547 /* MovieItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieItemViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
@ -716,9 +734,7 @@
|
||||||
AE8C3158265D6F90008AA076 /* bitrates.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitrates.json; sourceTree = "<group>"; };
|
AE8C3158265D6F90008AA076 /* bitrates.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitrates.json; sourceTree = "<group>"; };
|
||||||
C400DB6927FE894F007B65FE /* LiveTVChannelsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVChannelsView.swift; sourceTree = "<group>"; };
|
C400DB6927FE894F007B65FE /* LiveTVChannelsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVChannelsView.swift; sourceTree = "<group>"; };
|
||||||
C400DB6C27FE8E65007B65FE /* LiveTVChannelItemWideElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVChannelItemWideElement.swift; sourceTree = "<group>"; };
|
C400DB6C27FE8E65007B65FE /* LiveTVChannelItemWideElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVChannelItemWideElement.swift; sourceTree = "<group>"; };
|
||||||
C40CD921271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoviesLibrariesCoordinator.swift; sourceTree = "<group>"; };
|
C40CD924271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemTypeLibraryViewModel.swift; sourceTree = "<group>"; };
|
||||||
C40CD924271F8D1E000FB198 /* MovieLibrariesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieLibrariesViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
C40CD927271F8DAB000FB198 /* MovieLibrariesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieLibrariesView.swift; sourceTree = "<group>"; };
|
|
||||||
C453497E279A2DA50045F1E2 /* LiveTVPlayerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveTVPlayerViewController.swift; sourceTree = "<group>"; };
|
C453497E279A2DA50045F1E2 /* LiveTVPlayerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveTVPlayerViewController.swift; sourceTree = "<group>"; };
|
||||||
C4534980279A3F140045F1E2 /* tvOSLiveTVOverlay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = tvOSLiveTVOverlay.swift; sourceTree = "<group>"; };
|
C4534980279A3F140045F1E2 /* tvOSLiveTVOverlay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = tvOSLiveTVOverlay.swift; sourceTree = "<group>"; };
|
||||||
C4534982279A40990045F1E2 /* tvOSLiveTVVideoPlayerCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = tvOSLiveTVVideoPlayerCoordinator.swift; sourceTree = "<group>"; };
|
C4534982279A40990045F1E2 /* tvOSLiveTVVideoPlayerCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = tvOSLiveTVVideoPlayerCoordinator.swift; sourceTree = "<group>"; };
|
||||||
|
@ -731,9 +747,6 @@
|
||||||
C4AE2C2F27498D2300AE13CF /* LiveTVHomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVHomeView.swift; sourceTree = "<group>"; };
|
C4AE2C2F27498D2300AE13CF /* LiveTVHomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVHomeView.swift; sourceTree = "<group>"; };
|
||||||
C4AE2C3127498D6A00AE13CF /* LiveTVProgramsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVProgramsView.swift; sourceTree = "<group>"; };
|
C4AE2C3127498D6A00AE13CF /* LiveTVProgramsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVProgramsView.swift; sourceTree = "<group>"; };
|
||||||
C4B9B91327E1921B0063535C /* LiveTVNativeVideoPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVNativeVideoPlayerView.swift; sourceTree = "<group>"; };
|
C4B9B91327E1921B0063535C /* LiveTVNativeVideoPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVNativeVideoPlayerView.swift; sourceTree = "<group>"; };
|
||||||
C4BE0762271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVLibrariesCoordinator.swift; sourceTree = "<group>"; };
|
|
||||||
C4BE0765271FC109003F4AD1 /* TVLibrariesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVLibrariesViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
C4BE0768271FC164003F4AD1 /* TVLibrariesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVLibrariesView.swift; sourceTree = "<group>"; };
|
|
||||||
C4BE07702725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVProgramsCoordinator.swift; sourceTree = "<group>"; };
|
C4BE07702725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVProgramsCoordinator.swift; sourceTree = "<group>"; };
|
||||||
C4BE07732725EB66003F4AD1 /* LiveTVProgramsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVProgramsView.swift; sourceTree = "<group>"; };
|
C4BE07732725EB66003F4AD1 /* LiveTVProgramsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVProgramsView.swift; sourceTree = "<group>"; };
|
||||||
C4BE07752725EBEA003F4AD1 /* LiveTVProgramsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVProgramsViewModel.swift; sourceTree = "<group>"; };
|
C4BE07752725EBEA003F4AD1 /* LiveTVProgramsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTVProgramsViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
@ -753,6 +766,9 @@
|
||||||
E10D87E127852FD000BD264C /* EpisodesRowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodesRowManager.swift; sourceTree = "<group>"; };
|
E10D87E127852FD000BD264C /* EpisodesRowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodesRowManager.swift; sourceTree = "<group>"; };
|
||||||
E10EAA4E277BBCC4000269ED /* CGSizeExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGSizeExtensions.swift; sourceTree = "<group>"; };
|
E10EAA4E277BBCC4000269ED /* CGSizeExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGSizeExtensions.swift; sourceTree = "<group>"; };
|
||||||
E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BaseItemDto+VideoPlayerViewModel.swift"; sourceTree = "<group>"; };
|
E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BaseItemDto+VideoPlayerViewModel.swift"; sourceTree = "<group>"; };
|
||||||
|
E111D8F428D03B7500400001 /* PagingLibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagingLibraryViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
E111D8F728D03BF900400001 /* PagingLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagingLibraryView.swift; sourceTree = "<group>"; };
|
||||||
|
E111D8F928D0400900400001 /* PagingLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagingLibraryView.swift; sourceTree = "<group>"; };
|
||||||
E113132A28BDB4B500930F75 /* NavBarDrawerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavBarDrawerView.swift; sourceTree = "<group>"; };
|
E113132A28BDB4B500930F75 /* NavBarDrawerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavBarDrawerView.swift; sourceTree = "<group>"; };
|
||||||
E113132E28BDB66A00930F75 /* NavBarDrawerModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavBarDrawerModifier.swift; sourceTree = "<group>"; };
|
E113132E28BDB66A00930F75 /* NavBarDrawerModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavBarDrawerModifier.swift; sourceTree = "<group>"; };
|
||||||
E113133128BDC72000930F75 /* FilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterView.swift; sourceTree = "<group>"; };
|
E113133128BDC72000930F75 /* FilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -775,6 +791,18 @@
|
||||||
E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaStreamExtension.swift; sourceTree = "<group>"; };
|
E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaStreamExtension.swift; sourceTree = "<group>"; };
|
||||||
E1267D3D271A1F46003C492E /* PreferenceUIHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceUIHostingController.swift; sourceTree = "<group>"; };
|
E1267D3D271A1F46003C492E /* PreferenceUIHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceUIHostingController.swift; sourceTree = "<group>"; };
|
||||||
E126F740278A656C00A522BF /* ServerStreamType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerStreamType.swift; sourceTree = "<group>"; };
|
E126F740278A656C00A522BF /* ServerStreamType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerStreamType.swift; sourceTree = "<group>"; };
|
||||||
|
E12CC1AD28D0FAEA00678D5D /* NextUpLibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextUpLibraryViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
E12CC1B028D1008F00678D5D /* NextUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextUpView.swift; sourceTree = "<group>"; };
|
||||||
|
E12CC1B428D1124400678D5D /* BasicLibraryCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicLibraryCoordinator.swift; sourceTree = "<group>"; };
|
||||||
|
E12CC1B828D11A1D00678D5D /* BasicLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicLibraryView.swift; sourceTree = "<group>"; };
|
||||||
|
E12CC1BA28D11E1000678D5D /* RecentlyAddedViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentlyAddedViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
E12CC1BD28D11F4500678D5D /* RecentlyAddedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentlyAddedView.swift; sourceTree = "<group>"; };
|
||||||
|
E12CC1C028D12B0A00678D5D /* BasicLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicLibraryView.swift; sourceTree = "<group>"; };
|
||||||
|
E12CC1C428D12D9B00678D5D /* SeeAllPoster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeeAllPoster.swift; sourceTree = "<group>"; };
|
||||||
|
E12CC1C628D12FD600678D5D /* CinematicRecentlyAddedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicRecentlyAddedView.swift; sourceTree = "<group>"; };
|
||||||
|
E12CC1C828D132B800678D5D /* RecentlyAddedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentlyAddedView.swift; sourceTree = "<group>"; };
|
||||||
|
E12CC1CA28D1333400678D5D /* CinematicResumeItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicResumeItemView.swift; sourceTree = "<group>"; };
|
||||||
|
E12CC1CC28D135C700678D5D /* NextUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextUpView.swift; sourceTree = "<group>"; };
|
||||||
E1384943278036C70024FB48 /* VLCPlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VLCPlayerViewController.swift; sourceTree = "<group>"; };
|
E1384943278036C70024FB48 /* VLCPlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VLCPlayerViewController.swift; sourceTree = "<group>"; };
|
||||||
E1399473289B1EA900401ABC /* Defaults+Workaround.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Defaults+Workaround.swift"; sourceTree = "<group>"; };
|
E1399473289B1EA900401ABC /* Defaults+Workaround.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Defaults+Workaround.swift"; sourceTree = "<group>"; };
|
||||||
E13AD72D2798BC8D00FDCEE8 /* NativePlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativePlayerViewController.swift; sourceTree = "<group>"; };
|
E13AD72D2798BC8D00FDCEE8 /* NativePlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativePlayerViewController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -809,7 +837,7 @@
|
||||||
E168BD0F289A4162001A6922 /* HomeErrorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeErrorView.swift; sourceTree = "<group>"; };
|
E168BD0F289A4162001A6922 /* HomeErrorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeErrorView.swift; sourceTree = "<group>"; };
|
||||||
E16AA60728A364A6009A983C /* PosterButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PosterButton.swift; sourceTree = "<group>"; };
|
E16AA60728A364A6009A983C /* PosterButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PosterButton.swift; sourceTree = "<group>"; };
|
||||||
E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailView.swift; sourceTree = "<group>"; };
|
E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailView.swift; sourceTree = "<group>"; };
|
||||||
E173DA5126D04AAF00CC4EB7 /* ColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtension.swift; sourceTree = "<group>"; };
|
E173DA5126D04AAF00CC4EB7 /* ColorExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtensions.swift; sourceTree = "<group>"; };
|
||||||
E173DA5326D050F500CC4EB7 /* ServerDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailViewModel.swift; sourceTree = "<group>"; };
|
E173DA5326D050F500CC4EB7 /* ServerDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailViewModel.swift; sourceTree = "<group>"; };
|
||||||
E176DE6C278E30D2001EFD8D /* EpisodeCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeCard.swift; sourceTree = "<group>"; };
|
E176DE6C278E30D2001EFD8D /* EpisodeCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeCard.swift; sourceTree = "<group>"; };
|
||||||
E178859A2780F1F40094FBCF /* tvOSSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = tvOSSlider.swift; sourceTree = "<group>"; };
|
E178859A2780F1F40094FBCF /* tvOSSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = tvOSSlider.swift; sourceTree = "<group>"; };
|
||||||
|
@ -921,6 +949,13 @@
|
||||||
E1CCF13028AC07EC006CAC9E /* PosterHStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PosterHStack.swift; sourceTree = "<group>"; };
|
E1CCF13028AC07EC006CAC9E /* PosterHStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PosterHStack.swift; sourceTree = "<group>"; };
|
||||||
E1CEFBF427914C7700F60429 /* CustomizeViewsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeViewsSettings.swift; sourceTree = "<group>"; };
|
E1CEFBF427914C7700F60429 /* CustomizeViewsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeViewsSettings.swift; sourceTree = "<group>"; };
|
||||||
E1CEFBF627914E6400F60429 /* CustomizeViewsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeViewsSettings.swift; sourceTree = "<group>"; };
|
E1CEFBF627914E6400F60429 /* CustomizeViewsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeViewsSettings.swift; sourceTree = "<group>"; };
|
||||||
|
E1D3043128D175CE00587289 /* StaticLibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLibraryViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
E1D3043428D1763100587289 /* SeeAllButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeeAllButton.swift; sourceTree = "<group>"; };
|
||||||
|
E1D3043928D189C500587289 /* CastAndCrewLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CastAndCrewLibraryView.swift; sourceTree = "<group>"; };
|
||||||
|
E1D3043B28D18CD400587289 /* CastAndCrewLibraryCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CastAndCrewLibraryCoordinator.swift; sourceTree = "<group>"; };
|
||||||
|
E1D3043E28D18F5700587289 /* CastAndCrewLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CastAndCrewLibraryView.swift; sourceTree = "<group>"; };
|
||||||
|
E1D3044128D1976600587289 /* CastAndCrewItemRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CastAndCrewItemRow.swift; sourceTree = "<group>"; };
|
||||||
|
E1D3044328D1991900587289 /* LibraryViewTypeToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryViewTypeToggle.swift; sourceTree = "<group>"; };
|
||||||
E1D4BF7B2719D05000A11E64 /* BasicAppSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicAppSettingsView.swift; sourceTree = "<group>"; };
|
E1D4BF7B2719D05000A11E64 /* BasicAppSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicAppSettingsView.swift; sourceTree = "<group>"; };
|
||||||
E1D4BF7D2719D1DC00A11E64 /* BasicAppSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicAppSettingsViewModel.swift; sourceTree = "<group>"; };
|
E1D4BF7D2719D1DC00A11E64 /* BasicAppSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicAppSettingsViewModel.swift; sourceTree = "<group>"; };
|
||||||
E1D4BF802719D22800A11E64 /* AppAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAppearance.swift; sourceTree = "<group>"; };
|
E1D4BF802719D22800A11E64 /* AppAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAppearance.swift; sourceTree = "<group>"; };
|
||||||
|
@ -932,7 +967,7 @@
|
||||||
E1E1643928BAC2EF00323B0A /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
|
E1E1643928BAC2EF00323B0A /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
|
||||||
E1E1643D28BB074000323B0A /* SelectorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorView.swift; sourceTree = "<group>"; };
|
E1E1643D28BB074000323B0A /* SelectorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorView.swift; sourceTree = "<group>"; };
|
||||||
E1E1644028BB301900323B0A /* ArrayExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayExtensions.swift; sourceTree = "<group>"; };
|
E1E1644028BB301900323B0A /* ArrayExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayExtensions.swift; sourceTree = "<group>"; };
|
||||||
E1E1644328BC60C600323B0A /* LibraryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryItem.swift; sourceTree = "<group>"; };
|
E1E1644328BC60C600323B0A /* MediaLibraryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLibraryItem.swift; sourceTree = "<group>"; };
|
||||||
E1E48CC8271E6D410021A2F9 /* RefreshHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshHelper.swift; sourceTree = "<group>"; };
|
E1E48CC8271E6D410021A2F9 /* RefreshHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshHelper.swift; sourceTree = "<group>"; };
|
||||||
E1E5D5432783BB5100692DFE /* ItemDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDetailsView.swift; sourceTree = "<group>"; };
|
E1E5D5432783BB5100692DFE /* ItemDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemDetailsView.swift; sourceTree = "<group>"; };
|
||||||
E1E5D5472783CCF900692DFE /* OverlaySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlaySettingsView.swift; sourceTree = "<group>"; };
|
E1E5D5472783CCF900692DFE /* OverlaySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlaySettingsView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1071,19 +1106,21 @@
|
||||||
E10D87E127852FD000BD264C /* EpisodesRowManager.swift */,
|
E10D87E127852FD000BD264C /* EpisodesRowManager.swift */,
|
||||||
E113133928BEB71D00930F75 /* FilterViewModel.swift */,
|
E113133928BEB71D00930F75 /* FilterViewModel.swift */,
|
||||||
625CB5722678C32A00530A6E /* HomeViewModel.swift */,
|
625CB5722678C32A00530A6E /* HomeViewModel.swift */,
|
||||||
|
C40CD924271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift */,
|
||||||
E107BB9127880A4000354E07 /* ItemViewModel */,
|
E107BB9127880A4000354E07 /* ItemViewModel */,
|
||||||
62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */,
|
|
||||||
62E632DF267D30CA0063E547 /* LibraryViewModel.swift */,
|
62E632DF267D30CA0063E547 /* LibraryViewModel.swift */,
|
||||||
C4BE07842728446F003F4AD1 /* LiveTVChannelsViewModel.swift */,
|
C4BE07842728446F003F4AD1 /* LiveTVChannelsViewModel.swift */,
|
||||||
C4BE07752725EBEA003F4AD1 /* LiveTVProgramsViewModel.swift */,
|
C4BE07752725EBEA003F4AD1 /* LiveTVProgramsViewModel.swift */,
|
||||||
625CB5742678C33500530A6E /* MediaViewModel.swift */,
|
625CB5742678C33500530A6E /* MediaViewModel.swift */,
|
||||||
C40CD924271F8D1E000FB198 /* MovieLibrariesViewModel.swift */,
|
E12CC1AD28D0FAEA00678D5D /* NextUpLibraryViewModel.swift */,
|
||||||
|
E111D8F428D03B7500400001 /* PagingLibraryViewModel.swift */,
|
||||||
6334175C287DE0D0000603CE /* QuickConnectSettingsViewModel.swift */,
|
6334175C287DE0D0000603CE /* QuickConnectSettingsViewModel.swift */,
|
||||||
|
E12CC1BA28D11E1000678D5D /* RecentlyAddedViewModel.swift */,
|
||||||
62E632DB267D2E130063E547 /* SearchViewModel.swift */,
|
62E632DB267D2E130063E547 /* SearchViewModel.swift */,
|
||||||
E173DA5326D050F500CC4EB7 /* ServerDetailViewModel.swift */,
|
E173DA5326D050F500CC4EB7 /* ServerDetailViewModel.swift */,
|
||||||
E13DD3E027176BD3009D4DAF /* ServerListViewModel.swift */,
|
E13DD3E027176BD3009D4DAF /* ServerListViewModel.swift */,
|
||||||
5321753A2671BCFC005491E6 /* SettingsViewModel.swift */,
|
5321753A2671BCFC005491E6 /* SettingsViewModel.swift */,
|
||||||
C4BE0765271FC109003F4AD1 /* TVLibrariesViewModel.swift */,
|
E1D3043128D175CE00587289 /* StaticLibraryViewModel.swift */,
|
||||||
E13DD3F82717E961009D4DAF /* UserListViewModel.swift */,
|
E13DD3F82717E961009D4DAF /* UserListViewModel.swift */,
|
||||||
E13DD3EB27178A54009D4DAF /* UserSignInViewModel.swift */,
|
E13DD3EB27178A54009D4DAF /* UserSignInViewModel.swift */,
|
||||||
09389CC626819B4500AE350E /* VideoPlayerModel.swift */,
|
09389CC626819B4500AE350E /* VideoPlayerModel.swift */,
|
||||||
|
@ -1190,9 +1227,9 @@
|
||||||
E19169CD272514760085832A /* HTTPScheme.swift */,
|
E19169CD272514760085832A /* HTTPScheme.swift */,
|
||||||
535870AC2669D8DD00D05A09 /* ItemFilters.swift */,
|
535870AC2669D8DD00D05A09 /* ItemFilters.swift */,
|
||||||
E1C925F328875037002A7A66 /* ItemViewType.swift */,
|
E1C925F328875037002A7A66 /* ItemViewType.swift */,
|
||||||
E1E1644328BC60C600323B0A /* LibraryItem.swift */,
|
|
||||||
E113133728BEADBA00930F75 /* LibraryParent.swift */,
|
E113133728BEADBA00930F75 /* LibraryParent.swift */,
|
||||||
E13F05EB28BC9000003499D2 /* LibraryViewType.swift */,
|
E13F05EB28BC9000003499D2 /* LibraryViewType.swift */,
|
||||||
|
E1E1644328BC60C600323B0A /* MediaLibraryItem.swift */,
|
||||||
E1AA331E2782639D00F6439C /* OverlayType.swift */,
|
E1AA331E2782639D00F6439C /* OverlayType.swift */,
|
||||||
E1C925F62887504B002A7A66 /* PanDirectionGestureRecognizer.swift */,
|
E1C925F62887504B002A7A66 /* PanDirectionGestureRecognizer.swift */,
|
||||||
E1C812B4277A8E5D00918266 /* PlaybackSpeed.swift */,
|
E1C812B4277A8E5D00918266 /* PlaybackSpeed.swift */,
|
||||||
|
@ -1216,9 +1253,10 @@
|
||||||
E1E5D5432783BB5100692DFE /* ItemDetailsView.swift */,
|
E1E5D5432783BB5100692DFE /* ItemDetailsView.swift */,
|
||||||
531690F6267ACC00005D8AB9 /* LandscapeItemElement.swift */,
|
531690F6267ACC00005D8AB9 /* LandscapeItemElement.swift */,
|
||||||
E1A42E5028CBE44500A14DCB /* LandscapePosterProgressBar.swift */,
|
E1A42E5028CBE44500A14DCB /* LandscapePosterProgressBar.swift */,
|
||||||
536D3D80267BDFC60004248C /* PortraitItemElement.swift */,
|
E111D8F928D0400900400001 /* PagingLibraryView.swift */,
|
||||||
E1C92617288756BD002A7A66 /* PosterButton.swift */,
|
E1C92617288756BD002A7A66 /* PosterButton.swift */,
|
||||||
E1C92619288756BD002A7A66 /* PosterHStack.swift */,
|
E1C92619288756BD002A7A66 /* PosterHStack.swift */,
|
||||||
|
E12CC1C428D12D9B00678D5D /* SeeAllPoster.swift */,
|
||||||
E1E9EFE928C6B96400CC1F8B /* ServerButton.swift */,
|
E1E9EFE928C6B96400CC1F8B /* ServerButton.swift */,
|
||||||
E17885A3278105170094FBCF /* SFSymbolButton.swift */,
|
E17885A3278105170094FBCF /* SFSymbolButton.swift */,
|
||||||
E1CCC3D128C858A50020ED54 /* UserProfileButton.swift */,
|
E1CCC3D128C858A50020ED54 /* UserProfileButton.swift */,
|
||||||
|
@ -1453,11 +1491,15 @@
|
||||||
E18E01A7288746AF0022598C /* DotHStack.swift */,
|
E18E01A7288746AF0022598C /* DotHStack.swift */,
|
||||||
E1FE69AF28C2DA4A0021BC93 /* FilterDrawerHStack */,
|
E1FE69AF28C2DA4A0021BC93 /* FilterDrawerHStack */,
|
||||||
E1FE69A928C29CC20021BC93 /* LandscapePosterProgressBar.swift */,
|
E1FE69A928C29CC20021BC93 /* LandscapePosterProgressBar.swift */,
|
||||||
|
E13F05EF28BC9016003499D2 /* LibraryItemRow.swift */,
|
||||||
|
E1D3044328D1991900587289 /* LibraryViewTypeToggle.swift */,
|
||||||
|
E111D8F728D03BF900400001 /* PagingLibraryView.swift */,
|
||||||
E18E01A5288746AF0022598C /* PillHStack.swift */,
|
E18E01A5288746AF0022598C /* PillHStack.swift */,
|
||||||
E16AA60728A364A6009A983C /* PosterButton.swift */,
|
E16AA60728A364A6009A983C /* PosterButton.swift */,
|
||||||
E1CCF13028AC07EC006CAC9E /* PosterHStack.swift */,
|
E1CCF13028AC07EC006CAC9E /* PosterHStack.swift */,
|
||||||
E1AA331C2782541500F6439C /* PrimaryButton.swift */,
|
E1AA331C2782541500F6439C /* PrimaryButton.swift */,
|
||||||
E18E01A4288746AF0022598C /* RefreshableScrollView.swift */,
|
E18E01A4288746AF0022598C /* RefreshableScrollView.swift */,
|
||||||
|
E1D3043428D1763100587289 /* SeeAllButton.swift */,
|
||||||
);
|
);
|
||||||
path = Components;
|
path = Components;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1478,7 +1520,7 @@
|
||||||
E1A2C157279A7D76005EC829 /* BundleExtensions.swift */,
|
E1A2C157279A7D76005EC829 /* BundleExtensions.swift */,
|
||||||
E10EAA4E277BBCC4000269ED /* CGSizeExtensions.swift */,
|
E10EAA4E277BBCC4000269ED /* CGSizeExtensions.swift */,
|
||||||
6267B3D526710B8900A7371D /* CollectionExtensions.swift */,
|
6267B3D526710B8900A7371D /* CollectionExtensions.swift */,
|
||||||
E173DA5126D04AAF00CC4EB7 /* ColorExtension.swift */,
|
E173DA5126D04AAF00CC4EB7 /* ColorExtensions.swift */,
|
||||||
E1399473289B1EA900401ABC /* Defaults+Workaround.swift */,
|
E1399473289B1EA900401ABC /* Defaults+Workaround.swift */,
|
||||||
E1E00A34278628A40022235B /* DoubleExtensions.swift */,
|
E1E00A34278628A40022235B /* DoubleExtensions.swift */,
|
||||||
E11CEB8C28999B4A003E74C7 /* FontExtensions.swift */,
|
E11CEB8C28999B4A003E74C7 /* FontExtensions.swift */,
|
||||||
|
@ -1508,6 +1550,8 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E1D4BF892719D3D000A11E64 /* BasicAppSettingsCoordinator.swift */,
|
E1D4BF892719D3D000A11E64 /* BasicAppSettingsCoordinator.swift */,
|
||||||
|
E1D3043B28D18CD400587289 /* CastAndCrewLibraryCoordinator.swift */,
|
||||||
|
E12CC1B428D1124400678D5D /* BasicLibraryCoordinator.swift */,
|
||||||
62C29EA226D1030F00C1D2E7 /* ConnectToServerCoodinator.swift */,
|
62C29EA226D1030F00C1D2E7 /* ConnectToServerCoodinator.swift */,
|
||||||
6220D0B926D6092100B8E046 /* FilterCoordinator.swift */,
|
6220D0B926D6092100B8E046 /* FilterCoordinator.swift */,
|
||||||
62C29EA526D1036A00C1D2E7 /* HomeCoordinator.swift */,
|
62C29EA526D1036A00C1D2E7 /* HomeCoordinator.swift */,
|
||||||
|
@ -1520,13 +1564,11 @@
|
||||||
C4BE07782726EE82003F4AD1 /* LiveTVTabCoordinator.swift */,
|
C4BE07782726EE82003F4AD1 /* LiveTVTabCoordinator.swift */,
|
||||||
E193D5412719404B00900D82 /* MainCoordinator */,
|
E193D5412719404B00900D82 /* MainCoordinator */,
|
||||||
62C29EA726D103D500C1D2E7 /* MediaCoordinator.swift */,
|
62C29EA726D103D500C1D2E7 /* MediaCoordinator.swift */,
|
||||||
C40CD921271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift */,
|
|
||||||
E18CE0B828A2322D0092E7F1 /* QuickConnectCoordinator.swift */,
|
E18CE0B828A2322D0092E7F1 /* QuickConnectCoordinator.swift */,
|
||||||
6220D0B626D5EE1100B8E046 /* SearchCoordinator.swift */,
|
6220D0B626D5EE1100B8E046 /* SearchCoordinator.swift */,
|
||||||
E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */,
|
E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */,
|
||||||
E13DD3E827177ED6009D4DAF /* ServerListCoordinator.swift */,
|
E13DD3E827177ED6009D4DAF /* ServerListCoordinator.swift */,
|
||||||
6220D0B026D5EC9900B8E046 /* SettingsCoordinator.swift */,
|
6220D0B026D5EC9900B8E046 /* SettingsCoordinator.swift */,
|
||||||
C4BE0762271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift */,
|
|
||||||
E13DD4012717EE79009D4DAF /* UserListCoordinator.swift */,
|
E13DD4012717EE79009D4DAF /* UserListCoordinator.swift */,
|
||||||
E13DD3F127179378009D4DAF /* UserSignInCoordinator.swift */,
|
E13DD3F127179378009D4DAF /* UserSignInCoordinator.swift */,
|
||||||
E1C812CF277AE4C700918266 /* VideoPlayerCoordinator */,
|
E1C812CF277AE4C700918266 /* VideoPlayerCoordinator */,
|
||||||
|
@ -1661,22 +1703,21 @@
|
||||||
children = (
|
children = (
|
||||||
E1A2C15F279A7DCA005EC829 /* AboutAppView.swift */,
|
E1A2C15F279A7DCA005EC829 /* AboutAppView.swift */,
|
||||||
E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */,
|
E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */,
|
||||||
|
E1D3043E28D18F5700587289 /* CastAndCrewLibraryView.swift */,
|
||||||
|
E12CC1C028D12B0A00678D5D /* BasicLibraryView.swift */,
|
||||||
53ABFDEA2679753200886593 /* ConnectToServerView.swift */,
|
53ABFDEA2679753200886593 /* ConnectToServerView.swift */,
|
||||||
E1A42E4D28CBD3B200A14DCB /* HomeView */,
|
E1A42E4D28CBD3B200A14DCB /* HomeView */,
|
||||||
E193D54E271942C000900D82 /* ItemView */,
|
E193D54E271942C000900D82 /* ItemView */,
|
||||||
E1C925F828875647002A7A66 /* LatestInLibraryView.swift */,
|
|
||||||
53A83C32268A309300DF3D92 /* LibraryView.swift */,
|
53A83C32268A309300DF3D92 /* LibraryView.swift */,
|
||||||
C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */,
|
C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */,
|
||||||
C4BE078A272844AF003F4AD1 /* LiveTVChannelsView.swift */,
|
C4BE078A272844AF003F4AD1 /* LiveTVChannelsView.swift */,
|
||||||
C4BE078D27298817003F4AD1 /* LiveTVHomeView.swift */,
|
C4BE078D27298817003F4AD1 /* LiveTVHomeView.swift */,
|
||||||
C4BE07732725EB66003F4AD1 /* LiveTVProgramsView.swift */,
|
C4BE07732725EB66003F4AD1 /* LiveTVProgramsView.swift */,
|
||||||
C4E508172703E8190045C9AB /* MediaView.swift */,
|
C4E508172703E8190045C9AB /* MediaView.swift */,
|
||||||
C40CD927271F8DAB000FB198 /* MovieLibrariesView.swift */,
|
|
||||||
E1E1643928BAC2EF00323B0A /* SearchView.swift */,
|
E1E1643928BAC2EF00323B0A /* SearchView.swift */,
|
||||||
E193D54F2719430400900D82 /* ServerDetailView.swift */,
|
E193D54F2719430400900D82 /* ServerDetailView.swift */,
|
||||||
E193D54A271941D300900D82 /* ServerListView.swift */,
|
E193D54A271941D300900D82 /* ServerListView.swift */,
|
||||||
E1E5D54D2783E66600692DFE /* SettingsView */,
|
E1E5D54D2783E66600692DFE /* SettingsView */,
|
||||||
C4BE0768271FC164003F4AD1 /* TVLibrariesView.swift */,
|
|
||||||
E193D546271941C500900D82 /* UserListView.swift */,
|
E193D546271941C500900D82 /* UserListView.swift */,
|
||||||
E193D548271941CC00900D82 /* UserSignInView.swift */,
|
E193D548271941CC00900D82 /* UserSignInView.swift */,
|
||||||
5310694F2684E7EE00CFFDBA /* VideoPlayer */,
|
5310694F2684E7EE00CFFDBA /* VideoPlayer */,
|
||||||
|
@ -1693,6 +1734,18 @@
|
||||||
path = VideoPlayerViewModel;
|
path = VideoPlayerViewModel;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
E12CC1C328D12D6300678D5D /* Components */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E12CC1C628D12FD600678D5D /* CinematicRecentlyAddedView.swift */,
|
||||||
|
E12CC1CA28D1333400678D5D /* CinematicResumeItemView.swift */,
|
||||||
|
E1C925F828875647002A7A66 /* LatestInLibraryView.swift */,
|
||||||
|
E12CC1CC28D135C700678D5D /* NextUpView.swift */,
|
||||||
|
E12CC1C828D132B800678D5D /* RecentlyAddedView.swift */,
|
||||||
|
);
|
||||||
|
path = Components;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
E13DD3BB27163C3E009D4DAF /* App */ = {
|
E13DD3BB27163C3E009D4DAF /* App */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -1717,13 +1770,15 @@
|
||||||
children = (
|
children = (
|
||||||
E18E01F3288747580022598C /* AboutAppView.swift */,
|
E18E01F3288747580022598C /* AboutAppView.swift */,
|
||||||
E1D4BF7B2719D05000A11E64 /* BasicAppSettingsView.swift */,
|
E1D4BF7B2719D05000A11E64 /* BasicAppSettingsView.swift */,
|
||||||
|
E12CC1B828D11A1D00678D5D /* BasicLibraryView.swift */,
|
||||||
|
E1D3044028D1974700587289 /* CastAndCrewLibraryView */,
|
||||||
5338F74D263B61370014BF09 /* ConnectToServerView.swift */,
|
5338F74D263B61370014BF09 /* ConnectToServerView.swift */,
|
||||||
|
E113133128BDC72000930F75 /* FilterView.swift */,
|
||||||
62C83B07288C6A630004ED0C /* FontPicker.swift */,
|
62C83B07288C6A630004ED0C /* FontPicker.swift */,
|
||||||
E168BD07289A4162001A6922 /* HomeView */,
|
E168BD07289A4162001A6922 /* HomeView */,
|
||||||
E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */,
|
E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */,
|
||||||
E14F7D0A26DB3714007C3AE6 /* ItemView */,
|
E14F7D0A26DB3714007C3AE6 /* ItemView */,
|
||||||
E113133128BDC72000930F75 /* FilterView.swift */,
|
E13F05F028BC9016003499D2 /* LibraryView.swift */,
|
||||||
E13F05EE28BC9016003499D2 /* LibraryView */,
|
|
||||||
C4E5598828124C10003DECA5 /* LiveTVChannelItemElement.swift */,
|
C4E5598828124C10003DECA5 /* LiveTVChannelItemElement.swift */,
|
||||||
C400DB6C27FE8E65007B65FE /* LiveTVChannelItemWideElement.swift */,
|
C400DB6C27FE8E65007B65FE /* LiveTVChannelItemWideElement.swift */,
|
||||||
C400DB6927FE894F007B65FE /* LiveTVChannelsView.swift */,
|
C400DB6927FE894F007B65FE /* LiveTVChannelsView.swift */,
|
||||||
|
@ -1742,15 +1797,6 @@
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
E13F05EE28BC9016003499D2 /* LibraryView */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
E1C55AB228BD051700A9AD88 /* Components */,
|
|
||||||
E13F05F028BC9016003499D2 /* LibraryView.swift */,
|
|
||||||
);
|
|
||||||
path = LibraryView;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
E14F7D0A26DB3714007C3AE6 /* ItemView */ = {
|
E14F7D0A26DB3714007C3AE6 /* ItemView */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -1787,6 +1833,8 @@
|
||||||
children = (
|
children = (
|
||||||
E168BD0D289A4162001A6922 /* ContinueWatchingView.swift */,
|
E168BD0D289A4162001A6922 /* ContinueWatchingView.swift */,
|
||||||
E168BD0E289A4162001A6922 /* LatestInLibraryView.swift */,
|
E168BD0E289A4162001A6922 /* LatestInLibraryView.swift */,
|
||||||
|
E12CC1B028D1008F00678D5D /* NextUpView.swift */,
|
||||||
|
E12CC1BD28D11F4500678D5D /* RecentlyAddedView.swift */,
|
||||||
);
|
);
|
||||||
path = Components;
|
path = Components;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -2010,6 +2058,7 @@
|
||||||
E1A42E4D28CBD3B200A14DCB /* HomeView */ = {
|
E1A42E4D28CBD3B200A14DCB /* HomeView */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E12CC1C328D12D6300678D5D /* Components */,
|
||||||
E1A42E4B28CBD39300A14DCB /* HomeContentView.swift */,
|
E1A42E4B28CBD39300A14DCB /* HomeContentView.swift */,
|
||||||
E1A42E4E28CBD3E100A14DCB /* HomeErrorView.swift */,
|
E1A42E4E28CBD3E100A14DCB /* HomeErrorView.swift */,
|
||||||
531690E6267ABD79005D8AB9 /* HomeView.swift */,
|
531690E6267ABD79005D8AB9 /* HomeView.swift */,
|
||||||
|
@ -2056,14 +2105,6 @@
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
E1C55AB228BD051700A9AD88 /* Components */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
E13F05EF28BC9016003499D2 /* LibraryItemRow.swift */,
|
|
||||||
);
|
|
||||||
path = Components;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
E1C812CF277AE4C700918266 /* VideoPlayerCoordinator */ = {
|
E1C812CF277AE4C700918266 /* VideoPlayerCoordinator */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -2124,6 +2165,15 @@
|
||||||
path = Components;
|
path = Components;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
E1D3044028D1974700587289 /* CastAndCrewLibraryView */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E1D3044128D1976600587289 /* CastAndCrewItemRow.swift */,
|
||||||
|
E1D3043928D189C500587289 /* CastAndCrewLibraryView.swift */,
|
||||||
|
);
|
||||||
|
path = CastAndCrewLibraryView;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
E1DD1127271E7D15005BE12F /* Objects */ = {
|
E1DD1127271E7D15005BE12F /* Objects */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -2441,7 +2491,6 @@
|
||||||
E107BB9427880A8F00354E07 /* CollectionItemViewModel.swift in Sources */,
|
E107BB9427880A8F00354E07 /* CollectionItemViewModel.swift in Sources */,
|
||||||
C4534985279A40C60045F1E2 /* LiveTVVideoPlayerView.swift in Sources */,
|
C4534985279A40C60045F1E2 /* LiveTVVideoPlayerView.swift in Sources */,
|
||||||
E1A2C15A279A7D76005EC829 /* BundleExtensions.swift in Sources */,
|
E1A2C15A279A7D76005EC829 /* BundleExtensions.swift in Sources */,
|
||||||
C40CD929271F8DAB000FB198 /* MovieLibrariesView.swift in Sources */,
|
|
||||||
53ABFDE9267974EF00886593 /* HomeViewModel.swift in Sources */,
|
53ABFDE9267974EF00886593 /* HomeViewModel.swift in Sources */,
|
||||||
E13DD3F027178F87009D4DAF /* SwiftfinNotificationCenter.swift in Sources */,
|
E13DD3F027178F87009D4DAF /* SwiftfinNotificationCenter.swift in Sources */,
|
||||||
E1A16CA1288A7CFD00EA4679 /* AboutViewCard.swift in Sources */,
|
E1A16CA1288A7CFD00EA4679 /* AboutViewCard.swift in Sources */,
|
||||||
|
@ -2453,8 +2502,8 @@
|
||||||
5D32EA12278C95E30020E292 /* VLCPlayer+subtitles.swift in Sources */,
|
5D32EA12278C95E30020E292 /* VLCPlayer+subtitles.swift in Sources */,
|
||||||
E1D4BF8B2719D3D000A11E64 /* BasicAppSettingsCoordinator.swift in Sources */,
|
E1D4BF8B2719D3D000A11E64 /* BasicAppSettingsCoordinator.swift in Sources */,
|
||||||
E13DD3FA2717E961009D4DAF /* UserListViewModel.swift in Sources */,
|
E13DD3FA2717E961009D4DAF /* UserListViewModel.swift in Sources */,
|
||||||
C40CD926271F8D1E000FB198 /* MovieLibrariesViewModel.swift in Sources */,
|
C40CD926271F8D1E000FB198 /* ItemTypeLibraryViewModel.swift in Sources */,
|
||||||
62E632DE267D2E170063E547 /* LatestMediaViewModel.swift in Sources */,
|
E1D3043F28D18F5700587289 /* CastAndCrewLibraryView.swift in Sources */,
|
||||||
E1FCD09726C47118007C8DCF /* ErrorMessage.swift in Sources */,
|
E1FCD09726C47118007C8DCF /* ErrorMessage.swift in Sources */,
|
||||||
E193D53527193F8100900D82 /* ItemCoordinator.swift in Sources */,
|
E193D53527193F8100900D82 /* ItemCoordinator.swift in Sources */,
|
||||||
E1C926162887565C002A7A66 /* SeriesItemView.swift in Sources */,
|
E1C926162887565C002A7A66 /* SeriesItemView.swift in Sources */,
|
||||||
|
@ -2463,6 +2512,7 @@
|
||||||
E11CEB9128999D84003E74C7 /* EpisodeItemView.swift in Sources */,
|
E11CEB9128999D84003E74C7 /* EpisodeItemView.swift in Sources */,
|
||||||
E1C9260C2887565C002A7A66 /* MovieItemContentView.swift in Sources */,
|
E1C9260C2887565C002A7A66 /* MovieItemContentView.swift in Sources */,
|
||||||
E1C9260B2887565C002A7A66 /* MovieItemView.swift in Sources */,
|
E1C9260B2887565C002A7A66 /* MovieItemView.swift in Sources */,
|
||||||
|
E12CC1C128D12B0A00678D5D /* BasicLibraryView.swift in Sources */,
|
||||||
E1E9EFEB28C7EA2C00CC1F8B /* UserDtoExtensions.swift in Sources */,
|
E1E9EFEB28C7EA2C00CC1F8B /* UserDtoExtensions.swift in Sources */,
|
||||||
62E632EA267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */,
|
62E632EA267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */,
|
||||||
E1546777289AF46E00087E35 /* CollectionItemView.swift in Sources */,
|
E1546777289AF46E00087E35 /* CollectionItemView.swift in Sources */,
|
||||||
|
@ -2473,11 +2523,14 @@
|
||||||
E122A9142788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */,
|
E122A9142788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */,
|
||||||
E178859E2780F53B0094FBCF /* SliderView.swift in Sources */,
|
E178859E2780F53B0094FBCF /* SliderView.swift in Sources */,
|
||||||
E1384944278036C70024FB48 /* VLCPlayerViewController.swift in Sources */,
|
E1384944278036C70024FB48 /* VLCPlayerViewController.swift in Sources */,
|
||||||
|
E12CC1C528D12D9B00678D5D /* SeeAllPoster.swift in Sources */,
|
||||||
E1002B652793CEE800E47059 /* ChapterInfoExtensions.swift in Sources */,
|
E1002B652793CEE800E47059 /* ChapterInfoExtensions.swift in Sources */,
|
||||||
E12B835F28C07D8500878399 /* LibraryParent.swift in Sources */,
|
E12B835F28C07D8500878399 /* LibraryParent.swift in Sources */,
|
||||||
|
E111D8FA28D0400900400001 /* PagingLibraryView.swift in Sources */,
|
||||||
E18E021A2887492B0022598C /* AppIcon.swift in Sources */,
|
E18E021A2887492B0022598C /* AppIcon.swift in Sources */,
|
||||||
E10EAA50277BBCC4000269ED /* CGSizeExtensions.swift in Sources */,
|
E10EAA50277BBCC4000269ED /* CGSizeExtensions.swift in Sources */,
|
||||||
E126F742278A656C00A522BF /* ServerStreamType.swift in Sources */,
|
E126F742278A656C00A522BF /* ServerStreamType.swift in Sources */,
|
||||||
|
E1D3043328D175CE00587289 /* StaticLibraryViewModel.swift in Sources */,
|
||||||
E1FCD08926C35A0D007C8DCF /* NetworkError.swift in Sources */,
|
E1FCD08926C35A0D007C8DCF /* NetworkError.swift in Sources */,
|
||||||
E17885A4278105170094FBCF /* SFSymbolButton.swift in Sources */,
|
E17885A4278105170094FBCF /* SFSymbolButton.swift in Sources */,
|
||||||
E13DD3ED27178A54009D4DAF /* UserSignInViewModel.swift in Sources */,
|
E13DD3ED27178A54009D4DAF /* UserSignInViewModel.swift in Sources */,
|
||||||
|
@ -2485,6 +2538,7 @@
|
||||||
E1C9261C288756BD002A7A66 /* PosterHStack.swift in Sources */,
|
E1C9261C288756BD002A7A66 /* PosterHStack.swift in Sources */,
|
||||||
C453497F279A2DA50045F1E2 /* LiveTVPlayerViewController.swift in Sources */,
|
C453497F279A2DA50045F1E2 /* LiveTVPlayerViewController.swift in Sources */,
|
||||||
C4BE078E27298818003F4AD1 /* LiveTVHomeView.swift in Sources */,
|
C4BE078E27298818003F4AD1 /* LiveTVHomeView.swift in Sources */,
|
||||||
|
E12CC1BC28D11E1000678D5D /* RecentlyAddedViewModel.swift in Sources */,
|
||||||
E1C9261A288756BD002A7A66 /* PosterButton.swift in Sources */,
|
E1C9261A288756BD002A7A66 /* PosterButton.swift in Sources */,
|
||||||
E13DD3E227176BD3009D4DAF /* ServerListViewModel.swift in Sources */,
|
E13DD3E227176BD3009D4DAF /* ServerListViewModel.swift in Sources */,
|
||||||
E11895AA289383BC0042947B /* ScrollViewOffsetModifier.swift in Sources */,
|
E11895AA289383BC0042947B /* ScrollViewOffsetModifier.swift in Sources */,
|
||||||
|
@ -2501,16 +2555,17 @@
|
||||||
5398514526B64DA100101B49 /* SettingsView.swift in Sources */,
|
5398514526B64DA100101B49 /* SettingsView.swift in Sources */,
|
||||||
E193D54B271941D300900D82 /* ServerListView.swift in Sources */,
|
E193D54B271941D300900D82 /* ServerListView.swift in Sources */,
|
||||||
53ABFDE6267974EF00886593 /* SettingsViewModel.swift in Sources */,
|
53ABFDE6267974EF00886593 /* SettingsViewModel.swift in Sources */,
|
||||||
|
E111D8F628D03B7500400001 /* PagingLibraryViewModel.swift in Sources */,
|
||||||
C400DB6B27FE8C97007B65FE /* LiveTVChannelItemElement.swift in Sources */,
|
C400DB6B27FE8C97007B65FE /* LiveTVChannelItemElement.swift in Sources */,
|
||||||
62E632F4267D54030063E547 /* ItemViewModel.swift in Sources */,
|
62E632F4267D54030063E547 /* ItemViewModel.swift in Sources */,
|
||||||
6267B3D826710B9800A7371D /* CollectionExtensions.swift in Sources */,
|
6267B3D826710B9800A7371D /* CollectionExtensions.swift in Sources */,
|
||||||
62E632E7267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */,
|
62E632E7267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */,
|
||||||
E1C926122887565C002A7A66 /* SeriesItemContentView.swift in Sources */,
|
E1C926122887565C002A7A66 /* SeriesItemContentView.swift in Sources */,
|
||||||
C4BE0767271FC109003F4AD1 /* TVLibrariesViewModel.swift in Sources */,
|
|
||||||
E193D53727193F8700900D82 /* MediaCoordinator.swift in Sources */,
|
E193D53727193F8700900D82 /* MediaCoordinator.swift in Sources */,
|
||||||
E18E023C288749540022598C /* UIScrollViewExtensions.swift in Sources */,
|
E18E023C288749540022598C /* UIScrollViewExtensions.swift in Sources */,
|
||||||
E1A42E4C28CBD39300A14DCB /* HomeContentView.swift in Sources */,
|
E1A42E4C28CBD39300A14DCB /* HomeContentView.swift in Sources */,
|
||||||
C4534983279A40990045F1E2 /* tvOSLiveTVVideoPlayerCoordinator.swift in Sources */,
|
C4534983279A40990045F1E2 /* tvOSLiveTVVideoPlayerCoordinator.swift in Sources */,
|
||||||
|
E12CC1CB28D1333400678D5D /* CinematicResumeItemView.swift in Sources */,
|
||||||
E1C9260F2887565C002A7A66 /* AttributeHStack.swift in Sources */,
|
E1C9260F2887565C002A7A66 /* AttributeHStack.swift in Sources */,
|
||||||
E11CEB9428999D9E003E74C7 /* EpisodeItemContentView.swift in Sources */,
|
E11CEB9428999D9E003E74C7 /* EpisodeItemContentView.swift in Sources */,
|
||||||
E17885A02780F55C0094FBCF /* tvOSVLCOverlay.swift in Sources */,
|
E17885A02780F55C0094FBCF /* tvOSVLCOverlay.swift in Sources */,
|
||||||
|
@ -2528,6 +2583,7 @@
|
||||||
E1C925F928875647002A7A66 /* LatestInLibraryView.swift in Sources */,
|
E1C925F928875647002A7A66 /* LatestInLibraryView.swift in Sources */,
|
||||||
E11B1B6D2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */,
|
E11B1B6D2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */,
|
||||||
E18E02202887492B0022598C /* AttributeFillView.swift in Sources */,
|
E18E02202887492B0022598C /* AttributeFillView.swift in Sources */,
|
||||||
|
E12CC1C928D132B800678D5D /* RecentlyAddedView.swift in Sources */,
|
||||||
E1937A3C288E54AD00CB80AA /* BaseItemDto+Images.swift in Sources */,
|
E1937A3C288E54AD00CB80AA /* BaseItemDto+Images.swift in Sources */,
|
||||||
E1A42E4A28CA6CCD00A14DCB /* CinematicItemSelector.swift in Sources */,
|
E1A42E4A28CA6CCD00A14DCB /* CinematicItemSelector.swift in Sources */,
|
||||||
E154677A289AF48200087E35 /* CollectionItemContentView.swift in Sources */,
|
E154677A289AF48200087E35 /* CollectionItemContentView.swift in Sources */,
|
||||||
|
@ -2539,15 +2595,14 @@
|
||||||
E1AD104E26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */,
|
E1AD104E26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */,
|
||||||
E118959E289312020042947B /* BaseItemPerson+Poster.swift in Sources */,
|
E118959E289312020042947B /* BaseItemPerson+Poster.swift in Sources */,
|
||||||
62E632DD267D2E130063E547 /* SearchViewModel.swift in Sources */,
|
62E632DD267D2E130063E547 /* SearchViewModel.swift in Sources */,
|
||||||
536D3D81267BDFC60004248C /* PortraitItemElement.swift in Sources */,
|
|
||||||
5D1603FD278A40DB00D22B99 /* SubtitleSize.swift in Sources */,
|
5D1603FD278A40DB00D22B99 /* SubtitleSize.swift in Sources */,
|
||||||
E13F05F228BC9016003499D2 /* LibraryItemRow.swift in Sources */,
|
|
||||||
62E1DCC4273CE19800C9AE76 /* URLExtensions.swift in Sources */,
|
62E1DCC4273CE19800C9AE76 /* URLExtensions.swift in Sources */,
|
||||||
E10EAA54277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */,
|
E10EAA54277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */,
|
||||||
091B5A8D268315D400D78B61 /* ServerDiscovery.swift in Sources */,
|
091B5A8D268315D400D78B61 /* ServerDiscovery.swift in Sources */,
|
||||||
E1CCF12F28ABF989006CAC9E /* PosterType.swift in Sources */,
|
E1CCF12F28ABF989006CAC9E /* PosterType.swift in Sources */,
|
||||||
E18E021F2887492B0022598C /* InitialFailureView.swift in Sources */,
|
E18E021F2887492B0022598C /* InitialFailureView.swift in Sources */,
|
||||||
E1D4BF882719D27100A11E64 /* Bitrates.swift in Sources */,
|
E1D4BF882719D27100A11E64 /* Bitrates.swift in Sources */,
|
||||||
|
E1D3043D28D18CD400587289 /* CastAndCrewLibraryCoordinator.swift in Sources */,
|
||||||
E193D5432719407E00900D82 /* tvOSMainCoordinator.swift in Sources */,
|
E193D5432719407E00900D82 /* tvOSMainCoordinator.swift in Sources */,
|
||||||
E11CEB8928998549003E74C7 /* BottomEdgeGradientModifier.swift in Sources */,
|
E11CEB8928998549003E74C7 /* BottomEdgeGradientModifier.swift in Sources */,
|
||||||
53ABFDE7267974EF00886593 /* ConnectToServerViewModel.swift in Sources */,
|
53ABFDE7267974EF00886593 /* ConnectToServerViewModel.swift in Sources */,
|
||||||
|
@ -2561,21 +2616,24 @@
|
||||||
E184C161288C5C08000B25BA /* RequestBuilderExtensions.swift in Sources */,
|
E184C161288C5C08000B25BA /* RequestBuilderExtensions.swift in Sources */,
|
||||||
E19169CF272514760085832A /* HTTPScheme.swift in Sources */,
|
E19169CF272514760085832A /* HTTPScheme.swift in Sources */,
|
||||||
E148128628C15475003B8787 /* APISortOrderExtensions.swift in Sources */,
|
E148128628C15475003B8787 /* APISortOrderExtensions.swift in Sources */,
|
||||||
E1E1644528BC60C600323B0A /* LibraryItem.swift in Sources */,
|
E1E1644528BC60C600323B0A /* MediaLibraryItem.swift in Sources */,
|
||||||
E13849452780370B0024FB48 /* PlaybackSpeed.swift in Sources */,
|
E13849452780370B0024FB48 /* PlaybackSpeed.swift in Sources */,
|
||||||
E1C812CD277AE40A00918266 /* VideoPlayerViewModel.swift in Sources */,
|
E1C812CD277AE40A00918266 /* VideoPlayerViewModel.swift in Sources */,
|
||||||
C45B29BB26FAC5B600CEF5E0 /* ColorExtension.swift in Sources */,
|
C45B29BB26FAC5B600CEF5E0 /* ColorExtensions.swift in Sources */,
|
||||||
E1D4BF822719D22800A11E64 /* AppAppearance.swift in Sources */,
|
E1D4BF822719D22800A11E64 /* AppAppearance.swift in Sources */,
|
||||||
E1C926132887565C002A7A66 /* SeriesEpisodesView.swift in Sources */,
|
E1C926132887565C002A7A66 /* SeriesEpisodesView.swift in Sources */,
|
||||||
C40CD923271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift in Sources */,
|
E12CC1CD28D135C700678D5D /* NextUpView.swift in Sources */,
|
||||||
E13AD7302798C60F00FDCEE8 /* NativePlayerViewController.swift in Sources */,
|
E13AD7302798C60F00FDCEE8 /* NativePlayerViewController.swift in Sources */,
|
||||||
E18E02232887492B0022598C /* ImageView.swift in Sources */,
|
E18E02232887492B0022598C /* ImageView.swift in Sources */,
|
||||||
E1E5D54F2783E67100692DFE /* OverlaySettingsView.swift in Sources */,
|
E1E5D54F2783E67100692DFE /* OverlaySettingsView.swift in Sources */,
|
||||||
09389CC526814E4500AE350E /* DeviceProfileBuilder.swift in Sources */,
|
09389CC526814E4500AE350E /* DeviceProfileBuilder.swift in Sources */,
|
||||||
|
E12CC1AF28D0FAEA00678D5D /* NextUpLibraryViewModel.swift in Sources */,
|
||||||
E1C812D2277AE50A00918266 /* URLComponentsExtensions.swift in Sources */,
|
E1C812D2277AE50A00918266 /* URLComponentsExtensions.swift in Sources */,
|
||||||
|
E12CC1B628D1124400678D5D /* BasicLibraryCoordinator.swift in Sources */,
|
||||||
E1E00A36278628A40022235B /* DoubleExtensions.swift in Sources */,
|
E1E00A36278628A40022235B /* DoubleExtensions.swift in Sources */,
|
||||||
E1E1643A28BAC2EF00323B0A /* SearchView.swift in Sources */,
|
E1E1643A28BAC2EF00323B0A /* SearchView.swift in Sources */,
|
||||||
E1FA2F7427818A8800B4C270 /* SmallMenuOverlay.swift in Sources */,
|
E1FA2F7427818A8800B4C270 /* SmallMenuOverlay.swift in Sources */,
|
||||||
|
E12CC1C728D12FD600678D5D /* CinematicRecentlyAddedView.swift in Sources */,
|
||||||
E1E1644228BB301900323B0A /* ArrayExtensions.swift in Sources */,
|
E1E1644228BB301900323B0A /* ArrayExtensions.swift in Sources */,
|
||||||
E193D53C27193F9500900D82 /* UserListCoordinator.swift in Sources */,
|
E193D53C27193F9500900D82 /* UserListCoordinator.swift in Sources */,
|
||||||
E1CEFBF727914E6400F60429 /* CustomizeViewsSettings.swift in Sources */,
|
E1CEFBF727914E6400F60429 /* CustomizeViewsSettings.swift in Sources */,
|
||||||
|
@ -2614,10 +2672,8 @@
|
||||||
5364F456266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */,
|
5364F456266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */,
|
||||||
E1937A62288F32DB00CB80AA /* Poster.swift in Sources */,
|
E1937A62288F32DB00CB80AA /* Poster.swift in Sources */,
|
||||||
E1A42E5128CBE44500A14DCB /* LandscapePosterProgressBar.swift in Sources */,
|
E1A42E5128CBE44500A14DCB /* LandscapePosterProgressBar.swift in Sources */,
|
||||||
C4BE0764271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift in Sources */,
|
|
||||||
E1E5D5512783E67700692DFE /* ExperimentalSettingsView.swift in Sources */,
|
E1E5D5512783E67700692DFE /* ExperimentalSettingsView.swift in Sources */,
|
||||||
E1A2C160279A7DCA005EC829 /* AboutAppView.swift in Sources */,
|
E1A2C160279A7DCA005EC829 /* AboutAppView.swift in Sources */,
|
||||||
C4BE076A271FC164003F4AD1 /* TVLibrariesView.swift in Sources */,
|
|
||||||
E1FE69A828C29B720021BC93 /* ProgressBar.swift in Sources */,
|
E1FE69A828C29B720021BC93 /* ProgressBar.swift in Sources */,
|
||||||
E1A16C9D2889AF1E00EA4679 /* AboutView.swift in Sources */,
|
E1A16C9D2889AF1E00EA4679 /* AboutView.swift in Sources */,
|
||||||
E11CEB8E28999B4A003E74C7 /* FontExtensions.swift in Sources */,
|
E11CEB8E28999B4A003E74C7 /* FontExtensions.swift in Sources */,
|
||||||
|
@ -2656,6 +2712,7 @@
|
||||||
E148128828C154BF003B8787 /* ItemFilterExtensions.swift in Sources */,
|
E148128828C154BF003B8787 /* ItemFilterExtensions.swift in Sources */,
|
||||||
C400DB6D27FE8E65007B65FE /* LiveTVChannelItemWideElement.swift in Sources */,
|
C400DB6D27FE8E65007B65FE /* LiveTVChannelItemWideElement.swift in Sources */,
|
||||||
62C29E9F26D1016600C1D2E7 /* iOSMainCoordinator.swift in Sources */,
|
62C29E9F26D1016600C1D2E7 /* iOSMainCoordinator.swift in Sources */,
|
||||||
|
E12CC1B128D1008F00678D5D /* NextUpView.swift in Sources */,
|
||||||
E11895AF2893840F0042947B /* NavBarOffsetView.swift in Sources */,
|
E11895AF2893840F0042947B /* NavBarOffsetView.swift in Sources */,
|
||||||
E168BD11289A4162001A6922 /* HomeContentView.swift in Sources */,
|
E168BD11289A4162001A6922 /* HomeContentView.swift in Sources */,
|
||||||
E18E01AA288746AF0022598C /* RefreshableScrollView.swift in Sources */,
|
E18E01AA288746AF0022598C /* RefreshableScrollView.swift in Sources */,
|
||||||
|
@ -2682,8 +2739,11 @@
|
||||||
6220D0AD26D5EABB00B8E046 /* ViewExtensions.swift in Sources */,
|
6220D0AD26D5EABB00B8E046 /* ViewExtensions.swift in Sources */,
|
||||||
E19E551F2897326C003CE330 /* BottomEdgeGradientModifier.swift in Sources */,
|
E19E551F2897326C003CE330 /* BottomEdgeGradientModifier.swift in Sources */,
|
||||||
E13DD3EC27178A54009D4DAF /* UserSignInViewModel.swift in Sources */,
|
E13DD3EC27178A54009D4DAF /* UserSignInViewModel.swift in Sources */,
|
||||||
|
E12CC1BE28D11F4500678D5D /* RecentlyAddedView.swift in Sources */,
|
||||||
625CB5772678C34300530A6E /* ConnectToServerViewModel.swift in Sources */,
|
625CB5772678C34300530A6E /* ConnectToServerViewModel.swift in Sources */,
|
||||||
C4BE07852728446F003F4AD1 /* LiveTVChannelsViewModel.swift in Sources */,
|
C4BE07852728446F003F4AD1 /* LiveTVChannelsViewModel.swift in Sources */,
|
||||||
|
E1D3043228D175CE00587289 /* StaticLibraryViewModel.swift in Sources */,
|
||||||
|
E111D8F828D03BF900400001 /* PagingLibraryView.swift in Sources */,
|
||||||
E1FA891B289A302300176FEB /* iPadOSCollectionItemView.swift in Sources */,
|
E1FA891B289A302300176FEB /* iPadOSCollectionItemView.swift in Sources */,
|
||||||
5D160403278A41FD00D22B99 /* VLCPlayer+subtitles.swift in Sources */,
|
5D160403278A41FD00D22B99 /* VLCPlayer+subtitles.swift in Sources */,
|
||||||
E11895B32893844A0042947B /* BackgroundParallaxHeaderModifier.swift in Sources */,
|
E11895B32893844A0042947B /* BackgroundParallaxHeaderModifier.swift in Sources */,
|
||||||
|
@ -2691,6 +2751,7 @@
|
||||||
E1A2C158279A7D76005EC829 /* BundleExtensions.swift in Sources */,
|
E1A2C158279A7D76005EC829 /* BundleExtensions.swift in Sources */,
|
||||||
C45942C627F695FB00C54FE7 /* LiveTVProgramsCoordinator.swift in Sources */,
|
C45942C627F695FB00C54FE7 /* LiveTVProgramsCoordinator.swift in Sources */,
|
||||||
E1FCD08826C35A0D007C8DCF /* NetworkError.swift in Sources */,
|
E1FCD08826C35A0D007C8DCF /* NetworkError.swift in Sources */,
|
||||||
|
E12CC1BB28D11E1000678D5D /* RecentlyAddedViewModel.swift in Sources */,
|
||||||
E17FB55228C119D400311DFE /* Displayable.swift in Sources */,
|
E17FB55228C119D400311DFE /* Displayable.swift in Sources */,
|
||||||
E13AD72E2798BC8D00FDCEE8 /* NativePlayerViewController.swift in Sources */,
|
E13AD72E2798BC8D00FDCEE8 /* NativePlayerViewController.swift in Sources */,
|
||||||
E13DD3E527177D15009D4DAF /* ServerListView.swift in Sources */,
|
E13DD3E527177D15009D4DAF /* ServerListView.swift in Sources */,
|
||||||
|
@ -2704,7 +2765,6 @@
|
||||||
62133890265F83A900A81A2A /* MediaView.swift in Sources */,
|
62133890265F83A900A81A2A /* MediaView.swift in Sources */,
|
||||||
62C29EA326D1030F00C1D2E7 /* ConnectToServerCoodinator.swift in Sources */,
|
62C29EA326D1030F00C1D2E7 /* ConnectToServerCoodinator.swift in Sources */,
|
||||||
E18E01E1288747230022598C /* EpisodeItemContentView.swift in Sources */,
|
E18E01E1288747230022598C /* EpisodeItemContentView.swift in Sources */,
|
||||||
62E632DA267D2BC40063E547 /* LatestMediaViewModel.swift in Sources */,
|
|
||||||
C400DB6A27FE894F007B65FE /* LiveTVChannelsView.swift in Sources */,
|
C400DB6A27FE894F007B65FE /* LiveTVChannelsView.swift in Sources */,
|
||||||
E18E01F2288747230022598C /* ActionButtonHStack.swift in Sources */,
|
E18E01F2288747230022598C /* ActionButtonHStack.swift in Sources */,
|
||||||
E18E0204288749200022598C /* Divider.swift in Sources */,
|
E18E0204288749200022598C /* Divider.swift in Sources */,
|
||||||
|
@ -2715,7 +2775,8 @@
|
||||||
E168BD14289A4162001A6922 /* LatestInLibraryView.swift in Sources */,
|
E168BD14289A4162001A6922 /* LatestInLibraryView.swift in Sources */,
|
||||||
E1E5D5492783CDD700692DFE /* OverlaySettingsView.swift in Sources */,
|
E1E5D5492783CDD700692DFE /* OverlaySettingsView.swift in Sources */,
|
||||||
E18E01EF288747230022598C /* ListDetailsView.swift in Sources */,
|
E18E01EF288747230022598C /* ListDetailsView.swift in Sources */,
|
||||||
E173DA5226D04AAF00CC4EB7 /* ColorExtension.swift in Sources */,
|
E173DA5226D04AAF00CC4EB7 /* ColorExtensions.swift in Sources */,
|
||||||
|
E12CC1B928D11A1D00678D5D /* BasicLibraryView.swift in Sources */,
|
||||||
E1399474289B1EA900401ABC /* Defaults+Workaround.swift in Sources */,
|
E1399474289B1EA900401ABC /* Defaults+Workaround.swift in Sources */,
|
||||||
C45942D027F69C2400C54FE7 /* LiveTVChannelsCoordinator.swift in Sources */,
|
C45942D027F69C2400C54FE7 /* LiveTVChannelsCoordinator.swift in Sources */,
|
||||||
E118959D289312020042947B /* BaseItemPerson+Poster.swift in Sources */,
|
E118959D289312020042947B /* BaseItemPerson+Poster.swift in Sources */,
|
||||||
|
@ -2739,20 +2800,24 @@
|
||||||
E13F05F128BC9016003499D2 /* LibraryItemRow.swift in Sources */,
|
E13F05F128BC9016003499D2 /* LibraryItemRow.swift in Sources */,
|
||||||
E18E0205288749200022598C /* AppIcon.swift in Sources */,
|
E18E0205288749200022598C /* AppIcon.swift in Sources */,
|
||||||
E168BD10289A4162001A6922 /* HomeView.swift in Sources */,
|
E168BD10289A4162001A6922 /* HomeView.swift in Sources */,
|
||||||
|
E1D3043C28D18CD400587289 /* CastAndCrewLibraryCoordinator.swift in Sources */,
|
||||||
E18E01AB288746AF0022598C /* PillHStack.swift in Sources */,
|
E18E01AB288746AF0022598C /* PillHStack.swift in Sources */,
|
||||||
E18E01E4288747230022598C /* CompactLogoScrollView.swift in Sources */,
|
E18E01E4288747230022598C /* CompactLogoScrollView.swift in Sources */,
|
||||||
E18E0207288749200022598C /* AttributeOutlineView.swift in Sources */,
|
E18E0207288749200022598C /* AttributeOutlineView.swift in Sources */,
|
||||||
E1002B642793CEE800E47059 /* ChapterInfoExtensions.swift in Sources */,
|
E1002B642793CEE800E47059 /* ChapterInfoExtensions.swift in Sources */,
|
||||||
|
E1D3043A28D189C500587289 /* CastAndCrewLibraryView.swift in Sources */,
|
||||||
E18E01AD288746AF0022598C /* DotHStack.swift in Sources */,
|
E18E01AD288746AF0022598C /* DotHStack.swift in Sources */,
|
||||||
E1937A61288F32DB00CB80AA /* Poster.swift in Sources */,
|
E1937A61288F32DB00CB80AA /* Poster.swift in Sources */,
|
||||||
6267B3D626710B8900A7371D /* CollectionExtensions.swift in Sources */,
|
6267B3D626710B8900A7371D /* CollectionExtensions.swift in Sources */,
|
||||||
E168BD15289A4162001A6922 /* HomeErrorView.swift in Sources */,
|
E168BD15289A4162001A6922 /* HomeErrorView.swift in Sources */,
|
||||||
E13DD3F5271793BB009D4DAF /* UserSignInView.swift in Sources */,
|
E13DD3F5271793BB009D4DAF /* UserSignInView.swift in Sources */,
|
||||||
|
E1D3044428D1991900587289 /* LibraryViewTypeToggle.swift in Sources */,
|
||||||
E148128B28C15526003B8787 /* SortBy.swift in Sources */,
|
E148128B28C15526003B8787 /* SortBy.swift in Sources */,
|
||||||
5D1603FC278A3D5800D22B99 /* SubtitleSize.swift in Sources */,
|
5D1603FC278A3D5800D22B99 /* SubtitleSize.swift in Sources */,
|
||||||
E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */,
|
E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */,
|
||||||
E1FA891E289A305D00176FEB /* iPadOSCollectionItemContentView.swift in Sources */,
|
E1FA891E289A305D00176FEB /* iPadOSCollectionItemContentView.swift in Sources */,
|
||||||
E18CE0B928A2322D0092E7F1 /* QuickConnectCoordinator.swift in Sources */,
|
E18CE0B928A2322D0092E7F1 /* QuickConnectCoordinator.swift in Sources */,
|
||||||
|
E12CC1AE28D0FAEA00678D5D /* NextUpLibraryViewModel.swift in Sources */,
|
||||||
6334175D287DE0D0000603CE /* QuickConnectSettingsViewModel.swift in Sources */,
|
6334175D287DE0D0000603CE /* QuickConnectSettingsViewModel.swift in Sources */,
|
||||||
E18E01E0288747230022598C /* iPadOSMovieItemContentView.swift in Sources */,
|
E18E01E0288747230022598C /* iPadOSMovieItemContentView.swift in Sources */,
|
||||||
53649AB1269CFB1900A2D8B7 /* LogManager.swift in Sources */,
|
53649AB1269CFB1900A2D8B7 /* LogManager.swift in Sources */,
|
||||||
|
@ -2793,7 +2858,7 @@
|
||||||
E18E01EE288747230022598C /* AboutView.swift in Sources */,
|
E18E01EE288747230022598C /* AboutView.swift in Sources */,
|
||||||
62E632E0267D30CA0063E547 /* LibraryViewModel.swift in Sources */,
|
62E632E0267D30CA0063E547 /* LibraryViewModel.swift in Sources */,
|
||||||
E11CEB8B28998552003E74C7 /* iOSViewExtensions.swift in Sources */,
|
E11CEB8B28998552003E74C7 /* iOSViewExtensions.swift in Sources */,
|
||||||
E1E1644428BC60C600323B0A /* LibraryItem.swift in Sources */,
|
E1E1644428BC60C600323B0A /* MediaLibraryItem.swift in Sources */,
|
||||||
E18E0206288749200022598C /* AttributeFillView.swift in Sources */,
|
E18E0206288749200022598C /* AttributeFillView.swift in Sources */,
|
||||||
E1AA331F2782639D00F6439C /* OverlayType.swift in Sources */,
|
E1AA331F2782639D00F6439C /* OverlayType.swift in Sources */,
|
||||||
E1A2C154279A7D5A005EC829 /* UIApplicationExtensions.swift in Sources */,
|
E1A2C154279A7D5A005EC829 /* UIApplicationExtensions.swift in Sources */,
|
||||||
|
@ -2821,6 +2886,7 @@
|
||||||
E13DD3C827164B1E009D4DAF /* UIDeviceExtensions.swift in Sources */,
|
E13DD3C827164B1E009D4DAF /* UIDeviceExtensions.swift in Sources */,
|
||||||
C45640D0281A43EF007096DE /* LiveTVNativePlayerViewController.swift in Sources */,
|
C45640D0281A43EF007096DE /* LiveTVNativePlayerViewController.swift in Sources */,
|
||||||
E10EAA53277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */,
|
E10EAA53277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */,
|
||||||
|
E1D3044228D1976600587289 /* CastAndCrewItemRow.swift in Sources */,
|
||||||
E1AD104D26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */,
|
E1AD104D26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */,
|
||||||
E13DD3BF27163DD7009D4DAF /* AppDelegate.swift in Sources */,
|
E13DD3BF27163DD7009D4DAF /* AppDelegate.swift in Sources */,
|
||||||
535870AD2669D8DD00D05A09 /* ItemFilters.swift in Sources */,
|
535870AD2669D8DD00D05A09 /* ItemFilters.swift in Sources */,
|
||||||
|
@ -2832,17 +2898,21 @@
|
||||||
E18E01E6288747230022598C /* CollectionItemView.swift in Sources */,
|
E18E01E6288747230022598C /* CollectionItemView.swift in Sources */,
|
||||||
6220D0BA26D6092100B8E046 /* FilterCoordinator.swift in Sources */,
|
6220D0BA26D6092100B8E046 /* FilterCoordinator.swift in Sources */,
|
||||||
E1E5D54C2783E27200692DFE /* ExperimentalSettingsView.swift in Sources */,
|
E1E5D54C2783E27200692DFE /* ExperimentalSettingsView.swift in Sources */,
|
||||||
|
E111D8F528D03B7500400001 /* PagingLibraryViewModel.swift in Sources */,
|
||||||
E1E00A35278628A40022235B /* DoubleExtensions.swift in Sources */,
|
E1E00A35278628A40022235B /* DoubleExtensions.swift in Sources */,
|
||||||
E1D4BF8A2719D3D000A11E64 /* BasicAppSettingsCoordinator.swift in Sources */,
|
E1D4BF8A2719D3D000A11E64 /* BasicAppSettingsCoordinator.swift in Sources */,
|
||||||
E13DD3F92717E961009D4DAF /* UserListViewModel.swift in Sources */,
|
E13DD3F92717E961009D4DAF /* UserListViewModel.swift in Sources */,
|
||||||
E126F741278A656C00A522BF /* ServerStreamType.swift in Sources */,
|
E126F741278A656C00A522BF /* ServerStreamType.swift in Sources */,
|
||||||
|
E12CC1BF28D1260600678D5D /* ItemTypeLibraryViewModel.swift in Sources */,
|
||||||
539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */,
|
539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */,
|
||||||
5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */,
|
5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */,
|
||||||
E18E01EA288747230022598C /* MovieItemView.swift in Sources */,
|
E18E01EA288747230022598C /* MovieItemView.swift in Sources */,
|
||||||
|
E12CC1B528D1124400678D5D /* BasicLibraryCoordinator.swift in Sources */,
|
||||||
09389CC726819B4600AE350E /* VideoPlayerModel.swift in Sources */,
|
09389CC726819B4600AE350E /* VideoPlayerModel.swift in Sources */,
|
||||||
E1D4BF872719D27100A11E64 /* Bitrates.swift in Sources */,
|
E1D4BF872719D27100A11E64 /* Bitrates.swift in Sources */,
|
||||||
6220D0B726D5EE1100B8E046 /* SearchCoordinator.swift in Sources */,
|
6220D0B726D5EE1100B8E046 /* SearchCoordinator.swift in Sources */,
|
||||||
E148128528C15472003B8787 /* APISortOrderExtensions.swift in Sources */,
|
E148128528C15472003B8787 /* APISortOrderExtensions.swift in Sources */,
|
||||||
|
E1D3043528D1763100587289 /* SeeAllButton.swift in Sources */,
|
||||||
E13F05F328BC9016003499D2 /* LibraryView.swift in Sources */,
|
E13F05F328BC9016003499D2 /* LibraryView.swift in Sources */,
|
||||||
E13DD3EF27178F87009D4DAF /* SwiftfinNotificationCenter.swift in Sources */,
|
E13DD3EF27178F87009D4DAF /* SwiftfinNotificationCenter.swift in Sources */,
|
||||||
E13F05EC28BC9000003499D2 /* LibraryViewType.swift in Sources */,
|
E13F05EC28BC9000003499D2 /* LibraryViewType.swift in Sources */,
|
||||||
|
|
|
@ -34,7 +34,9 @@ struct LibraryItemRow: View {
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
|
||||||
DotHStack {
|
DotHStack {
|
||||||
if let premiereYear = item.premiereDateYear {
|
if item.type == .episode, let seasonEpisodeLocator = item.seasonEpisodeLocator {
|
||||||
|
Text(seasonEpisodeLocator)
|
||||||
|
} else if let premiereYear = item.premiereDateYear {
|
||||||
Text(premiereYear)
|
Text(premiereYear)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import Defaults
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct LibraryViewTypeToggle: View {
|
||||||
|
|
||||||
|
@Binding
|
||||||
|
var libraryViewType: LibraryViewType
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button {
|
||||||
|
switch libraryViewType {
|
||||||
|
case .grid:
|
||||||
|
libraryViewType = .list
|
||||||
|
case .list:
|
||||||
|
libraryViewType = .grid
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
switch libraryViewType {
|
||||||
|
case .grid:
|
||||||
|
Image(systemName: "list.dash")
|
||||||
|
case .list:
|
||||||
|
Image(systemName: "square.grid.2x2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import CollectionView
|
||||||
|
import Defaults
|
||||||
|
import JellyfinAPI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct PagingLibraryView: View {
|
||||||
|
|
||||||
|
@ObservedObject
|
||||||
|
var viewModel: PagingLibraryViewModel
|
||||||
|
private var onSelect: (BaseItemDto) -> Void
|
||||||
|
|
||||||
|
@Default(.Customization.Library.gridPosterType)
|
||||||
|
private var libraryGridPosterType
|
||||||
|
@Default(.Customization.Library.viewType)
|
||||||
|
private var libraryViewType
|
||||||
|
|
||||||
|
private var gridLayout: NSCollectionLayoutSection.GridLayoutMode {
|
||||||
|
if libraryGridPosterType == .landscape && UIDevice.isPhone {
|
||||||
|
return .fixedNumberOfColumns(2)
|
||||||
|
} else {
|
||||||
|
return .adaptive(withMinItemSize: libraryGridPosterType.width + (UIDevice.isIPad ? 10 : 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var libraryListView: some View {
|
||||||
|
CollectionView(items: viewModel.items) { _, item, _ in
|
||||||
|
LibraryItemRow(item: item)
|
||||||
|
.onSelect {
|
||||||
|
onSelect(item)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.layout { _, layoutEnvironment in
|
||||||
|
.list(using: .init(appearance: .plain), layoutEnvironment: layoutEnvironment)
|
||||||
|
}
|
||||||
|
.willReachEdge(insets: .init(top: 0, leading: 0, bottom: 200, trailing: 0)) { edge in
|
||||||
|
if !viewModel.isLoading && edge == .bottom {
|
||||||
|
viewModel.requestNextPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onEdgeReached { edge in
|
||||||
|
if viewModel.hasNextPage, !viewModel.isLoading, edge == .bottom {
|
||||||
|
viewModel.requestNextPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.configure { configuration in
|
||||||
|
configuration.showsVerticalScrollIndicator = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var libraryGridView: some View {
|
||||||
|
CollectionView(items: viewModel.items) { _, item, _ in
|
||||||
|
PosterButton(item: item, type: libraryGridPosterType)
|
||||||
|
.scaleItem(libraryGridPosterType == .landscape && UIDevice.isPhone ? 0.85 : 1)
|
||||||
|
.onSelect {
|
||||||
|
onSelect(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.layout { _, layoutEnvironment in
|
||||||
|
.grid(
|
||||||
|
layoutEnvironment: layoutEnvironment,
|
||||||
|
layoutMode: gridLayout,
|
||||||
|
sectionInsets: .init(top: 0, leading: 10, bottom: 0, trailing: 10)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.willReachEdge(insets: .init(top: 0, leading: 0, bottom: 200, trailing: 0)) { edge in
|
||||||
|
if !viewModel.isLoading && edge == .bottom {
|
||||||
|
viewModel.requestNextPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onEdgeReached { edge in
|
||||||
|
if viewModel.hasNextPage, !viewModel.isLoading, edge == .bottom {
|
||||||
|
viewModel.requestNextPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.configure { configuration in
|
||||||
|
configuration.showsVerticalScrollIndicator = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
switch libraryViewType {
|
||||||
|
case .grid:
|
||||||
|
libraryGridView
|
||||||
|
case .list:
|
||||||
|
libraryListView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PagingLibraryView {
|
||||||
|
init(viewModel: PagingLibraryViewModel) {
|
||||||
|
self.viewModel = viewModel
|
||||||
|
self.onSelect = { _ in }
|
||||||
|
}
|
||||||
|
|
||||||
|
func onSelect(_ action: @escaping (BaseItemDto) -> Void) -> Self {
|
||||||
|
var copy = self
|
||||||
|
copy.onSelect = action
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
}
|
|
@ -65,6 +65,7 @@ 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)
|
||||||
|
.content { content(item) }
|
||||||
.imageOverlay { imageOverlay(item) }
|
.imageOverlay { imageOverlay(item) }
|
||||||
.contextMenu { contextMenu(item) }
|
.contextMenu { contextMenu(item) }
|
||||||
.onSelect { onSelect(item) }
|
.onSelect { onSelect(item) }
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SeeAllButton: View {
|
||||||
|
|
||||||
|
private var onSelect: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button {
|
||||||
|
onSelect()
|
||||||
|
} label: {
|
||||||
|
HStack {
|
||||||
|
L10n.seeAll.text
|
||||||
|
Image(systemName: "chevron.right")
|
||||||
|
}
|
||||||
|
.font(.subheadline.bold())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SeeAllButton {
|
||||||
|
init() {
|
||||||
|
self.onSelect = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
func onSelect(_ action: @escaping () -> Void) -> Self {
|
||||||
|
var copy = self
|
||||||
|
copy.onSelect = action
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import CollectionView
|
||||||
|
import Defaults
|
||||||
|
import JellyfinAPI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct BasicLibraryView: View {
|
||||||
|
|
||||||
|
@Default(.Customization.Library.viewType)
|
||||||
|
private var libraryViewType
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: BasicLibraryCoordinator.Router
|
||||||
|
@ObservedObject
|
||||||
|
var viewModel: PagingLibraryViewModel
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var loadingView: some View {
|
||||||
|
ProgressView()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var noResultsView: some View {
|
||||||
|
L10n.noResults.text
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var libraryItemsView: some View {
|
||||||
|
PagingLibraryView(viewModel: viewModel)
|
||||||
|
.onSelect { item in
|
||||||
|
router.route(to: \.item, item)
|
||||||
|
}
|
||||||
|
.ignoresSafeArea()
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
if viewModel.isLoading && viewModel.items.isEmpty {
|
||||||
|
loadingView
|
||||||
|
} else if viewModel.items.isEmpty {
|
||||||
|
noResultsView
|
||||||
|
} else {
|
||||||
|
libraryItemsView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||||
|
|
||||||
|
if viewModel.isLoading && !viewModel.items.isEmpty {
|
||||||
|
ProgressView()
|
||||||
|
}
|
||||||
|
|
||||||
|
LibraryViewTypeToggle(libraryViewType: $libraryViewType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import JellyfinAPI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension CastAndCrewLibraryView {
|
||||||
|
|
||||||
|
struct CastAndCrewItemRow: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: CastAndCrewLibraryCoordinator.Router
|
||||||
|
|
||||||
|
let person: BaseItemPerson
|
||||||
|
private var onSelect: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button {
|
||||||
|
onSelect()
|
||||||
|
} label: {
|
||||||
|
HStack(alignment: .bottom) {
|
||||||
|
ImageView(person.portraitPosterImageSource(maxWidth: 60))
|
||||||
|
.posterStyle(type: .portrait, width: 60)
|
||||||
|
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text(person.displayName)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.lineLimit(2)
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
|
||||||
|
if let subtitle = person.subtitle {
|
||||||
|
Text(subtitle)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(Color(UIColor.lightGray))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.vertical)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CastAndCrewLibraryView.CastAndCrewItemRow {
|
||||||
|
init(person: BaseItemPerson) {
|
||||||
|
self.person = person
|
||||||
|
self.onSelect = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
func onSelect(_ action: @escaping () -> Void) -> Self {
|
||||||
|
var copy = self
|
||||||
|
copy.onSelect = action
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import CollectionView
|
||||||
|
import Defaults
|
||||||
|
import JellyfinAPI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct CastAndCrewLibraryView: View {
|
||||||
|
|
||||||
|
@Default(.Customization.Library.viewType)
|
||||||
|
private var libraryViewType
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: CastAndCrewLibraryCoordinator.Router
|
||||||
|
let people: [BaseItemPerson]
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var noResultsView: some View {
|
||||||
|
L10n.noResults.text
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var libraryListView: some View {
|
||||||
|
CollectionView(items: people) { _, person, _ in
|
||||||
|
CastAndCrewItemRow(person: person)
|
||||||
|
.onSelect {
|
||||||
|
router.route(to: \.library, .init(parent: person, type: .person, filters: .init()))
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.layout { _, layoutEnvironment in
|
||||||
|
.list(using: .init(appearance: .plain), layoutEnvironment: layoutEnvironment)
|
||||||
|
}
|
||||||
|
.configure { configuration in
|
||||||
|
configuration.showsVerticalScrollIndicator = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var libraryGridView: some View {
|
||||||
|
CollectionView(items: people) { _, person, _ in
|
||||||
|
PosterButton(item: person, type: .portrait)
|
||||||
|
.onSelect {
|
||||||
|
router.route(to: \.library, .init(parent: person, type: .person, filters: .init()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.layout { _, layoutEnvironment in
|
||||||
|
.grid(
|
||||||
|
layoutEnvironment: layoutEnvironment,
|
||||||
|
layoutMode: .adaptive(withMinItemSize: PosterType.portrait.width + (UIDevice.isIPad ? 10 : 0)),
|
||||||
|
sectionInsets: .init(top: 0, leading: 10, bottom: 0, trailing: 10)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.configure { configuration in
|
||||||
|
configuration.showsVerticalScrollIndicator = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
if people.isEmpty {
|
||||||
|
noResultsView
|
||||||
|
} else {
|
||||||
|
switch libraryViewType {
|
||||||
|
case .grid:
|
||||||
|
libraryGridView
|
||||||
|
case .list:
|
||||||
|
libraryListView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle(L10n.castAndCrew)
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||||
|
LibraryViewTypeToggle(libraryViewType: $libraryViewType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,31 +9,34 @@
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ContinueWatchingView: View {
|
extension HomeView {
|
||||||
|
|
||||||
@EnvironmentObject
|
struct ContinueWatchingView: View {
|
||||||
private var homeRouter: HomeCoordinator.Router
|
|
||||||
@ObservedObject
|
|
||||||
var viewModel: HomeViewModel
|
|
||||||
|
|
||||||
var body: some View {
|
@EnvironmentObject
|
||||||
PosterHStack(title: "", type: .landscape, items: viewModel.resumeItems)
|
private var router: HomeCoordinator.Router
|
||||||
.scaleItems(1.5)
|
@ObservedObject
|
||||||
.onSelect { item in
|
var viewModel: HomeViewModel
|
||||||
homeRouter.route(to: \.item, item)
|
|
||||||
}
|
var body: some View {
|
||||||
.contextMenu { item in
|
PosterHStack(title: "", type: .landscape, items: viewModel.resumeItems)
|
||||||
Button(role: .destructive) {
|
.scaleItems(1.5)
|
||||||
viewModel.removeItemFromResume(item)
|
.onSelect { item in
|
||||||
} label: {
|
router.route(to: \.item, item)
|
||||||
Label(L10n.removeFromResume, systemImage: "minus.circle")
|
|
||||||
}
|
}
|
||||||
}
|
.contextMenu { item in
|
||||||
.imageOverlay { item in
|
Button(role: .destructive) {
|
||||||
LandscapePosterProgressBar(
|
viewModel.removeItemFromResume(item)
|
||||||
title: item.progress ?? L10n.continue,
|
} label: {
|
||||||
progress: (item.userData?.playedPercentage ?? 0) / 100
|
Label(L10n.removeFromResume, systemImage: "minus.circle")
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
.imageOverlay { item in
|
||||||
|
LandscapePosterProgressBar(
|
||||||
|
title: item.progress ?? L10n.continue,
|
||||||
|
progress: (item.userData?.playedPercentage ?? 0) / 100
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,34 +7,35 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Defaults
|
import Defaults
|
||||||
import JellyfinAPI
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct LatestInLibraryView: View {
|
extension HomeView {
|
||||||
|
|
||||||
@EnvironmentObject
|
struct LatestInLibraryView: View {
|
||||||
private var homeRouter: HomeCoordinator.Router
|
|
||||||
@ObservedObject
|
|
||||||
var viewModel: LatestMediaViewModel
|
|
||||||
|
|
||||||
@Default(.Customization.latestInLibraryPosterType)
|
@EnvironmentObject
|
||||||
var latestInLibraryPosterType
|
private var router: HomeCoordinator.Router
|
||||||
|
@ObservedObject
|
||||||
|
var viewModel: LibraryViewModel
|
||||||
|
|
||||||
var body: some View {
|
@Default(.Customization.latestInLibraryPosterType)
|
||||||
PosterHStack(title: L10n.latestWithString(viewModel.library.displayName), type: latestInLibraryPosterType, items: viewModel.items)
|
var latestInLibraryPosterType
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
PosterHStack(
|
||||||
|
title: L10n.latestWithString(viewModel.parent?.displayName ?? .emptyDash),
|
||||||
|
type: latestInLibraryPosterType,
|
||||||
|
items: viewModel.items.prefix(20).asArray
|
||||||
|
)
|
||||||
.trailing {
|
.trailing {
|
||||||
Button {
|
SeeAllButton()
|
||||||
homeRouter.route(to: \.library, .init(parent: viewModel.library, type: .library, filters: .recent))
|
.onSelect {
|
||||||
} label: {
|
router.route(to: \.library, viewModel.libraryCoordinatorParameters)
|
||||||
HStack {
|
|
||||||
L10n.seeAll.text
|
|
||||||
Image(systemName: "chevron.right")
|
|
||||||
}
|
}
|
||||||
.font(.subheadline.bold())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.onSelect { item in
|
.onSelect { item in
|
||||||
homeRouter.route(to: \.item, item)
|
router.route(to: \.item, item)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import Defaults
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension HomeView {
|
||||||
|
|
||||||
|
struct NextUpView: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: HomeCoordinator.Router
|
||||||
|
@ObservedObject
|
||||||
|
var viewModel: NextUpLibraryViewModel
|
||||||
|
|
||||||
|
@Default(.Customization.nextUpPosterType)
|
||||||
|
private var nextUpPosterType
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
PosterHStack(
|
||||||
|
title: L10n.nextUp,
|
||||||
|
type: nextUpPosterType,
|
||||||
|
items: viewModel.items.prefix(20).asArray
|
||||||
|
)
|
||||||
|
.trailing {
|
||||||
|
SeeAllButton()
|
||||||
|
.onSelect {
|
||||||
|
router.route(to: \.basicLibrary, .init(title: L10n.nextUp, viewModel: viewModel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onSelect { item in
|
||||||
|
router.route(to: \.item, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import Defaults
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension HomeView {
|
||||||
|
|
||||||
|
struct RecentlyAddedView: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: HomeCoordinator.Router
|
||||||
|
@ObservedObject
|
||||||
|
var viewModel: ItemTypeLibraryViewModel
|
||||||
|
|
||||||
|
@Default(.Customization.recentlyAddedPosterType)
|
||||||
|
private var recentlyAddedPosterType
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
PosterHStack(
|
||||||
|
title: L10n.recentlyAdded,
|
||||||
|
type: recentlyAddedPosterType,
|
||||||
|
items: viewModel.items.prefix(20).asArray
|
||||||
|
)
|
||||||
|
.trailing {
|
||||||
|
SeeAllButton()
|
||||||
|
.onSelect {
|
||||||
|
router.route(to: \.basicLibrary, .init(title: L10n.recentlyAdded, viewModel: viewModel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onSelect { item in
|
||||||
|
router.route(to: \.item, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import CollectionView
|
import CollectionView
|
||||||
import Defaults
|
import Defaults
|
||||||
|
import JellyfinAPI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension HomeView {
|
extension HomeView {
|
||||||
|
@ -31,22 +32,21 @@ extension HomeView {
|
||||||
ContinueWatchingView(viewModel: viewModel)
|
ContinueWatchingView(viewModel: viewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !viewModel.nextUpItems.isEmpty {
|
if viewModel.hasNextUp {
|
||||||
PosterHStack(title: L10n.nextUp, type: nextUpPosterType, items: viewModel.nextUpItems)
|
NextUpView(viewModel: .init())
|
||||||
.onSelect { item in
|
|
||||||
homeRouter.route(to: \.item, item)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !viewModel.latestAddedItems.isEmpty {
|
if viewModel.hasRecentlyAdded {
|
||||||
PosterHStack(title: L10n.recentlyAdded, type: recentlyAddedPosterType, items: viewModel.latestAddedItems)
|
RecentlyAddedView(
|
||||||
.onSelect { item in
|
viewModel: .init(
|
||||||
homeRouter.route(to: \.item, item)
|
itemTypes: [.movie, .series],
|
||||||
}
|
filters: .init(sortOrder: [APISortOrder.descending.filter], sortBy: [SortBy.dateAdded.filter])
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ForEach(viewModel.libraries, id: \.self) { library in
|
ForEach(viewModel.libraries, id: \.self) { library in
|
||||||
LatestInLibraryView(viewModel: .init(library: library))
|
LatestInLibraryView(viewModel: .init(parent: library, type: .library, filters: .recent))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.bottom, 50)
|
.padding(.bottom, 50)
|
||||||
|
|
|
@ -12,7 +12,7 @@ import SwiftUI
|
||||||
struct HomeView: View {
|
struct HomeView: View {
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var homeRouter: HomeCoordinator.Router
|
private var router: HomeCoordinator.Router
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var viewModel: HomeViewModel
|
var viewModel: HomeViewModel
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ struct HomeView: View {
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||||
Button {
|
Button {
|
||||||
homeRouter.route(to: \.settings)
|
router.route(to: \.settings)
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "gearshape.fill")
|
Image(systemName: "gearshape.fill")
|
||||||
.accessibilityLabel(L10n.settings)
|
.accessibilityLabel(L10n.settings)
|
||||||
|
|
|
@ -36,7 +36,6 @@ extension ItemView {
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Image(systemName: "checkmark.circle")
|
Image(systemName: "checkmark.circle")
|
||||||
// .foregroundStyle(.white)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
|
@ -54,7 +53,6 @@ extension ItemView {
|
||||||
.foregroundStyle(Color.red)
|
.foregroundStyle(Color.red)
|
||||||
} else {
|
} else {
|
||||||
Image(systemName: "heart")
|
Image(systemName: "heart")
|
||||||
// .foregroundStyle(.white)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import JellyfinAPI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension ItemView {
|
extension ItemView {
|
||||||
|
|
||||||
struct CastAndCrewHStack: View {
|
struct CastAndCrewHStack: View {
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
|
@ -20,8 +21,14 @@ extension ItemView {
|
||||||
PosterHStack(
|
PosterHStack(
|
||||||
title: L10n.castAndCrew,
|
title: L10n.castAndCrew,
|
||||||
type: .portrait,
|
type: .portrait,
|
||||||
items: people
|
items: people.filter(\.isDisplayed).prefix(20).asArray
|
||||||
)
|
)
|
||||||
|
.trailing {
|
||||||
|
SeeAllButton()
|
||||||
|
.onSelect {
|
||||||
|
router.route(to: \.castAndCrew, people)
|
||||||
|
}
|
||||||
|
}
|
||||||
.onSelect { person in
|
.onSelect { person in
|
||||||
router.route(to: \.library, .init(parent: person, type: .person, filters: .init()))
|
router.route(to: \.library, .init(parent: person, type: .person, filters: .init()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import JellyfinAPI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension ItemView {
|
extension ItemView {
|
||||||
|
|
||||||
struct GenresHStack: View {
|
struct GenresHStack: View {
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
|
|
|
@ -11,6 +11,7 @@ import JellyfinAPI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension ItemView {
|
extension ItemView {
|
||||||
|
|
||||||
struct SimilarItemsHStack: View {
|
struct SimilarItemsHStack: View {
|
||||||
|
|
||||||
@Default(.Customization.similarPosterType)
|
@Default(.Customization.similarPosterType)
|
||||||
|
@ -26,6 +27,13 @@ extension ItemView {
|
||||||
type: similarPosterType,
|
type: similarPosterType,
|
||||||
items: items
|
items: items
|
||||||
)
|
)
|
||||||
|
.trailing {
|
||||||
|
SeeAllButton()
|
||||||
|
.onSelect {
|
||||||
|
let viewModel = StaticLibraryViewModel(items: items)
|
||||||
|
router.route(to: \.basicLibrary, .init(title: L10n.recommended, viewModel: viewModel))
|
||||||
|
}
|
||||||
|
}
|
||||||
.onSelect { item in
|
.onSelect { item in
|
||||||
router.route(to: \.item, item)
|
router.route(to: \.item, item)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import JellyfinAPI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension ItemView {
|
extension ItemView {
|
||||||
|
|
||||||
struct StudiosHStack: View {
|
struct StudiosHStack: View {
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
|
|
|
@ -43,7 +43,8 @@ struct ItemView: View {
|
||||||
CollectionItemView(viewModel: .init(item: item))
|
CollectionItemView(viewModel: .init(item: item))
|
||||||
}
|
}
|
||||||
case .person:
|
case .person:
|
||||||
LibraryView(viewModel: .init(parent: item, type: .person))
|
LibraryView(viewModel: LibraryViewModel(parent: item, type: .person))
|
||||||
|
// LibraryView(viewModel: .init(parent: item, type: .person))
|
||||||
default:
|
default:
|
||||||
Text(L10n.notImplementedYetWithType(item.type ?? "--"))
|
Text(L10n.notImplementedYetWithType(item.type ?? "--"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ extension EpisodeItemView {
|
||||||
|
|
||||||
// MARK: Cast and Crew
|
// MARK: Cast and Crew
|
||||||
|
|
||||||
if let castAndCrew = viewModel.item.people?.filter(\.isDisplayed),
|
if let castAndCrew = viewModel.item.people,
|
||||||
!castAndCrew.isEmpty
|
!castAndCrew.isEmpty
|
||||||
{
|
{
|
||||||
ItemView.CastAndCrewHStack(people: castAndCrew)
|
ItemView.CastAndCrewHStack(people: castAndCrew)
|
||||||
|
|
|
@ -40,7 +40,7 @@ extension MovieItemView {
|
||||||
|
|
||||||
// MARK: Cast and Crew
|
// MARK: Cast and Crew
|
||||||
|
|
||||||
if let castAndCrew = viewModel.item.people?.filter(\.isDisplayed),
|
if let castAndCrew = viewModel.item.people,
|
||||||
!castAndCrew.isEmpty
|
!castAndCrew.isEmpty
|
||||||
{
|
{
|
||||||
ItemView.CastAndCrewHStack(people: castAndCrew)
|
ItemView.CastAndCrewHStack(people: castAndCrew)
|
||||||
|
|
|
@ -44,7 +44,7 @@ extension SeriesItemView {
|
||||||
|
|
||||||
// MARK: Cast and Crew
|
// MARK: Cast and Crew
|
||||||
|
|
||||||
if let castAndCrew = viewModel.item.people?.filter(\.isDisplayed),
|
if let castAndCrew = viewModel.item.people,
|
||||||
!castAndCrew.isEmpty
|
!castAndCrew.isEmpty
|
||||||
{
|
{
|
||||||
ItemView.CastAndCrewHStack(people: castAndCrew)
|
ItemView.CastAndCrewHStack(people: castAndCrew)
|
||||||
|
|
|
@ -39,7 +39,7 @@ extension iPadOSEpisodeItemView {
|
||||||
|
|
||||||
// MARK: Cast and Crew
|
// MARK: Cast and Crew
|
||||||
|
|
||||||
if let castAndCrew = viewModel.item.people?.filter(\.isDisplayed),
|
if let castAndCrew = viewModel.item.people,
|
||||||
!castAndCrew.isEmpty
|
!castAndCrew.isEmpty
|
||||||
{
|
{
|
||||||
ItemView.CastAndCrewHStack(people: castAndCrew)
|
ItemView.CastAndCrewHStack(people: castAndCrew)
|
||||||
|
|
|
@ -39,7 +39,7 @@ extension iPadOSMovieItemView {
|
||||||
|
|
||||||
// MARK: Cast and Crew
|
// MARK: Cast and Crew
|
||||||
|
|
||||||
if let castAndCrew = viewModel.item.people?.filter(\.isDisplayed),
|
if let castAndCrew = viewModel.item.people,
|
||||||
!castAndCrew.isEmpty
|
!castAndCrew.isEmpty
|
||||||
{
|
{
|
||||||
ItemView.CastAndCrewHStack(people: castAndCrew)
|
ItemView.CastAndCrewHStack(people: castAndCrew)
|
||||||
|
|
|
@ -43,7 +43,7 @@ extension iPadOSSeriesItemView {
|
||||||
|
|
||||||
// MARK: Cast and Crew
|
// MARK: Cast and Crew
|
||||||
|
|
||||||
if let castAndCrew = viewModel.item.people?.filter(\.isDisplayed),
|
if let castAndCrew = viewModel.item.people,
|
||||||
!castAndCrew.isEmpty
|
!castAndCrew.isEmpty
|
||||||
{
|
{
|
||||||
ItemView.CastAndCrewHStack(people: castAndCrew)
|
ItemView.CastAndCrewHStack(people: castAndCrew)
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import CollectionView
|
||||||
|
import Defaults
|
||||||
|
import JellyfinAPI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct LibraryView: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: LibraryCoordinator.Router
|
||||||
|
@ObservedObject
|
||||||
|
var viewModel: LibraryViewModel
|
||||||
|
|
||||||
|
@Default(.Customization.Library.viewType)
|
||||||
|
private var libraryViewType
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var loadingView: some View {
|
||||||
|
ProgressView()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var noResultsView: some View {
|
||||||
|
L10n.noResults.text
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
private var libraryItemsView: some View {
|
||||||
|
PagingLibraryView(viewModel: viewModel)
|
||||||
|
.onSelect { item in
|
||||||
|
baseItemOnSelect(item)
|
||||||
|
}
|
||||||
|
.ignoresSafeArea()
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
if viewModel.isLoading && viewModel.items.isEmpty {
|
||||||
|
loadingView
|
||||||
|
} else if viewModel.items.isEmpty {
|
||||||
|
noResultsView
|
||||||
|
} else {
|
||||||
|
libraryItemsView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle(viewModel.parent?.displayName ?? "")
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.navBarDrawer {
|
||||||
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
FilterDrawerHStack(viewModel: viewModel.filterViewModel)
|
||||||
|
.onSelect { filterCoordinatorParameters in
|
||||||
|
router.route(to: \.filter, filterCoordinatorParameters)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.padding(.vertical, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||||
|
|
||||||
|
if viewModel.isLoading && !viewModel.items.isEmpty {
|
||||||
|
ProgressView()
|
||||||
|
}
|
||||||
|
|
||||||
|
LibraryViewTypeToggle(libraryViewType: $libraryViewType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,160 +0,0 @@
|
||||||
//
|
|
||||||
// Swiftfin is subject to the terms of the Mozilla Public
|
|
||||||
// License, v2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
|
||||||
//
|
|
||||||
|
|
||||||
import CollectionView
|
|
||||||
import Defaults
|
|
||||||
import JellyfinAPI
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct LibraryView: View {
|
|
||||||
|
|
||||||
@EnvironmentObject
|
|
||||||
private var router: LibraryCoordinator.Router
|
|
||||||
@ObservedObject
|
|
||||||
var viewModel: LibraryViewModel
|
|
||||||
|
|
||||||
@Default(.Customization.Library.gridPosterType)
|
|
||||||
private var libraryGridPosterType
|
|
||||||
@Default(.Customization.Library.viewType)
|
|
||||||
private var libraryViewType
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private var loadingView: some View {
|
|
||||||
ProgressView()
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private var noResultsView: some View {
|
|
||||||
L10n.noResults.text
|
|
||||||
}
|
|
||||||
|
|
||||||
private var gridLayout: NSCollectionLayoutSection.GridLayoutMode {
|
|
||||||
if libraryGridPosterType == .landscape && UIDevice.isPhone {
|
|
||||||
return .fixedNumberOfColumns(2)
|
|
||||||
} else {
|
|
||||||
return .adaptive(withMinItemSize: libraryGridPosterType.width + (UIDevice.isIPad ? 10 : 0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
private var libraryListView: some View {
|
|
||||||
CollectionView(items: viewModel.items) { _, item, _ in
|
|
||||||
LibraryItemRow(item: item)
|
|
||||||
.onSelect {
|
|
||||||
baseItemOnSelect(item)
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
.layout { _, layoutEnvironment in
|
|
||||||
.list(using: .init(appearance: .plain), layoutEnvironment: layoutEnvironment)
|
|
||||||
}
|
|
||||||
.willReachEdge(insets: .init(top: 0, leading: 0, bottom: 200, trailing: 0)) { edge in
|
|
||||||
if !viewModel.isLoading && edge == .bottom {
|
|
||||||
viewModel.requestNextPage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.configure { configuration in
|
|
||||||
configuration.showsVerticalScrollIndicator = false
|
|
||||||
}
|
|
||||||
.ignoresSafeArea()
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private var libraryGridView: some View {
|
|
||||||
CollectionView(items: viewModel.items) { _, item, _ in
|
|
||||||
PosterButton(item: item, type: libraryGridPosterType)
|
|
||||||
.scaleItem(libraryGridPosterType == .landscape && UIDevice.isPhone ? 0.85 : 1)
|
|
||||||
.onSelect {
|
|
||||||
baseItemOnSelect(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.layout { _, layoutEnvironment in
|
|
||||||
.grid(
|
|
||||||
layoutEnvironment: layoutEnvironment,
|
|
||||||
layoutMode: gridLayout,
|
|
||||||
sectionInsets: .init(top: 0, leading: 10, bottom: 0, trailing: 10)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.willReachEdge(insets: .init(top: 0, leading: 0, bottom: 200, trailing: 0)) { edge in
|
|
||||||
if !viewModel.isLoading && edge == .bottom {
|
|
||||||
viewModel.requestNextPage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.configure { configuration in
|
|
||||||
configuration.showsVerticalScrollIndicator = false
|
|
||||||
}
|
|
||||||
.ignoresSafeArea()
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Group {
|
|
||||||
if viewModel.isLoading && viewModel.items.isEmpty {
|
|
||||||
loadingView
|
|
||||||
} else if viewModel.items.isEmpty {
|
|
||||||
noResultsView
|
|
||||||
} else {
|
|
||||||
switch libraryViewType {
|
|
||||||
case .grid:
|
|
||||||
libraryGridView
|
|
||||||
case .list:
|
|
||||||
libraryListView
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.navigationTitle(viewModel.parent?.displayName ?? "")
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
|
||||||
.navBarDrawer {
|
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
|
||||||
FilterDrawerHStack(viewModel: viewModel.filterViewModel)
|
|
||||||
.onSelect { filterCoordinatorParameters in
|
|
||||||
router.route(to: \.filter, filterCoordinatorParameters)
|
|
||||||
}
|
|
||||||
.padding(.horizontal)
|
|
||||||
.padding(.vertical, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.toolbar {
|
|
||||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
|
||||||
|
|
||||||
if viewModel.isLoading && !viewModel.items.isEmpty {
|
|
||||||
ProgressView()
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
switch libraryViewType {
|
|
||||||
case .grid:
|
|
||||||
libraryViewType = .list
|
|
||||||
case .list:
|
|
||||||
libraryViewType = .grid
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
switch libraryViewType {
|
|
||||||
case .grid:
|
|
||||||
Image(systemName: "list.dash")
|
|
||||||
case .list:
|
|
||||||
Image(systemName: "square.grid.2x2")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue