// // InputView.swift // AIGrammar // // Created by oscar on 2024/4/1. // import SwiftUI import ToastUI fileprivate struct ProgressBar: View { @Binding var value: Float var body: some View { GeometryReader { geometry in ZStack(alignment: .leading) { Rectangle() .foregroundColor(.gray.opacity(0.3)) .frame(width: geometry.size.width, height: geometry.size.height) Rectangle() .foregroundColor(Color.green) .frame(width: min(CGFloat(self.value) * geometry.size.width, geometry.size.width), height: geometry.size.height) .animation(.linear, value: value) } .cornerRadius(45.0) } } } struct InputView: View { @Binding var textInput: String @Binding var progressValue: Float @Binding var showKeyboard: Bool @Binding var showBuyProView: Bool @Binding var showResult: Bool @Binding var results : [GrammarRes] @Binding var isLoading: Bool // 控制加载指示器的显示 @Binding var showingToast: Bool // 控制是否显示toast @Binding var toastText: String @FocusState private var isTextEditorFocused: Bool var body: some View { VStack { // TextEditor设计成无边框形式 TextEditor(text: $textInput) .onTapGesture { // 用户点击TextEditor时更新状态 self.showKeyboard = true // 显示键盘 self.showBuyProView = false // 隐藏BuyProView } .focused($isTextEditorFocused) .padding(5) .background(Color.white) .cornerRadius(5) .padding(5) .onAppear { if showKeyboard { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.isTextEditorFocused = true } } } //Divider() // 添加分割线,区分TextEditor和下方控件 // 进度条和按钮 ProgressBar(value: $progressValue).frame(height: 3) HStack { Button("Clear") { textInput = "" progressValue = 0 } Spacer() Button("Check") { // 清除输入内容前后的空格 let trimmedText = textInput.trimmingCharacters(in: .whitespacesAndNewlines) // 检查是否为空 if trimmedText.isEmpty { showingToast = true toastText = "Please enter some text." return // 提前返回,不执行网络请求 } // 检查长度是否超过200个字符 let MaxLen = globalEnvironment.isVip ? globalEnvironment.MaxLenGrammarCheckVIP : globalEnvironment.MaxLenGrammarCheckFree if trimmedText.count > MaxLen { showingToast = true toastText = "Input too long. Please re-enter the text." logger.info("input too lang, inputlen: \(trimmedText.count), maxlen: \(MaxLen), vip: \(globalEnvironment.isVip)") return // 提前返回,不执行网络请求 } // 检查是否含有非法字符 let checkRes = CommonFunc.shared.validateInputEng(input: trimmedText) if !checkRes.isValid { showingToast = true toastText = checkRes.message return } /* if trimmedText.range(of: "[^a-zA-Z0-9 .,;:!?'\"@-]", options: .regularExpression) != nil { showingToast = true toastText = "Please enter valid characters." return // 提前返回,不执行网络请求 } */ loadData() // 提交的显示 // Send the request to the server NetworkManager.shared.checkGrammar(inputText: trimmedText) { result in switch result { case .success(let results): // Update the main UI with the results loadComplete() DispatchQueue.main.async { self.results = results self.showResult = true self.showKeyboard = false self.showBuyProView = false hideKeyboard() logger.info("grammar check succ.") } case .failure(let error): loadComplete() 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.GrammarCheckOK: toastText = globalEnvironment.GrammarOKToast 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 } } } } } .padding(.vertical, 10) .padding(.horizontal, 40) .background(Color.green) .foregroundColor(.white) .cornerRadius(5) .font(.subheadline) // 调整字体大小为标题大小 } .padding(.bottom) } .padding() // 为所有内容添加padding .background( RoundedRectangle(cornerRadius: 10) .fill(Color.white) // 设置背景色为白色 ) .padding(5) .onTapGesture { // 点击背景时隐藏键盘 self.isTextEditorFocused = false showBuyProView = true } } // 模拟加载数据 func loadData() { isLoading = true } func loadComplete(){ isLoading = false } // 用于外部调用的方法来控制焦点 func toggleFocus() { isTextEditorFocused.toggle() } // 函数用于收起键盘 private func hideKeyboard() { UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } } struct InputView_Preview: View{ @State private var textInput: String @State private var progressValue: Float = 0 @State private var showKeyboard: Bool = false @State private var showBuyProView: Bool = true @State private var showResult : Bool = false @State private var results : [GrammarRes] // 提交等待,错误提示等 @State private var isLoading = false // 控制加载指示器的显示 @State private var showingToast = false // 控制是否显示toast @State private var toastText = "" init(){ let demoGrammarData = GrammarData.demoInstance() self.textInput = demoGrammarData.inputText self.results = demoGrammarData.results } var body: some View { VStack { InputView(textInput: $textInput, progressValue: $progressValue, showKeyboard: $showKeyboard, showBuyProView: $showBuyProView, showResult: $showResult, results: $results, isLoading: $isLoading, showingToast: $showingToast, toastText: $toastText) } } } #Preview { InputView_Preview() }