From 0dd592df02511a2df4a8def4b6522bcd098bdd91 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Thu, 13 Jan 2022 14:35:20 -0700 Subject: [PATCH 1/9] wip --- Shared/Coordinators/FilterCoordinator.swift | 11 +- Shared/Coordinators/LibraryCoordinator.swift | 8 +- .../MoviesLibrariesCoordinator.swift | 2 +- .../Coordinators/TVLibrariesCoordinator.swift | 2 +- .../BaseItemDtoExtensions.swift | 3 +- Shared/ViewModels/LibraryListViewModel.swift | 30 +++- Shared/ViewModels/LibraryViewModel.swift | 127 ++++++----------- Swiftfin tvOS/Views/LibraryView.swift | 4 - Swiftfin.xcodeproj/project.pbxproj | 24 ++-- .../Components/DetectBottomScrollView.swift | 99 ++++++++++++++ Swiftfin/Components/PortraitItemButton.swift | 69 ++++++++++ Swiftfin/Components/PortraitItemView.swift | 83 ----------- Swiftfin/Views/HomeView.swift | 6 +- Swiftfin/Views/LibraryListView.swift | 72 +++++----- Swiftfin/Views/LibrarySearchView.swift | 2 +- Swiftfin/Views/LibraryView.swift | 129 ++++++++---------- Swiftfin/Views/NextUpView.swift | 41 ------ 17 files changed, 359 insertions(+), 353 deletions(-) create mode 100644 Swiftfin/Components/DetectBottomScrollView.swift create mode 100644 Swiftfin/Components/PortraitItemButton.swift delete mode 100644 Swiftfin/Components/PortraitItemView.swift delete mode 100644 Swiftfin/Views/NextUpView.swift diff --git a/Shared/Coordinators/FilterCoordinator.swift b/Shared/Coordinators/FilterCoordinator.swift index 10a13f05..7a02e915 100644 --- a/Shared/Coordinators/FilterCoordinator.swift +++ b/Shared/Coordinators/FilterCoordinator.swift @@ -7,10 +7,11 @@ // import Foundation +import JellyfinAPI import Stinsen import SwiftUI -typealias FilterCoordinatorParams = (filters: Binding, enabledFilterType: [FilterType], parentId: String) +typealias FilterCoordinatorParams = (libraryItem: BaseItemDto, filters: Binding, enabledFilterType: [FilterType]) final class FilterCoordinator: NavigationCoordinatable { @@ -19,19 +20,19 @@ final class FilterCoordinator: NavigationCoordinatable { @Root var start = makeStart + let libraryItem: BaseItemDto @Binding var filters: LibraryFilters var enabledFilterType: [FilterType] - var parentId: String = "" - init(filters: Binding, enabledFilterType: [FilterType], parentId: String) { + init(libraryItem: BaseItemDto, filters: Binding, enabledFilterType: [FilterType]) { + self.libraryItem = libraryItem _filters = filters self.enabledFilterType = enabledFilterType - self.parentId = parentId } @ViewBuilder func makeStart() -> some View { - LibraryFilterView(filters: $filters, enabledFilterType: enabledFilterType, parentId: parentId) + LibraryFilterView(filters: $filters, enabledFilterType: enabledFilterType, parentId: libraryItem.id!) } } diff --git a/Shared/Coordinators/LibraryCoordinator.swift b/Shared/Coordinators/LibraryCoordinator.swift index 1678e407..32449cf2 100644 --- a/Shared/Coordinators/LibraryCoordinator.swift +++ b/Shared/Coordinators/LibraryCoordinator.swift @@ -11,8 +11,6 @@ import JellyfinAPI import Stinsen import SwiftUI -typealias LibraryCoordinatorParams = (viewModel: LibraryViewModel, title: String) - final class LibraryCoordinator: NavigationCoordinatable { let stack = NavigationStack(initial: \LibraryCoordinator.start) @@ -29,16 +27,14 @@ final class LibraryCoordinator: NavigationCoordinatable { var modalItem = makeModalItem let viewModel: LibraryViewModel - let title: String - init(viewModel: LibraryViewModel, title: String) { + init(viewModel: LibraryViewModel) { self.viewModel = viewModel - self.title = title } @ViewBuilder func makeStart() -> some View { - LibraryView(viewModel: self.viewModel, title: title) + LibraryView(viewModel: self.viewModel) } func makeSearch(viewModel: LibrarySearchViewModel) -> SearchCoordinator { diff --git a/Shared/Coordinators/MoviesLibrariesCoordinator.swift b/Shared/Coordinators/MoviesLibrariesCoordinator.swift index 15e14888..142238d2 100644 --- a/Shared/Coordinators/MoviesLibrariesCoordinator.swift +++ b/Shared/Coordinators/MoviesLibrariesCoordinator.swift @@ -34,6 +34,6 @@ final class MovieLibrariesCoordinator: NavigationCoordinatable { } func makeLibrary(library: BaseItemDto) -> LibraryCoordinator { - LibraryCoordinator(viewModel: LibraryViewModel(parentID: library.id), title: library.title) +// LibraryCoordinator(viewModel: LibraryViewModel(parentID: library.id), title: library.title) } } diff --git a/Shared/Coordinators/TVLibrariesCoordinator.swift b/Shared/Coordinators/TVLibrariesCoordinator.swift index b2ec1121..5335cd31 100644 --- a/Shared/Coordinators/TVLibrariesCoordinator.swift +++ b/Shared/Coordinators/TVLibrariesCoordinator.swift @@ -34,6 +34,6 @@ final class TVLibrariesCoordinator: NavigationCoordinatable { } func makeLibrary(library: BaseItemDto) -> LibraryCoordinator { - LibraryCoordinator(viewModel: LibraryViewModel(parentID: library.id), title: library.title) + LibraryCoordinator(viewModel: .init(libraryItem: <#T##BaseItemDto#>)) } } diff --git a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift index a043de60..849dffe8 100644 --- a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift +++ b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift @@ -206,6 +206,7 @@ public extension BaseItemDto { case episode = "Episode" case series = "Series" case boxset = "BoxSet" + case collectionFolder = "CollectionFolder" case unknown @@ -228,7 +229,7 @@ public extension BaseItemDto { func portraitHeaderViewURL(maxWidth: Int) -> URL { switch itemType { - case .movie, .season, .series, .boxset: + case .movie, .season, .series, .boxset, .collectionFolder: return getPrimaryImage(maxWidth: maxWidth) case .episode: return getSeriesPrimaryImage(maxWidth: maxWidth) diff --git a/Shared/ViewModels/LibraryListViewModel.swift b/Shared/ViewModels/LibraryListViewModel.swift index 25ed0557..d4bc01e5 100644 --- a/Shared/ViewModels/LibraryListViewModel.swift +++ b/Shared/ViewModels/LibraryListViewModel.swift @@ -13,6 +13,8 @@ final class LibraryListViewModel: ViewModel { @Published var libraries: [BaseItemDto] = [] + @Published + var libraryRandomItems: [BaseItemDto: BaseItemDto] = [:] // temp var withFavorites = LibraryFilters(filters: [.isFavorite], sortOrder: [], withGenres: [], sortBy: []) @@ -29,8 +31,34 @@ final class LibraryListViewModel: ViewModel { .sink(receiveCompletion: { completion in self.handleAPIRequestError(completion: completion) }, 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) } + + // 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) + } } diff --git a/Shared/ViewModels/LibraryViewModel.swift b/Shared/ViewModels/LibraryViewModel.swift index 4f41a662..02cbe13b 100644 --- a/Shared/ViewModels/LibraryViewModel.swift +++ b/Shared/ViewModels/LibraryViewModel.swift @@ -10,6 +10,7 @@ import Combine import Foundation import JellyfinAPI import SwiftUICollection +import UIKit typealias LibraryRow = CollectionRow @@ -20,15 +21,11 @@ struct LibraryRowCell: Hashable { } final class LibraryViewModel: ViewModel { - var parentID: String? - var person: BaseItemPerson? - var genre: NameGuidPair? - var studio: NameGuidPair? @Published - var items = [BaseItemDto]() + var items: [BaseItemDto] = [] @Published - var rows = [LibraryRow]() + var rows: [LibraryRow] = [] @Published var totalPages = 0 @@ -36,15 +33,17 @@ final class LibraryViewModel: ViewModel { var currentPage = 0 @Published var hasNextPage = false - @Published - var hasPreviousPage = false // temp @Published var filters: LibraryFilters - + + let libraryItem: BaseItemDto + var person: BaseItemPerson? + var genre: NameGuidPair? + var studio: NameGuidPair? private let columns: Int - private var libraries = [BaseItemDto]() + private let pageItemSize: Int var enabledFilterType: [FilterType] { if genre == nil { @@ -54,27 +53,40 @@ final class LibraryViewModel: ViewModel { } } - init(parentID: String? = nil, + init(libraryItem: BaseItemDto, person: BaseItemPerson? = nil, genre: NameGuidPair? = nil, studio: NameGuidPair? = nil, filters: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], sortBy: [.name]), columns: Int = 7) { - self.parentID = parentID + self.libraryItem = libraryItem 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) + + print("Page item size: \(pageItemSize)") + super.init() $filters - .sink(receiveValue: requestItems(with:)) + .sink(receiveValue: { newFilters in + self.requestItemsAsync(with: newFilters, replaceCurrentItems: true) + }) .store(in: &cancellables) } - func requestItems(with filters: LibraryFilters) { + func requestItemsAsync(with filters: LibraryFilters, replaceCurrentItems: Bool = false) { + + if replaceCurrentItems { + self.items = [] + } + let personIDs: [String] = [person].compactMap(\.?.id) let studioIDs: [String] = [studio].compactMap(\.?.id) let genreIDs: [String] @@ -85,65 +97,12 @@ final class LibraryViewModel: ViewModel { } let sortBy = filters.sortBy.map(\.rawValue) - ItemsAPI.getItemsByUserId(userId: SessionManager.main.currentLogin.user.id, - startIndex: currentPage * 100, - limit: 100, + ItemsAPI.getItemsByUserId(userId: SessionManager.main.currentLogin.user.id, startIndex: currentPage * pageItemSize, + limit: pageItemSize, 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) - } - - func requestItemsAsync(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, + parentId: libraryItem.id, fields: [ .primaryImageAspectRatio, .seriesPrimaryImage, @@ -163,13 +122,15 @@ final class LibraryViewModel: ViewModel { studioIds: studioIDs, genreIds: genreIDs, enableImages: true) + .trackActivity(loading) .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in + 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.hasPreviousPage = self.currentPage > 0 self.hasNextPage = self.currentPage < self.totalPages - 1 self.items.append(contentsOf: response.items ?? []) self.rows = self.calculateRows(for: self.items) @@ -177,21 +138,12 @@ final class LibraryViewModel: ViewModel { .store(in: &cancellables) } - func requestNextPage() { - currentPage += 1 - requestItems(with: filters) - } - func requestNextPageAsync() { currentPage += 1 requestItemsAsync(with: filters) } - func requestPreviousPage() { - currentPage -= 1 - requestItems(with: filters) - } - + // tvOS calculations for collection view private func calculateRows(for itemList: [BaseItemDto]) -> [LibraryRow] { guard !itemList.isEmpty else { return [] } let rowCount = itemList.count / columns @@ -220,3 +172,14 @@ final class LibraryViewModel: ViewModel { 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) + } +} diff --git a/Swiftfin tvOS/Views/LibraryView.swift b/Swiftfin tvOS/Views/LibraryView.swift index 1344730e..02df6a86 100644 --- a/Swiftfin tvOS/Views/LibraryView.swift +++ b/Swiftfin tvOS/Views/LibraryView.swift @@ -15,7 +15,6 @@ struct LibraryView: View { var libraryRouter: LibraryCoordinator.Router @StateObject var viewModel: LibraryViewModel - var title: String // MARK: tracks for grid @@ -91,6 +90,3 @@ struct LibraryView: View { } } } - -// stream BM^S by nicki! -// diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index e91d1ec5..4e1b381e 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -75,7 +75,6 @@ 5377CBF9263B596B003A4E83 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5377CBF8263B596B003A4E83 /* 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 */; }; - 53892770263C25230035E14B /* NextUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5389276F263C25230035E14B /* NextUpView.swift */; }; 5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5389277B263CC3DB0035E14B /* BlurHashDecode.swift */; }; 53913BEF26D323FE00EB3286 /* 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 */; }; 53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E4E648263F725B00F67C6B /* MultiSelectorView.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 */; }; 5D1603FC278A3D5800D22B99 /* 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 */; }; 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 */; }; + E111DE222790BB46008118A3 /* DetectBottomScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E111DE212790BB46008118A3 /* DetectBottomScrollView.swift */; }; E11B1B6C2718CD68006DA3E8 /* 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 */; }; @@ -549,7 +549,6 @@ 5377CBFB263B596B003A4E83 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 5377CC02263B596B003A4E83 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 5389276D263C25100035E14B /* ContinueWatchingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueWatchingView.swift; sourceTree = ""; }; - 5389276F263C25230035E14B /* NextUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextUpView.swift; sourceTree = ""; }; 5389277B263CC3DB0035E14B /* BlurHashDecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = ""; }; 53913BCA26D323FE00EB3286 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = Localizable.strings; sourceTree = ""; }; 53913BCD26D323FE00EB3286 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = Localizable.strings; sourceTree = ""; }; @@ -577,7 +576,7 @@ 53E4E646263F6CF100F67C6B /* LibraryFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryFilterView.swift; sourceTree = ""; }; 53E4E648263F725B00F67C6B /* MultiSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiSelectorView.swift; sourceTree = ""; }; 53EE24E5265060780068F029 /* LibrarySearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchView.swift; sourceTree = ""; }; - 53F866432687A45F00DCD1D7 /* PortraitItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortraitItemView.swift; sourceTree = ""; }; + 53F866432687A45F00DCD1D7 /* PortraitItemButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortraitItemButton.swift; sourceTree = ""; }; 53FF7F29263CF3F500585C35 /* LatestMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestMediaView.swift; sourceTree = ""; }; 5D1603FB278A3D5700D22B99 /* SubtitleSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubtitleSize.swift; sourceTree = ""; }; 5D160402278A41FD00D22B99 /* VLCPlayer+subtitles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VLCPlayer+subtitles.swift"; sourceTree = ""; }; @@ -672,6 +671,7 @@ E10D87E127852FD000BD264C /* EpisodesRowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodesRowManager.swift; sourceTree = ""; }; E10EAA4E277BBCC4000269ED /* CGSizeExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGSizeExtensions.swift; sourceTree = ""; }; E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BaseItemDto+VideoPlayerViewModel.swift"; sourceTree = ""; }; + E111DE212790BB46008118A3 /* DetectBottomScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectBottomScrollView.swift; sourceTree = ""; }; E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinAPIError.swift; sourceTree = ""; }; E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailCoordinator.swift; sourceTree = ""; }; E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaStreamExtension.swift; sourceTree = ""; }; @@ -1214,11 +1214,12 @@ 53F866422687A45400DCD1D7 /* Components */ = { isa = PBXGroup; children = ( + E111DE212790BB46008118A3 /* DetectBottomScrollView.swift */, E176DE6E278E3522001EFD8D /* EpisodesRowView */, E1AD105B26D9ABDD003E4A08 /* PillHStackView.swift */, E1AD105526D981CE003E4A08 /* PortraitHStackView.swift */, C4BE076D2720FEA8003F4AD1 /* PortraitItemElement.swift */, - 53F866432687A45F00DCD1D7 /* PortraitItemView.swift */, + 53F866432687A45F00DCD1D7 /* PortraitItemButton.swift */, E1AA331C2782541500F6439C /* PrimaryButtonView.swift */, E1EBCB41278BD174009FE6E9 /* TruncatedTextView.swift */, ); @@ -1454,7 +1455,6 @@ 53DF641D263D9C0600A7CD1A /* LibraryView.swift */, C4AE2C2F27498D2300AE13CF /* LiveTVHomeView.swift */, C4AE2C3127498D6A00AE13CF /* LiveTVProgramsView.swift */, - 5389276F263C25230035E14B /* NextUpView.swift */, E173DA4F26D048D600CC4EB7 /* ServerDetailView.swift */, E13DD3E427177D15009D4DAF /* ServerListView.swift */, E1E5D54A2783E26100692DFE /* SettingsView */, @@ -2294,7 +2294,7 @@ 62E632DC267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */, 62C29E9F26D1016600C1D2E7 /* iOSMainCoordinator.swift in Sources */, 5389276E263C25100035E14B /* ContinueWatchingView.swift in Sources */, - 53F866442687A45F00DCD1D7 /* PortraitItemView.swift in Sources */, + 53F866442687A45F00DCD1D7 /* PortraitItemButton.swift in Sources */, E1AD105626D981CE003E4A08 /* PortraitHStackView.swift in Sources */, 62C29EA126D102A500C1D2E7 /* iOSMainTabCoordinator.swift in Sources */, C4BE076E2720FEA8003F4AD1 /* PortraitItemElement.swift in Sources */, @@ -2309,6 +2309,7 @@ E13DD3EC27178A54009D4DAF /* UserSignInViewModel.swift in Sources */, 625CB5772678C34300530A6E /* ConnectToServerViewModel.swift in Sources */, C4BE07852728446F003F4AD1 /* LiveTVChannelsViewModel.swift in Sources */, + E111DE222790BB46008118A3 /* DetectBottomScrollView.swift in Sources */, 5D160403278A41FD00D22B99 /* VLCPlayer+subtitles.swift in Sources */, 536D3D78267BD5C30004248C /* ViewModel.swift in Sources */, C4BE078B272844AF003F4AD1 /* LiveTVChannelsView.swift in Sources */, @@ -2328,7 +2329,6 @@ E1C812BC277A8E5D00918266 /* PlaybackSpeed.swift in Sources */, E1E5D5492783CDD700692DFE /* OverlaySettingsView.swift in Sources */, E173DA5226D04AAF00CC4EB7 /* ColorExtension.swift in Sources */, - 53892770263C25230035E14B /* NextUpView.swift in Sources */, E11D224227378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */, 6264E88C273850380081A12A /* Strings.swift in Sources */, C4BE0766271FC109003F4AD1 /* TVLibrariesViewModel.swift in Sources */, @@ -2804,7 +2804,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2841,7 +2841,7 @@ CURRENT_PROJECT_VERSION = 66; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2872,7 +2872,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -2899,7 +2899,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/Swiftfin/Components/DetectBottomScrollView.swift b/Swiftfin/Components/DetectBottomScrollView.swift new file mode 100644 index 00000000..5d275f6c --- /dev/null +++ b/Swiftfin/Components/DetectBottomScrollView.swift @@ -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: 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/PortraitItemButton.swift b/Swiftfin/Components/PortraitItemButton.swift new file mode 100644 index 00000000..c6251064 --- /dev/null +++ b/Swiftfin/Components/PortraitItemButton.swift @@ -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: 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) + } +} diff --git a/Swiftfin/Components/PortraitItemView.swift b/Swiftfin/Components/PortraitItemView.swift deleted file mode 100644 index 0f67464c..00000000 --- a/Swiftfin/Components/PortraitItemView.swift +++ /dev/null @@ -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) - } -} diff --git a/Swiftfin/Views/HomeView.swift b/Swiftfin/Views/HomeView.swift index e6e8f764..a4fa04cf 100644 --- a/Swiftfin/Views/HomeView.swift +++ b/Swiftfin/Views/HomeView.swift @@ -90,9 +90,9 @@ struct HomeView: View { Button { homeRouter - .route(to: \.library, (viewModel: .init(parentID: library.id!, - filters: viewModel.recentFilterSet), - title: library.name ?? "")) +// .route(to: \.library, (viewModel: .init(parentID: library.id!, +// filters: viewModel.recentFilterSet), +// title: library.name ?? "")) } label: { HStack { L10n.seeAll.text.font(.subheadline).fontWeight(.bold) diff --git a/Swiftfin/Views/LibraryListView.swift b/Swiftfin/Views/LibraryListView.swift index 2438e35c..607c4a6d 100644 --- a/Swiftfin/Views/LibraryListView.swift +++ b/Swiftfin/Views/LibraryListView.swift @@ -23,23 +23,20 @@ struct LibraryListView: View { libraryListRouter.route(to: \.library, (viewModel: LibraryViewModel(filters: viewModel.withFavorites), title: L10n.favorites)) } label: { - ZStack { - HStack { - Spacer() - L10n.yourFavorites.text - .foregroundColor(.black) - .font(.subheadline) - .fontWeight(.semibold) - Spacer() - } - } - .padding(16) + HStack { + Spacer() + L10n.yourFavorites.text + .foregroundColor(.black) + .font(.subheadline) + .fontWeight(.semibold) + Spacer() + } + .frame(height: 100) .background(Color.white) - .frame(minWidth: 100, maxWidth: .infinity) } .cornerRadius(10) .shadow(radius: 5) - .padding(.bottom, 5) + .padding() if !viewModel.isLoading { @@ -62,17 +59,17 @@ struct LibraryListView: View { .fontWeight(.semibold) } Spacer() - }.padding(32) - }.background(Color.black) - .frame(minWidth: 100, maxWidth: .infinity) - .frame(height: 100) + } + } + .background(Color.black) + .frame(height: 100) } .cornerRadius(10) .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" { Button { libraryListRouter.route(to: \.library, @@ -80,25 +77,24 @@ struct LibraryListView: View { title: library.name ?? "")) } label: { ZStack { - ImageView(src: library.getPrimaryImage(maxWidth: 500), bh: library.getPrimaryImageBlurHash()) - .opacity(0.4) - HStack { - Spacer() - VStack { - Text(library.name ?? "") - .foregroundColor(.white) - .font(.title2) - .fontWeight(.semibold) - } - Spacer() - }.padding(32) - }.background(Color.black) - .frame(minWidth: 100, maxWidth: .infinity) - .frame(height: 100) +// ImageView(src: library.getPrimaryImage(maxWidth: 500), bh: library.getPrimaryImageBlurHash()) +// .opacity(0.4) + + ImageView(src: viewModel.libraryRandomItems[library]!.getBackdropImage(maxWidth: 500)) + + VStack { + Text(library.name ?? "") + .foregroundColor(.white) + .font(.title2) + .fontWeight(.semibold) + } + } + .background(Color.black) + .frame(height: 100) } .cornerRadius(10) - .shadow(radius: 5) - .padding(.bottom, 5) + .shadow(radius: 5) + .padding() } else { EmptyView() } @@ -106,9 +102,7 @@ struct LibraryListView: View { } else { ProgressView() } - }.padding(.leading, 16) - .padding(.trailing, 16) - .padding(.top, 8) + } } .navigationTitle(L10n.allMedia) .toolbar { diff --git a/Swiftfin/Views/LibrarySearchView.swift b/Swiftfin/Views/LibrarySearchView.swift index c1b431a3..d4ae3958 100644 --- a/Swiftfin/Views/LibrarySearchView.swift +++ b/Swiftfin/Views/LibrarySearchView.swift @@ -88,7 +88,7 @@ struct LibrarySearchView: View { Button { searchRouter.route(to: \.item, item) } label: { - PortraitItemView(item: item) + PortraitItemElement(item: item) } } } diff --git a/Swiftfin/Views/LibraryView.swift b/Swiftfin/Views/LibraryView.swift index c5c37a92..d015d8d7 100644 --- a/Swiftfin/Views/LibraryView.swift +++ b/Swiftfin/Views/LibraryView.swift @@ -10,100 +10,86 @@ import Stinsen import SwiftUI struct LibraryView: View { + @EnvironmentObject var libraryRouter: LibraryCoordinator.Router @StateObject var viewModel: LibraryViewModel - var title: String // MARK: tracks for grid var defaultFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], tags: [], sortBy: [.name]) @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() { - 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 { Group { - if viewModel.isLoading == true { + if viewModel.isLoading && viewModel.items.isEmpty { ProgressView() } else if !viewModel.items.isEmpty { - VStack { - 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) - } - } + libraryItemsView } else { - L10n.noResults.text + noResultsView } } - .navigationBarTitle(title, displayMode: .inline) .toolbar { ToolbarItemGroup(placement: .navigationBarTrailing) { - if viewModel.hasPreviousPage { - Button { - viewModel.requestPreviousPage() - } label: { - Image(systemName: "chevron.left") - }.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)) - .onTapGesture { - libraryRouter - .route(to: \.filter, (filters: $viewModel.filters, enabledFilterType: viewModel.enabledFilterType, - parentId: viewModel.parentID ?? "")) - } + + Button { +// libraryRouter +// .route(to: \.filter, (filters: $viewModel.filters, enabledFilterType: viewModel.enabledFilterType, +// parentId: viewModel.parentID ?? "")) + } label: { + Image(systemName: "line.horizontal.3.decrease.circle") + } + .foregroundColor(viewModel.filters == defaultFilters ? .accentColor : Color(UIColor.systemOrange)) + Button { - libraryRouter.route(to: \.search, .init(parentID: viewModel.parentID)) +// libraryRouter.route(to: \.search, .init(parentID: viewModel.parentID)) } label: { Image(systemName: "magnifyingglass") } @@ -111,6 +97,3 @@ struct LibraryView: View { } } } - -// stream BM^S by nicki! -// diff --git a/Swiftfin/Views/NextUpView.swift b/Swiftfin/Views/NextUpView.swift deleted file mode 100644 index 01496746..00000000 --- a/Swiftfin/Views/NextUpView.swift +++ /dev/null @@ -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) - } - } -} From cabaffc951a50a23c7f9b930adbf904dd8d805b9 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Thu, 13 Jan 2022 15:39:29 -0700 Subject: [PATCH 2/9] wip --- Shared/Coordinators/HomeCoordinator.swift | 8 ++++---- Shared/Coordinators/ItemCoordinator.swift | 4 ++-- Shared/Coordinators/LibraryCoordinator.swift | 6 +++--- .../Coordinators/LibraryListCoordinator.swift | 4 ++-- Swiftfin.xcodeproj/project.pbxproj | 20 ------------------- Swiftfin/Views/LibraryListView.swift | 9 ++++++--- 6 files changed, 17 insertions(+), 34 deletions(-) diff --git a/Shared/Coordinators/HomeCoordinator.swift b/Shared/Coordinators/HomeCoordinator.swift index 3ce9bc3f..da3f6582 100644 --- a/Shared/Coordinators/HomeCoordinator.swift +++ b/Shared/Coordinators/HomeCoordinator.swift @@ -32,8 +32,8 @@ final class HomeCoordinator: NavigationCoordinatable { NavigationViewCoordinator(SettingsCoordinator()) } - func makeLibrary(params: LibraryCoordinatorParams) -> LibraryCoordinator { - LibraryCoordinator(viewModel: params.viewModel, title: params.title) + func makeLibrary(viewModel: LibraryViewModel) -> LibraryCoordinator { + LibraryCoordinator(viewModel: viewModel) } func makeItem(item: BaseItemDto) -> ItemCoordinator { @@ -44,8 +44,8 @@ final class HomeCoordinator: NavigationCoordinatable { NavigationViewCoordinator(ItemCoordinator(item: item)) } - func makeModalLibrary(params: LibraryCoordinatorParams) -> NavigationViewCoordinator { - NavigationViewCoordinator(LibraryCoordinator(viewModel: params.viewModel, title: params.title)) + func makeModalLibrary(viewModel: LibraryViewModel) -> NavigationViewCoordinator { + NavigationViewCoordinator(LibraryCoordinator(viewModel: viewModel)) } @ViewBuilder diff --git a/Shared/Coordinators/ItemCoordinator.swift b/Shared/Coordinators/ItemCoordinator.swift index 9bb29489..f8b0cfea 100644 --- a/Shared/Coordinators/ItemCoordinator.swift +++ b/Shared/Coordinators/ItemCoordinator.swift @@ -32,8 +32,8 @@ final class ItemCoordinator: NavigationCoordinatable { self.itemDto = item } - func makeLibrary(params: LibraryCoordinatorParams) -> LibraryCoordinator { - LibraryCoordinator(viewModel: params.viewModel, title: params.title) + func makeLibrary(viewModel: LibraryViewModel) -> LibraryCoordinator { + LibraryCoordinator(viewModel: viewModel) } func makeItem(item: BaseItemDto) -> ItemCoordinator { diff --git a/Shared/Coordinators/LibraryCoordinator.swift b/Shared/Coordinators/LibraryCoordinator.swift index 32449cf2..2176dea2 100644 --- a/Shared/Coordinators/LibraryCoordinator.swift +++ b/Shared/Coordinators/LibraryCoordinator.swift @@ -42,9 +42,9 @@ final class LibraryCoordinator: NavigationCoordinatable { } func makeFilter(params: FilterCoordinatorParams) -> NavigationViewCoordinator { - NavigationViewCoordinator(FilterCoordinator(filters: params.filters, - enabledFilterType: params.enabledFilterType, - parentId: params.parentId)) + NavigationViewCoordinator(FilterCoordinator(libraryItem: viewModel.libraryItem, + filters: params.filters, + enabledFilterType: params.enabledFilterType)) } func makeItem(item: BaseItemDto) -> ItemCoordinator { diff --git a/Shared/Coordinators/LibraryListCoordinator.swift b/Shared/Coordinators/LibraryListCoordinator.swift index a413ff83..00989fe6 100644 --- a/Shared/Coordinators/LibraryListCoordinator.swift +++ b/Shared/Coordinators/LibraryListCoordinator.swift @@ -27,8 +27,8 @@ final class LibraryListCoordinator: NavigationCoordinatable { self.viewModel = viewModel } - func makeLibrary(params: LibraryCoordinatorParams) -> LibraryCoordinator { - LibraryCoordinator(viewModel: params.viewModel, title: params.title) + func makeLibrary(viewModel: LibraryViewModel) -> LibraryCoordinator { + LibraryCoordinator(viewModel: viewModel) } func makeSearch(viewModel: LibrarySearchViewModel) -> SearchCoordinator { diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 4e1b381e..ae5f4bae 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -214,36 +214,26 @@ 62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62ECA01726FA685A00E8EBB7 /* DeepLink.swift */; }; 9EA03141D129DC2763660E29 /* Pods_Swiftfin_tvOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D9B2BE5F9AD4A3CDE842523 /* Pods_Swiftfin_tvOS.framework */; }; AE8C3159265D6F90008AA076 /* bitrates.json in Resources */ = {isa = PBXBuildFile; fileRef = AE8C3158265D6F90008AA076 /* bitrates.json */; }; - C40CD922271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD921271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift */; }; C40CD923271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD921271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift */; }; - C40CD925271F8D1E000FB198 /* MovieLibrariesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD924271F8D1E000FB198 /* MovieLibrariesViewModel.swift */; }; C40CD926271F8D1E000FB198 /* MovieLibrariesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD924271F8D1E000FB198 /* MovieLibrariesViewModel.swift */; }; - C40CD928271F8DAB000FB198 /* MovieLibrariesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD927271F8DAB000FB198 /* MovieLibrariesView.swift */; }; C40CD929271F8DAB000FB198 /* MovieLibrariesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40CD927271F8DAB000FB198 /* MovieLibrariesView.swift */; }; C45B29BB26FAC5B600CEF5E0 /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E173DA5126D04AAF00CC4EB7 /* ColorExtension.swift */; }; C4AE2C3027498D2300AE13CF /* LiveTVHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AE2C2F27498D2300AE13CF /* LiveTVHomeView.swift */; }; C4AE2C3227498D6A00AE13CF /* LiveTVProgramsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4AE2C3127498D6A00AE13CF /* LiveTVProgramsView.swift */; }; C4AE2C3327498DBE00AE13CF /* LiveTVChannelItemElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E52304272CE68800654268 /* LiveTVChannelItemElement.swift */; }; - C4BE0763271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE0762271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift */; }; C4BE0764271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE0762271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift */; }; - C4BE0766271FC109003F4AD1 /* TVLibrariesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE0765271FC109003F4AD1 /* TVLibrariesViewModel.swift */; }; C4BE0767271FC109003F4AD1 /* TVLibrariesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE0765271FC109003F4AD1 /* TVLibrariesViewModel.swift */; }; - C4BE0769271FC164003F4AD1 /* TVLibrariesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE0768271FC164003F4AD1 /* TVLibrariesView.swift */; }; C4BE076A271FC164003F4AD1 /* TVLibrariesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE0768271FC164003F4AD1 /* TVLibrariesView.swift */; }; C4BE076E2720FEA8003F4AD1 /* PortraitItemElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE076D2720FEA8003F4AD1 /* PortraitItemElement.swift */; }; C4BE076F2720FEFF003F4AD1 /* PlainNavigationLinkButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */; }; - C4BE07712725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07702725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift */; }; C4BE07722725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07702725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift */; }; C4BE07742725EB66003F4AD1 /* LiveTVProgramsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07732725EB66003F4AD1 /* LiveTVProgramsView.swift */; }; C4BE07762725EBEA003F4AD1 /* LiveTVProgramsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07752725EBEA003F4AD1 /* LiveTVProgramsViewModel.swift */; }; C4BE07772725EBEA003F4AD1 /* LiveTVProgramsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07752725EBEA003F4AD1 /* LiveTVProgramsViewModel.swift */; }; - C4BE07792726EE82003F4AD1 /* LiveTVTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07782726EE82003F4AD1 /* LiveTVTabCoordinator.swift */; }; C4BE077A2726EE82003F4AD1 /* LiveTVTabCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07782726EE82003F4AD1 /* LiveTVTabCoordinator.swift */; }; C4BE07852728446F003F4AD1 /* LiveTVChannelsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07842728446F003F4AD1 /* LiveTVChannelsViewModel.swift */; }; C4BE07862728446F003F4AD1 /* LiveTVChannelsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07842728446F003F4AD1 /* LiveTVChannelsViewModel.swift */; }; - C4BE07882728448B003F4AD1 /* LiveTVChannelsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07872728448B003F4AD1 /* LiveTVChannelsCoordinator.swift */; }; C4BE07892728448B003F4AD1 /* LiveTVChannelsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE07872728448B003F4AD1 /* LiveTVChannelsCoordinator.swift */; }; - C4BE078B272844AF003F4AD1 /* LiveTVChannelsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE078A272844AF003F4AD1 /* LiveTVChannelsView.swift */; }; C4BE078C272844AF003F4AD1 /* LiveTVChannelsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE078A272844AF003F4AD1 /* LiveTVChannelsView.swift */; }; C4BE078E27298818003F4AD1 /* LiveTVHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BE078D27298817003F4AD1 /* LiveTVHomeView.swift */; }; C4E5081B2703F82A0045C9AB /* LibraryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E508172703E8190045C9AB /* LibraryListView.swift */; }; @@ -2303,7 +2293,6 @@ 535BAE9F2649E569005FA86D /* ItemView.swift in Sources */, E10EAA4F277BBCC4000269ED /* CGSizeExtensions.swift in Sources */, E10D87E227852FD000BD264C /* EpisodesRowManager.swift in Sources */, - C40CD925271F8D1E000FB198 /* MovieLibrariesViewModel.swift in Sources */, 6225FCCB2663841E00E067F6 /* ParallaxHeader.swift in Sources */, 6220D0AD26D5EABB00B8E046 /* ViewExtensions.swift in Sources */, E13DD3EC27178A54009D4DAF /* UserSignInViewModel.swift in Sources */, @@ -2312,7 +2301,6 @@ E111DE222790BB46008118A3 /* DetectBottomScrollView.swift in Sources */, 5D160403278A41FD00D22B99 /* VLCPlayer+subtitles.swift in Sources */, 536D3D78267BD5C30004248C /* ViewModel.swift in Sources */, - C4BE078B272844AF003F4AD1 /* LiveTVChannelsView.swift in Sources */, E1FCD08826C35A0D007C8DCF /* NetworkError.swift in Sources */, E13DD3E527177D15009D4DAF /* ServerListView.swift in Sources */, E18845F826DEA9C900B0C5B7 /* ItemViewBody.swift in Sources */, @@ -2331,7 +2319,6 @@ E173DA5226D04AAF00CC4EB7 /* ColorExtension.swift in Sources */, E11D224227378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */, 6264E88C273850380081A12A /* Strings.swift in Sources */, - C4BE0766271FC109003F4AD1 /* TVLibrariesViewModel.swift in Sources */, C4AE2C3227498D6A00AE13CF /* LiveTVProgramsView.swift in Sources */, 62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */, 62E632E6267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */, @@ -2340,7 +2327,6 @@ E14F7D0926DB36F7007C3AE6 /* ItemLandscapeMainView.swift in Sources */, E107BB9327880A8F00354E07 /* CollectionItemViewModel.swift in Sources */, 532175402671EE4F005491E6 /* LibraryFilterView.swift in Sources */, - C4BE0763271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift in Sources */, 53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */, E1C812CE277AE43100918266 /* VideoPlayerViewModel.swift in Sources */, E10D87DA2784E4F100BD264C /* ItemViewDetailsView.swift in Sources */, @@ -2362,14 +2348,12 @@ E1E48CC9271E6D410021A2F9 /* RefreshHelper.swift in Sources */, E1AA33222782648000F6439C /* OverlaySliderColor.swift in Sources */, E1D4BF842719D25A00A11E64 /* TrackLanguage.swift in Sources */, - C4BE07792726EE82003F4AD1 /* LiveTVTabCoordinator.swift in Sources */, E14F7D0726DB36EF007C3AE6 /* ItemPortraitMainView.swift in Sources */, E1AD106226D9B7CD003E4A08 /* ItemPortraitHeaderOverlayView.swift in Sources */, 53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */, 6220D0C626D62D8700B8E046 /* iOSVideoPlayerCoordinator.swift in Sources */, E11B1B6C2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */, E1D4BF812719D22800A11E64 /* AppAppearance.swift in Sources */, - C4BE07712725EB06003F4AD1 /* LiveTVProgramsCoordinator.swift in Sources */, 621338B32660A07800A81A2A /* LazyView.swift in Sources */, 6220D0B126D5EC9900B8E046 /* SettingsCoordinator.swift in Sources */, E10D87DC2784EC5200BD264C /* EpisodesRowView.swift in Sources */, @@ -2397,17 +2381,14 @@ 091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */, 62E632EF267D43320063E547 /* LibraryFilterViewModel.swift in Sources */, 5D64683D277B1649009E09AE /* PreferenceUIHostingSwizzling.swift in Sources */, - C40CD922271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift in Sources */, E13DD3C827164B1E009D4DAF /* UIDeviceExtensions.swift in Sources */, E10EAA53277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */, E1AD104D26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */, E13DD3BF27163DD7009D4DAF /* AppDelegate.swift in Sources */, 535870AD2669D8DD00D05A09 /* Typings.swift in Sources */, - C4BE07882728448B003F4AD1 /* LiveTVChannelsCoordinator.swift in Sources */, E1AD105F26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift in Sources */, E13DD3D5271693CD009D4DAF /* SwiftfinStoreDefaults.swift in Sources */, 62E1DCC3273CE19800C9AE76 /* URLExtensions.swift in Sources */, - C4BE0769271FC164003F4AD1 /* TVLibrariesView.swift in Sources */, E1267D3E271A1F46003C492E /* PreferenceUIHostingController.swift in Sources */, 6220D0BA26D6092100B8E046 /* FilterCoordinator.swift in Sources */, E1E5D54C2783E27200692DFE /* ExperimentalSettingsView.swift in Sources */, @@ -2425,7 +2406,6 @@ 6220D0B726D5EE1100B8E046 /* SearchCoordinator.swift in Sources */, E13DD3EF27178F87009D4DAF /* SwiftfinNotificationCenter.swift in Sources */, 5377CBF5263B596A003A4E83 /* JellyfinPlayerApp.swift in Sources */, - C40CD928271F8DAB000FB198 /* MovieLibrariesView.swift in Sources */, E10D87DE278510E400BD264C /* PosterSize.swift in Sources */, E13DD4022717EE79009D4DAF /* UserListCoordinator.swift in Sources */, E1FCD09626C47118007C8DCF /* ErrorMessage.swift in Sources */, diff --git a/Swiftfin/Views/LibraryListView.swift b/Swiftfin/Views/LibraryListView.swift index 607c4a6d..8899b088 100644 --- a/Swiftfin/Views/LibraryListView.swift +++ b/Swiftfin/Views/LibraryListView.swift @@ -7,10 +7,12 @@ // import Foundation +import JellyfinAPI import Stinsen import SwiftUI struct LibraryListView: View { + @EnvironmentObject var libraryListRouter: LibraryListCoordinator.Router @StateObject @@ -21,7 +23,7 @@ struct LibraryListView: View { LazyVStack { Button { libraryListRouter.route(to: \.library, - (viewModel: LibraryViewModel(filters: viewModel.withFavorites), title: L10n.favorites)) + LibraryViewModel(libraryItem: BaseItemDto(), filters: viewModel.withFavorites)) } label: { HStack { Spacer() @@ -43,8 +45,9 @@ struct LibraryListView: View { if let collectionsLibraryItem = viewModel.libraries.first(where: { $0.collectionType == "boxsets" }) { Button { libraryListRouter.route(to: \.library, - (viewModel: LibraryViewModel(parentID: collectionsLibraryItem.id), - title: collectionsLibraryItem.name ?? "")) + LibraryViewModel(libraryItem: collectionsLibraryItem)) +// (viewModel: LibraryViewModel(parentID: collectionsLibraryItem.id), +// title: collectionsLibraryItem.name ?? "")) } label: { ZStack { ImageView(src: collectionsLibraryItem.getPrimaryImage(maxWidth: 500), From 0c95fb48a76d82bb77bee928020343983066417d Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Thu, 13 Jan 2022 22:49:25 -0700 Subject: [PATCH 3/9] undo filter change attempt --- Shared/Coordinators/FilterCoordinator.swift | 11 +- Shared/Coordinators/HomeCoordinator.swift | 8 +- Shared/Coordinators/ItemCoordinator.swift | 4 +- Shared/Coordinators/LibraryCoordinator.swift | 14 +- .../Coordinators/LibraryListCoordinator.swift | 4 +- .../Coordinators/TVLibrariesCoordinator.swift | 2 +- .../BaseItemDtoExtensions.swift | 4 +- .../ViewModels/LibraryFilterViewModel.swift | 3 +- Shared/ViewModels/LibraryListViewModel.swift | 58 +++---- Shared/ViewModels/LibraryViewModel.swift | 76 +++++----- .../AccentColor.colorset/Contents.json | 12 ++ .../Components/DetectBottomScrollView.swift | 141 +++++++++--------- Swiftfin/Components/PortraitItemButton.swift | 108 +++++++------- Swiftfin/Views/HomeView.swift | 6 +- Swiftfin/Views/LibraryListView.swift | 63 ++++---- Swiftfin/Views/LibrarySearchView.swift | 2 +- Swiftfin/Views/LibraryView.swift | 109 +++++++------- 17 files changed, 319 insertions(+), 306 deletions(-) diff --git a/Shared/Coordinators/FilterCoordinator.swift b/Shared/Coordinators/FilterCoordinator.swift index 7a02e915..10a13f05 100644 --- a/Shared/Coordinators/FilterCoordinator.swift +++ b/Shared/Coordinators/FilterCoordinator.swift @@ -7,11 +7,10 @@ // import Foundation -import JellyfinAPI import Stinsen import SwiftUI -typealias FilterCoordinatorParams = (libraryItem: BaseItemDto, filters: Binding, enabledFilterType: [FilterType]) +typealias FilterCoordinatorParams = (filters: Binding, enabledFilterType: [FilterType], parentId: String) final class FilterCoordinator: NavigationCoordinatable { @@ -20,19 +19,19 @@ final class FilterCoordinator: NavigationCoordinatable { @Root var start = makeStart - let libraryItem: BaseItemDto @Binding var filters: LibraryFilters var enabledFilterType: [FilterType] + var parentId: String = "" - init(libraryItem: BaseItemDto, filters: Binding, enabledFilterType: [FilterType]) { - self.libraryItem = libraryItem + init(filters: Binding, enabledFilterType: [FilterType], parentId: String) { _filters = filters self.enabledFilterType = enabledFilterType + self.parentId = parentId } @ViewBuilder func makeStart() -> some View { - LibraryFilterView(filters: $filters, enabledFilterType: enabledFilterType, parentId: libraryItem.id!) + LibraryFilterView(filters: $filters, enabledFilterType: enabledFilterType, parentId: parentId) } } diff --git a/Shared/Coordinators/HomeCoordinator.swift b/Shared/Coordinators/HomeCoordinator.swift index da3f6582..3ce9bc3f 100644 --- a/Shared/Coordinators/HomeCoordinator.swift +++ b/Shared/Coordinators/HomeCoordinator.swift @@ -32,8 +32,8 @@ final class HomeCoordinator: NavigationCoordinatable { NavigationViewCoordinator(SettingsCoordinator()) } - func makeLibrary(viewModel: LibraryViewModel) -> LibraryCoordinator { - LibraryCoordinator(viewModel: viewModel) + func makeLibrary(params: LibraryCoordinatorParams) -> LibraryCoordinator { + LibraryCoordinator(viewModel: params.viewModel, title: params.title) } func makeItem(item: BaseItemDto) -> ItemCoordinator { @@ -44,8 +44,8 @@ final class HomeCoordinator: NavigationCoordinatable { NavigationViewCoordinator(ItemCoordinator(item: item)) } - func makeModalLibrary(viewModel: LibraryViewModel) -> NavigationViewCoordinator { - NavigationViewCoordinator(LibraryCoordinator(viewModel: viewModel)) + func makeModalLibrary(params: LibraryCoordinatorParams) -> NavigationViewCoordinator { + NavigationViewCoordinator(LibraryCoordinator(viewModel: params.viewModel, title: params.title)) } @ViewBuilder diff --git a/Shared/Coordinators/ItemCoordinator.swift b/Shared/Coordinators/ItemCoordinator.swift index f8b0cfea..9bb29489 100644 --- a/Shared/Coordinators/ItemCoordinator.swift +++ b/Shared/Coordinators/ItemCoordinator.swift @@ -32,8 +32,8 @@ final class ItemCoordinator: NavigationCoordinatable { self.itemDto = item } - func makeLibrary(viewModel: LibraryViewModel) -> LibraryCoordinator { - LibraryCoordinator(viewModel: viewModel) + func makeLibrary(params: LibraryCoordinatorParams) -> LibraryCoordinator { + LibraryCoordinator(viewModel: params.viewModel, title: params.title) } func makeItem(item: BaseItemDto) -> ItemCoordinator { diff --git a/Shared/Coordinators/LibraryCoordinator.swift b/Shared/Coordinators/LibraryCoordinator.swift index 2176dea2..1678e407 100644 --- a/Shared/Coordinators/LibraryCoordinator.swift +++ b/Shared/Coordinators/LibraryCoordinator.swift @@ -11,6 +11,8 @@ import JellyfinAPI import Stinsen import SwiftUI +typealias LibraryCoordinatorParams = (viewModel: LibraryViewModel, title: String) + final class LibraryCoordinator: NavigationCoordinatable { let stack = NavigationStack(initial: \LibraryCoordinator.start) @@ -27,14 +29,16 @@ final class LibraryCoordinator: NavigationCoordinatable { var modalItem = makeModalItem let viewModel: LibraryViewModel + let title: String - init(viewModel: LibraryViewModel) { + init(viewModel: LibraryViewModel, title: String) { self.viewModel = viewModel + self.title = title } @ViewBuilder func makeStart() -> some View { - LibraryView(viewModel: self.viewModel) + LibraryView(viewModel: self.viewModel, title: title) } func makeSearch(viewModel: LibrarySearchViewModel) -> SearchCoordinator { @@ -42,9 +46,9 @@ final class LibraryCoordinator: NavigationCoordinatable { } func makeFilter(params: FilterCoordinatorParams) -> NavigationViewCoordinator { - NavigationViewCoordinator(FilterCoordinator(libraryItem: viewModel.libraryItem, - filters: params.filters, - enabledFilterType: params.enabledFilterType)) + NavigationViewCoordinator(FilterCoordinator(filters: params.filters, + enabledFilterType: params.enabledFilterType, + parentId: params.parentId)) } func makeItem(item: BaseItemDto) -> ItemCoordinator { diff --git a/Shared/Coordinators/LibraryListCoordinator.swift b/Shared/Coordinators/LibraryListCoordinator.swift index 00989fe6..a413ff83 100644 --- a/Shared/Coordinators/LibraryListCoordinator.swift +++ b/Shared/Coordinators/LibraryListCoordinator.swift @@ -27,8 +27,8 @@ final class LibraryListCoordinator: NavigationCoordinatable { self.viewModel = viewModel } - func makeLibrary(viewModel: LibraryViewModel) -> LibraryCoordinator { - LibraryCoordinator(viewModel: viewModel) + func makeLibrary(params: LibraryCoordinatorParams) -> LibraryCoordinator { + LibraryCoordinator(viewModel: params.viewModel, title: params.title) } func makeSearch(viewModel: LibrarySearchViewModel) -> SearchCoordinator { diff --git a/Shared/Coordinators/TVLibrariesCoordinator.swift b/Shared/Coordinators/TVLibrariesCoordinator.swift index 5335cd31..97c715af 100644 --- a/Shared/Coordinators/TVLibrariesCoordinator.swift +++ b/Shared/Coordinators/TVLibrariesCoordinator.swift @@ -34,6 +34,6 @@ final class TVLibrariesCoordinator: NavigationCoordinatable { } func makeLibrary(library: BaseItemDto) -> LibraryCoordinator { - LibraryCoordinator(viewModel: .init(libraryItem: <#T##BaseItemDto#>)) + LibraryCoordinator(viewModel: .init(libraryItem: <#T##BaseItemDto#>)) } } diff --git a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift index 849dffe8..fdcc7128 100644 --- a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift +++ b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift @@ -206,7 +206,7 @@ public extension BaseItemDto { case episode = "Episode" case series = "Series" case boxset = "BoxSet" - case collectionFolder = "CollectionFolder" + case collectionFolder = "CollectionFolder" case unknown @@ -229,7 +229,7 @@ public extension BaseItemDto { func portraitHeaderViewURL(maxWidth: Int) -> URL { switch itemType { - case .movie, .season, .series, .boxset, .collectionFolder: + case .movie, .season, .series, .boxset, .collectionFolder: return getPrimaryImage(maxWidth: maxWidth) case .episode: return getSeriesPrimaryImage(maxWidth: maxWidth) diff --git a/Shared/ViewModels/LibraryFilterViewModel.swift b/Shared/ViewModels/LibraryFilterViewModel.swift index 2e607508..527cacfa 100644 --- a/Shared/ViewModels/LibraryFilterViewModel.swift +++ b/Shared/ViewModels/LibraryFilterViewModel.swift @@ -67,7 +67,8 @@ final class LibraryFilterViewModel: ViewModel { } func requestQueryFilters() { - FilterAPI.getQueryFilters(userId: SessionManager.main.currentLogin.user.id, parentId: self.parentId) + FilterAPI.getQueryFilters(userId: SessionManager.main.currentLogin.user.id, + parentId: self.parentId) .trackActivity(loading) .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) diff --git a/Shared/ViewModels/LibraryListViewModel.swift b/Shared/ViewModels/LibraryListViewModel.swift index d4bc01e5..b1c2b85d 100644 --- a/Shared/ViewModels/LibraryListViewModel.swift +++ b/Shared/ViewModels/LibraryListViewModel.swift @@ -13,8 +13,8 @@ final class LibraryListViewModel: ViewModel { @Published var libraries: [BaseItemDto] = [] - @Published - var libraryRandomItems: [BaseItemDto: BaseItemDto] = [:] + @Published + var libraryRandomItems: [BaseItemDto: BaseItemDto] = [:] // temp var withFavorites = LibraryFilters(filters: [.isFavorite], sortOrder: [], withGenres: [], sortBy: []) @@ -31,34 +31,34 @@ final class LibraryListViewModel: ViewModel { .sink(receiveCompletion: { completion in self.handleAPIRequestError(completion: completion) }, receiveValue: { response in - if let libraries = response.items { - self.libraries = libraries - - for library in libraries { - self.getRandomLibraryItem(for: library) - } - } + if let libraries = response.items { + self.libraries = libraries + + for library in libraries { + self.getRandomLibraryItem(for: library) + } + } }) .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) - } + + // 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) + } } diff --git a/Shared/ViewModels/LibraryViewModel.swift b/Shared/ViewModels/LibraryViewModel.swift index 02cbe13b..3f150172 100644 --- a/Shared/ViewModels/LibraryViewModel.swift +++ b/Shared/ViewModels/LibraryViewModel.swift @@ -23,9 +23,9 @@ struct LibraryRowCell: Hashable { final class LibraryViewModel: ViewModel { @Published - var items: [BaseItemDto] = [] + var items: [BaseItemDto] = [] @Published - var rows: [LibraryRow] = [] + var rows: [LibraryRow] = [] @Published var totalPages = 0 @@ -37,13 +37,13 @@ final class LibraryViewModel: ViewModel { // temp @Published var filters: LibraryFilters - - let libraryItem: BaseItemDto - var person: BaseItemPerson? - var genre: NameGuidPair? - var studio: NameGuidPair? + + var parentID: String? + var person: BaseItemPerson? + var genre: NameGuidPair? + var studio: NameGuidPair? private let columns: Int - private let pageItemSize: Int + private let pageItemSize: Int var enabledFilterType: [FilterType] { if genre == nil { @@ -53,40 +53,38 @@ final class LibraryViewModel: ViewModel { } } - init(libraryItem: BaseItemDto, + init(parentID: String? = nil, person: BaseItemPerson? = nil, genre: NameGuidPair? = nil, studio: NameGuidPair? = nil, filters: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], sortBy: [.name]), columns: Int = 7) { - self.libraryItem = libraryItem + 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) - - print("Page item size: \(pageItemSize)") - + + // Size is typical size of portrait items + self.pageItemSize = UIScreen.itemsFillableOnScreen(width: 130, height: 185) + super.init() $filters - .sink(receiveValue: { newFilters in - self.requestItemsAsync(with: newFilters, replaceCurrentItems: true) - }) + .sink(receiveValue: { newFilters in + self.requestItemsAsync(with: newFilters, replaceCurrentItems: true) + }) .store(in: &cancellables) } - func requestItemsAsync(with filters: LibraryFilters, replaceCurrentItems: Bool = false) { - - if replaceCurrentItems { - self.items = [] - } - + func requestItemsAsync(with filters: LibraryFilters, replaceCurrentItems: Bool = false) { + + if replaceCurrentItems { + self.items = [] + } + let personIDs: [String] = [person].compactMap(\.?.id) let studioIDs: [String] = [studio].compactMap(\.?.id) let genreIDs: [String] @@ -102,7 +100,7 @@ final class LibraryViewModel: ViewModel { recursive: true, searchTerm: nil, sortOrder: filters.sortOrder, - parentId: libraryItem.id, + parentId: parentID, fields: [ .primaryImageAspectRatio, .seriesPrimaryImage, @@ -122,14 +120,14 @@ final class LibraryViewModel: ViewModel { studioIds: studioIDs, genreIds: genreIDs, enableImages: true) - .trackActivity(loading) + .trackActivity(loading) .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in - + guard let self = self else { return } - let totalPages = ceil(Double(response.totalRecordCount ?? 0) / Double(self.pageItemSize)) - + let totalPages = ceil(Double(response.totalRecordCount ?? 0) / Double(self.pageItemSize)) + self.totalPages = Int(totalPages) self.hasNextPage = self.currentPage < self.totalPages - 1 self.items.append(contentsOf: response.items ?? []) @@ -143,7 +141,7 @@ final class LibraryViewModel: ViewModel { requestItemsAsync(with: filters) } - // tvOS calculations for collection view + // tvOS calculations for collection view private func calculateRows(for itemList: [BaseItemDto]) -> [LibraryRow] { guard !itemList.isEmpty else { return [] } let rowCount = itemList.count / columns @@ -174,12 +172,12 @@ final class LibraryViewModel: ViewModel { } 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) - } + + 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) + } } diff --git a/Swiftfin/Assets.xcassets/AccentColor.colorset/Contents.json b/Swiftfin/Assets.xcassets/AccentColor.colorset/Contents.json index 10fdb69d..d383c2d1 100644 --- a/Swiftfin/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/Swiftfin/Assets.xcassets/AccentColor.colorset/Contents.json @@ -12,6 +12,18 @@ }, "idiom" : "iphone" }, + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.765", + "green" : "0.361", + "red" : "0.667" + } + }, + "idiom" : "ipad" + }, { "color" : { "color-space" : "srgb", diff --git a/Swiftfin/Components/DetectBottomScrollView.swift b/Swiftfin/Components/DetectBottomScrollView.swift index 5d275f6c..45a2500c 100644 --- a/Swiftfin/Components/DetectBottomScrollView.swift +++ b/Swiftfin/Components/DetectBottomScrollView.swift @@ -11,89 +11,86 @@ import SwiftUI // https://stackoverflow.com/questions/56573373/swiftui-get-size-of-child struct ChildSizeReader: 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 - } - } + @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 + typealias Value = CGSize + static var defaultValue: Value = .zero - static func reduce(value _: inout Value, nextValue: () -> Value) { - _ = nextValue() - } + 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() - } + 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" + 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 - } + @State + private var wholeSize: CGSize = .zero + @State + private var scrollViewSize: CGSize = .zero + @State + private var previousDidReachBottom = false + let content: () -> Content + let didReachBottom: (Bool) -> Void - 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 + init(content: @escaping () -> Content, + didReachBottom: @escaping (Bool) -> Void) + { + self.content = content + self.didReachBottom = didReachBottom + } - if value >= scrollViewSize.height - wholeSize.height { - if !previousDidReachBottom { - previousDidReachBottom = true - didReachBottom(true) - } - } else { - if previousDidReachBottom { - previousDidReachBottom = false - didReachBottom(false) - } - } - } - ) - } - } - .coordinateSpace(name: spaceName) - } - } + 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/PortraitItemButton.swift b/Swiftfin/Components/PortraitItemButton.swift index c6251064..98a52521 100644 --- a/Swiftfin/Components/PortraitItemButton.swift +++ b/Swiftfin/Components/PortraitItemButton.swift @@ -10,60 +10,60 @@ import JellyfinAPI import SwiftUI struct PortraitItemButton: 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) - } + let item: ItemType + let maxWidth: CGFloat + let horizontalAlignment: HorizontalAlignment + let textAlignment: TextAlignment + let selectedAction: (ItemType) -> Void - 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) - } + 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) + } } diff --git a/Swiftfin/Views/HomeView.swift b/Swiftfin/Views/HomeView.swift index a4fa04cf..e6e8f764 100644 --- a/Swiftfin/Views/HomeView.swift +++ b/Swiftfin/Views/HomeView.swift @@ -90,9 +90,9 @@ struct HomeView: View { Button { homeRouter -// .route(to: \.library, (viewModel: .init(parentID: library.id!, -// filters: viewModel.recentFilterSet), -// title: library.name ?? "")) + .route(to: \.library, (viewModel: .init(parentID: library.id!, + filters: viewModel.recentFilterSet), + title: library.name ?? "")) } label: { HStack { L10n.seeAll.text.font(.subheadline).fontWeight(.bold) diff --git a/Swiftfin/Views/LibraryListView.swift b/Swiftfin/Views/LibraryListView.swift index 8899b088..7c40f19a 100644 --- a/Swiftfin/Views/LibraryListView.swift +++ b/Swiftfin/Views/LibraryListView.swift @@ -12,7 +12,7 @@ import Stinsen import SwiftUI struct LibraryListView: View { - + @EnvironmentObject var libraryListRouter: LibraryListCoordinator.Router @StateObject @@ -23,31 +23,30 @@ struct LibraryListView: View { LazyVStack { Button { libraryListRouter.route(to: \.library, - LibraryViewModel(libraryItem: BaseItemDto(), filters: viewModel.withFavorites)) + (viewModel: LibraryViewModel(filters: viewModel.withFavorites), title: L10n.favorites)) } label: { - HStack { - Spacer() - L10n.yourFavorites.text - .foregroundColor(.black) - .font(.subheadline) - .fontWeight(.semibold) - Spacer() - } - .frame(height: 100) + HStack { + Spacer() + L10n.yourFavorites.text + .foregroundColor(.black) + .font(.subheadline) + .fontWeight(.semibold) + Spacer() + } + .frame(height: 100) .background(Color.white) } .cornerRadius(10) .shadow(radius: 5) - .padding() + .padding() if !viewModel.isLoading { if let collectionsLibraryItem = viewModel.libraries.first(where: { $0.collectionType == "boxsets" }) { Button { libraryListRouter.route(to: \.library, - LibraryViewModel(libraryItem: collectionsLibraryItem)) -// (viewModel: LibraryViewModel(parentID: collectionsLibraryItem.id), -// title: collectionsLibraryItem.name ?? "")) + (viewModel: LibraryViewModel(parentID: collectionsLibraryItem.id), + title: collectionsLibraryItem.name ?? "")) } label: { ZStack { ImageView(src: collectionsLibraryItem.getPrimaryImage(maxWidth: 500), @@ -64,15 +63,15 @@ struct LibraryListView: View { Spacer() } } - .background(Color.black) - .frame(height: 100) + .background(Color.black) + .frame(height: 100) } .cornerRadius(10) .shadow(radius: 5) - .padding() + .padding() } - ForEach(Array(viewModel.libraryRandomItems.keys), id: \.id) { library in + ForEach(Array(viewModel.libraryRandomItems.keys), id: \.id) { library in if library.collectionType ?? "" == "movies" || library.collectionType ?? "" == "tvshows" { Button { libraryListRouter.route(to: \.library, @@ -82,22 +81,22 @@ struct LibraryListView: View { ZStack { // ImageView(src: library.getPrimaryImage(maxWidth: 500), bh: library.getPrimaryImageBlurHash()) // .opacity(0.4) - - ImageView(src: viewModel.libraryRandomItems[library]!.getBackdropImage(maxWidth: 500)) - - VStack { - Text(library.name ?? "") - .foregroundColor(.white) - .font(.title2) - .fontWeight(.semibold) - } + + ImageView(src: viewModel.libraryRandomItems[library]!.getBackdropImage(maxWidth: 500)) + + VStack { + Text(library.name ?? "") + .foregroundColor(.white) + .font(.title2) + .fontWeight(.semibold) + } } - .background(Color.black) - .frame(height: 100) + .background(Color.black) + .frame(height: 100) } .cornerRadius(10) - .shadow(radius: 5) - .padding() + .shadow(radius: 5) + .padding() } else { EmptyView() } diff --git a/Swiftfin/Views/LibrarySearchView.swift b/Swiftfin/Views/LibrarySearchView.swift index d4ae3958..374917a2 100644 --- a/Swiftfin/Views/LibrarySearchView.swift +++ b/Swiftfin/Views/LibrarySearchView.swift @@ -88,7 +88,7 @@ struct LibrarySearchView: View { Button { searchRouter.route(to: \.item, item) } label: { - PortraitItemElement(item: item) + PortraitItemElement(item: item) } } } diff --git a/Swiftfin/Views/LibraryView.swift b/Swiftfin/Views/LibraryView.swift index d015d8d7..adf1d29e 100644 --- a/Swiftfin/Views/LibraryView.swift +++ b/Swiftfin/Views/LibraryView.swift @@ -10,84 +10,87 @@ import Stinsen import SwiftUI struct LibraryView: View { - + @EnvironmentObject var libraryRouter: LibraryCoordinator.Router @StateObject var viewModel: LibraryViewModel + var title: String // MARK: tracks for grid var defaultFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], tags: [], sortBy: [.name]) @State - private var tracks: [GridItem] = Array(repeating: .init(.flexible(), alignment: .top), 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() { 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() - } - } - } + + @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 { Group { - if viewModel.isLoading && viewModel.items.isEmpty { + if viewModel.isLoading && viewModel.items.isEmpty { ProgressView() } else if !viewModel.items.isEmpty { - libraryItemsView + libraryItemsView } else { noResultsView } } + .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItemGroup(placement: .navigationBarTrailing) { - - Button { -// libraryRouter -// .route(to: \.filter, (filters: $viewModel.filters, enabledFilterType: viewModel.enabledFilterType, -// parentId: viewModel.parentID ?? "")) - } label: { - Image(systemName: "line.horizontal.3.decrease.circle") - } - .foregroundColor(viewModel.filters == defaultFilters ? .accentColor : Color(UIColor.systemOrange)) - + + Button { + libraryRouter + .route(to: \.filter, (filters: $viewModel.filters, enabledFilterType: viewModel.enabledFilterType, + parentId: viewModel.parentID ?? "")) + } label: { + Image(systemName: "line.horizontal.3.decrease.circle") + } + .foregroundColor(viewModel.filters == defaultFilters ? .accentColor : Color(UIColor.systemOrange)) + Button { // libraryRouter.route(to: \.search, .init(parentID: viewModel.parentID)) } label: { From e700f10531357a68d2805f4463ba22ce74b72e08 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Thu, 13 Jan 2022 22:56:24 -0700 Subject: [PATCH 4/9] Update LibraryView.swift --- Swiftfin/Views/LibraryView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Swiftfin/Views/LibraryView.swift b/Swiftfin/Views/LibraryView.swift index adf1d29e..d2efe0f3 100644 --- a/Swiftfin/Views/LibraryView.swift +++ b/Swiftfin/Views/LibraryView.swift @@ -92,7 +92,7 @@ struct LibraryView: View { .foregroundColor(viewModel.filters == defaultFilters ? .accentColor : Color(UIColor.systemOrange)) Button { -// libraryRouter.route(to: \.search, .init(parentID: viewModel.parentID)) + libraryRouter.route(to: \.search, .init(parentID: viewModel.parentID)) } label: { Image(systemName: "magnifyingglass") } From d47268cfe1d52c0fa876137c243f0fba35071464 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Thu, 13 Jan 2022 22:59:31 -0700 Subject: [PATCH 5/9] fix tvOS from previous attempt at work --- Shared/Coordinators/MoviesLibrariesCoordinator.swift | 2 +- Shared/Coordinators/TVLibrariesCoordinator.swift | 2 +- Swiftfin tvOS/Views/LibraryView.swift | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Shared/Coordinators/MoviesLibrariesCoordinator.swift b/Shared/Coordinators/MoviesLibrariesCoordinator.swift index 142238d2..15e14888 100644 --- a/Shared/Coordinators/MoviesLibrariesCoordinator.swift +++ b/Shared/Coordinators/MoviesLibrariesCoordinator.swift @@ -34,6 +34,6 @@ final class MovieLibrariesCoordinator: NavigationCoordinatable { } func makeLibrary(library: BaseItemDto) -> LibraryCoordinator { -// LibraryCoordinator(viewModel: LibraryViewModel(parentID: library.id), title: library.title) + LibraryCoordinator(viewModel: LibraryViewModel(parentID: library.id), title: library.title) } } diff --git a/Shared/Coordinators/TVLibrariesCoordinator.swift b/Shared/Coordinators/TVLibrariesCoordinator.swift index 97c715af..cdd2a493 100644 --- a/Shared/Coordinators/TVLibrariesCoordinator.swift +++ b/Shared/Coordinators/TVLibrariesCoordinator.swift @@ -34,6 +34,6 @@ final class TVLibrariesCoordinator: NavigationCoordinatable { } func makeLibrary(library: BaseItemDto) -> LibraryCoordinator { - LibraryCoordinator(viewModel: .init(libraryItem: <#T##BaseItemDto#>)) + LibraryCoordinator(viewModel: LibraryViewModel(parentID: library.id), title: library.title) } } diff --git a/Swiftfin tvOS/Views/LibraryView.swift b/Swiftfin tvOS/Views/LibraryView.swift index 02df6a86..08f957e5 100644 --- a/Swiftfin tvOS/Views/LibraryView.swift +++ b/Swiftfin tvOS/Views/LibraryView.swift @@ -15,6 +15,7 @@ struct LibraryView: View { var libraryRouter: LibraryCoordinator.Router @StateObject var viewModel: LibraryViewModel + var title: String // MARK: tracks for grid From 1c74928d938f886e87d5c9f88711efa5406e6e13 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Thu, 13 Jan 2022 23:03:39 -0700 Subject: [PATCH 6/9] revert LibraryListView --- .../Coordinators/TVLibrariesCoordinator.swift | 2 +- Swiftfin tvOS/Views/LibraryView.swift | 2 +- Swiftfin/Views/LibraryListView.swift | 70 ++++++++++--------- 3 files changed, 39 insertions(+), 35 deletions(-) diff --git a/Shared/Coordinators/TVLibrariesCoordinator.swift b/Shared/Coordinators/TVLibrariesCoordinator.swift index cdd2a493..b2ec1121 100644 --- a/Shared/Coordinators/TVLibrariesCoordinator.swift +++ b/Shared/Coordinators/TVLibrariesCoordinator.swift @@ -34,6 +34,6 @@ final class TVLibrariesCoordinator: NavigationCoordinatable { } func makeLibrary(library: BaseItemDto) -> LibraryCoordinator { - LibraryCoordinator(viewModel: LibraryViewModel(parentID: library.id), title: library.title) + LibraryCoordinator(viewModel: LibraryViewModel(parentID: library.id), title: library.title) } } diff --git a/Swiftfin tvOS/Views/LibraryView.swift b/Swiftfin tvOS/Views/LibraryView.swift index 08f957e5..6ee1fe63 100644 --- a/Swiftfin tvOS/Views/LibraryView.swift +++ b/Swiftfin tvOS/Views/LibraryView.swift @@ -15,7 +15,7 @@ struct LibraryView: View { var libraryRouter: LibraryCoordinator.Router @StateObject var viewModel: LibraryViewModel - var title: String + var title: String // MARK: tracks for grid diff --git a/Swiftfin/Views/LibraryListView.swift b/Swiftfin/Views/LibraryListView.swift index 7c40f19a..2438e35c 100644 --- a/Swiftfin/Views/LibraryListView.swift +++ b/Swiftfin/Views/LibraryListView.swift @@ -7,12 +7,10 @@ // import Foundation -import JellyfinAPI import Stinsen import SwiftUI struct LibraryListView: View { - @EnvironmentObject var libraryListRouter: LibraryListCoordinator.Router @StateObject @@ -25,20 +23,23 @@ struct LibraryListView: View { libraryListRouter.route(to: \.library, (viewModel: LibraryViewModel(filters: viewModel.withFavorites), title: L10n.favorites)) } label: { - HStack { - Spacer() - L10n.yourFavorites.text - .foregroundColor(.black) - .font(.subheadline) - .fontWeight(.semibold) - Spacer() + ZStack { + HStack { + Spacer() + L10n.yourFavorites.text + .foregroundColor(.black) + .font(.subheadline) + .fontWeight(.semibold) + Spacer() + } } - .frame(height: 100) + .padding(16) .background(Color.white) + .frame(minWidth: 100, maxWidth: .infinity) } .cornerRadius(10) .shadow(radius: 5) - .padding() + .padding(.bottom, 5) if !viewModel.isLoading { @@ -61,17 +62,17 @@ struct LibraryListView: View { .fontWeight(.semibold) } Spacer() - } - } - .background(Color.black) - .frame(height: 100) + }.padding(32) + }.background(Color.black) + .frame(minWidth: 100, maxWidth: .infinity) + .frame(height: 100) } .cornerRadius(10) .shadow(radius: 5) - .padding() + .padding(.bottom, 5) } - ForEach(Array(viewModel.libraryRandomItems.keys), id: \.id) { library in + ForEach(viewModel.libraries, id: \.id) { library in if library.collectionType ?? "" == "movies" || library.collectionType ?? "" == "tvshows" { Button { libraryListRouter.route(to: \.library, @@ -79,24 +80,25 @@ struct LibraryListView: View { title: library.name ?? "")) } label: { ZStack { -// ImageView(src: library.getPrimaryImage(maxWidth: 500), bh: library.getPrimaryImageBlurHash()) -// .opacity(0.4) - - ImageView(src: viewModel.libraryRandomItems[library]!.getBackdropImage(maxWidth: 500)) - - VStack { - Text(library.name ?? "") - .foregroundColor(.white) - .font(.title2) - .fontWeight(.semibold) - } - } - .background(Color.black) - .frame(height: 100) + ImageView(src: library.getPrimaryImage(maxWidth: 500), bh: library.getPrimaryImageBlurHash()) + .opacity(0.4) + HStack { + Spacer() + VStack { + Text(library.name ?? "") + .foregroundColor(.white) + .font(.title2) + .fontWeight(.semibold) + } + Spacer() + }.padding(32) + }.background(Color.black) + .frame(minWidth: 100, maxWidth: .infinity) + .frame(height: 100) } .cornerRadius(10) .shadow(radius: 5) - .padding() + .padding(.bottom, 5) } else { EmptyView() } @@ -104,7 +106,9 @@ struct LibraryListView: View { } else { ProgressView() } - } + }.padding(.leading, 16) + .padding(.trailing, 16) + .padding(.top, 8) } .navigationTitle(L10n.allMedia) .toolbar { From 7f55945c948b9c937f84fe043027e637bb3df689 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Thu, 13 Jan 2022 23:13:49 -0700 Subject: [PATCH 7/9] revert some work and fix collections --- .../BaseItemDto+Stackable.swift | 2 +- Shared/ViewModels/LibraryListViewModel.swift | 30 +------------------ Shared/ViewModels/LibraryViewModel.swift | 3 +- 3 files changed, 4 insertions(+), 31 deletions(-) diff --git a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+Stackable.swift b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+Stackable.swift index fb1b6bb4..d44f38ec 100644 --- a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+Stackable.swift +++ b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+Stackable.swift @@ -56,7 +56,7 @@ extension BaseItemDto: PortraitImageStackable { public var showTitle: Bool { switch self.itemType { - case .episode, .series, .movie: + case .episode, .series, .movie, .boxset: return Defaults[.showPosterLabels] default: return true diff --git a/Shared/ViewModels/LibraryListViewModel.swift b/Shared/ViewModels/LibraryListViewModel.swift index b1c2b85d..25ed0557 100644 --- a/Shared/ViewModels/LibraryListViewModel.swift +++ b/Shared/ViewModels/LibraryListViewModel.swift @@ -13,8 +13,6 @@ final class LibraryListViewModel: ViewModel { @Published var libraries: [BaseItemDto] = [] - @Published - var libraryRandomItems: [BaseItemDto: BaseItemDto] = [:] // temp var withFavorites = LibraryFilters(filters: [.isFavorite], sortOrder: [], withGenres: [], sortBy: []) @@ -31,34 +29,8 @@ final class LibraryListViewModel: ViewModel { .sink(receiveCompletion: { completion in self.handleAPIRequestError(completion: completion) }, receiveValue: { response in - if let libraries = response.items { - self.libraries = libraries - - for library in libraries { - self.getRandomLibraryItem(for: library) - } - } + self.libraries = response.items ?? [] }) .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) - } } diff --git a/Shared/ViewModels/LibraryViewModel.swift b/Shared/ViewModels/LibraryViewModel.swift index 3f150172..12581b39 100644 --- a/Shared/ViewModels/LibraryViewModel.swift +++ b/Shared/ViewModels/LibraryViewModel.swift @@ -111,7 +111,8 @@ final class LibraryViewModel: ViewModel { .chapters, ], includeItemTypes: filters.filters - .contains(.isFavorite) ? ["Movie", "Series", "Season", "Episode"] : ["Movie", "Series"], + .contains(.isFavorite) ? ["Movie", "Series", "Season", "Episode", "BoxSet"] : + ["Movie", "Series", "BoxSet"], filters: filters.filters, sortBy: sortBy, tags: filters.tags, From 5b9521caaa7cb8bc64d25e8723a73193ce1627e9 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Thu, 13 Jan 2022 23:26:06 -0700 Subject: [PATCH 8/9] begin moving things to customize settings --- Shared/Coordinators/SettingsCoordinator.swift | 7 ++++ Shared/Generated/Strings.swift | 2 ++ .../SettingsView/CustomizeViewsSettings.swift | 33 ++++++++++++++++++ .../Views/SettingsView/SettingsView.swift | 11 +++++- Swiftfin.xcodeproj/project.pbxproj | 10 +++++- .../SettingsView/CustomizeViewsSettings.swift | 31 ++++++++++++++++ .../Views/SettingsView/SettingsView.swift | 17 +++++---- Translations/en.lproj/Localizable.strings | Bin 11386 -> 11440 bytes 8 files changed, 103 insertions(+), 8 deletions(-) create mode 100644 Swiftfin tvOS/Views/SettingsView/CustomizeViewsSettings.swift create mode 100644 Swiftfin/Views/SettingsView/CustomizeViewsSettings.swift diff --git a/Shared/Coordinators/SettingsCoordinator.swift b/Shared/Coordinators/SettingsCoordinator.swift index 2467a708..5ffdaf27 100644 --- a/Shared/Coordinators/SettingsCoordinator.swift +++ b/Shared/Coordinators/SettingsCoordinator.swift @@ -23,6 +23,8 @@ final class SettingsCoordinator: NavigationCoordinatable { @Route(.push) var experimentalSettings = makeExperimentalSettings @Route(.push) + var customizeViewsSettings = makeCustomizeViewsSettings + @Route(.push) var missingSettings = makeMissingSettings @ViewBuilder @@ -41,6 +43,11 @@ final class SettingsCoordinator: NavigationCoordinatable { ExperimentalSettingsView() } + @ViewBuilder + func makeCustomizeViewsSettings() -> some View { + CustomizeViewsSettings() + } + @ViewBuilder func makeMissingSettings() -> some View { MissingItemsSettingsView() diff --git a/Shared/Generated/Strings.swift b/Shared/Generated/Strings.swift index 3842116f..e62b4404 100644 --- a/Shared/Generated/Strings.swift +++ b/Shared/Generated/Strings.swift @@ -76,6 +76,8 @@ internal enum L10n { internal static let `continue` = L10n.tr("Localizable", "continue") /// Continue Watching internal static let continueWatching = L10n.tr("Localizable", "continueWatching") + /// Customize + internal static let customize = L10n.tr("Localizable", "customize") /// Dark internal static let dark = L10n.tr("Localizable", "dark") /// Default Scheme diff --git a/Swiftfin tvOS/Views/SettingsView/CustomizeViewsSettings.swift b/Swiftfin tvOS/Views/SettingsView/CustomizeViewsSettings.swift new file mode 100644 index 00000000..65764e6f --- /dev/null +++ b/Swiftfin tvOS/Views/SettingsView/CustomizeViewsSettings.swift @@ -0,0 +1,33 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2022 Jellyfin & Jellyfin Contributors +// + +import Defaults +import SwiftUI + +struct CustomizeViewsSettings: View { + + @Default(.showPosterLabels) + var showPosterLabels + @Default(.showCastAndCrew) + var showCastAndCrew + + var body: some View { + Form { + Section { + + Toggle(L10n.showPosterLabels, isOn: $showPosterLabels) + + // TODO: Uncomment when cast and crew implemented in item views + // Toggle(L10n.showCastAndCrew, isOn: $showCastAndCrew) + + } header: { + L10n.customize.text + } + } + } +} diff --git a/Swiftfin tvOS/Views/SettingsView/SettingsView.swift b/Swiftfin tvOS/Views/SettingsView/SettingsView.swift index 212c076b..49f611a5 100644 --- a/Swiftfin tvOS/Views/SettingsView/SettingsView.swift +++ b/Swiftfin tvOS/Views/SettingsView/SettingsView.swift @@ -131,7 +131,16 @@ struct SettingsView: View { } Section(header: L10n.accessibility.text) { - Toggle(L10n.showPosterLabels, isOn: $showPosterLabels) + Button { + settingsRouter.route(to: \.customizeViewsSettings) + } label: { + HStack { + L10n.customize.text + .foregroundColor(.primary) + Spacer() + Image(systemName: "chevron.right") + } + } Button { settingsRouter.route(to: \.missingSettings) diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index ae5f4bae..c1d4ca3f 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -395,6 +395,8 @@ E1C812CE277AE43100918266 /* VideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C812C9277AE40900918266 /* VideoPlayerViewModel.swift */; }; E1C812D1277AE4E300918266 /* tvOSVideoPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C812D0277AE4E300918266 /* tvOSVideoPlayerCoordinator.swift */; }; E1C812D2277AE50A00918266 /* URLComponentsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C812C4277A90B200918266 /* URLComponentsExtensions.swift */; }; + E1CEFBF527914C7700F60429 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CEFBF427914C7700F60429 /* CustomizeViewsSettings.swift */; }; + E1CEFBF727914E6400F60429 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CEFBF627914E6400F60429 /* CustomizeViewsSettings.swift */; }; E1D4BF7C2719D05000A11E64 /* BasicAppSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF7B2719D05000A11E64 /* BasicAppSettingsView.swift */; }; E1D4BF7E2719D1DD00A11E64 /* BasicAppSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF7D2719D1DC00A11E64 /* BasicAppSettingsViewModel.swift */; }; E1D4BF7F2719D1DD00A11E64 /* BasicAppSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF7D2719D1DC00A11E64 /* BasicAppSettingsViewModel.swift */; }; @@ -732,6 +734,8 @@ E1C812C8277AE40900918266 /* VideoPlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = ""; }; E1C812C9277AE40900918266 /* VideoPlayerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoPlayerViewModel.swift; sourceTree = ""; }; E1C812D0277AE4E300918266 /* tvOSVideoPlayerCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = tvOSVideoPlayerCoordinator.swift; sourceTree = ""; }; + E1CEFBF427914C7700F60429 /* CustomizeViewsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeViewsSettings.swift; sourceTree = ""; }; + E1CEFBF627914E6400F60429 /* CustomizeViewsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeViewsSettings.swift; sourceTree = ""; }; E1D4BF7B2719D05000A11E64 /* BasicAppSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicAppSettingsView.swift; sourceTree = ""; }; E1D4BF7D2719D1DC00A11E64 /* BasicAppSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicAppSettingsViewModel.swift; sourceTree = ""; }; E1D4BF802719D22800A11E64 /* AppAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAppearance.swift; sourceTree = ""; }; @@ -1648,10 +1652,11 @@ E1E5D54A2783E26100692DFE /* SettingsView */ = { isa = PBXGroup; children = ( + E1CEFBF427914C7700F60429 /* CustomizeViewsSettings.swift */, E1E5D54B2783E27200692DFE /* ExperimentalSettingsView.swift */, + E176DE6F278E369F001EFD8D /* MissingItemsSettingsView.swift */, E1E5D5472783CCF900692DFE /* OverlaySettingsView.swift */, 539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */, - E176DE6F278E369F001EFD8D /* MissingItemsSettingsView.swift */, ); path = SettingsView; sourceTree = ""; @@ -1659,6 +1664,7 @@ E1E5D54D2783E66600692DFE /* SettingsView */ = { isa = PBXGroup; children = ( + E1CEFBF627914E6400F60429 /* CustomizeViewsSettings.swift */, E1E5D5502783E67700692DFE /* ExperimentalSettingsView.swift */, E1BDE358278E9ED2004E4022 /* MissingItemsSettingsView.swift */, E1E5D54E2783E67100692DFE /* OverlaySettingsView.swift */, @@ -2222,6 +2228,7 @@ E1E00A36278628A40022235B /* DoubleExtensions.swift in Sources */, E1FA2F7427818A8800B4C270 /* SmallMenuOverlay.swift in Sources */, E193D53C27193F9500900D82 /* UserListCoordinator.swift in Sources */, + E1CEFBF727914E6400F60429 /* CustomizeViewsSettings.swift in Sources */, E13DD3C927164B1E009D4DAF /* UIDeviceExtensions.swift in Sources */, 535870A62669D8AE00D05A09 /* LazyView.swift in Sources */, E193D53A27193F9000900D82 /* ServerListCoordinator.swift in Sources */, @@ -2314,6 +2321,7 @@ 62E632DA267D2BC40063E547 /* LatestMediaViewModel.swift in Sources */, E1AD105C26D9ABDD003E4A08 /* PillHStackView.swift in Sources */, 625CB56F2678C23300530A6E /* HomeView.swift in Sources */, + E1CEFBF527914C7700F60429 /* CustomizeViewsSettings.swift in Sources */, E1C812BC277A8E5D00918266 /* PlaybackSpeed.swift in Sources */, E1E5D5492783CDD700692DFE /* OverlaySettingsView.swift in Sources */, E173DA5226D04AAF00CC4EB7 /* ColorExtension.swift in Sources */, diff --git a/Swiftfin/Views/SettingsView/CustomizeViewsSettings.swift b/Swiftfin/Views/SettingsView/CustomizeViewsSettings.swift new file mode 100644 index 00000000..2cbe816d --- /dev/null +++ b/Swiftfin/Views/SettingsView/CustomizeViewsSettings.swift @@ -0,0 +1,31 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2022 Jellyfin & Jellyfin Contributors +// + +import Defaults +import SwiftUI + +struct CustomizeViewsSettings: View { + + @Default(.showPosterLabels) + var showPosterLabels + @Default(.showCastAndCrew) + var showCastAndCrew + + var body: some View { + Form { + Section { + + Toggle(L10n.showPosterLabels, isOn: $showPosterLabels) + Toggle(L10n.showCastAndCrew, isOn: $showCastAndCrew) + + } header: { + L10n.customize.text + } + } + } +} diff --git a/Swiftfin/Views/SettingsView/SettingsView.swift b/Swiftfin/Views/SettingsView/SettingsView.swift index de5d8f39..a99fe88e 100644 --- a/Swiftfin/Views/SettingsView/SettingsView.swift +++ b/Swiftfin/Views/SettingsView/SettingsView.swift @@ -38,10 +38,6 @@ struct SettingsView: View { var jumpBackwardLength @Default(.jumpGesturesEnabled) var jumpGesturesEnabled - @Default(.showPosterLabels) - var showPosterLabels - @Default(.showCastAndCrew) - var showCastAndCrew @Default(.resumeOffset) var resumeOffset @Default(.subtitleSize) @@ -138,8 +134,17 @@ struct SettingsView: View { } Section(header: L10n.accessibility.text) { - Toggle(L10n.showPosterLabels, isOn: $showPosterLabels) - Toggle(L10n.showCastAndCrew, isOn: $showCastAndCrew) + + Button { + settingsRouter.route(to: \.customizeViewsSettings) + } label: { + HStack { + L10n.customize.text + .foregroundColor(.primary) + Spacer() + Image(systemName: "chevron.right") + } + } Button { settingsRouter.route(to: \.missingSettings) diff --git a/Translations/en.lproj/Localizable.strings b/Translations/en.lproj/Localizable.strings index 77dcbd36d5f60fdc53ababf7df5793cb1fbd7079..cc09b41e36cdfbc25e4b2280cd27dce5b40fde0f 100644 GIT binary patch delta 62 zcmewru_1Cpm5!MbLo!1tLoq`MLq0<;LncENLn?z3g93vs5Gye_W0A9F;9>v(?XnEm delta 7 OcmdlG`72^Wl@0(9UjwoL From fd24f2af67a5fa2551cef2cf57da0202132cced3 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Thu, 13 Jan 2022 23:32:27 -0700 Subject: [PATCH 9/9] remove dev team --- Swiftfin.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index c1d4ca3f..827d3b35 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -2792,7 +2792,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2829,7 +2829,7 @@ CURRENT_PROJECT_VERSION = 66; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2860,7 +2860,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -2887,7 +2887,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = (