From 3bce439bc49fd7be2b267c1a28b473404b85f02a Mon Sep 17 00:00:00 2001 From: Avery Pace Date: Mon, 8 Nov 2021 12:24:46 -0500 Subject: [PATCH] Lessons implementation --- Toki Trainer.xcodeproj/project.pbxproj | 16 ++++ Toki Trainer/Models/TokiJSONLoader.swift | 11 +++ Toki Trainer/Models/TokiLesson.swift | 15 ++++ .../FlashCardLessonsViewModel.swift | 22 ++++++ Toki Trainer/Views/ContentView.swift | 2 +- Toki Trainer/Views/FlashCardLessonsView.swift | 30 ++++++++ Toki Trainer/Views/FlashCardView.swift | 76 ++++++++++++------- 7 files changed, 142 insertions(+), 30 deletions(-) create mode 100644 Toki Trainer/Models/TokiLesson.swift create mode 100644 Toki Trainer/ViewModels/FlashCardLessonsViewModel.swift create mode 100644 Toki Trainer/Views/FlashCardLessonsView.swift diff --git a/Toki Trainer.xcodeproj/project.pbxproj b/Toki Trainer.xcodeproj/project.pbxproj index cca1bb0..8ff750e 100644 --- a/Toki Trainer.xcodeproj/project.pbxproj +++ b/Toki Trainer.xcodeproj/project.pbxproj @@ -15,6 +15,10 @@ 7E28111C273302860063DC78 /* toki-partsofspeech.json in Resources */ = {isa = PBXBuildFile; fileRef = 7E28111A273302860063DC78 /* toki-partsofspeech.json */; }; 7E28111D273302860063DC78 /* toki-dictionary.json in Resources */ = {isa = PBXBuildFile; fileRef = 7E28111B273302860063DC78 /* toki-dictionary.json */; }; 7E28112227330DD30063DC78 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E28112127330DD20063DC78 /* Constants.swift */; }; + 7E716B3E273986E5009E2CF6 /* TokiLesson.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E716B3D273986E5009E2CF6 /* TokiLesson.swift */; }; + 7E716B4027398ABD009E2CF6 /* toki-lessons.json in Resources */ = {isa = PBXBuildFile; fileRef = 7E716B3F27398ABD009E2CF6 /* toki-lessons.json */; }; + 7E716B4227398CDF009E2CF6 /* FlashCardLessonsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E716B4127398CDF009E2CF6 /* FlashCardLessonsView.swift */; }; + 7E716B4427398D3D009E2CF6 /* FlashCardLessonsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E716B4327398D3D009E2CF6 /* FlashCardLessonsViewModel.swift */; }; 7E71E6ED2735D70C007CFF72 /* FlashCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E71E6EC2735D70C007CFF72 /* FlashCardView.swift */; }; 7E71E6F12736DAE4007CFF72 /* FlashCardsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E71E6F02736DAE4007CFF72 /* FlashCardsViewModel.swift */; }; 7E943A21273211C200E7DDF4 /* Toki_TrainerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E943A20273211C200E7DDF4 /* Toki_TrainerApp.swift */; }; @@ -35,6 +39,10 @@ 7E28111A273302860063DC78 /* toki-partsofspeech.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "toki-partsofspeech.json"; sourceTree = ""; }; 7E28111B273302860063DC78 /* toki-dictionary.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "toki-dictionary.json"; sourceTree = ""; }; 7E28112127330DD20063DC78 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + 7E716B3D273986E5009E2CF6 /* TokiLesson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokiLesson.swift; sourceTree = ""; }; + 7E716B3F27398ABD009E2CF6 /* toki-lessons.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = "toki-lessons.json"; path = "../../../../../Desktop/toki-pona-dict-json/output/toki-lessons.json"; sourceTree = ""; }; + 7E716B4127398CDF009E2CF6 /* FlashCardLessonsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlashCardLessonsView.swift; sourceTree = ""; }; + 7E716B4327398D3D009E2CF6 /* FlashCardLessonsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlashCardLessonsViewModel.swift; sourceTree = ""; }; 7E71E6EC2735D70C007CFF72 /* FlashCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlashCardView.swift; sourceTree = ""; }; 7E71E6F02736DAE4007CFF72 /* FlashCardsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlashCardsViewModel.swift; sourceTree = ""; }; 7E943A1D273211C200E7DDF4 /* Toki Trainer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Toki Trainer.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -64,6 +72,7 @@ 7E2811142733027F0063DC78 /* TokiDictionary.swift */, 7E2811152733027F0063DC78 /* TokiJSONLoader.swift */, 7E2811162733027F0063DC78 /* TokiPartOfSpeech.swift */, + 7E716B3D273986E5009E2CF6 /* TokiLesson.swift */, ); path = Models; sourceTree = ""; @@ -76,6 +85,7 @@ 7E20D5FE2733AFE700D75B9A /* PartsOfSpeechView.swift */, 7E71E6EC2735D70C007CFF72 /* FlashCardView.swift */, 7EF546152737B8FA00537AE6 /* FlashCardResultsView.swift */, + 7E716B4127398CDF009E2CF6 /* FlashCardLessonsView.swift */, ); path = Views; sourceTree = ""; @@ -85,6 +95,7 @@ children = ( 7E20D6002734466800D75B9A /* TokiDictionaryViewModel.swift */, 7E71E6F02736DAE4007CFF72 /* FlashCardsViewModel.swift */, + 7E716B4327398D3D009E2CF6 /* FlashCardLessonsViewModel.swift */, ); path = ViewModels; sourceTree = ""; @@ -93,6 +104,7 @@ isa = PBXGroup; children = ( 7E28111B273302860063DC78 /* toki-dictionary.json */, + 7E716B3F27398ABD009E2CF6 /* toki-lessons.json */, 7E28111A273302860063DC78 /* toki-partsofspeech.json */, ); path = "JSON Data"; @@ -197,6 +209,7 @@ buildActionMask = 2147483647; files = ( 7E943A28273211C300E7DDF4 /* Preview Assets.xcassets in Resources */, + 7E716B4027398ABD009E2CF6 /* toki-lessons.json in Resources */, 7E943A25273211C300E7DDF4 /* Assets.xcassets in Resources */, 7E28111D273302860063DC78 /* toki-dictionary.json in Resources */, 7E28111C273302860063DC78 /* toki-partsofspeech.json in Resources */, @@ -215,13 +228,16 @@ 7E20D5FF2733AFE700D75B9A /* PartsOfSpeechView.swift in Sources */, 7E71E6F12736DAE4007CFF72 /* FlashCardsViewModel.swift in Sources */, 7E20D6012734466800D75B9A /* TokiDictionaryViewModel.swift in Sources */, + 7E716B4427398D3D009E2CF6 /* FlashCardLessonsViewModel.swift in Sources */, 7E2811192733027F0063DC78 /* TokiPartOfSpeech.swift in Sources */, 7E943A23273211C200E7DDF4 /* ContentView.swift in Sources */, 7EF546162737B8FB00537AE6 /* FlashCardResultsView.swift in Sources */, + 7E716B4227398CDF009E2CF6 /* FlashCardLessonsView.swift in Sources */, 7E2811172733027F0063DC78 /* TokiDictionary.swift in Sources */, 7E71E6ED2735D70C007CFF72 /* FlashCardView.swift in Sources */, 7E943A21273211C200E7DDF4 /* Toki_TrainerApp.swift in Sources */, 7E2811182733027F0063DC78 /* TokiJSONLoader.swift in Sources */, + 7E716B3E273986E5009E2CF6 /* TokiLesson.swift in Sources */, 7E28112227330DD30063DC78 /* Constants.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Toki Trainer/Models/TokiJSONLoader.swift b/Toki Trainer/Models/TokiJSONLoader.swift index 5b8208c..87d271e 100644 --- a/Toki Trainer/Models/TokiJSONLoader.swift +++ b/Toki Trainer/Models/TokiJSONLoader.swift @@ -31,6 +31,17 @@ class TokiJSONLoader: ObservableObject { } } + func loadLessons() -> [TokiLesson]? { + let jsonData = loadJSON("toki-lessons") + do { + let decodedData = try JSONDecoder().decode([TokiLesson].self, from: jsonData!) + return decodedData + } catch { + print("Decode error: \(error)") + return nil + } + } + func loadJSON(_ resource: String) -> Data? { do { if let bundlePath = Bundle.main.path(forResource: resource, ofType: "json"), let jsonData = try String(contentsOfFile: bundlePath).data(using: .utf8) { diff --git a/Toki Trainer/Models/TokiLesson.swift b/Toki Trainer/Models/TokiLesson.swift new file mode 100644 index 0000000..e97cbd4 --- /dev/null +++ b/Toki Trainer/Models/TokiLesson.swift @@ -0,0 +1,15 @@ +// +// TokiLesson.swift +// Toki Trainer +// +// Created by Avery Ada Pace on 11/8/21. +// + +import Foundation + +struct TokiLesson: Decodable { + var lesson: String + var words: [TokiDictEntry] +} + + diff --git a/Toki Trainer/ViewModels/FlashCardLessonsViewModel.swift b/Toki Trainer/ViewModels/FlashCardLessonsViewModel.swift new file mode 100644 index 0000000..dde05de --- /dev/null +++ b/Toki Trainer/ViewModels/FlashCardLessonsViewModel.swift @@ -0,0 +1,22 @@ +// +// FlashCardLessonsViewModel.swift +// Toki Trainer +// +// Created by Avery Ada Pace on 11/8/21. +// + +import Foundation + +class FlashCardLessonsViewModel: ObservableObject { + + let jsonLoader: TokiJSONLoader = TokiJSONLoader() + + @Published var lessons: [TokiLesson] = [] + + + init() { + if let safeLessons = jsonLoader.loadLessons() { + lessons = safeLessons + } + } +} diff --git a/Toki Trainer/Views/ContentView.swift b/Toki Trainer/Views/ContentView.swift index 999f575..8c440a8 100644 --- a/Toki Trainer/Views/ContentView.swift +++ b/Toki Trainer/Views/ContentView.swift @@ -24,7 +24,7 @@ struct ContentView: View { Image(systemName: "pencil") Text("Phrase Lookup") } - FlashCardView() + FlashCardLessonsView() .tabItem { Image(systemName: "character.textbox") Text("Flash Cards") diff --git a/Toki Trainer/Views/FlashCardLessonsView.swift b/Toki Trainer/Views/FlashCardLessonsView.swift new file mode 100644 index 0000000..2eae3b0 --- /dev/null +++ b/Toki Trainer/Views/FlashCardLessonsView.swift @@ -0,0 +1,30 @@ +// +// FlashCardLessonsView.swift +// Toki Trainer +// +// Created by Avery Ada Pace on 11/8/21. +// + +import SwiftUI + +struct FlashCardLessonsView: View { + @ObservedObject var flashCardLessonsVM = FlashCardLessonsViewModel() + + var body: some View { + NavigationView { + List(flashCardLessonsVM.lessons, id: \.lesson) { lesson in + NavigationLink(destination: FlashCardView(lesson.words)) { + Text(lesson.lesson) + .bold() + } + } + .navigationBarTitle("Lessons") + } + } +} + +struct FlashCardLessonsView_Previews: PreviewProvider { + static var previews: some View { + FlashCardLessonsView() + } +} diff --git a/Toki Trainer/Views/FlashCardView.swift b/Toki Trainer/Views/FlashCardView.swift index 6cf6202..a41bb6c 100644 --- a/Toki Trainer/Views/FlashCardView.swift +++ b/Toki Trainer/Views/FlashCardView.swift @@ -18,9 +18,27 @@ struct FlashCardView: View { @ObservedObject var flashCardsViewModel = FlashCardsViewModel() + @State var dictionary: [TokiDictEntry]? + + init(_ passedDictionary: [TokiDictEntry]?) { + if passedDictionary != nil { + if let safePassedDictionary = passedDictionary { + self.dictionary = safePassedDictionary + } + } + } + var body: some View { VStack { - FlashCardStack(dictionary: flashCardsViewModel.randomDictionary) + FlashCardStack(dictionary: getDictionary()) + } + } + + func getDictionary() -> [TokiDictEntry] { + if dictionary != nil { + return dictionary ?? [] + } else { + return flashCardsViewModel.randomDictionary } } } @@ -59,7 +77,7 @@ struct FlashCardStack: View { ForEach(flashCards.indices, id: \.self) { index in flashCards[index] .offset(x: 0, y: flashCardsVertOffset[index]) - .zIndex(-(CGFloat(index * 100))) + .zIndex(-(CGFloat(index * 10))) } } } @@ -72,9 +90,9 @@ struct FlashCardStack: View { }.opacity(fadeOutOverlay ? 0.0 : 1.0), alignment: .top) } Spacer() - .onAppear { - initFlashCards() - } + .onAppear { + initFlashCards() + } } func initFlashCards() { @@ -83,17 +101,17 @@ struct FlashCardStack: View { flashCardsAreInteractive.append(false) flashCardsResults.append(FlashCardResult.Unanswered) flashCards.append(FlashCard(isInteractive: $flashCardsAreInteractive[index], result: $flashCardsResults[index].onChange(cardAnswerReceived), dictionaryEntry: dictionary[index])) - flashCardsVertOffset.append(470) + flashCardsVertOffset.append(370) } if flashCards.count - currentFlashCard >= 3 { - flashCardsVertOffset[currentFlashCard + 1] = 410 - flashCardsVertOffset[currentFlashCard + 2] = 440 - flashCardsVertOffset[currentFlashCard + 3] = 470 + flashCardsVertOffset[currentFlashCard + 1] = 310 + flashCardsVertOffset[currentFlashCard + 2] = 340 + flashCardsVertOffset[currentFlashCard + 3] = 370 } else if flashCards.count - currentFlashCard == 2 { - flashCardsVertOffset[currentFlashCard + 1] = 410 - flashCardsVertOffset[currentFlashCard + 2] = 440 + flashCardsVertOffset[currentFlashCard + 1] = 310 + flashCardsVertOffset[currentFlashCard + 2] = 340 } else if flashCards.count - currentFlashCard == 1 { - flashCardsVertOffset[currentFlashCard + 1] = 410 + flashCardsVertOffset[currentFlashCard + 1] = 310 } flashCardsVertOffset[currentFlashCard] = 100 @@ -126,15 +144,15 @@ struct FlashCardStack: View { print("answer not found in database") } -// for answer in flashCardAnswers { -// if answer.word == dictionary[currentFlashCard].word { -// flashCardAnswer.word = answer.word -// flashCardAnswer.triesCount = answer.triesCount + 1 -// if correct { -// flashCardAnswer.correctCount = answer.correctCount + 1 -// } -// } -// } + // for answer in flashCardAnswers { + // if answer.word == dictionary[currentFlashCard].word { + // flashCardAnswer.word = answer.word + // flashCardAnswer.triesCount = answer.triesCount + 1 + // if correct { + // flashCardAnswer.correctCount = answer.correctCount + 1 + // } + // } + // } try? viewContext.save() } @@ -160,14 +178,14 @@ struct FlashCardStack: View { self.fadeOutOverlay = true if flashCards.count - currentFlashCard >= 3 { - flashCardsVertOffset[currentFlashCard + 1] = 410 - flashCardsVertOffset[currentFlashCard + 2] = 440 - flashCardsVertOffset[currentFlashCard + 3] = 470 + flashCardsVertOffset[currentFlashCard + 1] = 310 + flashCardsVertOffset[currentFlashCard + 2] = 340 + flashCardsVertOffset[currentFlashCard + 3] = 370 } else if flashCards.count - currentFlashCard == 2 { - flashCardsVertOffset[currentFlashCard + 1] = 410 - flashCardsVertOffset[currentFlashCard + 2] = 440 + flashCardsVertOffset[currentFlashCard + 1] = 310 + flashCardsVertOffset[currentFlashCard + 2] = 340 } else if flashCards.count - currentFlashCard == 1 { - flashCardsVertOffset[currentFlashCard + 1] = 410 + flashCardsVertOffset[currentFlashCard + 1] = 310 } } @@ -220,7 +238,7 @@ struct FlashCard: View { Text("") .modifier(CardFlipModifier(isFaceDown: isFaceDown, frontText: dictionaryEntry.word, backText: concatenateDefinitions())) - .frame(width: 0.8 * screen.width, height: 200.0) + .frame(width: 0.8 * screen.width) .offset(x: isFaceDown ? -dragAmount : dragAmount, y: abs(dragAmount) / 10) .rotationEffect(.degrees(isFaceDown ? -(dragAmount / 50) : dragAmount / 50)) .font(.title) @@ -298,6 +316,6 @@ struct CardFlipModifier: AnimatableModifier { struct FlashCardView_Previews: PreviewProvider { static var previews: some View { - FlashCardView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) + FlashCardView(nil).environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) } }