diff --git a/JellyfinPlayer/LibrarySearchView.swift b/JellyfinPlayer/LibrarySearchView.swift index 6a8c6126..2d82099c 100644 --- a/JellyfinPlayer/LibrarySearchView.swift +++ b/JellyfinPlayer/LibrarySearchView.swift @@ -12,43 +12,70 @@ import SwiftUI struct LibrarySearchView: View { @StateObject var viewModel: LibrarySearchViewModel @State var searchQuery = "" - + @State private var tracks: [GridItem] = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 125) - + func recalcTracks() { tracks = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 125) } - + var body: some View { - VStack { - Spacer().frame(height: 6) - SearchBar(text: $searchQuery) - ZStack { - if !viewModel.isLoading { - ScrollView(.vertical) { - if !viewModel.items.isEmpty { - Spacer().frame(height: 16) - LazyVGrid(columns: tracks) { - ForEach(viewModel.items, id: \.id) { item in - PortraitItemView(item: item) - } - Spacer().frame(height: 16) - } - .onRotate { _ in - recalcTracks() - } - } else { - Text("Query returned 0 results.") - } - } + ZStack { + VStack { + SearchBar(text: $searchQuery) + .padding(.vertical, 8) + if searchQuery.isEmpty { + suggestionsListView } else { - ProgressView() + itemsGridView } } + if viewModel.isLoading { + ProgressView() + } } .onChange(of: searchQuery) { query in viewModel.searchQuerySubject.send(query) } .navigationBarTitle("Search", displayMode: .inline) } + + var suggestionsListView: some View { + ScrollView { + LazyVStack(alignment: .leading, spacing: 8) { + Text("Suggestions") + .font(.title) + .foregroundColor(.primary) + .padding(.bottom, 8) + ForEach(viewModel.suggestions, id: \.id) { item in + Button { + searchQuery = item.name ?? "" + } label: { + Text(item.name ?? "") + .font(.body) + .foregroundColor(.secondary) + } + } + } + .padding(.horizontal, 16) + } + } + + var itemsGridView: some View { + ScrollView { + if !viewModel.items.isEmpty { + LazyVGrid(columns: tracks) { + ForEach(viewModel.items, id: \.id) { item in + PortraitItemView(item: item) + } + } + .padding(.bottom, 16) + .onRotate { _ in + recalcTracks() + } + } else { + Text("Query returned 0 results.") + } + } + } } diff --git a/Shared/ViewModels/LibrarySearchViewModel.swift b/Shared/ViewModels/LibrarySearchViewModel.swift index 75c4f86b..b7dca966 100644 --- a/Shared/ViewModels/LibrarySearchViewModel.swift +++ b/Shared/ViewModels/LibrarySearchViewModel.swift @@ -12,8 +12,9 @@ import Foundation import JellyfinAPI final class LibrarySearchViewModel: ViewModel { - @Published - var items = [BaseItemDto]() + @Published var items = [BaseItemDto]() + + @Published var suggestions = [BaseItemDto]() var searchQuerySubject = CurrentValueSubject("") var parentID: String? @@ -23,9 +24,29 @@ final class LibrarySearchViewModel: ViewModel { super.init() searchQuerySubject + .filter { !$0.isEmpty } .debounce(for: 0.25, scheduler: DispatchQueue.main) .sink(receiveValue: search(with:)) .store(in: &cancellables) + + requestSuggestions() + } + + func requestSuggestions() { + ItemsAPI.getItemsByUserId(userId: SessionManager.current.user.user_id!, + limit: 20, + recursive: true, + parentId: parentID, + includeItemTypes: ["Movie", "Series", "MusicArtist"], + sortBy: ["IsFavoriteOrLiked", "Random"], + imageTypeLimit: 0, + enableTotalRecordCount: false, + enableImages: false) + .trackActivity(loading) + .sink(receiveCompletion: handleAPIRequestCompletion(completion:)) { [weak self] response in + self?.suggestions = response.items ?? [] + } + .store(in: &cancellables) } func search(with query: String) { diff --git a/Shared/ViewModels/SplashViewModel.swift b/Shared/ViewModels/SplashViewModel.swift index 5a33abad..df524bb1 100644 --- a/Shared/ViewModels/SplashViewModel.swift +++ b/Shared/ViewModels/SplashViewModel.swift @@ -10,6 +10,7 @@ import Foundation import Combine import Nuke +import UIKit #if !os(tvOS) import WidgetKit @@ -28,6 +29,7 @@ final class SplashViewModel: ViewModel { #if !os(tvOS) WidgetCenter.shared.reloadAllTimelines() + UIScrollView.appearance().keyboardDismissMode = .onDrag #endif let nc = NotificationCenter.default