Search Channels and Programs (#1037)

This commit is contained in:
Ethan Pippin 2024-04-19 15:55:47 -06:00 committed by GitHub
parent 58190d73fd
commit 2dace313d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 111 additions and 12 deletions

View File

@ -73,8 +73,12 @@ final class MainTabCoordinator: TabCoordinatable {
}
}
func makeSearch() -> NavigationViewCoordinator<SearchCoordinator> {
NavigationViewCoordinator(SearchCoordinator())
// TODO: does this cause issues?
func makeSearch() -> VideoPlayerWrapperCoordinator {
VideoPlayerWrapperCoordinator {
SearchCoordinator()
.view()
}
}
@ViewBuilder

View File

@ -9,6 +9,10 @@
import Foundation
import JellyfinAPI
// TODO: the video player needs to be slightly refactored anyways, so I'm fine
// with the channel retrieving method below and is mainly just for reference
// for how I should probably handle getting the channels of programs elsewhere.
class LiveVideoPlayerManager: VideoPlayerManager {
@Published
@ -26,4 +30,36 @@ class LiveVideoPlayerManager: VideoPlayerManager {
}
}
}
init(program: BaseItemDto) {
super.init()
Task {
guard let channel = try? await self.getChannel(for: program), let mediaSource = channel.mediaSources?.first else {
assertionFailure("No channel for program?")
return
}
let viewModel = try await program.liveVideoPlayerViewModel(with: mediaSource, logger: logger)
await MainActor.run {
self.currentViewModel = viewModel
}
}
}
private func getChannel(for program: BaseItemDto) async throws -> BaseItemDto? {
var parameters = Paths.GetItemsByUserIDParameters()
parameters.fields = .MinimumFields
parameters.ids = [program.channelID ?? ""]
let request = Paths.getItemsByUserID(
userID: userSession.user.id,
parameters: parameters
)
let response = try await userSession.client.send(request)
return response.value.items?.first
}
}

View File

@ -172,7 +172,6 @@ final class ProgramsViewModel: ViewModel, Stateful {
var parameters = Paths.GetLiveTvProgramsParameters()
parameters.fields = .MinimumFields
.appending(.channelInfo)
.appending(.mediaSources)
parameters.hasAired = false
parameters.limit = 10
parameters.userID = userSession.user.id

View File

@ -31,6 +31,8 @@ final class SearchViewModel: ViewModel, Stateful {
case searching
}
@Published
private(set) var channels: [BaseItemDto] = []
@Published
private(set) var collections: [BaseItemDto] = []
@Published
@ -40,6 +42,8 @@ final class SearchViewModel: ViewModel, Stateful {
@Published
private(set) var people: [BaseItemDto] = []
@Published
private(set) var programs: [BaseItemDto] = []
@Published
private(set) var series: [BaseItemDto] = []
@Published
private(set) var suggestions: [BaseItemDto] = []
@ -55,11 +59,15 @@ final class SearchViewModel: ViewModel, Stateful {
let filterViewModel: FilterViewModel
var hasNoResults: Bool {
collections.isEmpty &&
episodes.isEmpty &&
movies.isEmpty &&
people.isEmpty &&
series.isEmpty
[
collections,
channels,
episodes,
movies,
people,
programs,
series,
].allSatisfy(\.isEmpty)
}
// MARK: init
@ -144,7 +152,9 @@ final class SearchViewModel: ViewModel, Stateful {
.boxSet,
.episode,
.movie,
.liveTvProgram,
.series,
.tvChannel,
]
for type in retrievingItemTypes {
@ -173,9 +183,11 @@ final class SearchViewModel: ViewModel, Stateful {
await MainActor.run {
self.collections = items[.boxSet] ?? []
self.channels = items[.tvChannel] ?? []
self.episodes = items[.episode] ?? []
self.movies = items[.movie] ?? []
self.people = items[.person] ?? []
self.programs = items[.liveTvProgram] ?? []
self.series = items[.series] ?? []
self.state = .content

View File

@ -15,6 +15,8 @@ struct SearchView: View {
@Default(.Customization.searchPosterType)
private var searchPosterType
@EnvironmentObject
private var videoPlayerRouter: VideoPlayerWrapperCoordinator.Router
@EnvironmentObject
private var router: SearchCoordinator.Router
@ -58,15 +60,35 @@ struct SearchView: View {
if viewModel.people.isNotEmpty {
itemsSection(title: L10n.people, keyPath: \.people, posterType: .portrait)
}
if viewModel.programs.isNotEmpty {
itemsSection(title: L10n.programs, keyPath: \.programs, posterType: .landscape)
}
if viewModel.channels.isNotEmpty {
itemsSection(title: L10n.channels, keyPath: \.channels, posterType: .portrait)
}
}
}
}
private func select(_ item: BaseItemDto) {
if item.type == .person {
switch item.type {
case .person:
let viewModel = ItemLibraryViewModel(parent: item)
router.route(to: \.library, viewModel)
} else {
case .program:
videoPlayerRouter.route(
to: \.liveVideoPlayer,
LiveVideoPlayerManager(program: item)
)
case .tvChannel:
guard let mediaSource = item.mediaSources?.first else { return }
videoPlayerRouter.route(
to: \.liveVideoPlayer,
LiveVideoPlayerManager(item: item, mediaSource: mediaSource)
)
default:
router.route(to: \.item, item)
}
}

View File

@ -9,6 +9,8 @@
import JellyfinAPI
import SwiftUI
// TODO: item-type dependent views may be more appropriate near/on
// the `PosterButton` object instead of on these larger views
extension ProgramsView {
struct ProgramProgressOverlay: View {

View File

@ -13,6 +13,8 @@ import SwiftUI
// TODO: have a `SearchLibraryViewModel` that allows paging on searched items?
// TODO: implement search view result type between `PosterHStack`
// and `ListHStack` (3 row list columns)? (iOS only)
// TODO: have programs only pull recommended/current?
// - have progress overlay
struct SearchView: View {
@Default(.Customization.Search.enabledDrawerFilters)
@ -20,6 +22,8 @@ struct SearchView: View {
@Default(.Customization.searchPosterType)
private var searchPosterType
@EnvironmentObject
private var mainRouter: MainCoordinator.Router
@EnvironmentObject
private var router: SearchCoordinator.Router
@ -68,16 +72,36 @@ struct SearchView: View {
if viewModel.people.isNotEmpty {
itemsSection(title: L10n.people, keyPath: \.people, posterType: .portrait)
}
if viewModel.programs.isNotEmpty {
itemsSection(title: L10n.programs, keyPath: \.programs, posterType: .landscape)
}
if viewModel.channels.isNotEmpty {
itemsSection(title: L10n.channels, keyPath: \.channels, posterType: .portrait)
}
}
.edgePadding(.vertical)
}
}
private func select(_ item: BaseItemDto) {
if item.type == .person {
switch item.type {
case .person:
let viewModel = ItemLibraryViewModel(parent: item)
router.route(to: \.library, viewModel)
} else {
case .program:
mainRouter.route(
to: \.liveVideoPlayer,
LiveVideoPlayerManager(program: item)
)
case .tvChannel:
guard let mediaSource = item.mediaSources?.first else { return }
mainRouter.route(
to: \.liveVideoPlayer,
LiveVideoPlayerManager(item: item, mediaSource: mediaSource)
)
default:
router.route(to: \.item, item)
}
}