This commit is contained in:
anlicheng 2025-04-08 17:22:04 +08:00
parent cac31802b3
commit 22b74a9b36
6 changed files with 330 additions and 286 deletions

View File

@ -0,0 +1,79 @@
//
// SearchBar.swift
// dimensionhub
//
// Created by on 2025/4/8.
//
import SwiftUI
//
struct SearchBar: View {
@State private var isSearching = false
@Binding<String> var searchText: String
var placeholder: String
var onSearch: () -> Void
@FocusState private var isFocused
var body: some View {
//
HStack(alignment: .center, spacing: 8) {
//
TextField(placeholder, text: $searchText)
.textFieldStyle(PlainTextFieldStyle())
.keyboardType(.default)
.focused($isFocused)
.submitLabel(.search)
.onSubmit {
onSearch()
}
.contentShape(Rectangle())
.padding(8)
.background(Color(.systemGray6))
.cornerRadius(8)
.overlay(alignment: .trailing) {
HStack {
if isSearching {
Button(action: {
searchText = ""
}) {
Image(systemName: "multiply.circle.fill")
.foregroundColor(.gray)
.padding(.trailing, 8)
}
}
}
}
.frame(maxWidth: .infinity)
//
Button {
onSearch()
} label: {
Text("搜索")
.font(.system(size: 18))
.foregroundColor(.gray)
}
.padding(.trailing, 10)
.disabled(searchText.isEmpty)
}
.onChange(of: searchText) {
if searchText.trimmingCharacters(in: .whitespaces).isEmpty {
isSearching = false
} else {
isSearching = true
}
}
.onAppear {
DispatchQueue.main.async {
isFocused = true
}
}
}
}
//#Preview {
// SearchBar()
//}

View File

@ -0,0 +1,64 @@
//
// SearchDramaGroupView.swift
// dimensionhub
//
// Created by on 2025/4/8.
//
import SwiftUI
//
struct SearchDramaGroupView: 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(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)
}
}
}
}
}
}
//#Preview {
// SearchDramaGroupView()
//}

View File

@ -0,0 +1,54 @@
//
// SearchModel.swift
// dimensionhub
//
// Created by on 2025/4/8.
//
import Foundation
import Observation
@Observable
final class SearchModel {
var dramaGroups: [DramaGroup] = []
//
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]
}
//
func search(userId: String, name: String) async {
guard let encodeName = name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
await MainActor.run {
self.dramaGroups = []
}
return
}
let response = await API.searchDrama(userId: userId, name: encodeName, as: [DramaGroup].self)
switch response {
case .result(let dramaGroups):
await MainActor.run {
self.dramaGroups = dramaGroups
}
case .error(let int32, let string):
print("error_code: \(int32), message: \(string)")
await MainActor.run {
self.dramaGroups = []
}
}
}
}

View File

@ -0,0 +1,133 @@
//
// SearchView.swift
// dimensionhub
//
// Created by on 2025/4/1.
//
import SwiftUI
import SwiftData
struct SearchView: View {
@Environment(\.modelContext) var modelContext
@State var showHistoryNum: Int = 2
@AppStorage("userId") private var userId: String = Utils.defaultUserId()
@Environment(\.dismiss) var dismiss
@State var searchText: String = ""
@State var searchModel = SearchModel()
var body: some View {
VStack {
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: "搜索...") {
let trimmedSearchText = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmedSearchText.isEmpty {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
Task.detached {
await searchModel.search(userId: userId, name: trimmedSearchText)
}
// let historyModel = SearchHistory(keyword: trimmedSearchText, timestamp: Date())
// modelContext.insert(historyModel)
}
}
}
.frame(height: 50)
.padding([.top, .bottom], 8)
ScrollView(.vertical, showsIndicators: false) {
//
LazyVStack(alignment: .center, spacing: 10) {
ForEach(searchModel.dramaGroups, id: \.group_id) { group in
SearchDramaGroupView(group: group)
}
}
}
Spacer()
}
.navigationTitle("")
.navigationBarBackButtonHidden(true)
.padding(8)
.ignoresSafeArea(edges: .bottom)
}
}
extension SearchView {
struct SearchHistoryView1: View {
@Query(sort: \SearchHistory.timestamp, order: .reverse) private var historyItems: [SearchHistory]
var body: some View {
VStack {
if historyItems.count > 5 {
ForEach(Array(0..<5), id: \.self) { idx in
SearchHistoryItemView(history: historyItems[idx]) {
print("click me")
}
}
Button {
//self.showHistoryNum = 5
} label: {
Text("查看更多历史")
.font(.system(size: 16))
.foregroundColor(.gray)
}
.buttonStyle(.plain)
} else {
ForEach(historyItems) { history in
SearchHistoryItemView(history: history) {
print("click me")
}
}
}
}
}
}
//
struct SearchHistoryItemView: View {
let history: SearchHistory
var onClick: () -> Void
var body: some View {
HStack(alignment: .center, spacing: 20) {
Image("lost_network")
.resizable()
.clipShape(Circle())
.clipped()
.frame(width: 25, height: 25)
Text(history.keyword)
.font(.system(size: 16))
.lineLimit(1)
.frame(width: 280, alignment: .leading)
Button(action: {
onClick()
}) {
Image(systemName: "xmark")
.font(.system(size: 14))
.foregroundColor(.gray)
}
}
}
}
}
#Preview {
SearchView()
}

View File

@ -1,286 +0,0 @@
//
// SearchView.swift
// dimensionhub
//
// Created by on 2025/4/1.
//
import SwiftUI
import SwiftData
struct SearchView: View {
@Environment(\.modelContext) var modelContext
@State var showHistoryNum: Int = 2
@AppStorage("userId") private var userId: String = Utils.defaultUserId()
@Environment(\.dismiss) var dismiss
@State var searchText: String = ""
//
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]
}
@State var dramaGroups: [DramaGroup] = []
var body: some View {
VStack {
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: "搜索...") {
let trimmedSearchText = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmedSearchText.isEmpty {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
Task {
let dramaGroups = await self.search(userId: userId, name: trimmedSearchText)
DispatchQueue.main.async {
self.dramaGroups = dramaGroups
}
}
// let historyModel = SearchHistory(keyword: trimmedSearchText, timestamp: Date())
// modelContext.insert(historyModel)
}
}
}
.frame(height: 50)
.padding([.top, .bottom], 8)
ScrollView(.vertical, showsIndicators: false) {
//
LazyVStack(alignment: .center, spacing: 10) {
ForEach(dramaGroups, id: \.group_id) { group in
DramaGroupView(group: group)
}
}
}
Spacer()
}
.navigationTitle("")
.navigationBarBackButtonHidden(true)
.padding(8)
.ignoresSafeArea(edges: .bottom)
}
//
func search(userId: String, name: String) async -> [DramaGroup] {
guard let encodeName = name.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
return []
}
let response = await API.searchDrama(userId: userId, name: encodeName, as: [DramaGroup].self)
switch response {
case .result(let dramaGroups):
return dramaGroups
case .error(let int32, let string):
print("error_code: \(int32), message: \(string)")
return []
}
}
}
extension SearchView {
//
struct DramaGroupView: View {
let group: 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(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)
}
}
}
}
}
}
//
struct SearchBar: View {
@State private var isSearching = false
@Binding<String> var searchText: String
var placeholder: String
var onSearch: () -> Void
@FocusState private var isFocused
var body: some View {
//
HStack(alignment: .center, spacing: 8) {
//
TextField(placeholder, text: $searchText)
.textFieldStyle(PlainTextFieldStyle())
.keyboardType(.default)
.focused($isFocused)
.submitLabel(.search)
.onSubmit {
onSearch()
}
.contentShape(Rectangle())
.padding(8)
.background(Color(.systemGray6))
.cornerRadius(8)
.overlay(alignment: .trailing) {
HStack {
if isSearching {
Button(action: {
searchText = ""
}) {
Image(systemName: "multiply.circle.fill")
.foregroundColor(.gray)
.padding(.trailing, 8)
}
}
}
}
.frame(maxWidth: .infinity)
//
Button {
onSearch()
} label: {
Text("搜索")
.font(.system(size: 18))
.foregroundColor(.gray)
}
.padding(.trailing, 10)
.disabled(searchText.isEmpty)
}
.onChange(of: searchText) {
if searchText.trimmingCharacters(in: .whitespaces).isEmpty {
isSearching = false
} else {
isSearching = true
}
}
.onAppear {
DispatchQueue.main.async {
isFocused = true
}
}
}
}
struct SearchHistoryView1: View {
@Query(sort: \SearchHistory.timestamp, order: .reverse) private var historyItems: [SearchHistory]
var body: some View {
VStack {
if historyItems.count > 5 {
ForEach(Array(0..<5), id: \.self) { idx in
SearchHistoryItemView(history: historyItems[idx]) {
print("click me")
}
}
Button {
//self.showHistoryNum = 5
} label: {
Text("查看更多历史")
.font(.system(size: 16))
.foregroundColor(.gray)
}
.buttonStyle(.plain)
} else {
ForEach(historyItems) { history in
SearchHistoryItemView(history: history) {
print("click me")
}
}
}
}
}
}
//
struct SearchHistoryItemView: View {
let history: SearchHistory
var onClick: () -> Void
var body: some View {
HStack(alignment: .center, spacing: 20) {
Image("lost_network")
.resizable()
.clipShape(Circle())
.clipped()
.frame(width: 25, height: 25)
Text(history.keyword)
.font(.system(size: 16))
.lineLimit(1)
.frame(width: 280, alignment: .leading)
Button(action: {
onClick()
}) {
Image(systemName: "xmark")
.font(.system(size: 14))
.foregroundColor(.gray)
}
}
}
}
}
#Preview {
SearchView()
}