Sora/Sora-JS/Views/SearchView.swift
2025-01-05 16:22:26 +01:00

179 lines
6.8 KiB
Swift

//
// SearchView.swift
// Sora-JS
//
// Created by Francesco on 05/01/25.
//
import SwiftUI
import Kingfisher
struct MediaItem: Identifiable {
let id = UUID()
let title: String
let imageUrl: String
}
struct SearchView: View {
@StateObject private var jsController = JSController()
@EnvironmentObject var moduleManager: ModuleManager
@State private var searchText = ""
@State private var mediaItems: [MediaItem] = []
@State private var isSearching = false
@AppStorage("selectedModuleId") private var selectedModuleId: String?
private var selectedModule: ScrapingModule? {
guard let id = selectedModuleId else { return nil }
return moduleManager.modules.first { $0.id.uuidString == id }
}
var body: some View {
NavigationView {
ScrollView {
VStack(spacing: 0) {
if selectedModule == nil {
VStack(spacing: 8) {
Text("No Module Selected")
.font(.headline)
Text("Please select a module from settings")
.font(.caption)
.foregroundColor(.secondary)
}
.padding()
.frame(maxWidth: .infinity)
.background(Color(.systemBackground))
.shadow(color: Color.black.opacity(0.1), radius: 2, y: 1)
}
SearchBar(text: $searchText, onSearchButtonClicked: performSearch)
.padding()
.disabled(selectedModule == nil)
if isSearching {
ProgressView()
.padding()
}
LazyVGrid(columns: [GridItem(.adaptive(minimum: 150))], spacing: 16) {
ForEach(mediaItems) { item in
VStack {
KFImage(URL(string: item.imageUrl))
.resizable()
.aspectRatio(2/3, contentMode: .fill)
.cornerRadius(10)
.frame(width: 150, height: 225)
Text(item.title)
.font(.subheadline)
.foregroundColor(Color.primary)
.padding([.leading, .bottom], 8)
.lineLimit(1)
}
}
}
.padding()
}
}
.navigationTitle("Search")
.navigationBarTitleDisplayMode(.large)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
HStack {
if let selectedModule = selectedModule {
Text(selectedModule.metadata.sourceName)
.font(.headline)
.foregroundColor(.secondary)
}
Menu {
ForEach(moduleManager.modules) { module in
Button {
selectedModuleId = module.id.uuidString
} label: {
HStack {
KFImage(URL(string: module.metadata.iconUrl))
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 20, height: 20)
.cornerRadius(4)
Text(module.metadata.sourceName)
Spacer()
if module.id.uuidString == selectedModuleId {
Image(systemName: "checkmark")
}
}
}
}
} label: {
Image(systemName: "chevron.up.chevron.down")
.foregroundColor(.secondary)
}
}
}
}
}
.navigationViewStyle(StackNavigationViewStyle())
.onChange(of: selectedModuleId) { _ in
if !searchText.isEmpty {
performSearch()
}
}
}
private func performSearch() {
guard !searchText.isEmpty, let module = selectedModule else {
mediaItems = []
return
}
isSearching = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
Task {
do {
let jsContent = try moduleManager.getModuleContent(module)
jsController.loadScript(jsContent)
jsController.searchContent(keyword: searchText, module: module) { items in
mediaItems = items
isSearching = false
}
} catch {
print("Error loading module: \(error)")
isSearching = false
}
}
}
}
}
struct SearchBar: View {
@Binding var text: String
var onSearchButtonClicked: () -> Void
var body: some View {
HStack {
TextField("Search...", text: $text, onCommit: onSearchButtonClicked)
.padding(7)
.padding(.horizontal, 25)
.background(Color(.systemGray6))
.cornerRadius(8)
.overlay(
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.secondary)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 8)
if !text.isEmpty {
Button(action: {
self.text = ""
}) {
Image(systemName: "multiply.circle.fill")
.foregroundColor(.secondary)
.padding(.trailing, 8)
}
}
}
)
}
}
}