parent
9bdb9a2c62
commit
5c4ee44575
|
@ -74,7 +74,7 @@ struct PortraitItemView: View {
|
|||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
} else {
|
||||
Text("S\(String(item.parentIndexNumber ?? 0)):E\(String(item.indexNumber ?? 0))")
|
||||
Text("S\(String(item.parentIndexNumber ?? 0)):E\(String(item.indexNumber ?? 0)) - \(item.name ?? "")")
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
.fontWeight(.medium)
|
||||
|
|
|
@ -27,7 +27,7 @@ struct LibrarySearchView: View {
|
|||
if searchQuery.isEmpty {
|
||||
suggestionsListView
|
||||
} else {
|
||||
itemsGridView
|
||||
resultView
|
||||
}
|
||||
}
|
||||
if viewModel.isLoading {
|
||||
|
@ -61,21 +61,56 @@ struct LibrarySearchView: View {
|
|||
}
|
||||
}
|
||||
|
||||
var itemsGridView: some View {
|
||||
ScrollView {
|
||||
if !viewModel.items.isEmpty {
|
||||
LazyVGrid(columns: tracks) {
|
||||
ForEach(viewModel.items, id: \.id) { item in
|
||||
PortraitItemView(item: item)
|
||||
var resultView: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Picker("ItemType", selection: $viewModel.selectedItemType) {
|
||||
ForEach(viewModel.supportedItemTypeList, id: \.self) {
|
||||
Text($0.localized)
|
||||
.tag($0)
|
||||
}
|
||||
}
|
||||
.pickerStyle(SegmentedPickerStyle())
|
||||
let items = items(for: viewModel.selectedItemType)
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading, spacing: 16) {
|
||||
if !items.isEmpty {
|
||||
LazyVGrid(columns: tracks) {
|
||||
ForEach(items, id: \.id) { item in
|
||||
PortraitItemView(item: item)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 16)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 16)
|
||||
.onRotate { _ in
|
||||
recalcTracks()
|
||||
}
|
||||
} else {
|
||||
Text("Query returned 0 results.")
|
||||
}
|
||||
}
|
||||
.onRotate { _ in
|
||||
recalcTracks()
|
||||
}
|
||||
}
|
||||
|
||||
func items(for type: ItemType) -> [BaseItemDto] {
|
||||
switch type {
|
||||
case .episode:
|
||||
return viewModel.episodeItems
|
||||
case .movie:
|
||||
return viewModel.movieItems
|
||||
case .series:
|
||||
return viewModel.showItems
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ItemType {
|
||||
|
||||
var localized: String {
|
||||
switch self {
|
||||
case .episode:
|
||||
return "Episodes"
|
||||
case .movie:
|
||||
return "Movies"
|
||||
case .series:
|
||||
return "Shows"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,3 +67,9 @@ extension APISortOrder {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ItemType: String {
|
||||
case episode = "Episode"
|
||||
case movie = "Movie"
|
||||
case series = "Series"
|
||||
}
|
||||
|
|
|
@ -12,32 +12,63 @@ import Foundation
|
|||
import JellyfinAPI
|
||||
|
||||
final class LibrarySearchViewModel: ViewModel {
|
||||
@Published var items = [BaseItemDto]()
|
||||
|
||||
|
||||
@Published var supportedItemTypeList = [ItemType]()
|
||||
|
||||
@Published var selectedItemType = ItemType.movie
|
||||
|
||||
@Published var movieItems = [BaseItemDto]()
|
||||
@Published var showItems = [BaseItemDto]()
|
||||
@Published var episodeItems = [BaseItemDto]()
|
||||
|
||||
@Published var suggestions = [BaseItemDto]()
|
||||
|
||||
|
||||
var searchQuerySubject = CurrentValueSubject<String, Never>("")
|
||||
var parentID: String?
|
||||
|
||||
|
||||
init(parentID: String?) {
|
||||
self.parentID = parentID
|
||||
super.init()
|
||||
|
||||
|
||||
searchQuerySubject
|
||||
.filter { !$0.isEmpty }
|
||||
.debounce(for: 0.25, scheduler: DispatchQueue.main)
|
||||
.sink(receiveValue: search(with:))
|
||||
.sink(receiveValue: search)
|
||||
.store(in: &cancellables)
|
||||
|
||||
let supportedItemTypeListPublishers = Publishers.CombineLatest3($movieItems, $showItems, $episodeItems)
|
||||
.map { arg -> [ItemType] in
|
||||
var typeList = [ItemType]()
|
||||
if !arg.0.isEmpty {
|
||||
typeList.append(.movie)
|
||||
}
|
||||
if !arg.1.isEmpty {
|
||||
typeList.append(.series)
|
||||
}
|
||||
if !arg.2.isEmpty {
|
||||
typeList.append(.episode)
|
||||
}
|
||||
return typeList
|
||||
}
|
||||
|
||||
supportedItemTypeListPublishers
|
||||
.assign(to: \.supportedItemTypeList, on: self)
|
||||
.store(in: &cancellables)
|
||||
supportedItemTypeListPublishers
|
||||
.compactMap(\.first)
|
||||
.assign(to: \.selectedItemType, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
|
||||
requestSuggestions()
|
||||
}
|
||||
|
||||
|
||||
func requestSuggestions() {
|
||||
ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!,
|
||||
limit: 20,
|
||||
recursive: true,
|
||||
parentId: parentID,
|
||||
includeItemTypes: ["Movie", "Series", "MusicArtist"],
|
||||
includeItemTypes: ["Movie", "Series"],
|
||||
sortBy: ["IsFavoriteOrLiked", "Random"],
|
||||
imageTypeLimit: 0,
|
||||
enableTotalRecordCount: false,
|
||||
|
@ -48,17 +79,39 @@ final class LibrarySearchViewModel: ViewModel {
|
|||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
|
||||
func search(with query: String) {
|
||||
ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, limit: 60, recursive: true, searchTerm: query,
|
||||
ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, limit: 50, recursive: true, searchTerm: query,
|
||||
sortOrder: [.ascending], parentId: parentID,
|
||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
|
||||
includeItemTypes: ["Movie", "Series"], sortBy: ["SortName"], enableUserData: true, enableImages: true)
|
||||
includeItemTypes: [ItemType.movie.rawValue], sortBy: ["SortName"], enableUserData: true, enableImages: true)
|
||||
.trackActivity(loading)
|
||||
.sink(receiveCompletion: { [weak self] completion in
|
||||
self?.handleAPIRequestCompletion(completion: completion)
|
||||
}, receiveValue: { [weak self] response in
|
||||
self?.items = response.items ?? []
|
||||
self?.movieItems = response.items ?? []
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, limit: 50, recursive: true, searchTerm: query,
|
||||
sortOrder: [.ascending], parentId: parentID,
|
||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
|
||||
includeItemTypes: [ItemType.series.rawValue], sortBy: ["SortName"], enableUserData: true, enableImages: true)
|
||||
.trackActivity(loading)
|
||||
.sink(receiveCompletion: { [weak self] completion in
|
||||
self?.handleAPIRequestCompletion(completion: completion)
|
||||
}, receiveValue: { [weak self] response in
|
||||
self?.showItems = response.items ?? []
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, limit: 50, recursive: true, searchTerm: query,
|
||||
sortOrder: [.ascending], parentId: parentID,
|
||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
|
||||
includeItemTypes: [ItemType.episode.rawValue], sortBy: ["SortName"], enableUserData: true, enableImages: true)
|
||||
.trackActivity(loading)
|
||||
.sink(receiveCompletion: { [weak self] completion in
|
||||
self?.handleAPIRequestCompletion(completion: completion)
|
||||
}, receiveValue: { [weak self] response in
|
||||
self?.episodeItems = response.items ?? []
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue