diff --git a/JellyfinPlayer tvOS/Components/LandscapeItemElement.swift b/JellyfinPlayer tvOS/Components/LandscapeItemElement.swift index 23b07c1d..f4932fb6 100644 --- a/JellyfinPlayer tvOS/Components/LandscapeItemElement.swift +++ b/JellyfinPlayer tvOS/Components/LandscapeItemElement.swift @@ -70,7 +70,7 @@ struct LandscapeItemElement: View { .frame(width: 445, height: 90) .mask(CutOffShadow()) VStack(alignment: .leading) { - Text("CONTINUE • \(item.getItemProgressString())") + Text("CONTINUE • \(item.getItemProgressString() ?? "")") .font(.caption) .fontWeight(.medium) .offset(y: 5) diff --git a/JellyfinPlayer tvOS/Views/ItemView/CinematicItemView/CinematicEpisodeItemView.swift b/JellyfinPlayer tvOS/Views/ItemView/CinematicItemView/CinematicEpisodeItemView.swift index 014dc7ee..97aa4baa 100644 --- a/JellyfinPlayer tvOS/Views/ItemView/CinematicItemView/CinematicEpisodeItemView.swift +++ b/JellyfinPlayer tvOS/Views/ItemView/CinematicItemView/CinematicEpisodeItemView.swift @@ -32,12 +32,13 @@ struct CinematicEpisodeItemView: View { ZStack(alignment: .topLeading) { Color.black.ignoresSafeArea() + .frame(minHeight: UIScreen.main.bounds.height) VStack(alignment: .leading, spacing: 20) { CinematicItemAboutView(viewModel: viewModel) - EpisodesRowView(viewModel: viewModel) + EpisodesRowView(viewModel: EpisodesRowViewModel(episodeItemViewModel: viewModel)) .focusSection() if !viewModel.similarItems.isEmpty { diff --git a/JellyfinPlayer tvOS/Views/ItemView/EpisodesRowView.swift b/JellyfinPlayer tvOS/Views/ItemView/EpisodesRowView.swift index 1f1faf17..735216a0 100644 --- a/JellyfinPlayer tvOS/Views/ItemView/EpisodesRowView.swift +++ b/JellyfinPlayer tvOS/Views/ItemView/EpisodesRowView.swift @@ -13,58 +13,118 @@ import SwiftUI struct EpisodesRowView: View { @EnvironmentObject var itemRouter: ItemCoordinator.Router - @ObservedObject var viewModel: EpisodeItemViewModel + @ObservedObject var viewModel: EpisodesRowViewModel var body: some View { VStack(alignment: .leading) { - Text("Episodes") + Text(viewModel.selectedSeason?.name ?? "Episodes") .font(.title3) .padding(.horizontal, 50) ScrollView(.horizontal) { ScrollViewReader { reader in HStack(alignment: .top) { - ForEach(viewModel.seasonEpisodes, id:\.self) { episode in - Button { - itemRouter.route(to: \.item, episode) - } label: { - HStack(alignment: .top) { + if viewModel.isLoading { + VStack(alignment: .leading) { + + ZStack { + Color.secondary.ignoresSafeArea() + + ProgressView() + } + .mask(Rectangle().frame(width: 500, height: 280)) + .frame(width: 500, height: 280) + + VStack(alignment: .leading) { + Text("S-E-") + .font(.caption) + .foregroundColor(.secondary) + Text("--") + .font(.footnote) + .padding(.bottom, 1) + Text("--") + .font(.caption) + .fontWeight(.light) + .lineLimit(4) + } + .padding(.horizontal) + + Spacer() + } + .frame(width: 500) + .focusable() + } else if let selectedSeason = viewModel.selectedSeason { + if viewModel.seasonsEpisodes[selectedSeason]!.isEmpty { + VStack(alignment: .leading) { + + Color.secondary + .mask(Rectangle().frame(width: 500, height: 280)) + .frame(width: 500, height: 280) + VStack(alignment: .leading) { - - ImageView(src: episode.getBackdropImage(maxWidth: 445), - bh: episode.getBackdropImageBlurHash()) - .mask(Rectangle().frame(width: 500, height: 280)) - .frame(width: 500, height: 280) - - VStack(alignment: .leading) { - Text(episode.getEpisodeLocator() ?? "") - .font(.caption) - .foregroundColor(.secondary) - Text(episode.name ?? "") - .font(.footnote) - .padding(.bottom, 1) - Text(episode.overview ?? "") - .font(.caption) - .fontWeight(.light) - .lineLimit(4) - } - .padding(.horizontal) - - Spacer() + Text("--") + .font(.caption) + .foregroundColor(.secondary) + Text("No episodes available") + .font(.footnote) + .padding(.bottom, 1) } - .frame(width: 500) + .padding(.horizontal) + + Spacer() + } + .frame(width: 500) + .focusable() + } else { + ForEach(viewModel.seasonsEpisodes[selectedSeason]!, id:\.self) { episode in + Button { + itemRouter.route(to: \.item, episode) + } label: { + HStack(alignment: .top) { + VStack(alignment: .leading) { + + ImageView(src: episode.getBackdropImage(maxWidth: 445), + bh: episode.getBackdropImageBlurHash()) + .mask(Rectangle().frame(width: 500, height: 280)) + .frame(width: 500, height: 280) + + VStack(alignment: .leading) { + Text(episode.getEpisodeLocator() ?? "") + .font(.caption) + .foregroundColor(.secondary) + Text(episode.name ?? "") + .font(.footnote) + .padding(.bottom, 1) + Text(episode.overview ?? "") + .font(.caption) + .fontWeight(.light) + .lineLimit(4) + } + .padding(.horizontal) + + Spacer() + } + .frame(width: 500) + } + } + .buttonStyle(PlainButtonStyle()) + .id(episode.name) } } - .buttonStyle(PlainButtonStyle()) - .id(episode.name) } } .padding(.horizontal, 50) .padding(.vertical) - .onAppear { - // TODO: Get this working - reader.scrollTo(viewModel.item.name) + .onChange(of: viewModel.selectedSeason) { _ in + if viewModel.selectedSeason?.id == viewModel.episodeItemViewModel.item.seasonId { + reader.scrollTo(viewModel.episodeItemViewModel.item.name) + } + } + .onChange(of: viewModel.seasonsEpisodes) { _ in + if viewModel.selectedSeason?.id == viewModel.episodeItemViewModel.item.seasonId { + reader.scrollTo(viewModel.episodeItemViewModel.item.name) + } } } .edgesIgnoringSafeArea(.horizontal) diff --git a/JellyfinPlayer tvOS/Views/ItemView/ItemDetailsView.swift b/JellyfinPlayer tvOS/Views/ItemView/ItemDetailsView.swift index b302e613..c235679b 100644 --- a/JellyfinPlayer tvOS/Views/ItemView/ItemDetailsView.swift +++ b/JellyfinPlayer tvOS/Views/ItemView/ItemDetailsView.swift @@ -23,12 +23,12 @@ struct ItemDetailsView: View { HStack(alignment: .top) { VStack(alignment: .leading, spacing: 20) { - Text("Details") + Text("Information") .font(.title3) .padding(.bottom, 5) - ForEach(detailItems, id: \.self.0) { (title, content) in - ItemDetail(title: title, content: content) + ForEach(viewModel.informationItems, id: \.self.title) { informationItem in + ItemDetail(title: informationItem.title, content: informationItem.content) } } @@ -39,8 +39,8 @@ struct ItemDetailsView: View { .font(.title3) .padding(.bottom, 5) - ForEach(mediaItems, id: \.self.0) { (title, content) in - ItemDetail(title: title, content: content) + ForEach(viewModel.mediaItems, id: \.self.title) { mediaItem in + ItemDetail(title: mediaItem.title, content: mediaItem.content) } } @@ -49,8 +49,7 @@ struct ItemDetailsView: View { .ignoresSafeArea() .focusable() .focused($focused) - .padding(.horizontal, 50) - .padding(.bottom, 50) + .padding(50) } } } diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 9fd95481..d52dc955 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -1324,8 +1324,8 @@ E14F7D0A26DB3714007C3AE6 /* ItemView */ = { isa = PBXGroup; children = ( - 535BAE9E2649E569005FA86D /* ItemView.swift */, E10D87DB2784EC5200BD264C /* EpisodesRowView.swift */, + 535BAE9E2649E569005FA86D /* ItemView.swift */, E18845F726DEA9C900B0C5B7 /* ItemViewBody.swift */, E10D87D92784E4F100BD264C /* ItemViewDetailsView.swift */, E18845FB26DEACC400B0C5B7 /* Landscape */, @@ -1399,8 +1399,8 @@ children = ( E1E5D53C2783A85F00692DFE /* CinematicItemView */, 53272538268C20100035FBF1 /* EpisodeItemView.swift */, - E1E5D5432783BB5100692DFE /* ItemDetailsView.swift */, E1E5D5382783A56B00692DFE /* EpisodesRowView.swift */, + E1E5D5432783BB5100692DFE /* ItemDetailsView.swift */, 53CD2A3F268A49C2002ABD4E /* ItemView.swift */, 53CD2A41268A4B38002ABD4E /* MovieItemView.swift */, E1E5D5412783B33900692DFE /* PortraitItemsRowView.swift */, diff --git a/JellyfinPlayer/Views/ItemView/EpisodesRowView.swift b/JellyfinPlayer/Views/ItemView/EpisodesRowView.swift index a75504c8..d327341b 100644 --- a/JellyfinPlayer/Views/ItemView/EpisodesRowView.swift +++ b/JellyfinPlayer/Views/ItemView/EpisodesRowView.swift @@ -59,10 +59,10 @@ struct EpisodesRowView: View { .frame(width: 200, height: 112) VStack(alignment: .leading) { - Text("--") + Text("S-E-") .font(.footnote) .foregroundColor(.secondary) - Text("Loading") + Text("--") .font(.body) .padding(.bottom, 1) .lineLimit(2) diff --git a/Shared/Coordinators/VideoPlayerCoordinator/iOSVideoPlayerCoordinator.swift b/Shared/Coordinators/VideoPlayerCoordinator/iOSVideoPlayerCoordinator.swift index ea74d736..33ff2ee4 100644 --- a/Shared/Coordinators/VideoPlayerCoordinator/iOSVideoPlayerCoordinator.swift +++ b/Shared/Coordinators/VideoPlayerCoordinator/iOSVideoPlayerCoordinator.swift @@ -32,7 +32,7 @@ final class VideoPlayerCoordinator: NavigationCoordinatable { .statusBar(hidden: true) .ignoresSafeArea() .prefersHomeIndicatorAutoHidden(true) - .supportedOrientations(.landscape) + .supportedOrientations(UIDevice.current.userInterfaceIdiom == .pad ? .all : .landscape) }.ignoresSafeArea() } } diff --git a/Shared/Singleton/SwiftfinNotificationCenter.swift b/Shared/Singleton/SwiftfinNotificationCenter.swift index a9382880..a5d8407f 100644 --- a/Shared/Singleton/SwiftfinNotificationCenter.swift +++ b/Shared/Singleton/SwiftfinNotificationCenter.swift @@ -21,7 +21,5 @@ enum SwiftfinNotificationCenter { static let processDeepLink = Notification.Name("processDeepLink") static let didPurge = Notification.Name("didPurge") static let didChangeServerCurrentURI = Notification.Name("didChangeCurrentLoginURI") - - static let didEndPlayback = Notification.Name("didEndPlayback") } } diff --git a/Shared/ViewModels/ConnectToServerViewModel.swift b/Shared/ViewModels/ConnectToServerViewModel.swift index 378c297b..4e2ec728 100644 --- a/Shared/ViewModels/ConnectToServerViewModel.swift +++ b/Shared/ViewModels/ConnectToServerViewModel.swift @@ -49,10 +49,10 @@ final class ConnectToServerViewModel: ViewModel { } #endif - let uri = uri.trimmingCharacters(in: .whitespaces) + let trimmedURI = uri.trimmingCharacters(in: .whitespaces) - LogManager.shared.log.debug("Attempting to connect to server at \"\(uri)\"", tag: "connectToServer") - SessionManager.main.connectToServer(with: uri) + LogManager.shared.log.debug("Attempting to connect to server at \"\(trimmedURI)\"", tag: "connectToServer") + SessionManager.main.connectToServer(with: trimmedURI) .trackActivity(loading) .sink(receiveCompletion: { completion in // This is disgusting. ViewModel Error handling overall needs to be refactored diff --git a/Shared/ViewModels/HomeViewModel.swift b/Shared/ViewModels/HomeViewModel.swift index a162a6fb..e00f07c5 100644 --- a/Shared/ViewModels/HomeViewModel.swift +++ b/Shared/ViewModels/HomeViewModel.swift @@ -32,11 +32,6 @@ final class HomeViewModel: ViewModel { let nc = SwiftfinNotificationCenter.main nc.addObserver(self, selector: #selector(didSignIn), name: SwiftfinNotificationCenter.Keys.didSignIn, object: nil) nc.addObserver(self, selector: #selector(didSignOut), name: SwiftfinNotificationCenter.Keys.didSignOut, object: nil) - nc.addObserver(self, selector: #selector(didEndPlayback), name: SwiftfinNotificationCenter.Keys.didEndPlayback, object: nil) - } - - deinit { - SwiftfinNotificationCenter.main.removeObserver(self) } @objc private func didSignIn() { @@ -59,11 +54,6 @@ final class HomeViewModel: ViewModel { cancellables.removeAll() } - - @objc private func didEndPlayback() { - refreshResumeItems() - refreshNextUpItems() - } @objc func refresh() { LogManager.shared.log.debug("Refresh called.")