add search view

This commit is contained in:
anlicheng 2025-04-01 20:17:50 +08:00
parent a9d306225a
commit d95c35992f
2 changed files with 213 additions and 1 deletions

View File

@ -82,6 +82,13 @@ struct API {
return await doRequest(request: request, as: T.self)
}
// nameurl_encode
static func searchDrama<T: Codable>(userId: String, name: String, as: T.Type) async -> APIResponse<T> {
let request = URLRequest(url: URL(string: baseUrl + "/api/search?user_id=\(userId)&name=\(name)")!)
return await doRequest(request: request, as: T.self)
}
// http
private static func doRequest<T: Codable>(request: URLRequest, as: T.Type) async -> APIResponse<T> {
do {

View File

@ -6,10 +6,215 @@
//
import SwiftUI
import Observation
struct SearchView: View {
@Environment(\.dismiss) var dismiss
@State var model = SearchModel()
@State var searchText: String = ""
@FocusState private var isSearchFocused: Bool
@AppStorage("userId") private var userId: String = Utils.defaultUserId()
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
VStack {
HStack(alignment: .center) {
Color.clear
.overlay {
HStack(alignment: .center, spacing: 0) {
Button(action: { dismiss() }) {
Image(systemName: "chevron.left")
.font(.system(size: 20, weight: .medium))
.foregroundColor(.black)
}
Spacer()
SearchBar(searchText: $searchText, placeholder: "搜索...") {
Task { @MainActor in
let trimmedSearchText = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmedSearchText.isEmpty {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
await model.search(userId: userId, name: trimmedSearchText)
}
}
}
}
}
}
.frame(height: 50)
.padding([.top, .bottom], 8)
ScrollView(.vertical, showsIndicators: false) {
//
LazyVStack(alignment: .center, spacing: 10) {
ForEach(model.dramaGroups, id: \.group_id) { group in
DramaGroupView(group: group)
}
}
}
Spacer()
}
.navigationTitle("")
.navigationBarBackButtonHidden(true)
.ignoresSafeArea(edges: [.bottom])
.padding(8)
.task {
await model.search(userId: userId, name: "第二季")
}
}
}
extension SearchView {
//
struct DramaGroupView: View {
let group: SearchModel.DramaGroup
var body: some View {
VStack(alignment: .center, spacing: 10) {
ForEach(group.items, id: \.id) { item in
NavigationLink(destination: DetailView(id: item.id)) {
AsyncImage(url: URL(string: item.thumb)) { phase in
switch phase {
case .empty:
ProgressView()
case .success(let image):
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 370, height: 180)
.clipped()
default:
Image("ph_img_big")
.resizable()
.aspectRatio(contentMode: .fill)
.clipped()
}
}
.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)
}
}
}
}
}
}
}
extension SearchView {
struct SearchBar: View {
@State private var isSearching = false
@Binding<String> var searchText: String
var placeholder: String
var onTap: () -> Void
var body: some View {
//
HStack(alignment: .center, spacing: 8) {
TextField(placeholder, text: $searchText)
.padding(8)
.padding(.horizontal, 30)
.background(Color(.systemGray6))
.cornerRadius(8)
.overlay(
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.gray)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 8)
if isSearching {
Button(action: {
searchText = ""
}) {
Image(systemName: "multiply.circle.fill")
.foregroundColor(.gray)
.padding(.trailing, 8)
}
}
}
)
.frame(maxWidth: .infinity)
.onChange(of: searchText) {
if searchText.trimmingCharacters(in: .whitespaces).isEmpty {
isSearching = false
} else {
isSearching = true
}
}
Button {
onTap()
} label: {
Text("搜索")
.font(.system(size: 18))
.foregroundColor(.gray)
}
.padding(.trailing, 10)
}
}
}
}
@Observable
final class SearchModel {
struct DramaGroup: Codable {
struct Item: Codable {
let id: Int
let name: String
let time: Int
let thumb: String
let status: String
}
let group_id: String
let group_name: String
let items: [Item]
}
var dramaGroups: [DramaGroup] = []
@MainActor
func search(userId: String, name: String) async {
guard let encodeName = name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
self.dramaGroups = []
return
}
let response = await API.searchDrama(userId: userId, name: encodeName, as: [DramaGroup].self)
switch response {
case .result(let dramaGroups):
self.dramaGroups = dramaGroups
case .error(let int32, let string):
print("error_code: \(int32), message: \(string)")
self.dramaGroups = []
}
}
}