From 9b1d4162b5089cfd80dd6959a6447ef390f453ae Mon Sep 17 00:00:00 2001
From: Functionhx <172989722+Functionhx@users.noreply.github.com>
Date: Sat, 3 Oct 2026 21:34:05 -0700
Subject: [PATCH 1/3] Add native iPad writing and handwriting app
---
.github/workflows/build.yml | 21 +
.gitignore | 4 +
README.md | 5 +
WriteBench.xcodeproj/project.pbxproj | 418 +++++++++++++++++-
.../xcschemes/WriteBenchPad.xcscheme | 113 +++++
WriteBench/Persistence/EssaySession.swift | 2 +
.../Services/Backup/BackupService.swift | 6 +-
WriteBench/Services/OCR/OCRService.swift | 5 +
platforms/ipad/Info.plist | 33 ++
platforms/ipad/README.md | 38 ++
.../AppIcon.appiconset/Contents.json | 1 +
.../AppIcon.appiconset/icon.png | Bin 0 -> 312478 bytes
platforms/ipad/WriteBenchPad/PadEditor.swift | 65 +++
.../ipad/WriteBenchPad/PadHandwriting.swift | 37 ++
platforms/ipad/WriteBenchPad/PadLibrary.swift | 144 ++++++
.../ipad/WriteBenchPad/PadSettings.swift | 52 +++
platforms/ipad/WriteBenchPad/PadStore.swift | 110 +++++
.../ipad/WriteBenchPad/PadWorkspace.swift | 263 +++++++++++
.../ipad/WriteBenchPad/WriteBenchPadApp.swift | 21 +
.../ipad/WriteBenchPadTests/PadTests.swift | 68 +++
.../WriteBenchPadUITests/PadUITests.swift | 72 +++
project.yml | 81 ++++
22 files changed, 1555 insertions(+), 4 deletions(-)
create mode 100644 WriteBench.xcodeproj/xcshareddata/xcschemes/WriteBenchPad.xcscheme
create mode 100644 platforms/ipad/Info.plist
create mode 100644 platforms/ipad/README.md
create mode 100644 platforms/ipad/WriteBenchPad/Assets.xcassets/AppIcon.appiconset/Contents.json
create mode 100644 platforms/ipad/WriteBenchPad/Assets.xcassets/AppIcon.appiconset/icon.png
create mode 100644 platforms/ipad/WriteBenchPad/PadEditor.swift
create mode 100644 platforms/ipad/WriteBenchPad/PadHandwriting.swift
create mode 100644 platforms/ipad/WriteBenchPad/PadLibrary.swift
create mode 100644 platforms/ipad/WriteBenchPad/PadSettings.swift
create mode 100644 platforms/ipad/WriteBenchPad/PadStore.swift
create mode 100644 platforms/ipad/WriteBenchPad/PadWorkspace.swift
create mode 100644 platforms/ipad/WriteBenchPad/WriteBenchPadApp.swift
create mode 100644 platforms/ipad/WriteBenchPadTests/PadTests.swift
create mode 100644 platforms/ipad/WriteBenchPadUITests/PadUITests.swift
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 12c6843..f483202 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -13,6 +13,27 @@ jobs:
- uses: actions/checkout@v4
- name: Build and test SwiftUI app
run: xcodebuild -project WriteBench.xcodeproj -scheme WriteBench -configuration Debug -derivedDataPath build -destination 'platform=macOS' CODE_SIGN_IDENTITY=- test
+ ipad:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Select an available iPad simulator
+ run: |
+ xcrun simctl list devices available -j > /tmp/simulators.json
+ python3 - <<'PY' >> "$GITHUB_ENV"
+ import json
+ devices = json.load(open('/tmp/simulators.json'))['devices']
+ ipad = next(d for group in devices.values() for d in group if d['name'].startswith('iPad'))
+ print('IPAD_SIMULATOR=' + ipad['udid'])
+ PY
+ - name: Build and test native iPad app
+ run: xcodebuild -project WriteBench.xcodeproj -scheme WriteBenchPad -destination "platform=iOS Simulator,id=$IPAD_SIMULATOR" -derivedDataPath build-ipad CODE_SIGN_IDENTITY=- test
+ - name: Upload iPad test evidence
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: ipad-verification
+ path: build-ipad/Logs/Test/*.xcresult
windows:
runs-on: windows-latest
steps:
diff --git a/.gitignore b/.gitignore
index 87a8caa..6f2f1cc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,7 @@ Documentation/codex-live-check.json
*.iml
.codex-build/
+
+.codex-ipad/
+
+.codex-ipad-device/
diff --git a/README.md b/README.md
index 62b7e15..c2b6206 100644
--- a/README.md
+++ b/README.md
@@ -160,3 +160,8 @@ macOS 自动化测试覆盖 87 个案例,Android 有 5 个领域测试与 2
WriteBench · 一次练习,一份原稿,一次认真重写。
考研翻译报告支持“意群精讲”:词汇、结构与翻译要点、分组译文及完整译文组合;英一只讲解作答划线句,英二按全文句子展开。点击交卷即暂停计时,提交受阻时可点击继续作答恢复。
+
+
+### iPadOS 开发版
+
+已新增 iPad 专用原生 target `WriteBenchPad`(iPadOS 18+),支持横竖屏答题、PencilKit 手写与 OCR 校对、内置六级题库、DeepSeek 评阅及与 macOS 兼容的备份。详见 [iPad 开发和安装说明](platforms/ipad/README.md)。实体 iPad 安装需要自己的 Apple 开发签名,当前尚无公开可安装的 IPA。
diff --git a/WriteBench.xcodeproj/project.pbxproj b/WriteBench.xcodeproj/project.pbxproj
index c9480e6..6d1fa03 100644
--- a/WriteBench.xcodeproj/project.pbxproj
+++ b/WriteBench.xcodeproj/project.pbxproj
@@ -9,12 +9,17 @@
/* Begin PBXBuildFile section */
0108E61BCDE2D4568F4D5C8B /* ProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3584B2D03E15D42CF5A4B1 /* ProviderTests.swift */; };
042D9A8FAAC22D71BDF6F7E4 /* ReviewCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16DC3843F83A5135DD60057 /* ReviewCard.swift */; };
+ 0611D0C9511C7414C1165A1F /* TranslationLesson.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6211317C38F78901403EE347 /* TranslationLesson.swift */; };
+ 0AB189E45DA34198003C4ACF /* ielts_task1.md in Resources */ = {isa = PBXBuildFile; fileRef = 405A85F2A07E72B5AD277D96 /* ielts_task1.md */; };
0B2FE86F272731005E6A17BF /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3647AF5325DFD1CE43543B /* Theme.swift */; };
+ 0ED522D3B22EAEB2CDF79443 /* kaoyan_english2_small.md in Resources */ = {isa = PBXBuildFile; fileRef = 015B10F8076FB519F8FDAACD /* kaoyan_english2_small.md */; };
+ 0F800C08810BB466CBE5CF50 /* cet6_writing.md in Resources */ = {isa = PBXBuildFile; fileRef = 650B4D62F506EDDC71D6396A /* cet6_writing.md */; };
143462C249149F8D7065D80D /* AppLifecycle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018639A9F1452A7617A5A0A2 /* AppLifecycle.swift */; };
14D92D7829215C6748E48951 /* StreamingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 306B86B1159828782A062144 /* StreamingTests.swift */; };
17236CF00A287A2737947138 /* QuestionTextReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE5F2FF0974E091C6E391D13 /* QuestionTextReader.swift */; };
1E1C317A4096B9830C2A3786 /* ExamTaskBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78510A02F0F827272F84695 /* ExamTaskBadge.swift */; };
21B1D6631C778F17583209A5 /* ielts_task2.md in Resources */ = {isa = PBXBuildFile; fileRef = AA7BA8561FCE7DD2841C904B /* ielts_task2.md */; };
+ 242298DFFDBE665961BCD774 /* PadEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5C2AC6FE9A57B96996AC706 /* PadEditor.swift */; };
27C8DB78E1B529A54B818ABD /* Exam.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AEB65E44D0601AEC6053865 /* Exam.swift */; };
291B90A1A7DDA40A4591BE8A /* QuestionBank.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71E6EE384D62168057B908A3 /* QuestionBank.swift */; };
2B8AA947E573B4AD052F6F95 /* OCRService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F892E2F452C5999A8C08043 /* OCRService.swift */; };
@@ -23,10 +28,17 @@
337FAAE005C9D52F7539091A /* CETQuestionBank.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1689A9A92BD0E930F5FB82F9 /* CETQuestionBank.swift */; };
361B2B881C01F86CC3A784DC /* cet6_translation.md in Resources */ = {isa = PBXBuildFile; fileRef = 6CED406B3EAB7FA3947795C5 /* cet6_translation.md */; };
380960FEBBC3C2CDC16C3147 /* BackgroundGradingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34A07798AF0255AB8EAFF76A /* BackgroundGradingTests.swift */; };
+ 3860A78F309E21B3B68AA88E /* kaoyan_english2_large.md in Resources */ = {isa = PBXBuildFile; fileRef = 0506352B36FD805B5E9053D8 /* kaoyan_english2_large.md */; };
+ 3AF9A2452A97E843CA28DBD3 /* kaoyan_english2_translation.md in Resources */ = {isa = PBXBuildFile; fileRef = 56792E2C1501DBCE7B80739B /* kaoyan_english2_translation.md */; };
3D7E00CED5955E5E709BAF28 /* RevisionViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E5B6AA16B1E69446387608 /* RevisionViews.swift */; };
+ 3FAD16D146887EE128D0907B /* PadSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3877BD86B363D93BD41E2B /* PadSettings.swift */; };
3FF816DF3394076A7EAFCA8E /* QuestionLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26C9A0B8BBB075532D147B45 /* QuestionLibraryView.swift */; };
+ 41EF54312B6D1CE976DD7BB1 /* cet6_translation.md in Resources */ = {isa = PBXBuildFile; fileRef = 6CED406B3EAB7FA3947795C5 /* cet6_translation.md */; };
+ 4241BA18661160643D61DE99 /* kaoyan_english1_large.md in Resources */ = {isa = PBXBuildFile; fileRef = ACA34C8DE60D85ABFA45116F /* kaoyan_english1_large.md */; };
448090E99953CC5C5F9C2F40 /* DeepSeekStreaming.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCC37FAB6E0DDC543B0D48B /* DeepSeekStreaming.swift */; };
+ 44EC8DBB916264EFCE0C8D90 /* PadHandwriting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28AEE0BB5A40311EB0DF71A8 /* PadHandwriting.swift */; };
473B2ECB957707EDB2BF41AA /* BackgroundGradingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA8E73D997ED19A98227B24C /* BackgroundGradingView.swift */; };
+ 4A24CA800AC78EA32367B092 /* QuestionBank.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71E6EE384D62168057B908A3 /* QuestionBank.swift */; };
4B7B1CDF7FADBE73B96E26DA /* kaoyan_english1_translation.md in Resources */ = {isa = PBXBuildFile; fileRef = AD3CEB6EC11C595CA1EA79D6 /* kaoyan_english1_translation.md */; };
51899521D61CB025A5559EC5 /* LocalProcessRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 100F5B726342F502CA8C4207 /* LocalProcessRunner.swift */; };
5317F309ED9004BD4665C0DC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 37444757CFEC8F67705A2D6C /* Assets.xcassets */; };
@@ -34,7 +46,11 @@
55C7907B2F577F01BC57A08B /* ReviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F9DB3748FBE72E213C6EB37 /* ReviewView.swift */; };
55E01FD08D907D18E2AFCBFC /* WritingStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C00C1A68BA4227431C3B8884 /* WritingStore.swift */; };
57B1584FB0F0DC411801C7D5 /* WritingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3788E5EB782131E544DDEDC /* WritingView.swift */; };
+ 5802389F014B6F342E5152C7 /* DeepSeekStreaming.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCC37FAB6E0DDC543B0D48B /* DeepSeekStreaming.swift */; };
+ 5F1536C7F3ABF096C23F2B41 /* CETQuestionBank.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1689A9A92BD0E930F5FB82F9 /* CETQuestionBank.swift */; };
611D1CBBEE2017C06DCD6D8D /* GradingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 634F608AF0DF72D3DE51E438 /* GradingService.swift */; };
+ 629089D675B6EC701836CB53 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B6B0E9758B9FE559058FEBBE /* Assets.xcassets */; };
+ 632FDD2AB3BEDB2E641E1090 /* kaoyan_english1_translation.md in Resources */ = {isa = PBXBuildFile; fileRef = AD3CEB6EC11C595CA1EA79D6 /* kaoyan_english1_translation.md */; };
6A2D61FFBE7F968DBCFB1164 /* cet6-2022-2026.json in Resources */ = {isa = PBXBuildFile; fileRef = 445C1E2A61051C152970E26E /* cet6-2022-2026.json */; };
6C1F7BEC5994E87DFA1CEF27 /* DeepSeekClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = A399A503863E6199B340A50C /* DeepSeekClient.swift */; };
6C3E278AD86905C7C0A63BCE /* QuestionText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0DD31CC0B614898B36ED76 /* QuestionText.swift */; };
@@ -44,41 +60,66 @@
7A7FFA1F4A7D393FDC0F3AA0 /* EssayHistoryGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11E52B4F1877F33DAEDBED32 /* EssayHistoryGroup.swift */; };
7ADD51F051853BB04DC26A4D /* WindowImmersionBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A76CC13C7CABACEC80CBAE /* WindowImmersionBridge.swift */; };
7E4B43903D948A9E26E11E8D /* BrandArt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA993471F8CFF92BD9B2956 /* BrandArt.swift */; };
+ 865F02CEF67F20E89E96A4E9 /* PadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 524AD2D04A2A9004F1783E8A /* PadTests.swift */; };
86CD1B5AEC144D4283491FC5 /* PlainTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6C23EF9D33F68C7816C642 /* PlainTextEditor.swift */; };
892F119876541FA3E8699E90 /* BackgroundGradingJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31DECD6B9F0B3A54C27F4535 /* BackgroundGradingJob.swift */; };
8938EFAD301830E8B39DF4FD /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEF0EC53CAA982107C3AA320 /* SettingsView.swift */; };
8A386C67F3728D7CC51BC97A /* CETQuestionBankTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FAD6385C4872B3E80D4B00C /* CETQuestionBankTests.swift */; };
+ 8F5898E5109076BF0EFF1F88 /* EssaySession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8874F23C092B83269A6E56 /* EssaySession.swift */; };
+ 90C3DE5400C92979D32AC1AD /* PadUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9A3D1D6ED82A1482C350CCB /* PadUITests.swift */; };
938D3916C068089FACA81D61 /* MistakesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4391A8E13E5C7A35D0A1467A /* MistakesView.swift */; };
+ 96416E493449971277BD75FC /* CorrectionMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1020C698B4DA8C1C47852419 /* CorrectionMatcher.swift */; };
96EB80336D8E7D70E3839817 /* kaoyan_english1_small.md in Resources */ = {isa = PBXBuildFile; fileRef = 6A7B637179FC6E271F31DBF4 /* kaoyan_english1_small.md */; };
+ 9850669404F8D67333E6C3D7 /* OCRService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F892E2F452C5999A8C08043 /* OCRService.swift */; };
+ 9B429BF8E4F61CDC600CE7D9 /* PadWorkspace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427C85E80F277C5F00ACFD3C /* PadWorkspace.swift */; };
+ 9B536D6CE7FE2E019121A5D3 /* ocr-sample-1.png in Resources */ = {isa = PBXBuildFile; fileRef = A35C864C69C265F8C846C881 /* ocr-sample-1.png */; };
9EA1454709EC8802D43C7CA4 /* HistoryFolderEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419DE9ABE9E26F14128B9B1E /* HistoryFolderEditor.swift */; };
A104DD7F66FF512A80BA9717 /* QuestionImportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29453693001AD3ECDE2185B6 /* QuestionImportTests.swift */; };
+ A4D399A6F4E03F23FE75BD10 /* ielts_task2.md in Resources */ = {isa = PBXBuildFile; fileRef = AA7BA8561FCE7DD2841C904B /* ielts_task2.md */; };
A54BFAD371DEB925FCA1963B /* PracticeLoopTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F5E568498AF07D6BF8C1335 /* PracticeLoopTests.swift */; };
+ A6458710E8DAF4A3CDF760CD /* Exam.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AEB65E44D0601AEC6053865 /* Exam.swift */; };
A64FFC28FE1B55A2B44B2E4B /* ReportSynthesis.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99093337348356622254C44E /* ReportSynthesis.swift */; };
A840C857A17F4257AD96C9AF /* ReviewTextExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 029A81F1C48B2FAE8914576B /* ReviewTextExporter.swift */; };
+ A88B15150ABE8B3ECDEB51CB /* PadStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6585246941091E1966ACFE51 /* PadStore.swift */; };
A97D07FFDB81393349741F89 /* PracticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F1B7E5BB4C4D7689B346DE /* PracticeView.swift */; };
+ ACAB0542046596D38C566B67 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3647AF5325DFD1CE43543B /* Theme.swift */; };
+ BA2F77C94675557CB5CE6F5D /* PadLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9DD6C821E6AAACD390D0D60 /* PadLibrary.swift */; };
+ BCD28EBF26B2DBEF3E99CCDD /* cet6-2022-2026.json in Resources */ = {isa = PBXBuildFile; fileRef = 445C1E2A61051C152970E26E /* cet6-2022-2026.json */; };
C118D4E53ACEC303DE46EA50 /* CorrectionMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1020C698B4DA8C1C47852419 /* CorrectionMatcher.swift */; };
+ C2DD60867C21D42E48479564 /* ReportSynthesis.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99093337348356622254C44E /* ReportSynthesis.swift */; };
C66E0F5339DADB4AE603BD5D /* GradingModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA0CA900CDD0D63C0AA6C39F /* GradingModels.swift */; };
C7DF6658F1F355665EEC175A /* GradingProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFD296324E8F63CD0B525458 /* GradingProvider.swift */; };
+ CA861DEBE1C347F7936FFB80 /* WriteBenchPadApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BC9D080A01177C3E30321D3 /* WriteBenchPadApp.swift */; };
CB760C24CAE63253B9400E19 /* WriteBenchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 679F1D25813DBB4821B089EF /* WriteBenchTests.swift */; };
CCB636BAB11DE1B7DE6C1CA2 /* ImmersiveWritingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B600C2CBBB1B477ABB65C42 /* ImmersiveWritingView.swift */; };
+ CCCE53E8DC22C561B10A1F5E /* UsageCost.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6B358A2E1043646F5A65FA4 /* UsageCost.swift */; };
+ CDFCD6C8002893F0FE940D50 /* GradingModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA0CA900CDD0D63C0AA6C39F /* GradingModels.swift */; };
CF9CA154E89BD40306ABC7D2 /* EssayFolderMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F9C06884EE52989CB4491F /* EssayFolderMetadata.swift */; };
D03424B806D55BF2BB9F11DC /* kaoyan_english2_large.md in Resources */ = {isa = PBXBuildFile; fileRef = 0506352B36FD805B5E9053D8 /* kaoyan_english2_large.md */; };
D3F29CFA674B891722D5B739 /* cet6_writing.md in Resources */ = {isa = PBXBuildFile; fileRef = 650B4D62F506EDDC71D6396A /* cet6_writing.md */; };
D4A1396AF9FFF2FF6C40C24D /* kaoyan_english2_translation.md in Resources */ = {isa = PBXBuildFile; fileRef = 56792E2C1501DBCE7B80739B /* kaoyan_english2_translation.md */; };
+ DA1225EBA78250B9BBF1BE37 /* BackupService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 888D43292E376FCF9480159D /* BackupService.swift */; };
DB3DEEA68D82D9F8BECF108E /* ReviewOutline.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B9132C06548AC9B8B44070 /* ReviewOutline.swift */; };
DB6452447E988C02C326B0BE /* WriteBenchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2322E689ACEC8741C4B1C432 /* WriteBenchApp.swift */; };
DB8D1A55F0E264D96E7E1D8D /* TextQuestionImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D56C8434F452DE88A5B9669 /* TextQuestionImportView.swift */; };
DCC333A3FAF19BC2ECC79BA0 /* KeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17802ADDF6589CB80F4B4F01 /* KeychainService.swift */; };
DEBA962949C4AADDFCB2C5D0 /* ielts_task1.md in Resources */ = {isa = PBXBuildFile; fileRef = 405A85F2A07E72B5AD277D96 /* ielts_task1.md */; };
DEF094BE47996A62C68AB985 /* CodexJudgeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27300883AE5AE3DD46C36924 /* CodexJudgeService.swift */; };
+ E0073DF9787252034D15C5FC /* GradingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 634F608AF0DF72D3DE51E438 /* GradingService.swift */; };
E041B0138CF95145600B02C6 /* kaoyan_english2_small.md in Resources */ = {isa = PBXBuildFile; fileRef = 015B10F8076FB519F8FDAACD /* kaoyan_english2_small.md */; };
E0495C7F8744F99633CD2EF5 /* UsageCost.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6B358A2E1043646F5A65FA4 /* UsageCost.swift */; };
E32387900468FFCA09760D27 /* kaoyan_english1_large.md in Resources */ = {isa = PBXBuildFile; fileRef = ACA34C8DE60D85ABFA45116F /* kaoyan_english1_large.md */; };
+ E6E5B98B8CAC1DCC944E885A /* QuestionText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0DD31CC0B614898B36ED76 /* QuestionText.swift */; };
E7928726F27B1B2EB5341AEB /* EssaySession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8874F23C092B83269A6E56 /* EssaySession.swift */; };
+ EBAE3F8E1B56E6AEBD98A371 /* EssayFolderMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F9C06884EE52989CB4491F /* EssayFolderMetadata.swift */; };
ECFAF47EFB1BFE1187B43C7D /* PracticeRail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 876B3B648AA84D3EBCAD18D8 /* PracticeRail.swift */; };
+ F09E424B1B72E44052E8B365 /* KeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17802ADDF6589CB80F4B4F01 /* KeychainService.swift */; };
F2DBAF31C0FA351AB0D126DB /* HistoryReviewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 879136039903990A8065C3BD /* HistoryReviewTests.swift */; };
+ F3588953C5283BF0EFF36061 /* DeepSeekClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = A399A503863E6199B340A50C /* DeepSeekClient.swift */; };
F59451A7E3C70F80EF599140 /* DeepSeekCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = D46587A10C9A9B126523FD04 /* DeepSeekCredentials.swift */; };
+ F7C987F988EE4BA49468C249 /* ReviewCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16DC3843F83A5135DD60057 /* ReviewCard.swift */; };
FAEA4C1F539996D40D1AD105 /* BackupService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 888D43292E376FCF9480159D /* BackupService.swift */; };
+ FB1610A4D5B992AA3477C943 /* kaoyan_english1_small.md in Resources */ = {isa = PBXBuildFile; fileRef = 6A7B637179FC6E271F31DBF4 /* kaoyan_english1_small.md */; };
FBEB290B54B4DA0E050F77E1 /* StatisticsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 781A9E69AB1A93F99198A60E /* StatisticsView.swift */; };
/* End PBXBuildFile section */
@@ -90,6 +131,20 @@
remoteGlobalIDString = 42685113F6CE8DBF0DF8E5C6;
remoteInfo = WriteBench;
};
+ 27A4741527532F7BC63449EC /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 29C958723C914B73391AB82A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 2CE8CC0563717B5A4D293DA4;
+ remoteInfo = WriteBenchPad;
+ };
+ E1B472EF8E8182D7A74DD385 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 29C958723C914B73391AB82A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 2CE8CC0563717B5A4D293DA4;
+ remoteInfo = WriteBenchPad;
+ };
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
@@ -103,14 +158,17 @@
1020C698B4DA8C1C47852419 /* CorrectionMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CorrectionMatcher.swift; sourceTree = ""; };
11E52B4F1877F33DAEDBED32 /* EssayHistoryGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EssayHistoryGroup.swift; sourceTree = ""; };
161DE1C1158C7D408EAD0C20 /* WriteBenchTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WriteBenchTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 1658A3CDE48F857354323CD7 /* WriteBenchPadTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = WriteBenchPadTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
1689A9A92BD0E930F5FB82F9 /* CETQuestionBank.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CETQuestionBank.swift; sourceTree = ""; };
17802ADDF6589CB80F4B4F01 /* KeychainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainService.swift; sourceTree = ""; };
1D56C8434F452DE88A5B9669 /* TextQuestionImportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextQuestionImportView.swift; sourceTree = ""; };
2322E689ACEC8741C4B1C432 /* WriteBenchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteBenchApp.swift; sourceTree = ""; };
26C9A0B8BBB075532D147B45 /* QuestionLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionLibraryView.swift; sourceTree = ""; };
27300883AE5AE3DD46C36924 /* CodexJudgeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodexJudgeService.swift; sourceTree = ""; };
+ 28AEE0BB5A40311EB0DF71A8 /* PadHandwriting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadHandwriting.swift; sourceTree = ""; };
29453693001AD3ECDE2185B6 /* QuestionImportTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionImportTests.swift; sourceTree = ""; };
296A6193086826B2B071503F /* MockGradingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGradingService.swift; sourceTree = ""; };
+ 2BC9D080A01177C3E30321D3 /* WriteBenchPadApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteBenchPadApp.swift; sourceTree = ""; };
306B86B1159828782A062144 /* StreamingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamingTests.swift; sourceTree = ""; };
31DECD6B9F0B3A54C27F4535 /* BackgroundGradingJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundGradingJob.swift; sourceTree = ""; };
34A07798AF0255AB8EAFF76A /* BackgroundGradingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundGradingTests.swift; sourceTree = ""; };
@@ -118,21 +176,25 @@
3A3647AF5325DFD1CE43543B /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; };
405A85F2A07E72B5AD277D96 /* ielts_task1.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = ielts_task1.md; sourceTree = ""; };
419DE9ABE9E26F14128B9B1E /* HistoryFolderEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryFolderEditor.swift; sourceTree = ""; };
+ 427C85E80F277C5F00ACFD3C /* PadWorkspace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadWorkspace.swift; sourceTree = ""; };
4391A8E13E5C7A35D0A1467A /* MistakesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MistakesView.swift; sourceTree = ""; };
445C1E2A61051C152970E26E /* cet6-2022-2026.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "cet6-2022-2026.json"; sourceTree = ""; };
4BCC37FAB6E0DDC543B0D48B /* DeepSeekStreaming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepSeekStreaming.swift; sourceTree = ""; };
4FAD6385C4872B3E80D4B00C /* CETQuestionBankTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CETQuestionBankTests.swift; sourceTree = ""; };
51A76CC13C7CABACEC80CBAE /* WindowImmersionBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowImmersionBridge.swift; sourceTree = ""; };
+ 524AD2D04A2A9004F1783E8A /* PadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadTests.swift; sourceTree = ""; };
56792E2C1501DBCE7B80739B /* kaoyan_english2_translation.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = kaoyan_english2_translation.md; sourceTree = ""; };
5AEB65E44D0601AEC6053865 /* Exam.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Exam.swift; sourceTree = ""; };
6211317C38F78901403EE347 /* TranslationLesson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslationLesson.swift; sourceTree = ""; };
634F608AF0DF72D3DE51E438 /* GradingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradingService.swift; sourceTree = ""; };
650B4D62F506EDDC71D6396A /* cet6_writing.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = cet6_writing.md; sourceTree = ""; };
+ 6585246941091E1966ACFE51 /* PadStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadStore.swift; sourceTree = ""; };
679F1D25813DBB4821B089EF /* WriteBenchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteBenchTests.swift; sourceTree = ""; };
6A7B637179FC6E271F31DBF4 /* kaoyan_english1_small.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = kaoyan_english1_small.md; sourceTree = ""; };
6CED406B3EAB7FA3947795C5 /* cet6_translation.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = cet6_translation.md; sourceTree = ""; };
70A5D3F3824C11B7B262C308 /* HistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryView.swift; sourceTree = ""; };
71E6EE384D62168057B908A3 /* QuestionBank.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionBank.swift; sourceTree = ""; };
+ 755D2250B6AED145B82C8E1B /* WriteBenchPad.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = WriteBenchPad.app; sourceTree = BUILT_PRODUCTS_DIR; };
781A9E69AB1A93F99198A60E /* StatisticsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatisticsView.swift; sourceTree = ""; };
84F1B7E5BB4C4D7689B346DE /* PracticeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PracticeView.swift; sourceTree = ""; };
876B3B648AA84D3EBCAD18D8 /* PracticeRail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PracticeRail.swift; sourceTree = ""; };
@@ -145,12 +207,15 @@
9AA993471F8CFF92BD9B2956 /* BrandArt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrandArt.swift; sourceTree = ""; };
9B600C2CBBB1B477ABB65C42 /* ImmersiveWritingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImmersiveWritingView.swift; sourceTree = ""; };
9F5E568498AF07D6BF8C1335 /* PracticeLoopTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PracticeLoopTests.swift; sourceTree = ""; };
+ A35C864C69C265F8C846C881 /* ocr-sample-1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ocr-sample-1.png"; sourceTree = ""; };
A399A503863E6199B340A50C /* DeepSeekClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepSeekClient.swift; sourceTree = ""; };
+ AA3877BD86B363D93BD41E2B /* PadSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadSettings.swift; sourceTree = ""; };
AA7BA8561FCE7DD2841C904B /* ielts_task2.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = ielts_task2.md; sourceTree = ""; };
ACA34C8DE60D85ABFA45116F /* kaoyan_english1_large.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = kaoyan_english1_large.md; sourceTree = ""; };
AD3CEB6EC11C595CA1EA79D6 /* kaoyan_english1_translation.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = kaoyan_english1_translation.md; sourceTree = ""; };
AEF0EC53CAA982107C3AA320 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; };
B16DC3843F83A5135DD60057 /* ReviewCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewCard.swift; sourceTree = ""; };
+ B6B0E9758B9FE559058FEBBE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
BB3584B2D03E15D42CF5A4B1 /* ProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderTests.swift; sourceTree = ""; };
C00C1A68BA4227431C3B8884 /* WritingStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WritingStore.swift; sourceTree = ""; };
C6B92D96525F9799F42C2EDE /* WriteBench.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WriteBench.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -162,13 +227,17 @@
DA0CA900CDD0D63C0AA6C39F /* GradingModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradingModels.swift; sourceTree = ""; };
DA8E73D997ED19A98227B24C /* BackgroundGradingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundGradingView.swift; sourceTree = ""; };
DD0DD31CC0B614898B36ED76 /* QuestionText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionText.swift; sourceTree = ""; };
+ E5C2AC6FE9A57B96996AC706 /* PadEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadEditor.swift; sourceTree = ""; };
E6B358A2E1043646F5A65FA4 /* UsageCost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsageCost.swift; sourceTree = ""; };
E94CDCC1049F7BD4C287AF84 /* WriteBench.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WriteBench.entitlements; sourceTree = ""; };
+ E9DD6C821E6AAACD390D0D60 /* PadLibrary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadLibrary.swift; sourceTree = ""; };
EE5F2FF0974E091C6E391D13 /* QuestionTextReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionTextReader.swift; sourceTree = ""; };
F11CD6DC48ADA2A2066B58A3 /* TranslationLessonCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslationLessonCard.swift; sourceTree = ""; };
F3788E5EB782131E544DDEDC /* WritingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WritingView.swift; sourceTree = ""; };
F54C82E0AE1ACA592616CDDB /* OCRConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OCRConfirmationView.swift; sourceTree = ""; };
+ F60CDC36C4B907FF32441E0C /* WriteBenchPadUITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = WriteBenchPadUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
F7B9132C06548AC9B8B44070 /* ReviewOutline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewOutline.swift; sourceTree = ""; };
+ F9A3D1D6ED82A1482C350CCB /* PadUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadUITests.swift; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXGroup section */
@@ -180,6 +249,31 @@
path = OCR;
sourceTree = "";
};
+ 1B79BA391979FBB2B22B1F4B /* WriteBenchPad */ = {
+ isa = PBXGroup;
+ children = (
+ B6B0E9758B9FE559058FEBBE /* Assets.xcassets */,
+ E5C2AC6FE9A57B96996AC706 /* PadEditor.swift */,
+ 28AEE0BB5A40311EB0DF71A8 /* PadHandwriting.swift */,
+ E9DD6C821E6AAACD390D0D60 /* PadLibrary.swift */,
+ AA3877BD86B363D93BD41E2B /* PadSettings.swift */,
+ 6585246941091E1966ACFE51 /* PadStore.swift */,
+ 427C85E80F277C5F00ACFD3C /* PadWorkspace.swift */,
+ 2BC9D080A01177C3E30321D3 /* WriteBenchPadApp.swift */,
+ );
+ name = WriteBenchPad;
+ path = platforms/ipad/WriteBenchPad;
+ sourceTree = "";
+ };
+ 1D6F3A87B962A76C59CB35C8 /* WriteBenchPadUITests */ = {
+ isa = PBXGroup;
+ children = (
+ F9A3D1D6ED82A1482C350CCB /* PadUITests.swift */,
+ );
+ name = WriteBenchPadUITests;
+ path = platforms/ipad/WriteBenchPadUITests;
+ sourceTree = "";
+ };
1E299B61878E0D3292AC4569 /* Review */ = {
isa = PBXGroup;
children = (
@@ -244,10 +338,22 @@
path = Models;
sourceTree = "";
};
+ 5F7DA285C20E5D45609396D7 /* TestImages */ = {
+ isa = PBXGroup;
+ children = (
+ A35C864C69C265F8C846C881 /* ocr-sample-1.png */,
+ );
+ name = TestImages;
+ path = Documentation/TestImages;
+ sourceTree = "";
+ };
6C0EBEADE4784897205C53CF /* Products */ = {
isa = PBXGroup;
children = (
C6B92D96525F9799F42C2EDE /* WriteBench.app */,
+ 755D2250B6AED145B82C8E1B /* WriteBenchPad.app */,
+ 1658A3CDE48F857354323CD7 /* WriteBenchPadTests.xctest */,
+ F60CDC36C4B907FF32441E0C /* WriteBenchPadUITests.xctest */,
161DE1C1158C7D408EAD0C20 /* WriteBenchTests.xctest */,
);
name = Products;
@@ -354,11 +460,24 @@
path = History;
sourceTree = "";
};
+ AF2B269A7710935879110FE6 /* WriteBenchPadTests */ = {
+ isa = PBXGroup;
+ children = (
+ 524AD2D04A2A9004F1783E8A /* PadTests.swift */,
+ );
+ name = WriteBenchPadTests;
+ path = platforms/ipad/WriteBenchPadTests;
+ sourceTree = "";
+ };
B32DAE4CDBDFC97EC7BE4B5A = {
isa = PBXGroup;
children = (
85C04166008095CC4B9B2FA4 /* QuestionBank */,
+ 5F7DA285C20E5D45609396D7 /* TestImages */,
CC65E74040E23A5ED1894671 /* WriteBench */,
+ 1B79BA391979FBB2B22B1F4B /* WriteBenchPad */,
+ AF2B269A7710935879110FE6 /* WriteBenchPadTests */,
+ 1D6F3A87B962A76C59CB35C8 /* WriteBenchPadUITests */,
8E4D664CF8F8C451767E12E6 /* WriteBenchTests */,
6C0EBEADE4784897205C53CF /* Products */,
);
@@ -476,6 +595,24 @@
productReference = 161DE1C1158C7D408EAD0C20 /* WriteBenchTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
+ 2CE8CC0563717B5A4D293DA4 /* WriteBenchPad */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 6F9EEFA295C71511C7B62D78 /* Build configuration list for PBXNativeTarget "WriteBenchPad" */;
+ buildPhases = (
+ 8F8AEADB279D2985A71097A6 /* Sources */,
+ E36BF57CDAEF42663F20055C /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = WriteBenchPad;
+ packageProductDependencies = (
+ );
+ productName = WriteBenchPad;
+ productReference = 755D2250B6AED145B82C8E1B /* WriteBenchPad.app */;
+ productType = "com.apple.product-type.application";
+ };
42685113F6CE8DBF0DF8E5C6 /* WriteBench */ = {
isa = PBXNativeTarget;
buildConfigurationList = A95368A01FC6E68C8FBCD4FF /* Build configuration list for PBXNativeTarget "WriteBench" */;
@@ -494,6 +631,43 @@
productReference = C6B92D96525F9799F42C2EDE /* WriteBench.app */;
productType = "com.apple.product-type.application";
};
+ 4D319C0D85B3B2D52BB9F38B /* WriteBenchPadTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = F36EC6B07186189AA6AB61AF /* Build configuration list for PBXNativeTarget "WriteBenchPadTests" */;
+ buildPhases = (
+ 7051537DAE21242AD61D8E50 /* Sources */,
+ 5558A3DC85775B1382A4F307 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 5D1E68316177777BADD3862F /* PBXTargetDependency */,
+ );
+ name = WriteBenchPadTests;
+ packageProductDependencies = (
+ );
+ productName = WriteBenchPadTests;
+ productReference = 1658A3CDE48F857354323CD7 /* WriteBenchPadTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ 9A12594761FB8665EB47620A /* WriteBenchPadUITests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = C1B3410118B038770315BB3A /* Build configuration list for PBXNativeTarget "WriteBenchPadUITests" */;
+ buildPhases = (
+ FB29ADC38F2DBD78134FC2E0 /* Sources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ EE717C6CCCBB2C5D6A7CC02D /* PBXTargetDependency */,
+ );
+ name = WriteBenchPadUITests;
+ packageProductDependencies = (
+ );
+ productName = WriteBenchPadUITests;
+ productReference = F60CDC36C4B907FF32441E0C /* WriteBenchPadUITests.xctest */;
+ productType = "com.apple.product-type.bundle.ui-testing";
+ };
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@@ -506,9 +680,19 @@
2544962C2680945644A01417 = {
ProvisioningStyle = Automatic;
};
+ 2CE8CC0563717B5A4D293DA4 = {
+ ProvisioningStyle = Automatic;
+ };
42685113F6CE8DBF0DF8E5C6 = {
ProvisioningStyle = Automatic;
};
+ 4D319C0D85B3B2D52BB9F38B = {
+ ProvisioningStyle = Automatic;
+ };
+ 9A12594761FB8665EB47620A = {
+ ProvisioningStyle = Automatic;
+ TestTargetID = 2CE8CC0563717B5A4D293DA4;
+ };
};
};
buildConfigurationList = EF77D57F624459FCA134BB20 /* Build configuration list for PBXProject "WriteBench" */;
@@ -527,11 +711,22 @@
targets = (
42685113F6CE8DBF0DF8E5C6 /* WriteBench */,
2544962C2680945644A01417 /* WriteBenchTests */,
+ 2CE8CC0563717B5A4D293DA4 /* WriteBenchPad */,
+ 4D319C0D85B3B2D52BB9F38B /* WriteBenchPadTests */,
+ 9A12594761FB8665EB47620A /* WriteBenchPadUITests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
+ 5558A3DC85775B1382A4F307 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 9B536D6CE7FE2E019121A5D3 /* ocr-sample-1.png in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
9D8C3A595D9330A9226535FD /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -551,6 +746,25 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ E36BF57CDAEF42663F20055C /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 629089D675B6EC701836CB53 /* Assets.xcassets in Resources */,
+ BCD28EBF26B2DBEF3E99CCDD /* cet6-2022-2026.json in Resources */,
+ 41EF54312B6D1CE976DD7BB1 /* cet6_translation.md in Resources */,
+ 0F800C08810BB466CBE5CF50 /* cet6_writing.md in Resources */,
+ 0AB189E45DA34198003C4ACF /* ielts_task1.md in Resources */,
+ A4D399A6F4E03F23FE75BD10 /* ielts_task2.md in Resources */,
+ 4241BA18661160643D61DE99 /* kaoyan_english1_large.md in Resources */,
+ FB1610A4D5B992AA3477C943 /* kaoyan_english1_small.md in Resources */,
+ 632FDD2AB3BEDB2E641E1090 /* kaoyan_english1_translation.md in Resources */,
+ 3860A78F309E21B3B68AA88E /* kaoyan_english2_large.md in Resources */,
+ 0ED522D3B22EAEB2CDF79443 /* kaoyan_english2_small.md in Resources */,
+ 3AF9A2452A97E843CA28DBD3 /* kaoyan_english2_translation.md in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -629,17 +843,94 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 7051537DAE21242AD61D8E50 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 865F02CEF67F20E89E96A4E9 /* PadTests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 8F8AEADB279D2985A71097A6 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DA1225EBA78250B9BBF1BE37 /* BackupService.swift in Sources */,
+ 5F1536C7F3ABF096C23F2B41 /* CETQuestionBank.swift in Sources */,
+ 96416E493449971277BD75FC /* CorrectionMatcher.swift in Sources */,
+ F3588953C5283BF0EFF36061 /* DeepSeekClient.swift in Sources */,
+ 5802389F014B6F342E5152C7 /* DeepSeekStreaming.swift in Sources */,
+ EBAE3F8E1B56E6AEBD98A371 /* EssayFolderMetadata.swift in Sources */,
+ 8F5898E5109076BF0EFF1F88 /* EssaySession.swift in Sources */,
+ A6458710E8DAF4A3CDF760CD /* Exam.swift in Sources */,
+ CDFCD6C8002893F0FE940D50 /* GradingModels.swift in Sources */,
+ E0073DF9787252034D15C5FC /* GradingService.swift in Sources */,
+ F09E424B1B72E44052E8B365 /* KeychainService.swift in Sources */,
+ 9850669404F8D67333E6C3D7 /* OCRService.swift in Sources */,
+ 242298DFFDBE665961BCD774 /* PadEditor.swift in Sources */,
+ 44EC8DBB916264EFCE0C8D90 /* PadHandwriting.swift in Sources */,
+ BA2F77C94675557CB5CE6F5D /* PadLibrary.swift in Sources */,
+ 3FAD16D146887EE128D0907B /* PadSettings.swift in Sources */,
+ A88B15150ABE8B3ECDEB51CB /* PadStore.swift in Sources */,
+ 9B429BF8E4F61CDC600CE7D9 /* PadWorkspace.swift in Sources */,
+ 4A24CA800AC78EA32367B092 /* QuestionBank.swift in Sources */,
+ E6E5B98B8CAC1DCC944E885A /* QuestionText.swift in Sources */,
+ C2DD60867C21D42E48479564 /* ReportSynthesis.swift in Sources */,
+ F7C987F988EE4BA49468C249 /* ReviewCard.swift in Sources */,
+ ACAB0542046596D38C566B67 /* Theme.swift in Sources */,
+ 0611D0C9511C7414C1165A1F /* TranslationLesson.swift in Sources */,
+ CCCE53E8DC22C561B10A1F5E /* UsageCost.swift in Sources */,
+ CA861DEBE1C347F7936FFB80 /* WriteBenchPadApp.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ FB29ADC38F2DBD78134FC2E0 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 90C3DE5400C92979D32AC1AD /* PadUITests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
+ 5D1E68316177777BADD3862F /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 2CE8CC0563717B5A4D293DA4 /* WriteBenchPad */;
+ targetProxy = E1B472EF8E8182D7A74DD385 /* PBXContainerItemProxy */;
+ };
6267C51647F1331E22F68E23 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 42685113F6CE8DBF0DF8E5C6 /* WriteBench */;
targetProxy = 05E28766A5A1D3E6A0617685 /* PBXContainerItemProxy */;
};
+ EE717C6CCCBB2C5D6A7CC02D /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 2CE8CC0563717B5A4D293DA4 /* WriteBenchPad */;
+ targetProxy = 27A4741527532F7BC63449EC /* PBXContainerItemProxy */;
+ };
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
+ 0774EAF5920BA355785F491F /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_ENTITLEMENTS = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 18.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.chen.WriteBenchPadUITests;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = 2;
+ TEST_TARGET_NAME = WriteBenchPad;
+ };
+ name = Debug;
+ };
0D4182C73077754150582570 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -656,6 +947,24 @@
};
name = Release;
};
+ 2F229AEB87C17144C272914A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_ENTITLEMENTS = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 18.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.chen.WriteBenchPadTests;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = 2;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WriteBenchPad.app/WriteBenchPad";
+ };
+ name = Release;
+ };
770C895008D3624B0ADFB0CE /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -711,13 +1020,34 @@
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
- SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 6.0;
};
name = Release;
};
+ 82E2FD9948C210020B9C1D7A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_ENTITLEMENTS = "";
+ CODE_SIGN_IDENTITY = "Apple Development";
+ ENABLE_HARDENED_RUNTIME = NO;
+ INFOPLIST_FILE = platforms/ipad/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = WriteBench;
+ IPHONEOS_DEPLOYMENT_TARGET = 18.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.chen.WriteBench.ipad;
+ SDKROOT = iphoneos;
+ SUPPORTS_MACCATALYST = NO;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
+ TARGETED_DEVICE_FAMILY = 2;
+ };
+ name = Debug;
+ };
99460C1F2BDFC0094106F5F2 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -736,6 +1066,64 @@
};
name = Debug;
};
+ A44940CC0FDB9C8B38D9C643 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_ENTITLEMENTS = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 18.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.chen.WriteBenchPadUITests;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = 2;
+ TEST_TARGET_NAME = WriteBenchPad;
+ };
+ name = Release;
+ };
+ A87D63488E47D987322B4927 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_ENTITLEMENTS = "";
+ CODE_SIGN_IDENTITY = "Apple Development";
+ ENABLE_HARDENED_RUNTIME = NO;
+ INFOPLIST_FILE = platforms/ipad/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = WriteBench;
+ IPHONEOS_DEPLOYMENT_TARGET = 18.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.chen.WriteBench.ipad;
+ SDKROOT = iphoneos;
+ SUPPORTS_MACCATALYST = NO;
+ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
+ TARGETED_DEVICE_FAMILY = 2;
+ };
+ name = Release;
+ };
+ B17B8D7A317AE3C2A37ADF6C /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_ENTITLEMENTS = "";
+ IPHONEOS_DEPLOYMENT_TARGET = 18.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.chen.WriteBenchPadTests;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = 2;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WriteBenchPad.app/WriteBenchPad";
+ };
+ name = Debug;
+ };
B6F36944794557B5D49E23DE /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -814,7 +1202,6 @@
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
- SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 6.0;
@@ -851,6 +1238,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
+ 6F9EEFA295C71511C7B62D78 /* Build configuration list for PBXNativeTarget "WriteBenchPad" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 82E2FD9948C210020B9C1D7A /* Debug */,
+ A87D63488E47D987322B4927 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Debug;
+ };
A95368A01FC6E68C8FBCD4FF /* Build configuration list for PBXNativeTarget "WriteBench" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -860,6 +1256,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
+ C1B3410118B038770315BB3A /* Build configuration list for PBXNativeTarget "WriteBenchPadUITests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 0774EAF5920BA355785F491F /* Debug */,
+ A44940CC0FDB9C8B38D9C643 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Debug;
+ };
EF77D57F624459FCA134BB20 /* Build configuration list for PBXProject "WriteBench" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -869,6 +1274,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
+ F36EC6B07186189AA6AB61AF /* Build configuration list for PBXNativeTarget "WriteBenchPadTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ B17B8D7A317AE3C2A37ADF6C /* Debug */,
+ 2F229AEB87C17144C272914A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Debug;
+ };
/* End XCConfigurationList section */
};
rootObject = 29C958723C914B73391AB82A /* Project object */;
diff --git a/WriteBench.xcodeproj/xcshareddata/xcschemes/WriteBenchPad.xcscheme b/WriteBench.xcodeproj/xcshareddata/xcschemes/WriteBenchPad.xcscheme
new file mode 100644
index 0000000..a6840f8
--- /dev/null
+++ b/WriteBench.xcodeproj/xcshareddata/xcschemes/WriteBenchPad.xcscheme
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/WriteBench/Persistence/EssaySession.swift b/WriteBench/Persistence/EssaySession.swift
index 1531309..eeb2b8c 100644
--- a/WriteBench/Persistence/EssaySession.swift
+++ b/WriteBench/Persistence/EssaySession.swift
@@ -42,6 +42,8 @@ import SwiftData
}
@Model final class WritingDraft {
+ @Attribute(.externalStorage) var richText: Data? = nil
+ @Attribute(.externalStorage) var pencilDrawing: Data? = nil
@Attribute(.unique) var subtype: String
var question: String
var questionLabel: String
diff --git a/WriteBench/Services/Backup/BackupService.swift b/WriteBench/Services/Backup/BackupService.swift
index 7f11e1d..ececc95 100644
--- a/WriteBench/Services/Backup/BackupService.swift
+++ b/WriteBench/Services/Backup/BackupService.swift
@@ -21,6 +21,8 @@ struct BackupArchive: Codable {
var parentSessionID: UUID?, questionImage: Data?, sourceImages: Data?
}
struct DraftRecord: Codable {
+ var richText: Data? = nil
+ var pencilDrawing: Data? = nil
var subtype: String, question: String, questionLabel: String, essay: String, elapsed: Double, inputMode: String, updatedAt: Date
var rewriteSessionID: UUID?, questionImage: Data?, sourceImages: Data?
}
@@ -59,7 +61,7 @@ struct BackupSummary: Equatable {
questionImage: $0.questionImage, sourceImages: $0.sourceImages)
},
drafts: try context.fetch(FetchDescriptor()).map {
- .init(subtype: $0.subtype, question: $0.question, questionLabel: $0.questionLabel, essay: $0.essay, elapsed: $0.elapsed,
+ .init(richText: $0.richText, pencilDrawing: $0.pencilDrawing, subtype: $0.subtype, question: $0.question, questionLabel: $0.questionLabel, essay: $0.essay, elapsed: $0.elapsed,
inputMode: $0.inputMode, updatedAt: $0.updatedAt, rewriteSessionID: $0.rewriteSessionID, questionImage: $0.questionImage, sourceImages: $0.sourceImages)
},
questions: try context.fetch(FetchDescriptor()).map {
@@ -111,7 +113,7 @@ struct BackupSummary: Equatable {
if drafts[record.subtype] == nil { context.insert(draft) }
draft.question = record.question; draft.questionLabel = record.questionLabel; draft.essay = record.essay; draft.elapsed = record.elapsed
draft.inputMode = record.inputMode; draft.updatedAt = record.updatedAt; draft.rewriteSessionID = record.rewriteSessionID
- draft.questionImage = record.questionImage; draft.sourceImages = record.sourceImages
+ draft.questionImage = record.questionImage; draft.sourceImages = record.sourceImages; draft.richText = record.richText; draft.pencilDrawing = record.pencilDrawing
summary.added += 1
}
let questionIDs = Set(try context.fetch(FetchDescriptor()).map(\.id))
diff --git a/WriteBench/Services/OCR/OCRService.swift b/WriteBench/Services/OCR/OCRService.swift
index 34a9b21..8754eee 100644
--- a/WriteBench/Services/OCR/OCRService.swift
+++ b/WriteBench/Services/OCR/OCRService.swift
@@ -2,7 +2,9 @@ import Foundation
import Vision
import ImageIO
import UniformTypeIdentifiers
+#if os(macOS)
import AppKit
+#endif
struct OCRPage: Identifiable, Sendable {
var id = UUID()
@@ -66,6 +68,7 @@ struct VisionOCRService: Sendable {
}
}
+#if os(macOS)
@MainActor enum ImageImporter {
static func selectImages() async -> [URL] {
let panel = NSOpenPanel()
@@ -78,3 +81,5 @@ struct VisionOCRService: Sendable {
return await panel.begin() == .OK ? panel.urls : []
}
}
+
+#endif
diff --git a/platforms/ipad/Info.plist b/platforms/ipad/Info.plist
new file mode 100644
index 0000000..e6a857b
--- /dev/null
+++ b/platforms/ipad/Info.plist
@@ -0,0 +1,33 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(MARKETING_VERSION)
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ UIApplicationSupportsIndirectInputEvents
+
+ UILaunchScreen
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+
diff --git a/platforms/ipad/README.md b/platforms/ipad/README.md
new file mode 100644
index 0000000..0f1b544
--- /dev/null
+++ b/platforms/ipad/README.md
@@ -0,0 +1,38 @@
+# WriteBench for iPad
+
+原生 SwiftUI / UIKit / PencilKit 应用,要求 **iPadOS 18 或更新版本**。`TARGETED_DEVICE_FAMILY = 2`,只面向 iPad,不包含 iPhone 版,也不启用 Mac Catalyst。
+
+## 功能
+
+- 考研英语一/二、六级、IELTS 的写作与翻译练习;66 道近五年六级完整正文随应用打包。
+- 宽窗口题目与答题卡并排;窄窗口上下排列。横竖屏、窗口大小变化均可使用。
+- 原生键盘编辑:保留连续空格,Tab 插入四个空格,当前/选中段落左对齐、居中、右对齐。草稿和备份保留段落格式。
+- PencilKit 手写纸、手指/Apple Pencil 输入、橡皮擦和清空确认;笔迹随草稿和备份保存。手写识别需要人工校对,再转换成评阅所需文字。
+- 文件 App 图片导入、本机 Vision 中英文 OCR、原图预览、强制校对确认;UTF-8 文本、带文字层 PDF 导入。
+- 交卷立即暂停计时;页面切换和退到后台暂停计时并保存草稿。离开前台会取消未完成的评阅,保留提交内容,不生成部分总分。
+- DeepSeek 快速/三评审评阅,共用桌面的 rubric、JSON 校验、聚合规则、修改建议与翻译教学。
+- 评阅历史、分享复习文本、错题/表达的间隔复习、按题型统计。
+- Keychain 保存 API Key;无额外账号或云同步。可导入/导出与 macOS 兼容的 JSON 备份,备份不含凭据。自定义六级题库 JSON 需单独导入到题库。
+
+桌面版的 Codex CLI 无法在 iPadOS 执行,因此 iPad 版目前只提供 DeepSeek。付费真实评阅与实体 Apple Pencil 尚需实机验证。
+
+## Xcode 开发与测试
+
+在仓库根目录:
+
+```sh
+xcodegen generate
+xcodebuild -project WriteBench.xcodeproj -scheme WriteBenchPad \
+ -destination 'platform=iOS Simulator,name=iPad Pro 11-inch (M5)' \
+ -derivedDataPath build-ipad CODE_SIGN_IDENTITY=- test
+```
+
+使用本机实际可用的 iPad 模拟器名称(`xcrun simctl list devices available`)。模拟器构建使用临时签名;不要设置 `CODE_SIGNING_ALLOWED=NO` 来运行应用,否则 Keychain 会返回缺少 entitlement。
+
+CI 会自动选取可用 iPad 模拟器,执行单元测试与 UI 测试并上传 xcresult。测试使用内存数据库;OCR 测试用样本图片,不调用付费评阅。
+
+## 安装到实体 iPad / TestFlight
+
+打开 `WriteBench.xcodeproj`,选择 **WriteBenchPad** target,在 Signing & Capabilities 选择自己的 Apple 开发 Team,连接 iPad 并运行。按设备要求开启 Developer Mode。
+
+TestFlight / App Store 需要 Apple Developer Program、有效的分发签名和 App Store Connect 配置。模拟器 `.app` 与未签名设备构建都不能直接作为可安装 IPA 分发。当前没有发布 iPad 安装包,不会把模拟器包伪装成 IPA。
diff --git a/platforms/ipad/WriteBenchPad/Assets.xcassets/AppIcon.appiconset/Contents.json b/platforms/ipad/WriteBenchPad/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..c59a08a
--- /dev/null
+++ b/platforms/ipad/WriteBenchPad/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1 @@
+{"images": [{"filename": "icon.png", "idiom": "universal", "platform": "ios", "size": "1024x1024"}], "info": {"author": "xcode", "version": 1}}
\ No newline at end of file
diff --git a/platforms/ipad/WriteBenchPad/Assets.xcassets/AppIcon.appiconset/icon.png b/platforms/ipad/WriteBenchPad/Assets.xcassets/AppIcon.appiconset/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..dcdc057f50bc3e3cc3afc46cfb1858fadc38cbcd
GIT binary patch
literal 312478
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