wip
This commit is contained in:
parent
be7be050d6
commit
0dd592df02
|
@ -7,10 +7,11 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import JellyfinAPI
|
||||||
import Stinsen
|
import Stinsen
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
typealias FilterCoordinatorParams = (filters: Binding<LibraryFilters>, enabledFilterType: [FilterType], parentId: String)
|
typealias FilterCoordinatorParams = (libraryItem: BaseItemDto, filters: Binding<LibraryFilters>, enabledFilterType: [FilterType])
|
||||||
|
|
||||||
final class FilterCoordinator: NavigationCoordinatable {
|
final class FilterCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
|
@ -19,19 +20,19 @@ final class FilterCoordinator: NavigationCoordinatable {
|
||||||
@Root
|
@Root
|
||||||
var start = makeStart
|
var start = makeStart
|
||||||
|
|
||||||
|
let libraryItem: BaseItemDto
|
||||||
@Binding
|
@Binding
|
||||||
var filters: LibraryFilters
|
var filters: LibraryFilters
|
||||||
var enabledFilterType: [FilterType]
|
var enabledFilterType: [FilterType]
|
||||||
var parentId: String = ""
|
|
||||||
|
|
||||||
init(filters: Binding<LibraryFilters>, enabledFilterType: [FilterType], parentId: String) {
|
init(libraryItem: BaseItemDto, filters: Binding<LibraryFilters>, enabledFilterType: [FilterType]) {
|
||||||
|
self.libraryItem = libraryItem
|
||||||
_filters = filters
|
_filters = filters
|
||||||
self.enabledFilterType = enabledFilterType
|
self.enabledFilterType = enabledFilterType
|
||||||
self.parentId = parentId
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeStart() -> some View {
|
func makeStart() -> some View {
|
||||||
LibraryFilterView(filters: $filters, enabledFilterType: enabledFilterType, parentId: parentId)
|
LibraryFilterView(filters: $filters, enabledFilterType: enabledFilterType, parentId: libraryItem.id!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,6 @@ import JellyfinAPI
|
||||||
import Stinsen
|
import Stinsen
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
typealias LibraryCoordinatorParams = (viewModel: LibraryViewModel, title: String)
|
|
||||||
|
|
||||||
final class LibraryCoordinator: NavigationCoordinatable {
|
final class LibraryCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \LibraryCoordinator.start)
|
let stack = NavigationStack(initial: \LibraryCoordinator.start)
|
||||||
|
@ -29,16 +27,14 @@ final class LibraryCoordinator: NavigationCoordinatable {
|
||||||
var modalItem = makeModalItem
|
var modalItem = makeModalItem
|
||||||
|
|
||||||
let viewModel: LibraryViewModel
|
let viewModel: LibraryViewModel
|
||||||
let title: String
|
|
||||||
|
|
||||||
init(viewModel: LibraryViewModel, title: String) {
|
init(viewModel: LibraryViewModel) {
|
||||||
self.viewModel = viewModel
|
self.viewModel = viewModel
|
||||||
self.title = title
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeStart() -> some View {
|
func makeStart() -> some View {
|
||||||
LibraryView(viewModel: self.viewModel, title: title)
|
LibraryView(viewModel: self.viewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeSearch(viewModel: LibrarySearchViewModel) -> SearchCoordinator {
|
func makeSearch(viewModel: LibrarySearchViewModel) -> SearchCoordinator {
|
||||||
|
|
|
@ -34,6 +34,6 @@ final class MovieLibrariesCoordinator: NavigationCoordinatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeLibrary(library: BaseItemDto) -> LibraryCoordinator {
|
func makeLibrary(library: BaseItemDto) -> LibraryCoordinator {
|
||||||
LibraryCoordinator(viewModel: LibraryViewModel(parentID: library.id), title: library.title)
|
// LibraryCoordinator(viewModel: LibraryViewModel(parentID: library.id), title: library.title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,6 @@ final class TVLibrariesCoordinator: NavigationCoordinatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeLibrary(library: BaseItemDto) -> LibraryCoordinator {
|
func makeLibrary(library: BaseItemDto) -> LibraryCoordinator {
|
||||||
LibraryCoordinator(viewModel: LibraryViewModel(parentID: library.id), title: library.title)
|
LibraryCoordinator(viewModel: .init(libraryItem: <#T##BaseItemDto#>))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -206,6 +206,7 @@ public extension BaseItemDto {
|
||||||
case episode = "Episode"
|
case episode = "Episode"
|
||||||
case series = "Series"
|
case series = "Series"
|
||||||
case boxset = "BoxSet"
|
case boxset = "BoxSet"
|
||||||
|
case collectionFolder = "CollectionFolder"
|
||||||
|
|
||||||
case unknown
|
case unknown
|
||||||
|
|
||||||
|
@ -228,7 +229,7 @@ public extension BaseItemDto {
|
||||||
|
|
||||||
func portraitHeaderViewURL(maxWidth: Int) -> URL {
|
func portraitHeaderViewURL(maxWidth: Int) -> URL {
|
||||||
switch itemType {
|
switch itemType {
|
||||||
case .movie, .season, .series, .boxset:
|
case .movie, .season, .series, .boxset, .collectionFolder:
|
||||||
return getPrimaryImage(maxWidth: maxWidth)
|
return getPrimaryImage(maxWidth: maxWidth)
|
||||||
case .episode:
|
case .episode:
|
||||||
return getSeriesPrimaryImage(maxWidth: maxWidth)
|
return getSeriesPrimaryImage(maxWidth: maxWidth)
|
||||||
|
|
|
@ -13,6 +13,8 @@ final class LibraryListViewModel: ViewModel {
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var libraries: [BaseItemDto] = []
|
var libraries: [BaseItemDto] = []
|
||||||
|
@Published
|
||||||
|
var libraryRandomItems: [BaseItemDto: BaseItemDto] = [:]
|
||||||
|
|
||||||
// temp
|
// temp
|
||||||
var withFavorites = LibraryFilters(filters: [.isFavorite], sortOrder: [], withGenres: [], sortBy: [])
|
var withFavorites = LibraryFilters(filters: [.isFavorite], sortOrder: [], withGenres: [], sortBy: [])
|
||||||
|
@ -29,8 +31,34 @@ final class LibraryListViewModel: ViewModel {
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
self.handleAPIRequestError(completion: completion)
|
self.handleAPIRequestError(completion: completion)
|
||||||
}, receiveValue: { response in
|
}, receiveValue: { response in
|
||||||
self.libraries = response.items ?? []
|
if let libraries = response.items {
|
||||||
|
self.libraries = libraries
|
||||||
|
|
||||||
|
for library in libraries {
|
||||||
|
self.getRandomLibraryItem(for: library)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Library random item
|
||||||
|
|
||||||
|
func getRandomLibraryItem(for library: BaseItemDto) {
|
||||||
|
guard library.itemType == .collectionFolder else { return }
|
||||||
|
|
||||||
|
ItemsAPI.getItems(userId: SessionManager.main.currentLogin.user.id,
|
||||||
|
limit: 1,
|
||||||
|
parentId: library.id)
|
||||||
|
.sink { completion in
|
||||||
|
self.handleAPIRequestError(completion: completion)
|
||||||
|
} receiveValue: { result in
|
||||||
|
if let item = result.items?.first {
|
||||||
|
self.libraryRandomItems[library] = item
|
||||||
|
} else {
|
||||||
|
self.libraryRandomItems[library] = library
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import SwiftUICollection
|
import SwiftUICollection
|
||||||
|
import UIKit
|
||||||
|
|
||||||
typealias LibraryRow = CollectionRow<Int, LibraryRowCell>
|
typealias LibraryRow = CollectionRow<Int, LibraryRowCell>
|
||||||
|
|
||||||
|
@ -20,15 +21,11 @@ struct LibraryRowCell: Hashable {
|
||||||
}
|
}
|
||||||
|
|
||||||
final class LibraryViewModel: ViewModel {
|
final class LibraryViewModel: ViewModel {
|
||||||
var parentID: String?
|
|
||||||
var person: BaseItemPerson?
|
|
||||||
var genre: NameGuidPair?
|
|
||||||
var studio: NameGuidPair?
|
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var items = [BaseItemDto]()
|
var items: [BaseItemDto] = []
|
||||||
@Published
|
@Published
|
||||||
var rows = [LibraryRow]()
|
var rows: [LibraryRow] = []
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var totalPages = 0
|
var totalPages = 0
|
||||||
|
@ -36,15 +33,17 @@ final class LibraryViewModel: ViewModel {
|
||||||
var currentPage = 0
|
var currentPage = 0
|
||||||
@Published
|
@Published
|
||||||
var hasNextPage = false
|
var hasNextPage = false
|
||||||
@Published
|
|
||||||
var hasPreviousPage = false
|
|
||||||
|
|
||||||
// temp
|
// temp
|
||||||
@Published
|
@Published
|
||||||
var filters: LibraryFilters
|
var filters: LibraryFilters
|
||||||
|
|
||||||
|
let libraryItem: BaseItemDto
|
||||||
|
var person: BaseItemPerson?
|
||||||
|
var genre: NameGuidPair?
|
||||||
|
var studio: NameGuidPair?
|
||||||
private let columns: Int
|
private let columns: Int
|
||||||
private var libraries = [BaseItemDto]()
|
private let pageItemSize: Int
|
||||||
|
|
||||||
var enabledFilterType: [FilterType] {
|
var enabledFilterType: [FilterType] {
|
||||||
if genre == nil {
|
if genre == nil {
|
||||||
|
@ -54,80 +53,40 @@ final class LibraryViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(parentID: String? = nil,
|
init(libraryItem: BaseItemDto,
|
||||||
person: BaseItemPerson? = nil,
|
person: BaseItemPerson? = nil,
|
||||||
genre: NameGuidPair? = nil,
|
genre: NameGuidPair? = nil,
|
||||||
studio: NameGuidPair? = nil,
|
studio: NameGuidPair? = nil,
|
||||||
filters: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], sortBy: [.name]),
|
filters: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], sortBy: [.name]),
|
||||||
columns: Int = 7)
|
columns: Int = 7)
|
||||||
{
|
{
|
||||||
self.parentID = parentID
|
self.libraryItem = libraryItem
|
||||||
self.person = person
|
self.person = person
|
||||||
self.genre = genre
|
self.genre = genre
|
||||||
self.studio = studio
|
self.studio = studio
|
||||||
self.filters = filters
|
self.filters = filters
|
||||||
self.columns = columns
|
self.columns = columns
|
||||||
|
|
||||||
|
// Size is typical size of portrait items
|
||||||
|
self.pageItemSize = UIScreen.itemsFillableOnScreen(width: 130, height: 185)
|
||||||
|
|
||||||
|
print("Page item size: \(pageItemSize)")
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
$filters
|
$filters
|
||||||
.sink(receiveValue: requestItems(with:))
|
.sink(receiveValue: { newFilters in
|
||||||
.store(in: &cancellables)
|
self.requestItemsAsync(with: newFilters, replaceCurrentItems: true)
|
||||||
}
|
|
||||||
|
|
||||||
func requestItems(with filters: LibraryFilters) {
|
|
||||||
let personIDs: [String] = [person].compactMap(\.?.id)
|
|
||||||
let studioIDs: [String] = [studio].compactMap(\.?.id)
|
|
||||||
let genreIDs: [String]
|
|
||||||
if filters.withGenres.isEmpty {
|
|
||||||
genreIDs = [genre].compactMap(\.?.id)
|
|
||||||
} else {
|
|
||||||
genreIDs = filters.withGenres.compactMap(\.id)
|
|
||||||
}
|
|
||||||
let sortBy = filters.sortBy.map(\.rawValue)
|
|
||||||
|
|
||||||
ItemsAPI.getItemsByUserId(userId: SessionManager.main.currentLogin.user.id,
|
|
||||||
startIndex: currentPage * 100,
|
|
||||||
limit: 100,
|
|
||||||
recursive: true,
|
|
||||||
searchTerm: nil,
|
|
||||||
sortOrder: filters.sortOrder,
|
|
||||||
parentId: parentID,
|
|
||||||
fields: [
|
|
||||||
.primaryImageAspectRatio,
|
|
||||||
.seriesPrimaryImage,
|
|
||||||
.seasonUserData,
|
|
||||||
.overview,
|
|
||||||
.genres,
|
|
||||||
.people,
|
|
||||||
.chapters,
|
|
||||||
],
|
|
||||||
includeItemTypes: filters.filters
|
|
||||||
.contains(.isFavorite) ? ["Movie", "Series", "Season", "Episode"] : ["Movie", "Series", "BoxSet"],
|
|
||||||
filters: filters.filters,
|
|
||||||
sortBy: sortBy,
|
|
||||||
tags: filters.tags,
|
|
||||||
enableUserData: true,
|
|
||||||
personIds: personIDs,
|
|
||||||
studioIds: studioIDs,
|
|
||||||
genreIds: genreIDs,
|
|
||||||
enableImages: true)
|
|
||||||
.trackActivity(loading)
|
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
|
||||||
self?.handleAPIRequestError(completion: completion)
|
|
||||||
}, receiveValue: { [weak self] response in
|
|
||||||
LogManager.shared.log.debug("Received \(String(response.items?.count ?? 0)) items in library \(self?.parentID ?? "nil")")
|
|
||||||
guard let self = self else { return }
|
|
||||||
let totalPages = ceil(Double(response.totalRecordCount ?? 0) / 100.0)
|
|
||||||
self.totalPages = Int(totalPages)
|
|
||||||
self.hasPreviousPage = self.currentPage > 0
|
|
||||||
self.hasNextPage = self.currentPage < self.totalPages - 1
|
|
||||||
self.items = response.items ?? []
|
|
||||||
self.rows = self.calculateRows(for: self.items)
|
|
||||||
})
|
})
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestItemsAsync(with filters: LibraryFilters) {
|
func requestItemsAsync(with filters: LibraryFilters, replaceCurrentItems: Bool = false) {
|
||||||
|
|
||||||
|
if replaceCurrentItems {
|
||||||
|
self.items = []
|
||||||
|
}
|
||||||
|
|
||||||
let personIDs: [String] = [person].compactMap(\.?.id)
|
let personIDs: [String] = [person].compactMap(\.?.id)
|
||||||
let studioIDs: [String] = [studio].compactMap(\.?.id)
|
let studioIDs: [String] = [studio].compactMap(\.?.id)
|
||||||
let genreIDs: [String]
|
let genreIDs: [String]
|
||||||
|
@ -138,12 +97,12 @@ final class LibraryViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
let sortBy = filters.sortBy.map(\.rawValue)
|
let sortBy = filters.sortBy.map(\.rawValue)
|
||||||
|
|
||||||
ItemsAPI.getItemsByUserId(userId: SessionManager.main.currentLogin.user.id, startIndex: currentPage * 100,
|
ItemsAPI.getItemsByUserId(userId: SessionManager.main.currentLogin.user.id, startIndex: currentPage * pageItemSize,
|
||||||
limit: 100,
|
limit: pageItemSize,
|
||||||
recursive: true,
|
recursive: true,
|
||||||
searchTerm: nil,
|
searchTerm: nil,
|
||||||
sortOrder: filters.sortOrder,
|
sortOrder: filters.sortOrder,
|
||||||
parentId: parentID,
|
parentId: libraryItem.id,
|
||||||
fields: [
|
fields: [
|
||||||
.primaryImageAspectRatio,
|
.primaryImageAspectRatio,
|
||||||
.seriesPrimaryImage,
|
.seriesPrimaryImage,
|
||||||
|
@ -163,13 +122,15 @@ final class LibraryViewModel: ViewModel {
|
||||||
studioIds: studioIDs,
|
studioIds: studioIDs,
|
||||||
genreIds: genreIDs,
|
genreIds: genreIDs,
|
||||||
enableImages: true)
|
enableImages: true)
|
||||||
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
self?.handleAPIRequestError(completion: completion)
|
self?.handleAPIRequestError(completion: completion)
|
||||||
}, receiveValue: { [weak self] response in
|
}, receiveValue: { [weak self] response in
|
||||||
|
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
let totalPages = ceil(Double(response.totalRecordCount ?? 0) / 100.0)
|
let totalPages = ceil(Double(response.totalRecordCount ?? 0) / Double(self.pageItemSize))
|
||||||
|
|
||||||
self.totalPages = Int(totalPages)
|
self.totalPages = Int(totalPages)
|
||||||
self.hasPreviousPage = self.currentPage > 0
|
|
||||||
self.hasNextPage = self.currentPage < self.totalPages - 1
|
self.hasNextPage = self.currentPage < self.totalPages - 1
|
||||||
self.items.append(contentsOf: response.items ?? [])
|
self.items.append(contentsOf: response.items ?? [])
|
||||||
self.rows = self.calculateRows(for: self.items)
|
self.rows = self.calculateRows(for: self.items)
|
||||||
|
@ -177,21 +138,12 @@ final class LibraryViewModel: ViewModel {
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestNextPage() {
|
|
||||||
currentPage += 1
|
|
||||||
requestItems(with: filters)
|
|
||||||
}
|
|
||||||
|
|
||||||
func requestNextPageAsync() {
|
func requestNextPageAsync() {
|
||||||
currentPage += 1
|
currentPage += 1
|
||||||
requestItemsAsync(with: filters)
|
requestItemsAsync(with: filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestPreviousPage() {
|
// tvOS calculations for collection view
|
||||||
currentPage -= 1
|
|
||||||
requestItems(with: filters)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func calculateRows(for itemList: [BaseItemDto]) -> [LibraryRow] {
|
private func calculateRows(for itemList: [BaseItemDto]) -> [LibraryRow] {
|
||||||
guard !itemList.isEmpty else { return [] }
|
guard !itemList.isEmpty else { return [] }
|
||||||
let rowCount = itemList.count / columns
|
let rowCount = itemList.count / columns
|
||||||
|
@ -220,3 +172,14 @@ final class LibraryViewModel: ViewModel {
|
||||||
return calculatedRows
|
return calculatedRows
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension UIScreen {
|
||||||
|
|
||||||
|
static func itemsFillableOnScreen(width: CGFloat, height: CGFloat) -> Int {
|
||||||
|
|
||||||
|
let screenSize = UIScreen.main.bounds.height * UIScreen.main.bounds.width
|
||||||
|
let itemSize = width * height
|
||||||
|
|
||||||
|
return Int(screenSize / itemSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ struct LibraryView: View {
|
||||||
var libraryRouter: LibraryCoordinator.Router
|
var libraryRouter: LibraryCoordinator.Router
|
||||||
@StateObject
|
@StateObject
|
||||||
var viewModel: LibraryViewModel
|
var viewModel: LibraryViewModel
|
||||||
var title: String
|
|
||||||
|
|
||||||
// MARK: tracks for grid
|
// MARK: tracks for grid
|
||||||
|
|
||||||
|
@ -91,6 +90,3 @@ struct LibraryView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// stream BM^S by nicki!
|
|
||||||
//
|
|
||||||
|
|
|
@ -75,7 +75,6 @@
|
||||||
5377CBF9263B596B003A4E83 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5377CBF8263B596B003A4E83 /* Assets.xcassets */; };
|
5377CBF9263B596B003A4E83 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5377CBF8263B596B003A4E83 /* Assets.xcassets */; };
|
||||||
5377CBFC263B596B003A4E83 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5377CBFB263B596B003A4E83 /* Preview Assets.xcassets */; };
|
5377CBFC263B596B003A4E83 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5377CBFB263B596B003A4E83 /* Preview Assets.xcassets */; };
|
||||||
5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5389276D263C25100035E14B /* ContinueWatchingView.swift */; };
|
5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5389276D263C25100035E14B /* ContinueWatchingView.swift */; };
|
||||||
53892770263C25230035E14B /* NextUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5389276F263C25230035E14B /* NextUpView.swift */; };
|
|
||||||
5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5389277B263CC3DB0035E14B /* BlurHashDecode.swift */; };
|
5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5389277B263CC3DB0035E14B /* BlurHashDecode.swift */; };
|
||||||
53913BEF26D323FE00EB3286 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 53913BC926D323FE00EB3286 /* Localizable.strings */; };
|
53913BEF26D323FE00EB3286 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 53913BC926D323FE00EB3286 /* Localizable.strings */; };
|
||||||
53913BF026D323FE00EB3286 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 53913BC926D323FE00EB3286 /* Localizable.strings */; };
|
53913BF026D323FE00EB3286 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 53913BC926D323FE00EB3286 /* Localizable.strings */; };
|
||||||
|
@ -136,7 +135,7 @@
|
||||||
53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DF641D263D9C0600A7CD1A /* LibraryView.swift */; };
|
53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DF641D263D9C0600A7CD1A /* LibraryView.swift */; };
|
||||||
53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E4E648263F725B00F67C6B /* MultiSelectorView.swift */; };
|
53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E4E648263F725B00F67C6B /* MultiSelectorView.swift */; };
|
||||||
53EE24E6265060780068F029 /* LibrarySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EE24E5265060780068F029 /* LibrarySearchView.swift */; };
|
53EE24E6265060780068F029 /* LibrarySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EE24E5265060780068F029 /* LibrarySearchView.swift */; };
|
||||||
53F866442687A45F00DCD1D7 /* PortraitItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53F866432687A45F00DCD1D7 /* PortraitItemView.swift */; };
|
53F866442687A45F00DCD1D7 /* PortraitItemButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53F866432687A45F00DCD1D7 /* PortraitItemButton.swift */; };
|
||||||
53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FF7F29263CF3F500585C35 /* LatestMediaView.swift */; };
|
53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FF7F29263CF3F500585C35 /* LatestMediaView.swift */; };
|
||||||
5D1603FC278A3D5800D22B99 /* SubtitleSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D1603FB278A3D5700D22B99 /* SubtitleSize.swift */; };
|
5D1603FC278A3D5800D22B99 /* SubtitleSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D1603FB278A3D5700D22B99 /* SubtitleSize.swift */; };
|
||||||
5D1603FD278A40DB00D22B99 /* SubtitleSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D1603FB278A3D5700D22B99 /* SubtitleSize.swift */; };
|
5D1603FD278A40DB00D22B99 /* SubtitleSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D1603FB278A3D5700D22B99 /* SubtitleSize.swift */; };
|
||||||
|
@ -276,6 +275,7 @@
|
||||||
E10EAA51277BBCC4000269ED /* CGSizeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10EAA4E277BBCC4000269ED /* CGSizeExtensions.swift */; };
|
E10EAA51277BBCC4000269ED /* CGSizeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10EAA4E277BBCC4000269ED /* CGSizeExtensions.swift */; };
|
||||||
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 */; };
|
||||||
|
E111DE222790BB46008118A3 /* DetectBottomScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E111DE212790BB46008118A3 /* DetectBottomScrollView.swift */; };
|
||||||
E11B1B6C2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */; };
|
E11B1B6C2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */; };
|
||||||
E11B1B6D2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */; };
|
E11B1B6D2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */; };
|
||||||
E11B1B6E2718CDBA006DA3E8 /* JellyfinAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */; };
|
E11B1B6E2718CDBA006DA3E8 /* JellyfinAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */; };
|
||||||
|
@ -549,7 +549,6 @@
|
||||||
5377CBFB263B596B003A4E83 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
5377CBFB263B596B003A4E83 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||||
5377CC02263B596B003A4E83 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
5377CC02263B596B003A4E83 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
5389276D263C25100035E14B /* ContinueWatchingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueWatchingView.swift; sourceTree = "<group>"; };
|
5389276D263C25100035E14B /* ContinueWatchingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueWatchingView.swift; sourceTree = "<group>"; };
|
||||||
5389276F263C25230035E14B /* NextUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextUpView.swift; sourceTree = "<group>"; };
|
|
||||||
5389277B263CC3DB0035E14B /* BlurHashDecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = "<group>"; };
|
5389277B263CC3DB0035E14B /* BlurHashDecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = "<group>"; };
|
||||||
53913BCA26D323FE00EB3286 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = Localizable.strings; sourceTree = "<group>"; };
|
53913BCA26D323FE00EB3286 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = Localizable.strings; sourceTree = "<group>"; };
|
||||||
53913BCD26D323FE00EB3286 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = Localizable.strings; sourceTree = "<group>"; };
|
53913BCD26D323FE00EB3286 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = Localizable.strings; sourceTree = "<group>"; };
|
||||||
|
@ -577,7 +576,7 @@
|
||||||
53E4E646263F6CF100F67C6B /* LibraryFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryFilterView.swift; sourceTree = "<group>"; };
|
53E4E646263F6CF100F67C6B /* LibraryFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryFilterView.swift; sourceTree = "<group>"; };
|
||||||
53E4E648263F725B00F67C6B /* MultiSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiSelectorView.swift; sourceTree = "<group>"; };
|
53E4E648263F725B00F67C6B /* MultiSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiSelectorView.swift; sourceTree = "<group>"; };
|
||||||
53EE24E5265060780068F029 /* LibrarySearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchView.swift; sourceTree = "<group>"; };
|
53EE24E5265060780068F029 /* LibrarySearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchView.swift; sourceTree = "<group>"; };
|
||||||
53F866432687A45F00DCD1D7 /* PortraitItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortraitItemView.swift; sourceTree = "<group>"; };
|
53F866432687A45F00DCD1D7 /* PortraitItemButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortraitItemButton.swift; sourceTree = "<group>"; };
|
||||||
53FF7F29263CF3F500585C35 /* LatestMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestMediaView.swift; sourceTree = "<group>"; };
|
53FF7F29263CF3F500585C35 /* LatestMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestMediaView.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>"; };
|
||||||
|
@ -672,6 +671,7 @@
|
||||||
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>"; };
|
||||||
|
E111DE212790BB46008118A3 /* DetectBottomScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectBottomScrollView.swift; sourceTree = "<group>"; };
|
||||||
E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinAPIError.swift; sourceTree = "<group>"; };
|
E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinAPIError.swift; sourceTree = "<group>"; };
|
||||||
E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailCoordinator.swift; sourceTree = "<group>"; };
|
E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailCoordinator.swift; sourceTree = "<group>"; };
|
||||||
E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaStreamExtension.swift; sourceTree = "<group>"; };
|
E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaStreamExtension.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1214,11 +1214,12 @@
|
||||||
53F866422687A45400DCD1D7 /* Components */ = {
|
53F866422687A45400DCD1D7 /* Components */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E111DE212790BB46008118A3 /* DetectBottomScrollView.swift */,
|
||||||
E176DE6E278E3522001EFD8D /* EpisodesRowView */,
|
E176DE6E278E3522001EFD8D /* EpisodesRowView */,
|
||||||
E1AD105B26D9ABDD003E4A08 /* PillHStackView.swift */,
|
E1AD105B26D9ABDD003E4A08 /* PillHStackView.swift */,
|
||||||
E1AD105526D981CE003E4A08 /* PortraitHStackView.swift */,
|
E1AD105526D981CE003E4A08 /* PortraitHStackView.swift */,
|
||||||
C4BE076D2720FEA8003F4AD1 /* PortraitItemElement.swift */,
|
C4BE076D2720FEA8003F4AD1 /* PortraitItemElement.swift */,
|
||||||
53F866432687A45F00DCD1D7 /* PortraitItemView.swift */,
|
53F866432687A45F00DCD1D7 /* PortraitItemButton.swift */,
|
||||||
E1AA331C2782541500F6439C /* PrimaryButtonView.swift */,
|
E1AA331C2782541500F6439C /* PrimaryButtonView.swift */,
|
||||||
E1EBCB41278BD174009FE6E9 /* TruncatedTextView.swift */,
|
E1EBCB41278BD174009FE6E9 /* TruncatedTextView.swift */,
|
||||||
);
|
);
|
||||||
|
@ -1454,7 +1455,6 @@
|
||||||
53DF641D263D9C0600A7CD1A /* LibraryView.swift */,
|
53DF641D263D9C0600A7CD1A /* LibraryView.swift */,
|
||||||
C4AE2C2F27498D2300AE13CF /* LiveTVHomeView.swift */,
|
C4AE2C2F27498D2300AE13CF /* LiveTVHomeView.swift */,
|
||||||
C4AE2C3127498D6A00AE13CF /* LiveTVProgramsView.swift */,
|
C4AE2C3127498D6A00AE13CF /* LiveTVProgramsView.swift */,
|
||||||
5389276F263C25230035E14B /* NextUpView.swift */,
|
|
||||||
E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */,
|
E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */,
|
||||||
E13DD3E427177D15009D4DAF /* ServerListView.swift */,
|
E13DD3E427177D15009D4DAF /* ServerListView.swift */,
|
||||||
E1E5D54A2783E26100692DFE /* SettingsView */,
|
E1E5D54A2783E26100692DFE /* SettingsView */,
|
||||||
|
@ -2294,7 +2294,7 @@
|
||||||
62E632DC267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */,
|
62E632DC267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */,
|
||||||
62C29E9F26D1016600C1D2E7 /* iOSMainCoordinator.swift in Sources */,
|
62C29E9F26D1016600C1D2E7 /* iOSMainCoordinator.swift in Sources */,
|
||||||
5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */,
|
5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */,
|
||||||
53F866442687A45F00DCD1D7 /* PortraitItemView.swift in Sources */,
|
53F866442687A45F00DCD1D7 /* PortraitItemButton.swift in Sources */,
|
||||||
E1AD105626D981CE003E4A08 /* PortraitHStackView.swift in Sources */,
|
E1AD105626D981CE003E4A08 /* PortraitHStackView.swift in Sources */,
|
||||||
62C29EA126D102A500C1D2E7 /* iOSMainTabCoordinator.swift in Sources */,
|
62C29EA126D102A500C1D2E7 /* iOSMainTabCoordinator.swift in Sources */,
|
||||||
C4BE076E2720FEA8003F4AD1 /* PortraitItemElement.swift in Sources */,
|
C4BE076E2720FEA8003F4AD1 /* PortraitItemElement.swift in Sources */,
|
||||||
|
@ -2309,6 +2309,7 @@
|
||||||
E13DD3EC27178A54009D4DAF /* UserSignInViewModel.swift in Sources */,
|
E13DD3EC27178A54009D4DAF /* UserSignInViewModel.swift in Sources */,
|
||||||
625CB5772678C34300530A6E /* ConnectToServerViewModel.swift in Sources */,
|
625CB5772678C34300530A6E /* ConnectToServerViewModel.swift in Sources */,
|
||||||
C4BE07852728446F003F4AD1 /* LiveTVChannelsViewModel.swift in Sources */,
|
C4BE07852728446F003F4AD1 /* LiveTVChannelsViewModel.swift in Sources */,
|
||||||
|
E111DE222790BB46008118A3 /* DetectBottomScrollView.swift in Sources */,
|
||||||
5D160403278A41FD00D22B99 /* VLCPlayer+subtitles.swift in Sources */,
|
5D160403278A41FD00D22B99 /* VLCPlayer+subtitles.swift in Sources */,
|
||||||
536D3D78267BD5C30004248C /* ViewModel.swift in Sources */,
|
536D3D78267BD5C30004248C /* ViewModel.swift in Sources */,
|
||||||
C4BE078B272844AF003F4AD1 /* LiveTVChannelsView.swift in Sources */,
|
C4BE078B272844AF003F4AD1 /* LiveTVChannelsView.swift in Sources */,
|
||||||
|
@ -2328,7 +2329,6 @@
|
||||||
E1C812BC277A8E5D00918266 /* PlaybackSpeed.swift in Sources */,
|
E1C812BC277A8E5D00918266 /* PlaybackSpeed.swift in Sources */,
|
||||||
E1E5D5492783CDD700692DFE /* OverlaySettingsView.swift in Sources */,
|
E1E5D5492783CDD700692DFE /* OverlaySettingsView.swift in Sources */,
|
||||||
E173DA5226D04AAF00CC4EB7 /* ColorExtension.swift in Sources */,
|
E173DA5226D04AAF00CC4EB7 /* ColorExtension.swift in Sources */,
|
||||||
53892770263C25230035E14B /* NextUpView.swift in Sources */,
|
|
||||||
E11D224227378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */,
|
E11D224227378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */,
|
||||||
6264E88C273850380081A12A /* Strings.swift in Sources */,
|
6264E88C273850380081A12A /* Strings.swift in Sources */,
|
||||||
C4BE0766271FC109003F4AD1 /* TVLibrariesViewModel.swift in Sources */,
|
C4BE0766271FC109003F4AD1 /* TVLibrariesViewModel.swift in Sources */,
|
||||||
|
@ -2804,7 +2804,7 @@
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 66;
|
CURRENT_PROJECT_VERSION = 66;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = TY84JMYEFE;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
EXCLUDED_ARCHS = "";
|
EXCLUDED_ARCHS = "";
|
||||||
|
@ -2841,7 +2841,7 @@
|
||||||
CURRENT_PROJECT_VERSION = 66;
|
CURRENT_PROJECT_VERSION = 66;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = TY84JMYEFE;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
EXCLUDED_ARCHS = "";
|
EXCLUDED_ARCHS = "";
|
||||||
|
@ -2872,7 +2872,7 @@
|
||||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 66;
|
CURRENT_PROJECT_VERSION = 66;
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = TY84JMYEFE;
|
||||||
INFOPLIST_FILE = WidgetExtension/Info.plist;
|
INFOPLIST_FILE = WidgetExtension/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
@ -2899,7 +2899,7 @@
|
||||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 66;
|
CURRENT_PROJECT_VERSION = 66;
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = TY84JMYEFE;
|
||||||
INFOPLIST_FILE = WidgetExtension/Info.plist;
|
INFOPLIST_FILE = WidgetExtension/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/56573373/swiftui-get-size-of-child
|
||||||
|
|
||||||
|
struct ChildSizeReader<Content: View>: View {
|
||||||
|
@Binding var size: CGSize
|
||||||
|
let content: () -> Content
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
content()
|
||||||
|
.background(
|
||||||
|
GeometryReader { proxy in
|
||||||
|
Color.clear
|
||||||
|
.preference(key: SizePreferenceKey.self, value: proxy.size)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.onPreferenceChange(SizePreferenceKey.self) { preferences in
|
||||||
|
self.size = preferences
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SizePreferenceKey: PreferenceKey {
|
||||||
|
typealias Value = CGSize
|
||||||
|
static var defaultValue: Value = .zero
|
||||||
|
|
||||||
|
static func reduce(value _: inout Value, nextValue: () -> Value) {
|
||||||
|
_ = nextValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ViewOffsetKey: PreferenceKey {
|
||||||
|
typealias Value = CGFloat
|
||||||
|
static var defaultValue = CGFloat.zero
|
||||||
|
static func reduce(value: inout Value, nextValue: () -> Value) {
|
||||||
|
value += nextValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DetectBottomScrollView<Content: View>: View {
|
||||||
|
private let spaceName = "scroll"
|
||||||
|
|
||||||
|
@State private var wholeSize: CGSize = .zero
|
||||||
|
@State private var scrollViewSize: CGSize = .zero
|
||||||
|
@State private var previousDidReachBottom = false
|
||||||
|
let content: () -> Content
|
||||||
|
let didReachBottom: (Bool) -> Void
|
||||||
|
|
||||||
|
init(content: @escaping () -> Content,
|
||||||
|
didReachBottom: @escaping (Bool) -> Void) {
|
||||||
|
self.content = content
|
||||||
|
self.didReachBottom = didReachBottom
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ChildSizeReader(size: $wholeSize) {
|
||||||
|
ScrollView {
|
||||||
|
ChildSizeReader(size: $scrollViewSize) {
|
||||||
|
content()
|
||||||
|
.background(
|
||||||
|
GeometryReader { proxy in
|
||||||
|
Color.clear.preference(
|
||||||
|
key: ViewOffsetKey.self,
|
||||||
|
value: -1 * proxy.frame(in: .named(spaceName)).origin.y
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.onPreferenceChange(
|
||||||
|
ViewOffsetKey.self,
|
||||||
|
perform: { value in
|
||||||
|
|
||||||
|
if value >= scrollViewSize.height - wholeSize.height {
|
||||||
|
if !previousDidReachBottom {
|
||||||
|
previousDidReachBottom = true
|
||||||
|
didReachBottom(true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if previousDidReachBottom {
|
||||||
|
previousDidReachBottom = false
|
||||||
|
didReachBottom(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.coordinateSpace(name: spaceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
//
|
||||||
|
// 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 PortraitItemButton<ItemType: PortraitImageStackable>: View {
|
||||||
|
|
||||||
|
let item: ItemType
|
||||||
|
let maxWidth: CGFloat
|
||||||
|
let horizontalAlignment: HorizontalAlignment
|
||||||
|
let textAlignment: TextAlignment
|
||||||
|
let selectedAction: (ItemType) -> Void
|
||||||
|
|
||||||
|
init(item: ItemType,
|
||||||
|
maxWidth: CGFloat = 110,
|
||||||
|
horizontalAlignment: HorizontalAlignment = .leading,
|
||||||
|
textAlignment: TextAlignment = .leading,
|
||||||
|
selectedAction: @escaping (ItemType) -> Void)
|
||||||
|
{
|
||||||
|
self.item = item
|
||||||
|
self.maxWidth = maxWidth
|
||||||
|
self.horizontalAlignment = horizontalAlignment
|
||||||
|
self.textAlignment = textAlignment
|
||||||
|
self.selectedAction = selectedAction
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button {
|
||||||
|
selectedAction(item)
|
||||||
|
} label: {
|
||||||
|
VStack(alignment: horizontalAlignment) {
|
||||||
|
ImageView(src: item.imageURLContsructor(maxWidth: Int(maxWidth)),
|
||||||
|
bh: item.blurHash,
|
||||||
|
failureInitials: item.failureInitials)
|
||||||
|
.portraitPoster(width: maxWidth)
|
||||||
|
.shadow(radius: 4, y: 2)
|
||||||
|
|
||||||
|
if item.showTitle {
|
||||||
|
Text(item.title)
|
||||||
|
.font(.footnote)
|
||||||
|
.fontWeight(.regular)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
.multilineTextAlignment(textAlignment)
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
.lineLimit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let description = item.subtitle {
|
||||||
|
Text(description)
|
||||||
|
.font(.caption)
|
||||||
|
.fontWeight(.medium)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.multilineTextAlignment(textAlignment)
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
.lineLimit(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(width: maxWidth)
|
||||||
|
}
|
||||||
|
.frame(alignment: .top)
|
||||||
|
.padding(.bottom)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,83 +0,0 @@
|
||||||
//
|
|
||||||
// Swiftfin is subject to the terms of the Mozilla Public
|
|
||||||
// License, v2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
|
||||||
//
|
|
||||||
|
|
||||||
import JellyfinAPI
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct PortraitItemView: View {
|
|
||||||
var item: BaseItemDto
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
ImageView(src: item.type != "Episode" ? item.getPrimaryImage(maxWidth: 100) : item.getSeriesPrimaryImage(maxWidth: 100),
|
|
||||||
bh: item.type != "Episode" ? item.getPrimaryImageBlurHash() : item.getSeriesPrimaryImageBlurHash())
|
|
||||||
.frame(width: 100, height: 150)
|
|
||||||
.cornerRadius(10)
|
|
||||||
.shadow(radius: 4, y: 2)
|
|
||||||
.shadow(radius: 4, y: 2)
|
|
||||||
.overlay(Rectangle()
|
|
||||||
.fill(Color.jellyfinPurple)
|
|
||||||
.frame(width: CGFloat(item.userData?.playedPercentage ?? 0), height: 7)
|
|
||||||
.padding(0), alignment: .bottomLeading)
|
|
||||||
.overlay(ZStack {
|
|
||||||
if item.userData?.isFavorite ?? false {
|
|
||||||
Image(systemName: "circle.fill")
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.opacity(0.6)
|
|
||||||
Image(systemName: "heart.fill")
|
|
||||||
.foregroundColor(Color(.systemRed))
|
|
||||||
.font(.system(size: 10))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.leading, 2)
|
|
||||||
.padding(.bottom, item.userData?.playedPercentage == nil ? 2 : 9)
|
|
||||||
.opacity(1), alignment: .bottomLeading)
|
|
||||||
.overlay(ZStack {
|
|
||||||
if item.userData?.played ?? false {
|
|
||||||
Image(systemName: "checkmark.circle.fill")
|
|
||||||
.foregroundColor(.accentColor)
|
|
||||||
.background(Color(.white))
|
|
||||||
.clipShape(Circle().scale(0.8))
|
|
||||||
} else {
|
|
||||||
if item.userData?.unplayedItemCount != nil {
|
|
||||||
Capsule()
|
|
||||||
.fill(Color.accentColor)
|
|
||||||
.frame(minWidth: 20, minHeight: 20, maxHeight: 20)
|
|
||||||
Text(String(item.userData!.unplayedItemCount ?? 0))
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.font(.caption2)
|
|
||||||
.padding(2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.padding(2)
|
|
||||||
.fixedSize()
|
|
||||||
.opacity(1), alignment: .topTrailing).opacity(1)
|
|
||||||
Text(item.seriesName ?? item.name ?? "")
|
|
||||||
.font(.caption)
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
.foregroundColor(.primary)
|
|
||||||
.lineLimit(1)
|
|
||||||
if item.type == "Movie" || item.type == "Series" {
|
|
||||||
Text("\(String(item.productionYear ?? 0)) • \(item.officialRating ?? "N/A")")
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
.font(.caption)
|
|
||||||
.fontWeight(.medium)
|
|
||||||
} else if item.type == "Season" {
|
|
||||||
Text("\(item.name ?? "") • \(String(item.productionYear ?? 0))")
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
.font(.caption)
|
|
||||||
.fontWeight(.medium)
|
|
||||||
} else {
|
|
||||||
Text(L10n.seasonAndEpisode(String(item.parentIndexNumber ?? 0), String(item.indexNumber ?? 0)))
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
.font(.caption)
|
|
||||||
.fontWeight(.medium)
|
|
||||||
}
|
|
||||||
}.frame(width: 100)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -90,9 +90,9 @@ struct HomeView: View {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
homeRouter
|
homeRouter
|
||||||
.route(to: \.library, (viewModel: .init(parentID: library.id!,
|
// .route(to: \.library, (viewModel: .init(parentID: library.id!,
|
||||||
filters: viewModel.recentFilterSet),
|
// filters: viewModel.recentFilterSet),
|
||||||
title: library.name ?? ""))
|
// title: library.name ?? ""))
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
L10n.seeAll.text.font(.subheadline).fontWeight(.bold)
|
L10n.seeAll.text.font(.subheadline).fontWeight(.bold)
|
||||||
|
|
|
@ -23,7 +23,6 @@ struct LibraryListView: View {
|
||||||
libraryListRouter.route(to: \.library,
|
libraryListRouter.route(to: \.library,
|
||||||
(viewModel: LibraryViewModel(filters: viewModel.withFavorites), title: L10n.favorites))
|
(viewModel: LibraryViewModel(filters: viewModel.withFavorites), title: L10n.favorites))
|
||||||
} label: {
|
} label: {
|
||||||
ZStack {
|
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
L10n.yourFavorites.text
|
L10n.yourFavorites.text
|
||||||
|
@ -32,14 +31,12 @@ struct LibraryListView: View {
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
.frame(height: 100)
|
||||||
.padding(16)
|
|
||||||
.background(Color.white)
|
.background(Color.white)
|
||||||
.frame(minWidth: 100, maxWidth: .infinity)
|
|
||||||
}
|
}
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.shadow(radius: 5)
|
.shadow(radius: 5)
|
||||||
.padding(.bottom, 5)
|
.padding()
|
||||||
|
|
||||||
if !viewModel.isLoading {
|
if !viewModel.isLoading {
|
||||||
|
|
||||||
|
@ -62,17 +59,17 @@ struct LibraryListView: View {
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
}.padding(32)
|
}
|
||||||
}.background(Color.black)
|
}
|
||||||
.frame(minWidth: 100, maxWidth: .infinity)
|
.background(Color.black)
|
||||||
.frame(height: 100)
|
.frame(height: 100)
|
||||||
}
|
}
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.shadow(radius: 5)
|
.shadow(radius: 5)
|
||||||
.padding(.bottom, 5)
|
.padding()
|
||||||
}
|
}
|
||||||
|
|
||||||
ForEach(viewModel.libraries, id: \.id) { library in
|
ForEach(Array(viewModel.libraryRandomItems.keys), id: \.id) { library in
|
||||||
if library.collectionType ?? "" == "movies" || library.collectionType ?? "" == "tvshows" {
|
if library.collectionType ?? "" == "movies" || library.collectionType ?? "" == "tvshows" {
|
||||||
Button {
|
Button {
|
||||||
libraryListRouter.route(to: \.library,
|
libraryListRouter.route(to: \.library,
|
||||||
|
@ -80,25 +77,24 @@ struct LibraryListView: View {
|
||||||
title: library.name ?? ""))
|
title: library.name ?? ""))
|
||||||
} label: {
|
} label: {
|
||||||
ZStack {
|
ZStack {
|
||||||
ImageView(src: library.getPrimaryImage(maxWidth: 500), bh: library.getPrimaryImageBlurHash())
|
// ImageView(src: library.getPrimaryImage(maxWidth: 500), bh: library.getPrimaryImageBlurHash())
|
||||||
.opacity(0.4)
|
// .opacity(0.4)
|
||||||
HStack {
|
|
||||||
Spacer()
|
ImageView(src: viewModel.libraryRandomItems[library]!.getBackdropImage(maxWidth: 500))
|
||||||
|
|
||||||
VStack {
|
VStack {
|
||||||
Text(library.name ?? "")
|
Text(library.name ?? "")
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
}
|
}
|
||||||
Spacer()
|
}
|
||||||
}.padding(32)
|
.background(Color.black)
|
||||||
}.background(Color.black)
|
|
||||||
.frame(minWidth: 100, maxWidth: .infinity)
|
|
||||||
.frame(height: 100)
|
.frame(height: 100)
|
||||||
}
|
}
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.shadow(radius: 5)
|
.shadow(radius: 5)
|
||||||
.padding(.bottom, 5)
|
.padding()
|
||||||
} else {
|
} else {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
|
@ -106,9 +102,7 @@ struct LibraryListView: View {
|
||||||
} else {
|
} else {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
}
|
}
|
||||||
}.padding(.leading, 16)
|
}
|
||||||
.padding(.trailing, 16)
|
|
||||||
.padding(.top, 8)
|
|
||||||
}
|
}
|
||||||
.navigationTitle(L10n.allMedia)
|
.navigationTitle(L10n.allMedia)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
|
|
@ -88,7 +88,7 @@ struct LibrarySearchView: View {
|
||||||
Button {
|
Button {
|
||||||
searchRouter.route(to: \.item, item)
|
searchRouter.route(to: \.item, item)
|
||||||
} label: {
|
} label: {
|
||||||
PortraitItemView(item: item)
|
PortraitItemElement(item: item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,100 +10,86 @@ import Stinsen
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct LibraryView: View {
|
struct LibraryView: View {
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
var libraryRouter: LibraryCoordinator.Router
|
var libraryRouter: LibraryCoordinator.Router
|
||||||
@StateObject
|
@StateObject
|
||||||
var viewModel: LibraryViewModel
|
var viewModel: LibraryViewModel
|
||||||
var title: String
|
|
||||||
|
|
||||||
// MARK: tracks for grid
|
// MARK: tracks for grid
|
||||||
|
|
||||||
var defaultFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], tags: [], sortBy: [.name])
|
var defaultFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], tags: [], sortBy: [.name])
|
||||||
|
|
||||||
@State
|
@State
|
||||||
private var tracks: [GridItem] = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 125)
|
private var tracks: [GridItem] = Array(repeating: .init(.flexible(), alignment: .top), count: Int(UIScreen.main.bounds.size.width) / 125)
|
||||||
|
|
||||||
func recalcTracks() {
|
func recalcTracks() {
|
||||||
tracks = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 125)
|
tracks = Array(repeating: .init(.flexible(), alignment: .top), count: Int(UIScreen.main.bounds.size.width) / 125)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var loadingView: some View {
|
||||||
|
ProgressView()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var noResultsView: some View {
|
||||||
|
L10n.noResults.text
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var libraryItemsView: some View {
|
||||||
|
DetectBottomScrollView {
|
||||||
|
VStack {
|
||||||
|
LazyVGrid(columns: tracks) {
|
||||||
|
ForEach(viewModel.items, id: \.id) { item in
|
||||||
|
if item.type != "Folder" {
|
||||||
|
PortraitItemButton(item: item) { item in
|
||||||
|
libraryRouter.route(to: \.item, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ignoresSafeArea()
|
||||||
|
.listRowSeparator(.hidden)
|
||||||
|
.onRotate { _ in
|
||||||
|
recalcTracks()
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
.frame(height: 30)
|
||||||
|
}
|
||||||
|
} didReachBottom: { newValue in
|
||||||
|
if newValue && viewModel.hasNextPage {
|
||||||
|
viewModel.requestNextPageAsync()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
if viewModel.isLoading == true {
|
if viewModel.isLoading && viewModel.items.isEmpty {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
} else if !viewModel.items.isEmpty {
|
} else if !viewModel.items.isEmpty {
|
||||||
VStack {
|
libraryItemsView
|
||||||
ScrollView(.vertical) {
|
|
||||||
Spacer().frame(height: 16)
|
|
||||||
LazyVGrid(columns: tracks) {
|
|
||||||
ForEach(viewModel.items, id: \.id) { item in
|
|
||||||
if item.type != "Folder" {
|
|
||||||
Button {
|
|
||||||
libraryRouter.route(to: \.item, item)
|
|
||||||
} label: {
|
|
||||||
PortraitItemView(item: item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.onRotate { _ in
|
|
||||||
recalcTracks()
|
|
||||||
}
|
|
||||||
if viewModel.hasNextPage || viewModel.hasPreviousPage {
|
|
||||||
HStack {
|
|
||||||
Spacer()
|
|
||||||
HStack {
|
|
||||||
Button {
|
|
||||||
viewModel.requestPreviousPage()
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "chevron.left")
|
|
||||||
.font(.system(size: 25))
|
|
||||||
}.disabled(!viewModel.hasPreviousPage)
|
|
||||||
Text(L10n.pageOfWithNumbers(String(viewModel.currentPage + 1), String(viewModel.totalPages)))
|
|
||||||
.font(.subheadline)
|
|
||||||
.fontWeight(.medium)
|
|
||||||
Button {
|
|
||||||
viewModel.requestNextPage()
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "chevron.right")
|
|
||||||
.font(.system(size: 25))
|
|
||||||
}.disabled(!viewModel.hasNextPage)
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer().frame(height: 16)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
L10n.noResults.text
|
noResultsView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationBarTitle(title, displayMode: .inline)
|
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||||
if viewModel.hasPreviousPage {
|
|
||||||
Button {
|
Button {
|
||||||
viewModel.requestPreviousPage()
|
// libraryRouter
|
||||||
|
// .route(to: \.filter, (filters: $viewModel.filters, enabledFilterType: viewModel.enabledFilterType,
|
||||||
|
// parentId: viewModel.parentID ?? ""))
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "chevron.left")
|
Image(systemName: "line.horizontal.3.decrease.circle")
|
||||||
}.disabled(viewModel.isLoading)
|
|
||||||
}
|
}
|
||||||
if viewModel.hasNextPage {
|
|
||||||
Button {
|
|
||||||
viewModel.requestNextPage()
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "chevron.right")
|
|
||||||
}.disabled(viewModel.isLoading)
|
|
||||||
}
|
|
||||||
Label("Icon One", systemImage: "line.horizontal.3.decrease.circle")
|
|
||||||
.foregroundColor(viewModel.filters == defaultFilters ? .accentColor : Color(UIColor.systemOrange))
|
.foregroundColor(viewModel.filters == defaultFilters ? .accentColor : Color(UIColor.systemOrange))
|
||||||
.onTapGesture {
|
|
||||||
libraryRouter
|
|
||||||
.route(to: \.filter, (filters: $viewModel.filters, enabledFilterType: viewModel.enabledFilterType,
|
|
||||||
parentId: viewModel.parentID ?? ""))
|
|
||||||
}
|
|
||||||
Button {
|
Button {
|
||||||
libraryRouter.route(to: \.search, .init(parentID: viewModel.parentID))
|
// libraryRouter.route(to: \.search, .init(parentID: viewModel.parentID))
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "magnifyingglass")
|
Image(systemName: "magnifyingglass")
|
||||||
}
|
}
|
||||||
|
@ -111,6 +97,3 @@ struct LibraryView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// stream BM^S by nicki!
|
|
||||||
//
|
|
||||||
|
|
|
@ -1,41 +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 JellyfinAPI
|
|
||||||
import Stinsen
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct NextUpView: View {
|
|
||||||
@EnvironmentObject
|
|
||||||
var homeRouter: HomeCoordinator.Router
|
|
||||||
|
|
||||||
var items: [BaseItemDto]
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
L10n.nextUp.text
|
|
||||||
.font(.title2)
|
|
||||||
.fontWeight(.bold)
|
|
||||||
.padding(.leading, 16)
|
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
|
||||||
LazyHStack {
|
|
||||||
ForEach(items, id: \.id) { item in
|
|
||||||
Button {
|
|
||||||
homeRouter.route(to: \.item, item)
|
|
||||||
} label: {
|
|
||||||
PortraitItemView(item: item)
|
|
||||||
}
|
|
||||||
}.padding(.trailing, 16)
|
|
||||||
}
|
|
||||||
.padding(.leading, 20)
|
|
||||||
}
|
|
||||||
.frame(height: 200)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue