stremio/apps/ios/VMCTV/UI/SearchView.swift
Jos Vooges | STH ac951617cb Add Live Events across API/admin/Android TV, mobile ClearKey/DASH fixes, and tvOS app.
Wire FAAB schedule-events into master-api and admin IPTV settings, add Android TV Events UI, fix mobile ClearKey/stream playback (DASH + bump 1.0.23), and scaffold the Apple TV (VMCTV) client with docs/scripts.
2026-09-18 23:34:42 +02:00

97 lines
3.6 KiB
Swift

import SwiftUI
struct SearchView: View {
@EnvironmentObject private var app: AppModel
@State private var query = ""
@State private var results: [MediaItem] = []
@State private var error: String?
@State private var searching = false
private let cols = [
GridItem(.adaptive(minimum: 168, maximum: 200), spacing: 24)
]
var body: some View {
VStack(alignment: .leading, spacing: 24) {
Text("Zoeken")
.font(TvFonts.title)
.foregroundStyle(TvColors.text)
.padding(.horizontal, 48)
.padding(.top, 12)
TextField("Films, series…", text: $query)
.font(TvFonts.headline)
.foregroundStyle(TvColors.text)
.padding(.horizontal, 28)
.padding(.vertical, 20)
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 18, style: .continuous))
.overlay(
RoundedRectangle(cornerRadius: 18, style: .continuous)
.strokeBorder(Color.white.opacity(0.12), lineWidth: 1)
)
.padding(.horizontal, 48)
.onSubmit { Task { await runSearch() } }
Group {
if searching {
ProgressView().tint(.white).frame(maxWidth: .infinity, maxHeight: .infinity)
} else if let error {
Text(error).font(TvFonts.body).foregroundStyle(TvColors.danger).padding(.horizontal, 48)
Spacer()
} else if results.isEmpty && query.count >= 2 {
Text("Geen resultaten")
.font(TvFonts.headline)
.foregroundStyle(TvColors.textDim)
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else if results.isEmpty {
Text("Typ minstens 2 tekens om te zoeken.")
.font(TvFonts.body)
.foregroundStyle(TvColors.textDim)
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else {
ScrollView {
LazyVGrid(columns: cols, spacing: 28) {
ForEach(results) { item in
NavigationLink(value: item) {
PosterCard(item: item, width: 168)
}
.buttonStyle(FocusableCardStyle())
}
}
.padding(.horizontal, 48)
.padding(.bottom, 48)
}
.focusSection()
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(TvColors.bg.ignoresSafeArea())
.navigationDestination(for: MediaItem.self) { MediaRouter(item: $0) }
.onChange(of: query) { _, newValue in
Task {
try? await Task.sleep(nanoseconds: 400_000_000)
guard newValue == query else { return }
await runSearch()
}
}
}
private func runSearch() async {
let q = query.trimmingCharacters(in: .whitespacesAndNewlines)
guard q.count >= 2 else {
results = []
error = nil
return
}
searching = true
error = nil
do {
results = try await app.api.search(query: q)
searching = false
} catch {
self.error = error.localizedDescription
searching = false
}
}
}