230 lines
9.0 KiB
Swift
230 lines
9.0 KiB
Swift
//
|
||
// 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()
|
||
}
|