213 lines
6.9 KiB
Swift
213 lines
6.9 KiB
Swift
//
|
||
// SearchView.swift
|
||
// dimensionhub
|
||
//
|
||
// Created by 安礼成 on 2025/4/1.
|
||
//
|
||
|
||
import SwiftUI
|
||
|
||
struct SearchView: View {
|
||
@Environment(\.modelContext) var modelContext
|
||
@Environment(\.userId) private var userId
|
||
|
||
@Environment(\.dismiss) var dismiss
|
||
@State var searchText: String = ""
|
||
@State var searchModel = SearchModel()
|
||
|
||
@State private var isSearching = false
|
||
|
||
// 是否显示搜索结果
|
||
@State private var showSearchResult: Bool = false
|
||
|
||
// 键盘焦点的控制
|
||
@State private var toolbarID = UUID()
|
||
@State private var isFocused: Bool = true
|
||
|
||
var body: some View {
|
||
ScrollView(.vertical, showsIndicators: false) {
|
||
LazyVStack(alignment: .center, spacing: 10) {
|
||
ForEach(searchModel.dramaGroups, id: \.group_id) { group in
|
||
SearchDramaGroupView(group: group)
|
||
}
|
||
}
|
||
.padding(.top, 8)
|
||
}
|
||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||
.background(
|
||
GestureObserver {
|
||
// 当检测到系统返回手势时调用
|
||
if isFocused {
|
||
isFocused = false
|
||
}
|
||
} onGestureCancelled: {
|
||
if !isFocused && !showSearchResult {
|
||
isFocused = true
|
||
}
|
||
}
|
||
)
|
||
.onTapGesture {
|
||
isFocused = false
|
||
}
|
||
.simultaneousGesture(
|
||
DragGesture().onChanged{ _ in
|
||
isFocused = false
|
||
}
|
||
)
|
||
.overlay(alignment: .center) {
|
||
Text("什么都没有找到")
|
||
.font(.system(size: 18))
|
||
.foregroundColor(Color(hex: "#333333"))
|
||
.opacity(showSearchResult && searchModel.dramaGroups.isEmpty ? 1 : 0)
|
||
}
|
||
.navigationTitle("")
|
||
.toolbar {
|
||
ToolbarItem(placement: .principal) {
|
||
SearchBar(isFirstResponder: $isFocused, searchText: $searchText) {
|
||
doSearch()
|
||
}
|
||
.frame(width: UIScreen.main.bounds.width * 0.8)
|
||
.id(toolbarID)
|
||
}
|
||
}
|
||
.onAppear {
|
||
toolbarID = UUID()
|
||
}
|
||
}
|
||
|
||
private func doSearch() {
|
||
let trimmed = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
if !trimmed.isEmpty {
|
||
Task.detached {
|
||
await searchModel.search(userId: userId, name: trimmed)
|
||
DispatchQueue.main.async {
|
||
// 显示搜索结果
|
||
self.showSearchResult = true
|
||
}
|
||
}
|
||
|
||
// 可选:添加历史记录
|
||
// let history = SearchHistory(keyword: trimmed, timestamp: Date())
|
||
// modelContext.insert(history)
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
// 显示分组信息
|
||
struct SearchDramaGroupView: View {
|
||
let group: SearchModel.DramaGroup
|
||
|
||
var body: some View {
|
||
LazyVStack(alignment: .center, spacing: 10) {
|
||
ForEach(group.items, id: \.id) { item in
|
||
NavigationLink(destination: DetailView(id: item.id, channelName: nil)) {
|
||
FlexImage(urlString: item.thumb, width: 370, height: 180, placeholder: "ph_img_big")
|
||
.frame(width: 370, height: 180)
|
||
.overlay(alignment: .topLeading) {
|
||
VStack(alignment: .leading, spacing: 8) {
|
||
Text(item.name)
|
||
.font(.system(size: 16))
|
||
.foregroundColor(.white)
|
||
.lineLimit(1)
|
||
|
||
Text(item.status)
|
||
.font(.system(size: 12))
|
||
.foregroundColor(.white)
|
||
.lineLimit(1)
|
||
}
|
||
.padding(5)
|
||
.background(
|
||
Color.black.opacity(0.6)
|
||
)
|
||
.cornerRadius(5)
|
||
.padding(8)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
struct GestureObserver: UIViewControllerRepresentable {
|
||
var onGestureBegin: () -> Void
|
||
var onGestureCancelled: () -> Void
|
||
|
||
func makeUIViewController(context: Context) -> UIViewController {
|
||
return context.coordinator
|
||
}
|
||
|
||
func updateUIViewController(_ uiViewController: UIViewController, context: Context) { }
|
||
|
||
func makeCoordinator() -> Coordinator {
|
||
Coordinator(onGestureBegin: onGestureBegin, onGestureCancelled: onGestureCancelled)
|
||
}
|
||
|
||
class Coordinator: UIViewController {
|
||
var onGestureBegin: () -> Void
|
||
var onGestureCancelled: () -> Void
|
||
|
||
// 用来保存当前堆栈中的数量
|
||
var initialStackCount: Int = 0
|
||
|
||
// 保存起来
|
||
var cacheNavigationController: UINavigationController?
|
||
|
||
init(onGestureBegin: @escaping () -> Void, onGestureCancelled: @escaping () -> Void) {
|
||
self.onGestureBegin = onGestureBegin
|
||
self.onGestureCancelled = onGestureCancelled
|
||
super.init(nibName: nil, bundle: nil)
|
||
}
|
||
|
||
required init?(coder: NSCoder) {
|
||
fatalError("init(coder:) has not been implemented")
|
||
}
|
||
|
||
override func viewDidAppear(_ animated: Bool) {
|
||
super.viewDidAppear(animated)
|
||
guard let nav = self.navigationController else {
|
||
return
|
||
}
|
||
initialStackCount = nav.viewControllers.count
|
||
|
||
self.cacheNavigationController = nav
|
||
// 添加手势识别函数
|
||
if let gesture = nav.interactivePopGestureRecognizer {
|
||
gesture.addTarget(self, action: #selector(handleGesture(_:)))
|
||
}
|
||
}
|
||
|
||
@objc func handleGesture(_ gesture: UIScreenEdgePanGestureRecognizer) {
|
||
switch gesture.state {
|
||
case .began:
|
||
onGestureBegin()
|
||
case .ended:
|
||
if let nav = self.cacheNavigationController {
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { [self] in
|
||
let afterCount = nav.viewControllers.count
|
||
if afterCount < initialStackCount {
|
||
// 页面真的 pop 出去了
|
||
print("pop out")
|
||
} else {
|
||
// 页面还在
|
||
onGestureCancelled()
|
||
}
|
||
}
|
||
}
|
||
case .cancelled, .failed:
|
||
onGestureCancelled()
|
||
|
||
default:
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
|
||
//
|
||
//#Preview {
|
||
// SearchView()
|
||
//}
|