From 4093b5a7f3f1f8f74c75984d716f08521c4d3670 Mon Sep 17 00:00:00 2001 From: Trey t Date: Wed, 22 Apr 2026 09:57:32 -0500 Subject: [PATCH] =?UTF-8?q?Fixes=20#27=20=E2=80=94=20AI-generated=20exampl?= =?UTF-8?q?e=20sentences=20on=20verb=20detail?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tapping a verb now shows six example sentences beneath the conjugation table, one per core tense (Present, Preterite, Imperfect, Future, Present Subjunctive, Imperative). Each example renders the tense label, Spanish sentence, and English translation in the DeckStudyView style. Generation uses Foundation Models with a @Generable schema that pins each response to the requested tenseId and forces tú/nosotros subjects for imperatives. Results are cached as JSON in the Caches directory keyed by verb id (DictionaryService pattern); cache misses regenerate on demand. Devices without Apple Intelligence see an inline notice instead of the loading state. No network dependency. Co-Authored-By: Claude Opus 4.7 (1M context) --- Conjuga/Conjuga.xcodeproj/project.pbxproj | 359 ++++++------------ .../xcshareddata/xcschemes/Conjuga.xcscheme | 10 - Conjuga/Conjuga/ConjugaApp.swift | 2 + Conjuga/Conjuga/Info.plist | 16 +- .../Conjuga/Services/VerbExampleCache.swift | 65 ++++ .../Services/VerbExampleGenerator.swift | 78 ++++ .../Conjuga/Views/Verbs/VerbDetailView.swift | 112 ++++++ .../Sources/SharedModels/VerbExample.swift | 17 + 8 files changed, 387 insertions(+), 272 deletions(-) create mode 100644 Conjuga/Conjuga/Services/VerbExampleCache.swift create mode 100644 Conjuga/Conjuga/Services/VerbExampleGenerator.swift create mode 100644 Conjuga/SharedModels/Sources/SharedModels/VerbExample.swift diff --git a/Conjuga/Conjuga.xcodeproj/project.pbxproj b/Conjuga/Conjuga.xcodeproj/project.pbxproj index 7e46e6f..b7ac5ba 100644 --- a/Conjuga/Conjuga.xcodeproj/project.pbxproj +++ b/Conjuga/Conjuga.xcodeproj/project.pbxproj @@ -8,12 +8,15 @@ /* Begin PBXBuildFile section */ 00BEC0BDBB49198022D9852E /* WordOfDayWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E9BCDBB9BC24F5C8117767E /* WordOfDayWidget.swift */; }; + 04C74D9E0ED84BF785A8331C /* ClozeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D232CDA43CC9218D748BA121 /* ClozeView.swift */; }; 0A89DCC82BE11605CB866DEF /* TenseInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BC3247457109FC6BF00D85B /* TenseInfo.swift */; }; - 12D2C9311D5C4764B48B1754 /* StoryQuizView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E292A183ABB24FFE9CB719C8 /* StoryQuizView.swift */; }; + 0AD63CAED7C568590A16E879 /* StoryQuizView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7CDC5F2660A3009A3ADF048 /* StoryQuizView.swift */; }; + 0D0D3B5CC128D1A1D1252282 /* PronunciationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AE3D1D8723D5C41D3501774 /* PronunciationService.swift */; }; 13F29AD5745FB532709FA28A /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E972AA745F44586EF0B1B0C8 /* OnboardingView.swift */; }; + 14242FD1F500D296D41E927C /* FeatureReferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C3E36BDC2540AF2A67AEEB1 /* FeatureReferenceView.swift */; }; 1A230C01A045F0C095BFBD35 /* PracticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EA0FA4F9149B9D8E197ADE9 /* PracticeView.swift */; }; - 1B0B3B2C771AD72E25B3493C /* StemChangeToggleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F08E1DC6932D9EA1D380913 /* StemChangeToggleTests.swift */; }; 1C2636790E70B6BC7FFCC904 /* DailyLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0313D24F96E6A0039C34341F /* DailyLog.swift */; }; + 20B71403A8D305C29C73ADA2 /* StemChangeConjugationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F92BCE1A6720E47FCD26BADC /* StemChangeConjugationView.swift */; }; 218E982FC4267949F82AABAD /* SharedModels in Frameworks */ = {isa = PBXBuildFile; productRef = 4A4D7B02884EBA9ACD93F0FD /* SharedModels */; }; 261E582449BED6EF41881B04 /* AdaptiveContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B16FF4C52457CD8CD703532 /* AdaptiveContainer.swift */; }; 27BA7FA9356467846A07697D /* TypingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10C16AA6022E4742898745CE /* TypingView.swift */; }; @@ -22,69 +25,65 @@ 2C7ABAB4D88E3E3B0EAD1EF7 /* PracticeHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF946245110C92F087D81E8 /* PracticeHeaderView.swift */; }; 33E885EB38C3BB0CB058871A /* HandwritingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F842EB5E566C74658D918BB /* HandwritingView.swift */; }; 352A5BAA6E406AA5850653A4 /* PracticeSessionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842DB48F8570C39CDCFF2F57 /* PracticeSessionService.swift */; }; + 354631F309E625046A3A436B /* TextbookExerciseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 854EA2A8D6CF203958BA3C24 /* TextbookExerciseView.swift */; }; 35A0F6E7124D989312721F7D /* DashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18AC3C548BDB9EF8701BE64C /* DashboardView.swift */; }; - 35D6404C60C249D5995AD895 /* ConversationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10603F454E54341AA4B9931 /* ConversationService.swift */; }; 36F92EBAEB0E5F2B010401EF /* StreakCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EF2362D9FFF9B07A45CE6D /* StreakCalendarView.swift */; }; 377C4AA000CE9A0D8CC43DA9 /* GrammarNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D389CA5B5C4E7A12CAEA5BC /* GrammarNote.swift */; }; 39D0666B293DC265CF87B9DD /* SentenceBuilderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 731614CACCB73B6FD592D34A /* SentenceBuilderView.swift */; }; - 3EC2A2F4B9C24B029DA49C40 /* VocabReviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3698CE7ACF148318615293E /* VocabReviewView.swift */; }; 3F4F0C07BE61512CBFBBB203 /* HandwritingCanvas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80D974250C396589656B8443 /* HandwritingCanvas.swift */; }; 4005E258FDF03C8B3A0D53BD /* VocabFlashcardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2931634BEB33B93429CE254F /* VocabFlashcardView.swift */; }; 46943ACFABF329DE1CBFC471 /* TensePill.swift in Sources */ = {isa = PBXBuildFile; fileRef = 102F0E136CDFF8CED710210F /* TensePill.swift */; }; - 4C2649215B81470195F38ED0 /* StoryLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950347251CC94D4A9DFF7CBC /* StoryLibraryView.swift */; }; + 48967E05C65E32F7082716CD /* AnswerChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3EFFA19D0AB2528A868E8ED /* AnswerChecker.swift */; }; 4C3484403FD96E37DA4BEA66 /* NewWordIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72CB5F95DF256DF7CD73269D /* NewWordIntent.swift */; }; - 4DCC5CC233DE4701A12FD7EB /* ListeningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B2179562E54E148C98219D /* ListeningView.swift */; }; + 4C577CF6B137D0A32759A169 /* VerbExampleGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02EB3F9305349775E0EB28B9 /* VerbExampleGenerator.swift */; }; 50E0095A23E119D1AB561232 /* VerbDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DBE662F89F02A0282F5BEE /* VerbDetailView.swift */; }; 519E68D2DF4C80AB96058C0D /* LyricsConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EA01795655C444795577A22 /* LyricsConfirmationView.swift */; }; 51D072AF30F4B12CD3E8F918 /* SRSEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0E6EAFC0D24928BA956FA5 /* SRSEngine.swift */; }; - 53908E41767B438C8BD229CD /* ClozeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A649B04B8B3C49419AD9219C /* ClozeView.swift */; }; 53A0AC57EAC44B676C997374 /* QuizType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626873572466403C0288090D /* QuizType.swift */; }; 5A3246026E68AB6483126D0B /* WeekProgressWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1980E8E439EB76ED7330A90D /* WeekProgressWidget.swift */; }; 5EA915FFA906C5C2938FCADA /* ConjugaWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E325FE0E484DE75009672D02 /* ConjugaWidgetBundle.swift */; }; + 5EE41911F3D17224CAB359ED /* StudyTimerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EC8C4E931AD7A1D87C490BB /* StudyTimerService.swift */; }; 60E86BABE2735E2052B99DF3 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCCC95A95581458E068E0484 /* SettingsView.swift */; }; + 61328552866DE185B15011A9 /* StoryLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15AC27B1E3D332709657F20B /* StoryLibraryView.swift */; }; 615D3128ED6E84EF59BB5AA3 /* LyricsReaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58394296923991E56BAC2B02 /* LyricsReaderView.swift */; }; - 65ABC39F35804C619DAB3200 /* GrammarExercise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E5252282F44ECD9BA70DB8 /* GrammarExercise.swift */; }; 6BB4B0A655E6CB6F82D81B5A /* WeekTestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7EF4161C73AAC67B3A0004 /* WeekTestView.swift */; }; - 6CCC8D51F5524688A4BC5AF8 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA5FE6E149F54A6BA7D01D99 /* ChatView.swift */; }; 6D4A29280FDD99B8E18AF264 /* WidgetDataReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2889F2F81673AFF3A58A07A8 /* WidgetDataReader.swift */; }; 6ED2AC2CAA54688161D4B920 /* SyncStatusMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18CCD69C14D1B0CFBD03C92F /* SyncStatusMonitor.swift */; }; 728702D9AA7A8BDABBA62513 /* ReviewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBCF6FCFA6B00151C2371E77 /* ReviewStore.swift */; }; 760628EFE1CF191CE2FC07DC /* GuideView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C935ECDF8A5D8D6FA541E20 /* GuideView.swift */; }; + 78FE99C5D511737B6877EDD5 /* VocabReviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8D95887B18216FCA71643D6 /* VocabReviewView.swift */; }; 7A13757EA40E81E55640D0FC /* LyricsSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70960F0FD7509310B3F61C48 /* LyricsSearchView.swift */; }; - 7A1B2C3D4E5F60718293A4B5 /* textbook_data.json in Resources */ = {isa = PBXBuildFile; fileRef = 7A1B2C3D4E5F60718293A4C6 /* textbook_data.json */; }; - 7A1B2C3D4E5F60718293A4B6 /* textbook_vocab.json in Resources */ = {isa = PBXBuildFile; fileRef = 7A1B2C3D4E5F60718293A4C7 /* textbook_vocab.json */; }; - 7A1B2C3D4E5F60718293AA01 /* TextbookChapterListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1B2C3D4E5F60718293AA11 /* TextbookChapterListView.swift */; }; - 7A1B2C3D4E5F60718293AA02 /* TextbookChapterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1B2C3D4E5F60718293AA12 /* TextbookChapterView.swift */; }; - 7A1B2C3D4E5F60718293AA03 /* TextbookExerciseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1B2C3D4E5F60718293AA13 /* TextbookExerciseView.swift */; }; - 7A1B2C3D4E5F60718293AA04 /* AnswerChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1B2C3D4E5F60718293AA14 /* AnswerChecker.swift */; }; + 81E4DB9F64F3FF3AB8BCB03A /* TextbookChapterListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 496D851D2D95BEA283C9FD45 /* TextbookChapterListView.swift */; }; 81FA7EBCF18F0AAE0BF385C3 /* VerbListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A63061BBC8998DF33E3DCA2B /* VerbListView.swift */; }; 82F6079BE3F31AC3FB2D1013 /* MultipleChoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA3A33983B2F2078C9EA1A3D /* MultipleChoiceView.swift */; }; 84CCBAE22A9E0DA27AE28723 /* DeckStudyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 631DC0A942DD57C81DECE083 /* DeckStudyView.swift */; }; - 8510085D78E248D885181E80 /* FeatureReferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E9DDEFD53C49E0A48EA655 /* FeatureReferenceView.swift */; }; - 8C1E4E7F36D64EFF8D092AC8 /* StoryGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 327659ABFD524514B6D2D505 /* StoryGenerator.swift */; }; + 8B516215E0842189DEA0DBB1 /* GrammarExercise.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A3099BE24A56F9B1F179E0 /* GrammarExercise.swift */; }; + 8BD4B5A2DDDD4BE4B4A94962 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79576893566932D2BE207528 /* ChatView.swift */; }; 8C43F09F52EA9B537EA27E43 /* CourseReviewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAF7CA1E6F9979CB2C699FDC /* CourseReviewStore.swift */; }; - 8D7CA0F4496B44C28CD5EBD5 /* DictionaryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A04370CF6B4E4D38BE3EB0C7 /* DictionaryService.swift */; }; - 8E3D8E8254CF4213B9D9FAD3 /* StoryReaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8B6081226847E0A0A174BC /* StoryReaderView.swift */; }; - 943728CD3E65FE6CCADB05EE /* StemChangeConjugationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF3A181BF2399D34C23DA933 /* StemChangeConjugationView.swift */; }; + 8DC1CB93333F94C5297D33BF /* GrammarExerciseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07F24ED76059609D6857EC97 /* GrammarExerciseView.swift */; }; + 90B76E34F195223580F7CCCF /* DictionaryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76BE2A08EC694FF784ED5575 /* DictionaryService.swift */; }; 943A94A8C71919F3EFC0E8FA /* UserProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = E536AD1180FE10576EAC884A /* UserProgress.swift */; }; - 968D626462B0ADEC8D7D56AA /* CheckpointExamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1F177F7ABF5D2E4E5466CD /* CheckpointExamView.swift */; }; - 96A3E5FA8EC63123D97365E1 /* TextbookFlowUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEEA84E15880A9D56DE18F33 /* TextbookFlowUITests.swift */; }; 97EFCF6724CE59DC4F0274FD /* AchievementService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C42EA0EBD4CB1E10A82BA25 /* AchievementService.swift */; }; 9D9FD3853C5C969C62AE9999 /* StartupCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B95B276C054DBFE508C4D1 /* StartupCoordinator.swift */; }; + 9F0ACDC1F4ACB1E0D331283D /* CheckpointExamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C67DD1A1CB9B8B5A2BDCED /* CheckpointExamView.swift */; }; + A7DF435F99E66E067F2B33E1 /* ListeningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20D1904DF07E0A6816134CF3 /* ListeningView.swift */; }; A9959AE6C87B4AD21554E401 /* FullTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 711CB7539EF5887F6F7B8B82 /* FullTableView.swift */; }; AAC6F85A1C3B6C1186E1656A /* TenseEndingTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69D98E1564C6538056D81200 /* TenseEndingTable.swift */; }; + ABBE5080E254D1D3E3465E40 /* ConversationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A96C065B8787DEC6818E497 /* ConversationService.swift */; }; + ACE9D8B3116757B5D6F0F766 /* StoryGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 713F23A9C2935408B136C7C7 /* StoryGenerator.swift */; }; + B10A324C06F0957DDE2233F8 /* TextbookChapterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39908548430FDF01D76201FB /* TextbookChapterView.swift */; }; B4603AA6EFB134794AA39BF4 /* LyricsLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC2B1F646394D7C03493F1BF /* LyricsLibraryView.swift */; }; - B73F6EED00304B718C6FEFFA /* GrammarExerciseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F71CA5CD67342F18319DB9A /* GrammarExerciseView.swift */; }; + B48C0015BE53279B0631C2D7 /* ChatLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648436F8326CF95777E2FA58 /* ChatLibraryView.swift */; }; + BA3DE2DA319AA3B572C19E11 /* VerbExampleCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBEEC9CC9A8C502AF5F42914 /* VerbExampleCache.swift */; }; BB48230C3B26EA6E84D2D823 /* DailyProgressRing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180F9D59828C36B44A5E384F /* DailyProgressRing.swift */; }; - BC662C36AC503E00A977CEC1 /* VocabGridTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6584E0FDA939E3B82EECA4B5 /* VocabGridTests.swift */; }; BF0832865857EFDA1D1CDEAD /* SharedModels in Frameworks */ = {isa = PBXBuildFile; productRef = BCCBABD74CADDB118179D8E9 /* SharedModels */; }; C0BAEF49A6270D8F64CF13D6 /* PracticeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C359C051FB157EF447561405 /* PracticeViewModel.swift */; }; C1F84182F12EB5CFF32768B6 /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5983A534E4836F30B5281ACB /* MainTabView.swift */; }; C2B3D97F119EFCE97E3CB1CE /* ConjugaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EB4830F9289AACC82D753F8 /* ConjugaApp.swift */; }; C3851F960C1162239DC2F935 /* CourseQuizView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 143D06606AE10DCA30A140C2 /* CourseQuizView.swift */; }; - C8AF0931F7FD458C80B6EC0D /* ChatLibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5667AA04211A449A9150BD28 /* ChatLibraryView.swift */; }; C8C3880535008764B7117049 /* DataLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DADCA82DDD34DF36D59BB283 /* DataLoader.swift */; }; CAC69045B74249F121643E88 /* AnswerReviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83A8C1A048627C8DEB83C12D /* AnswerReviewView.swift */; }; + CC886125F8ECE72D1AAD4861 /* StoryReaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A48474D969CEF5F573DF09B /* StoryReaderView.swift */; }; CF9E48ADF0501FB79F3DDB7B /* conjuga_data.json in Resources */ = {isa = PBXBuildFile; fileRef = 8C2D88FF9A3B0590B22C7837 /* conjuga_data.json */; }; D3FFE73A5AD27F1759F50727 /* SpeechService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E3AD244327CBF24B7A2752 /* SpeechService.swift */; }; D40B4E919DE379C50265CA9F /* SyncToast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C4B5204F6B8647C816814F0 /* SyncToast.swift */; }; @@ -92,18 +91,14 @@ D6B67523714E0B3618391956 /* CombinedWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43345D6C7EAA4017E3A45935 /* CombinedWidget.swift */; }; D7456B289D135CEB3A15122B /* TestResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAFE27F29412021AEC57E728 /* TestResult.swift */; }; DB73836F751BB2751439E826 /* LyricsSearchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43B8AED76C14A05AF2339C27 /* LyricsSearchService.swift */; }; - DDF58F3899FC4B92BF6587D2 /* StudyTimerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 978FB24DF8D7436CB5210ACE /* StudyTimerService.swift */; }; DF06034A4B2C11BA0C0A84CB /* ConjugaWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9708FF3CF33E4765DB225F93 /* ConjugaWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; DF82C2579F9889DDB06362CC /* ReferenceStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 777C696A841803D5B775B678 /* ReferenceStore.swift */; }; E7BFEE9A90E1300EFF5B1F32 /* HandwritingRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3695075616689E72DBB26D4C /* HandwritingRecognizer.swift */; }; E814A9CF1067313F74B509C6 /* StoreInspector.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8E9833868EB73AF9EB3A611 /* StoreInspector.swift */; }; - E82C743EB1FDF6B67ED22EAD /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A6153A5C7241C1AB0373AA17 /* Foundation.framework */; }; E99473B7DF9BCAE150E9D1E1 /* WidgetDataService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D570252DA3DCDD9217C71863 /* WidgetDataService.swift */; }; - EA07DB964C8940F69C14DE2C /* PronunciationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D535EF6988A24B47B70209A2 /* PronunciationService.swift */; }; ED0401D05A7C2B4C55057A88 /* DailyProgressWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 195DA9CDA703DDFAD1B3CD5A /* DailyProgressWidget.swift */; }; F0D0778207F144D6AC3D39C3 /* CourseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 833516C5D57F164C8660A479 /* CourseView.swift */; }; F59655A8B8FCE6264315DD33 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A014EEC3EE08E945FBBA5335 /* Assets.xcassets */; }; - F7E459C46F25A8A45D7E0DFB /* AllChaptersScreenshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A630C74D28CE1B280C9F296 /* AllChaptersScreenshotTests.swift */; }; F84706B47A2156B2138FB8D5 /* GrammarNotesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F1A6221A35699BD8065D064 /* GrammarNotesView.swift */; }; FC7873F97017532C215DAD34 /* ReviewCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8A63F750065CA4EF36B4D3 /* ReviewCard.swift */; }; /* End PBXBuildFile section */ @@ -116,13 +111,6 @@ remoteGlobalIDString = F73909B4044081DB8F6272AF; remoteInfo = ConjugaWidgetExtension; }; - 6E1F966015DA38BD4E3CE8AF /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = AB7396D9C3E14B65B5238368 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 96127FACA68AE541F5C0F8BC; - remoteInfo = Conjuga; - }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -140,33 +128,34 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 02B2179562E54E148C98219D /* ListeningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListeningView.swift; sourceTree = ""; }; + 02EB3F9305349775E0EB28B9 /* VerbExampleGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerbExampleGenerator.swift; sourceTree = ""; }; 0313D24F96E6A0039C34341F /* DailyLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyLog.swift; sourceTree = ""; }; + 07F24ED76059609D6857EC97 /* GrammarExerciseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrammarExerciseView.swift; sourceTree = ""; }; 0A8A63F750065CA4EF36B4D3 /* ReviewCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewCard.swift; sourceTree = ""; }; 102F0E136CDFF8CED710210F /* TensePill.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TensePill.swift; sourceTree = ""; }; 10C16AA6022E4742898745CE /* TypingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingView.swift; sourceTree = ""; }; - 12E9DDEFD53C49E0A48EA655 /* FeatureReferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureReferenceView.swift; sourceTree = ""; }; 143D06606AE10DCA30A140C2 /* CourseQuizView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseQuizView.swift; sourceTree = ""; }; + 15AC27B1E3D332709657F20B /* StoryLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryLibraryView.swift; sourceTree = ""; }; 16C1F74196C3C5628953BE3F /* Conjuga.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = Conjuga.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 17E5252282F44ECD9BA70DB8 /* GrammarExercise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrammarExercise.swift; sourceTree = ""; }; 180F9D59828C36B44A5E384F /* DailyProgressRing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyProgressRing.swift; sourceTree = ""; }; 18AC3C548BDB9EF8701BE64C /* DashboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardView.swift; sourceTree = ""; }; 18CCD69C14D1B0CFBD03C92F /* SyncStatusMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncStatusMonitor.swift; sourceTree = ""; }; 195DA9CDA703DDFAD1B3CD5A /* DailyProgressWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyProgressWidget.swift; sourceTree = ""; }; 1980E8E439EB76ED7330A90D /* WeekProgressWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeekProgressWidget.swift; sourceTree = ""; }; + 1C3E36BDC2540AF2A67AEEB1 /* FeatureReferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureReferenceView.swift; sourceTree = ""; }; 1C42EA0EBD4CB1E10A82BA25 /* AchievementService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementService.swift; sourceTree = ""; }; 1C4B5204F6B8647C816814F0 /* SyncToast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncToast.swift; sourceTree = ""; }; 1EA0FA4F9149B9D8E197ADE9 /* PracticeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PracticeView.swift; sourceTree = ""; }; 1EB4830F9289AACC82D753F8 /* ConjugaApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConjugaApp.swift; sourceTree = ""; }; - 1F71CA5CD67342F18319DB9A /* GrammarExerciseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrammarExerciseView.swift; sourceTree = ""; }; 1F842EB5E566C74658D918BB /* HandwritingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandwritingView.swift; sourceTree = ""; }; - 27B2A75AAF79A9402AAF3F57 /* ConjugaUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ConjugaUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 20D1904DF07E0A6816134CF3 /* ListeningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListeningView.swift; sourceTree = ""; }; 2889F2F81673AFF3A58A07A8 /* WidgetDataReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDataReader.swift; sourceTree = ""; }; 2931634BEB33B93429CE254F /* VocabFlashcardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VocabFlashcardView.swift; sourceTree = ""; }; - 2A8B6081226847E0A0A174BC /* StoryReaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryReaderView.swift; sourceTree = ""; }; 30EF2362D9FFF9B07A45CE6D /* StreakCalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreakCalendarView.swift; sourceTree = ""; }; - 327659ABFD524514B6D2D505 /* StoryGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryGenerator.swift; sourceTree = ""; }; + 34C67DD1A1CB9B8B5A2BDCED /* CheckpointExamView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckpointExamView.swift; sourceTree = ""; }; 3695075616689E72DBB26D4C /* HandwritingRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandwritingRecognizer.swift; sourceTree = ""; }; + 39908548430FDF01D76201FB /* TextbookChapterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextbookChapterView.swift; sourceTree = ""; }; + 3A96C065B8787DEC6818E497 /* ConversationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationService.swift; sourceTree = ""; }; 3B16FF4C52457CD8CD703532 /* AdaptiveContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveContainer.swift; sourceTree = ""; }; 3BC3247457109FC6BF00D85B /* TenseInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TenseInfo.swift; sourceTree = ""; }; 3CC1AD23158CBABBB753FA1E /* ConjugaWidget.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ConjugaWidget.entitlements; sourceTree = ""; }; @@ -175,9 +164,11 @@ 42ADC600530309A9B147A663 /* IrregularHighlightText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IrregularHighlightText.swift; sourceTree = ""; }; 43345D6C7EAA4017E3A45935 /* CombinedWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombinedWidget.swift; sourceTree = ""; }; 43B8AED76C14A05AF2339C27 /* LyricsSearchService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LyricsSearchService.swift; sourceTree = ""; }; + 496D851D2D95BEA283C9FD45 /* TextbookChapterListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextbookChapterListView.swift; sourceTree = ""; }; 49E3AD244327CBF24B7A2752 /* SpeechService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpeechService.swift; sourceTree = ""; }; + 4AE3D1D8723D5C41D3501774 /* PronunciationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PronunciationService.swift; sourceTree = ""; }; 4D389CA5B5C4E7A12CAEA5BC /* GrammarNote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrammarNote.swift; sourceTree = ""; }; - 5667AA04211A449A9150BD28 /* ChatLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatLibraryView.swift; sourceTree = ""; }; + 4EC8C4E931AD7A1D87C490BB /* StudyTimerService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyTimerService.swift; sourceTree = ""; }; 58394296923991E56BAC2B02 /* LyricsReaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LyricsReaderView.swift; sourceTree = ""; }; 5983A534E4836F30B5281ACB /* MainTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabView.swift; sourceTree = ""; }; 5BF946245110C92F087D81E8 /* PracticeHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PracticeHeaderView.swift; sourceTree = ""; }; @@ -185,64 +176,55 @@ 5E7EF4161C73AAC67B3A0004 /* WeekTestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeekTestView.swift; sourceTree = ""; }; 626873572466403C0288090D /* QuizType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuizType.swift; sourceTree = ""; }; 631DC0A942DD57C81DECE083 /* DeckStudyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeckStudyView.swift; sourceTree = ""; }; - 6584E0FDA939E3B82EECA4B5 /* VocabGridTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = VocabGridTests.swift; sourceTree = ""; }; + 648436F8326CF95777E2FA58 /* ChatLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatLibraryView.swift; sourceTree = ""; }; 69D98E1564C6538056D81200 /* TenseEndingTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TenseEndingTable.swift; sourceTree = ""; }; + 6A48474D969CEF5F573DF09B /* StoryReaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryReaderView.swift; sourceTree = ""; }; 6B9A9F2AB21895E06989A4D5 /* FlashcardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlashcardView.swift; sourceTree = ""; }; 70960F0FD7509310B3F61C48 /* LyricsSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LyricsSearchView.swift; sourceTree = ""; }; 711CB7539EF5887F6F7B8B82 /* FullTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullTableView.swift; sourceTree = ""; }; + 713F23A9C2935408B136C7C7 /* StoryGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryGenerator.swift; sourceTree = ""; }; 72CB5F95DF256DF7CD73269D /* NewWordIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewWordIntent.swift; sourceTree = ""; }; 731614CACCB73B6FD592D34A /* SentenceBuilderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentenceBuilderView.swift; sourceTree = ""; }; + 76BE2A08EC694FF784ED5575 /* DictionaryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DictionaryService.swift; sourceTree = ""; }; 777C696A841803D5B775B678 /* ReferenceStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferenceStore.swift; sourceTree = ""; }; - 7A1B2C3D4E5F60718293A4C6 /* textbook_data.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = textbook_data.json; sourceTree = ""; }; - 7A1B2C3D4E5F60718293A4C7 /* textbook_vocab.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = textbook_vocab.json; sourceTree = ""; }; - 7A1B2C3D4E5F60718293AA11 /* TextbookChapterListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextbookChapterListView.swift; sourceTree = ""; }; - 7A1B2C3D4E5F60718293AA12 /* TextbookChapterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextbookChapterView.swift; sourceTree = ""; }; - 7A1B2C3D4E5F60718293AA13 /* TextbookExerciseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextbookExerciseView.swift; sourceTree = ""; }; - 7A1B2C3D4E5F60718293AA14 /* AnswerChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnswerChecker.swift; sourceTree = ""; }; + 79576893566932D2BE207528 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = ""; }; 7E6AF62A3A949630E067DC22 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 80D974250C396589656B8443 /* HandwritingCanvas.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandwritingCanvas.swift; sourceTree = ""; }; 833516C5D57F164C8660A479 /* CourseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseView.swift; sourceTree = ""; }; 83A8C1A048627C8DEB83C12D /* AnswerReviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnswerReviewView.swift; sourceTree = ""; }; 842DB48F8570C39CDCFF2F57 /* PracticeSessionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PracticeSessionService.swift; sourceTree = ""; }; - 8A630C74D28CE1B280C9F296 /* AllChaptersScreenshotTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AllChaptersScreenshotTests.swift; sourceTree = ""; }; + 854EA2A8D6CF203958BA3C24 /* TextbookExerciseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextbookExerciseView.swift; sourceTree = ""; }; 8C2D88FF9A3B0590B22C7837 /* conjuga_data.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = conjuga_data.json; sourceTree = ""; }; 8C935ECDF8A5D8D6FA541E20 /* GuideView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuideView.swift; sourceTree = ""; }; 8E9BCDBB9BC24F5C8117767E /* WordOfDayWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordOfDayWidget.swift; sourceTree = ""; }; - 8F08E1DC6932D9EA1D380913 /* StemChangeToggleTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StemChangeToggleTests.swift; sourceTree = ""; }; - 950347251CC94D4A9DFF7CBC /* StoryLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryLibraryView.swift; sourceTree = ""; }; 9708FF3CF33E4765DB225F93 /* ConjugaWidgetExtension.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = ConjugaWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; - 978FB24DF8D7436CB5210ACE /* StudyTimerService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StudyTimerService.swift; sourceTree = ""; }; 9E1FB35614B709E6B1D1D017 /* Conjuga.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Conjuga.entitlements; sourceTree = ""; }; A014EEC3EE08E945FBBA5335 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - A04370CF6B4E4D38BE3EB0C7 /* DictionaryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DictionaryService.swift; sourceTree = ""; }; A4B95B276C054DBFE508C4D1 /* StartupCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartupCoordinator.swift; sourceTree = ""; }; - A6153A5C7241C1AB0373AA17 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; A63061BBC8998DF33E3DCA2B /* VerbListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerbListView.swift; sourceTree = ""; }; - A649B04B8B3C49419AD9219C /* ClozeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClozeView.swift; sourceTree = ""; }; + A7CDC5F2660A3009A3ADF048 /* StoryQuizView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryQuizView.swift; sourceTree = ""; }; AC34396050805693AA4AC582 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + B3EFFA19D0AB2528A868E8ED /* AnswerChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnswerChecker.swift; sourceTree = ""; }; BC273716CD14A99EFF8206CA /* course_data.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = course_data.json; sourceTree = ""; }; BCCC95A95581458E068E0484 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; C359C051FB157EF447561405 /* PracticeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PracticeViewModel.swift; sourceTree = ""; }; CBCF6FCFA6B00151C2371E77 /* ReviewStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewStore.swift; sourceTree = ""; }; - CEEA84E15880A9D56DE18F33 /* TextbookFlowUITests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TextbookFlowUITests.swift; sourceTree = ""; }; - CF3A181BF2399D34C23DA933 /* StemChangeConjugationView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StemChangeConjugationView.swift; sourceTree = ""; }; CF6D58AEE2F0DFE0F1829A73 /* SharedModels */ = {isa = PBXFileReference; lastKnownFileType = folder; name = SharedModels; path = SharedModels; sourceTree = SOURCE_ROOT; }; - D3698CE7ACF148318615293E /* VocabReviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VocabReviewView.swift; sourceTree = ""; }; - D535EF6988A24B47B70209A2 /* PronunciationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PronunciationService.swift; sourceTree = ""; }; + D232CDA43CC9218D748BA121 /* ClozeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClozeView.swift; sourceTree = ""; }; D570252DA3DCDD9217C71863 /* WidgetDataService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDataService.swift; sourceTree = ""; }; DA3A33983B2F2078C9EA1A3D /* MultipleChoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipleChoiceView.swift; sourceTree = ""; }; DADCA82DDD34DF36D59BB283 /* DataLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataLoader.swift; sourceTree = ""; }; DAF7CA1E6F9979CB2C699FDC /* CourseReviewStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseReviewStore.swift; sourceTree = ""; }; DAFE27F29412021AEC57E728 /* TestResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestResult.swift; sourceTree = ""; }; - E10603F454E54341AA4B9931 /* ConversationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationService.swift; sourceTree = ""; }; E1DBE662F89F02A0282F5BEE /* VerbDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerbDetailView.swift; sourceTree = ""; }; - E292A183ABB24FFE9CB719C8 /* StoryQuizView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryQuizView.swift; sourceTree = ""; }; E325FE0E484DE75009672D02 /* ConjugaWidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConjugaWidgetBundle.swift; sourceTree = ""; }; E536AD1180FE10576EAC884A /* UserProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProgress.swift; sourceTree = ""; }; + E8D95887B18216FCA71643D6 /* VocabReviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VocabReviewView.swift; sourceTree = ""; }; E8E9833868EB73AF9EB3A611 /* StoreInspector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreInspector.swift; sourceTree = ""; }; E972AA745F44586EF0B1B0C8 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; - EA1F177F7ABF5D2E4E5466CD /* CheckpointExamView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckpointExamView.swift; sourceTree = ""; }; - FA5FE6E149F54A6BA7D01D99 /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = ""; }; + EBEEC9CC9A8C502AF5F42914 /* VerbExampleCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerbExampleCache.swift; sourceTree = ""; }; + F0A3099BE24A56F9B1F179E0 /* GrammarExercise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrammarExercise.swift; sourceTree = ""; }; + F92BCE1A6720E47FCD26BADC /* StemChangeConjugationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StemChangeConjugationView.swift; sourceTree = ""; }; FC2B1F646394D7C03493F1BF /* LyricsLibraryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LyricsLibraryView.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -263,14 +245,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - C5C1BB325D49EE6ED3AC3D5F /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - E82C743EB1FDF6B67ED22EAD /* Foundation.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -282,8 +256,6 @@ 9E1FB35614B709E6B1D1D017 /* Conjuga.entitlements */, 1EB4830F9289AACC82D753F8 /* ConjugaApp.swift */, BC273716CD14A99EFF8206CA /* course_data.json */, - 7A1B2C3D4E5F60718293A4C6 /* textbook_data.json */, - 7A1B2C3D4E5F60718293A4C7 /* textbook_vocab.json */, 7E6AF62A3A949630E067DC22 /* Info.plist */, 353C5DE41FD410FA82E3AED7 /* Models */, 1994867BC8E985795A172854 /* Services */, @@ -297,8 +269,8 @@ 0931AEB5B728C3A03F06A1CA /* Settings */ = { isa = PBXGroup; children = ( + 1C3E36BDC2540AF2A67AEEB1 /* FeatureReferenceView.swift */, BCCC95A95581458E068E0484 /* SettingsView.swift */, - 12E9DDEFD53C49E0A48EA655 /* FeatureReferenceView.swift */, ); path = Settings; sourceTree = ""; @@ -316,24 +288,26 @@ isa = PBXGroup; children = ( 1C42EA0EBD4CB1E10A82BA25 /* AchievementService.swift */, + B3EFFA19D0AB2528A868E8ED /* AnswerChecker.swift */, + 3A96C065B8787DEC6818E497 /* ConversationService.swift */, DAF7CA1E6F9979CB2C699FDC /* CourseReviewStore.swift */, DADCA82DDD34DF36D59BB283 /* DataLoader.swift */, - 7A1B2C3D4E5F60718293AA14 /* AnswerChecker.swift */, + 76BE2A08EC694FF784ED5575 /* DictionaryService.swift */, 3695075616689E72DBB26D4C /* HandwritingRecognizer.swift */, 43B8AED76C14A05AF2339C27 /* LyricsSearchService.swift */, 842DB48F8570C39CDCFF2F57 /* PracticeSessionService.swift */, - E10603F454E54341AA4B9931 /* ConversationService.swift */, - D535EF6988A24B47B70209A2 /* PronunciationService.swift */, - A04370CF6B4E4D38BE3EB0C7 /* DictionaryService.swift */, - 327659ABFD524514B6D2D505 /* StoryGenerator.swift */, - 978FB24DF8D7436CB5210ACE /* StudyTimerService.swift */, + 4AE3D1D8723D5C41D3501774 /* PronunciationService.swift */, 777C696A841803D5B775B678 /* ReferenceStore.swift */, CBCF6FCFA6B00151C2371E77 /* ReviewStore.swift */, 49E3AD244327CBF24B7A2752 /* SpeechService.swift */, 5C0E6EAFC0D24928BA956FA5 /* SRSEngine.swift */, A4B95B276C054DBFE508C4D1 /* StartupCoordinator.swift */, E8E9833868EB73AF9EB3A611 /* StoreInspector.swift */, + 713F23A9C2935408B136C7C7 /* StoryGenerator.swift */, + 4EC8C4E931AD7A1D87C490BB /* StudyTimerService.swift */, 18CCD69C14D1B0CFBD03C92F /* SyncStatusMonitor.swift */, + EBEEC9CC9A8C502AF5F42914 /* VerbExampleCache.swift */, + 02EB3F9305349775E0EB28B9 /* VerbExampleGenerator.swift */, D570252DA3DCDD9217C71863 /* WidgetDataService.swift */, ); path = Services; @@ -351,6 +325,7 @@ isa = PBXGroup; children = ( 0313D24F96E6A0039C34341F /* DailyLog.swift */, + F0A3099BE24A56F9B1F179E0 /* GrammarExercise.swift */, 4D389CA5B5C4E7A12CAEA5BC /* GrammarNote.swift */, 626873572466403C0288090D /* QuizType.swift */, 0A8A63F750065CA4EF36B4D3 /* ReviewCard.swift */, @@ -358,7 +333,6 @@ 3BC3247457109FC6BF00D85B /* TenseInfo.swift */, DAFE27F29412021AEC57E728 /* TestResult.swift */, E536AD1180FE10576EAC884A /* UserProgress.swift */, - 17E5252282F44ECD9BA70DB8 /* GrammarExercise.swift */, ); path = Models; sourceTree = ""; @@ -384,6 +358,16 @@ path = ViewModels; sourceTree = ""; }; + 43E4D263B0AF47E401A51601 /* Stories */ = { + isa = PBXGroup; + children = ( + 15AC27B1E3D332709657F20B /* StoryLibraryView.swift */, + A7CDC5F2660A3009A3ADF048 /* StoryQuizView.swift */, + 6A48474D969CEF5F573DF09B /* StoryReaderView.swift */, + ); + path = Stories; + sourceTree = ""; + }; 4B183AB0C56BC2EC302531E7 /* ConjugaWidget */ = { isa = PBXGroup; children = ( @@ -404,20 +388,20 @@ isa = PBXGroup; children = ( 83A8C1A048627C8DEB83C12D /* AnswerReviewView.swift */, + D232CDA43CC9218D748BA121 /* ClozeView.swift */, 6B9A9F2AB21895E06989A4D5 /* FlashcardView.swift */, 711CB7539EF5887F6F7B8B82 /* FullTableView.swift */, 1F842EB5E566C74658D918BB /* HandwritingView.swift */, + 20D1904DF07E0A6816134CF3 /* ListeningView.swift */, DA3A33983B2F2078C9EA1A3D /* MultipleChoiceView.swift */, 5BF946245110C92F087D81E8 /* PracticeHeaderView.swift */, 1EA0FA4F9149B9D8E197ADE9 /* PracticeView.swift */, - D3698CE7ACF148318615293E /* VocabReviewView.swift */, 731614CACCB73B6FD592D34A /* SentenceBuilderView.swift */, 10C16AA6022E4742898745CE /* TypingView.swift */, + E8D95887B18216FCA71643D6 /* VocabReviewView.swift */, + 8FB89F19B33894DDF27C8EC2 /* Chat */, 895E547BEFB5D0FBF676BE33 /* Lyrics */, - 8A1DED0596E04DDE9536A9A9 /* Stories */, - DFD75E32A53845A693D98F48 /* Chat */, - 02B2179562E54E148C98219D /* ListeningView.swift */, - A649B04B8B3C49419AD9219C /* ClozeView.swift */, + 43E4D263B0AF47E401A51601 /* Stories */, ); path = Practice; sourceTree = ""; @@ -425,9 +409,9 @@ 8102F7FA5BFE6D38B2212AD3 /* Guide */ = { isa = PBXGroup; children = ( + 07F24ED76059609D6857EC97 /* GrammarExerciseView.swift */, 3F1A6221A35699BD8065D064 /* GrammarNotesView.swift */, 8C935ECDF8A5D8D6FA541E20 /* GuideView.swift */, - 1F71CA5CD67342F18319DB9A /* GrammarExerciseView.swift */, ); path = Guide; sourceTree = ""; @@ -443,14 +427,13 @@ path = Lyrics; sourceTree = ""; }; - 8A1DED0596E04DDE9536A9A9 /* Stories */ = { + 8FB89F19B33894DDF27C8EC2 /* Chat */ = { isa = PBXGroup; children = ( - 950347251CC94D4A9DFF7CBC /* StoryLibraryView.swift */, - 2A8B6081226847E0A0A174BC /* StoryReaderView.swift */, - E292A183ABB24FFE9CB719C8 /* StoryQuizView.swift */, + 648436F8326CF95777E2FA58 /* ChatLibraryView.swift */, + 79576893566932D2BE207528 /* ChatView.swift */, ); - path = Stories; + path = Chat; sourceTree = ""; }; A591A3B6F1F13D23D68D7A9D = { @@ -460,8 +443,6 @@ 4B183AB0C56BC2EC302531E7 /* ConjugaWidget */, F7D740BB7D1E23949D4C1AE5 /* Packages */, F605D24E5EA11065FD18AF7E /* Products */, - B442229C0A26C1D531472C7D /* Frameworks */, - C77B065CF67D1F5128E10CC7 /* ConjugaUITests */, ); sourceTree = ""; }; @@ -481,14 +462,6 @@ path = Views; sourceTree = ""; }; - B442229C0A26C1D531472C7D /* Frameworks */ = { - isa = PBXGroup; - children = ( - E772BA9C3FF67FEA9A034B4B /* iOS */, - ); - name = Frameworks; - sourceTree = ""; - }; BA34B77A38B698101DBBE241 /* Dashboard */ = { isa = PBXGroup; children = ( @@ -501,16 +474,16 @@ BE5A40BAC9DD6884C58A2096 /* Course */ = { isa = PBXGroup; children = ( + 34C67DD1A1CB9B8B5A2BDCED /* CheckpointExamView.swift */, 143D06606AE10DCA30A140C2 /* CourseQuizView.swift */, 833516C5D57F164C8660A479 /* CourseView.swift */, 631DC0A942DD57C81DECE083 /* DeckStudyView.swift */, - 7A1B2C3D4E5F60718293AA11 /* TextbookChapterListView.swift */, - 7A1B2C3D4E5F60718293AA12 /* TextbookChapterView.swift */, - 7A1B2C3D4E5F60718293AA13 /* TextbookExerciseView.swift */, + F92BCE1A6720E47FCD26BADC /* StemChangeConjugationView.swift */, + 496D851D2D95BEA283C9FD45 /* TextbookChapterListView.swift */, + 39908548430FDF01D76201FB /* TextbookChapterView.swift */, + 854EA2A8D6CF203958BA3C24 /* TextbookExerciseView.swift */, 2931634BEB33B93429CE254F /* VocabFlashcardView.swift */, 5E7EF4161C73AAC67B3A0004 /* WeekTestView.swift */, - EA1F177F7ABF5D2E4E5466CD /* CheckpointExamView.swift */, - CF3A181BF2399D34C23DA933 /* StemChangeConjugationView.swift */, ); path = Course; sourceTree = ""; @@ -522,41 +495,11 @@ path = Utilities; sourceTree = ""; }; - C77B065CF67D1F5128E10CC7 /* ConjugaUITests */ = { - isa = PBXGroup; - children = ( - CEEA84E15880A9D56DE18F33 /* TextbookFlowUITests.swift */, - 8A630C74D28CE1B280C9F296 /* AllChaptersScreenshotTests.swift */, - 8F08E1DC6932D9EA1D380913 /* StemChangeToggleTests.swift */, - 6584E0FDA939E3B82EECA4B5 /* VocabGridTests.swift */, - ); - name = ConjugaUITests; - path = ConjugaUITests; - sourceTree = ""; - }; - DFD75E32A53845A693D98F48 /* Chat */ = { - isa = PBXGroup; - children = ( - 5667AA04211A449A9150BD28 /* ChatLibraryView.swift */, - FA5FE6E149F54A6BA7D01D99 /* ChatView.swift */, - ); - path = Chat; - sourceTree = ""; - }; - E772BA9C3FF67FEA9A034B4B /* iOS */ = { - isa = PBXGroup; - children = ( - A6153A5C7241C1AB0373AA17 /* Foundation.framework */, - ); - name = iOS; - sourceTree = ""; - }; F605D24E5EA11065FD18AF7E /* Products */ = { isa = PBXGroup; children = ( 16C1F74196C3C5628953BE3F /* Conjuga.app */, 9708FF3CF33E4765DB225F93 /* ConjugaWidgetExtension.appex */, - 27B2A75AAF79A9402AAF3F57 /* ConjugaUITests.xctest */, ); name = Products; sourceTree = ""; @@ -594,24 +537,6 @@ productReference = 16C1F74196C3C5628953BE3F /* Conjuga.app */; productType = "com.apple.product-type.application"; }; - C6CC399BFD5A2574CB9956B4 /* ConjugaUITests */ = { - isa = PBXNativeTarget; - buildConfigurationList = F454EA7279A44C5E151F71BA /* Build configuration list for PBXNativeTarget "ConjugaUITests" */; - buildPhases = ( - 66589E8F78971725CA2066ED /* Sources */, - C5C1BB325D49EE6ED3AC3D5F /* Frameworks */, - 425DC31DA6EF2C4C7A873DAA /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 04C7E3C8079DE56024C2154E /* PBXTargetDependency */, - ); - name = ConjugaUITests; - productName = ConjugaUITests; - productReference = 27B2A75AAF79A9402AAF3F57 /* ConjugaUITests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; F73909B4044081DB8F6272AF /* ConjugaWidgetExtension */ = { isa = PBXNativeTarget; buildConfigurationList = EA7E12CF28EB750C2B8BB2F1 /* Build configuration list for PBXNativeTarget "ConjugaWidgetExtension" */; @@ -664,25 +589,16 @@ 548B46ED3C40F5F28A5ADCC6 /* XCLocalSwiftPackageReference "SharedModels" */, ); preferredProjectObjectVersion = 77; - productRefGroup = F605D24E5EA11065FD18AF7E /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 96127FACA68AE541F5C0F8BC /* Conjuga */, F73909B4044081DB8F6272AF /* ConjugaWidgetExtension */, - C6CC399BFD5A2574CB9956B4 /* ConjugaUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 425DC31DA6EF2C4C7A873DAA /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; B74A8384221C70A670B902D8 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -690,8 +606,6 @@ F59655A8B8FCE6264315DD33 /* Assets.xcassets in Resources */, CF9E48ADF0501FB79F3DDB7B /* conjuga_data.json in Resources */, 2B5B2D63DC9C290F66890A4A /* course_data.json in Resources */, - 7A1B2C3D4E5F60718293A4B5 /* textbook_data.json in Resources */, - 7A1B2C3D4E5F60718293A4B6 /* textbook_vocab.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -704,22 +618,28 @@ files = ( 97EFCF6724CE59DC4F0274FD /* AchievementService.swift in Sources */, 261E582449BED6EF41881B04 /* AdaptiveContainer.swift in Sources */, + 48967E05C65E32F7082716CD /* AnswerChecker.swift in Sources */, CAC69045B74249F121643E88 /* AnswerReviewView.swift in Sources */, + B48C0015BE53279B0631C2D7 /* ChatLibraryView.swift in Sources */, + 8BD4B5A2DDDD4BE4B4A94962 /* ChatView.swift in Sources */, + 9F0ACDC1F4ACB1E0D331283D /* CheckpointExamView.swift in Sources */, + 04C74D9E0ED84BF785A8331C /* ClozeView.swift in Sources */, C2B3D97F119EFCE97E3CB1CE /* ConjugaApp.swift in Sources */, + ABBE5080E254D1D3E3465E40 /* ConversationService.swift in Sources */, C3851F960C1162239DC2F935 /* CourseQuizView.swift in Sources */, 8C43F09F52EA9B537EA27E43 /* CourseReviewStore.swift in Sources */, F0D0778207F144D6AC3D39C3 /* CourseView.swift in Sources */, - 7A1B2C3D4E5F60718293AA01 /* TextbookChapterListView.swift in Sources */, - 7A1B2C3D4E5F60718293AA02 /* TextbookChapterView.swift in Sources */, - 7A1B2C3D4E5F60718293AA03 /* TextbookExerciseView.swift in Sources */, - 7A1B2C3D4E5F60718293AA04 /* AnswerChecker.swift in Sources */, 1C2636790E70B6BC7FFCC904 /* DailyLog.swift in Sources */, BB48230C3B26EA6E84D2D823 /* DailyProgressRing.swift in Sources */, 35A0F6E7124D989312721F7D /* DashboardView.swift in Sources */, C8C3880535008764B7117049 /* DataLoader.swift in Sources */, 84CCBAE22A9E0DA27AE28723 /* DeckStudyView.swift in Sources */, + 90B76E34F195223580F7CCCF /* DictionaryService.swift in Sources */, + 14242FD1F500D296D41E927C /* FeatureReferenceView.swift in Sources */, D4DDE25FB2DAD315370AFB74 /* FlashcardView.swift in Sources */, A9959AE6C87B4AD21554E401 /* FullTableView.swift in Sources */, + 8B516215E0842189DEA0DBB1 /* GrammarExercise.swift in Sources */, + 8DC1CB93333F94C5297D33BF /* GrammarExerciseView.swift in Sources */, 377C4AA000CE9A0D8CC43DA9 /* GrammarNote.swift in Sources */, F84706B47A2156B2138FB8D5 /* GrammarNotesView.swift in Sources */, 760628EFE1CF191CE2FC07DC /* GuideView.swift in Sources */, @@ -727,6 +647,7 @@ E7BFEE9A90E1300EFF5B1F32 /* HandwritingRecognizer.swift in Sources */, 33E885EB38C3BB0CB058871A /* HandwritingView.swift in Sources */, 28D2F489F1927BCCC2B56086 /* IrregularHighlightText.swift in Sources */, + A7DF435F99E66E067F2B33E1 /* ListeningView.swift in Sources */, 519E68D2DF4C80AB96058C0D /* LyricsConfirmationView.swift in Sources */, B4603AA6EFB134794AA39BF4 /* LyricsLibraryView.swift in Sources */, 615D3128ED6E84EF59BB5AA3 /* LyricsReaderView.swift in Sources */, @@ -739,6 +660,7 @@ 352A5BAA6E406AA5850653A4 /* PracticeSessionService.swift in Sources */, 1A230C01A045F0C095BFBD35 /* PracticeView.swift in Sources */, C0BAEF49A6270D8F64CF13D6 /* PracticeViewModel.swift in Sources */, + 0D0D3B5CC128D1A1D1252282 /* PronunciationService.swift in Sources */, 53A0AC57EAC44B676C997374 /* QuizType.swift in Sources */, DF82C2579F9889DDB06362CC /* ReferenceStore.swift in Sources */, FC7873F97017532C215DAD34 /* ReviewCard.swift in Sources */, @@ -748,39 +670,33 @@ 60E86BABE2735E2052B99DF3 /* SettingsView.swift in Sources */, D3FFE73A5AD27F1759F50727 /* SpeechService.swift in Sources */, 9D9FD3853C5C969C62AE9999 /* StartupCoordinator.swift in Sources */, + 20B71403A8D305C29C73ADA2 /* StemChangeConjugationView.swift in Sources */, E814A9CF1067313F74B509C6 /* StoreInspector.swift in Sources */, + ACE9D8B3116757B5D6F0F766 /* StoryGenerator.swift in Sources */, + 61328552866DE185B15011A9 /* StoryLibraryView.swift in Sources */, + 0AD63CAED7C568590A16E879 /* StoryQuizView.swift in Sources */, + CC886125F8ECE72D1AAD4861 /* StoryReaderView.swift in Sources */, 36F92EBAEB0E5F2B010401EF /* StreakCalendarView.swift in Sources */, + 5EE41911F3D17224CAB359ED /* StudyTimerService.swift in Sources */, 6ED2AC2CAA54688161D4B920 /* SyncStatusMonitor.swift in Sources */, D40B4E919DE379C50265CA9F /* SyncToast.swift in Sources */, AAC6F85A1C3B6C1186E1656A /* TenseEndingTable.swift in Sources */, 0A89DCC82BE11605CB866DEF /* TenseInfo.swift in Sources */, 46943ACFABF329DE1CBFC471 /* TensePill.swift in Sources */, D7456B289D135CEB3A15122B /* TestResult.swift in Sources */, + 81E4DB9F64F3FF3AB8BCB03A /* TextbookChapterListView.swift in Sources */, + B10A324C06F0957DDE2233F8 /* TextbookChapterView.swift in Sources */, + 354631F309E625046A3A436B /* TextbookExerciseView.swift in Sources */, 27BA7FA9356467846A07697D /* TypingView.swift in Sources */, 943A94A8C71919F3EFC0E8FA /* UserProgress.swift in Sources */, 50E0095A23E119D1AB561232 /* VerbDetailView.swift in Sources */, + BA3DE2DA319AA3B572C19E11 /* VerbExampleCache.swift in Sources */, + 4C577CF6B137D0A32759A169 /* VerbExampleGenerator.swift in Sources */, 81FA7EBCF18F0AAE0BF385C3 /* VerbListView.swift in Sources */, 4005E258FDF03C8B3A0D53BD /* VocabFlashcardView.swift in Sources */, + 78FE99C5D511737B6877EDD5 /* VocabReviewView.swift in Sources */, 6BB4B0A655E6CB6F82D81B5A /* WeekTestView.swift in Sources */, - 968D626462B0ADEC8D7D56AA /* CheckpointExamView.swift in Sources */, E99473B7DF9BCAE150E9D1E1 /* WidgetDataService.swift in Sources */, - DDF58F3899FC4B92BF6587D2 /* StudyTimerService.swift in Sources */, - 8C1E4E7F36D64EFF8D092AC8 /* StoryGenerator.swift in Sources */, - 4C2649215B81470195F38ED0 /* StoryLibraryView.swift in Sources */, - 8E3D8E8254CF4213B9D9FAD3 /* StoryReaderView.swift in Sources */, - 12D2C9311D5C4764B48B1754 /* StoryQuizView.swift in Sources */, - 8D7CA0F4496B44C28CD5EBD5 /* DictionaryService.swift in Sources */, - 3EC2A2F4B9C24B029DA49C40 /* VocabReviewView.swift in Sources */, - 53908E41767B438C8BD229CD /* ClozeView.swift in Sources */, - 65ABC39F35804C619DAB3200 /* GrammarExercise.swift in Sources */, - B73F6EED00304B718C6FEFFA /* GrammarExerciseView.swift in Sources */, - EA07DB964C8940F69C14DE2C /* PronunciationService.swift in Sources */, - 4DCC5CC233DE4701A12FD7EB /* ListeningView.swift in Sources */, - 35D6404C60C249D5995AD895 /* ConversationService.swift in Sources */, - C8AF0931F7FD458C80B6EC0D /* ChatLibraryView.swift in Sources */, - 6CCC8D51F5524688A4BC5AF8 /* ChatView.swift in Sources */, - 8510085D78E248D885181E80 /* FeatureReferenceView.swift in Sources */, - 943728CD3E65FE6CCADB05EE /* StemChangeConjugationView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -798,26 +714,9 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 66589E8F78971725CA2066ED /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 96A3E5FA8EC63123D97365E1 /* TextbookFlowUITests.swift in Sources */, - F7E459C46F25A8A45D7E0DFB /* AllChaptersScreenshotTests.swift in Sources */, - 1B0B3B2C771AD72E25B3493C /* StemChangeToggleTests.swift in Sources */, - BC662C36AC503E00A977CEC1 /* VocabGridTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 04C7E3C8079DE56024C2154E /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = Conjuga; - target = 96127FACA68AE541F5C0F8BC /* Conjuga */; - targetProxy = 6E1F966015DA38BD4E3CE8AF /* PBXContainerItemProxy */; - }; 0B370CF10B68E386093E5BB2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = F73909B4044081DB8F6272AF /* ConjugaWidgetExtension */; @@ -966,24 +865,6 @@ }; name = Release; }; - A923186E44A25A8086B27A34 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_OBJC_WEAK = NO; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = V3PF3M6B6U; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; - PRODUCT_BUNDLE_IDENTIFIER = com.conjuga.app.uitests; - SDKROOT = iphoneos; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = Conjuga; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; B9223DC55BB69E9AB81B59AE /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1049,23 +930,6 @@ }; name = Debug; }; - DB8C0F513F77A50F2EF2D561 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_OBJC_WEAK = NO; - CODE_SIGN_IDENTITY = "iPhone Developer"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = V3PF3M6B6U; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; - PRODUCT_BUNDLE_IDENTIFIER = com.conjuga.app.uitests; - SDKROOT = iphoneos; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = Conjuga; - }; - name = Debug; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1096,15 +960,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; - F454EA7279A44C5E151F71BA /* Build configuration list for PBXNativeTarget "ConjugaUITests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - A923186E44A25A8086B27A34 /* Release */, - DB8C0F513F77A50F2EF2D561 /* Debug */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ diff --git a/Conjuga/Conjuga.xcodeproj/xcshareddata/xcschemes/Conjuga.xcscheme b/Conjuga/Conjuga.xcodeproj/xcshareddata/xcschemes/Conjuga.xcscheme index 4822c84..d7181a7 100644 --- a/Conjuga/Conjuga.xcodeproj/xcshareddata/xcschemes/Conjuga.xcscheme +++ b/Conjuga/Conjuga.xcodeproj/xcshareddata/xcschemes/Conjuga.xcscheme @@ -53,16 +53,6 @@ - - - - diff --git a/Conjuga/Conjuga/ConjugaApp.swift b/Conjuga/Conjuga/ConjugaApp.swift index f94bda4..21827a7 100644 --- a/Conjuga/Conjuga/ConjugaApp.swift +++ b/Conjuga/Conjuga/ConjugaApp.swift @@ -40,6 +40,7 @@ struct ConjugaApp: App { @State private var syncMonitor = SyncStatusMonitor() @State private var studyTimer = StudyTimerService() @State private var dictionary = DictionaryService() + @State private var verbExampleCache = VerbExampleCache() let localContainer: ModelContainer let cloudContainer: ModelContainer @@ -113,6 +114,7 @@ struct ConjugaApp: App { .environment(\.cloudModelContextProvider, { cloudContainer.mainContext }) .environment(studyTimer) .environment(dictionary) + .environment(verbExampleCache) .task { let needsSeed = await DataLoader.needsSeeding(container: localContainer) if needsSeed { diff --git a/Conjuga/Conjuga/Info.plist b/Conjuga/Conjuga/Info.plist index 9a8967e..74e99bb 100644 --- a/Conjuga/Conjuga/Info.plist +++ b/Conjuga/Conjuga/Info.plist @@ -2,6 +2,10 @@ + BGTaskSchedulerPermittedIdentifiers + + com.conjuga.app.refresh + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -22,21 +26,13 @@ 1 LSApplicationCategoryType public.app-category.education - UILaunchScreen - - NSSpeechRecognitionUsageDescription - Conjuga uses speech recognition to check your Spanish pronunciation and transcribe what you say during listening exercises. - NSMicrophoneUsageDescription - Conjuga needs microphone access to record your voice for pronunciation practice. UIBackgroundModes fetch remote-notification - BGTaskSchedulerPermittedIdentifiers - - com.conjuga.app.refresh - + UILaunchScreen + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/Conjuga/Conjuga/Services/VerbExampleCache.swift b/Conjuga/Conjuga/Services/VerbExampleCache.swift new file mode 100644 index 0000000..ebb560d --- /dev/null +++ b/Conjuga/Conjuga/Services/VerbExampleCache.swift @@ -0,0 +1,65 @@ +import Foundation +import SharedModels + +/// Disk-backed cache for verb example sentences (Issue #27). One JSON file +/// in the Caches directory keyed by verb id; lazy-loaded on first access and +/// write-through on every generation. Matches DictionaryService's disk pattern. +/// +/// Cache eviction by the OS is acceptable because contents are regenerable. +@MainActor +@Observable +final class VerbExampleCache { + + private var store: [Int: [VerbExample]] = [:] + private var isLoaded = false + + init() {} + + // MARK: - Public API + + /// Look up cached examples for a verb; returns nil on miss. + /// Safe to call before `loadIfNeeded()`; it triggers the disk load itself. + func examples(for verbId: Int) -> [VerbExample]? { + loadIfNeeded() + return store[verbId] + } + + /// Store newly generated examples and persist to disk. + func setExamples(_ examples: [VerbExample], for verbId: Int) { + loadIfNeeded() + store[verbId] = examples + save() + } + + // MARK: - Disk I/O + + private static var cacheURL: URL { + FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] + .appendingPathComponent("verb_examples.json") + } + + private func loadIfNeeded() { + guard !isLoaded else { return } + defer { isLoaded = true } + + guard let data = try? Data(contentsOf: Self.cacheURL), + let decoded = try? JSONDecoder().decode([String: [VerbExample]].self, from: data) + else { return } + + // Persisted with String keys because JSON object keys are strings; + // convert back to Int for in-memory lookup. + var rebuilt: [Int: [VerbExample]] = [:] + for (key, value) in decoded { + if let id = Int(key) { + rebuilt[id] = value + } + } + store = rebuilt + } + + private func save() { + let serialized = Dictionary(uniqueKeysWithValues: store.map { (String($0.key), $0.value) }) + guard let data = try? JSONEncoder().encode(serialized) else { return } + try? data.write(to: Self.cacheURL) + } +} diff --git a/Conjuga/Conjuga/Services/VerbExampleGenerator.swift b/Conjuga/Conjuga/Services/VerbExampleGenerator.swift new file mode 100644 index 0000000..1a87187 --- /dev/null +++ b/Conjuga/Conjuga/Services/VerbExampleGenerator.swift @@ -0,0 +1,78 @@ +import Foundation +import FoundationModels +import SharedModels + +/// Generates a set of example sentences for a single verb, one per core tense +/// (Issue #27). Mirrors the StoryGenerator pattern: @Generable response types, +/// a static availability flag, and a single generate(...) entry point. +@MainActor +struct VerbExampleGenerator { + + // MARK: - Generable Types + + @Generable + struct GeneratedExampleSet { + @Guide( + description: "Six example sentences, one per tense in the exact order requested. Each sentence must actually use the target verb conjugated in that tense.", + .count(6) + ) + var examples: [GeneratedExample] + } + + @Generable + struct GeneratedExample { + @Guide(description: "The tense id this sentence demonstrates. Must match one of the ids provided in the prompt exactly (e.g. ind_presente).") + var tenseId: String + + @Guide(description: "A natural Spanish sentence, 6-14 words, that uses the target verb in the specified tense. For imperative tenses use tú or nosotros — never yo.") + var spanish: String + + @Guide(description: "An accurate, idiomatic English translation of the Spanish sentence.") + var english: String + } + + // MARK: - Generation + + /// Generate one example per tense in `tenseIds`. Returns the examples in the + /// same order as `tenseIds`, filling in placeholders for any the model skipped. + static func generate( + verbInfinitive: String, + verbEnglish: String, + tenseIds: [String] + ) async throws -> [VerbExample] { + let tenseList = tenseIds + .compactMap { id in TenseInfo.find(id).map { "\(id) (\($0.english))" } } + .joined(separator: ", ") + + let session = LanguageModelSession(instructions: """ + You are a Spanish language teacher writing short example sentences for a learner. + The learner is studying the verb "\(verbInfinitive)" (to \(verbEnglish)). + Write one sentence per requested tense. Each sentence must: + - Actually conjugate "\(verbInfinitive)" in that tense (not just mention it). + - Be 6-14 words, natural and everyday. + - Use vocabulary appropriate for intermediate learners. + - Vary subjects and contexts across the set; do not reuse the same subject twice. + For imperative tenses, address "tú" or "nosotros" — never "yo". + """) + + let prompt = """ + Write example sentences for "\(verbInfinitive)" in these tenses, in this order: + \(tenseList) + + Return one GeneratedExample per tense with the matching tenseId, spanish, and english. + """ + + let response = try await session.respond(to: prompt, generating: GeneratedExampleSet.self) + + // Map by tenseId and return in the caller's requested order so the UI + // renders a predictable sequence even if the model shuffles its output. + let byTense = Dictionary(uniqueKeysWithValues: response.content.examples.map { + ($0.tenseId, VerbExample(tenseId: $0.tenseId, spanish: $0.spanish, english: $0.english)) + }) + return tenseIds.compactMap { byTense[$0] } + } + + static var isAvailable: Bool { + SystemLanguageModel.default.availability == .available + } +} diff --git a/Conjuga/Conjuga/Views/Verbs/VerbDetailView.swift b/Conjuga/Conjuga/Views/Verbs/VerbDetailView.swift index a600d11..881b0ce 100644 --- a/Conjuga/Conjuga/Views/Verbs/VerbDetailView.swift +++ b/Conjuga/Conjuga/Views/Verbs/VerbDetailView.swift @@ -4,10 +4,31 @@ import SwiftData struct VerbDetailView: View { @Environment(\.modelContext) private var modelContext + @Environment(VerbExampleCache.self) private var exampleCache @State private var speechService = SpeechService() let verb: Verb @State private var selectedTense: TenseInfo = TenseInfo.all[0] + @State private var examples: [VerbExample] = [] + @State private var examplesState: ExamplesState = .idle + + private enum ExamplesState: Equatable { + case idle + case loading + case loaded + case unavailable + case failed(String) + } + + private static let exampleTenseIds: [String] = [ + TenseID.ind_presente.rawValue, + TenseID.ind_preterito.rawValue, + TenseID.ind_imperfecto.rawValue, + TenseID.ind_futuro.rawValue, + TenseID.subj_presente.rawValue, + TenseID.imp_afirmativo.rawValue, + ] + private var formsForTense: [VerbForm] { ReferenceStore(context: modelContext).fetchForms(verbId: verb.id, tenseId: selectedTense.id) } @@ -66,6 +87,8 @@ struct VerbDetailView: View { } header: { Text("Conjugation") } + + examplesSection } .navigationTitle(verb.infinitive) .toolbar { @@ -78,6 +101,94 @@ struct VerbDetailView: View { .tint(.secondary) } } + .task(id: verb.id) { + await loadExamples() + } + } + + // MARK: - Examples + + @ViewBuilder + private var examplesSection: some View { + Section { + switch examplesState { + case .idle, .loading: + HStack(spacing: 10) { + ProgressView() + Text("Generating examples…") + .font(.caption) + .foregroundStyle(.secondary) + } + case .unavailable: + Label("Examples require Apple Intelligence on this device.", systemImage: "sparkles") + .font(.caption) + .foregroundStyle(.secondary) + case .failed(let message): + Label(message, systemImage: "exclamationmark.triangle") + .font(.caption) + .foregroundStyle(.secondary) + case .loaded: + if examples.isEmpty { + Label("No examples available.", systemImage: "text.quote") + .font(.caption) + .foregroundStyle(.secondary) + } else { + ForEach(Array(examples.enumerated()), id: \.offset) { _, example in + VStack(alignment: .leading, spacing: 4) { + if let info = TenseInfo.find(example.tenseId) { + Text(info.english) + .font(.caption2.weight(.semibold)) + .foregroundStyle(.tint) + } + Text(example.spanish) + .font(.body) + .italic() + Text(example.english) + .font(.caption) + .foregroundStyle(.secondary) + } + .padding(.vertical, 2) + } + } + } + } header: { + Text("Examples") + } + } + + private func loadExamples() async { + // Reset state when navigating between verbs via NavigationSplitView. + examples = [] + examplesState = .idle + + if let cached = exampleCache.examples(for: verb.id), !cached.isEmpty { + examples = cached + examplesState = .loaded + return + } + + guard VerbExampleGenerator.isAvailable else { + examplesState = .unavailable + return + } + + examplesState = .loading + do { + let generated = try await VerbExampleGenerator.generate( + verbInfinitive: verb.infinitive, + verbEnglish: verb.english, + tenseIds: Self.exampleTenseIds + ) + guard !generated.isEmpty else { + examplesState = .failed("Could not generate examples.") + return + } + exampleCache.setExamples(generated, for: verb.id) + examples = generated + examplesState = .loaded + } catch { + examplesState = .failed("Could not generate examples.") + } } } @@ -86,4 +197,5 @@ struct VerbDetailView: View { VerbDetailView(verb: Verb(id: 1, infinitive: "hablar", english: "to speak", rank: 1, ending: "ar", reflexive: 0, level: "basic")) } .modelContainer(for: [Verb.self, VerbForm.self], inMemory: true) + .environment(VerbExampleCache()) } diff --git a/Conjuga/SharedModels/Sources/SharedModels/VerbExample.swift b/Conjuga/SharedModels/Sources/SharedModels/VerbExample.swift new file mode 100644 index 0000000..548963f --- /dev/null +++ b/Conjuga/SharedModels/Sources/SharedModels/VerbExample.swift @@ -0,0 +1,17 @@ +import Foundation + +/// A single Spanish / English example sentence pair for a verb at a specific tense. +/// Used by the Verb detail view (Issue #27). Generated at runtime via Foundation +/// Models and cached to disk; shape is intentionally simple Codable for easy +/// JSON persistence and cross-module sharing. +public struct VerbExample: Codable, Hashable, Sendable { + public let tenseId: String + public let spanish: String + public let english: String + + public init(tenseId: String, spanish: String, english: String) { + self.tenseId = tenseId + self.spanish = spanish + self.english = english + } +}