diff --git a/.gitignore b/.gitignore index 45b9600..ec080c2 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,9 @@ scrape/ *.pdf *.epub epub_extract/ +# Exception: weekly course-material PDFs are bundled into the app and must +# travel with the repo so fresh clones build with the feature working. +!Conjuga/Conjuga/CourseMaterials/*.pdf # Textbook extraction artifacts — regenerate locally via run_pipeline.sh. # Scripts are committed; their generated outputs are not. diff --git a/Conjuga/Conjuga.xcodeproj/project.pbxproj b/Conjuga/Conjuga.xcodeproj/project.pbxproj index bd605aa..dfcd8e8 100644 --- a/Conjuga/Conjuga.xcodeproj/project.pbxproj +++ b/Conjuga/Conjuga.xcodeproj/project.pbxproj @@ -31,7 +31,9 @@ 2B76DE203AEEBAE4536BD4F0 /* NounMultipleChoicePracticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F1F30E0A920FD7AA4F952CE /* NounMultipleChoicePracticeView.swift */; }; 2C7ABAB4D88E3E3B0EAD1EF7 /* PracticeHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BF946245110C92F087D81E8 /* PracticeHeaderView.swift */; }; 33E885EB38C3BB0CB058871A /* HandwritingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F842EB5E566C74658D918BB /* HandwritingView.swift */; }; + 345AB6723C15590031B75A01 /* Beginner_I_W2.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 9518665E390E8BE4D7D957FE /* Beginner_I_W2.pdf */; }; 352A5BAA6E406AA5850653A4 /* PracticeSessionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842DB48F8570C39CDCFF2F57 /* PracticeSessionService.swift */; }; + 3535A6B73D03486EB2E43823 /* Beginner_I_W1.pdf in Resources */ = {isa = PBXBuildFile; fileRef = CF62E9D108E2E564BA61A694 /* Beginner_I_W1.pdf */; }; 354631F309E625046A3A436B /* TextbookExerciseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 854EA2A8D6CF203958BA3C24 /* TextbookExerciseView.swift */; }; 35A0F6E7124D989312721F7D /* DashboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18AC3C548BDB9EF8701BE64C /* DashboardView.swift */; }; 362F2159FC4C6E2A3DCBF07F /* YouTubeKit in Frameworks */ = {isa = PBXBuildFile; productRef = 08D6313690BEE4E2F18EADC3 /* YouTubeKit */; }; @@ -49,10 +51,13 @@ 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 */; }; + 5224FD701320B7DBCEFDD95B /* Beginner_I_W4.pdf in Resources */ = {isa = PBXBuildFile; fileRef = B07767E9BC347964B98C0F3D /* Beginner_I_W4.pdf */; }; 53A0AC57EAC44B676C997374 /* QuizType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 626873572466403C0288090D /* QuizType.swift */; }; 55FBABFE026058CFE8B40CB6 /* LexemeReviewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4B8AB6EC4F3EB86C732243D /* LexemeReviewStore.swift */; }; 5A3246026E68AB6483126D0B /* WeekProgressWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1980E8E439EB76ED7330A90D /* WeekProgressWidget.swift */; }; 5BB8A0C1A8A6374B04F50781 /* VocabMultipleChoicePracticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 878D04B21589BAB7CF8EA0AF /* VocabMultipleChoicePracticeView.swift */; }; + 5C1C0011594A2C06BCD777A4 /* Beginner_I_W7.pdf in Resources */ = {isa = PBXBuildFile; fileRef = E8CEEA98D1805C5BA20A4559 /* Beginner_I_W7.pdf */; }; + 5CBAD967B3545EA7560761C6 /* Beginner_I_W3.pdf in Resources */ = {isa = PBXBuildFile; fileRef = EBDEA606F038A4A3DF1967E3 /* Beginner_I_W3.pdf */; }; 5EA89448E97D76CFC97DC4E2 /* VocabFlashcardPracticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB6453FB9DCFAEC1C4E42D83 /* VocabFlashcardPracticeView.swift */; }; 5EA915FFA906C5C2938FCADA /* ConjugaWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E325FE0E484DE75009672D02 /* ConjugaWidgetBundle.swift */; }; 5EE41911F3D17224CAB359ED /* StudyTimerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EC8C4E931AD7A1D87C490BB /* StudyTimerService.swift */; }; @@ -87,6 +92,7 @@ 97A2088134FC6CB41C507182 /* reflexive_verbs.json in Resources */ = {isa = PBXBuildFile; fileRef = 3644B5ED77F29A65877D926A /* reflexive_verbs.json */; }; 97EFCF6724CE59DC4F0274FD /* AchievementService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C42EA0EBD4CB1E10A82BA25 /* AchievementService.swift */; }; 983988CE911C0FC5D869C516 /* youtube_videos.md in Resources */ = {isa = PBXBuildFile; fileRef = A6EC7C278E4287D91A0DB355 /* youtube_videos.md */; }; + 995C466AE3C95A95DB9457A1 /* Beginner_I_W6.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 8B54119366CF052443A8C080 /* Beginner_I_W6.pdf */; }; 9D9FD3853C5C969C62AE9999 /* StartupCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4B95B276C054DBFE508C4D1 /* StartupCoordinator.swift */; }; 9F0ACDC1F4ACB1E0D331283D /* CheckpointExamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C67DD1A1CB9B8B5A2BDCED /* CheckpointExamView.swift */; }; A4DA8CF1957A4FE161830AB2 /* ReflexiveVerbStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940826D9ED5C18D2C4E7B2C7 /* ReflexiveVerbStore.swift */; }; @@ -106,6 +112,7 @@ BB48230C3B26EA6E84D2D823 /* DailyProgressRing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180F9D59828C36B44A5E384F /* DailyProgressRing.swift */; }; BF0832865857EFDA1D1CDEAD /* SharedModels in Frameworks */ = {isa = PBXBuildFile; productRef = BCCBABD74CADDB118179D8E9 /* SharedModels */; }; C0BAEF49A6270D8F64CF13D6 /* PracticeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C359C051FB157EF447561405 /* PracticeViewModel.swift */; }; + C0DF369A6E30F01514A78CA1 /* Beginner_I_W5.pdf in Resources */ = {isa = PBXBuildFile; fileRef = EBBA697AFA8988BBB15C4717 /* Beginner_I_W5.pdf */; }; C1F84182F12EB5CFF32768B6 /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5983A534E4836F30B5281ACB /* MainTabView.swift */; }; C2B3D97F119EFCE97E3CB1CE /* ConjugaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EB4830F9289AACC82D753F8 /* ConjugaApp.swift */; }; C384F12E910EE909AA97ECD9 /* LexemeSessionQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE79D3EC48233CAEC2E39F81 /* LexemeSessionQueue.swift */; }; @@ -129,9 +136,11 @@ E7BFEE9A90E1300EFF5B1F32 /* HandwritingRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3695075616689E72DBB26D4C /* HandwritingRecognizer.swift */; }; E814A9CF1067313F74B509C6 /* StoreInspector.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8E9833868EB73AF9EB3A611 /* StoreInspector.swift */; }; E99473B7DF9BCAE150E9D1E1 /* WidgetDataService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D570252DA3DCDD9217C71863 /* WidgetDataService.swift */; }; + EB7CF33BA416BD7B5D995FF4 /* Beginner_I_W8.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 83AA457D61B9D2C86C301236 /* Beginner_I_W8.pdf */; }; ED0401D05A7C2B4C55057A88 /* DailyProgressWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 195DA9CDA703DDFAD1B3CD5A /* DailyProgressWidget.swift */; }; F0D0778207F144D6AC3D39C3 /* CourseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 833516C5D57F164C8660A479 /* CourseView.swift */; }; F15E2E02DD6261323BB75789 /* textbook_data.json in Resources */ = {isa = PBXBuildFile; fileRef = 539736EB2AB8D149ED0F9C39 /* textbook_data.json */; }; + F22FD38D5CD6A89CC5940B0E /* CourseMaterialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42513164C858C8CBE1A70AF /* CourseMaterialView.swift */; }; F26F3BF58CF557D5A65EE901 /* youtube_videos.json in Resources */ = {isa = PBXBuildFile; fileRef = 6658C35E454C137B53FC05A4 /* youtube_videos.json */; }; F59655A8B8FCE6264315DD33 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A014EEC3EE08E945FBBA5335 /* Assets.xcassets */; }; F84706B47A2156B2138FB8D5 /* GrammarNotesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F1A6221A35699BD8065D064 /* GrammarNotesView.swift */; }; @@ -240,15 +249,18 @@ 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 = ""; }; + 83AA457D61B9D2C86C301236 /* Beginner_I_W8.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = Beginner_I_W8.pdf; sourceTree = ""; }; 841A3015E99E1E2FF19FF8EA /* VocabSessionQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VocabSessionQueue.swift; sourceTree = ""; }; 842DB48F8570C39CDCFF2F57 /* PracticeSessionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PracticeSessionService.swift; sourceTree = ""; }; 854EA2A8D6CF203958BA3C24 /* TextbookExerciseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextbookExerciseView.swift; sourceTree = ""; }; 86A57770AAE3FAC5DCA329F9 /* VideoActionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoActionsView.swift; sourceTree = ""; }; 878D04B21589BAB7CF8EA0AF /* VocabMultipleChoicePracticeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VocabMultipleChoicePracticeView.swift; sourceTree = ""; }; + 8B54119366CF052443A8C080 /* Beginner_I_W6.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = Beginner_I_W6.pdf; 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 = ""; }; 940826D9ED5C18D2C4E7B2C7 /* ReflexiveVerbStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReflexiveVerbStore.swift; sourceTree = ""; }; + 9518665E390E8BE4D7D957FE /* Beginner_I_W2.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = Beginner_I_W2.pdf; sourceTree = ""; }; 9708FF3CF33E4765DB225F93 /* ConjugaWidgetExtension.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = ConjugaWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; @@ -261,6 +273,7 @@ AC34396050805693AA4AC582 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; AEC6005F9D3D462B33803E7A /* LexemeStudyGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LexemeStudyGroup.swift; sourceTree = ""; }; AFF65B05E7CEC386F121973E /* YouTubeVideoStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YouTubeVideoStore.swift; sourceTree = ""; }; + B07767E9BC347964B98C0F3D /* Beginner_I_W4.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = Beginner_I_W4.pdf; 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 = ""; }; @@ -273,6 +286,7 @@ CB6453FB9DCFAEC1C4E42D83 /* VocabFlashcardPracticeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VocabFlashcardPracticeView.swift; sourceTree = ""; }; CBCF6FCFA6B00151C2371E77 /* ReviewStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewStore.swift; sourceTree = ""; }; CCC782D61A0763072E4964B6 /* VerbReviewStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerbReviewStore.swift; sourceTree = ""; }; + CF62E9D108E2E564BA61A694 /* Beginner_I_W1.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = Beginner_I_W1.pdf; sourceTree = ""; }; CF6D58AEE2F0DFE0F1829A73 /* SharedModels */ = {isa = PBXFileReference; lastKnownFileType = folder; name = SharedModels; path = SharedModels; sourceTree = SOURCE_ROOT; }; D232CDA43CC9218D748BA121 /* ClozeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClozeView.swift; sourceTree = ""; }; D4B8AB6EC4F3EB86C732243D /* LexemeReviewStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LexemeReviewStore.swift; sourceTree = ""; }; @@ -283,12 +297,16 @@ DAFE27F29412021AEC57E728 /* TestResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestResult.swift; sourceTree = ""; }; E1DBE662F89F02A0282F5BEE /* VerbDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerbDetailView.swift; sourceTree = ""; }; E325FE0E484DE75009672D02 /* ConjugaWidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConjugaWidgetBundle.swift; sourceTree = ""; }; + E42513164C858C8CBE1A70AF /* CourseMaterialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseMaterialView.swift; sourceTree = ""; }; E4365AC54DB1DA5D7017CB42 /* NounReviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NounReviewView.swift; sourceTree = ""; }; E536AD1180FE10576EAC884A /* UserProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProgress.swift; sourceTree = ""; }; E6BA2ABC841F9C987AB15F67 /* GuideCrossLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuideCrossLinks.swift; sourceTree = ""; }; + E8CEEA98D1805C5BA20A4559 /* Beginner_I_W7.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = Beginner_I_W7.pdf; 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 = ""; }; + EBBA697AFA8988BBB15C4717 /* Beginner_I_W5.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = Beginner_I_W5.pdf; sourceTree = ""; }; + EBDEA606F038A4A3DF1967E3 /* Beginner_I_W3.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = Beginner_I_W3.pdf; sourceTree = ""; }; EBEEC9CC9A8C502AF5F42914 /* VerbExampleCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerbExampleCache.swift; sourceTree = ""; }; EE00D37419072ED6AC1AC63A /* ExtraStudyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtraStudyView.swift; sourceTree = ""; }; EE79D3EC48233CAEC2E39F81 /* LexemeSessionQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LexemeSessionQueue.swift; sourceTree = ""; }; @@ -336,6 +354,7 @@ 7C60B9668AC8A5B3424E9F9C /* vocab_lexemes.json */, 6658C35E454C137B53FC05A4 /* youtube_videos.json */, A6EC7C278E4287D91A0DB355 /* youtube_videos.md */, + 2610354CB0D62BD8A19BEC20 /* CourseMaterials */, 353C5DE41FD410FA82E3AED7 /* Models */, 23B49FBE9B44D8734D96625F /* Scripts */, 1994867BC8E985795A172854 /* Services */, @@ -410,6 +429,21 @@ path = Scripts; sourceTree = ""; }; + 2610354CB0D62BD8A19BEC20 /* CourseMaterials */ = { + isa = PBXGroup; + children = ( + CF62E9D108E2E564BA61A694 /* Beginner_I_W1.pdf */, + 9518665E390E8BE4D7D957FE /* Beginner_I_W2.pdf */, + EBDEA606F038A4A3DF1967E3 /* Beginner_I_W3.pdf */, + B07767E9BC347964B98C0F3D /* Beginner_I_W4.pdf */, + EBBA697AFA8988BBB15C4717 /* Beginner_I_W5.pdf */, + 8B54119366CF052443A8C080 /* Beginner_I_W6.pdf */, + E8CEEA98D1805C5BA20A4559 /* Beginner_I_W7.pdf */, + 83AA457D61B9D2C86C301236 /* Beginner_I_W8.pdf */, + ); + path = CourseMaterials; + sourceTree = ""; + }; 29F9EEAED5A6969FEDEAE227 /* Onboarding */ = { isa = PBXGroup; children = ( @@ -621,6 +655,7 @@ isa = PBXGroup; children = ( 34C67DD1A1CB9B8B5A2BDCED /* CheckpointExamView.swift */, + E42513164C858C8CBE1A70AF /* CourseMaterialView.swift */, 143D06606AE10DCA30A140C2 /* CourseQuizView.swift */, 833516C5D57F164C8660A479 /* CourseView.swift */, 631DC0A942DD57C81DECE083 /* DeckStudyView.swift */, @@ -753,6 +788,14 @@ buildActionMask = 2147483647; files = ( F59655A8B8FCE6264315DD33 /* Assets.xcassets in Resources */, + 3535A6B73D03486EB2E43823 /* Beginner_I_W1.pdf in Resources */, + 345AB6723C15590031B75A01 /* Beginner_I_W2.pdf in Resources */, + 5CBAD967B3545EA7560761C6 /* Beginner_I_W3.pdf in Resources */, + 5224FD701320B7DBCEFDD95B /* Beginner_I_W4.pdf in Resources */, + C0DF369A6E30F01514A78CA1 /* Beginner_I_W5.pdf in Resources */, + 995C466AE3C95A95DB9457A1 /* Beginner_I_W6.pdf in Resources */, + 5C1C0011594A2C06BCD777A4 /* Beginner_I_W7.pdf in Resources */, + EB7CF33BA416BD7B5D995FF4 /* Beginner_I_W8.pdf in Resources */, 877B0306A15A0AD680B361F8 /* book_olly-vol2.json in Resources */, CF9E48ADF0501FB79F3DDB7B /* conjuga_data.json in Resources */, 2B5B2D63DC9C290F66890A4A /* course_data.json in Resources */, @@ -790,6 +833,7 @@ 04C74D9E0ED84BF785A8331C /* ClozeView.swift in Sources */, C2B3D97F119EFCE97E3CB1CE /* ConjugaApp.swift in Sources */, ABBE5080E254D1D3E3465E40 /* ConversationService.swift in Sources */, + F22FD38D5CD6A89CC5940B0E /* CourseMaterialView.swift in Sources */, C3851F960C1162239DC2F935 /* CourseQuizView.swift in Sources */, 8C43F09F52EA9B537EA27E43 /* CourseReviewStore.swift in Sources */, F0D0778207F144D6AC3D39C3 /* CourseView.swift in Sources */, diff --git a/Conjuga/Conjuga/CourseMaterials/Beginner_I_W1.pdf b/Conjuga/Conjuga/CourseMaterials/Beginner_I_W1.pdf new file mode 100644 index 0000000..3882caf Binary files /dev/null and b/Conjuga/Conjuga/CourseMaterials/Beginner_I_W1.pdf differ diff --git a/Conjuga/Conjuga/CourseMaterials/Beginner_I_W2.pdf b/Conjuga/Conjuga/CourseMaterials/Beginner_I_W2.pdf new file mode 100644 index 0000000..72902a8 Binary files /dev/null and b/Conjuga/Conjuga/CourseMaterials/Beginner_I_W2.pdf differ diff --git a/Conjuga/Conjuga/CourseMaterials/Beginner_I_W3.pdf b/Conjuga/Conjuga/CourseMaterials/Beginner_I_W3.pdf new file mode 100644 index 0000000..e42f368 Binary files /dev/null and b/Conjuga/Conjuga/CourseMaterials/Beginner_I_W3.pdf differ diff --git a/Conjuga/Conjuga/CourseMaterials/Beginner_I_W4.pdf b/Conjuga/Conjuga/CourseMaterials/Beginner_I_W4.pdf new file mode 100644 index 0000000..7c9f631 Binary files /dev/null and b/Conjuga/Conjuga/CourseMaterials/Beginner_I_W4.pdf differ diff --git a/Conjuga/Conjuga/CourseMaterials/Beginner_I_W5.pdf b/Conjuga/Conjuga/CourseMaterials/Beginner_I_W5.pdf new file mode 100644 index 0000000..8e7f92b Binary files /dev/null and b/Conjuga/Conjuga/CourseMaterials/Beginner_I_W5.pdf differ diff --git a/Conjuga/Conjuga/CourseMaterials/Beginner_I_W6.pdf b/Conjuga/Conjuga/CourseMaterials/Beginner_I_W6.pdf new file mode 100644 index 0000000..45b582f Binary files /dev/null and b/Conjuga/Conjuga/CourseMaterials/Beginner_I_W6.pdf differ diff --git a/Conjuga/Conjuga/CourseMaterials/Beginner_I_W7.pdf b/Conjuga/Conjuga/CourseMaterials/Beginner_I_W7.pdf new file mode 100644 index 0000000..cedf0fb Binary files /dev/null and b/Conjuga/Conjuga/CourseMaterials/Beginner_I_W7.pdf differ diff --git a/Conjuga/Conjuga/CourseMaterials/Beginner_I_W8.pdf b/Conjuga/Conjuga/CourseMaterials/Beginner_I_W8.pdf new file mode 100644 index 0000000..893ccf4 Binary files /dev/null and b/Conjuga/Conjuga/CourseMaterials/Beginner_I_W8.pdf differ diff --git a/Conjuga/Conjuga/Views/Course/CourseMaterialView.swift b/Conjuga/Conjuga/Views/Course/CourseMaterialView.swift new file mode 100644 index 0000000..bcc9f12 --- /dev/null +++ b/Conjuga/Conjuga/Views/Course/CourseMaterialView.swift @@ -0,0 +1,69 @@ +import SwiftUI +import PDFKit + +struct CourseMaterialView: View { + let weekNumber: Int + let courseName: String + + private var resourceName: String? { + // Only Beginner I has bundled PDFs (Beginner_I_W1.pdf … Beginner_I_W8.pdf). + guard courseName.contains("Beginner I") else { return nil } + return "Beginner_I_W\(weekNumber)" + } + + private var pdfURL: URL? { + guard let name = resourceName else { return nil } + return Bundle.main.url(forResource: name, withExtension: "pdf") + } + + var body: some View { + Group { + if let url = pdfURL { + PDFKitView(url: url) + .ignoresSafeArea(edges: .bottom) + } else { + ContentUnavailableView( + "Material unavailable", + systemImage: "doc.questionmark", + description: Text("No course material is bundled for week \(weekNumber).") + ) + } + } + .navigationTitle("Week \(weekNumber) Material") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + if let url = pdfURL { + ToolbarItem(placement: .topBarTrailing) { + ShareLink(item: url) { + Image(systemName: "square.and.arrow.up") + } + } + } + } + } +} + +private struct PDFKitView: UIViewRepresentable { + let url: URL + + func makeUIView(context: Context) -> PDFView { + let view = PDFView() + view.document = PDFDocument(url: url) + view.autoScales = true + view.displayMode = .singlePageContinuous + view.displayDirection = .vertical + view.usePageViewController(false) + return view + } + + func updateUIView(_ uiView: PDFView, context: Context) { + if uiView.document?.documentURL != url { + uiView.document = PDFDocument(url: url) + } + } +} + +struct CourseMaterialDestination: Hashable { + let courseName: String + let weekNumber: Int +} diff --git a/Conjuga/Conjuga/Views/Course/CourseView.swift b/Conjuga/Conjuga/Views/Course/CourseView.swift index d9d3d27..bcebe92 100644 --- a/Conjuga/Conjuga/Views/Course/CourseView.swift +++ b/Conjuga/Conjuga/Views/Course/CourseView.swift @@ -57,6 +57,11 @@ struct CourseView: View { return results.map(\.scorePercent).max() } + private func hasCourseMaterial(for week: Int) -> Bool { + guard activeCourse.contains("Beginner I") else { return false } + return Bundle.main.url(forResource: "Beginner_I_W\(week)", withExtension: "pdf") != nil + } + private func shortName(_ full: String) -> String { full.replacingOccurrences(of: "LanGo Spanish | ", with: "") .replacingOccurrences(of: "LanGo Spanish ", with: "") @@ -116,6 +121,28 @@ struct CourseView: View { // Week sections ForEach(weekGroups, id: \.week) { week, weekDecks in Section { + // Course material (PDF) + if hasCourseMaterial(for: week) { + NavigationLink(value: CourseMaterialDestination(courseName: activeCourse, weekNumber: week)) { + HStack(spacing: 12) { + Image(systemName: "doc.richtext") + .font(.title3) + .foregroundStyle(.purple) + .frame(width: 32) + + VStack(alignment: .leading, spacing: 2) { + Text("Review Course Material") + .font(.subheadline.weight(.semibold)) + Text("Week \(week) PDF") + .font(.caption) + .foregroundStyle(.secondary) + } + + Spacer() + } + } + } + // Test button NavigationLink(value: WeekTestDestination(courseName: activeCourse, weekNumber: week)) { HStack(spacing: 12) { @@ -231,6 +258,9 @@ struct CourseView: View { .navigationDestination(for: TextbookExerciseDestination.self) { dest in textbookExerciseView(for: dest) } + .navigationDestination(for: CourseMaterialDestination.self) { dest in + CourseMaterialView(weekNumber: dest.weekNumber, courseName: dest.courseName) + } } } diff --git a/Conjuga/project.yml b/Conjuga/project.yml index 7459ac6..2ea8047 100644 --- a/Conjuga/project.yml +++ b/Conjuga/project.yml @@ -62,6 +62,8 @@ targets: buildPhase: resources - path: Conjuga/vocab_lexemes.json buildPhase: resources + - path: Conjuga/CourseMaterials + buildPhase: resources info: path: Conjuga/Info.plist properties: