diff --git a/Shared/Coordinators/LibraryCoordinator.swift b/Shared/Coordinators/LibraryCoordinator.swift index 2c637206..adcaff91 100644 --- a/Shared/Coordinators/LibraryCoordinator.swift +++ b/Shared/Coordinators/LibraryCoordinator.swift @@ -42,7 +42,7 @@ final class LibraryCoordinator: NavigationCoordinatable { @ViewBuilder func makeStart() -> some View { - LibraryView(viewModel: self.viewModel, title: title) + LibraryView(viewModel: self.viewModel) } func makeSearch(viewModel: LibrarySearchViewModel) -> SearchCoordinator { diff --git a/Shared/Extensions/ViewExtensions/ViewExtensions.swift b/Shared/Extensions/ViewExtensions/ViewExtensions.swift index b4aa201f..5a947d4b 100644 --- a/Shared/Extensions/ViewExtensions/ViewExtensions.swift +++ b/Shared/Extensions/ViewExtensions/ViewExtensions.swift @@ -46,7 +46,7 @@ extension View { } } - func poster(type: PosterType, width: CGFloat) -> some View { + func posterStyle(type: PosterType, width: CGFloat) -> some View { Group { switch type { case .portrait: diff --git a/Shared/Objects/PosterType.swift b/Shared/Objects/PosterType.swift index d27f5635..34327359 100644 --- a/Shared/Objects/PosterType.swift +++ b/Shared/Objects/PosterType.swift @@ -7,12 +7,21 @@ // import Defaults -import Foundation +import SwiftUI enum PosterType: String, CaseIterable, Defaults.Serializable { case portrait case landscape + var width: CGFloat { + switch self { + case .portrait: + return Width.portrait + case .landscape: + return Width.landscape + } + } + var localizedName: String { switch self { case .portrait: @@ -21,4 +30,18 @@ enum PosterType: String, CaseIterable, Defaults.Serializable { return "Landscape" } } + + enum Width { + #if os(tvOS) + static let portrait = 250.0 + + static let landscape = 490.0 + #else + @ScaledMetric(relativeTo: .largeTitle) + static var portrait = 100.0 + + @ScaledMetric(relativeTo: .largeTitle) + static var landscape = 200.0 + #endif + } } diff --git a/Shared/Objects/Typings.swift b/Shared/Objects/Typings.swift index 5be73dba..aa93fd17 100644 --- a/Shared/Objects/Typings.swift +++ b/Shared/Objects/Typings.swift @@ -12,10 +12,12 @@ import JellyfinAPI struct LibraryFilters: Codable, Hashable { var filters: [ItemFilter] = [] - var sortOrder: [APISortOrder] = [.descending] + var sortOrder: [APISortOrder] = [.ascending] var withGenres: [NameGuidPair] = [] var tags: [String] = [] var sortBy: [SortBy] = [.name] + + static let `default` = LibraryFilters() } public enum SortBy: String, Codable, CaseIterable { diff --git a/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift b/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift index f87df458..b85a8fb5 100644 --- a/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift +++ b/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift @@ -36,6 +36,7 @@ extension Defaults.Keys { static let recentlyAddedPosterType = Key("recentlyAddedPosterType", default: .portrait, suite: .generalSuite) static let latestInLibraryPosterType = Key("latestInLibraryPosterType", default: .portrait, suite: .generalSuite) static let recommendedPosterType = Key("recommendedPosterType", default: .portrait, suite: .generalSuite) + static let libraryPosterType = Key("libraryPosterType", default: .portrait, suite: .generalSuite) enum Episodes { static let useSeriesLandscapeBackdrop = Key("useSeriesBackdrop", default: true, suite: .generalSuite) diff --git a/Shared/ViewModels/LibraryViewModel.swift b/Shared/ViewModels/LibraryViewModel.swift index ebd9070b..e0a2875b 100644 --- a/Shared/ViewModels/LibraryViewModel.swift +++ b/Shared/ViewModels/LibraryViewModel.swift @@ -8,26 +8,16 @@ import Combine import Defaults -import Foundation import JellyfinAPI -import SwiftUICollection import UIKit -typealias LibraryRow = CollectionRow - -struct LibraryRowCell: Hashable { - let id = UUID() - let item: BaseItemDto? - var loadingCell: Bool = false -} - final class LibraryViewModel: ViewModel { + @Default(.Customization.libraryPosterType) + var libraryPosterType + @Published var items: [BaseItemDto] = [] - @Published - var rows: [LibraryRow] = [] - @Published var totalPages = 0 @Published @@ -43,8 +33,11 @@ final class LibraryViewModel: ViewModel { var person: BaseItemPerson? var genre: NameGuidPair? var studio: NameGuidPair? - private let columns: Int - private let pageItemSize: Int + + private var pageItemSize: Int { + let height = libraryPosterType == .portrait ? libraryPosterType.width * 1.5 : libraryPosterType.width / 1.77 + return UIScreen.itemsFillableOnScreen(width: libraryPosterType.width, height: height) + } var enabledFilterType: [FilterType] { if genre == nil { @@ -59,18 +52,13 @@ final class LibraryViewModel: ViewModel { person: BaseItemPerson? = nil, genre: NameGuidPair? = nil, studio: NameGuidPair? = nil, - filters: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], sortBy: [.name]), - columns: Int = 7 + filters: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], sortBy: [.name]) ) { self.parentID = parentID self.person = person self.genre = genre self.studio = studio self.filters = filters - self.columns = columns - - // Size is typical size of portrait items - self.pageItemSize = UIScreen.itemsFillableOnScreen(width: 130, height: 185) super.init() @@ -137,7 +125,6 @@ final class LibraryViewModel: ViewModel { self.totalPages = Int(totalPages) self.hasNextPage = self.currentPage < self.totalPages - 1 self.items.append(contentsOf: response.items ?? []) - self.rows = self.calculateRows(for: self.items) }) .store(in: &cancellables) } @@ -146,50 +133,13 @@ final class LibraryViewModel: ViewModel { currentPage += 1 requestItemsAsync(with: filters) } - - // tvOS calculations for collection view - private func calculateRows(for itemList: [BaseItemDto]) -> [LibraryRow] { - guard !itemList.isEmpty else { return [] } - let rowCount = itemList.count / columns - var calculatedRows = [LibraryRow]() - for i in 0 ... rowCount { - let firstItemIndex = i * columns - var lastItemIndex = firstItemIndex + columns - if lastItemIndex > itemList.count { - lastItemIndex = itemList.count - } - - var rowCells = [LibraryRowCell]() - for item in itemList[firstItemIndex ..< lastItemIndex] { - let newCell = LibraryRowCell(item: item) - rowCells.append(newCell) - } - if i == rowCount, hasNextPage { - var loadingCell = LibraryRowCell(item: nil) - loadingCell.loadingCell = true - rowCells.append(loadingCell) - } - - calculatedRows.append(LibraryRow( - section: i, - items: rowCells - )) - } - return calculatedRows - } } extension UIScreen { static func itemsFillableOnScreen(width: CGFloat, height: CGFloat) -> Int { - let screenSize = UIScreen.main.bounds.height * UIScreen.main.bounds.width let itemSize = width * height - - #if os(tvOS) - return Int(screenSize / itemSize) * 2 - #else - return Int(screenSize / itemSize) - #endif + return Int(screenSize / itemSize) } } diff --git a/Shared/ViewModels/TVLibrariesViewModel.swift b/Shared/ViewModels/TVLibrariesViewModel.swift index 3d96ed09..2bff284a 100644 --- a/Shared/ViewModels/TVLibrariesViewModel.swift +++ b/Shared/ViewModels/TVLibrariesViewModel.swift @@ -12,6 +12,14 @@ import JellyfinAPI import Stinsen import SwiftUICollection +typealias LibraryRow = CollectionRow + +struct LibraryRowCell: Hashable { + let id = UUID() + let item: BaseItemDto? + var loadingCell: Bool = false +} + final class TVLibrariesViewModel: ViewModel { @Published diff --git a/Swiftfin tvOS/Assets.xcassets/AccentColor.colorset/Contents.json b/Swiftfin tvOS/Assets.xcassets/AccentColor.colorset/Contents.json deleted file mode 100644 index a21e4dca..00000000 --- a/Swiftfin tvOS/Assets.xcassets/AccentColor.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.765", - "green" : "0.361", - "red" : "0.667" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Swiftfin tvOS/Components/PosterButton.swift b/Swiftfin tvOS/Components/PosterButton.swift index 52705c7f..b5664111 100644 --- a/Swiftfin tvOS/Components/PosterButton.swift +++ b/Swiftfin tvOS/Components/PosterButton.swift @@ -8,10 +8,12 @@ import SwiftUI -struct PosterButton: View { +enum PosterButtonWidth { + static let landscape = 490.0 + static let portrait = 250.0 +} - private let landscapePosterWidth = 490.0 - private let portraitPosterWidth = 250.0 +struct PosterButton: View { private let item: Item private let type: PosterType @@ -26,9 +28,9 @@ struct PosterButton: 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: 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) - } - } -} diff --git a/Swiftfin/Components/PosterButton.swift b/Swiftfin/Components/PosterButton.swift index f993ab3b..09c78536 100644 --- a/Swiftfin/Components/PosterButton.swift +++ b/Swiftfin/Components/PosterButton.swift @@ -10,12 +10,6 @@ import SwiftUI struct PosterButton: View { - @ScaledMetric(relativeTo: .largeTitle) - private var landscapePosterWidth = 200.0 - - @ScaledMetric(relativeTo: .largeTitle) - private var portraitPosterWidth = 100.0 - private let item: Item private let type: PosterType private let itemScale: CGFloat @@ -27,12 +21,7 @@ struct PosterButton