iOS/iPadOS - Refactor Filter Selection (#548)
This commit is contained in:
parent
109c0328b6
commit
f92edb83fb
|
@ -17,6 +17,7 @@
|
||||||
--varattributes prev-line
|
--varattributes prev-line
|
||||||
--trailingclosures
|
--trailingclosures
|
||||||
--shortoptionals "always"
|
--shortoptionals "always"
|
||||||
|
--ifdef no-indent
|
||||||
|
|
||||||
--enable isEmpty, \
|
--enable isEmpty, \
|
||||||
leadingDelimiters, \
|
leadingDelimiters, \
|
||||||
|
|
|
@ -7,31 +7,41 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import JellyfinAPI
|
||||||
import Stinsen
|
import Stinsen
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
typealias FilterCoordinatorParams = (filters: Binding<LibraryFilters>, enabledFilterType: [FilterType], parentId: String)
|
|
||||||
|
|
||||||
final class FilterCoordinator: NavigationCoordinatable {
|
final class FilterCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
|
struct Parameters {
|
||||||
|
let title: String
|
||||||
|
let viewModel: FilterViewModel
|
||||||
|
let filter: WritableKeyPath<ItemFilters, [ItemFilters.Filter]>
|
||||||
|
let selectorType: SelectorType
|
||||||
|
}
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \FilterCoordinator.start)
|
let stack = NavigationStack(initial: \FilterCoordinator.start)
|
||||||
|
|
||||||
@Root
|
@Root
|
||||||
var start = makeStart
|
var start = makeStart
|
||||||
|
|
||||||
@Binding
|
private let parameters: Parameters
|
||||||
var filters: LibraryFilters
|
|
||||||
var enabledFilterType: [FilterType]
|
|
||||||
var parentId: String = ""
|
|
||||||
|
|
||||||
init(filters: Binding<LibraryFilters>, enabledFilterType: [FilterType], parentId: String) {
|
init(parameters: Parameters) {
|
||||||
_filters = filters
|
self.parameters = parameters
|
||||||
self.enabledFilterType = enabledFilterType
|
|
||||||
self.parentId = parentId
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeStart() -> some View {
|
func makeStart() -> some View {
|
||||||
LibraryFilterView(filters: $filters, enabledFilterType: enabledFilterType, parentId: parentId)
|
#if os(tvOS)
|
||||||
|
Text(verbatim: .emptyDash)
|
||||||
|
#else
|
||||||
|
FilterView(
|
||||||
|
title: parameters.title,
|
||||||
|
viewModel: parameters.viewModel,
|
||||||
|
filter: parameters.filter,
|
||||||
|
selectorType: parameters.selectorType
|
||||||
|
)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,36 +21,38 @@ final class HomeCoordinator: NavigationCoordinatable {
|
||||||
var settings = makeSettings
|
var settings = makeSettings
|
||||||
|
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
@Route(.modal)
|
@Route(.modal)
|
||||||
var item = makeModalItem
|
var item = makeItem
|
||||||
@Route(.modal)
|
@Route(.modal)
|
||||||
var library = makeModalLibrary
|
var library = makeLibrary
|
||||||
#else
|
#else
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var item = makeItem
|
var item = makeItem
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var library = makeLibrary
|
var library = makeLibrary
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
func makeSettings() -> NavigationViewCoordinator<SettingsCoordinator> {
|
func makeSettings() -> NavigationViewCoordinator<SettingsCoordinator> {
|
||||||
NavigationViewCoordinator(SettingsCoordinator())
|
NavigationViewCoordinator(SettingsCoordinator())
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeLibrary(params: LibraryCoordinatorParams) -> LibraryCoordinator {
|
#if os(tvOS)
|
||||||
LibraryCoordinator(viewModel: params.viewModel, title: params.title)
|
func makeItem(item: BaseItemDto) -> NavigationViewCoordinator<ItemCoordinator> {
|
||||||
|
NavigationViewCoordinator(ItemCoordinator(item: item))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeLibrary(parameters: LibraryCoordinator.Parameters) -> NavigationViewCoordinator<LibraryCoordinator> {
|
||||||
|
NavigationViewCoordinator(LibraryCoordinator(parameters: parameters))
|
||||||
|
}
|
||||||
|
#else
|
||||||
func makeItem(item: BaseItemDto) -> ItemCoordinator {
|
func makeItem(item: BaseItemDto) -> ItemCoordinator {
|
||||||
ItemCoordinator(item: item)
|
ItemCoordinator(item: item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeModalItem(item: BaseItemDto) -> NavigationViewCoordinator<ItemCoordinator> {
|
func makeLibrary(parameters: LibraryCoordinator.Parameters) -> LibraryCoordinator {
|
||||||
NavigationViewCoordinator(ItemCoordinator(item: item))
|
LibraryCoordinator(parameters: parameters)
|
||||||
}
|
|
||||||
|
|
||||||
func makeModalLibrary(params: LibraryCoordinatorParams) -> NavigationViewCoordinator<LibraryCoordinator> {
|
|
||||||
NavigationViewCoordinator(LibraryCoordinator(viewModel: params.viewModel, title: params.title))
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeStart() -> some View {
|
func makeStart() -> some View {
|
||||||
|
|
|
@ -32,8 +32,8 @@ final class ItemCoordinator: NavigationCoordinatable {
|
||||||
self.itemDto = item
|
self.itemDto = item
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeLibrary(params: LibraryCoordinatorParams) -> LibraryCoordinator {
|
func makeLibrary(parameters: LibraryCoordinator.Parameters) -> LibraryCoordinator {
|
||||||
LibraryCoordinator(viewModel: params.viewModel, title: params.title)
|
LibraryCoordinator(parameters: parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeItem(item: BaseItemDto) -> ItemCoordinator {
|
func makeItem(item: BaseItemDto) -> ItemCoordinator {
|
||||||
|
|
|
@ -26,9 +26,9 @@ final class ItemOverviewCoordinator: NavigationCoordinatable {
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeStart() -> some View {
|
func makeStart() -> some View {
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
EmptyView()
|
EmptyView()
|
||||||
#else
|
#else
|
||||||
ItemOverviewView(item: item)
|
ItemOverviewView(item: item)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,51 +11,71 @@ import JellyfinAPI
|
||||||
import Stinsen
|
import Stinsen
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
typealias LibraryCoordinatorParams = (viewModel: LibraryViewModel, title: String)
|
|
||||||
|
|
||||||
final class LibraryCoordinator: NavigationCoordinatable {
|
final class LibraryCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
|
struct Parameters {
|
||||||
|
let parent: LibraryParent?
|
||||||
|
let type: LibraryParentType
|
||||||
|
let filters: ItemFilters
|
||||||
|
|
||||||
|
init(
|
||||||
|
parent: LibraryParent,
|
||||||
|
type: LibraryParentType,
|
||||||
|
filters: ItemFilters
|
||||||
|
) {
|
||||||
|
self.parent = parent
|
||||||
|
self.type = type
|
||||||
|
self.filters = filters
|
||||||
|
}
|
||||||
|
|
||||||
|
init(filters: ItemFilters) {
|
||||||
|
self.parent = nil
|
||||||
|
self.type = .library
|
||||||
|
self.filters = filters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \LibraryCoordinator.start)
|
let stack = NavigationStack(initial: \LibraryCoordinator.start)
|
||||||
|
|
||||||
@Root
|
@Root
|
||||||
var start = makeStart
|
var start = makeStart
|
||||||
@Route(.modal)
|
|
||||||
var filter = makeFilter
|
|
||||||
|
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
@Route(.modal)
|
@Route(.modal)
|
||||||
var item = makeModalItem
|
var item = makeItem
|
||||||
#else
|
#else
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var item = makeItem
|
var item = makeItem
|
||||||
|
@Route(.modal)
|
||||||
|
var filter = makeFilter
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
let viewModel: LibraryViewModel
|
private let parameters: Parameters
|
||||||
let title: String
|
|
||||||
|
|
||||||
init(viewModel: LibraryViewModel, title: String) {
|
init(parameters: Parameters) {
|
||||||
self.viewModel = viewModel
|
self.parameters = parameters
|
||||||
self.title = title
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeStart() -> some View {
|
func makeStart() -> some View {
|
||||||
LibraryView(viewModel: self.viewModel)
|
if let parent = parameters.parent {
|
||||||
|
LibraryView(viewModel: .init(parent: parent, type: parameters.type, filters: parameters.filters))
|
||||||
|
} else {
|
||||||
|
LibraryView(viewModel: .init(filters: parameters.filters))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeFilter(params: FilterCoordinatorParams) -> NavigationViewCoordinator<FilterCoordinator> {
|
#if os(tvOS)
|
||||||
NavigationViewCoordinator(FilterCoordinator(
|
func makeItem(item: BaseItemDto) -> NavigationViewCoordinator<ItemCoordinator> {
|
||||||
filters: params.filters,
|
NavigationViewCoordinator(ItemCoordinator(item: item))
|
||||||
enabledFilterType: params.enabledFilterType,
|
|
||||||
parentId: params.parentId
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
func makeItem(item: BaseItemDto) -> ItemCoordinator {
|
func makeItem(item: BaseItemDto) -> ItemCoordinator {
|
||||||
ItemCoordinator(item: item)
|
ItemCoordinator(item: item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeModalItem(item: BaseItemDto) -> NavigationViewCoordinator<ItemCoordinator> {
|
func makeFilter(parameters: FilterCoordinator.Parameters) -> NavigationViewCoordinator<FilterCoordinator> {
|
||||||
NavigationViewCoordinator(ItemCoordinator(item: item))
|
NavigationViewCoordinator(FilterCoordinator(parameters: parameters))
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,28 +17,28 @@ final class MediaCoordinator: NavigationCoordinatable {
|
||||||
@Root
|
@Root
|
||||||
var start = makeStart
|
var start = makeStart
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
@Route(.modal)
|
@Route(.modal)
|
||||||
var library = makeLibrary
|
var library = makeLibrary
|
||||||
#else
|
#else
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var library = makeLibrary
|
var library = makeLibrary
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var liveTV = makeLiveTV
|
var liveTV = makeLiveTV
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
func makeLibrary(params: LibraryCoordinatorParams) -> NavigationViewCoordinator<LibraryCoordinator> {
|
func makeLibrary(parameters: LibraryCoordinator.Parameters) -> NavigationViewCoordinator<LibraryCoordinator> {
|
||||||
NavigationViewCoordinator(LibraryCoordinator(viewModel: params.viewModel, title: params.title))
|
NavigationViewCoordinator(LibraryCoordinator(parameters: parameters))
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
func makeLibrary(params: LibraryCoordinatorParams) -> LibraryCoordinator {
|
func makeLibrary(parameters: LibraryCoordinator.Parameters) -> LibraryCoordinator {
|
||||||
LibraryCoordinator(viewModel: params.viewModel, title: params.title)
|
LibraryCoordinator(parameters: parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeLiveTV() -> LiveTVCoordinator {
|
func makeLiveTV() -> LiveTVCoordinator {
|
||||||
LiveTVCoordinator()
|
LiveTVCoordinator()
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|
|
@ -36,10 +36,10 @@ final class MovieLibrariesCoordinator: NavigationCoordinatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeLibrary(library: BaseItemDto) -> LibraryCoordinator {
|
func makeLibrary(library: BaseItemDto) -> LibraryCoordinator {
|
||||||
LibraryCoordinator(viewModel: LibraryViewModel(library: library), title: library.title)
|
LibraryCoordinator(parameters: .init(parent: library, type: .library, filters: .init()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeRootLibrary(library: BaseItemDto) -> LibraryCoordinator {
|
func makeRootLibrary(library: BaseItemDto) -> LibraryCoordinator {
|
||||||
LibraryCoordinator(viewModel: LibraryViewModel(library: library), title: library.title)
|
LibraryCoordinator(parameters: .init(parent: library, type: .library, filters: .init()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,21 +18,27 @@ final class SearchCoordinator: NavigationCoordinatable {
|
||||||
@Root
|
@Root
|
||||||
var start = makeStart
|
var start = makeStart
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
@Route(.modal)
|
@Route(.modal)
|
||||||
var item = makeItem
|
var item = makeItem
|
||||||
#else
|
#else
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var item = makeItem
|
var item = makeItem
|
||||||
|
@Route(.modal)
|
||||||
|
var filter = makeFilter
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
func makeItem(item: BaseItemDto) -> NavigationViewCoordinator<ItemCoordinator> {
|
func makeItem(item: BaseItemDto) -> NavigationViewCoordinator<ItemCoordinator> {
|
||||||
NavigationViewCoordinator(ItemCoordinator(item: item))
|
NavigationViewCoordinator(ItemCoordinator(item: item))
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
func makeItem(item: BaseItemDto) -> ItemCoordinator {
|
func makeItem(item: BaseItemDto) -> ItemCoordinator {
|
||||||
ItemCoordinator(item: item)
|
ItemCoordinator(item: item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeFilter(parameters: FilterCoordinator.Parameters) -> NavigationViewCoordinator<FilterCoordinator> {
|
||||||
|
NavigationViewCoordinator(FilterCoordinator(parameters: parameters))
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|
|
@ -27,10 +27,10 @@ final class SettingsCoordinator: NavigationCoordinatable {
|
||||||
var about = makeAbout
|
var about = makeAbout
|
||||||
|
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var quickConnect = makeQuickConnectSettings
|
var quickConnect = makeQuickConnectSettings
|
||||||
@Route(.push)
|
@Route(.push)
|
||||||
var fontPicker = makeFontPicker
|
var fontPicker = makeFontPicker
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
@ -59,17 +59,17 @@ final class SettingsCoordinator: NavigationCoordinatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeQuickConnectSettings() -> some View {
|
func makeQuickConnectSettings() -> some View {
|
||||||
let viewModel = QuickConnectSettingsViewModel()
|
let viewModel = QuickConnectSettingsViewModel()
|
||||||
QuickConnectSettingsView(viewModel: viewModel)
|
QuickConnectSettingsView(viewModel: viewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeFontPicker() -> some View {
|
func makeFontPicker() -> some View {
|
||||||
FontPickerView()
|
FontPickerView()
|
||||||
.navigationTitle(L10n.subtitleFont)
|
.navigationTitle(L10n.subtitleFont)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|
|
@ -36,10 +36,10 @@ final class TVLibrariesCoordinator: NavigationCoordinatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeLibrary(library: BaseItemDto) -> LibraryCoordinator {
|
func makeLibrary(library: BaseItemDto) -> LibraryCoordinator {
|
||||||
LibraryCoordinator(viewModel: LibraryViewModel(library: library), title: library.title)
|
LibraryCoordinator(parameters: .init(parent: library, type: .library, filters: .init()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeRootLibrary(library: BaseItemDto) -> LibraryCoordinator {
|
func makeRootLibrary(library: BaseItemDto) -> LibraryCoordinator {
|
||||||
LibraryCoordinator(viewModel: LibraryViewModel(library: library), title: library.title)
|
LibraryCoordinator(parameters: .init(parent: library, type: .library, filters: .init()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,8 @@ final class UserSignInCoordinator: NavigationCoordinatable {
|
||||||
@Root
|
@Root
|
||||||
var start = makeStart
|
var start = makeStart
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
@Route(.modal)
|
@Route(.modal)
|
||||||
var quickConnect = makeQuickConnect
|
var quickConnect = makeQuickConnect
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
let viewModel: UserSignInViewModel
|
let viewModel: UserSignInViewModel
|
||||||
|
@ -28,9 +28,9 @@ final class UserSignInCoordinator: NavigationCoordinatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
func makeQuickConnect() -> NavigationViewCoordinator<QuickConnectCoordinator> {
|
func makeQuickConnect() -> NavigationViewCoordinator<QuickConnectCoordinator> {
|
||||||
NavigationViewCoordinator(QuickConnectCoordinator(viewModel: viewModel))
|
NavigationViewCoordinator(QuickConnectCoordinator(viewModel: viewModel))
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|
|
@ -13,15 +13,15 @@ public extension Color {
|
||||||
internal static let jellyfinPurple = Color(uiColor: .jellyfinPurple)
|
internal static let jellyfinPurple = Color(uiColor: .jellyfinPurple)
|
||||||
|
|
||||||
#if os(tvOS) // tvOS doesn't have these
|
#if os(tvOS) // tvOS doesn't have these
|
||||||
static let systemFill = Color(UIColor.white)
|
static let systemFill = Color(UIColor.white)
|
||||||
static let secondarySystemFill = Color(UIColor.gray)
|
static let secondarySystemFill = Color(UIColor.gray)
|
||||||
static let tertiarySystemFill = Color(UIColor.black)
|
static let tertiarySystemFill = Color(UIColor.black)
|
||||||
static let lightGray = Color(UIColor.lightGray)
|
static let lightGray = Color(UIColor.lightGray)
|
||||||
#else
|
#else
|
||||||
static let systemFill = Color(UIColor.systemFill)
|
static let systemFill = Color(UIColor.systemFill)
|
||||||
static let systemBackground = Color(UIColor.systemBackground)
|
static let systemBackground = Color(UIColor.systemBackground)
|
||||||
static let secondarySystemFill = Color(UIColor.secondarySystemFill)
|
static let secondarySystemFill = Color(UIColor.secondarySystemFill)
|
||||||
static let tertiarySystemFill = Color(UIColor.tertiarySystemFill)
|
static let tertiarySystemFill = Color(UIColor.tertiarySystemFill)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,8 @@ extension Font {
|
||||||
func toUIFont() -> UIFont {
|
func toUIFont() -> UIFont {
|
||||||
switch self {
|
switch self {
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
case .largeTitle:
|
case .largeTitle:
|
||||||
return UIFont.preferredFont(forTextStyle: .largeTitle)
|
return UIFont.preferredFont(forTextStyle: .largeTitle)
|
||||||
#endif
|
#endif
|
||||||
case .title:
|
case .title:
|
||||||
return UIFont.preferredFont(forTextStyle: .title1)
|
return UIFont.preferredFont(forTextStyle: .title1)
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
extension APISortOrder {
|
||||||
|
// TODO: Localize
|
||||||
|
var localized: String {
|
||||||
|
switch self {
|
||||||
|
case .ascending:
|
||||||
|
return "Ascending"
|
||||||
|
case .descending:
|
||||||
|
return "Descending"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var filter: ItemFilters.Filter {
|
||||||
|
.init(displayName: localized, filterName: rawValue)
|
||||||
|
}
|
||||||
|
}
|
|
@ -86,7 +86,7 @@ extension BaseItemDto {
|
||||||
}
|
}
|
||||||
|
|
||||||
var displayName: String {
|
var displayName: String {
|
||||||
name ?? "--"
|
name ?? .emptyDash
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: ItemDetail
|
// MARK: ItemDetail
|
||||||
|
@ -247,3 +247,5 @@ extension BaseItemDtoImageBlurHashes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension BaseItemDto: LibraryParent {}
|
||||||
|
|
|
@ -14,10 +14,6 @@ import UIKit
|
||||||
|
|
||||||
extension BaseItemPerson: Poster {
|
extension BaseItemPerson: Poster {
|
||||||
|
|
||||||
var title: String {
|
|
||||||
self.name ?? "--"
|
|
||||||
}
|
|
||||||
|
|
||||||
var subtitle: String? {
|
var subtitle: String? {
|
||||||
self.firstRole
|
self.firstRole
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,3 +50,11 @@ extension BaseItemPerson {
|
||||||
return DisplayedType(rawValue: type) != nil
|
return DisplayedType(rawValue: type) != nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension BaseItemPerson: Displayable {
|
||||||
|
var displayName: String {
|
||||||
|
self.name ?? .emptyDash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension BaseItemPerson: LibraryParent {}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
extension ItemFilter {
|
||||||
|
static var supportedCases: [ItemFilter] {
|
||||||
|
[.isUnplayed, .isPlayed, .isFavorite, .likes]
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Localize
|
||||||
|
var localized: String {
|
||||||
|
switch self {
|
||||||
|
case .isUnplayed:
|
||||||
|
return "Unplayed"
|
||||||
|
case .isPlayed:
|
||||||
|
return "Played"
|
||||||
|
case .isFavorite:
|
||||||
|
return "Favorites"
|
||||||
|
case .likes:
|
||||||
|
return "Liked Items"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var filter: ItemFilters.Filter {
|
||||||
|
.init(displayName: localized, filterName: rawValue)
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,8 +9,16 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
|
||||||
extension NameGuidPair: PillStackable {
|
extension NameGuidPair {
|
||||||
var title: String {
|
var filter: ItemFilters.Filter {
|
||||||
self.name ?? ""
|
.init(displayName: displayName, id: id, filterName: displayName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension NameGuidPair: Displayable {
|
||||||
|
var displayName: String {
|
||||||
|
self.name ?? .emptyDash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NameGuidPair: LibraryParent {}
|
||||||
|
|
|
@ -54,6 +54,12 @@ extension String {
|
||||||
let textSize = self.size(withAttributes: fontAttributes)
|
let textSize = self.size(withAttributes: fontAttributes)
|
||||||
return textSize.width
|
return textSize.width
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var filter: ItemFilters.Filter {
|
||||||
|
.init(displayName: self, id: self, filterName: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
static var emptyDash = "--"
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension CharacterSet {
|
public extension CharacterSet {
|
||||||
|
|
|
@ -22,22 +22,22 @@ extension UIDevice {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
static var isPortrait: Bool {
|
static var isPortrait: Bool {
|
||||||
UIDevice.current.orientation.isPortrait
|
UIDevice.current.orientation.isPortrait
|
||||||
}
|
}
|
||||||
|
|
||||||
static var isLandscape: Bool {
|
static var isLandscape: Bool {
|
||||||
isIPad || UIDevice.current.orientation.isLandscape
|
isIPad || UIDevice.current.orientation.isLandscape
|
||||||
}
|
}
|
||||||
|
|
||||||
static func feedback(_ type: UINotificationFeedbackGenerator.FeedbackType) {
|
static func feedback(_ type: UINotificationFeedbackGenerator.FeedbackType) {
|
||||||
let generator = UINotificationFeedbackGenerator()
|
let generator = UINotificationFeedbackGenerator()
|
||||||
generator.notificationOccurred(type)
|
generator.notificationOccurred(type)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func impact(_ type: UIImpactFeedbackGenerator.FeedbackStyle) {
|
static func impact(_ type: UIImpactFeedbackGenerator.FeedbackStyle) {
|
||||||
let generator = UIImpactFeedbackGenerator(style: type)
|
let generator = UIImpactFeedbackGenerator(style: type)
|
||||||
generator.impactOccurred()
|
generator.impactOccurred()
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,9 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
import TVVLCKit
|
import TVVLCKit
|
||||||
#else
|
#else
|
||||||
import MobileVLCKit
|
import MobileVLCKit
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
extension VLCMediaPlayer {
|
extension VLCMediaPlayer {
|
||||||
|
|
|
@ -234,16 +234,16 @@ class DeviceProfileBuilder {
|
||||||
private func CPUinfo() -> CPUModel {
|
private func CPUinfo() -> CPUModel {
|
||||||
|
|
||||||
#if targetEnvironment(simulator)
|
#if targetEnvironment(simulator)
|
||||||
let identifier = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"]!
|
let identifier = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"]!
|
||||||
#else
|
#else
|
||||||
|
|
||||||
var systemInfo = utsname()
|
var systemInfo = utsname()
|
||||||
uname(&systemInfo)
|
uname(&systemInfo)
|
||||||
let machineMirror = Mirror(reflecting: systemInfo.machine)
|
let machineMirror = Mirror(reflecting: systemInfo.machine)
|
||||||
let identifier = machineMirror.children.reduce("") { identifier, element in
|
let identifier = machineMirror.children.reduce("") { identifier, element in
|
||||||
guard let value = element.value as? Int8, value != 0 else { return identifier }
|
guard let value = element.value as? Int8, value != 0 else { return identifier }
|
||||||
return identifier + String(UnicodeScalar(UInt8(value)))
|
return identifier + String(UnicodeScalar(UInt8(value)))
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
switch identifier {
|
switch identifier {
|
||||||
|
|
|
@ -1,32 +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
|
|
||||||
//
|
|
||||||
|
|
||||||
// https://www.hackingwithswift.com/quick-start/swiftui/how-to-detect-device-rotation
|
|
||||||
import Foundation
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
// Our custom view modifier to track rotation and
|
|
||||||
// call our action
|
|
||||||
struct DeviceRotationViewModifier: ViewModifier {
|
|
||||||
let action: (UIDeviceOrientation) -> Void
|
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
|
||||||
content
|
|
||||||
.onAppear()
|
|
||||||
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
|
|
||||||
action(UIDevice.current.orientation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A View wrapper to make the modifier easier to use
|
|
||||||
extension View {
|
|
||||||
func onRotate(perform action: @escaping (UIDeviceOrientation) -> Void) -> some View {
|
|
||||||
self.modifier(DeviceRotationViewModifier(action: action))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
protocol Displayable {
|
||||||
|
var displayName: String { get }
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
struct ItemFilters: Hashable {
|
||||||
|
|
||||||
|
var genres: [Filter] = []
|
||||||
|
var tags: [Filter] = []
|
||||||
|
var filters: [Filter] = []
|
||||||
|
var sortOrder: [Filter] = [APISortOrder.ascending.filter]
|
||||||
|
var sortBy: [Filter] = [SortBy.name.filter]
|
||||||
|
|
||||||
|
static let favorites: ItemFilters = .init(filters: [ItemFilter.isFavorite.filter])
|
||||||
|
static let recent: ItemFilters = .init(sortOrder: [APISortOrder.descending.filter], sortBy: [SortBy.dateAdded.filter])
|
||||||
|
static let all: ItemFilters = .init(
|
||||||
|
filters: ItemFilter.supportedCases.map(\.filter),
|
||||||
|
sortOrder: APISortOrder.allCases.map(\.filter),
|
||||||
|
sortBy: SortBy.allCases.map(\.filter)
|
||||||
|
)
|
||||||
|
|
||||||
|
var hasFilters: Bool {
|
||||||
|
self != .init()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type-erased object for use with WritableKeyPath
|
||||||
|
struct Filter: Displayable, Hashable, Identifiable {
|
||||||
|
var displayName: String
|
||||||
|
var id: String?
|
||||||
|
var filterName: String
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ struct LibraryItem: Equatable, Poster {
|
||||||
|
|
||||||
var library: BaseItemDto
|
var library: BaseItemDto
|
||||||
var viewModel: MediaViewModel
|
var viewModel: MediaViewModel
|
||||||
var title: String = ""
|
var displayName: String = ""
|
||||||
var subtitle: String?
|
var subtitle: String?
|
||||||
var showTitle: Bool = false
|
var showTitle: Bool = false
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
protocol LibraryParent: Displayable {
|
||||||
|
var id: String? { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove so multiple people/studios can be used
|
||||||
|
enum LibraryParentType {
|
||||||
|
case library
|
||||||
|
case folders
|
||||||
|
case person
|
||||||
|
case studio
|
||||||
|
}
|
|
@ -10,8 +10,7 @@ import Defaults
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
protocol Poster: Hashable {
|
protocol Poster: Displayable, Hashable {
|
||||||
var title: String { get }
|
|
||||||
var subtitle: String? { get }
|
var subtitle: String? { get }
|
||||||
var showTitle: Bool { get }
|
var showTitle: Bool { get }
|
||||||
|
|
||||||
|
@ -21,7 +20,7 @@ protocol Poster: Hashable {
|
||||||
|
|
||||||
extension Poster {
|
extension Poster {
|
||||||
func hash(into hasher: inout Hasher) {
|
func hash(into hasher: inout Hasher) {
|
||||||
hasher.combine(title)
|
hasher.combine(displayName)
|
||||||
hasher.combine(subtitle)
|
hasher.combine(subtitle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ enum PosterType: String, CaseIterable, Defaults.Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: localize
|
||||||
var localizedName: String {
|
var localizedName: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .portrait:
|
case .portrait:
|
||||||
|
@ -33,15 +34,15 @@ enum PosterType: String, CaseIterable, Defaults.Serializable {
|
||||||
|
|
||||||
enum Width {
|
enum Width {
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
static let portrait = 250.0
|
static let portrait = 250.0
|
||||||
|
|
||||||
static let landscape = 490.0
|
static let landscape = 490.0
|
||||||
#else
|
#else
|
||||||
@ScaledMetric(relativeTo: .largeTitle)
|
@ScaledMetric(relativeTo: .largeTitle)
|
||||||
static var portrait = 100.0
|
static var portrait = 100.0
|
||||||
|
|
||||||
@ScaledMetric(relativeTo: .largeTitle)
|
@ScaledMetric(relativeTo: .largeTitle)
|
||||||
static var landscape = 200.0
|
static var landscape = 200.0
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
protocol PillStackable {
|
enum SelectorType {
|
||||||
var title: String { get }
|
case single
|
||||||
|
case multi
|
||||||
}
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
public enum SortBy: String, Codable, CaseIterable {
|
||||||
|
case premiereDate = "PremiereDate"
|
||||||
|
case name = "SortName"
|
||||||
|
case dateAdded = "DateCreated"
|
||||||
|
case random = "Random"
|
||||||
|
|
||||||
|
// TODO: Localize
|
||||||
|
var localized: String {
|
||||||
|
switch self {
|
||||||
|
case .premiereDate:
|
||||||
|
return "Premiere date"
|
||||||
|
case .name:
|
||||||
|
return "Name"
|
||||||
|
case .dateAdded:
|
||||||
|
return "Date added"
|
||||||
|
case .random:
|
||||||
|
return "Random"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var filter: ItemFilters.Filter {
|
||||||
|
.init(displayName: localized, filterName: rawValue)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,101 +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
|
|
||||||
|
|
||||||
// TODO: Look at refactoring everything in this file, probably move to JellyfinAPI
|
|
||||||
struct LibraryFilters: Codable, Hashable {
|
|
||||||
var filters: [ItemFilter] = []
|
|
||||||
var sortOrder: [APISortOrder] = [.ascending]
|
|
||||||
var withGenres: [NameGuidPair] = []
|
|
||||||
var tags: [String] = []
|
|
||||||
var sortBy: [SortBy] = [.name]
|
|
||||||
|
|
||||||
static let `default` = LibraryFilters()
|
|
||||||
static let favorites: LibraryFilters = .init(filters: [.isFavorite], sortOrder: [.ascending], sortBy: [.name])
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum SortBy: String, Codable, CaseIterable {
|
|
||||||
case premiereDate = "PremiereDate"
|
|
||||||
case name = "SortName"
|
|
||||||
case dateAdded = "DateCreated"
|
|
||||||
case random = "Random"
|
|
||||||
}
|
|
||||||
|
|
||||||
extension SortBy {
|
|
||||||
// TODO: Localize
|
|
||||||
var localized: String {
|
|
||||||
switch self {
|
|
||||||
case .premiereDate:
|
|
||||||
return "Premiere date"
|
|
||||||
case .name:
|
|
||||||
return "Name"
|
|
||||||
case .dateAdded:
|
|
||||||
return "Date added"
|
|
||||||
case .random:
|
|
||||||
return "Random"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ItemFilter {
|
|
||||||
static var supportedTypes: [ItemFilter] {
|
|
||||||
[.isUnplayed, isPlayed, .isFavorite, .likes]
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Localize
|
|
||||||
var localized: String {
|
|
||||||
switch self {
|
|
||||||
case .isUnplayed:
|
|
||||||
return "Unplayed"
|
|
||||||
case .isPlayed:
|
|
||||||
return "Played"
|
|
||||||
case .isFavorite:
|
|
||||||
return "Favorites"
|
|
||||||
case .likes:
|
|
||||||
return "Liked Items"
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension APISortOrder {
|
|
||||||
// TODO: Localize
|
|
||||||
var localized: String {
|
|
||||||
switch self {
|
|
||||||
case .ascending:
|
|
||||||
return "Ascending"
|
|
||||||
case .descending:
|
|
||||||
return "Descending"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove
|
|
||||||
enum ItemType: String {
|
|
||||||
case episode = "Episode"
|
|
||||||
case movie = "Movie"
|
|
||||||
case series = "Series"
|
|
||||||
case season = "Season"
|
|
||||||
|
|
||||||
var localized: String {
|
|
||||||
switch self {
|
|
||||||
case .episode:
|
|
||||||
return L10n.episodes
|
|
||||||
case .movie:
|
|
||||||
return "Movies"
|
|
||||||
case .series:
|
|
||||||
return "Shows"
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -302,9 +302,9 @@ final class SessionManager {
|
||||||
|
|
||||||
let platform: String
|
let platform: String
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
platform = "tvOS"
|
platform = "tvOS"
|
||||||
#else
|
#else
|
||||||
platform = "iOS"
|
platform = "iOS"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var header = "MediaBrowser "
|
var header = "MediaBrowser "
|
||||||
|
|
|
@ -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 Foundation
|
||||||
|
import JellyfinAPI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
final class FilterViewModel: ViewModel {
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var allFilters: ItemFilters = .all
|
||||||
|
@Published
|
||||||
|
var currentFilters: ItemFilters
|
||||||
|
|
||||||
|
let parent: LibraryParent?
|
||||||
|
|
||||||
|
init(
|
||||||
|
parent: LibraryParent?,
|
||||||
|
currentFilters: ItemFilters
|
||||||
|
) {
|
||||||
|
self.parent = parent
|
||||||
|
self.currentFilters = currentFilters
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
getQueryFilters()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getQueryFilters() {
|
||||||
|
FilterAPI.getQueryFilters(
|
||||||
|
userId: SessionManager.main.currentLogin.user.id,
|
||||||
|
parentId: parent?.id
|
||||||
|
)
|
||||||
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
|
self?.handleAPIRequestError(completion: completion)
|
||||||
|
}, receiveValue: { [weak self] queryFilters in
|
||||||
|
self?.allFilters.genres = queryFilters.genres?.map(\.filter) ?? []
|
||||||
|
self?.allFilters.tags = queryFilters.tags?.map(\.filter) ?? []
|
||||||
|
})
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,9 +24,6 @@ final class HomeViewModel: ViewModel {
|
||||||
@Published
|
@Published
|
||||||
var libraries: [BaseItemDto] = []
|
var libraries: [BaseItemDto] = []
|
||||||
|
|
||||||
// temp
|
|
||||||
static let recentFilterSet = LibraryFilters(filters: [], sortOrder: [.descending], sortBy: [.dateAdded])
|
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
refresh()
|
refresh()
|
||||||
|
|
|
@ -69,7 +69,7 @@ final class EpisodeItemViewModel: ItemViewModel {
|
||||||
.joined(separator: ", ")
|
.joined(separator: ", ")
|
||||||
|
|
||||||
let currentMediaItems: [BaseItemDto.ItemDetail] = [
|
let currentMediaItems: [BaseItemDto.ItemDetail] = [
|
||||||
.init(title: "File", content: viewModel.filename ?? "--"),
|
.init(title: "File", content: viewModel.filename ?? .emptyDash),
|
||||||
.init(title: "Audio", content: audioStreams),
|
.init(title: "Audio", content: audioStreams),
|
||||||
.init(title: "Subtitles", content: subtitleStreams),
|
.init(title: "Subtitles", content: subtitleStreams),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,86 +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
|
|
||||||
|
|
||||||
enum FilterType {
|
|
||||||
case tag
|
|
||||||
case genre
|
|
||||||
case sortOrder
|
|
||||||
case sortBy
|
|
||||||
case filter
|
|
||||||
}
|
|
||||||
|
|
||||||
final class LibraryFilterViewModel: ViewModel {
|
|
||||||
|
|
||||||
@Published
|
|
||||||
var modifiedFilters = LibraryFilters()
|
|
||||||
|
|
||||||
@Published
|
|
||||||
var possibleGenres = [NameGuidPair]()
|
|
||||||
@Published
|
|
||||||
var possibleTags = [String]()
|
|
||||||
@Published
|
|
||||||
var possibleSortOrders = APISortOrder.allCases
|
|
||||||
@Published
|
|
||||||
var possibleSortBys = SortBy.allCases
|
|
||||||
@Published
|
|
||||||
var possibleItemFilters = ItemFilter.supportedTypes
|
|
||||||
@Published
|
|
||||||
var enabledFilterType: [FilterType]
|
|
||||||
@Published
|
|
||||||
var selectedSortOrder: APISortOrder = .descending
|
|
||||||
@Published
|
|
||||||
var selectedSortBy: SortBy = .name
|
|
||||||
|
|
||||||
var parentId: String = ""
|
|
||||||
|
|
||||||
func updateModifiedFilter() {
|
|
||||||
modifiedFilters.sortOrder = [selectedSortOrder]
|
|
||||||
modifiedFilters.sortBy = [selectedSortBy]
|
|
||||||
}
|
|
||||||
|
|
||||||
func resetFilters() {
|
|
||||||
modifiedFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], tags: [], sortBy: [.name])
|
|
||||||
}
|
|
||||||
|
|
||||||
init(
|
|
||||||
filters: LibraryFilters? = nil,
|
|
||||||
enabledFilterType: [FilterType] = [.tag, .genre, .sortBy, .sortOrder, .filter],
|
|
||||||
parentId: String
|
|
||||||
) {
|
|
||||||
self.enabledFilterType = enabledFilterType
|
|
||||||
self.selectedSortBy = filters?.sortBy.first ?? .name
|
|
||||||
self.selectedSortOrder = filters?.sortOrder.first ?? .descending
|
|
||||||
self.parentId = parentId
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
if let filters = filters {
|
|
||||||
self.modifiedFilters = filters
|
|
||||||
}
|
|
||||||
requestQueryFilters()
|
|
||||||
}
|
|
||||||
|
|
||||||
func requestQueryFilters() {
|
|
||||||
FilterAPI.getQueryFilters(
|
|
||||||
userId: SessionManager.main.currentLogin.user.id,
|
|
||||||
parentId: self.parentId
|
|
||||||
)
|
|
||||||
.trackActivity(loading)
|
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
|
||||||
self?.handleAPIRequestError(completion: completion)
|
|
||||||
}, receiveValue: { [weak self] queryFilters in
|
|
||||||
guard let self = self else { return }
|
|
||||||
self.possibleGenres = queryFilters.genres ?? []
|
|
||||||
self.possibleTags = queryFilters.tags ?? []
|
|
||||||
})
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,63 +9,61 @@
|
||||||
import Combine
|
import Combine
|
||||||
import Defaults
|
import Defaults
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
import SwiftUI
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
// TODO: Look at refactoring
|
// TODO: Look at refactoring
|
||||||
final class LibraryViewModel: ViewModel {
|
final class LibraryViewModel: ViewModel {
|
||||||
|
|
||||||
@Published
|
|
||||||
var items: [BaseItemDto] = []
|
|
||||||
@Published
|
|
||||||
private var currentPage = 0
|
|
||||||
private var hasNextPage = true
|
|
||||||
@Published
|
|
||||||
var filters: LibraryFilters
|
|
||||||
|
|
||||||
@Default(.Customization.Library.gridPosterType)
|
@Default(.Customization.Library.gridPosterType)
|
||||||
private var libraryGridPosterType
|
private var libraryGridPosterType
|
||||||
|
|
||||||
let library: BaseItemDto?
|
@Published
|
||||||
let person: BaseItemPerson?
|
var items: [BaseItemDto] = []
|
||||||
let genre: NameGuidPair?
|
|
||||||
let studio: NameGuidPair?
|
let filterViewModel: FilterViewModel
|
||||||
|
private var currentPage = 0
|
||||||
|
private var hasNextPage = true
|
||||||
|
|
||||||
|
let parent: LibraryParent?
|
||||||
|
let type: LibraryParentType
|
||||||
|
|
||||||
|
init(filters: ItemFilters) {
|
||||||
|
self.parent = nil
|
||||||
|
self.type = .library
|
||||||
|
self.filterViewModel = .init(parent: nil, currentFilters: filters)
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
filterViewModel.$currentFilters
|
||||||
|
.sink { newFilters in
|
||||||
|
self.requestItemsAsync(with: newFilters, replaceCurrentItems: true)
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(
|
||||||
|
parent: LibraryParent,
|
||||||
|
type: LibraryParentType,
|
||||||
|
filters: ItemFilters = .init()
|
||||||
|
) {
|
||||||
|
self.parent = parent
|
||||||
|
self.type = type
|
||||||
|
self.filterViewModel = .init(parent: parent, currentFilters: filters)
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
filterViewModel.$currentFilters
|
||||||
|
.sink { newFilters in
|
||||||
|
self.requestItemsAsync(with: newFilters, replaceCurrentItems: true)
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
private var pageItemSize: Int {
|
private var pageItemSize: Int {
|
||||||
let height = libraryGridPosterType == .portrait ? libraryGridPosterType.width * 1.5 : libraryGridPosterType.width / 1.77
|
let height = libraryGridPosterType == .portrait ? libraryGridPosterType.width * 1.5 : libraryGridPosterType.width / 1.77
|
||||||
return UIScreen.itemsFillableOnScreen(width: libraryGridPosterType.width, height: height)
|
return UIScreen.itemsFillableOnScreen(width: libraryGridPosterType.width, height: height)
|
||||||
}
|
}
|
||||||
|
|
||||||
var enabledFilterType: [FilterType] {
|
func requestItemsAsync(with filters: ItemFilters, replaceCurrentItems: Bool = false) {
|
||||||
if genre == nil {
|
|
||||||
return [.tag, .genre, .sortBy, .sortOrder, .filter]
|
|
||||||
} else {
|
|
||||||
return [.tag, .sortBy, .sortOrder, .filter]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init(
|
|
||||||
library: BaseItemDto? = nil,
|
|
||||||
person: BaseItemPerson? = nil,
|
|
||||||
genre: NameGuidPair? = nil,
|
|
||||||
studio: NameGuidPair? = nil,
|
|
||||||
filters: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], sortBy: [.name])
|
|
||||||
) {
|
|
||||||
self.library = library
|
|
||||||
self.person = person
|
|
||||||
self.genre = genre
|
|
||||||
self.studio = studio
|
|
||||||
self.filters = filters
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
$filters
|
|
||||||
.sink(receiveValue: { newFilters in
|
|
||||||
self.requestItemsAsync(with: newFilters, replaceCurrentItems: true)
|
|
||||||
})
|
|
||||||
.store(in: &cancellables)
|
|
||||||
}
|
|
||||||
|
|
||||||
func requestItemsAsync(with filters: LibraryFilters, replaceCurrentItems: Bool = false) {
|
|
||||||
|
|
||||||
if replaceCurrentItems {
|
if replaceCurrentItems {
|
||||||
self.items = []
|
self.items = []
|
||||||
|
@ -73,23 +71,26 @@ final class LibraryViewModel: ViewModel {
|
||||||
self.hasNextPage = true
|
self.hasNextPage = true
|
||||||
}
|
}
|
||||||
|
|
||||||
let personIDs: [String] = [person].compactMap(\.?.id)
|
var libraryID: String?
|
||||||
let studioIDs: [String] = [studio].compactMap(\.?.id)
|
var personIDs: [String]?
|
||||||
let genreIDs: [String]
|
var studioIDs: [String]?
|
||||||
|
|
||||||
if filters.withGenres.isEmpty {
|
if let parent = parent {
|
||||||
genreIDs = [genre].compactMap(\.?.id)
|
switch type {
|
||||||
} else {
|
case .library, .folders:
|
||||||
genreIDs = filters.withGenres.compactMap(\.id)
|
libraryID = parent.id
|
||||||
|
case .person:
|
||||||
|
personIDs = [parent].compactMap(\.id)
|
||||||
|
case .studio:
|
||||||
|
studioIDs = [parent].compactMap(\.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let sortBy = filters.sortBy.map(\.rawValue)
|
|
||||||
|
|
||||||
let includeItemTypes: [BaseItemKind]
|
let includeItemTypes: [BaseItemKind]
|
||||||
|
|
||||||
if filters.filters.contains(.isFavorite) {
|
if filters.filters.contains(ItemFilter.isFavorite.filter) {
|
||||||
includeItemTypes = [.movie, .boxSet, .series, .season, .episode]
|
includeItemTypes = [.movie, .boxSet, .series, .season, .episode]
|
||||||
} else if library?.collectionType == "folders" {
|
} else if type == .folders {
|
||||||
includeItemTypes = [.collectionFolder]
|
includeItemTypes = [.collectionFolder]
|
||||||
} else {
|
} else {
|
||||||
includeItemTypes = [.movie, .series, .boxSet]
|
includeItemTypes = [.movie, .series, .boxSet]
|
||||||
|
@ -97,26 +98,31 @@ final class LibraryViewModel: ViewModel {
|
||||||
|
|
||||||
let excludedIDs: [String]?
|
let excludedIDs: [String]?
|
||||||
|
|
||||||
if filters.sortBy == [.random] {
|
if filters.sortBy.first == SortBy.random.filter {
|
||||||
excludedIDs = items.compactMap(\.id)
|
excludedIDs = items.compactMap(\.id)
|
||||||
} else {
|
} else {
|
||||||
excludedIDs = nil
|
excludedIDs = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let genreIDs = filters.genres.compactMap(\.id)
|
||||||
|
let sortBy: [String] = filters.sortBy.map(\.filterName)
|
||||||
|
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(
|
ItemsAPI.getItemsByUserId(
|
||||||
userId: SessionManager.main.currentLogin.user.id,
|
userId: SessionManager.main.currentLogin.user.id,
|
||||||
excludeItemIds: excludedIDs,
|
excludeItemIds: excludedIDs,
|
||||||
startIndex: currentPage * pageItemSize,
|
startIndex: currentPage * pageItemSize,
|
||||||
limit: pageItemSize,
|
limit: pageItemSize,
|
||||||
recursive: true,
|
recursive: true,
|
||||||
searchTerm: nil,
|
sortOrder: sortOrder,
|
||||||
sortOrder: filters.sortOrder.compactMap { SortOrder(rawValue: $0.rawValue) },
|
parentId: libraryID,
|
||||||
parentId: library?.id,
|
|
||||||
fields: ItemFields.allCases,
|
fields: ItemFields.allCases,
|
||||||
includeItemTypes: includeItemTypes,
|
includeItemTypes: includeItemTypes,
|
||||||
filters: filters.filters,
|
filters: itemFilters,
|
||||||
sortBy: sortBy,
|
sortBy: sortBy,
|
||||||
tags: filters.tags,
|
tags: tags,
|
||||||
enableUserData: true,
|
enableUserData: true,
|
||||||
personIds: personIDs,
|
personIds: personIDs,
|
||||||
studioIds: studioIDs,
|
studioIds: studioIDs,
|
||||||
|
@ -139,7 +145,7 @@ final class LibraryViewModel: ViewModel {
|
||||||
// excluded ids. This causes shorter item additions when using "Random" over
|
// excluded ids. This causes shorter item additions when using "Random" over
|
||||||
// consecutive calls. Investigation needs to be done to find the root of the problem.
|
// consecutive calls. Investigation needs to be done to find the root of the problem.
|
||||||
// Only filter for "Random" as an optimization.
|
// Only filter for "Random" as an optimization.
|
||||||
if filters.sortBy == [.random] {
|
if filters.sortBy.first == SortBy.random.filter {
|
||||||
items = response.items?.filter { !(self?.items.contains($0) ?? true) } ?? []
|
items = response.items?.filter { !(self?.items.contains($0) ?? true) } ?? []
|
||||||
} else {
|
} else {
|
||||||
items = response.items ?? []
|
items = response.items ?? []
|
||||||
|
@ -153,7 +159,7 @@ final class LibraryViewModel: ViewModel {
|
||||||
func requestNextPageAsync() {
|
func requestNextPageAsync() {
|
||||||
guard hasNextPage else { return }
|
guard hasNextPage else { return }
|
||||||
currentPage += 1
|
currentPage += 1
|
||||||
requestItemsAsync(with: filters)
|
requestItemsAsync(with: filterViewModel.currentFilters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,6 @@ import SwiftUI
|
||||||
|
|
||||||
final class SearchViewModel: ViewModel {
|
final class SearchViewModel: ViewModel {
|
||||||
|
|
||||||
private var searchCancellables = Set<AnyCancellable>()
|
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var movies: [BaseItemDto] = []
|
var movies: [BaseItemDto] = []
|
||||||
@Published
|
@Published
|
||||||
|
@ -28,6 +26,10 @@ final class SearchViewModel: ViewModel {
|
||||||
@Published
|
@Published
|
||||||
var suggestions: [BaseItemDto] = []
|
var suggestions: [BaseItemDto] = []
|
||||||
|
|
||||||
|
let filterViewModel: FilterViewModel
|
||||||
|
private var searchTextSubject = CurrentValueSubject<String, Never>("")
|
||||||
|
private var searchCancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
var noResults: Bool {
|
var noResults: Bool {
|
||||||
movies.isEmpty &&
|
movies.isEmpty &&
|
||||||
collections.isEmpty &&
|
collections.isEmpty &&
|
||||||
|
@ -36,9 +38,8 @@ final class SearchViewModel: ViewModel {
|
||||||
people.isEmpty
|
people.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
private var searchTextSubject = CurrentValueSubject<String, Never>("")
|
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
|
self.filterViewModel = .init(parent: nil, currentFilters: .init())
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
getSuggestions()
|
getSuggestions()
|
||||||
|
@ -47,7 +48,15 @@ final class SearchViewModel: ViewModel {
|
||||||
.handleEvents(receiveOutput: { _ in self.cancelPreviousSearch() })
|
.handleEvents(receiveOutput: { _ in self.cancelPreviousSearch() })
|
||||||
.filter { !$0.isEmpty }
|
.filter { !$0.isEmpty }
|
||||||
.debounce(for: 0.25, scheduler: DispatchQueue.main)
|
.debounce(for: 0.25, scheduler: DispatchQueue.main)
|
||||||
.sink(receiveValue: _search)
|
.sink { newSearch in
|
||||||
|
self._search(with: newSearch, filters: self.filterViewModel.currentFilters)
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
filterViewModel.$currentFilters
|
||||||
|
.sink { newFilters in
|
||||||
|
self._search(with: self.searchTextSubject.value, filters: newFilters)
|
||||||
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,29 +68,39 @@ final class SearchViewModel: ViewModel {
|
||||||
searchTextSubject.send(query)
|
searchTextSubject.send(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func _search(with query: String) {
|
private func _search(with query: String, filters: ItemFilters) {
|
||||||
getItems(with: query, for: .movie, keyPath: \.movies)
|
getItems(for: query, with: filters, type: .movie, keyPath: \.movies)
|
||||||
getItems(with: query, for: .boxSet, keyPath: \.collections)
|
getItems(for: query, with: filters, type: .boxSet, keyPath: \.collections)
|
||||||
getItems(with: query, for: .series, keyPath: \.series)
|
getItems(for: query, with: filters, type: .series, keyPath: \.series)
|
||||||
getItems(with: query, for: .episode, keyPath: \.episodes)
|
getItems(for: query, with: filters, type: .episode, keyPath: \.episodes)
|
||||||
getPeople(with: query)
|
getPeople(for: query, with: filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getItems(
|
private func getItems(
|
||||||
with query: String,
|
for query: String,
|
||||||
for itemType: BaseItemKind,
|
with filters: ItemFilters,
|
||||||
|
type itemType: BaseItemKind,
|
||||||
keyPath: ReferenceWritableKeyPath<SearchViewModel, [BaseItemDto]>
|
keyPath: ReferenceWritableKeyPath<SearchViewModel, [BaseItemDto]>
|
||||||
) {
|
) {
|
||||||
|
let genreIDs = filters.genres.compactMap(\.id)
|
||||||
|
let sortBy: [String] = filters.sortBy.map(\.filterName)
|
||||||
|
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(
|
ItemsAPI.getItemsByUserId(
|
||||||
userId: SessionManager.main.currentLogin.user.id,
|
userId: SessionManager.main.currentLogin.user.id,
|
||||||
limit: 20,
|
limit: 20,
|
||||||
recursive: true,
|
recursive: true,
|
||||||
searchTerm: query,
|
searchTerm: query,
|
||||||
sortOrder: [.ascending],
|
sortOrder: sortOrder,
|
||||||
fields: ItemFields.allCases,
|
fields: ItemFields.allCases,
|
||||||
includeItemTypes: [itemType],
|
includeItemTypes: [itemType],
|
||||||
sortBy: ["SortName"],
|
filters: itemFilters,
|
||||||
|
sortBy: sortBy,
|
||||||
|
tags: tags,
|
||||||
enableUserData: true,
|
enableUserData: true,
|
||||||
|
genreIds: genreIDs,
|
||||||
enableImages: true
|
enableImages: true
|
||||||
)
|
)
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
|
@ -93,7 +112,12 @@ final class SearchViewModel: ViewModel {
|
||||||
.store(in: &searchCancellables)
|
.store(in: &searchCancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getPeople(with query: String) {
|
private func getPeople(for query: String?, with filters: ItemFilters) {
|
||||||
|
guard !filters.hasFilters else {
|
||||||
|
self.people = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
PersonsAPI.getPersons(
|
PersonsAPI.getPersons(
|
||||||
limit: 20,
|
limit: 20,
|
||||||
searchTerm: query
|
searchTerm: query
|
||||||
|
|
|
@ -99,7 +99,7 @@ final class UserSignInViewModel: ViewModel {
|
||||||
|
|
||||||
self.quickConnectSecret = response.secret
|
self.quickConnectSecret = response.secret
|
||||||
self.quickConnectCode = response.code
|
self.quickConnectCode = response.code
|
||||||
LogManager.log.debug("QuickConnect code: \(response.code ?? "--")")
|
LogManager.log.debug("QuickConnect code: \(response.code ?? .emptyDash)")
|
||||||
|
|
||||||
self.quickConnectTimer = RepeatingTimer(interval: 5) {
|
self.quickConnectTimer = RepeatingTimer(interval: 5) {
|
||||||
self.checkAuthStatus(onSuccess)
|
self.checkAuthStatus(onSuccess)
|
||||||
|
|
|
@ -14,9 +14,9 @@ import JellyfinAPI
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
import TVVLCKit
|
import TVVLCKit
|
||||||
#else
|
#else
|
||||||
import MobileVLCKit
|
import MobileVLCKit
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
final class VideoPlayerViewModel: ViewModel {
|
final class VideoPlayerViewModel: ViewModel {
|
||||||
|
|
|
@ -1,75 +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 SwiftUI
|
|
||||||
|
|
||||||
private struct MultiSelectionView<Selectable: Hashable>: View {
|
|
||||||
let options: [Selectable]
|
|
||||||
let optionToString: (Selectable) -> String
|
|
||||||
let label: String
|
|
||||||
|
|
||||||
@Binding
|
|
||||||
var selected: [Selectable]
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
List {
|
|
||||||
ForEach(options, id: \.self) { selectable in
|
|
||||||
Button(action: { toggleSelection(selectable: selectable) }) {
|
|
||||||
HStack {
|
|
||||||
Text(optionToString(selectable)).foregroundColor(Color.primary)
|
|
||||||
Spacer()
|
|
||||||
if selected.contains { $0 == selectable } {
|
|
||||||
Image(systemName: "checkmark").foregroundColor(.accentColor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.tag(selectable)
|
|
||||||
}
|
|
||||||
}.listStyle(GroupedListStyle())
|
|
||||||
}
|
|
||||||
|
|
||||||
private func toggleSelection(selectable: Selectable) {
|
|
||||||
if let existingIndex = selected.firstIndex(where: { $0 == selectable }) {
|
|
||||||
selected.remove(at: existingIndex)
|
|
||||||
} else {
|
|
||||||
selected.append(selectable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MultiSelector<Selectable: Hashable>: View {
|
|
||||||
let label: String
|
|
||||||
let options: [Selectable]
|
|
||||||
let optionToString: (Selectable) -> String
|
|
||||||
|
|
||||||
var selected: Binding<[Selectable]>
|
|
||||||
|
|
||||||
private var formattedSelectedListString: String {
|
|
||||||
ListFormatter.localizedString(byJoining: selected.wrappedValue.map { optionToString($0) })
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
NavigationLink(destination: multiSelectionView()) {
|
|
||||||
HStack {
|
|
||||||
Text(label)
|
|
||||||
Spacer()
|
|
||||||
Text(formattedSelectedListString)
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
.multilineTextAlignment(.trailing)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func multiSelectionView() -> some View {
|
|
||||||
MultiSelectionView(
|
|
||||||
options: options,
|
|
||||||
optionToString: optionToString,
|
|
||||||
label: self.label,
|
|
||||||
selected: selected
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 SwiftUI
|
||||||
|
|
||||||
|
// TODO: Implement different behavior types, where selected/unselected
|
||||||
|
// items appear in different sections
|
||||||
|
struct SelectorView<Item: Displayable>: View {
|
||||||
|
|
||||||
|
private let allItems: [Item]
|
||||||
|
@Binding
|
||||||
|
private var selectedItems: [Item]
|
||||||
|
private let type: SelectorType
|
||||||
|
|
||||||
|
init(type: SelectorType, allItems: [Item], selectedItems: Binding<[Item]>) {
|
||||||
|
self.type = type
|
||||||
|
self.allItems = allItems
|
||||||
|
self._selectedItems = selectedItems
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
List {
|
||||||
|
ForEach(allItems, id: \.displayName) { item in
|
||||||
|
Button {
|
||||||
|
switch type {
|
||||||
|
case .single:
|
||||||
|
handleSingleSelect(with: item)
|
||||||
|
case .multi:
|
||||||
|
handleMultiSelect(with: item)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
HStack {
|
||||||
|
Text(item.displayName)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
if selectedItems.contains { $0.displayName == item.displayName } {
|
||||||
|
Image(systemName: "checkmark.circle.fill")
|
||||||
|
.foregroundColor(.jellyfinPurple)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleSingleSelect(with item: Item) {
|
||||||
|
selectedItems = [item]
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleMultiSelect(with item: Item) {
|
||||||
|
if selectedItems.contains(where: { $0.displayName == item.displayName }) {
|
||||||
|
selectedItems.removeAll(where: { $0.displayName == item.displayName })
|
||||||
|
} else {
|
||||||
|
selectedItems.append(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -59,17 +59,17 @@ struct TruncatedTextView: View {
|
||||||
|
|
||||||
if truncated {
|
if truncated {
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
|
Text(seeMoreText)
|
||||||
|
.font(font)
|
||||||
|
.foregroundColor(.purple)
|
||||||
|
#else
|
||||||
|
Button {
|
||||||
|
seeMoreAction()
|
||||||
|
} label: {
|
||||||
Text(seeMoreText)
|
Text(seeMoreText)
|
||||||
.font(font)
|
.font(font)
|
||||||
.foregroundColor(.purple)
|
.foregroundColor(.purple)
|
||||||
#else
|
}
|
||||||
Button {
|
|
||||||
seeMoreAction()
|
|
||||||
} label: {
|
|
||||||
Text(seeMoreText)
|
|
||||||
.font(font)
|
|
||||||
.foregroundColor(.purple)
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -209,7 +209,7 @@ struct PosterButtonDefaultContentView<Item: Poster>: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
if item.showTitle {
|
if item.showTitle {
|
||||||
Text(item.title)
|
Text(item.displayName)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.fontWeight(.regular)
|
.fontWeight(.regular)
|
||||||
.foregroundColor(.primary)
|
.foregroundColor(.primary)
|
||||||
|
|
|
@ -30,7 +30,7 @@ struct BasicAppSettingsView: View {
|
||||||
HStack {
|
HStack {
|
||||||
L10n.version.text
|
L10n.version.text
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("\(UIApplication.appVersion ?? "--") (\(UIApplication.bundleVersion ?? "--"))")
|
Text("\(UIApplication.appVersion ?? .emptyDash) (\(UIApplication.bundleVersion ?? .emptyDash))")
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ struct ContinueWatchingCard: View {
|
||||||
.frame(width: 500, alignment: .leading)
|
.frame(width: 500, alignment: .leading)
|
||||||
|
|
||||||
if item.type == .episode {
|
if item.type == .episode {
|
||||||
Text(item.episodeLocator ?? "--")
|
Text(item.episodeLocator ?? .emptyDash)
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.fontWeight(.medium)
|
.fontWeight(.medium)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
|
|
@ -30,7 +30,7 @@ struct ItemView: View {
|
||||||
case .boxSet:
|
case .boxSet:
|
||||||
CollectionItemView(viewModel: .init(item: item))
|
CollectionItemView(viewModel: .init(item: item))
|
||||||
case .person:
|
case .person:
|
||||||
LibraryView(viewModel: .init(person: .init(id: item.id)))
|
LibraryView(viewModel: .init(parent: item, type: .person, filters: .init()))
|
||||||
default:
|
default:
|
||||||
Text(L10n.notImplementedYetWithType(item.type ?? "--"))
|
Text(L10n.notImplementedYetWithType(item.type ?? "--"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,17 +20,7 @@ struct LatestInLibraryView: View {
|
||||||
PosterHStack(title: L10n.latestWithString(viewModel.library.displayName), type: .portrait, items: viewModel.items)
|
PosterHStack(title: L10n.latestWithString(viewModel.library.displayName), type: .portrait, items: viewModel.items)
|
||||||
.trailing {
|
.trailing {
|
||||||
Button {
|
Button {
|
||||||
router.route(to: \.library, (
|
router.route(to: \.library, .init(parent: viewModel.library, type: .library, filters: .recent))
|
||||||
viewModel: .init(
|
|
||||||
library: viewModel.library,
|
|
||||||
filters: LibraryFilters(
|
|
||||||
filters: [],
|
|
||||||
sortOrder: [.descending],
|
|
||||||
sortBy: [.dateAdded]
|
|
||||||
)
|
|
||||||
),
|
|
||||||
title: viewModel.library.displayName
|
|
||||||
))
|
|
||||||
} label: {
|
} label: {
|
||||||
ZStack {
|
ZStack {
|
||||||
Color(UIColor.darkGray)
|
Color(UIColor.darkGray)
|
||||||
|
|
|
@ -1,104 +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 Stinsen
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct LibraryFilterView: View {
|
|
||||||
|
|
||||||
@EnvironmentObject
|
|
||||||
private var filterRouter: FilterCoordinator.Router
|
|
||||||
@Binding
|
|
||||||
var filters: LibraryFilters
|
|
||||||
var parentId: String = ""
|
|
||||||
|
|
||||||
@StateObject
|
|
||||||
var viewModel: LibraryFilterViewModel
|
|
||||||
|
|
||||||
init(filters: Binding<LibraryFilters>, enabledFilterType: [FilterType], parentId: String) {
|
|
||||||
_filters = filters
|
|
||||||
self.parentId = parentId
|
|
||||||
_viewModel =
|
|
||||||
StateObject(wrappedValue: .init(filters: filters.wrappedValue, enabledFilterType: enabledFilterType, parentId: parentId))
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack {
|
|
||||||
if viewModel.isLoading {
|
|
||||||
ProgressView()
|
|
||||||
} else {
|
|
||||||
Form {
|
|
||||||
if viewModel.enabledFilterType.contains(.genre) {
|
|
||||||
MultiSelector(
|
|
||||||
label: L10n.genres,
|
|
||||||
options: viewModel.possibleGenres,
|
|
||||||
optionToString: { $0.name ?? "" },
|
|
||||||
selected: $viewModel.modifiedFilters.withGenres
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if viewModel.enabledFilterType.contains(.filter) {
|
|
||||||
MultiSelector(
|
|
||||||
label: L10n.filters,
|
|
||||||
options: viewModel.possibleItemFilters,
|
|
||||||
optionToString: { $0.localized },
|
|
||||||
selected: $viewModel.modifiedFilters.filters
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if viewModel.enabledFilterType.contains(.tag) {
|
|
||||||
MultiSelector(
|
|
||||||
label: L10n.tags,
|
|
||||||
options: viewModel.possibleTags,
|
|
||||||
optionToString: { $0 },
|
|
||||||
selected: $viewModel.modifiedFilters.tags
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if viewModel.enabledFilterType.contains(.sortBy) {
|
|
||||||
Picker(selection: $viewModel.selectedSortBy, label: L10n.sortBy.text) {
|
|
||||||
ForEach(viewModel.possibleSortBys, id: \.self) { so in
|
|
||||||
Text(so.localized).tag(so)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if viewModel.enabledFilterType.contains(.sortOrder) {
|
|
||||||
Picker(selection: $viewModel.selectedSortOrder, label: L10n.displayOrder.text) {
|
|
||||||
ForEach(viewModel.possibleSortOrders, id: \.self) { so in
|
|
||||||
Text(so.rawValue).tag(so)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
viewModel.resetFilters()
|
|
||||||
self.filters = viewModel.modifiedFilters
|
|
||||||
filterRouter.dismissCoordinator()
|
|
||||||
} label: {
|
|
||||||
L10n.reset.text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.toolbar {
|
|
||||||
ToolbarItemGroup(placement: .navigationBarLeading) {
|
|
||||||
Button {
|
|
||||||
filterRouter.dismissCoordinator()
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "xmark")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
|
||||||
Button {
|
|
||||||
viewModel.updateModifiedFilter()
|
|
||||||
self.filters = viewModel.modifiedFilters
|
|
||||||
filterRouter.dismissCoordinator()
|
|
||||||
} label: {
|
|
||||||
L10n.apply.text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -21,7 +21,7 @@ struct LibraryView: View {
|
||||||
private var scrollViewOffset: CGPoint = .zero
|
private var scrollViewOffset: CGPoint = .zero
|
||||||
|
|
||||||
@Default(.Customization.Library.gridPosterType)
|
@Default(.Customization.Library.gridPosterType)
|
||||||
var libraryPosterType
|
private var libraryPosterType
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var loadingView: some View {
|
private var loadingView: some View {
|
||||||
|
|
|
@ -27,11 +27,13 @@ struct MediaView: View {
|
||||||
.onSelect { _ in
|
.onSelect { _ in
|
||||||
switch item.library.collectionType {
|
switch item.library.collectionType {
|
||||||
case "favorites":
|
case "favorites":
|
||||||
router.route(to: \.library, (viewModel: .init(filters: .favorites), title: ""))
|
router.route(to: \.library, .init(parent: item.library, type: .library, filters: .favorites))
|
||||||
|
case "folders":
|
||||||
|
router.route(to: \.library, .init(parent: item.library, type: .folders, filters: .init()))
|
||||||
case "liveTV":
|
case "liveTV":
|
||||||
tabRouter.root(\.liveTV)
|
tabRouter.root(\.liveTV)
|
||||||
default:
|
default:
|
||||||
router.route(to: \.library, (viewModel: .init(library: item.library), title: ""))
|
router.route(to: \.library, .init(parent: item.library, type: .library, filters: .init()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.imageOverlay { _ in
|
.imageOverlay { _ in
|
||||||
|
|
|
@ -142,7 +142,7 @@ struct SettingsView: View {
|
||||||
HStack {
|
HStack {
|
||||||
L10n.version.text
|
L10n.version.text
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("\(UIApplication.appVersion ?? "--") (\(UIApplication.bundleVersion ?? "--"))")
|
Text("\(UIApplication.appVersion ?? .emptyDash) (\(UIApplication.bundleVersion ?? .emptyDash))")
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -476,11 +476,11 @@ extension LiveTVPlayerViewController {
|
||||||
viewModel = newViewModel
|
viewModel = newViewModel
|
||||||
|
|
||||||
if viewModel.streamType == .direct {
|
if viewModel.streamType == .direct {
|
||||||
LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? "--")")
|
LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
} else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] {
|
} else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] {
|
||||||
LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? "--")")
|
LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
} else {
|
} else {
|
||||||
LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? "--")")
|
LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -476,11 +476,11 @@ extension VLCPlayerViewController {
|
||||||
viewModel = newViewModel
|
viewModel = newViewModel
|
||||||
|
|
||||||
if viewModel.streamType == .direct {
|
if viewModel.streamType == .direct {
|
||||||
LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? "--")")
|
LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
} else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] {
|
} else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] {
|
||||||
LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? "--")")
|
LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
} else {
|
} else {
|
||||||
LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? "--")")
|
LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,7 @@
|
||||||
53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */; };
|
53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */; };
|
||||||
531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531AC8BE26750DE20091C7EB /* ImageView.swift */; };
|
531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531AC8BE26750DE20091C7EB /* ImageView.swift */; };
|
||||||
5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5321753A2671BCFC005491E6 /* SettingsViewModel.swift */; };
|
5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5321753A2671BCFC005491E6 /* SettingsViewModel.swift */; };
|
||||||
5321753E2671DE9C005491E6 /* Typings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535870AC2669D8DD00D05A09 /* Typings.swift */; };
|
5321753E2671DE9C005491E6 /* ItemFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535870AC2669D8DD00D05A09 /* ItemFilters.swift */; };
|
||||||
532175402671EE4F005491E6 /* LibraryFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E4E646263F6CF100F67C6B /* LibraryFilterView.swift */; };
|
|
||||||
53352571265EA0A0006CCA86 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 53352570265EA0A0006CCA86 /* Introspect */; };
|
53352571265EA0A0006CCA86 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 53352570265EA0A0006CCA86 /* Introspect */; };
|
||||||
5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5338F74D263B61370014BF09 /* ConnectToServerView.swift */; };
|
5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5338F74D263B61370014BF09 /* ConnectToServerView.swift */; };
|
||||||
534D4FF026A7D7CC000A7A48 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 534D4FEE26A7D7CC000A7A48 /* Localizable.strings */; };
|
534D4FF026A7D7CC000A7A48 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 534D4FEE26A7D7CC000A7A48 /* Localizable.strings */; };
|
||||||
|
@ -34,7 +33,7 @@
|
||||||
5358707E2669D64F00D05A09 /* bitrates.json in Resources */ = {isa = PBXBuildFile; fileRef = AE8C3158265D6F90008AA076 /* bitrates.json */; };
|
5358707E2669D64F00D05A09 /* bitrates.json in Resources */ = {isa = PBXBuildFile; fileRef = AE8C3158265D6F90008AA076 /* bitrates.json */; };
|
||||||
535870912669D7A800D05A09 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 535870902669D7A800D05A09 /* Introspect */; };
|
535870912669D7A800D05A09 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 535870902669D7A800D05A09 /* Introspect */; };
|
||||||
535870A82669D8AE00D05A09 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621338922660107500A81A2A /* StringExtensions.swift */; };
|
535870A82669D8AE00D05A09 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621338922660107500A81A2A /* StringExtensions.swift */; };
|
||||||
535870AD2669D8DD00D05A09 /* Typings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535870AC2669D8DD00D05A09 /* Typings.swift */; };
|
535870AD2669D8DD00D05A09 /* ItemFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535870AC2669D8DD00D05A09 /* ItemFilters.swift */; };
|
||||||
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535BAE9E2649E569005FA86D /* ItemView.swift */; };
|
535BAE9F2649E569005FA86D /* ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535BAE9E2649E569005FA86D /* ItemView.swift */; };
|
||||||
53649AB1269CFB1900A2D8B7 /* LogManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53649AB0269CFB1900A2D8B7 /* LogManager.swift */; };
|
53649AB1269CFB1900A2D8B7 /* LogManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53649AB0269CFB1900A2D8B7 /* LogManager.swift */; };
|
||||||
53649AB2269D019100A2D8B7 /* LogManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53649AB0269CFB1900A2D8B7 /* LogManager.swift */; };
|
53649AB2269D019100A2D8B7 /* LogManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53649AB0269CFB1900A2D8B7 /* LogManager.swift */; };
|
||||||
|
@ -169,13 +168,10 @@
|
||||||
62E632EA267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632E8267D3FF50063E547 /* SeasonItemViewModel.swift */; };
|
62E632EA267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632E8267D3FF50063E547 /* SeasonItemViewModel.swift */; };
|
||||||
62E632EC267D410B0063E547 /* SeriesItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */; };
|
62E632EC267D410B0063E547 /* SeriesItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */; };
|
||||||
62E632ED267D410B0063E547 /* SeriesItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */; };
|
62E632ED267D410B0063E547 /* SeriesItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */; };
|
||||||
62E632EF267D43320063E547 /* LibraryFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */; };
|
|
||||||
62E632F0267D43320063E547 /* LibraryFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */; };
|
|
||||||
62E632F3267D54030063E547 /* ItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632F2267D54030063E547 /* ItemViewModel.swift */; };
|
62E632F3267D54030063E547 /* ItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632F2267D54030063E547 /* ItemViewModel.swift */; };
|
||||||
62E632F4267D54030063E547 /* ItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632F2267D54030063E547 /* ItemViewModel.swift */; };
|
62E632F4267D54030063E547 /* ItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632F2267D54030063E547 /* ItemViewModel.swift */; };
|
||||||
62EC352F267666A5000E9F2D /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352E267666A5000E9F2D /* SessionManager.swift */; };
|
62EC352F267666A5000E9F2D /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352E267666A5000E9F2D /* SessionManager.swift */; };
|
||||||
62EC3530267666A5000E9F2D /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352E267666A5000E9F2D /* SessionManager.swift */; };
|
62EC3530267666A5000E9F2D /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC352E267666A5000E9F2D /* SessionManager.swift */; };
|
||||||
62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */; };
|
|
||||||
62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62ECA01726FA685A00E8EBB7 /* DeepLink.swift */; };
|
62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62ECA01726FA685A00E8EBB7 /* DeepLink.swift */; };
|
||||||
6334175B287DDFB9000603CE /* QuickConnectSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6334175A287DDFB9000603CE /* QuickConnectSettingsView.swift */; };
|
6334175B287DDFB9000603CE /* QuickConnectSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6334175A287DDFB9000603CE /* QuickConnectSettingsView.swift */; };
|
||||||
6334175D287DE0D0000603CE /* QuickConnectSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6334175C287DE0D0000603CE /* QuickConnectSettingsViewModel.swift */; };
|
6334175D287DE0D0000603CE /* QuickConnectSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6334175C287DE0D0000603CE /* QuickConnectSettingsViewModel.swift */; };
|
||||||
|
@ -243,6 +239,14 @@
|
||||||
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 */; };
|
||||||
|
E113132B28BDB4B500930F75 /* NavBarDrawerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113132A28BDB4B500930F75 /* NavBarDrawerView.swift */; };
|
||||||
|
E113132F28BDB66A00930F75 /* NavBarDrawerModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113132E28BDB66A00930F75 /* NavBarDrawerModifier.swift */; };
|
||||||
|
E113133228BDC72000930F75 /* FilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113133128BDC72000930F75 /* FilterView.swift */; };
|
||||||
|
E113133428BE988200930F75 /* FilterDrawerHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113133328BE988200930F75 /* FilterDrawerHStack.swift */; };
|
||||||
|
E113133628BE98AA00930F75 /* FilterDrawerButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113133528BE98AA00930F75 /* FilterDrawerButton.swift */; };
|
||||||
|
E113133828BEADBA00930F75 /* LibraryParent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113133728BEADBA00930F75 /* LibraryParent.swift */; };
|
||||||
|
E113133A28BEB71D00930F75 /* FilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113133928BEB71D00930F75 /* FilterViewModel.swift */; };
|
||||||
|
E113133B28BEB71D00930F75 /* FilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E113133928BEB71D00930F75 /* FilterViewModel.swift */; };
|
||||||
E1171A1928A2212600FA1AF5 /* QuickConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1171A1828A2212600FA1AF5 /* QuickConnectView.swift */; };
|
E1171A1928A2212600FA1AF5 /* QuickConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1171A1828A2212600FA1AF5 /* QuickConnectView.swift */; };
|
||||||
E118959D289312020042947B /* BaseItemPerson+Poster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E118959C289312020042947B /* BaseItemPerson+Poster.swift */; };
|
E118959D289312020042947B /* BaseItemPerson+Poster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E118959C289312020042947B /* BaseItemPerson+Poster.swift */; };
|
||||||
E118959E289312020042947B /* BaseItemPerson+Poster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E118959C289312020042947B /* BaseItemPerson+Poster.swift */; };
|
E118959E289312020042947B /* BaseItemPerson+Poster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E118959C289312020042947B /* BaseItemPerson+Poster.swift */; };
|
||||||
|
@ -268,6 +272,7 @@
|
||||||
E1267D3E271A1F46003C492E /* PreferenceUIHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1267D3D271A1F46003C492E /* PreferenceUIHostingController.swift */; };
|
E1267D3E271A1F46003C492E /* PreferenceUIHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1267D3D271A1F46003C492E /* PreferenceUIHostingController.swift */; };
|
||||||
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 */; };
|
||||||
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 */; };
|
||||||
|
@ -309,6 +314,13 @@
|
||||||
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 */; };
|
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 */; };
|
||||||
|
E148128528C15472003B8787 /* APISortOrderExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E148128428C15472003B8787 /* APISortOrderExtensions.swift */; };
|
||||||
|
E148128628C15475003B8787 /* APISortOrderExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E148128428C15472003B8787 /* APISortOrderExtensions.swift */; };
|
||||||
|
E148128828C154BF003B8787 /* ItemFilterExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E148128728C154BF003B8787 /* ItemFilterExtensions.swift */; };
|
||||||
|
E148128928C154BF003B8787 /* ItemFilterExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E148128728C154BF003B8787 /* ItemFilterExtensions.swift */; };
|
||||||
|
E148128B28C15526003B8787 /* SortBy.swift in Sources */ = {isa = PBXBuildFile; fileRef = E148128A28C15526003B8787 /* SortBy.swift */; };
|
||||||
|
E148128C28C15526003B8787 /* SortBy.swift in Sources */ = {isa = PBXBuildFile; fileRef = E148128A28C15526003B8787 /* SortBy.swift */; };
|
||||||
E1546777289AF46E00087E35 /* CollectionItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1546776289AF46E00087E35 /* CollectionItemView.swift */; };
|
E1546777289AF46E00087E35 /* CollectionItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1546776289AF46E00087E35 /* CollectionItemView.swift */; };
|
||||||
E154677A289AF48200087E35 /* CollectionItemContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1546779289AF48200087E35 /* CollectionItemContentView.swift */; };
|
E154677A289AF48200087E35 /* CollectionItemContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1546779289AF48200087E35 /* CollectionItemContentView.swift */; };
|
||||||
E168BD10289A4162001A6922 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E168BD08289A4162001A6922 /* HomeView.swift */; };
|
E168BD10289A4162001A6922 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E168BD08289A4162001A6922 /* HomeView.swift */; };
|
||||||
|
@ -328,6 +340,14 @@
|
||||||
E178859E2780F53B0094FBCF /* SliderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E178859D2780F53B0094FBCF /* SliderView.swift */; };
|
E178859E2780F53B0094FBCF /* SliderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E178859D2780F53B0094FBCF /* SliderView.swift */; };
|
||||||
E17885A02780F55C0094FBCF /* tvOSVLCOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = E178859F2780F55C0094FBCF /* tvOSVLCOverlay.swift */; };
|
E17885A02780F55C0094FBCF /* tvOSVLCOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = E178859F2780F55C0094FBCF /* tvOSVLCOverlay.swift */; };
|
||||||
E17885A4278105170094FBCF /* SFSymbolButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E17885A3278105170094FBCF /* SFSymbolButton.swift */; };
|
E17885A4278105170094FBCF /* SFSymbolButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E17885A3278105170094FBCF /* SFSymbolButton.swift */; };
|
||||||
|
E17FB54F28C1197700311DFE /* SelectorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E17FB54E28C1197700311DFE /* SelectorType.swift */; };
|
||||||
|
E17FB55028C1197700311DFE /* SelectorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E17FB54E28C1197700311DFE /* SelectorType.swift */; };
|
||||||
|
E17FB55228C119D400311DFE /* Displayable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E17FB55128C119D400311DFE /* Displayable.swift */; };
|
||||||
|
E17FB55328C119D400311DFE /* Displayable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E17FB55128C119D400311DFE /* Displayable.swift */; };
|
||||||
|
E17FB55528C1250B00311DFE /* SimilarItemsHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E17FB55428C1250B00311DFE /* SimilarItemsHStack.swift */; };
|
||||||
|
E17FB55728C1256400311DFE /* CastAndCrewHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E17FB55628C1256400311DFE /* CastAndCrewHStack.swift */; };
|
||||||
|
E17FB55928C125E900311DFE /* StudiosHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E17FB55828C125E900311DFE /* StudiosHStack.swift */; };
|
||||||
|
E17FB55B28C1266400311DFE /* GenresHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E17FB55A28C1266400311DFE /* GenresHStack.swift */; };
|
||||||
E184C160288C5C08000B25BA /* RequestBuilderExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E184C15F288C5C08000B25BA /* RequestBuilderExtensions.swift */; };
|
E184C160288C5C08000B25BA /* RequestBuilderExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E184C15F288C5C08000B25BA /* RequestBuilderExtensions.swift */; };
|
||||||
E184C161288C5C08000B25BA /* RequestBuilderExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E184C15F288C5C08000B25BA /* RequestBuilderExtensions.swift */; };
|
E184C161288C5C08000B25BA /* RequestBuilderExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E184C15F288C5C08000B25BA /* RequestBuilderExtensions.swift */; };
|
||||||
E18845F526DD631E00B0C5B7 /* BaseItemDto+Poster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18845F426DD631E00B0C5B7 /* BaseItemDto+Poster.swift */; };
|
E18845F526DD631E00B0C5B7 /* BaseItemDto+Poster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18845F426DD631E00B0C5B7 /* BaseItemDto+Poster.swift */; };
|
||||||
|
@ -387,8 +407,6 @@
|
||||||
E1937A3F288F0D3D00CB80AA /* UIScreenExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1937A3D288F0D3D00CB80AA /* UIScreenExtensions.swift */; };
|
E1937A3F288F0D3D00CB80AA /* UIScreenExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1937A3D288F0D3D00CB80AA /* UIScreenExtensions.swift */; };
|
||||||
E1937A61288F32DB00CB80AA /* Poster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1937A60288F32DB00CB80AA /* Poster.swift */; };
|
E1937A61288F32DB00CB80AA /* Poster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1937A60288F32DB00CB80AA /* Poster.swift */; };
|
||||||
E1937A62288F32DB00CB80AA /* Poster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1937A60288F32DB00CB80AA /* Poster.swift */; };
|
E1937A62288F32DB00CB80AA /* Poster.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1937A60288F32DB00CB80AA /* Poster.swift */; };
|
||||||
E193D4DB27193CCA00900D82 /* PillStackable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E193D4DA27193CCA00900D82 /* PillStackable.swift */; };
|
|
||||||
E193D4DC27193CCA00900D82 /* PillStackable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E193D4DA27193CCA00900D82 /* PillStackable.swift */; };
|
|
||||||
E193D53227193F7B00900D82 /* ConnectToServerCoodinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA226D1030F00C1D2E7 /* ConnectToServerCoodinator.swift */; };
|
E193D53227193F7B00900D82 /* ConnectToServerCoodinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA226D1030F00C1D2E7 /* ConnectToServerCoodinator.swift */; };
|
||||||
E193D53327193F7D00900D82 /* FilterCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0B926D6092100B8E046 /* FilterCoordinator.swift */; };
|
E193D53327193F7D00900D82 /* FilterCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0B926D6092100B8E046 /* FilterCoordinator.swift */; };
|
||||||
E193D53427193F7F00900D82 /* HomeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA526D1036A00C1D2E7 /* HomeCoordinator.swift */; };
|
E193D53427193F7F00900D82 /* HomeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA526D1036A00C1D2E7 /* HomeCoordinator.swift */; };
|
||||||
|
@ -404,7 +422,6 @@
|
||||||
E193D547271941C500900D82 /* UserListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E193D546271941C500900D82 /* UserListView.swift */; };
|
E193D547271941C500900D82 /* UserListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E193D546271941C500900D82 /* UserListView.swift */; };
|
||||||
E193D549271941CC00900D82 /* UserSignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E193D548271941CC00900D82 /* UserSignInView.swift */; };
|
E193D549271941CC00900D82 /* UserSignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E193D548271941CC00900D82 /* UserSignInView.swift */; };
|
||||||
E193D54B271941D300900D82 /* ServerListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E193D54A271941D300900D82 /* ServerListView.swift */; };
|
E193D54B271941D300900D82 /* ServerListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E193D54A271941D300900D82 /* ServerListView.swift */; };
|
||||||
E193D54D2719426600900D82 /* LibraryFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E193D54C2719426600900D82 /* LibraryFilterView.swift */; };
|
|
||||||
E193D5502719430400900D82 /* ServerDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E193D54F2719430400900D82 /* ServerDetailView.swift */; };
|
E193D5502719430400900D82 /* ServerDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E193D54F2719430400900D82 /* ServerDetailView.swift */; };
|
||||||
E193D5512719432400900D82 /* ServerDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA5326D050F500CC4EB7 /* ServerDetailViewModel.swift */; };
|
E193D5512719432400900D82 /* ServerDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA5326D050F500CC4EB7 /* ServerDetailViewModel.swift */; };
|
||||||
E193D553271943D500900D82 /* tvOSMainTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E193D552271943D500900D82 /* tvOSMainTabCoordinator.swift */; };
|
E193D553271943D500900D82 /* tvOSMainTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E193D552271943D500900D82 /* tvOSMainTabCoordinator.swift */; };
|
||||||
|
@ -479,8 +496,8 @@
|
||||||
E1E00A35278628A40022235B /* DoubleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E00A34278628A40022235B /* DoubleExtensions.swift */; };
|
E1E00A35278628A40022235B /* DoubleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E00A34278628A40022235B /* DoubleExtensions.swift */; };
|
||||||
E1E00A36278628A40022235B /* DoubleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E00A34278628A40022235B /* DoubleExtensions.swift */; };
|
E1E00A36278628A40022235B /* DoubleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E00A34278628A40022235B /* DoubleExtensions.swift */; };
|
||||||
E1E1643A28BAC2EF00323B0A /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1643928BAC2EF00323B0A /* SearchView.swift */; };
|
E1E1643A28BAC2EF00323B0A /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1643928BAC2EF00323B0A /* SearchView.swift */; };
|
||||||
E1E1643E28BB074000323B0A /* MultiSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1643D28BB074000323B0A /* MultiSelectorView.swift */; };
|
E1E1643E28BB074000323B0A /* SelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1643D28BB074000323B0A /* SelectorView.swift */; };
|
||||||
E1E1643F28BB075C00323B0A /* MultiSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1643D28BB074000323B0A /* MultiSelectorView.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 /* LibraryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E1644328BC60C600323B0A /* LibraryItem.swift */; };
|
||||||
|
@ -575,7 +592,7 @@
|
||||||
535870662669D21700D05A09 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
535870662669D21700D05A09 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
535870692669D21700D05A09 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
535870692669D21700D05A09 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||||
535870702669D21700D05A09 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
535870702669D21700D05A09 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
535870AC2669D8DD00D05A09 /* Typings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Typings.swift; sourceTree = "<group>"; };
|
535870AC2669D8DD00D05A09 /* ItemFilters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemFilters.swift; sourceTree = "<group>"; };
|
||||||
535BAE9E2649E569005FA86D /* ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemView.swift; sourceTree = "<group>"; };
|
535BAE9E2649E569005FA86D /* ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemView.swift; sourceTree = "<group>"; };
|
||||||
5362E4A7267D4067000E2F71 /* GoogleCast.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GoogleCast.framework; path = "../../Downloads/GoogleCastSDK-ios-4.6.0_dynamic/GoogleCast.framework"; sourceTree = "<group>"; };
|
5362E4A7267D4067000E2F71 /* GoogleCast.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GoogleCast.framework; path = "../../Downloads/GoogleCastSDK-ios-4.6.0_dynamic/GoogleCast.framework"; sourceTree = "<group>"; };
|
||||||
5362E4AA267D40AD000E2F71 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
|
5362E4AA267D40AD000E2F71 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
|
||||||
|
@ -622,7 +639,6 @@
|
||||||
53ABFDEA2679753200886593 /* ConnectToServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerView.swift; sourceTree = "<group>"; };
|
53ABFDEA2679753200886593 /* ConnectToServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerView.swift; sourceTree = "<group>"; };
|
||||||
53CD2A3F268A49C2002ABD4E /* ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemView.swift; sourceTree = "<group>"; };
|
53CD2A3F268A49C2002ABD4E /* ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemView.swift; sourceTree = "<group>"; };
|
||||||
53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MobileVLCKit.xcframework; path = Carthage/Build/MobileVLCKit.xcframework; sourceTree = "<group>"; };
|
53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MobileVLCKit.xcframework; path = Carthage/Build/MobileVLCKit.xcframework; sourceTree = "<group>"; };
|
||||||
53E4E646263F6CF100F67C6B /* LibraryFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryFilterView.swift; sourceTree = "<group>"; };
|
|
||||||
53EE24E5265060780068F029 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
|
53EE24E5265060780068F029 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
|
||||||
5D1603FB278A3D5700D22B99 /* SubtitleSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubtitleSize.swift; sourceTree = "<group>"; };
|
5D1603FB278A3D5700D22B99 /* SubtitleSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubtitleSize.swift; sourceTree = "<group>"; };
|
||||||
5D160402278A41FD00D22B99 /* VLCPlayer+subtitles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VLCPlayer+subtitles.swift"; sourceTree = "<group>"; };
|
5D160402278A41FD00D22B99 /* VLCPlayer+subtitles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VLCPlayer+subtitles.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -687,10 +703,8 @@
|
||||||
62E632E5267D3F5B0063E547 /* EpisodeItemViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EpisodeItemViewModel.swift; sourceTree = "<group>"; };
|
62E632E5267D3F5B0063E547 /* EpisodeItemViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EpisodeItemViewModel.swift; sourceTree = "<group>"; };
|
||||||
62E632E8267D3FF50063E547 /* SeasonItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeasonItemViewModel.swift; sourceTree = "<group>"; };
|
62E632E8267D3FF50063E547 /* SeasonItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeasonItemViewModel.swift; sourceTree = "<group>"; };
|
||||||
62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeriesItemViewModel.swift; sourceTree = "<group>"; };
|
62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeriesItemViewModel.swift; sourceTree = "<group>"; };
|
||||||
62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryFilterViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
62E632F2267D54030063E547 /* ItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemViewModel.swift; sourceTree = "<group>"; };
|
62E632F2267D54030063E547 /* ItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemViewModel.swift; sourceTree = "<group>"; };
|
||||||
62EC352E267666A5000E9F2D /* SessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = "<group>"; };
|
62EC352E267666A5000E9F2D /* SessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = "<group>"; };
|
||||||
62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRotationViewModifier.swift; sourceTree = "<group>"; };
|
|
||||||
62ECA01726FA685A00E8EBB7 /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = "<group>"; };
|
62ECA01726FA685A00E8EBB7 /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = "<group>"; };
|
||||||
6334175A287DDFB9000603CE /* QuickConnectSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickConnectSettingsView.swift; sourceTree = "<group>"; };
|
6334175A287DDFB9000603CE /* QuickConnectSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickConnectSettingsView.swift; sourceTree = "<group>"; };
|
||||||
6334175C287DE0D0000603CE /* QuickConnectSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickConnectSettingsViewModel.swift; sourceTree = "<group>"; };
|
6334175C287DE0D0000603CE /* QuickConnectSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickConnectSettingsViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
@ -740,6 +754,13 @@
|
||||||
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>"; };
|
||||||
|
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>"; };
|
||||||
|
E113133128BDC72000930F75 /* FilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterView.swift; sourceTree = "<group>"; };
|
||||||
|
E113133328BE988200930F75 /* FilterDrawerHStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterDrawerHStack.swift; sourceTree = "<group>"; };
|
||||||
|
E113133528BE98AA00930F75 /* FilterDrawerButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterDrawerButton.swift; sourceTree = "<group>"; };
|
||||||
|
E113133728BEADBA00930F75 /* LibraryParent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryParent.swift; sourceTree = "<group>"; };
|
||||||
|
E113133928BEB71D00930F75 /* FilterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterViewModel.swift; sourceTree = "<group>"; };
|
||||||
E1171A1828A2212600FA1AF5 /* QuickConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickConnectView.swift; sourceTree = "<group>"; };
|
E1171A1828A2212600FA1AF5 /* QuickConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickConnectView.swift; sourceTree = "<group>"; };
|
||||||
E118959C289312020042947B /* BaseItemPerson+Poster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BaseItemPerson+Poster.swift"; sourceTree = "<group>"; };
|
E118959C289312020042947B /* BaseItemPerson+Poster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BaseItemPerson+Poster.swift"; sourceTree = "<group>"; };
|
||||||
E11895A8289383BC0042947B /* ScrollViewOffsetModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollViewOffsetModifier.swift; sourceTree = "<group>"; };
|
E11895A8289383BC0042947B /* ScrollViewOffsetModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollViewOffsetModifier.swift; sourceTree = "<group>"; };
|
||||||
|
@ -777,6 +798,9 @@
|
||||||
E13F05EB28BC9000003499D2 /* LibraryViewType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibraryViewType.swift; sourceTree = "<group>"; };
|
E13F05EB28BC9000003499D2 /* LibraryViewType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibraryViewType.swift; sourceTree = "<group>"; };
|
||||||
E13F05EF28BC9016003499D2 /* LibraryItemRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibraryItemRow.swift; sourceTree = "<group>"; };
|
E13F05EF28BC9016003499D2 /* LibraryItemRow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibraryItemRow.swift; sourceTree = "<group>"; };
|
||||||
E13F05F028BC9016003499D2 /* LibraryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibraryView.swift; sourceTree = "<group>"; };
|
E13F05F028BC9016003499D2 /* LibraryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LibraryView.swift; sourceTree = "<group>"; };
|
||||||
|
E148128428C15472003B8787 /* APISortOrderExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APISortOrderExtensions.swift; sourceTree = "<group>"; };
|
||||||
|
E148128728C154BF003B8787 /* ItemFilterExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemFilterExtensions.swift; sourceTree = "<group>"; };
|
||||||
|
E148128A28C15526003B8787 /* SortBy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortBy.swift; sourceTree = "<group>"; };
|
||||||
E1546776289AF46E00087E35 /* CollectionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionItemView.swift; sourceTree = "<group>"; };
|
E1546776289AF46E00087E35 /* CollectionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionItemView.swift; sourceTree = "<group>"; };
|
||||||
E1546779289AF48200087E35 /* CollectionItemContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionItemContentView.swift; sourceTree = "<group>"; };
|
E1546779289AF48200087E35 /* CollectionItemContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionItemContentView.swift; sourceTree = "<group>"; };
|
||||||
E168BD08289A4162001A6922 /* HomeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
|
E168BD08289A4162001A6922 /* HomeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -793,6 +817,12 @@
|
||||||
E178859D2780F53B0094FBCF /* SliderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderView.swift; sourceTree = "<group>"; };
|
E178859D2780F53B0094FBCF /* SliderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderView.swift; sourceTree = "<group>"; };
|
||||||
E178859F2780F55C0094FBCF /* tvOSVLCOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = tvOSVLCOverlay.swift; sourceTree = "<group>"; };
|
E178859F2780F55C0094FBCF /* tvOSVLCOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = tvOSVLCOverlay.swift; sourceTree = "<group>"; };
|
||||||
E17885A3278105170094FBCF /* SFSymbolButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SFSymbolButton.swift; sourceTree = "<group>"; };
|
E17885A3278105170094FBCF /* SFSymbolButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SFSymbolButton.swift; sourceTree = "<group>"; };
|
||||||
|
E17FB54E28C1197700311DFE /* SelectorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectorType.swift; sourceTree = "<group>"; };
|
||||||
|
E17FB55128C119D400311DFE /* Displayable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Displayable.swift; sourceTree = "<group>"; };
|
||||||
|
E17FB55428C1250B00311DFE /* SimilarItemsHStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimilarItemsHStack.swift; sourceTree = "<group>"; };
|
||||||
|
E17FB55628C1256400311DFE /* CastAndCrewHStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CastAndCrewHStack.swift; sourceTree = "<group>"; };
|
||||||
|
E17FB55828C125E900311DFE /* StudiosHStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudiosHStack.swift; sourceTree = "<group>"; };
|
||||||
|
E17FB55A28C1266400311DFE /* GenresHStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenresHStack.swift; sourceTree = "<group>"; };
|
||||||
E184C15F288C5C08000B25BA /* RequestBuilderExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestBuilderExtensions.swift; sourceTree = "<group>"; };
|
E184C15F288C5C08000B25BA /* RequestBuilderExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestBuilderExtensions.swift; sourceTree = "<group>"; };
|
||||||
E18845F426DD631E00B0C5B7 /* BaseItemDto+Poster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BaseItemDto+Poster.swift"; sourceTree = "<group>"; };
|
E18845F426DD631E00B0C5B7 /* BaseItemDto+Poster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BaseItemDto+Poster.swift"; sourceTree = "<group>"; };
|
||||||
E18CE0AE28A222240092E7F1 /* PublicUserSignInView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PublicUserSignInView.swift; sourceTree = "<group>"; };
|
E18CE0AE28A222240092E7F1 /* PublicUserSignInView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PublicUserSignInView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -841,12 +871,10 @@
|
||||||
E1937A3D288F0D3D00CB80AA /* UIScreenExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIScreenExtensions.swift; sourceTree = "<group>"; };
|
E1937A3D288F0D3D00CB80AA /* UIScreenExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIScreenExtensions.swift; sourceTree = "<group>"; };
|
||||||
E1937A60288F32DB00CB80AA /* Poster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Poster.swift; sourceTree = "<group>"; };
|
E1937A60288F32DB00CB80AA /* Poster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Poster.swift; sourceTree = "<group>"; };
|
||||||
E1937A63288F683300CB80AA /* ContinueWatchingCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueWatchingCard.swift; sourceTree = "<group>"; };
|
E1937A63288F683300CB80AA /* ContinueWatchingCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueWatchingCard.swift; sourceTree = "<group>"; };
|
||||||
E193D4DA27193CCA00900D82 /* PillStackable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillStackable.swift; sourceTree = "<group>"; };
|
|
||||||
E193D5422719407E00900D82 /* tvOSMainCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = tvOSMainCoordinator.swift; sourceTree = "<group>"; };
|
E193D5422719407E00900D82 /* tvOSMainCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = tvOSMainCoordinator.swift; sourceTree = "<group>"; };
|
||||||
E193D546271941C500900D82 /* UserListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserListView.swift; sourceTree = "<group>"; };
|
E193D546271941C500900D82 /* UserListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserListView.swift; sourceTree = "<group>"; };
|
||||||
E193D548271941CC00900D82 /* UserSignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSignInView.swift; sourceTree = "<group>"; };
|
E193D548271941CC00900D82 /* UserSignInView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSignInView.swift; sourceTree = "<group>"; };
|
||||||
E193D54A271941D300900D82 /* ServerListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListView.swift; sourceTree = "<group>"; };
|
E193D54A271941D300900D82 /* ServerListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListView.swift; sourceTree = "<group>"; };
|
||||||
E193D54C2719426600900D82 /* LibraryFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryFilterView.swift; sourceTree = "<group>"; };
|
|
||||||
E193D54F2719430400900D82 /* ServerDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailView.swift; sourceTree = "<group>"; };
|
E193D54F2719430400900D82 /* ServerDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailView.swift; sourceTree = "<group>"; };
|
||||||
E193D552271943D500900D82 /* tvOSMainTabCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = tvOSMainTabCoordinator.swift; sourceTree = "<group>"; };
|
E193D552271943D500900D82 /* tvOSMainTabCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = tvOSMainTabCoordinator.swift; sourceTree = "<group>"; };
|
||||||
E19E551E2897326C003CE330 /* BottomEdgeGradientModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomEdgeGradientModifier.swift; sourceTree = "<group>"; };
|
E19E551E2897326C003CE330 /* BottomEdgeGradientModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomEdgeGradientModifier.swift; sourceTree = "<group>"; };
|
||||||
|
@ -902,7 +930,7 @@
|
||||||
E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicAppSettingsView.swift; sourceTree = "<group>"; };
|
E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicAppSettingsView.swift; sourceTree = "<group>"; };
|
||||||
E1E00A34278628A40022235B /* DoubleExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoubleExtensions.swift; sourceTree = "<group>"; };
|
E1E00A34278628A40022235B /* DoubleExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoubleExtensions.swift; sourceTree = "<group>"; };
|
||||||
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 /* MultiSelectorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultiSelectorView.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 /* LibraryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryItem.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>"; };
|
||||||
|
@ -1038,10 +1066,10 @@
|
||||||
E1D4BF7D2719D1DC00A11E64 /* BasicAppSettingsViewModel.swift */,
|
E1D4BF7D2719D1DC00A11E64 /* BasicAppSettingsViewModel.swift */,
|
||||||
625CB5762678C34300530A6E /* ConnectToServerViewModel.swift */,
|
625CB5762678C34300530A6E /* ConnectToServerViewModel.swift */,
|
||||||
E10D87E127852FD000BD264C /* EpisodesRowManager.swift */,
|
E10D87E127852FD000BD264C /* EpisodesRowManager.swift */,
|
||||||
|
E113133928BEB71D00930F75 /* FilterViewModel.swift */,
|
||||||
625CB5722678C32A00530A6E /* HomeViewModel.swift */,
|
625CB5722678C32A00530A6E /* HomeViewModel.swift */,
|
||||||
E107BB9127880A4000354E07 /* ItemViewModel */,
|
E107BB9127880A4000354E07 /* ItemViewModel */,
|
||||||
62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */,
|
62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */,
|
||||||
62E632EE267D43320063E547 /* LibraryFilterViewModel.swift */,
|
|
||||||
62E632DF267D30CA0063E547 /* LibraryViewModel.swift */,
|
62E632DF267D30CA0063E547 /* LibraryViewModel.swift */,
|
||||||
C4BE07842728446F003F4AD1 /* LiveTVChannelsViewModel.swift */,
|
C4BE07842728446F003F4AD1 /* LiveTVChannelsViewModel.swift */,
|
||||||
C4BE07752725EBEA003F4AD1 /* LiveTVProgramsViewModel.swift */,
|
C4BE07752725EBEA003F4AD1 /* LiveTVProgramsViewModel.swift */,
|
||||||
|
@ -1155,21 +1183,23 @@
|
||||||
E1D4BF802719D22800A11E64 /* AppAppearance.swift */,
|
E1D4BF802719D22800A11E64 /* AppAppearance.swift */,
|
||||||
E1D4BF862719D27100A11E64 /* Bitrates.swift */,
|
E1D4BF862719D27100A11E64 /* Bitrates.swift */,
|
||||||
53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */,
|
53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */,
|
||||||
62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */,
|
E17FB55128C119D400311DFE /* Displayable.swift */,
|
||||||
E19169CD272514760085832A /* HTTPScheme.swift */,
|
E19169CD272514760085832A /* HTTPScheme.swift */,
|
||||||
|
535870AC2669D8DD00D05A09 /* ItemFilters.swift */,
|
||||||
E1C925F328875037002A7A66 /* ItemViewType.swift */,
|
E1C925F328875037002A7A66 /* ItemViewType.swift */,
|
||||||
E1E1644328BC60C600323B0A /* LibraryItem.swift */,
|
E1E1644328BC60C600323B0A /* LibraryItem.swift */,
|
||||||
|
E113133728BEADBA00930F75 /* LibraryParent.swift */,
|
||||||
E13F05EB28BC9000003499D2 /* LibraryViewType.swift */,
|
E13F05EB28BC9000003499D2 /* LibraryViewType.swift */,
|
||||||
E1AA331E2782639D00F6439C /* OverlayType.swift */,
|
E1AA331E2782639D00F6439C /* OverlayType.swift */,
|
||||||
E1C925F62887504B002A7A66 /* PanDirectionGestureRecognizer.swift */,
|
E1C925F62887504B002A7A66 /* PanDirectionGestureRecognizer.swift */,
|
||||||
E193D4DA27193CCA00900D82 /* PillStackable.swift */,
|
|
||||||
E1C812B4277A8E5D00918266 /* PlaybackSpeed.swift */,
|
E1C812B4277A8E5D00918266 /* PlaybackSpeed.swift */,
|
||||||
E1937A60288F32DB00CB80AA /* Poster.swift */,
|
E1937A60288F32DB00CB80AA /* Poster.swift */,
|
||||||
E1CCF12D28ABF989006CAC9E /* PosterType.swift */,
|
E1CCF12D28ABF989006CAC9E /* PosterType.swift */,
|
||||||
E18CE0B328A22EDA0092E7F1 /* RepeatingTimer.swift */,
|
E18CE0B328A22EDA0092E7F1 /* RepeatingTimer.swift */,
|
||||||
|
E17FB54E28C1197700311DFE /* SelectorType.swift */,
|
||||||
|
E148128A28C15526003B8787 /* SortBy.swift */,
|
||||||
5D1603FB278A3D5700D22B99 /* SubtitleSize.swift */,
|
5D1603FB278A3D5700D22B99 /* SubtitleSize.swift */,
|
||||||
E1D4BF832719D25A00A11E64 /* TrackLanguage.swift */,
|
E1D4BF832719D25A00A11E64 /* TrackLanguage.swift */,
|
||||||
535870AC2669D8DD00D05A09 /* Typings.swift */,
|
|
||||||
E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */,
|
E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */,
|
||||||
);
|
);
|
||||||
path = Objects;
|
path = Objects;
|
||||||
|
@ -1558,6 +1588,15 @@
|
||||||
path = ItemViewModel;
|
path = ItemViewModel;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
E113133028BDB6D600930F75 /* NavBarDrawerButtons */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E113132E28BDB66A00930F75 /* NavBarDrawerModifier.swift */,
|
||||||
|
E113132A28BDB4B500930F75 /* NavBarDrawerView.swift */,
|
||||||
|
);
|
||||||
|
path = NavBarDrawerButtons;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
E1171A1A28A2215800FA1AF5 /* UserSignInView */ = {
|
E1171A1A28A2215800FA1AF5 /* UserSignInView */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -1578,13 +1617,13 @@
|
||||||
path = ViewExtensions;
|
path = ViewExtensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
E11895B12893842D0042947B /* NavBarOffsetModifier */ = {
|
E11895B12893842D0042947B /* NavBarOffset */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E11895AB289383EE0042947B /* NavBarOffsetModifier.swift */,
|
E11895AB289383EE0042947B /* NavBarOffsetModifier.swift */,
|
||||||
E11895AE2893840F0042947B /* NavBarOffsetView.swift */,
|
E11895AE2893840F0042947B /* NavBarOffsetView.swift */,
|
||||||
);
|
);
|
||||||
path = NavBarOffsetModifier;
|
path = NavBarOffset;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
E11CEB85289984F5003E74C7 /* Extensions */ = {
|
E11CEB85289984F5003E74C7 /* Extensions */ = {
|
||||||
|
@ -1598,8 +1637,9 @@
|
||||||
E11CEB8828998522003E74C7 /* iOSViewExtensions */ = {
|
E11CEB8828998522003E74C7 /* iOSViewExtensions */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E11895B12893842D0042947B /* NavBarOffsetModifier */,
|
|
||||||
E11CEB8A28998552003E74C7 /* iOSViewExtensions.swift */,
|
E11CEB8A28998552003E74C7 /* iOSViewExtensions.swift */,
|
||||||
|
E113133028BDB6D600930F75 /* NavBarDrawerButtons */,
|
||||||
|
E11895B12893842D0042947B /* NavBarOffset */,
|
||||||
);
|
);
|
||||||
path = iOSViewExtensions;
|
path = iOSViewExtensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1631,7 +1671,6 @@
|
||||||
531690E6267ABD79005D8AB9 /* HomeView.swift */,
|
531690E6267ABD79005D8AB9 /* HomeView.swift */,
|
||||||
E193D54E271942C000900D82 /* ItemView */,
|
E193D54E271942C000900D82 /* ItemView */,
|
||||||
E1C925F828875647002A7A66 /* LatestInLibraryView.swift */,
|
E1C925F828875647002A7A66 /* LatestInLibraryView.swift */,
|
||||||
E193D54C2719426600900D82 /* LibraryFilterView.swift */,
|
|
||||||
53A83C32268A309300DF3D92 /* LibraryView.swift */,
|
53A83C32268A309300DF3D92 /* LibraryView.swift */,
|
||||||
C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */,
|
C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */,
|
||||||
C4BE078A272844AF003F4AD1 /* LiveTVChannelsView.swift */,
|
C4BE078A272844AF003F4AD1 /* LiveTVChannelsView.swift */,
|
||||||
|
@ -1689,7 +1728,7 @@
|
||||||
E168BD07289A4162001A6922 /* HomeView */,
|
E168BD07289A4162001A6922 /* HomeView */,
|
||||||
E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */,
|
E1EBCB45278BD595009FE6E9 /* ItemOverviewView.swift */,
|
||||||
E14F7D0A26DB3714007C3AE6 /* ItemView */,
|
E14F7D0A26DB3714007C3AE6 /* ItemView */,
|
||||||
53E4E646263F6CF100F67C6B /* LibraryFilterView.swift */,
|
E113133128BDC72000930F75 /* FilterView.swift */,
|
||||||
E13F05EE28BC9016003499D2 /* LibraryView */,
|
E13F05EE28BC9016003499D2 /* LibraryView */,
|
||||||
C4E5598828124C10003DECA5 /* LiveTVChannelItemElement.swift */,
|
C4E5598828124C10003DECA5 /* LiveTVChannelItemElement.swift */,
|
||||||
C400DB6C27FE8E65007B65FE /* LiveTVChannelItemWideElement.swift */,
|
C400DB6C27FE8E65007B65FE /* LiveTVChannelItemWideElement.swift */,
|
||||||
|
@ -1848,8 +1887,8 @@
|
||||||
E18E01BD288747230022598C /* MovieItemView */ = {
|
E18E01BD288747230022598C /* MovieItemView */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E18E01BE288747230022598C /* iPadOSMovieItemView.swift */,
|
|
||||||
E18E01BF288747230022598C /* iPadOSMovieItemContentView.swift */,
|
E18E01BF288747230022598C /* iPadOSMovieItemContentView.swift */,
|
||||||
|
E18E01BE288747230022598C /* iPadOSMovieItemView.swift */,
|
||||||
);
|
);
|
||||||
path = MovieItemView;
|
path = MovieItemView;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1888,8 +1927,8 @@
|
||||||
E18E01C8288747230022598C /* CollectionItemView */ = {
|
E18E01C8288747230022598C /* CollectionItemView */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E18E01C9288747230022598C /* CollectionItemView.swift */,
|
|
||||||
E18E01CA288747230022598C /* CollectionItemContentView.swift */,
|
E18E01CA288747230022598C /* CollectionItemContentView.swift */,
|
||||||
|
E18E01C9288747230022598C /* CollectionItemView.swift */,
|
||||||
);
|
);
|
||||||
path = CollectionItemView;
|
path = CollectionItemView;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1915,12 +1954,16 @@
|
||||||
E18E01D4288747230022598C /* Components */ = {
|
E18E01D4288747230022598C /* Components */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E176DE6E278E3522001EFD8D /* EpisodesRowView */,
|
|
||||||
E18E01D5288747230022598C /* AboutView.swift */,
|
E18E01D5288747230022598C /* AboutView.swift */,
|
||||||
E18E01D6288747230022598C /* ListDetailsView.swift */,
|
|
||||||
E18E01D7288747230022598C /* AttributeHStack.swift */,
|
|
||||||
E18E01D8288747230022598C /* PlayButton.swift */,
|
|
||||||
E18E01D9288747230022598C /* ActionButtonHStack.swift */,
|
E18E01D9288747230022598C /* ActionButtonHStack.swift */,
|
||||||
|
E18E01D7288747230022598C /* AttributeHStack.swift */,
|
||||||
|
E17FB55628C1256400311DFE /* CastAndCrewHStack.swift */,
|
||||||
|
E176DE6E278E3522001EFD8D /* EpisodesRowView */,
|
||||||
|
E17FB55A28C1266400311DFE /* GenresHStack.swift */,
|
||||||
|
E18E01D6288747230022598C /* ListDetailsView.swift */,
|
||||||
|
E18E01D8288747230022598C /* PlayButton.swift */,
|
||||||
|
E17FB55428C1250B00311DFE /* SimilarItemsHStack.swift */,
|
||||||
|
E17FB55828C125E900311DFE /* StudiosHStack.swift */,
|
||||||
);
|
);
|
||||||
path = Components;
|
path = Components;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1984,6 +2027,7 @@
|
||||||
E1AD105226D96D5F003E4A08 /* JellyfinAPIExtensions */ = {
|
E1AD105226D96D5F003E4A08 /* JellyfinAPIExtensions */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E148128428C15472003B8787 /* APISortOrderExtensions.swift */,
|
||||||
E1937A3A288E54AD00CB80AA /* BaseItemDto+Images.swift */,
|
E1937A3A288E54AD00CB80AA /* BaseItemDto+Images.swift */,
|
||||||
E18845F426DD631E00B0C5B7 /* BaseItemDto+Poster.swift */,
|
E18845F426DD631E00B0C5B7 /* BaseItemDto+Poster.swift */,
|
||||||
E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */,
|
E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */,
|
||||||
|
@ -1994,6 +2038,7 @@
|
||||||
E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */,
|
E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */,
|
||||||
E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */,
|
E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */,
|
||||||
E1AD105E26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift */,
|
E1AD105E26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift */,
|
||||||
|
E148128728C154BF003B8787 /* ItemFilterExtensions.swift */,
|
||||||
E184C15F288C5C08000B25BA /* RequestBuilderExtensions.swift */,
|
E184C15F288C5C08000B25BA /* RequestBuilderExtensions.swift */,
|
||||||
E18CE0B128A229E70092E7F1 /* UserDtoExtensions.swift */,
|
E18CE0B128A229E70092E7F1 /* UserDtoExtensions.swift */,
|
||||||
);
|
);
|
||||||
|
@ -2010,8 +2055,8 @@
|
||||||
E18E01FF288749200022598C /* Divider.swift */,
|
E18E01FF288749200022598C /* Divider.swift */,
|
||||||
531AC8BE26750DE20091C7EB /* ImageView.swift */,
|
531AC8BE26750DE20091C7EB /* ImageView.swift */,
|
||||||
E1047E2227E5880000CB0D4A /* InitialFailureView.swift */,
|
E1047E2227E5880000CB0D4A /* InitialFailureView.swift */,
|
||||||
E1E1643D28BB074000323B0A /* MultiSelectorView.swift */,
|
|
||||||
531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */,
|
531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */,
|
||||||
|
E1E1643D28BB074000323B0A /* SelectorView.swift */,
|
||||||
E1EBCB41278BD174009FE6E9 /* TruncatedTextView.swift */,
|
E1EBCB41278BD174009FE6E9 /* TruncatedTextView.swift */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
|
@ -2029,6 +2074,8 @@
|
||||||
E1C55AB228BD051700A9AD88 /* Components */ = {
|
E1C55AB228BD051700A9AD88 /* Components */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E113133528BE98AA00930F75 /* FilterDrawerButton.swift */,
|
||||||
|
E113133328BE988200930F75 /* FilterDrawerHStack.swift */,
|
||||||
E13F05EF28BC9016003499D2 /* LibraryItemRow.swift */,
|
E13F05EF28BC9016003499D2 /* LibraryItemRow.swift */,
|
||||||
);
|
);
|
||||||
path = Components;
|
path = Components;
|
||||||
|
@ -2394,7 +2441,6 @@
|
||||||
E193D53627193F8500900D82 /* LibraryCoordinator.swift in Sources */,
|
E193D53627193F8500900D82 /* LibraryCoordinator.swift in Sources */,
|
||||||
C4BE07742725EB66003F4AD1 /* LiveTVProgramsView.swift in Sources */,
|
C4BE07742725EB66003F4AD1 /* LiveTVProgramsView.swift in Sources */,
|
||||||
E18845F626DD631E00B0C5B7 /* BaseItemDto+Poster.swift in Sources */,
|
E18845F626DD631E00B0C5B7 /* BaseItemDto+Poster.swift in Sources */,
|
||||||
E193D4DC27193CCA00900D82 /* PillStackable.swift in Sources */,
|
|
||||||
E193D53327193F7D00900D82 /* FilterCoordinator.swift in Sources */,
|
E193D53327193F7D00900D82 /* FilterCoordinator.swift in Sources */,
|
||||||
E103A6A9278AB6FF00820EC7 /* CinematicNextUpCardView.swift in Sources */,
|
E103A6A9278AB6FF00820EC7 /* CinematicNextUpCardView.swift in Sources */,
|
||||||
C4B9B91427E1921B0063535C /* LiveTVNativeVideoPlayerView.swift in Sources */,
|
C4B9B91427E1921B0063535C /* LiveTVNativeVideoPlayerView.swift in Sources */,
|
||||||
|
@ -2408,7 +2454,8 @@
|
||||||
E1A16CA1288A7CFD00EA4679 /* AboutViewCard.swift in Sources */,
|
E1A16CA1288A7CFD00EA4679 /* AboutViewCard.swift in Sources */,
|
||||||
E1937A3F288F0D3D00CB80AA /* UIScreenExtensions.swift in Sources */,
|
E1937A3F288F0D3D00CB80AA /* UIScreenExtensions.swift in Sources */,
|
||||||
531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */,
|
531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */,
|
||||||
E1E1643E28BB074000323B0A /* MultiSelectorView.swift in Sources */,
|
E17FB55328C119D400311DFE /* Displayable.swift in Sources */,
|
||||||
|
E1E1643E28BB074000323B0A /* SelectorView.swift in Sources */,
|
||||||
E11D224327378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */,
|
E11D224327378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */,
|
||||||
5D32EA12278C95E30020E292 /* VLCPlayer+subtitles.swift in Sources */,
|
5D32EA12278C95E30020E292 /* VLCPlayer+subtitles.swift in Sources */,
|
||||||
E1D4BF8B2719D3D000A11E64 /* BasicAppSettingsCoordinator.swift in Sources */,
|
E1D4BF8B2719D3D000A11E64 /* BasicAppSettingsCoordinator.swift in Sources */,
|
||||||
|
@ -2432,6 +2479,7 @@
|
||||||
E178859E2780F53B0094FBCF /* SliderView.swift in Sources */,
|
E178859E2780F53B0094FBCF /* SliderView.swift in Sources */,
|
||||||
E1384944278036C70024FB48 /* VLCPlayerViewController.swift in Sources */,
|
E1384944278036C70024FB48 /* VLCPlayerViewController.swift in Sources */,
|
||||||
E1002B652793CEE800E47059 /* ChapterInfoExtensions.swift in Sources */,
|
E1002B652793CEE800E47059 /* ChapterInfoExtensions.swift in Sources */,
|
||||||
|
E12B835F28C07D8500878399 /* LibraryParent.swift in Sources */,
|
||||||
E103A6A5278A82E500820EC7 /* HomeCinematicView.swift in Sources */,
|
E103A6A5278A82E500820EC7 /* HomeCinematicView.swift in Sources */,
|
||||||
E18E021A2887492B0022598C /* AppIcon.swift in Sources */,
|
E18E021A2887492B0022598C /* AppIcon.swift in Sources */,
|
||||||
E10EAA50277BBCC4000269ED /* CGSizeExtensions.swift in Sources */,
|
E10EAA50277BBCC4000269ED /* CGSizeExtensions.swift in Sources */,
|
||||||
|
@ -2458,7 +2506,6 @@
|
||||||
53A83C33268A309300DF3D92 /* LibraryView.swift in Sources */,
|
53A83C33268A309300DF3D92 /* LibraryView.swift in Sources */,
|
||||||
62E632ED267D410B0063E547 /* SeriesItemViewModel.swift in Sources */,
|
62E632ED267D410B0063E547 /* SeriesItemViewModel.swift in Sources */,
|
||||||
5398514526B64DA100101B49 /* SettingsView.swift in Sources */,
|
5398514526B64DA100101B49 /* SettingsView.swift in Sources */,
|
||||||
62E632F0267D43320063E547 /* LibraryFilterViewModel.swift in Sources */,
|
|
||||||
E193D54B271941D300900D82 /* ServerListView.swift in Sources */,
|
E193D54B271941D300900D82 /* ServerListView.swift in Sources */,
|
||||||
53ABFDE6267974EF00886593 /* SettingsViewModel.swift in Sources */,
|
53ABFDE6267974EF00886593 /* SettingsViewModel.swift in Sources */,
|
||||||
C400DB6B27FE8C97007B65FE /* LiveTVChannelItemElement.swift in Sources */,
|
C400DB6B27FE8C97007B65FE /* LiveTVChannelItemElement.swift in Sources */,
|
||||||
|
@ -2474,13 +2521,14 @@
|
||||||
E103A6A0278A7E4500820EC7 /* UICinematicBackgroundView.swift in Sources */,
|
E103A6A0278A7E4500820EC7 /* UICinematicBackgroundView.swift in Sources */,
|
||||||
E11CEB9428999D9E003E74C7 /* EpisodeItemContentView.swift in Sources */,
|
E11CEB9428999D9E003E74C7 /* EpisodeItemContentView.swift in Sources */,
|
||||||
E17885A02780F55C0094FBCF /* tvOSVLCOverlay.swift in Sources */,
|
E17885A02780F55C0094FBCF /* tvOSVLCOverlay.swift in Sources */,
|
||||||
|
E148128328C1443D003B8787 /* NameGUIDPairExtensions.swift in Sources */,
|
||||||
E1BDE359278E9ED2004E4022 /* MissingItemsSettingsView.swift in Sources */,
|
E1BDE359278E9ED2004E4022 /* MissingItemsSettingsView.swift in Sources */,
|
||||||
E193D54D2719426600900D82 /* LibraryFilterView.swift in Sources */,
|
|
||||||
C4BE07892728448B003F4AD1 /* LiveTVChannelsCoordinator.swift in Sources */,
|
C4BE07892728448B003F4AD1 /* LiveTVChannelsCoordinator.swift in Sources */,
|
||||||
E193D53927193F8E00900D82 /* SearchCoordinator.swift in Sources */,
|
E193D53927193F8E00900D82 /* SearchCoordinator.swift in Sources */,
|
||||||
C4BE078C272844AF003F4AD1 /* LiveTVChannelsView.swift in Sources */,
|
C4BE078C272844AF003F4AD1 /* LiveTVChannelsView.swift in Sources */,
|
||||||
E1D4BF852719D25A00A11E64 /* TrackLanguage.swift in Sources */,
|
E1D4BF852719D25A00A11E64 /* TrackLanguage.swift in Sources */,
|
||||||
E1C926142887565C002A7A66 /* FocusGuide.swift in Sources */,
|
E1C926142887565C002A7A66 /* FocusGuide.swift in Sources */,
|
||||||
|
E148128928C154BF003B8787 /* ItemFilterExtensions.swift in Sources */,
|
||||||
E193D53427193F7F00900D82 /* HomeCoordinator.swift in Sources */,
|
E193D53427193F7F00900D82 /* HomeCoordinator.swift in Sources */,
|
||||||
E193D5502719430400900D82 /* ServerDetailView.swift in Sources */,
|
E193D5502719430400900D82 /* ServerDetailView.swift in Sources */,
|
||||||
E1399475289B1EA900401ABC /* Defaults+Workaround.swift in Sources */,
|
E1399475289B1EA900401ABC /* Defaults+Workaround.swift in Sources */,
|
||||||
|
@ -2489,6 +2537,7 @@
|
||||||
E18E02202887492B0022598C /* AttributeFillView.swift in Sources */,
|
E18E02202887492B0022598C /* AttributeFillView.swift in Sources */,
|
||||||
E1937A3C288E54AD00CB80AA /* BaseItemDto+Images.swift in Sources */,
|
E1937A3C288E54AD00CB80AA /* BaseItemDto+Images.swift in Sources */,
|
||||||
E154677A289AF48200087E35 /* CollectionItemContentView.swift in Sources */,
|
E154677A289AF48200087E35 /* CollectionItemContentView.swift in Sources */,
|
||||||
|
E148128C28C15526003B8787 /* SortBy.swift in Sources */,
|
||||||
E1C812D1277AE4E300918266 /* tvOSVideoPlayerCoordinator.swift in Sources */,
|
E1C812D1277AE4E300918266 /* tvOSVideoPlayerCoordinator.swift in Sources */,
|
||||||
E1A2C156279A7D5A005EC829 /* UIApplicationExtensions.swift in Sources */,
|
E1A2C156279A7D5A005EC829 /* UIApplicationExtensions.swift in Sources */,
|
||||||
E193D53D27193F9700900D82 /* UserSignInCoordinator.swift in Sources */,
|
E193D53D27193F9700900D82 /* UserSignInCoordinator.swift in Sources */,
|
||||||
|
@ -2518,6 +2567,7 @@
|
||||||
53ABFDE5267974EF00886593 /* ViewModel.swift in Sources */,
|
53ABFDE5267974EF00886593 /* ViewModel.swift in Sources */,
|
||||||
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 */,
|
||||||
E1E1644528BC60C600323B0A /* LibraryItem.swift in Sources */,
|
E1E1644528BC60C600323B0A /* LibraryItem.swift in Sources */,
|
||||||
E13849452780370B0024FB48 /* PlaybackSpeed.swift in Sources */,
|
E13849452780370B0024FB48 /* PlaybackSpeed.swift in Sources */,
|
||||||
E1C812CD277AE40A00918266 /* VideoPlayerViewModel.swift in Sources */,
|
E1C812CD277AE40A00918266 /* VideoPlayerViewModel.swift in Sources */,
|
||||||
|
@ -2542,7 +2592,7 @@
|
||||||
E193D53A27193F9000900D82 /* ServerListCoordinator.swift in Sources */,
|
E193D53A27193F9000900D82 /* ServerListCoordinator.swift in Sources */,
|
||||||
6220D0AE26D5EABB00B8E046 /* ViewExtensions.swift in Sources */,
|
6220D0AE26D5EABB00B8E046 /* ViewExtensions.swift in Sources */,
|
||||||
E11895B42893844A0042947B /* BackgroundParallaxHeaderModifier.swift in Sources */,
|
E11895B42893844A0042947B /* BackgroundParallaxHeaderModifier.swift in Sources */,
|
||||||
5321753E2671DE9C005491E6 /* Typings.swift in Sources */,
|
5321753E2671DE9C005491E6 /* ItemFilters.swift in Sources */,
|
||||||
E1C812CA277AE40900918266 /* PlayerOverlayDelegate.swift in Sources */,
|
E1C812CA277AE40900918266 /* PlayerOverlayDelegate.swift in Sources */,
|
||||||
E1AA33202782639D00F6439C /* OverlayType.swift in Sources */,
|
E1AA33202782639D00F6439C /* OverlayType.swift in Sources */,
|
||||||
C4BE07722725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift in Sources */,
|
C4BE07722725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift in Sources */,
|
||||||
|
@ -2554,6 +2604,7 @@
|
||||||
E193D5512719432400900D82 /* ServerDetailViewModel.swift in Sources */,
|
E193D5512719432400900D82 /* ServerDetailViewModel.swift in Sources */,
|
||||||
C4E5081B2703F82A0045C9AB /* MediaView.swift in Sources */,
|
C4E5081B2703F82A0045C9AB /* MediaView.swift in Sources */,
|
||||||
E193D53B27193F9200900D82 /* SettingsCoordinator.swift in Sources */,
|
E193D53B27193F9200900D82 /* SettingsCoordinator.swift in Sources */,
|
||||||
|
E113133B28BEB71D00930F75 /* FilterViewModel.swift in Sources */,
|
||||||
E13F05ED28BC9000003499D2 /* LibraryViewType.swift in Sources */,
|
E13F05ED28BC9000003499D2 /* LibraryViewType.swift in Sources */,
|
||||||
E18E021C2887492B0022598C /* BlurView.swift in Sources */,
|
E18E021C2887492B0022598C /* BlurView.swift in Sources */,
|
||||||
E1E5D5442783BB5100692DFE /* ItemDetailsView.swift in Sources */,
|
E1E5D5442783BB5100692DFE /* ItemDetailsView.swift in Sources */,
|
||||||
|
@ -2576,6 +2627,7 @@
|
||||||
E11CEB8E28999B4A003E74C7 /* FontExtensions.swift in Sources */,
|
E11CEB8E28999B4A003E74C7 /* FontExtensions.swift in Sources */,
|
||||||
C4BE077A2726EE82003F4AD1 /* LiveTVTabCoordinator.swift in Sources */,
|
C4BE077A2726EE82003F4AD1 /* LiveTVTabCoordinator.swift in Sources */,
|
||||||
E13DD3C327164941009D4DAF /* SwiftfinStore.swift in Sources */,
|
E13DD3C327164941009D4DAF /* SwiftfinStore.swift in Sources */,
|
||||||
|
E17FB55028C1197700311DFE /* SelectorType.swift in Sources */,
|
||||||
09389CC826819B4600AE350E /* VideoPlayerModel.swift in Sources */,
|
09389CC826819B4600AE350E /* VideoPlayerModel.swift in Sources */,
|
||||||
E193D553271943D500900D82 /* tvOSMainTabCoordinator.swift in Sources */,
|
E193D553271943D500900D82 /* tvOSMainTabCoordinator.swift in Sources */,
|
||||||
E1C9261B288756BD002A7A66 /* DotHStack.swift in Sources */,
|
E1C9261B288756BD002A7A66 /* DotHStack.swift in Sources */,
|
||||||
|
@ -2586,6 +2638,7 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
E17FB55528C1250B00311DFE /* SimilarItemsHStack.swift in Sources */,
|
||||||
5364F455266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */,
|
5364F455266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */,
|
||||||
E18845F526DD631E00B0C5B7 /* BaseItemDto+Poster.swift in Sources */,
|
E18845F526DD631E00B0C5B7 /* BaseItemDto+Poster.swift in Sources */,
|
||||||
E1D4BF7E2719D1DD00A11E64 /* BasicAppSettingsViewModel.swift in Sources */,
|
E1D4BF7E2719D1DD00A11E64 /* BasicAppSettingsViewModel.swift in Sources */,
|
||||||
|
@ -2596,6 +2649,7 @@
|
||||||
621338932660107500A81A2A /* StringExtensions.swift in Sources */,
|
621338932660107500A81A2A /* StringExtensions.swift in Sources */,
|
||||||
62C83B08288C6A630004ED0C /* FontPicker.swift in Sources */,
|
62C83B08288C6A630004ED0C /* FontPicker.swift in Sources */,
|
||||||
E122A9132788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */,
|
E122A9132788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */,
|
||||||
|
E17FB55928C125E900311DFE /* StudiosHStack.swift in Sources */,
|
||||||
E1C812C5277A90B200918266 /* URLComponentsExtensions.swift in Sources */,
|
E1C812C5277A90B200918266 /* URLComponentsExtensions.swift in Sources */,
|
||||||
E1EBCB44278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift in Sources */,
|
E1EBCB44278BD1CE009FE6E9 /* ItemOverviewCoordinator.swift in Sources */,
|
||||||
E1C925F428875037002A7A66 /* ItemViewType.swift in Sources */,
|
E1C925F428875037002A7A66 /* ItemViewType.swift in Sources */,
|
||||||
|
@ -2603,6 +2657,7 @@
|
||||||
625CB5732678C32A00530A6E /* HomeViewModel.swift in Sources */,
|
625CB5732678C32A00530A6E /* HomeViewModel.swift in Sources */,
|
||||||
62C29EA826D103D500C1D2E7 /* MediaCoordinator.swift in Sources */,
|
62C29EA826D103D500C1D2E7 /* MediaCoordinator.swift in Sources */,
|
||||||
62E632DC267D2E130063E547 /* SearchViewModel.swift in Sources */,
|
62E632DC267D2E130063E547 /* SearchViewModel.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 */,
|
||||||
E11895AF2893840F0042947B /* NavBarOffsetView.swift in Sources */,
|
E11895AF2893840F0042947B /* NavBarOffsetView.swift in Sources */,
|
||||||
|
@ -2610,7 +2665,7 @@
|
||||||
E18E01AA288746AF0022598C /* RefreshableScrollView.swift in Sources */,
|
E18E01AA288746AF0022598C /* RefreshableScrollView.swift in Sources */,
|
||||||
E18E0208288749200022598C /* BlurView.swift in Sources */,
|
E18E0208288749200022598C /* BlurView.swift in Sources */,
|
||||||
E18E01E7288747230022598C /* CollectionItemContentView.swift in Sources */,
|
E18E01E7288747230022598C /* CollectionItemContentView.swift in Sources */,
|
||||||
E1E1643F28BB075C00323B0A /* MultiSelectorView.swift in Sources */,
|
E1E1643F28BB075C00323B0A /* SelectorView.swift in Sources */,
|
||||||
E18E01DF288747230022598C /* iPadOSMovieItemView.swift in Sources */,
|
E18E01DF288747230022598C /* iPadOSMovieItemView.swift in Sources */,
|
||||||
E168BD13289A4162001A6922 /* ContinueWatchingView.swift in Sources */,
|
E168BD13289A4162001A6922 /* ContinueWatchingView.swift in Sources */,
|
||||||
C45942CD27F6994A00C54FE7 /* LiveTVPlayerView.swift in Sources */,
|
C45942CD27F6994A00C54FE7 /* LiveTVPlayerView.swift in Sources */,
|
||||||
|
@ -2626,6 +2681,7 @@
|
||||||
E10EAA4F277BBCC4000269ED /* CGSizeExtensions.swift in Sources */,
|
E10EAA4F277BBCC4000269ED /* CGSizeExtensions.swift in Sources */,
|
||||||
E10D87E227852FD000BD264C /* EpisodesRowManager.swift in Sources */,
|
E10D87E227852FD000BD264C /* EpisodesRowManager.swift in Sources */,
|
||||||
E18E01EB288747230022598C /* MovieItemContentView.swift in Sources */,
|
E18E01EB288747230022598C /* MovieItemContentView.swift in Sources */,
|
||||||
|
E17FB55B28C1266400311DFE /* GenresHStack.swift in Sources */,
|
||||||
E18E01FA288747580022598C /* AboutAppView.swift in Sources */,
|
E18E01FA288747580022598C /* AboutAppView.swift in Sources */,
|
||||||
6220D0AD26D5EABB00B8E046 /* ViewExtensions.swift in Sources */,
|
6220D0AD26D5EABB00B8E046 /* ViewExtensions.swift in Sources */,
|
||||||
E19E551F2897326C003CE330 /* BottomEdgeGradientModifier.swift in Sources */,
|
E19E551F2897326C003CE330 /* BottomEdgeGradientModifier.swift in Sources */,
|
||||||
|
@ -2639,8 +2695,10 @@
|
||||||
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 */,
|
||||||
|
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 */,
|
||||||
|
E113132B28BDB4B500930F75 /* NavBarDrawerView.swift in Sources */,
|
||||||
C45942CB27F6984100C54FE7 /* LiveTVPlayerViewController.swift in Sources */,
|
C45942CB27F6984100C54FE7 /* LiveTVPlayerViewController.swift in Sources */,
|
||||||
E173DA5426D050F500CC4EB7 /* ServerDetailViewModel.swift in Sources */,
|
E173DA5426D050F500CC4EB7 /* ServerDetailViewModel.swift in Sources */,
|
||||||
E19169CE272514760085832A /* HTTPScheme.swift in Sources */,
|
E19169CE272514760085832A /* HTTPScheme.swift in Sources */,
|
||||||
|
@ -2670,9 +2728,9 @@
|
||||||
C4AE2C3227498D6A00AE13CF /* LiveTVProgramsView.swift in Sources */,
|
C4AE2C3227498D6A00AE13CF /* LiveTVProgramsView.swift in Sources */,
|
||||||
62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */,
|
62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */,
|
||||||
62E632E6267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */,
|
62E632E6267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */,
|
||||||
|
E113133428BE988200930F75 /* FilterDrawerHStack.swift in Sources */,
|
||||||
5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */,
|
5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */,
|
||||||
E107BB9327880A8F00354E07 /* CollectionItemViewModel.swift in Sources */,
|
E107BB9327880A8F00354E07 /* CollectionItemViewModel.swift in Sources */,
|
||||||
532175402671EE4F005491E6 /* LibraryFilterView.swift in Sources */,
|
|
||||||
E1171A1928A2212600FA1AF5 /* QuickConnectView.swift in Sources */,
|
E1171A1928A2212600FA1AF5 /* QuickConnectView.swift in Sources */,
|
||||||
E11CEB8D28999B4A003E74C7 /* FontExtensions.swift in Sources */,
|
E11CEB8D28999B4A003E74C7 /* FontExtensions.swift in Sources */,
|
||||||
E1C812CE277AE43100918266 /* VideoPlayerViewModel.swift in Sources */,
|
E1C812CE277AE43100918266 /* VideoPlayerViewModel.swift in Sources */,
|
||||||
|
@ -2681,6 +2739,7 @@
|
||||||
E18CE0B228A229E70092E7F1 /* UserDtoExtensions.swift in Sources */,
|
E18CE0B228A229E70092E7F1 /* UserDtoExtensions.swift in Sources */,
|
||||||
E18E01F0288747230022598C /* AttributeHStack.swift in Sources */,
|
E18E01F0288747230022598C /* AttributeHStack.swift in Sources */,
|
||||||
6334175B287DDFB9000603CE /* QuickConnectSettingsView.swift in Sources */,
|
6334175B287DDFB9000603CE /* QuickConnectSettingsView.swift in Sources */,
|
||||||
|
E17FB54F28C1197700311DFE /* SelectorType.swift in Sources */,
|
||||||
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 */,
|
||||||
|
@ -2693,6 +2752,7 @@
|
||||||
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 */,
|
||||||
|
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 */,
|
||||||
|
@ -2703,8 +2763,10 @@
|
||||||
E13DD3E127176BD3009D4DAF /* ServerListViewModel.swift in Sources */,
|
E13DD3E127176BD3009D4DAF /* ServerListViewModel.swift in Sources */,
|
||||||
E1937A3B288E54AD00CB80AA /* BaseItemDto+Images.swift in Sources */,
|
E1937A3B288E54AD00CB80AA /* BaseItemDto+Images.swift in Sources */,
|
||||||
62E632E9267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */,
|
62E632E9267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */,
|
||||||
|
E113133228BDC72000930F75 /* FilterView.swift in Sources */,
|
||||||
62E632F3267D54030063E547 /* ItemViewModel.swift in Sources */,
|
62E632F3267D54030063E547 /* ItemViewModel.swift in Sources */,
|
||||||
E11895AC289383EE0042947B /* NavBarOffsetModifier.swift in Sources */,
|
E11895AC289383EE0042947B /* NavBarOffsetModifier.swift in Sources */,
|
||||||
|
E113133628BE98AA00930F75 /* FilterDrawerButton.swift in Sources */,
|
||||||
E13DD3FC2717EAE8009D4DAF /* UserListView.swift in Sources */,
|
E13DD3FC2717EAE8009D4DAF /* UserListView.swift in Sources */,
|
||||||
E18E01DE288747230022598C /* iPadOSSeriesItemView.swift in Sources */,
|
E18E01DE288747230022598C /* iPadOSSeriesItemView.swift in Sources */,
|
||||||
6220D0CC26D640C400B8E046 /* AppURLHandler.swift in Sources */,
|
6220D0CC26D640C400B8E046 /* AppURLHandler.swift in Sources */,
|
||||||
|
@ -2727,12 +2789,13 @@
|
||||||
E13DD3E927177ED6009D4DAF /* ServerListCoordinator.swift in Sources */,
|
E13DD3E927177ED6009D4DAF /* ServerListCoordinator.swift in Sources */,
|
||||||
E1CCF12E28ABF989006CAC9E /* PosterType.swift in Sources */,
|
E1CCF12E28ABF989006CAC9E /* PosterType.swift in Sources */,
|
||||||
E1C812BD277A8E5D00918266 /* PlayerOverlayDelegate.swift in Sources */,
|
E1C812BD277A8E5D00918266 /* PlayerOverlayDelegate.swift in Sources */,
|
||||||
|
E113132F28BDB66A00930F75 /* NavBarDrawerModifier.swift in Sources */,
|
||||||
C45942C527F67DA400C54FE7 /* LiveTVCoordinator.swift in Sources */,
|
C45942C527F67DA400C54FE7 /* LiveTVCoordinator.swift in Sources */,
|
||||||
|
E113133A28BEB71D00930F75 /* FilterViewModel.swift in Sources */,
|
||||||
E13DD3C227164941009D4DAF /* SwiftfinStore.swift in Sources */,
|
E13DD3C227164941009D4DAF /* SwiftfinStore.swift in Sources */,
|
||||||
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 */,
|
||||||
E193D4DB27193CCA00900D82 /* PillStackable.swift in Sources */,
|
|
||||||
E1E1644428BC60C600323B0A /* LibraryItem.swift in Sources */,
|
E1E1644428BC60C600323B0A /* LibraryItem.swift in Sources */,
|
||||||
E18E0206288749200022598C /* AttributeFillView.swift in Sources */,
|
E18E0206288749200022598C /* AttributeFillView.swift in Sources */,
|
||||||
E1AA331F2782639D00F6439C /* OverlayType.swift in Sources */,
|
E1AA331F2782639D00F6439C /* OverlayType.swift in Sources */,
|
||||||
|
@ -2746,13 +2809,14 @@
|
||||||
E1D4BF7C2719D05000A11E64 /* BasicAppSettingsView.swift in Sources */,
|
E1D4BF7C2719D05000A11E64 /* BasicAppSettingsView.swift in Sources */,
|
||||||
E173DA5026D048D600CC4EB7 /* ServerDetailView.swift in Sources */,
|
E173DA5026D048D600CC4EB7 /* ServerDetailView.swift in Sources */,
|
||||||
62EC352F267666A5000E9F2D /* SessionManager.swift in Sources */,
|
62EC352F267666A5000E9F2D /* SessionManager.swift in Sources */,
|
||||||
|
E17FB55728C1256400311DFE /* CastAndCrewHStack.swift in Sources */,
|
||||||
62E632E3267D3BA60063E547 /* MovieItemViewModel.swift in Sources */,
|
62E632E3267D3BA60063E547 /* MovieItemViewModel.swift in Sources */,
|
||||||
|
E113133828BEADBA00930F75 /* LibraryParent.swift in Sources */,
|
||||||
E1937A3E288F0D3D00CB80AA /* UIScreenExtensions.swift in Sources */,
|
E1937A3E288F0D3D00CB80AA /* UIScreenExtensions.swift in Sources */,
|
||||||
C4BE076F2720FEFF003F4AD1 /* PlainNavigationLinkButton.swift in Sources */,
|
C4BE076F2720FEFF003F4AD1 /* PlainNavigationLinkButton.swift in Sources */,
|
||||||
E1EBCB46278BD595009FE6E9 /* ItemOverviewView.swift in Sources */,
|
E1EBCB46278BD595009FE6E9 /* ItemOverviewView.swift in Sources */,
|
||||||
E18CE0B428A22EDA0092E7F1 /* RepeatingTimer.swift in Sources */,
|
E18CE0B428A22EDA0092E7F1 /* RepeatingTimer.swift in Sources */,
|
||||||
091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */,
|
091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */,
|
||||||
62E632EF267D43320063E547 /* LibraryFilterViewModel.swift in Sources */,
|
|
||||||
5D64683D277B1649009E09AE /* PreferenceUIHostingSwizzling.swift in Sources */,
|
5D64683D277B1649009E09AE /* PreferenceUIHostingSwizzling.swift in Sources */,
|
||||||
C45942C927F697CA00C54FE7 /* iOSLiveTVVideoPlayerCoordinator.swift in Sources */,
|
C45942C927F697CA00C54FE7 /* iOSLiveTVVideoPlayerCoordinator.swift in Sources */,
|
||||||
E1CCF13128AC07EC006CAC9E /* PosterHStack.swift in Sources */,
|
E1CCF13128AC07EC006CAC9E /* PosterHStack.swift in Sources */,
|
||||||
|
@ -2761,7 +2825,7 @@
|
||||||
E10EAA53277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */,
|
E10EAA53277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */,
|
||||||
E1AD104D26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */,
|
E1AD104D26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */,
|
||||||
E13DD3BF27163DD7009D4DAF /* AppDelegate.swift in Sources */,
|
E13DD3BF27163DD7009D4DAF /* AppDelegate.swift in Sources */,
|
||||||
535870AD2669D8DD00D05A09 /* Typings.swift in Sources */,
|
535870AD2669D8DD00D05A09 /* ItemFilters.swift in Sources */,
|
||||||
E1AD105F26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift in Sources */,
|
E1AD105F26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift in Sources */,
|
||||||
E18E01F1288747230022598C /* PlayButton.swift in Sources */,
|
E18E01F1288747230022598C /* PlayButton.swift in Sources */,
|
||||||
E13DD3D5271693CD009D4DAF /* SwiftfinStoreDefaults.swift in Sources */,
|
E13DD3D5271693CD009D4DAF /* SwiftfinStoreDefaults.swift in Sources */,
|
||||||
|
@ -2771,7 +2835,6 @@
|
||||||
6220D0BA26D6092100B8E046 /* FilterCoordinator.swift in Sources */,
|
6220D0BA26D6092100B8E046 /* FilterCoordinator.swift in Sources */,
|
||||||
E1E5D54C2783E27200692DFE /* ExperimentalSettingsView.swift in Sources */,
|
E1E5D54C2783E27200692DFE /* ExperimentalSettingsView.swift in Sources */,
|
||||||
E1E00A35278628A40022235B /* DoubleExtensions.swift in Sources */,
|
E1E00A35278628A40022235B /* DoubleExtensions.swift in Sources */,
|
||||||
62EC353426766B03000E9F2D /* DeviceRotationViewModifier.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 */,
|
||||||
|
@ -2781,6 +2844,7 @@
|
||||||
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 */,
|
||||||
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 */,
|
||||||
|
|
|
@ -8,11 +8,11 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct PillHStack<Item: PillStackable>: View {
|
struct PillHStack<Item: Displayable>: View {
|
||||||
|
|
||||||
let title: String
|
private var title: String
|
||||||
let items: [Item]
|
private var items: [Item]
|
||||||
let onSelect: (Item) -> Void
|
private var onSelect: (Item) -> Void
|
||||||
|
|
||||||
private init(
|
private init(
|
||||||
title: String,
|
title: String,
|
||||||
|
@ -37,11 +37,11 @@ struct PillHStack<Item: PillStackable>: View {
|
||||||
|
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
HStack {
|
HStack {
|
||||||
ForEach(items, id: \.title) { item in
|
ForEach(items, id: \.displayName) { item in
|
||||||
Button {
|
Button {
|
||||||
onSelect(item)
|
onSelect(item)
|
||||||
} label: {
|
} label: {
|
||||||
Text(item.title)
|
Text(item.displayName)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.foregroundColor(.primary)
|
.foregroundColor(.primary)
|
||||||
|
@ -68,12 +68,9 @@ extension PillHStack {
|
||||||
self.init(title: title, items: items, onSelect: { _ in })
|
self.init(title: title, items: items, onSelect: { _ in })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
func onSelect(_ onSelect: @escaping (Item) -> Void) -> Self {
|
||||||
func onSelect(_ onSelect: @escaping (Item) -> Void) -> PillHStack {
|
var copy = self
|
||||||
PillHStack(
|
copy.onSelect = onSelect
|
||||||
title: title,
|
return copy
|
||||||
items: items,
|
|
||||||
onSelect: onSelect
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,7 +196,7 @@ struct PosterButtonDefaultContentView<Item: Poster>: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
if item.showTitle {
|
if item.showTitle {
|
||||||
Text(item.title)
|
Text(item.displayName)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.fontWeight(.regular)
|
.fontWeight(.regular)
|
||||||
.foregroundColor(.primary)
|
.foregroundColor(.primary)
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
//
|
||||||
|
// 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 NavBarDrawerModifier<Drawer: View>: ViewModifier {
|
||||||
|
|
||||||
|
let drawer: () -> Drawer
|
||||||
|
|
||||||
|
init(@ViewBuilder drawer: @escaping () -> Drawer) {
|
||||||
|
self.drawer = drawer
|
||||||
|
}
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
NavBarDrawerView {
|
||||||
|
drawer()
|
||||||
|
.ignoresSafeArea()
|
||||||
|
} content: {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
.ignoresSafeArea()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
private let drawerHeight: CGFloat = 36
|
||||||
|
|
||||||
|
struct NavBarDrawerView<Buttons: View, Content: View>: UIViewControllerRepresentable {
|
||||||
|
|
||||||
|
private let buttons: () -> Buttons
|
||||||
|
private let content: () -> Content
|
||||||
|
|
||||||
|
init(
|
||||||
|
@ViewBuilder buttons: @escaping () -> Buttons,
|
||||||
|
@ViewBuilder content: @escaping () -> Content
|
||||||
|
) {
|
||||||
|
self.buttons = buttons
|
||||||
|
self.content = content
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeUIViewController(context: Context) -> UINavBarDrawerHostingController<Buttons, Content> {
|
||||||
|
UINavBarDrawerHostingController(buttons: buttons, content: content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIViewController(_ uiViewController: UINavBarDrawerHostingController<Buttons, Content>, context: Context) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UINavBarDrawerHostingController<Buttons: View, Content: View>: UIViewController {
|
||||||
|
|
||||||
|
private let buttons: () -> Buttons
|
||||||
|
private let content: () -> Content
|
||||||
|
|
||||||
|
private lazy var navBarBlurView: UIVisualEffectView = {
|
||||||
|
let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThinMaterial))
|
||||||
|
blurView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
return blurView
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var contentView: UIHostingController<Content> = {
|
||||||
|
let contentView = UIHostingController(rootView: content())
|
||||||
|
contentView.view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
contentView.view.backgroundColor = nil
|
||||||
|
return contentView
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var drawerButtonsView: UIHostingController<Buttons> = {
|
||||||
|
let drawerButtonsView = UIHostingController(rootView: buttons())
|
||||||
|
drawerButtonsView.view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
drawerButtonsView.view.backgroundColor = nil
|
||||||
|
return drawerButtonsView
|
||||||
|
}()
|
||||||
|
|
||||||
|
init(
|
||||||
|
buttons: @escaping () -> Buttons,
|
||||||
|
content: @escaping () -> Content
|
||||||
|
) {
|
||||||
|
self.buttons = buttons
|
||||||
|
self.content = content
|
||||||
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
view.backgroundColor = nil
|
||||||
|
|
||||||
|
addChild(contentView)
|
||||||
|
view.addSubview(contentView.view)
|
||||||
|
contentView.didMove(toParent: self)
|
||||||
|
|
||||||
|
view.addSubview(navBarBlurView)
|
||||||
|
|
||||||
|
addChild(drawerButtonsView)
|
||||||
|
view.addSubview(drawerButtonsView.view)
|
||||||
|
drawerButtonsView.didMove(toParent: self)
|
||||||
|
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
drawerButtonsView.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: -drawerHeight),
|
||||||
|
drawerButtonsView.view.heightAnchor.constraint(equalToConstant: drawerHeight),
|
||||||
|
drawerButtonsView.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
|
drawerButtonsView.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
|
])
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
navBarBlurView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||||
|
navBarBlurView.bottomAnchor.constraint(equalTo: drawerButtonsView.view.bottomAnchor),
|
||||||
|
navBarBlurView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
|
navBarBlurView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
|
])
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
contentView.view.topAnchor.constraint(equalTo: view.topAnchor),
|
||||||
|
contentView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||||
|
contentView.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
|
contentView.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
|
||||||
|
self.navigationController?.navigationBar.shadowImage = UIImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
|
super.viewWillDisappear(animated)
|
||||||
|
self.navigationController?.navigationBar.setBackgroundImage(nil, for: .default)
|
||||||
|
self.navigationController?.navigationBar.shadowImage = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
override var additionalSafeAreaInsets: UIEdgeInsets {
|
||||||
|
get {
|
||||||
|
.init(top: drawerHeight, left: 0, bottom: 0, right: 0)
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
super.additionalSafeAreaInsets = .init(top: drawerHeight, left: 0, bottom: 0, right: 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,4 +12,8 @@ extension View {
|
||||||
func navBarOffset(_ scrollViewOffset: Binding<CGFloat>, start: CGFloat, end: CGFloat) -> some View {
|
func navBarOffset(_ scrollViewOffset: Binding<CGFloat>, start: CGFloat, end: CGFloat) -> some View {
|
||||||
self.modifier(NavBarOffsetModifier(scrollViewOffset: scrollViewOffset, start: start, end: end))
|
self.modifier(NavBarOffsetModifier(scrollViewOffset: scrollViewOffset, start: start, end: end))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func navBarDrawer<Drawer: View>(@ViewBuilder _ drawer: @escaping () -> Drawer) -> some View {
|
||||||
|
self.modifier(NavBarDrawerModifier(drawer: drawer))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ struct AboutAppView: View {
|
||||||
HStack {
|
HStack {
|
||||||
L10n.about.text
|
L10n.about.text
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("\(UIApplication.appVersion ?? "--") (\(UIApplication.bundleVersion ?? "--"))")
|
Text("\(UIApplication.appVersion ?? .emptyDash) (\(UIApplication.bundleVersion ?? .emptyDash))")
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
struct FilterView: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: FilterCoordinator.Router
|
||||||
|
|
||||||
|
@ObservedObject
|
||||||
|
private var viewModel: FilterViewModel
|
||||||
|
|
||||||
|
private let title: String
|
||||||
|
private let filter: WritableKeyPath<ItemFilters, [ItemFilters.Filter]>
|
||||||
|
private let selectedFiltersBinding: Binding<[ItemFilters.Filter]>
|
||||||
|
private let selectorType: SelectorType
|
||||||
|
|
||||||
|
init(
|
||||||
|
title: String,
|
||||||
|
viewModel: FilterViewModel,
|
||||||
|
filter: WritableKeyPath<ItemFilters, [ItemFilters.Filter]>,
|
||||||
|
selectorType: SelectorType
|
||||||
|
) {
|
||||||
|
self.title = title
|
||||||
|
self.viewModel = viewModel
|
||||||
|
self.filter = filter
|
||||||
|
self.selectorType = selectorType
|
||||||
|
|
||||||
|
self.selectedFiltersBinding = Binding(get: {
|
||||||
|
viewModel.currentFilters[keyPath: filter]
|
||||||
|
}, set: { newValue, _ in
|
||||||
|
viewModel.currentFilters[keyPath: filter] = newValue
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
|
||||||
|
VStack {
|
||||||
|
SelectorView(
|
||||||
|
type: selectorType,
|
||||||
|
allItems: viewModel.allFilters[keyPath: filter],
|
||||||
|
selectedItems: selectedFiltersBinding
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.navigationTitle(title)
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItemGroup(placement: .navigationBarLeading) {
|
||||||
|
Button {
|
||||||
|
router.dismissCoordinator()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "xmark.circle.fill")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,8 +24,7 @@ struct LatestInLibraryView: View {
|
||||||
PosterHStack(title: L10n.latestWithString(viewModel.library.displayName), type: latestInLibraryPosterType, items: viewModel.items)
|
PosterHStack(title: L10n.latestWithString(viewModel.library.displayName), type: latestInLibraryPosterType, items: viewModel.items)
|
||||||
.trailing {
|
.trailing {
|
||||||
Button {
|
Button {
|
||||||
let libraryViewModel = LibraryViewModel(library: viewModel.library, filters: HomeViewModel.recentFilterSet)
|
homeRouter.route(to: \.library, .init(parent: viewModel.library, type: .library, filters: .recent))
|
||||||
homeRouter.route(to: \.library, (viewModel: libraryViewModel, title: viewModel.library.displayName))
|
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
L10n.seeAll.text
|
L10n.seeAll.text
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
//
|
||||||
|
// 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 ItemView {
|
||||||
|
struct CastAndCrewHStack: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: ItemCoordinator.Router
|
||||||
|
let people: [BaseItemPerson]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
PosterHStack(
|
||||||
|
title: L10n.castAndCrew,
|
||||||
|
type: .portrait,
|
||||||
|
items: people
|
||||||
|
)
|
||||||
|
.onSelect { person in
|
||||||
|
router.route(to: \.library, .init(parent: person, type: .person, filters: .init()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
//
|
||||||
|
// 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 ItemView {
|
||||||
|
struct GenresHStack: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: ItemCoordinator.Router
|
||||||
|
let genres: [NameGuidPair]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
PillHStack(
|
||||||
|
title: L10n.genres,
|
||||||
|
items: genres
|
||||||
|
).onSelect { genre in
|
||||||
|
router.route(to: \.library, .init(filters: .init(genres: [genre.filter])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
//
|
||||||
|
// 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 ItemView {
|
||||||
|
struct SimilarItemsHStack: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: ItemCoordinator.Router
|
||||||
|
let items: [BaseItemDto]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
PosterHStack(
|
||||||
|
title: L10n.recommended,
|
||||||
|
type: .portrait,
|
||||||
|
items: items
|
||||||
|
)
|
||||||
|
.onSelect { item in
|
||||||
|
router.route(to: \.item, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
//
|
||||||
|
// 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 ItemView {
|
||||||
|
struct StudiosHStack: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var router: ItemCoordinator.Router
|
||||||
|
let studios: [NameGuidPair]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
PillHStack(
|
||||||
|
title: L10n.studios,
|
||||||
|
items: studios
|
||||||
|
).onSelect { studio in
|
||||||
|
router.route(to: \.library, .init(parent: studio, type: .studio, filters: .init()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,9 +43,9 @@ struct ItemView: View {
|
||||||
CollectionItemView(viewModel: .init(item: item))
|
CollectionItemView(viewModel: .init(item: item))
|
||||||
}
|
}
|
||||||
case .person:
|
case .person:
|
||||||
LibraryView(viewModel: .init(person: .init(id: item.id)))
|
LibraryView(viewModel: .init(parent: item, type: .person))
|
||||||
case .collectionFolder:
|
case .collectionFolder:
|
||||||
LibraryView(viewModel: .init(library: item))
|
LibraryView(viewModel: .init(parent: item, type: .folders))
|
||||||
default:
|
default:
|
||||||
Text(L10n.notImplementedYetWithType(item.type ?? "--"))
|
Text(L10n.notImplementedYetWithType(item.type ?? "--"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,25 +23,15 @@ extension CollectionItemView {
|
||||||
// MARK: Genres
|
// MARK: Genres
|
||||||
|
|
||||||
if let genres = viewModel.item.genreItems, !genres.isEmpty {
|
if let genres = viewModel.item.genreItems, !genres.isEmpty {
|
||||||
PillHStack(
|
ItemView.GenresHStack(genres: genres)
|
||||||
title: L10n.genres,
|
|
||||||
items: genres
|
|
||||||
).onSelect { genre in
|
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(genre: genre), title: genre.title))
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Studios
|
// MARK: Studios
|
||||||
|
|
||||||
if let studios = viewModel.item.studios {
|
if let studios = viewModel.item.studios, !studios.isEmpty {
|
||||||
PillHStack(
|
ItemView.StudiosHStack(studios: studios)
|
||||||
title: L10n.studios,
|
|
||||||
items: studios
|
|
||||||
).onSelect { studio in
|
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(studio: studio), title: studio.name ?? ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,34 +45,25 @@ extension EpisodeItemView {
|
||||||
// MARK: Genres
|
// MARK: Genres
|
||||||
|
|
||||||
if let genres = viewModel.item.genreItems, !genres.isEmpty {
|
if let genres = viewModel.item.genreItems, !genres.isEmpty {
|
||||||
PillHStack(
|
ItemView.GenresHStack(genres: genres)
|
||||||
title: L10n.genres,
|
|
||||||
items: genres
|
|
||||||
).onSelect { genre in
|
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(genre: genre), title: genre.title))
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Studios
|
||||||
|
|
||||||
if let studios = viewModel.item.studios, !studios.isEmpty {
|
if let studios = viewModel.item.studios, !studios.isEmpty {
|
||||||
PillHStack(
|
ItemView.StudiosHStack(studios: studios)
|
||||||
title: L10n.studios,
|
|
||||||
items: studios
|
|
||||||
).onSelect { studio in
|
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(studio: studio), title: studio.name ?? ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Cast and Crew
|
||||||
|
|
||||||
if let castAndCrew = viewModel.item.people?.filter(\.isDisplayed),
|
if let castAndCrew = viewModel.item.people?.filter(\.isDisplayed),
|
||||||
!castAndCrew.isEmpty
|
!castAndCrew.isEmpty
|
||||||
{
|
{
|
||||||
PosterHStack(title: L10n.castAndCrew, type: .portrait, items: castAndCrew)
|
ItemView.CastAndCrewHStack(people: castAndCrew)
|
||||||
.onSelect { person in
|
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(person: person), title: person.title))
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
@ -99,7 +90,7 @@ extension EpisodeItemView.ContentView {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .center, spacing: 10) {
|
VStack(alignment: .center, spacing: 10) {
|
||||||
Text(viewModel.item.seriesName ?? "--")
|
Text(viewModel.item.seriesName ?? .emptyDash)
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
|
|
|
@ -25,12 +25,7 @@ extension MovieItemView {
|
||||||
// MARK: Genres
|
// MARK: Genres
|
||||||
|
|
||||||
if let genres = viewModel.item.genreItems, !genres.isEmpty {
|
if let genres = viewModel.item.genreItems, !genres.isEmpty {
|
||||||
PillHStack(
|
ItemView.GenresHStack(genres: genres)
|
||||||
title: L10n.genres,
|
|
||||||
items: genres
|
|
||||||
).onSelect { genre in
|
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(genre: genre), title: genre.title))
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
@ -38,12 +33,7 @@ extension MovieItemView {
|
||||||
// MARK: Studios
|
// MARK: Studios
|
||||||
|
|
||||||
if let studios = viewModel.item.studios, !studios.isEmpty {
|
if let studios = viewModel.item.studios, !studios.isEmpty {
|
||||||
PillHStack(
|
ItemView.StudiosHStack(studios: studios)
|
||||||
title: L10n.studios,
|
|
||||||
items: studios
|
|
||||||
).onSelect { studio in
|
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(studio: studio), title: studio.name ?? ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
@ -53,10 +43,7 @@ extension MovieItemView {
|
||||||
if let castAndCrew = viewModel.item.people?.filter(\.isDisplayed),
|
if let castAndCrew = viewModel.item.people?.filter(\.isDisplayed),
|
||||||
!castAndCrew.isEmpty
|
!castAndCrew.isEmpty
|
||||||
{
|
{
|
||||||
PosterHStack(title: L10n.castAndCrew, type: .portrait, items: castAndCrew)
|
ItemView.CastAndCrewHStack(people: castAndCrew)
|
||||||
.onSelect { person in
|
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(person: person), title: person.title))
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
@ -64,10 +51,7 @@ extension MovieItemView {
|
||||||
// MARK: Similar
|
// MARK: Similar
|
||||||
|
|
||||||
if !viewModel.similarItems.isEmpty {
|
if !viewModel.similarItems.isEmpty {
|
||||||
PosterHStack(title: L10n.recommended, type: .portrait, items: viewModel.similarItems)
|
ItemView.SimilarItemsHStack(items: viewModel.similarItems)
|
||||||
.onSelect { item in
|
|
||||||
itemRouter.route(to: \.item, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,9 +29,7 @@ extension SeriesItemView {
|
||||||
// MARK: Genres
|
// MARK: Genres
|
||||||
|
|
||||||
if let genres = viewModel.item.genreItems, !genres.isEmpty {
|
if let genres = viewModel.item.genreItems, !genres.isEmpty {
|
||||||
PillHStack(title: L10n.genres, items: genres).onSelect { genre in
|
ItemView.GenresHStack(genres: genres)
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(genre: genre), title: genre.title))
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
@ -39,23 +37,17 @@ extension SeriesItemView {
|
||||||
// MARK: Studios
|
// MARK: Studios
|
||||||
|
|
||||||
if let studios = viewModel.item.studios, !studios.isEmpty {
|
if let studios = viewModel.item.studios, !studios.isEmpty {
|
||||||
PillHStack(
|
ItemView.StudiosHStack(studios: studios)
|
||||||
title: L10n.studios,
|
|
||||||
items: studios
|
|
||||||
).onSelect { studio in
|
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(studio: studio), title: studio.name ?? ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Cast and Crew
|
// MARK: Cast and Crew
|
||||||
|
|
||||||
if let castAndCrew = viewModel.item.people?.filter(\.isDisplayed), !castAndCrew.isEmpty {
|
if let castAndCrew = viewModel.item.people?.filter(\.isDisplayed),
|
||||||
PosterHStack(title: L10n.castAndCrew, type: .portrait, items: castAndCrew)
|
!castAndCrew.isEmpty
|
||||||
.onSelect { person in
|
{
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(person: person), title: person.title))
|
ItemView.CastAndCrewHStack(people: castAndCrew)
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
@ -63,10 +55,7 @@ extension SeriesItemView {
|
||||||
// MARK: Similar
|
// MARK: Similar
|
||||||
|
|
||||||
if !viewModel.similarItems.isEmpty {
|
if !viewModel.similarItems.isEmpty {
|
||||||
PosterHStack(title: L10n.recommended, type: .portrait, items: viewModel.similarItems)
|
ItemView.SimilarItemsHStack(items: viewModel.similarItems)
|
||||||
.onSelect { item in
|
|
||||||
itemRouter.route(to: \.item, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,25 +23,15 @@ extension iPadOSCollectionItemView {
|
||||||
// MARK: Genres
|
// MARK: Genres
|
||||||
|
|
||||||
if let genres = viewModel.item.genreItems, !genres.isEmpty {
|
if let genres = viewModel.item.genreItems, !genres.isEmpty {
|
||||||
PillHStack(
|
ItemView.GenresHStack(genres: genres)
|
||||||
title: L10n.genres,
|
|
||||||
items: genres
|
|
||||||
).onSelect { genre in
|
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(genre: genre), title: genre.title))
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Studios
|
// MARK: Studios
|
||||||
|
|
||||||
if let studios = viewModel.item.studios {
|
if let studios = viewModel.item.studios, !studios.isEmpty {
|
||||||
PillHStack(
|
ItemView.StudiosHStack(studios: studios)
|
||||||
title: L10n.studios,
|
|
||||||
items: studios
|
|
||||||
).onSelect { studio in
|
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(studio: studio), title: studio.name ?? ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,34 +24,25 @@ extension iPadOSEpisodeItemView {
|
||||||
// MARK: Genres
|
// MARK: Genres
|
||||||
|
|
||||||
if let genres = viewModel.item.genreItems, !genres.isEmpty {
|
if let genres = viewModel.item.genreItems, !genres.isEmpty {
|
||||||
PillHStack(
|
ItemView.GenresHStack(genres: genres)
|
||||||
title: L10n.genres,
|
|
||||||
items: genres
|
|
||||||
).onSelect { genre in
|
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(genre: genre), title: genre.title))
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Studios
|
||||||
|
|
||||||
if let studios = viewModel.item.studios, !studios.isEmpty {
|
if let studios = viewModel.item.studios, !studios.isEmpty {
|
||||||
PillHStack(
|
ItemView.StudiosHStack(studios: studios)
|
||||||
title: L10n.studios,
|
|
||||||
items: studios
|
|
||||||
).onSelect { studio in
|
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(studio: studio), title: studio.name ?? ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Cast and Crew
|
||||||
|
|
||||||
if let castAndCrew = viewModel.item.people?.filter(\.isDisplayed),
|
if let castAndCrew = viewModel.item.people?.filter(\.isDisplayed),
|
||||||
!castAndCrew.isEmpty
|
!castAndCrew.isEmpty
|
||||||
{
|
{
|
||||||
PosterHStack(title: L10n.castAndCrew, type: .portrait, items: castAndCrew)
|
ItemView.CastAndCrewHStack(people: castAndCrew)
|
||||||
.onSelect { person in
|
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(person: person), title: person.title))
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,13 +24,7 @@ extension iPadOSMovieItemView {
|
||||||
// MARK: Genres
|
// MARK: Genres
|
||||||
|
|
||||||
if let genres = viewModel.item.genreItems, !genres.isEmpty {
|
if let genres = viewModel.item.genreItems, !genres.isEmpty {
|
||||||
PillHStack(
|
ItemView.GenresHStack(genres: genres)
|
||||||
title: L10n.genres,
|
|
||||||
items: genres
|
|
||||||
)
|
|
||||||
.onSelect { genre in
|
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(genre: genre), title: genre.title))
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
@ -38,12 +32,7 @@ extension iPadOSMovieItemView {
|
||||||
// MARK: Studios
|
// MARK: Studios
|
||||||
|
|
||||||
if let studios = viewModel.item.studios, !studios.isEmpty {
|
if let studios = viewModel.item.studios, !studios.isEmpty {
|
||||||
PillHStack(
|
ItemView.StudiosHStack(studios: studios)
|
||||||
title: L10n.studios,
|
|
||||||
items: studios
|
|
||||||
).onSelect { studio in
|
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(studio: studio), title: studio.name ?? ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
@ -53,10 +42,7 @@ extension iPadOSMovieItemView {
|
||||||
if let castAndCrew = viewModel.item.people?.filter(\.isDisplayed),
|
if let castAndCrew = viewModel.item.people?.filter(\.isDisplayed),
|
||||||
!castAndCrew.isEmpty
|
!castAndCrew.isEmpty
|
||||||
{
|
{
|
||||||
PosterHStack(title: L10n.castAndCrew, type: .portrait, items: castAndCrew)
|
ItemView.CastAndCrewHStack(people: castAndCrew)
|
||||||
.onSelect { person in
|
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(person: person), title: person.title))
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,12 +28,7 @@ extension iPadOSSeriesItemView {
|
||||||
// MARK: Genres
|
// MARK: Genres
|
||||||
|
|
||||||
if let genres = viewModel.item.genreItems, !genres.isEmpty {
|
if let genres = viewModel.item.genreItems, !genres.isEmpty {
|
||||||
PillHStack(
|
ItemView.GenresHStack(genres: genres)
|
||||||
title: L10n.genres,
|
|
||||||
items: genres
|
|
||||||
).onSelect { genre in
|
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(genre: genre), title: genre.title))
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
@ -41,12 +36,7 @@ extension iPadOSSeriesItemView {
|
||||||
// MARK: Studios
|
// MARK: Studios
|
||||||
|
|
||||||
if let studios = viewModel.item.studios, !studios.isEmpty {
|
if let studios = viewModel.item.studios, !studios.isEmpty {
|
||||||
PillHStack(
|
ItemView.StudiosHStack(studios: studios)
|
||||||
title: L10n.studios,
|
|
||||||
items: studios
|
|
||||||
).onSelect { studio in
|
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(studio: studio), title: studio.name ?? ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
@ -56,10 +46,7 @@ extension iPadOSSeriesItemView {
|
||||||
if let castAndCrew = viewModel.item.people?.filter(\.isDisplayed),
|
if let castAndCrew = viewModel.item.people?.filter(\.isDisplayed),
|
||||||
!castAndCrew.isEmpty
|
!castAndCrew.isEmpty
|
||||||
{
|
{
|
||||||
PosterHStack(title: L10n.castAndCrew, type: .portrait, items: castAndCrew)
|
ItemView.CastAndCrewHStack(people: castAndCrew)
|
||||||
.onSelect { person in
|
|
||||||
itemRouter.route(to: \.library, (viewModel: .init(person: person), title: person.title))
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,105 +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 Stinsen
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct LibraryFilterView: View {
|
|
||||||
|
|
||||||
@EnvironmentObject
|
|
||||||
private var filterRouter: FilterCoordinator.Router
|
|
||||||
@Binding
|
|
||||||
var filters: LibraryFilters
|
|
||||||
var parentId: String = ""
|
|
||||||
|
|
||||||
@StateObject
|
|
||||||
var viewModel: LibraryFilterViewModel
|
|
||||||
|
|
||||||
init(filters: Binding<LibraryFilters>, enabledFilterType: [FilterType], parentId: String) {
|
|
||||||
_filters = filters
|
|
||||||
self.parentId = parentId
|
|
||||||
_viewModel =
|
|
||||||
StateObject(wrappedValue: .init(filters: filters.wrappedValue, enabledFilterType: enabledFilterType, parentId: parentId))
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack {
|
|
||||||
if viewModel.isLoading {
|
|
||||||
ProgressView()
|
|
||||||
} else {
|
|
||||||
Form {
|
|
||||||
if viewModel.enabledFilterType.contains(.genre) {
|
|
||||||
MultiSelector(
|
|
||||||
label: L10n.genres,
|
|
||||||
options: viewModel.possibleGenres,
|
|
||||||
optionToString: { $0.name ?? "" },
|
|
||||||
selected: $viewModel.modifiedFilters.withGenres
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if viewModel.enabledFilterType.contains(.filter) {
|
|
||||||
MultiSelector(
|
|
||||||
label: L10n.filters,
|
|
||||||
options: viewModel.possibleItemFilters,
|
|
||||||
optionToString: { $0.localized },
|
|
||||||
selected: $viewModel.modifiedFilters.filters
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if viewModel.enabledFilterType.contains(.tag) {
|
|
||||||
MultiSelector(
|
|
||||||
label: L10n.tags,
|
|
||||||
options: viewModel.possibleTags,
|
|
||||||
optionToString: { $0 },
|
|
||||||
selected: $viewModel.modifiedFilters.tags
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if viewModel.enabledFilterType.contains(.sortBy) {
|
|
||||||
Picker(selection: $viewModel.selectedSortBy, label: L10n.sortBy.text) {
|
|
||||||
ForEach(viewModel.possibleSortBys, id: \.self) { so in
|
|
||||||
Text(so.localized).tag(so)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if viewModel.enabledFilterType.contains(.sortOrder) {
|
|
||||||
Picker(selection: $viewModel.selectedSortOrder, label: L10n.displayOrder.text) {
|
|
||||||
ForEach(viewModel.possibleSortOrders, id: \.self) { so in
|
|
||||||
Text(so.rawValue).tag(so)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
viewModel.resetFilters()
|
|
||||||
self.filters = viewModel.modifiedFilters
|
|
||||||
filterRouter.dismissCoordinator()
|
|
||||||
} label: {
|
|
||||||
L10n.reset.text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.navigationBarTitle(L10n.filterResults, displayMode: .inline)
|
|
||||||
.toolbar {
|
|
||||||
ToolbarItemGroup(placement: .navigationBarLeading) {
|
|
||||||
Button {
|
|
||||||
filterRouter.dismissCoordinator()
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "xmark")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
|
||||||
Button {
|
|
||||||
viewModel.updateModifiedFilter()
|
|
||||||
self.filters = viewModel.modifiedFilters
|
|
||||||
filterRouter.dismissCoordinator()
|
|
||||||
} label: {
|
|
||||||
L10n.apply.text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
extension FilterDrawerHStack {
|
||||||
|
struct FilterDrawerButton: View {
|
||||||
|
|
||||||
|
private let systemName: String?
|
||||||
|
private let title: String
|
||||||
|
private let activated: Bool
|
||||||
|
private var onSelect: () -> Void
|
||||||
|
|
||||||
|
private init(
|
||||||
|
systemName: String?,
|
||||||
|
title: String,
|
||||||
|
activated: Bool,
|
||||||
|
onSelect: @escaping () -> Void
|
||||||
|
) {
|
||||||
|
self.systemName = systemName
|
||||||
|
self.title = title
|
||||||
|
self.activated = activated
|
||||||
|
self.onSelect = onSelect
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button {
|
||||||
|
onSelect()
|
||||||
|
} label: {
|
||||||
|
HStack(spacing: 2) {
|
||||||
|
Group {
|
||||||
|
if let systemName = systemName {
|
||||||
|
Image(systemName: systemName)
|
||||||
|
} else {
|
||||||
|
Text(title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.font(.footnote.weight(.semibold))
|
||||||
|
|
||||||
|
Image(systemName: "chevron.down")
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
.padding(.vertical, 5)
|
||||||
|
.background {
|
||||||
|
Capsule()
|
||||||
|
.foregroundColor(activated ? .jellyfinPurple : Color(UIColor.secondarySystemFill))
|
||||||
|
.opacity(0.5)
|
||||||
|
}
|
||||||
|
.overlay(
|
||||||
|
Capsule()
|
||||||
|
.stroke(activated ? .purple : Color(UIColor.secondarySystemFill), lineWidth: 1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FilterDrawerHStack.FilterDrawerButton {
|
||||||
|
init(title: String, activated: Bool) {
|
||||||
|
self.init(
|
||||||
|
systemName: nil,
|
||||||
|
title: title,
|
||||||
|
activated: activated,
|
||||||
|
onSelect: {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(systemName: String, activated: Bool) {
|
||||||
|
self.init(
|
||||||
|
systemName: systemName,
|
||||||
|
title: "",
|
||||||
|
activated: activated,
|
||||||
|
onSelect: {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func onSelect(_ action: @escaping () -> Void) -> Self {
|
||||||
|
var copy = self
|
||||||
|
copy.onSelect = action
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
//
|
||||||
|
// 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 FilterDrawerHStack: View {
|
||||||
|
|
||||||
|
@ObservedObject
|
||||||
|
var viewModel: FilterViewModel
|
||||||
|
private var onSelect: (FilterCoordinator.Parameters) -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
if viewModel.currentFilters.hasFilters {
|
||||||
|
Menu {
|
||||||
|
Button(role: .destructive) {
|
||||||
|
viewModel.currentFilters = .init()
|
||||||
|
} label: {
|
||||||
|
L10n.reset.text
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
FilterDrawerButton(systemName: "line.3.horizontal.decrease.circle.fill", activated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterDrawerButton(title: L10n.genres, activated: viewModel.currentFilters.genres != [])
|
||||||
|
.onSelect {
|
||||||
|
onSelect(.init(
|
||||||
|
title: L10n.genres,
|
||||||
|
viewModel: viewModel,
|
||||||
|
filter: \.genres,
|
||||||
|
selectorType: .multi
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterDrawerButton(title: L10n.tags, activated: viewModel.currentFilters.tags != [])
|
||||||
|
.onSelect {
|
||||||
|
onSelect(.init(
|
||||||
|
title: L10n.tags,
|
||||||
|
viewModel: viewModel,
|
||||||
|
filter: \.tags,
|
||||||
|
selectorType: .multi
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterDrawerButton(title: L10n.filters, activated: viewModel.currentFilters.filters != [])
|
||||||
|
.onSelect {
|
||||||
|
onSelect(.init(
|
||||||
|
title: L10n.filters,
|
||||||
|
viewModel: viewModel,
|
||||||
|
filter: \.filters,
|
||||||
|
selectorType: .multi
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Localize
|
||||||
|
FilterDrawerButton(title: "Order", activated: viewModel.currentFilters.sortOrder != [APISortOrder.ascending.filter])
|
||||||
|
.onSelect {
|
||||||
|
onSelect(.init(
|
||||||
|
title: "Order",
|
||||||
|
viewModel: viewModel,
|
||||||
|
filter: \.sortOrder,
|
||||||
|
selectorType: .single
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Localize
|
||||||
|
FilterDrawerButton(title: "Sort", activated: viewModel.currentFilters.sortBy != [SortBy.name.filter])
|
||||||
|
.onSelect {
|
||||||
|
onSelect(.init(
|
||||||
|
title: "Sort",
|
||||||
|
viewModel: viewModel,
|
||||||
|
filter: \.sortBy,
|
||||||
|
selectorType: .single
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FilterDrawerHStack {
|
||||||
|
init(viewModel: FilterViewModel) {
|
||||||
|
self.viewModel = viewModel
|
||||||
|
self.onSelect = { _ in }
|
||||||
|
}
|
||||||
|
|
||||||
|
func onSelect(_ onSelect: @escaping (FilterCoordinator.Parameters) -> Void) -> Self {
|
||||||
|
var copy = self
|
||||||
|
copy.onSelect = onSelect
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
}
|
|
@ -102,7 +102,18 @@ struct LibraryView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.navigationTitle(viewModel.parent?.displayName ?? "")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.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 {
|
.toolbar {
|
||||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||||
Button {
|
Button {
|
||||||
|
@ -120,18 +131,6 @@ struct LibraryView: View {
|
||||||
Image(systemName: "square.grid.2x2")
|
Image(systemName: "square.grid.2x2")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
|
||||||
router
|
|
||||||
.route(to: \.filter, (
|
|
||||||
filters: $viewModel.filters,
|
|
||||||
enabledFilterType: viewModel.enabledFilterType,
|
|
||||||
parentId: viewModel.library?.id ?? ""
|
|
||||||
))
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "line.horizontal.3.decrease.circle")
|
|
||||||
}
|
|
||||||
.foregroundColor(viewModel.filters == .default ? .accentColor : Color(UIColor.systemOrange))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,11 +33,13 @@ struct MediaView: View {
|
||||||
.onSelect { _ in
|
.onSelect { _ in
|
||||||
switch item.library.collectionType {
|
switch item.library.collectionType {
|
||||||
case "favorites":
|
case "favorites":
|
||||||
router.route(to: \.library, (viewModel: .init(filters: .favorites), title: ""))
|
router.route(to: \.library, .init(parent: item.library, type: .library, filters: .favorites))
|
||||||
|
case "folders":
|
||||||
|
router.route(to: \.library, .init(parent: item.library, type: .folders, filters: .init()))
|
||||||
case "liveTV":
|
case "liveTV":
|
||||||
router.route(to: \.liveTV)
|
router.route(to: \.liveTV)
|
||||||
default:
|
default:
|
||||||
router.route(to: \.library, (viewModel: .init(library: item.library), title: ""))
|
router.route(to: \.library, .init(parent: item.library, type: .library, filters: .init()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.imageOverlay { _ in
|
.imageOverlay { _ in
|
||||||
|
|
|
@ -95,8 +95,18 @@ struct SearchView: View {
|
||||||
.onChange(of: searchText) { newText in
|
.onChange(of: searchText) { newText in
|
||||||
viewModel.search(with: newText)
|
viewModel.search(with: newText)
|
||||||
}
|
}
|
||||||
.searchable(text: $searchText, placement: .navigationBarDrawer, prompt: L10n.search)
|
|
||||||
.navigationTitle(L10n.search)
|
.navigationTitle(L10n.search)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.navBarDrawer {
|
||||||
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
FilterDrawerHStack(viewModel: viewModel.filterViewModel)
|
||||||
|
.onSelect { filterCoordinatorParameters in
|
||||||
|
router.route(to: \.filter, filterCoordinatorParameters)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.padding(.vertical, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: L10n.search)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ extension UserSignInView {
|
||||||
DisclosureGroup {
|
DisclosureGroup {
|
||||||
SecureField(L10n.password, text: $enteredPassword)
|
SecureField(L10n.password, text: $enteredPassword)
|
||||||
Button {
|
Button {
|
||||||
viewModel.signIn(username: publicUser.name ?? "--", password: enteredPassword)
|
viewModel.signIn(username: publicUser.name ?? .emptyDash, password: enteredPassword)
|
||||||
} label: {
|
} label: {
|
||||||
L10n.signIn.text
|
L10n.signIn.text
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ extension UserSignInView {
|
||||||
.frame(width: 50, height: 50)
|
.frame(width: 50, height: 50)
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
|
|
||||||
Text(publicUser.name ?? "--")
|
Text(publicUser.name ?? .emptyDash)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -532,11 +532,11 @@ extension LiveTVPlayerViewController {
|
||||||
viewModel = newViewModel
|
viewModel = newViewModel
|
||||||
|
|
||||||
if viewModel.streamType == .direct {
|
if viewModel.streamType == .direct {
|
||||||
LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? "--")")
|
LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
} else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] {
|
} else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] {
|
||||||
LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? "--")")
|
LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
} else {
|
} else {
|
||||||
LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? "--")")
|
LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -351,7 +351,7 @@ struct VLCPlayerOverlayView: View {
|
||||||
viewModel.playerOverlayDelegate?.didSelectChapters()
|
viewModel.playerOverlayDelegate?.didSelectChapters()
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
Text(currentChapter.name ?? "--")
|
Text(currentChapter.name ?? .emptyDash)
|
||||||
Image(systemName: "chevron.right")
|
Image(systemName: "chevron.right")
|
||||||
}
|
}
|
||||||
.font(.system(size: 16, weight: .semibold, design: .default))
|
.font(.system(size: 16, weight: .semibold, design: .default))
|
||||||
|
|
|
@ -615,11 +615,11 @@ extension VLCPlayerViewController {
|
||||||
viewModel = newViewModel
|
viewModel = newViewModel
|
||||||
|
|
||||||
if viewModel.streamType == .direct {
|
if viewModel.streamType == .direct {
|
||||||
LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? "--")")
|
LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
} else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] {
|
} else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] {
|
||||||
LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? "--")")
|
LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
} else {
|
} else {
|
||||||
LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? "--")")
|
LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue