// // WordsView.swift // AIGrammar // // Created by oscar on 2024/3/27. // import SwiftUI import ToastUI struct WordsView: View { @EnvironmentObject var globalEnv: GlobalEnvironment // 引入环境对象 @State private var searchText = "" @State private var showingResults = false @State private var res = WordDetails() @State private var isLoading = false // 控制加载指示器的显示 @State private var showingToast = false // 控制是否显示toast @State private var toastText = "" var body: some View { NavigationView { ZStack { // 设置背景色 Color.pink.opacity(0.2).edgesIgnoringSafeArea(.all) VStack(spacing: 0) { // 搜索框区域 HStack { TextField("word", text: $searchText, onCommit: fetchWordData) .padding(.horizontal, 40) .padding(.vertical, 10) .background(Color(.systemGray6)) .cornerRadius(8) .font(.headline) .overlay( Image(systemName: "magnifyingglass") .foregroundColor(.gray) .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading).padding(.leading, 8) ) Spacer() Button("Cancel") { self.searchText = "" self.showingResults = false self.hideKeyboard() } } .background(Color(.systemPink).opacity(0.2)) .cornerRadius(5) // 边框圆角 .shadow(radius: 2) // 轻微阴影 .padding(8) // 使用 Spacer 来推动搜索框保持在顶部 if !showingResults { Spacer() } if showingResults { // 结果显示区域 HStack { List { Section(header: Text("Definitions").font(.headline)) { ForEach(res.explanations, id: \.self) { definition in Text(definition) .font(.system(.subheadline)) .padding(.leading, 4) } } Section(header: Text("Common Phrases").font(.headline)) { ForEach(res.phrases, id: \.self) { phrase in Text(phrase) .font(.system(.subheadline)) .padding(.leading, 4) } } Section(header: Text("Synonyms").font(.system(size: UIFont.systemFontSize * 1.2))) { ForEach(res.synonyms, id: \.self) { synonym in Text(synonym) .font(.system(.subheadline)) .padding(.leading, 4) } } } .padding(8) .cornerRadius(5) // 边框圆角 .frame(maxHeight: .infinity) // 限制ErrorCard占用半个屏幕 */ } .listStyle(GroupedListStyle()) // 使用分组列表样式,以适应背景 } } .toast(isPresented: $showingToast, dismissAfter: globalEnvironment.toastPresentMsNormal) { HStack { Image(systemName: "exclamationmark.bubble") .foregroundColor(.yellow) Text(toastText) .foregroundColor(.black) } .padding() .background(Color.white) .cornerRadius(8) .shadow(radius: 10) } // 加载指示器 if isLoading { LoadingView() } } .onTapGesture { // 点击任何非TextField区域时收起键盘 self.hideKeyboard() } .navigationBarTitle("Words", displayMode: .inline) } } private func hideKeyboard() { UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } // 模拟加载数据 func loadData() { isLoading = true } func loadComplete(){ isLoading = false } private func fetchWordData() { // 清除输入内容前后的空格 let trimmedText = searchText.trimmingCharacters(in: .whitespacesAndNewlines) // 检查是否为空 if trimmedText.isEmpty { showingToast = true toastText = "Please enter some text." return // 提前返回,不执行网络请求 } // 检查长度是否超过200个字符 if trimmedText.count > globalEnvironment.MaxLenWords { showingToast = true toastText = "Input too long. Please re-enter the text." return // 提前返回,不执行网络请求 } // 检查是否含有非法字符 let checkRes = CommonFunc.shared.validateInputEng(input: trimmedText, isSingleWord: true) if !checkRes.isValid { showingToast = true toastText = checkRes.message return } /* if trimmedText.range(of: "[^a-zA-Z]", options: .regularExpression) != nil { showingToast = true toastText = "Please enter valid characters." return // 提前返回,不执行网络请求 } */ loadData() // 添加提交的提示 NetworkManager.shared.fetchWordDetails(inputText: trimmedText) { result in switch result { case .success(let wordDetails): DispatchQueue.main.async { res = wordDetails showingResults = true } logger.info("fetch words succ.", context: ["word":wordDetails.word, "explanations":wordDetails.explanations, "phrases":wordDetails.phrases, "synonyms":wordDetails.synonyms]) case .failure(let error): switch error { case .businessError(let ret, let message): // 业务错误,比如无免费可用次数等,需要处理逻辑。 logger.error("Business error - Ret: \(ret), Message: \(message)") DispatchQueue.main.async { showingToast = true switch ret{ case globalEnvironment.RetCodeFreeLimited: toastText = globalEnvironment.FreeLimitedToast case globalEnvironment.RetCodeDirtyInput: toastText = globalEnvironment.DirtyInputErrToast default: toastText = globalEnvironment.OtherServerErrToast } } case .other(let error): logger.error("network error occurred: \(error.localizedDescription)") DispatchQueue.main.async { showingToast = true toastText = globalEnvironment.NetWorkErrToast } } } loadComplete() } } } struct WordsView_Previews: PreviewProvider { static var previews: some View { WordsView() } } #Preview { WordsView() }