From 2b888e9b82a14abaf9d6b1d1212cbc5e15c02aa5 Mon Sep 17 00:00:00 2001 From: jhays Date: Wed, 20 Oct 2021 17:58:45 -0500 Subject: [PATCH 1/5] Additional coordinators and routing fixes --- .../Views/ContinueWatchingView.swift | 7 +- JellyfinPlayer tvOS/Views/HomeView.swift | 7 +- .../Views/LibraryListView.swift | 42 +----- JellyfinPlayer tvOS/Views/LibraryView.swift | 129 +++++++++--------- .../Views/MovieLibrariesView.swift | 82 +++++++++++ JellyfinPlayer tvOS/Views/NextUpView.swift | 7 +- .../Views/TVLibrariesView.swift | 80 +++++++++++ JellyfinPlayer.xcodeproj/project.pbxproj | 36 +++++ Shared/Coordinators/HomeCoordinator.swift | 10 ++ Shared/Coordinators/LibraryCoordinator.swift | 5 + .../tvOSMainTabCoordinator.swift | 36 ++++- .../MoviesLibrariesCoordinator.swift | 37 +++++ .../Coordinators/TVLibrariesCoordinator.swift | 37 +++++ Shared/ViewModels/LibraryViewModel.swift | 87 ++++++------ .../ViewModels/MovieLibrariesViewModel.swift | 99 ++++++++++++++ Shared/ViewModels/TVLibrariesViewModel.swift | 99 ++++++++++++++ 16 files changed, 645 insertions(+), 155 deletions(-) create mode 100644 JellyfinPlayer tvOS/Views/MovieLibrariesView.swift create mode 100644 JellyfinPlayer tvOS/Views/TVLibrariesView.swift create mode 100644 Shared/Coordinators/MoviesLibrariesCoordinator.swift create mode 100644 Shared/Coordinators/TVLibrariesCoordinator.swift create mode 100644 Shared/ViewModels/MovieLibrariesViewModel.swift create mode 100644 Shared/ViewModels/TVLibrariesViewModel.swift diff --git a/JellyfinPlayer tvOS/Views/ContinueWatchingView.swift b/JellyfinPlayer tvOS/Views/ContinueWatchingView.swift index ad793923..fd59664a 100644 --- a/JellyfinPlayer tvOS/Views/ContinueWatchingView.swift +++ b/JellyfinPlayer tvOS/Views/ContinueWatchingView.swift @@ -9,11 +9,14 @@ import SwiftUI import JellyfinAPI import Combine +import Stinsen struct ContinueWatchingView: View { var items: [BaseItemDto] @Namespace private var namespace + var homeRouter: HomeCoordinator.Router? = RouterStore.shared.retrieve() + var body: some View { VStack(alignment: .leading) { if items.count > 0 { @@ -25,7 +28,9 @@ struct ContinueWatchingView: View { LazyHStack { Spacer().frame(width: 45) ForEach(items, id: \.id) { item in - NavigationLink(destination: LazyView { ItemView(item: item) }) { + Button { + self.homeRouter?.route(to: \.modalItem, item) + } label: { LandscapeItemElement(item: item) } .buttonStyle(PlainNavigationLinkButtonStyle()) diff --git a/JellyfinPlayer tvOS/Views/HomeView.swift b/JellyfinPlayer tvOS/Views/HomeView.swift index 8642a1a6..6ab1d03f 100644 --- a/JellyfinPlayer tvOS/Views/HomeView.swift +++ b/JellyfinPlayer tvOS/Views/HomeView.swift @@ -11,6 +11,7 @@ import Foundation import SwiftUI struct HomeView: View { + @EnvironmentObject var homeRouter: HomeCoordinator.Router @StateObject var viewModel = HomeViewModel() @State var showingSettings = false @@ -33,9 +34,9 @@ struct HomeView: View { VStack(alignment: .leading) { let library = viewModel.libraries.first(where: { $0.id == libraryID }) - NavigationLink(destination: LazyView { - LibraryView(viewModel: .init(parentID: libraryID, filters: viewModel.recentFilterSet), title: library?.name ?? "") - }) { + Button { + self.homeRouter.route(to: \.modalLibrary, (.init(parentID: libraryID, filters: viewModel.recentFilterSet), title: library?.name ?? "")) + } label: { HStack { Text("Latest \(library?.name ?? "")") .font(.headline) diff --git a/JellyfinPlayer tvOS/Views/LibraryListView.swift b/JellyfinPlayer tvOS/Views/LibraryListView.swift index 7096ba08..051dcbbf 100644 --- a/JellyfinPlayer tvOS/Views/LibraryListView.swift +++ b/JellyfinPlayer tvOS/Views/LibraryListView.swift @@ -16,47 +16,11 @@ struct LibraryListView: View { var body: some View { ScrollView { LazyVStack { - NavigationLink(destination: LazyView { - LibraryView(viewModel: .init(filters: viewModel.withFavorites), title: "Favorites") - }) { - ZStack { - HStack { - Spacer() - Text("Your Favorites") - .font(.subheadline) - .fontWeight(.semibold) - Spacer() - } - } - .padding(16) - .frame(minWidth: 100, maxWidth: .infinity) - } - .cornerRadius(10) - .shadow(radius: 5) - .padding(.bottom, 5) - - NavigationLink(destination: LazyView { - Text("WIP") - }) { - ZStack { - HStack { - Spacer() - Text("All Genres") - .font(.subheadline) - .fontWeight(.semibold) - Spacer() - } - } - .padding(16) - .frame(minWidth: 100, maxWidth: .infinity) - } - .cornerRadius(10) - .shadow(radius: 5) - .padding(.bottom, 15) - if !viewModel.isLoading { ForEach(viewModel.libraries, id: \.id) { library in if library.collectionType ?? "" == "movies" || library.collectionType ?? "" == "tvshows" { + EmptyView() + } else { NavigationLink(destination: LazyView { LibraryView(viewModel: .init(parentID: library.id), title: library.name ?? "") }) { @@ -80,8 +44,6 @@ struct LibraryListView: View { .cornerRadius(10) .shadow(radius: 5) .padding(.bottom, 5) - } else { - EmptyView() } } } else { diff --git a/JellyfinPlayer tvOS/Views/LibraryView.swift b/JellyfinPlayer tvOS/Views/LibraryView.swift index e7035067..d4f05aeb 100644 --- a/JellyfinPlayer tvOS/Views/LibraryView.swift +++ b/JellyfinPlayer tvOS/Views/LibraryView.swift @@ -11,80 +11,85 @@ import SwiftUICollection import JellyfinAPI struct LibraryView: View { - @StateObject var viewModel: LibraryViewModel - var title: String + @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]) + // MARK: tracks for grid + var defaultFilters = LibraryFilters(filters: [], sortOrder: [.ascending], withGenres: [], tags: [], sortBy: [.name]) - @State var isShowingSearchView = false - @State var isShowingFilterView = false - - var body: some View { + @State var isShowingSearchView = false + @State var isShowingFilterView = false + + var body: some View { if viewModel.isLoading == true { ProgressView() - } else if !viewModel.items.isEmpty { - CollectionView(rows: viewModel.rows) { _, _ in - let itemSize = NSCollectionLayoutSize( - widthDimension: .fractionalWidth(1), - heightDimension: .fractionalHeight(1) - ) - let item = NSCollectionLayoutItem(layoutSize: itemSize) + } else if !viewModel.rows.isEmpty { + CollectionView(rows: viewModel.rows) { _, _ in + let itemSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1), + heightDimension: .fractionalHeight(1) + ) + let item = NSCollectionLayoutItem(layoutSize: itemSize) - let groupSize = NSCollectionLayoutSize( - widthDimension: .absolute(200), - heightDimension: .absolute(300) - ) - let group = NSCollectionLayoutGroup.horizontal( - layoutSize: groupSize, - subitems: [item] - ) + let groupSize = NSCollectionLayoutSize( + widthDimension: .absolute(200), + heightDimension: .absolute(300) + ) + let group = NSCollectionLayoutGroup.horizontal( + layoutSize: groupSize, + subitems: [item] + ) - let header = - NSCollectionLayoutBoundarySupplementaryItem( - layoutSize: NSCollectionLayoutSize( - widthDimension: .fractionalWidth(1), - heightDimension: .absolute(44) - ), - elementKind: UICollectionView.elementKindSectionHeader, - alignment: .topLeading - ) + let header = + NSCollectionLayoutBoundarySupplementaryItem( + layoutSize: NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1), + heightDimension: .absolute(44) + ), + elementKind: UICollectionView.elementKindSectionHeader, + alignment: .topLeading + ) - let section = NSCollectionLayoutSection(group: group) + let section = NSCollectionLayoutSection(group: group) - section.contentInsets = NSDirectionalEdgeInsets(top: 30, leading: 0, bottom: 80, trailing: 80) - section.interGroupSpacing = 48 - section.orthogonalScrollingBehavior = .continuous - section.boundarySupplementaryItems = [header] - return section - } cell: { _, cell in - GeometryReader { _ in - if let item = cell.item { - if item.type != "Folder" { - NavigationLink(destination: LazyView { ItemView(item: item) }) { - PortraitItemElement(item: item) - } - .buttonStyle(PlainNavigationLinkButtonStyle()) - .onAppear { - if item == viewModel.items.last && viewModel.hasNextPage { - viewModel.requestNextPageAsync() + section.contentInsets = NSDirectionalEdgeInsets(top: 30, leading: 0, bottom: 80, trailing: 80) + section.interGroupSpacing = 48 + section.orthogonalScrollingBehavior = .continuous + section.boundarySupplementaryItems = [header] + return section + } cell: { _, cell in + GeometryReader { _ in + if let item = cell.item { + if item.type != "Folder" { + Button { + libraryRouter.route(to: \.modalItem, item) + } label: { + PortraitItemElement(item: item) + } + .buttonStyle(PlainNavigationLinkButtonStyle()) + .onAppear { + if item == viewModel.items.last && viewModel.hasNextPage { + viewModel.requestNextPageAsync() + } + } + } + } else if cell.loadingCell { + ProgressView() + .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center) } - } } - } else if cell.loadingCell { - ProgressView() - .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center) - } + } supplementaryView: { _, indexPath in + HStack { + Spacer() + }.accessibilityIdentifier("\(indexPath.section).\(indexPath.row)") } - } supplementaryView: { _, indexPath in - HStack { - Spacer() - }.accessibilityIdentifier("\(indexPath.section).\(indexPath.row)") - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .ignoresSafeArea(.all) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .ignoresSafeArea(.all) } else { - Text("No results.") + Button { } label: { + Text("No results.") + } } } } diff --git a/JellyfinPlayer tvOS/Views/MovieLibrariesView.swift b/JellyfinPlayer tvOS/Views/MovieLibrariesView.swift new file mode 100644 index 00000000..4a78d1cb --- /dev/null +++ b/JellyfinPlayer tvOS/Views/MovieLibrariesView.swift @@ -0,0 +1,82 @@ +/* + * JellyfinPlayer/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 2021 Aiden Vigue & Jellyfin Contributors + */ + +import SwiftUI +import SwiftUICollection +import JellyfinAPI + +struct MovieLibrariesView: View { + @EnvironmentObject var movieLibrariesRouter: MovieLibrariesCoordinator.Router + @StateObject var viewModel: MovieLibrariesViewModel + var title: String + + var body: some View { + if viewModel.isLoading == true { + ProgressView() + } else if !viewModel.rows.isEmpty { + CollectionView(rows: viewModel.rows) { _, _ in + let itemSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1), + heightDimension: .fractionalHeight(1) + ) + let item = NSCollectionLayoutItem(layoutSize: itemSize) + + let groupSize = NSCollectionLayoutSize( + widthDimension: .absolute(200), + heightDimension: .absolute(300) + ) + let group = NSCollectionLayoutGroup.horizontal( + layoutSize: groupSize, + subitems: [item] + ) + + let header = + NSCollectionLayoutBoundarySupplementaryItem( + layoutSize: NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1), + heightDimension: .absolute(44) + ), + elementKind: UICollectionView.elementKindSectionHeader, + alignment: .topLeading + ) + + let section = NSCollectionLayoutSection(group: group) + + section.contentInsets = NSDirectionalEdgeInsets(top: 30, leading: 0, bottom: 80, trailing: 80) + section.interGroupSpacing = 48 + section.orthogonalScrollingBehavior = .continuous + section.boundarySupplementaryItems = [header] + return section + } cell: { _, cell in + GeometryReader { _ in + if let item = cell.item { + if item.type != "Folder" { + Button { + self.movieLibrariesRouter.route(to: \.library, item) + } label: { + PortraitItemElement(item: item) + } + .buttonStyle(PlainNavigationLinkButtonStyle()) + } + } else if cell.loadingCell { + ProgressView() + .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center) + } + } + } supplementaryView: { _, indexPath in + HStack { + Spacer() + }.accessibilityIdentifier("\(indexPath.section).\(indexPath.row)") + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .ignoresSafeArea(.all) + } else { + Text("No results.") + } + } +} diff --git a/JellyfinPlayer tvOS/Views/NextUpView.swift b/JellyfinPlayer tvOS/Views/NextUpView.swift index 1db5d360..8a61a3e5 100644 --- a/JellyfinPlayer tvOS/Views/NextUpView.swift +++ b/JellyfinPlayer tvOS/Views/NextUpView.swift @@ -9,9 +9,12 @@ import SwiftUI import JellyfinAPI import Combine +import Stinsen struct NextUpView: View { var items: [BaseItemDto] + + var homeRouter: HomeCoordinator.Router? = RouterStore.shared.retrieve() var body: some View { VStack(alignment: .leading) { @@ -24,7 +27,9 @@ struct NextUpView: View { LazyHStack { Spacer().frame(width: 45) ForEach(items, id: \.id) { item in - NavigationLink(destination: LazyView { ItemView(item: item) }) { + Button { + self.homeRouter?.route(to: \.modalItem, item) + } label: { LandscapeItemElement(item: item) }.buttonStyle(PlainNavigationLinkButtonStyle()) } diff --git a/JellyfinPlayer tvOS/Views/TVLibrariesView.swift b/JellyfinPlayer tvOS/Views/TVLibrariesView.swift new file mode 100644 index 00000000..725a13d6 --- /dev/null +++ b/JellyfinPlayer tvOS/Views/TVLibrariesView.swift @@ -0,0 +1,80 @@ +/* + * JellyfinPlayer/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 2021 Aiden Vigue & Jellyfin Contributors + */ + +import SwiftUI +import SwiftUICollection +import JellyfinAPI + +struct TVLibrariesView: View { + @EnvironmentObject var tvLibrariesRouter: TVLibrariesCoordinator.Router + @StateObject var viewModel: TVLibrariesViewModel + var title: String + + var body: some View { + if viewModel.isLoading == true { + ProgressView() + } else if !viewModel.rows.isEmpty { + CollectionView(rows: viewModel.rows) { _, _ in + let itemSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1), + heightDimension: .fractionalHeight(1) + ) + let item = NSCollectionLayoutItem(layoutSize: itemSize) + + let groupSize = NSCollectionLayoutSize( + widthDimension: .absolute(200), + heightDimension: .absolute(300) + ) + let group = NSCollectionLayoutGroup.horizontal( + layoutSize: groupSize, + subitems: [item] + ) + + let header = + NSCollectionLayoutBoundarySupplementaryItem( + layoutSize: NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1), + heightDimension: .absolute(44) + ), + elementKind: UICollectionView.elementKindSectionHeader, + alignment: .topLeading + ) + + let section = NSCollectionLayoutSection(group: group) + + section.contentInsets = NSDirectionalEdgeInsets(top: 30, leading: 0, bottom: 80, trailing: 80) + section.interGroupSpacing = 48 + section.orthogonalScrollingBehavior = .continuous + section.boundarySupplementaryItems = [header] + return section + } cell: { _, cell in + GeometryReader { _ in + if let item = cell.item { + if item.type != "Folder" { + Button {} label: { + PortraitItemElement(item: item) + } + .buttonStyle(PlainNavigationLinkButtonStyle()) + } + } else if cell.loadingCell { + ProgressView() + .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center) + } + } + } supplementaryView: { _, indexPath in + HStack { + Spacer() + }.accessibilityIdentifier("\(indexPath.section).\(indexPath.row)") + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .ignoresSafeArea(.all) + } else { + Text("No results.") + } + } +} diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 85605874..63a1911f 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -219,7 +219,19 @@ 62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */; }; 62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62ECA01726FA685A00E8EBB7 /* DeepLink.swift */; }; 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 */; }; + 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 */; }; C4E5081B2703F82A0045C9AB /* LibraryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E508172703E8190045C9AB /* LibraryListView.swift */; }; C4E5081D2703F8370045C9AB /* LibrarySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5081C2703F8370045C9AB /* LibrarySearchView.swift */; }; E100720726BDABC100CE3E31 /* MediaPlayButtonRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */; }; @@ -522,6 +534,12 @@ 62ECA01726FA685A00E8EBB7 /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = ""; }; AE8C3158265D6F90008AA076 /* bitrates.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitrates.json; sourceTree = ""; }; BEEC50E7EFD4848C0E320941 /* Pods-JellyfinPlayer iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer iOS.release.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS.release.xcconfig"; sourceTree = ""; }; + C40CD921271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoviesLibrariesCoordinator.swift; sourceTree = ""; }; + C40CD924271F8D1E000FB198 /* MovieLibrariesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieLibrariesViewModel.swift; sourceTree = ""; }; + C40CD927271F8DAB000FB198 /* MovieLibrariesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieLibrariesView.swift; sourceTree = ""; }; + C4BE0762271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVLibrariesCoordinator.swift; sourceTree = ""; }; + C4BE0765271FC109003F4AD1 /* TVLibrariesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVLibrariesViewModel.swift; sourceTree = ""; }; + C4BE0768271FC164003F4AD1 /* TVLibrariesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVLibrariesView.swift; sourceTree = ""; }; C4E508172703E8190045C9AB /* LibraryListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryListView.swift; sourceTree = ""; }; C4E5081C2703F8370045C9AB /* LibrarySearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchView.swift; sourceTree = ""; }; D79953919FED0C4DF72BA578 /* Pods-JellyfinPlayer tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer tvOS.release.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS.release.xcconfig"; sourceTree = ""; }; @@ -677,6 +695,8 @@ 62E632DB267D2E130063E547 /* LibrarySearchViewModel.swift */, 62E632DF267D30CA0063E547 /* LibraryViewModel.swift */, 536D3D75267BA9BB0004248C /* MainTabViewModel.swift */, + C40CD924271F8D1E000FB198 /* MovieLibrariesViewModel.swift */, + C4BE0765271FC109003F4AD1 /* TVLibrariesViewModel.swift */, 62E632E2267D3BA60063E547 /* MovieItemViewModel.swift */, 62E632E8267D3FF50063E547 /* SeasonItemViewModel.swift */, 62E632EB267D410B0063E547 /* SeriesItemViewModel.swift */, @@ -1050,6 +1070,8 @@ 6220D0BF26D61C5000B8E046 /* ItemCoordinator.swift */, 6220D0B326D5ED8000B8E046 /* LibraryCoordinator.swift */, 62C29EA726D103D500C1D2E7 /* LibraryListCoordinator.swift */, + C40CD921271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift */, + C4BE0762271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift */, 6220D0B626D5EE1100B8E046 /* SearchCoordinator.swift */, E13DD3E827177ED6009D4DAF /* ServerListCoordinator.swift */, 6220D0B026D5EC9900B8E046 /* SettingsCoordinator.swift */, @@ -1120,6 +1142,8 @@ C4E508172703E8190045C9AB /* LibraryListView.swift */, C4E5081C2703F8370045C9AB /* LibrarySearchView.swift */, 53A83C32268A309300DF3D92 /* LibraryView.swift */, + C40CD927271F8DAB000FB198 /* MovieLibrariesView.swift */, + C4BE0768271FC164003F4AD1 /* TVLibrariesView.swift */, 531690EE267ABF72005D8AB9 /* NextUpView.swift */, E193D54F2719430400900D82 /* ServerDetailView.swift */, E193D54A271941D300900D82 /* ServerListView.swift */, @@ -1632,6 +1656,7 @@ E193D4DC27193CCA00900D82 /* PillStackable.swift in Sources */, E193D53327193F7D00900D82 /* FilterCoordinator.swift in Sources */, 6267B3DC2671139500A7371D /* ImageExtensions.swift in Sources */, + C40CD929271F8DAB000FB198 /* MovieLibrariesView.swift in Sources */, 531069592684E7EE00CFFDBA /* SubtitlesView.swift in Sources */, C4E5081D2703F8370045C9AB /* LibrarySearchView.swift in Sources */, 53ABFDE9267974EF00886593 /* HomeViewModel.swift in Sources */, @@ -1640,6 +1665,7 @@ 531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */, E1D4BF8B2719D3D000A11E64 /* BasicAppSettingsCoordinator.swift in Sources */, E13DD3FA2717E961009D4DAF /* UserListViewModel.swift in Sources */, + C40CD926271F8D1E000FB198 /* MovieLibrariesViewModel.swift in Sources */, 62E632DE267D2E170063E547 /* LatestMediaViewModel.swift in Sources */, E1FCD09726C47118007C8DCF /* ErrorMessage.swift in Sources */, E193D53527193F8100900D82 /* ItemCoordinator.swift in Sources */, @@ -1672,6 +1698,7 @@ 62E632F4267D54030063E547 /* ItemViewModel.swift in Sources */, 6267B3D826710B9800A7371D /* CollectionExtensions.swift in Sources */, 62E632E7267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */, + C4BE0767271FC109003F4AD1 /* TVLibrariesViewModel.swift in Sources */, E193D53727193F8700900D82 /* LibraryListCoordinator.swift in Sources */, E100720726BDABC100CE3E31 /* MediaPlayButtonRowView.swift in Sources */, E193D54D2719426600900D82 /* LibraryFilterView.swift in Sources */, @@ -1706,6 +1733,7 @@ C45B29BB26FAC5B600CEF5E0 /* ColorExtension.swift in Sources */, 531069582684E7EE00CFFDBA /* MediaInfoView.swift in Sources */, E1D4BF822719D22800A11E64 /* AppAppearance.swift in Sources */, + C40CD923271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift in Sources */, 53272537268C1DBB0035FBF1 /* SeasonItemView.swift in Sources */, 09389CC526814E4500AE350E /* DeviceProfileBuilder.swift in Sources */, E193D53C27193F9500900D82 /* UserListCoordinator.swift in Sources */, @@ -1731,7 +1759,9 @@ 5364F456266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */, 5364F456266CA0DC0026ECBA /* BaseItemPersonExtensions.swift in Sources */, 531690FA267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift in Sources */, + C4BE0764271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift in Sources */, E131691826C583BC0074BFEE /* LogConstructor.swift in Sources */, + C4BE076A271FC164003F4AD1 /* TVLibrariesView.swift in Sources */, E13DD3C327164941009D4DAF /* SwiftfinStore.swift in Sources */, 09389CC826819B4600AE350E /* VideoPlayerModel.swift in Sources */, E193D553271943D500900D82 /* tvOSMainTabCoordinator.swift in Sources */, @@ -1761,6 +1791,7 @@ E1AD105626D981CE003E4A08 /* PortraitHStackView.swift in Sources */, 62C29EA126D102A500C1D2E7 /* iOSMainTabCoordinator.swift in Sources */, 535BAE9F2649E569005FA86D /* ItemView.swift in Sources */, + C40CD925271F8D1E000FB198 /* MovieLibrariesViewModel.swift in Sources */, 6225FCCB2663841E00E067F6 /* ParallaxHeader.swift in Sources */, 6220D0AD26D5EABB00B8E046 /* ViewExtensions.swift in Sources */, E13DD3EC27178A54009D4DAF /* UserSignInViewModel.swift in Sources */, @@ -1781,6 +1812,7 @@ 625CB56F2678C23300530A6E /* HomeView.swift in Sources */, E173DA5226D04AAF00CC4EB7 /* ColorExtension.swift in Sources */, 53892770263C25230035E14B /* NextUpView.swift in Sources */, + C4BE0766271FC109003F4AD1 /* TVLibrariesViewModel.swift in Sources */, 62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */, 535BAEA5264A151C005FA86D /* VideoPlayer.swift in Sources */, 62E632E6267D3F5B0063E547 /* EpisodeItemViewModel.swift in Sources */, @@ -1789,6 +1821,7 @@ 532E68CF267D9F6B007B9F13 /* VideoPlayerCastDeviceSelector.swift in Sources */, E14F7D0926DB36F7007C3AE6 /* ItemLandscapeMainView.swift in Sources */, 532175402671EE4F005491E6 /* LibraryFilterView.swift in Sources */, + C4BE0763271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift in Sources */, 53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */, E188460026DECB9E00B0C5B7 /* ItemLandscapeTopBarView.swift in Sources */, 091B5A8B2683142E00D78B61 /* UDPBroadCastConnection.swift in Sources */, @@ -1828,12 +1861,14 @@ 62E632E3267D3BA60063E547 /* MovieItemViewModel.swift in Sources */, 091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */, 62E632EF267D43320063E547 /* LibraryFilterViewModel.swift in Sources */, + C40CD922271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift in Sources */, E13DD3C827164B1E009D4DAF /* UIDeviceExtensions.swift in Sources */, E1AD104D26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */, E13DD3BF27163DD7009D4DAF /* AppDelegate.swift in Sources */, 535870AD2669D8DD00D05A09 /* Typings.swift in Sources */, E1AD105F26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift in Sources */, E13DD3D5271693CD009D4DAF /* SwiftfinStoreDefaults.swift in Sources */, + C4BE0769271FC164003F4AD1 /* TVLibrariesView.swift in Sources */, E1267D3E271A1F46003C492E /* PreferenceUIHostingController.swift in Sources */, 6220D0BA26D6092100B8E046 /* FilterCoordinator.swift in Sources */, 6267B3DA2671138200A7371D /* ImageExtensions.swift in Sources */, @@ -1848,6 +1883,7 @@ 6220D0B726D5EE1100B8E046 /* SearchCoordinator.swift in Sources */, E13DD3EF27178F87009D4DAF /* SwiftfinNotificationCenter.swift in Sources */, 5377CBF5263B596A003A4E83 /* JellyfinPlayerApp.swift in Sources */, + C40CD928271F8DAB000FB198 /* MovieLibrariesView.swift in Sources */, E13DD4022717EE79009D4DAF /* UserListCoordinator.swift in Sources */, E1FCD09626C47118007C8DCF /* ErrorMessage.swift in Sources */, 53EE24E6265060780068F029 /* LibrarySearchView.swift in Sources */, diff --git a/Shared/Coordinators/HomeCoordinator.swift b/Shared/Coordinators/HomeCoordinator.swift index 31837271..e30c79af 100644 --- a/Shared/Coordinators/HomeCoordinator.swift +++ b/Shared/Coordinators/HomeCoordinator.swift @@ -20,6 +20,8 @@ final class HomeCoordinator: NavigationCoordinatable { @Route(.modal) var settings = makeSettings @Route(.push) var library = makeLibrary @Route(.push) var item = makeItem + @Route(.modal) var modalItem = makeModalItem + @Route(.modal) var modalLibrary = makeModalLibrary func makeSettings() -> NavigationViewCoordinator { NavigationViewCoordinator(SettingsCoordinator()) @@ -32,6 +34,14 @@ final class HomeCoordinator: NavigationCoordinatable { func makeItem(item: BaseItemDto) -> ItemCoordinator { ItemCoordinator(item: item) } + + func makeModalItem(item: BaseItemDto) -> NavigationViewCoordinator { + return NavigationViewCoordinator(ItemCoordinator(item: item)) + } + + func makeModalLibrary(params: LibraryCoordinatorParams) -> NavigationViewCoordinator { + return NavigationViewCoordinator(LibraryCoordinator(viewModel: params.viewModel, title: params.title)) + } @ViewBuilder func makeStart() -> some View { HomeView() diff --git a/Shared/Coordinators/LibraryCoordinator.swift b/Shared/Coordinators/LibraryCoordinator.swift index c90234c5..a5ef3495 100644 --- a/Shared/Coordinators/LibraryCoordinator.swift +++ b/Shared/Coordinators/LibraryCoordinator.swift @@ -22,6 +22,7 @@ final class LibraryCoordinator: NavigationCoordinatable { @Route(.push) var search = makeSearch @Route(.modal) var filter = makeFilter @Route(.push) var item = makeItem + @Route(.modal) var modalItem = makeModalItem let viewModel: LibraryViewModel let title: String @@ -48,4 +49,8 @@ final class LibraryCoordinator: NavigationCoordinatable { func makeItem(item: BaseItemDto) -> ItemCoordinator { ItemCoordinator(item: item) } + + func makeModalItem(item: BaseItemDto) -> NavigationViewCoordinator { + return NavigationViewCoordinator(ItemCoordinator(item: item)) + } } diff --git a/Shared/Coordinators/MainCoordinator/tvOSMainTabCoordinator.swift b/Shared/Coordinators/MainCoordinator/tvOSMainTabCoordinator.swift index 82edaedf..8be5a5c6 100644 --- a/Shared/Coordinators/MainCoordinator/tvOSMainTabCoordinator.swift +++ b/Shared/Coordinators/MainCoordinator/tvOSMainTabCoordinator.swift @@ -14,12 +14,16 @@ import Stinsen final class MainTabCoordinator: TabCoordinatable { var child = TabChild(startingItems: [ \MainTabCoordinator.home, - \MainTabCoordinator.allMedia, + \MainTabCoordinator.tv, + \MainTabCoordinator.movies, + \MainTabCoordinator.other, \MainTabCoordinator.settings ]) @Route(tabItem: makeHomeTab) var home = makeHome - @Route(tabItem: makeAllMediaTab) var allMedia = makeAllMedia + @Route(tabItem: makeTvTab) var tv = makeTv + @Route(tabItem: makeMoviesTab) var movies = makeMovies + @Route(tabItem: makeOtherTab) var other = makeOther @Route(tabItem: makeSettingsTab) var settings = makeSettings func makeHome() -> NavigationViewCoordinator { @@ -32,15 +36,37 @@ final class MainTabCoordinator: TabCoordinatable { Text("Home") } } + + func makeTv() -> NavigationViewCoordinator { + return NavigationViewCoordinator(TVLibrariesCoordinator(viewModel: TVLibrariesViewModel(), title: "TV Shows")) + } - func makeAllMedia() -> NavigationViewCoordinator { + @ViewBuilder func makeTvTab(isActive: Bool) -> some View { + HStack { + Image(systemName: "tv") + Text("TV Shows") + } + } + + func makeMovies() -> NavigationViewCoordinator { + return NavigationViewCoordinator(MovieLibrariesCoordinator(viewModel: MovieLibrariesViewModel(), title: "Movies")) + } + + @ViewBuilder func makeMoviesTab(isActive: Bool) -> some View { + HStack { + Image(systemName: "film") + Text("Movies") + } + } + + func makeOther() -> NavigationViewCoordinator { return NavigationViewCoordinator(LibraryListCoordinator()) } - @ViewBuilder func makeAllMediaTab(isActive: Bool) -> some View { + @ViewBuilder func makeOtherTab(isActive: Bool) -> some View { HStack { Image(systemName: "folder") - Text("All Media") + Text("Other") } } diff --git a/Shared/Coordinators/MoviesLibrariesCoordinator.swift b/Shared/Coordinators/MoviesLibrariesCoordinator.swift new file mode 100644 index 00000000..9c530d3c --- /dev/null +++ b/Shared/Coordinators/MoviesLibrariesCoordinator.swift @@ -0,0 +1,37 @@ +// +/* + * SwiftFin is subject to the terms of the Mozilla Public + * License, v2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright 2021 Aiden Vigue & Jellyfin Contributors + */ + +import Foundation +import JellyfinAPI +import Stinsen +import SwiftUI + +final class MovieLibrariesCoordinator: NavigationCoordinatable { + + let stack = NavigationStack(initial: \MovieLibrariesCoordinator.start) + + @Root var start = makeStart + @Route(.push) var library = makeLibrary + + let viewModel: MovieLibrariesViewModel + let title: String + + init(viewModel: MovieLibrariesViewModel, title: String) { + self.viewModel = viewModel + self.title = title + } + + @ViewBuilder func makeStart() -> some View { + MovieLibrariesView(viewModel: self.viewModel, title: title) + } + + func makeLibrary(library: BaseItemDto) -> LibraryCoordinator { + LibraryCoordinator(viewModel: LibraryViewModel(parentID: library.id), title: library.title) + } +} diff --git a/Shared/Coordinators/TVLibrariesCoordinator.swift b/Shared/Coordinators/TVLibrariesCoordinator.swift new file mode 100644 index 00000000..2ad50744 --- /dev/null +++ b/Shared/Coordinators/TVLibrariesCoordinator.swift @@ -0,0 +1,37 @@ +// +/* + * SwiftFin is subject to the terms of the Mozilla Public + * License, v2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright 2021 Aiden Vigue & Jellyfin Contributors + */ + +import Foundation +import JellyfinAPI +import Stinsen +import SwiftUI + +final class TVLibrariesCoordinator: NavigationCoordinatable { + + let stack = NavigationStack(initial: \TVLibrariesCoordinator.start) + + @Root var start = makeStart + @Route(.push) var library = makeLibrary + + let viewModel: TVLibrariesViewModel + let title: String + + init(viewModel: TVLibrariesViewModel, title: String) { + self.viewModel = viewModel + self.title = title + } + + @ViewBuilder func makeStart() -> some View { + TVLibrariesView(viewModel: self.viewModel, title: title) + } + + func makeLibrary(library: BaseItemDto) -> LibraryCoordinator { + LibraryCoordinator(viewModel: LibraryViewModel(parentID: library.id), title: library.title) + } +} diff --git a/Shared/ViewModels/LibraryViewModel.swift b/Shared/ViewModels/LibraryViewModel.swift index 1df7ac24..f0839708 100644 --- a/Shared/ViewModels/LibraryViewModel.swift +++ b/Shared/ViewModels/LibraryViewModel.swift @@ -15,9 +15,9 @@ import SwiftUICollection typealias LibraryRow = CollectionRow struct LibraryRowCell: Hashable { - let id = UUID() - let item: BaseItemDto? - var loadingCell: Bool = false + let id = UUID() + let item: BaseItemDto? + var loadingCell: Bool = false } final class LibraryViewModel: ViewModel { @@ -38,6 +38,7 @@ final class LibraryViewModel: ViewModel { @Published var filters: LibraryFilters private let columns: Int + private var libraries = [BaseItemDto]() var enabledFilterType: [FilterType] { if genre == nil { @@ -48,12 +49,12 @@ final class LibraryViewModel: ViewModel { } 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 + 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.parentID = parentID self.person = person @@ -63,9 +64,11 @@ final class LibraryViewModel: ViewModel { self.columns = columns super.init() + $filters .sink(receiveValue: requestItems(with:)) .store(in: &cancellables) + } func requestItems(with filters: LibraryFilters) { @@ -95,7 +98,7 @@ final class LibraryViewModel: ViewModel { self.hasPreviousPage = self.currentPage > 0 self.hasNextPage = self.currentPage < self.totalPages - 1 self.items = response.items ?? [] - self.rows = self.calculateRows() + self.rows = self.calculateRows(for: self.items) }) .store(in: &cancellables) } @@ -125,7 +128,7 @@ final class LibraryViewModel: ViewModel { self.hasPreviousPage = self.currentPage > 0 self.hasNextPage = self.currentPage < self.totalPages - 1 self.items.append(contentsOf: response.items ?? []) - self.rows = self.calculateRows() + self.rows = self.calculateRows(for: self.items) }) .store(in: &cancellables) } @@ -145,37 +148,35 @@ final class LibraryViewModel: ViewModel { requestItems(with: filters) } - private func calculateRows() -> [LibraryRow] { - guard items.count > 0 else { return [] } - let rowCount = items.count / columns - var calculatedRows = [LibraryRow]() - for i in (0...rowCount) { - - let firstItemIndex = i * columns - var lastItemIndex = firstItemIndex + columns - if lastItemIndex > items.count { - lastItemIndex = items.count - } - - var rowCells = [LibraryRowCell]() - for item in items[firstItemIndex.. [LibraryRow] { + guard itemList.count > 0 else { return [] } + let rowCount = itemList.count / columns + var calculatedRows = [LibraryRow]() + for i in (0...rowCount) { + let firstItemIndex = i * columns + var lastItemIndex = firstItemIndex + columns + if lastItemIndex > itemList.count { + lastItemIndex = itemList.count + } + + var rowCells = [LibraryRowCell]() + for item in itemList[firstItemIndex.. [LibraryRow] { + guard libraries.count > 0 else { return [] } + let rowCount = libraries.count / columns + var calculatedRows = [LibraryRow]() + for i in (0...rowCount) { + let firstItemIndex = i * columns + var lastItemIndex = firstItemIndex + columns + if lastItemIndex > libraries.count { + lastItemIndex = libraries.count + } + + var rowCells = [LibraryRowCell]() + for item in libraries[firstItemIndex.. [LibraryRow] { + guard libraries.count > 0 else { return [] } + let rowCount = libraries.count / columns + var calculatedRows = [LibraryRow]() + for i in (0...rowCount) { + let firstItemIndex = i * columns + var lastItemIndex = firstItemIndex + columns + if lastItemIndex > libraries.count { + lastItemIndex = libraries.count + } + + var rowCells = [LibraryRowCell]() + for item in libraries[firstItemIndex.. Date: Wed, 20 Oct 2021 20:54:45 -0500 Subject: [PATCH 2/5] fix iOS build with dud view --- JellyfinPlayer.xcodeproj/project.pbxproj | 12 ++++++++--- .../Components/PortraitItemElement.swift | 20 +++++++++++++++++++ .../Views}/PlainNavigationLinkButton.swift | 0 3 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 JellyfinPlayer/Components/PortraitItemElement.swift rename {JellyfinPlayer tvOS/Components => Shared/Views}/PlainNavigationLinkButton.swift (100%) diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 63a1911f..814c1754 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -232,6 +232,8 @@ 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 */; }; C4E5081B2703F82A0045C9AB /* LibraryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E508172703E8190045C9AB /* LibraryListView.swift */; }; C4E5081D2703F8370045C9AB /* LibrarySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5081C2703F8370045C9AB /* LibrarySearchView.swift */; }; E100720726BDABC100CE3E31 /* MediaPlayButtonRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */; }; @@ -540,6 +542,7 @@ C4BE0762271FC0BB003F4AD1 /* TVLibrariesCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVLibrariesCoordinator.swift; sourceTree = ""; }; C4BE0765271FC109003F4AD1 /* TVLibrariesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVLibrariesViewModel.swift; sourceTree = ""; }; C4BE0768271FC164003F4AD1 /* TVLibrariesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVLibrariesView.swift; sourceTree = ""; }; + C4BE076D2720FEA8003F4AD1 /* PortraitItemElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortraitItemElement.swift; sourceTree = ""; }; C4E508172703E8190045C9AB /* LibraryListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryListView.swift; sourceTree = ""; }; C4E5081C2703F8370045C9AB /* LibrarySearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchView.swift; sourceTree = ""; }; D79953919FED0C4DF72BA578 /* Pods-JellyfinPlayer tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer tvOS.release.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS.release.xcconfig"; sourceTree = ""; }; @@ -820,7 +823,6 @@ E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */, 53272531268BF09D0035FBF1 /* MediaViewActionButton.swift */, 53116A18268B947A003024C9 /* PlainLinkButton.swift */, - 531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */, 536D3D80267BDFC60004248C /* PortraitItemElement.swift */, 536D3D87267C17350004248C /* PublicUserButton.swift */, ); @@ -1029,6 +1031,7 @@ E1AD105526D981CE003E4A08 /* PortraitHStackView.swift */, 53F866432687A45F00DCD1D7 /* PortraitItemView.swift */, E188460326DEF04800B0C5B7 /* EpisodeCardVStackView.swift */, + C4BE076D2720FEA8003F4AD1 /* PortraitItemElement.swift */, ); path = Components; sourceTree = ""; @@ -1279,6 +1282,7 @@ E1AD105326D96F5A003E4A08 /* Views */ = { isa = PBXGroup; children = ( + 531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */, 531AC8BE26750DE20091C7EB /* ImageView.swift */, 621338B22660A07800A81A2A /* LazyView.swift */, 53E4E648263F725B00F67C6B /* MultiSelectorView.swift */, @@ -1790,6 +1794,7 @@ 53F866442687A45F00DCD1D7 /* PortraitItemView.swift in Sources */, E1AD105626D981CE003E4A08 /* PortraitHStackView.swift in Sources */, 62C29EA126D102A500C1D2E7 /* iOSMainTabCoordinator.swift in Sources */, + C4BE076E2720FEA8003F4AD1 /* PortraitItemElement.swift in Sources */, 535BAE9F2649E569005FA86D /* ItemView.swift in Sources */, C40CD925271F8D1E000FB198 /* MovieLibrariesViewModel.swift in Sources */, 6225FCCB2663841E00E067F6 /* ParallaxHeader.swift in Sources */, @@ -1859,6 +1864,7 @@ E173DA5026D048D600CC4EB7 /* ServerDetailView.swift in Sources */, 62EC352F267666A5000E9F2D /* SessionManager.swift in Sources */, 62E632E3267D3BA60063E547 /* MovieItemViewModel.swift in Sources */, + C4BE076F2720FEFF003F4AD1 /* PlainNavigationLinkButton.swift in Sources */, 091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */, 62E632EF267D43320063E547 /* LibraryFilterViewModel.swift in Sources */, C40CD922271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift in Sources */, @@ -2072,7 +2078,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = JM7WWM3V8C; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "JellyfinPlayer tvOS/Info.plist"; @@ -2102,7 +2108,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = JM7WWM3V8C; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "JellyfinPlayer tvOS/Info.plist"; diff --git a/JellyfinPlayer/Components/PortraitItemElement.swift b/JellyfinPlayer/Components/PortraitItemElement.swift new file mode 100644 index 00000000..9a63b6f1 --- /dev/null +++ b/JellyfinPlayer/Components/PortraitItemElement.swift @@ -0,0 +1,20 @@ +// + /* + * 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 2021 Aiden Vigue & Jellyfin Contributors + */ + +import SwiftUI +import JellyfinAPI + +// Not implemented on iOS, but used by a shared Coordinator. +struct PortraitItemElement: View { + var item: BaseItemDto + + var body: some View { + EmptyView() + } +} diff --git a/JellyfinPlayer tvOS/Components/PlainNavigationLinkButton.swift b/Shared/Views/PlainNavigationLinkButton.swift similarity index 100% rename from JellyfinPlayer tvOS/Components/PlainNavigationLinkButton.swift rename to Shared/Views/PlainNavigationLinkButton.swift From 64c0df33ec6440075551d57cca5b7f5815b56367 Mon Sep 17 00:00:00 2001 From: jhays Date: Wed, 20 Oct 2021 21:10:40 -0500 Subject: [PATCH 3/5] remove dev team --- JellyfinPlayer.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 814c1754..6a3bb58f 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -2078,7 +2078,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\""; - DEVELOPMENT_TEAM = JM7WWM3V8C; + DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "JellyfinPlayer tvOS/Info.plist"; @@ -2108,7 +2108,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\""; - DEVELOPMENT_TEAM = JM7WWM3V8C; + DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "JellyfinPlayer tvOS/Info.plist"; From 992361a1cfb9aa1ea191692699a74ee7a114d27b Mon Sep 17 00:00:00 2001 From: jhays Date: Thu, 21 Oct 2021 21:18:18 -0500 Subject: [PATCH 4/5] bugfixes, add year and rating to items --- .../Components/PortraitItemElement.swift | 16 ++++++++++++++++ .../Views/ContinueWatchingView.swift | 2 +- JellyfinPlayer tvOS/Views/LatestMediaView.swift | 2 +- JellyfinPlayer tvOS/Views/LibraryView.swift | 5 ++++- .../Views/MovieLibrariesView.swift | 9 ++++++++- JellyfinPlayer tvOS/Views/NextUpView.swift | 2 +- JellyfinPlayer tvOS/Views/TVLibrariesView.swift | 13 +++++++++++-- Shared/ViewModels/MovieLibrariesViewModel.swift | 6 +----- Shared/ViewModels/TVLibrariesViewModel.swift | 6 +----- 9 files changed, 44 insertions(+), 17 deletions(-) diff --git a/JellyfinPlayer tvOS/Components/PortraitItemElement.swift b/JellyfinPlayer tvOS/Components/PortraitItemElement.swift index 37c611ab..e2290ee3 100644 --- a/JellyfinPlayer tvOS/Components/PortraitItemElement.swift +++ b/JellyfinPlayer tvOS/Components/PortraitItemElement.swift @@ -57,6 +57,22 @@ struct PortraitItemElement: View { .opacity(1), alignment: .topTrailing).opacity(1) Text(item.title) .frame(width: 200, height: 30, alignment: .center) + if item.type == "Movie" || item.type == "Series" { + Text("\(String(item.productionYear ?? 0)) • \(item.officialRating ?? "N/A")") + .foregroundColor(.secondary) + .font(.caption) + .fontWeight(.medium) + } else if item.type == "Season" { + Text("\(item.name ?? "") • \(String(item.productionYear ?? 0))") + .foregroundColor(.secondary) + .font(.caption) + .fontWeight(.medium) + } else { + Text("S\(String(item.parentIndexNumber ?? 0)):E\(String(item.indexNumber ?? 0))") + .foregroundColor(.secondary) + .font(.caption) + .fontWeight(.medium) + } } .onChange(of: envFocused) { envFocus in withAnimation(.linear(duration: 0.15)) { diff --git a/JellyfinPlayer tvOS/Views/ContinueWatchingView.swift b/JellyfinPlayer tvOS/Views/ContinueWatchingView.swift index fd59664a..0ca800bd 100644 --- a/JellyfinPlayer tvOS/Views/ContinueWatchingView.swift +++ b/JellyfinPlayer tvOS/Views/ContinueWatchingView.swift @@ -37,7 +37,7 @@ struct ContinueWatchingView: View { } Spacer().frame(width: 45) } - }.frame(height: 330) + }.frame(height: 350) } else { EmptyView() } diff --git a/JellyfinPlayer tvOS/Views/LatestMediaView.swift b/JellyfinPlayer tvOS/Views/LatestMediaView.swift index 4034892a..92be14d8 100644 --- a/JellyfinPlayer tvOS/Views/LatestMediaView.swift +++ b/JellyfinPlayer tvOS/Views/LatestMediaView.swift @@ -48,7 +48,7 @@ struct LatestMediaView: View { } Spacer().frame(width: 45) } - }.frame(height: 396) + }.frame(height: 480) .onAppear(perform: onAppear) } } diff --git a/JellyfinPlayer tvOS/Views/LibraryView.swift b/JellyfinPlayer tvOS/Views/LibraryView.swift index d4f05aeb..ca272c9e 100644 --- a/JellyfinPlayer tvOS/Views/LibraryView.swift +++ b/JellyfinPlayer tvOS/Views/LibraryView.swift @@ -87,8 +87,11 @@ struct LibraryView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .ignoresSafeArea(.all) } else { - Button { } label: { + VStack { Text("No results.") + Button { } label: { + Text("Reload") + } } } } diff --git a/JellyfinPlayer tvOS/Views/MovieLibrariesView.swift b/JellyfinPlayer tvOS/Views/MovieLibrariesView.swift index 4a78d1cb..9e388718 100644 --- a/JellyfinPlayer tvOS/Views/MovieLibrariesView.swift +++ b/JellyfinPlayer tvOS/Views/MovieLibrariesView.swift @@ -76,7 +76,14 @@ struct MovieLibrariesView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .ignoresSafeArea(.all) } else { - Text("No results.") + VStack { + Text("No results.") + Button { + print("movieLibraries reload") + } label: { + Text("Reload") + } + } } } } diff --git a/JellyfinPlayer tvOS/Views/NextUpView.swift b/JellyfinPlayer tvOS/Views/NextUpView.swift index 8a61a3e5..3de41d6b 100644 --- a/JellyfinPlayer tvOS/Views/NextUpView.swift +++ b/JellyfinPlayer tvOS/Views/NextUpView.swift @@ -35,7 +35,7 @@ struct NextUpView: View { } Spacer().frame(width: 45) } - }.frame(height: 330) + }.frame(height: 350) .offset(y: -10) } else { EmptyView() diff --git a/JellyfinPlayer tvOS/Views/TVLibrariesView.swift b/JellyfinPlayer tvOS/Views/TVLibrariesView.swift index 725a13d6..3ae4d8df 100644 --- a/JellyfinPlayer tvOS/Views/TVLibrariesView.swift +++ b/JellyfinPlayer tvOS/Views/TVLibrariesView.swift @@ -56,7 +56,9 @@ struct TVLibrariesView: View { GeometryReader { _ in if let item = cell.item { if item.type != "Folder" { - Button {} label: { + Button { + self.tvLibrariesRouter.route(to: \.library, item) + } label: { PortraitItemElement(item: item) } .buttonStyle(PlainNavigationLinkButtonStyle()) @@ -74,7 +76,14 @@ struct TVLibrariesView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .ignoresSafeArea(.all) } else { - Text("No results.") + VStack { + Text("No results.") + Button { + print("tvLibraries reload") + } label: { + Text("Reload") + } + } } } } diff --git a/Shared/ViewModels/MovieLibrariesViewModel.swift b/Shared/ViewModels/MovieLibrariesViewModel.swift index a7c1a678..17bd4567 100644 --- a/Shared/ViewModels/MovieLibrariesViewModel.swift +++ b/Shared/ViewModels/MovieLibrariesViewModel.swift @@ -51,12 +51,10 @@ final class MovieLibrariesViewModel: ViewModel { self.libraries.append(library) } } + self.rows = self.calculateRows() if self.libraries.count == 1, let library = self.libraries.first { // show library self.router?.route(to: \.library, library) - } else { - // display list of libraries - self.rows = self.calculateRows() } } }) @@ -76,7 +74,6 @@ final class MovieLibrariesViewModel: ViewModel { var rowCells = [LibraryRowCell]() for item in libraries[firstItemIndex.. Date: Fri, 22 Oct 2021 17:12:54 -0500 Subject: [PATCH 5/5] omit music from libraries list --- JellyfinPlayer tvOS/Views/LibraryListView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JellyfinPlayer tvOS/Views/LibraryListView.swift b/JellyfinPlayer tvOS/Views/LibraryListView.swift index 051dcbbf..d7ecd279 100644 --- a/JellyfinPlayer tvOS/Views/LibraryListView.swift +++ b/JellyfinPlayer tvOS/Views/LibraryListView.swift @@ -18,7 +18,7 @@ struct LibraryListView: View { LazyVStack { if !viewModel.isLoading { ForEach(viewModel.libraries, id: \.id) { library in - if library.collectionType ?? "" == "movies" || library.collectionType ?? "" == "tvshows" { + if library.collectionType ?? "" == "movies" || library.collectionType ?? "" == "tvshows" || library.collectionType ?? "" == "music" { EmptyView() } else { NavigationLink(destination: LazyView {