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,86 @@
//
import SwiftUI
import UIKit
import Vision
struct CameraView: View {
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
func performOCR(on uiImage: UIImage, completion: @escaping (String) -> Void) {
guard let cgImage = uiImage.cgImage else { return }
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
let request = VNRecognizeTextRequest { (request, error) in
guard let observations = request.results as? [VNRecognizedTextObservation] else { return }
let recognizedStrings = observations.compactMap { $0.topCandidates(1).first?.string }
completion(recognizedStrings.joined(separator: "\n"))
}
request.recognitionLanguages = ["en-US", "zh-Hans"] //
request.usesLanguageCorrection = true
do {
try handler.perform([request])
} catch {
print("OCR失败: \(error)")
}
}
#Preview {
CameraView()
struct CameraView: UIViewControllerRepresentable {
@Binding var textInput: String
@Environment(\.presentationMode) var presentationMode
func makeUIViewController(context: Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
picker.sourceType = .camera
picker.allowsEditing = true //
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
var parent: CameraView
init(_ parent: CameraView) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
//
if let editedImage = info[.editedImage] as? UIImage {
// 使OCR
performOCR(on: editedImage) { recognizedText in
//
self.parent.textInput = recognizedText
self.parent.presentationMode.wrappedValue.dismiss()
}
} else if let originalImage = info[.originalImage] as? UIImage {
// 退使
performOCR(on: originalImage) { recognizedText in
self.parent.textInput = recognizedText
self.parent.presentationMode.wrappedValue.dismiss()
}
} else {
//
self.parent.presentationMode.wrappedValue.dismiss()
}
}
func imagePickerController2(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let uiImage = info[.originalImage] as? UIImage {
// OCR
performOCR(on: uiImage) { recognizedText in
//
self.parent.textInput = recognizedText
}
}
parent.presentationMode.wrappedValue.dismiss()
}
}
}

View File

@ -6,13 +6,197 @@
//
import SwiftUI
import StoreKit
struct IAPTestView: View {
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
enum IAPProductTest: String, CaseIterable {
case premiumFeature1 = "grammar_1_month"
case premiumFeature2 = "grammar_1_week"
}
class IAPManagerTest: ObservableObject {
@Published var products: [Product] = []
@Published var purchasedProducts: [Product] = []
init() {
Task {
await requestProducts()
await updatePurchasedProducts()
await listenForTransactions()
}
}
func requestProducts() async {
do {
let products = try await Product.products(for: IAPProductTest.allCases.map { $0.rawValue })
DispatchQueue.main.async {
//
self.products = products
//
for product in self.products {
print("--------------------------")
print("Product ID: \(product.id)")
print("Product Title: \(product.displayName)")
print("Product Description: \(product.description)")
print("Product Price: \(product.price)")
print("Product displayPrice: \(product.displayPrice)")
print("Product priceFormatStyle: \(product.priceFormatStyle)")
print("Product subscriptionPeriodFormatStyle: \(product.subscriptionPeriodFormatStyle)")
print("Product subscriptionPeriodUnitFormatStyle: \(product.subscriptionPeriodUnitFormatStyle)")
print("--------------------------")
}
}
} catch {
print("Failed to fetch products: \(error.localizedDescription)")
}
}
func buy(product: Product) async {
do {
let uuid = UUID()
let token = Product.PurchaseOption.appAccountToken(uuid)
print("purchase appAccountToken: \(uuid.uuidString)")
let result = try await product.purchase(options: [token])
switch result {
case .success(let verification):
if case .verified(let transaction) = verification {
// listenForTransactions
//
print("Purchase initiated for product: \(product.id)")
}
case .userCancelled:
print("User cancelled the purchase.")
case .pending:
print("Purchase is pending.")
default:
break
}
} catch {
print("Failed to purchase product: \(error.localizedDescription)")
}
}
func updatePurchasedProducts() async {
for await result in Transaction.currentEntitlements {
if case .verified(let transaction) = result {
if let product = products.first(where: { $0.id == transaction.productID }) {
DispatchQueue.main.async {
self.purchasedProducts.append(product)
}
}
}
}
}
func listenForTransactions() async {
for await transactionResult in Transaction.updates {
if case .verified(let transaction) = transactionResult {
if let product = products.first(where: { $0.id == transaction.productID }) {
DispatchQueue.main.async {
self.purchasedProducts.append(product)
}
}
//
print("Transaction ID: \(transaction.id)")
print("Product ID: \(transaction.productID)")
print("Purchase Date: \(transaction.purchaseDate)")
//print("Transaction State: \(transaction.revocationReason ?? "None")")
//print("Original Transaction ID: \(transaction.originalID ?? "None")")
//
// jsonRepresentation
let jsonData = try transaction.jsonRepresentation
// Data String
if let jsonString = String(data: jsonData, encoding: .utf8) {
print("Transaction Receipt: \(jsonString)")
} else {
print("Failed to convert JSON data to string.")
}
await transaction.finish()
}
}
}
func restorePurchases() async {
do {
try await AppStore.sync()
await updatePurchasedProducts()
print("Purchases restored")
} catch {
print("Failed to restore purchases: \(error.localizedDescription)")
}
}
}
struct IAPTestView: View {
@StateObject var iapManager = IAPManager()
//@StateObject var iapManager = IAPManagerTest()
var body: some View {
VStack(spacing: 20) {
if iapManager.products.isEmpty {
Text("Loading products...")
} else {
ForEach(iapManager.products, id: \.id) { product in
VStack {
Text(product.displayName)
.font(.title)
Button("Buy \(product.displayName)") {
Task {
await iapManager.buy(product: product){ result in
switch result {
case .success(let message):
logger.info("succ")
case .failure(let error):
logger.error("error")
}
}
}
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
}
Button("Restore Purchases") {
Task {
await iapManager.restorePurchases(){ result in
switch result {
case .success(let message):
logger.info("restore purchase succ. message: \(message)")
case .failure(let error):
logger.error("restore purchase error. message: \(error)")
}
}
}
}
.padding()
.background(Color.green)
.foregroundColor(.white)
.cornerRadius(10)
}
}
.onAppear {
Task {
//await iapManager.requestProducts()
}
}
}
}
#Preview {
IAPTestView()
}

View File

@ -6,13 +6,224 @@
//
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 {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
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()
InputView_Preview()
}

View File

@ -6,13 +6,201 @@
//
import SwiftUI
import Foundation
// SingleCorrectionCard访
fileprivate struct SingleCorrectionCard: View {
let correction: String
var body: some View {
Text(correction)
.padding(.vertical, 6)
.font(.system(size: UIFontMetrics.default.scaledValue(for: 15) * 4 / 5))
.background(Color.yellow.opacity(0.5))
.cornerRadius(5)
}
}
struct GrammarDetailsCardView: View {
let res: GrammarRes
var body: some View {
// 线
Divider()
.background(Color.yellow)
.frame(height: 1)
.overlay(
Rectangle()
.stroke(style: StrokeStyle(lineWidth: 1, dash: [5]))
.foregroundColor(.yellow)
)
.padding(3)
VStack {
Text("Error: \(res.plain)")
.frame(maxWidth: .infinity, alignment: .leading) //
.padding(.vertical, 8) // 2/3
.background(Color.gray.opacity(0.5)) // Gray
.foregroundColor(.black)
.cornerRadius(5)
Text("Reason: \(res.reason)")
.frame(maxWidth: .infinity, alignment: .leading) //
Text("Correction:")
.frame(maxWidth: .infinity, alignment: .leading) //
.padding(.top, 6) // Correction
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(Array(res.correction.enumerated()), id: \.offset) { index, correction in
SingleCorrectionCard(correction: correction)
}
}
}
}
.padding(.horizontal, 3) //
.padding(.bottom, 3) //
.background(Color.gray.opacity(0.2)) // LightGray
.cornerRadius(10)
}
}
struct ResultView: View {
//
@Binding var textContent: String
@Binding var results: [GrammarRes]
@Binding var showResult : Bool
@Binding var showKeyboard : Bool
@State private var selectedCardIndex: Int? = 0
@State private var textRes = AttributedString()
@State private var resContent: String = String("")
func getColoredText(str : String, type : String, index : Int?) -> AttributedString {
var text = AttributedString( str )
if(type == GrammarResType.ok.rawValue){
return text
}
if (type == GrammarResType.grammar.rawValue ){
text.foregroundColor = .red
}else
{
text.backgroundColor = .yellow
}
text.link = URL(string: String(index!))
return text
}
func styledText() -> Text {
var outstr = AttributedString()
var index = 0
let substr: String = textContent
var currentIndex = substr.startIndex
for res in results {
if let range = substr.range(of: res.plain, range: currentIndex..<substr.endIndex){
let tmpABStr = AttributedString(String(substr[currentIndex..<range.lowerBound]))
outstr.append(tmpABStr)
outstr.append(getColoredText(str: res.plain, type: res.type, index: index))
index = index + 1
currentIndex = range.upperBound
}
}
let tmpABStr = AttributedString(String(substr[currentIndex..<substr.endIndex]))
outstr.append(tmpABStr)
return Text(outstr)
}
/*
func styledText() -> Text {
var outstr = AttributedString()
var index = 0
for res in results {
let tmp = getColoredText(str: res.plain, type: res.type, index: index)
outstr.append(tmp)
index = index + 1
}
return Text(outstr)
}
*/
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
VStack {
// styledText
ScrollView {
styledText()
.padding()
.environment(\.openURL, OpenURLAction { url in
let path = url.absoluteString
print("index: \(path)")
self.selectedCardIndex = Int(path)
return .handled
})
Spacer() // 使SpacerText
}
.onTapGesture {
self.showResult = false
self.showKeyboard = true
}
.background(Color.white) // Gray
.frame(maxHeight: .infinity) // styledText
//index
ScrollViewReader { scrollView in
ScrollView {
LazyVStack {
ForEach(Array(results.enumerated()), id: \.offset) { index, res in
if(res.type != GrammarResType.ok.rawValue){
GrammarDetailsCardView(res: res)
.id(index) // CardViewID
}
}
}
}
.onChange(of: selectedCardIndex) { newIndex in
if let newIndex = newIndex {
withAnimation {
scrollView.scrollTo(newIndex, anchor: .top)
}
}
}
}
.frame(maxHeight: .infinity) // ErrorCard */
}
.padding() // padding
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color.white) //
)
.padding(5)
}
}
struct ResultView_Preview: View {
@State var input : String = "this is demo text"
@State var results : [GrammarRes]
@State var showResult : Bool = true
@State var showKeyboard : Bool = true
init() {
let demoGrammarData = GrammarData.demoInstance()
self.input = demoGrammarData.inputText
self.results = demoGrammarData.results
}
var body: some View {
ResultView(textContent: $input, results: $results, showResult: $showResult, showKeyboard: $showKeyboard)
}
}
#Preview {
ResultView()
ResultView_Preview()
}

View File

@ -7,12 +7,197 @@
import SwiftUI
struct RichText: View {
var text1: AttributedString {
var text = AttributedString(localized:"登录即表示同意")
text.foregroundColor = .gray
return text
}
var text2: AttributedString {
var text = AttributedString(localized:"用户协议")
text.link = URL(string: "111")
text.foregroundColor = .red
return text
}
var text3: AttributedString {
var text = AttributedString(localized:"")
text.foregroundColor = .gray
return text
}
var text4: AttributedString {
var text = AttributedString(localized:"隐私协议")
text.link = URL(string: "222")
text.foregroundColor = .red
return text
}
var text: AttributedString {
text1 + text2 + text3 + text4
}
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
VStack {
Text(text)
.environment(\.openURL, OpenURLAction { url in
let path = url.absoluteString
if path.hasPrefix("111") {
print("111...")
} else if path.hasPrefix("222") {
print("222...")
}
return .handled
})
Text("Device ID: \(globalEnvironment.deviceID)")
}
}
}
/*
import UIKit
import SwiftUI
class RichTextViewController: UIViewController, UITextViewDelegate {
var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// UITextView
textView = UITextView(frame: self.view.bounds)
textView.delegate = self
//
textView.isEditable = false
textView.isSelectable = true
//
let attributedString = NSMutableAttributedString(string: "")
//
let linkAttributes: [NSAttributedString.Key: Any] = [
.link: URL(string: "http://baidu.com")!, // 使URL scheme
.foregroundColor: UIColor.blue
]
attributedString.setAttributes(linkAttributes, range: NSRange(location: 0, length: 4)) // ""
textView.attributedText = attributedString
// UITextView
self.view.addSubview(textView)
}
//
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
print("")
print(URL.scheme as Any)
if URL.scheme == "http" {
//
return false // falseURL
}
return true
}
}
struct RichTextView: UIViewRepresentable {
func makeUIView(context: Context) -> UITextView {
// RichTextViewController textView
// textView 使
let controller = RichTextViewController()
controller.loadViewIfNeeded() // textView
return controller.textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
// UI
}
}
struct RichText: View {
var body: some View {
VStack{
Text(" ")
// 使 RichTextView
RichTextView()
.frame(maxHeight: .infinity) //
//.edgesIgnoringSafeArea(.all) //
}
}
}
*/
/*
import SwiftUI
import UIKit
struct AttributedText: UIViewRepresentable {
var attributedString: NSAttributedString
func makeUIView(context: Context) -> UILabel {
let label = UILabel()
label.numberOfLines = 0 //
label.attributedText = attributedString
//
let tapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.labelTapped(_:)))
label.addGestureRecognizer(tapGesture)
label.isUserInteractionEnabled = true
return label
}
func updateUIView(_ uiView: UILabel, context: Context) {
//
uiView.attributedText = attributedString
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject {
var parent: AttributedText
init(_ parent: AttributedText) {
self.parent = parent
}
@objc func labelTapped(_ sender: UITapGestureRecognizer) {
//
// 使UILabel
print("Label was tapped")
}
}
}
struct RichText: View {
var body: some View {
// 使AttributedText
AttributedText(attributedString: attributedString)
}
var attributedString: NSAttributedString {
let fullString = NSMutableAttributedString(string: "Tap on ")
let clickablePart = NSAttributedString(string: "this text", attributes: [
.foregroundColor: UIColor.blue,
.underlineStyle: NSUnderlineStyle.single.rawValue
])
fullString.append(clickablePart)
fullString.append(NSAttributedString(string: " to see action."))
return fullString
}
}
*/
#Preview {
RichText()
}

View File

@ -6,13 +6,16 @@
//
import SwiftUI
import UIKit
struct ShareSheet: View {
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
struct ShareSheet: UIViewControllerRepresentable {
var itemsToShare: [Any]
func makeUIViewController(context: Context) -> UIActivityViewController {
let controller = UIActivityViewController(activityItems: itemsToShare, applicationActivities: nil)
return controller
}
func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {}
}
#Preview {
ShareSheet()
}