Implement search by ItemType

Search Logic Improvements
This commit is contained in:
PangMo5 2021-07-16 00:50:46 +09:00
parent 9bdb9a2c62
commit 5c4ee44575
4 changed files with 120 additions and 26 deletions

View File

@ -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)

View File

@ -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"
}
}
}

View File

@ -67,3 +67,9 @@ extension APISortOrder {
}
}
}
enum ItemType: String {
case episode = "Episode"
case movie = "Movie"
case series = "Series"
}

View File

@ -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)
}