Search Channels and Programs (#1037)
This commit is contained in:
parent
58190d73fd
commit
2dace313d1
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue