Initial commit

This commit is contained in:
oscarz
2024-08-12 10:49:20 +08:00
parent 3002510aaf
commit 00fd0adf89
331 changed files with 53210 additions and 130 deletions

View File

@ -6,13 +6,381 @@
//
import SwiftUI
import AVFoundation
import ToastUI
struct TranslateView: View {
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
struct SubmitTextEditor: UIViewRepresentable {
@Binding var text: String
var onCommit: () -> Void
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.delegate = context.coordinator
textView.font = UIFont.preferredFont(forTextStyle: .body)
textView.isScrollEnabled = true
textView.returnKeyType = .send //
textView.enablesReturnKeyAutomatically = true
textView.backgroundColor = UIColor.white.withAlphaComponent(0.5) //
textView.layer.cornerRadius = 10 //
textView.layer.borderColor = UIColor.blue.cgColor //
textView.layer.borderWidth = 1 //
return textView
}
func updateUIView(_ textView: UITextView, context: Context) {
textView.text = text
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextViewDelegate {
var parent: SubmitTextEditor
init(_ parent: SubmitTextEditor) {
self.parent = parent
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text == "\n" { //
parent.onCommit()
textView.resignFirstResponder() //
return false
}
return true
}
func textViewDidChange(_ textView: UITextView) {
parent.text = textView.text
}
}
}
struct TranslateView: View {
@EnvironmentObject var globalEnv: GlobalEnvironment //
@State private var inputText = ""
@State private var translations: [Translation] = [
Translation(input: "This is demo text", translation: "这是演示文本"),
]
@State private var showMenu = false //
@FocusState private var isInputFocused: Bool // TextEditor
@State private var currentLang = "Chinese" //
@State private var langCode = "chs" //
@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 {
ScrollViewReader { scrollView in
ScrollView {
VStack(spacing: 10) {
ForEach(translations) { translation in
TranslateCardView(translation: translation, onEdit: onEdit, onCopy: onCopy, onFeedback: onFeedBack)
.id(translation.id) // Assign an ID to each card view
}
}
.padding()
}
.onChange(of: translations) { _ in
if let lastId = translations.last?.id {
withAnimation {
scrollView.scrollTo(lastId, anchor: .bottom) // Scroll to the last translation
}
}
}
}
SubmitTextEditor(text: $inputText, onCommit: {
//
let trimmedText = inputText.trimmingCharacters(in: .whitespacesAndNewlines)
//
if trimmedText.isEmpty {
showingToast = true
toastText = "Please enter some text."
return //
}
// 200
if trimmedText.count > globalEnvironment.MaxLenTranslate {
showingToast = true
toastText = "Input too long. Please re-enter the text."
return //
}
//
/*
if trimmedText.range(of: "[^a-zA-Z0-9 .,;:!?'\"@-]", options: .regularExpression) != nil {
showingToast = true
toastText = "Please enter valid characters."
return //
}*/
if currentLang == "Chinese" {
let checkRes = CommonFunc.shared.validateInputEng(input: trimmedText)
if !checkRes.isValid {
showingToast = true
toastText = checkRes.message
return
}
} else {
let checkRes = CommonFunc.shared.validateChineseInput(input: trimmedText)
if !checkRes.isValid {
showingToast = true
toastText = checkRes.message
return
}
}
loadData()
NetworkManager.shared.translate(inputText: trimmedText, lang: langCode) { result in
switch result {
case .success(let translation):
addTranslation(output: translation.translation)
logger.info("translate succ.", context: ["input": inputText, "output":translation.translation, "lang":langCode])
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()
}
})
.frame(height: 100) //
.padding()
.focused($isInputFocused)
}
.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("AI Translate", displayMode: .inline)
.navigationBarItems(trailing: Menu(currentLang) {
Button("English -> Chinese") {
currentLang = "Chinese"
langCode = "chs"
}
Button("Chinese -> English") {
currentLang = "English"
langCode = "eng"
}
})
}
}
//
func loadData() {
isLoading = true
}
func loadComplete(){
isLoading = false
}
private func onEdit(text : String){
self.inputText = text
self.isInputFocused = true
}
private func onCopy(text : String){
UIPasteboard.general.string = text
}
private func onFeedBack( feedback: TranslateFeedback){
let fb:Bool = feedback.good ? true : false
NetworkManager.shared.sendFeedback(input: feedback.input, output: feedback.translation, isPositive: fb)
}
private func addTranslation(output: String) {
let newTranslation = Translation(input: inputText, translation: output)
translations.append(newTranslation)
if translations.count > 10 {
translations.removeFirst()
}
inputText = "" //
}
private func hideKeyboard() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
/*
struct Translation: Identifiable, Equatable {
let id = UUID()
var input: String
var translation: String
}
*/
struct TranslateFeedback {
var input: String
var translation: String
var good: Bool = false
var bad: Bool = false
}
struct TranslateCardView: View {
var translation: Translation
var onEdit: (String) -> Void
var onCopy: (String) -> Void
var onFeedback: (TranslateFeedback) -> Void
let synthesizer = AVSpeechSynthesizer()
var body: some View {
VStack {
//
VStack(alignment: .leading) {
Text("Input")
.font(.system(size: UIFont.systemFontSize * 0.8))
.foregroundColor(Color.black.opacity(0.6))
.padding(.top, 0) //
.padding(.bottom,3) //
Text(translation.input)
.padding(.bottom, 3)
HStack {
/*
Button(action: {
speakText(translation.input)
}) {
Image(systemName: "speaker.wave.2")
.foregroundColor(.blue)
.padding(.trailing)
}
*/
Spacer()
Button(action: {
onEdit(translation.input)
}) {
Image(systemName: "square.and.pencil")
.foregroundColor(.gray)
.frame(width: 15, height: 15) //
}
}
}
.padding([.horizontal, .top])
.padding(.bottom, 3) //
Divider().dashed()
//
VStack(alignment: .leading) {
Text(translation.translation)
.padding(.vertical, 1) // 线
.padding(.bottom, 3) //
HStack {
/*
Button(action: {
speakText(translation.translation)
}) {
Image(systemName: "speaker.wave.2")
.foregroundColor(.blue)
}
*/
Spacer()
HStack(spacing: 15) {//
Button(action: {
onCopy(translation.translation)
}) {
Image(systemName: "doc.on.doc")
.resizable()
.scaledToFit()
.frame(width: 15, height: 15) //
}
Button(action: {
onFeedback(TranslateFeedback(input: translation.input, translation: translation.translation, good: true))
}) {
Image(systemName: "hand.thumbsup")
.resizable()
.scaledToFit()
.frame(width: 15, height: 15) //
}
Button(action: {
onFeedback(TranslateFeedback(input: translation.input, translation: translation.translation, bad: true))
}) {
Image(systemName: "hand.thumbsdown")
.resizable()
.scaledToFit()
.frame(width: 15, height: 15) //
}
}
}
.padding(.bottom, 1) //
}
.padding()
}
.padding(0)
.background(Color.gray.opacity(0.2)) // LightGray
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.gray, lineWidth: 1))
}
func speakText(_ text: String) {
let utterance = AVSpeechUtterance(string: text)
utterance.voice = AVSpeechSynthesisVoice(language: "en-US")
synthesizer.speak(utterance)
}
}
extension View {
func dashed() -> some View {
self.overlay(
Rectangle()
.fill(Color.clear)
//.border( StrokeStyle(lineWidth: 1, dash: [5]))
)
}
}
#Preview {
TranslateView()
}