From d95c35992f3af05cc2de390224f7ff6a06c4d331 Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Tue, 1 Apr 2025 20:17:50 +0800 Subject: [PATCH] add search view --- dimensionhub/Core/API.swift | 7 + dimensionhub/Views/SearchView.swift | 207 +++++++++++++++++++++++++++- 2 files changed, 213 insertions(+), 1 deletion(-) diff --git a/dimensionhub/Core/API.swift b/dimensionhub/Core/API.swift index 41470e6..081e71d 100644 --- a/dimensionhub/Core/API.swift +++ b/dimensionhub/Core/API.swift @@ -82,6 +82,13 @@ struct API { return await doRequest(request: request, as: T.self) } + // 传入的时候,name参数是经过url_encode逻辑处理过的 + static func searchDrama(userId: String, name: String, as: T.Type) async -> APIResponse { + 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(request: URLRequest, as: T.Type) async -> APIResponse { do { diff --git a/dimensionhub/Views/SearchView.swift b/dimensionhub/Views/SearchView.swift index 658d288..c1f1634 100644 --- a/dimensionhub/Views/SearchView.swift +++ b/dimensionhub/Views/SearchView.swift @@ -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 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 = [] + } } }