// // SearchView.swift // LuminateUI // // Created by Brendan Szymanski on 6/5/26. // @preconcurrency import Adwaita import Foundation import LuminateCore import LuminateDI struct SearchView: View { var client: JellyfinClient var userId: String @State private nonisolated(unsafe) var searchText = "" @State private nonisolated(unsafe) var results: [Components.Schemas.SearchHint] = [] @State private nonisolated(unsafe) var isSearching = false var view: Body { VStack { SearchEntry() .text($searchText) .placeholderText("Search") if isSearching { Spinner() .padding(20) } else { ScrollView { VStack { ForEach(results) { hint in SearchResultRow(hint: hint, client: client) } } } } } .padding() } private func performSearch() { guard !searchText.isEmpty || searchText == "" else { results = [] return } isSearching = true Task { [self] in let result = try? await client.getSearchHints( searchTerm: searchText, userId: userId, limit: 50 ) results = result?.searchHints ?? [] isSearching = false } } } extension Components.Schemas.SearchHint: Identifiable { public var id: String { id ?? itemId ?? String(describing: self) } } struct SearchResultRow: View { var hint: Components.Schemas.SearchHint var client: JellyfinClient @Injected(\.pageAnimationTracker) var pageAnimationTracker @State private nonisolated(unsafe) var imageData: Data? var view: Body { HStack { if let data = imageData { Picture() .data(data) .frame(minWidth: 80, minHeight: 120) .frame(maxWidth: 80) .frame(maxHeight: 120) .card() } else { Box(spacing: 0) {} .frame(minWidth: 80, minHeight: 120) .frame(maxWidth: 80) .frame(maxHeight: 120) .card() } VStack { Text(hint.name ?? "") .body() .halign(.start) if let type = hint._type?.value1 { Text("\(type)") .caption() .halign(.start) } if let year = hint.productionYear { Text("\(year)") .caption() .halign(.start) } Text(hint.runtimeString) .caption() .halign(.start) } .hexpand(true) } .padding(5, .vertical) .onAppear { Idle { loadImage() } } } private func loadImage() { guard let tag = hint.primaryImageTag, let itemId = hint.id ?? hint.itemId else { return } Task { [self] in guard let url = await client.imageURL( itemId: itemId, imageType: .primary, tag: tag, maxWidth: 160 ) else { return } let service = ImageService() let data = try? await service.loadImage(url: url) if pageAnimationTracker.isAnimating { _imageData.rawValue = data } else { imageData = data } } } }