diff --git a/SwiftUIQuizz.xcodeproj/project.pbxproj b/SwiftUIQuizz.xcodeproj/project.pbxproj index afdf7a2..ca3fc04 100644 --- a/SwiftUIQuizz.xcodeproj/project.pbxproj +++ b/SwiftUIQuizz.xcodeproj/project.pbxproj @@ -16,7 +16,8 @@ 185BB8162835202700BB689A /* correct.json in Resources */ = {isa = PBXBuildFile; fileRef = 185BB8142835202700BB689A /* correct.json */; }; 18DF400E282A8402006B7254 /* MultipleChoiceButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18DF400D282A8402006B7254 /* MultipleChoiceButton.swift */; }; 23A73D02283686C700C611D4 /* Quicksand.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 23A73D01283686C700C611D4 /* Quicksand.ttf */; }; - 3D12614C283D237A0079E14B /* Manager.AnswerTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D12614B283D237A0079E14B /* Manager.AnswerTracker.swift */; }; + 3DFB3603283FA6560045D062 /* Manager.QuestionStash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DFB3602283FA6560045D062 /* Manager.QuestionStash.swift */; }; + 3DFB3605283FB4580045D062 /* Manager.SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DFB3604283FB4580045D062 /* Manager.SessionManager.swift */; }; 3DFD23EE28295C180074AB54 /* Manager.SaveSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DFD23ED28295C180074AB54 /* Manager.SaveSystem.swift */; }; 5F0B738C283BACCE008EE6DC /* DesignSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F0B738B283BACCE008EE6DC /* DesignSystem.swift */; }; 5F0B738E283BAD6E008EE6DC /* DesignSystem.Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F0B738D283BAD6E008EE6DC /* DesignSystem.Color.swift */; }; @@ -48,7 +49,8 @@ 185BB8142835202700BB689A /* correct.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = correct.json; sourceTree = ""; }; 18DF400D282A8402006B7254 /* MultipleChoiceButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipleChoiceButton.swift; sourceTree = ""; }; 23A73D01283686C700C611D4 /* Quicksand.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Quicksand.ttf; sourceTree = ""; }; - 3D12614B283D237A0079E14B /* Manager.AnswerTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Manager.AnswerTracker.swift; sourceTree = ""; }; + 3DFB3602283FA6560045D062 /* Manager.QuestionStash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Manager.QuestionStash.swift; sourceTree = ""; }; + 3DFB3604283FB4580045D062 /* Manager.SessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Manager.SessionManager.swift; sourceTree = ""; }; 3DFD23ED28295C180074AB54 /* Manager.SaveSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Manager.SaveSystem.swift; sourceTree = ""; }; 5F0B738B283BACCE008EE6DC /* DesignSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesignSystem.swift; sourceTree = ""; }; 5F0B738D283BAD6E008EE6DC /* DesignSystem.Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesignSystem.Color.swift; sourceTree = ""; }; @@ -135,7 +137,8 @@ 5F3AC736282408250041CBBC /* Manager.API.swift */, 3DFD23ED28295C180074AB54 /* Manager.SaveSystem.swift */, 1830AF87283E4A5800D4EA1A /* Manager.SFX.swift */, - 3D12614B283D237A0079E14B /* Manager.AnswerTracker.swift */, + 3DFB3602283FA6560045D062 /* Manager.QuestionStash.swift */, + 3DFB3604283FB4580045D062 /* Manager.SessionManager.swift */, ); path = Manager; sourceTree = ""; @@ -319,6 +322,7 @@ files = ( 5F3AC737282408250041CBBC /* Manager.API.swift in Sources */, 5FDC436D2829371100A4F011 /* LaunchView.swift in Sources */, + 3DFB3603283FA6560045D062 /* Manager.QuestionStash.swift in Sources */, 5FE9134A2822A76E00C0CDE0 /* QuestionView.swift in Sources */, 3DFD23EE28295C180074AB54 /* Manager.SaveSystem.swift in Sources */, 5F0B738C283BACCE008EE6DC /* DesignSystem.swift in Sources */, @@ -334,8 +338,8 @@ 18DF400E282A8402006B7254 /* MultipleChoiceButton.swift in Sources */, 5F0B738E283BAD6E008EE6DC /* DesignSystem.Color.swift in Sources */, 5F9C58ED2833D0B100070A37 /* ConclusionView.swift in Sources */, + 3DFB3605283FB4580045D062 /* Manager.SessionManager.swift in Sources */, 5F0B7390283BC476008EE6DC /* Data.swift in Sources */, - 3D12614C283D237A0079E14B /* Manager.AnswerTracker.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/SwiftUIQuizz/Manager/Manager.API.swift b/SwiftUIQuizz/Manager/Manager.API.swift index e49670f..57a8d98 100644 --- a/SwiftUIQuizz/Manager/Manager.API.swift +++ b/SwiftUIQuizz/Manager/Manager.API.swift @@ -98,7 +98,7 @@ extension Manager { return url } - mutating func fetchQuestions( + mutating private func simpleFetch( category: QuestionCategory, difficulty: Difficulty, answerType: AnswerTypes, @@ -120,6 +120,34 @@ extension Manager { return questions } + mutating func fetchQuestions( + category: QuestionCategory, + difficulty: Difficulty, + answerType: AnswerTypes, + amount: Int = 10 + ) async throws -> [Question] { + do { + var returnArray: [Question] = [] + while returnArray.count < amount { + let questionArr = try await simpleFetch(category: category, + difficulty: difficulty, + answerType: answerType, + amount: amount*2) + for question in questionArr { + if !QuestionStash.shared.isRepeatedQuestion(question: question.question) { + returnArray.append(question) + } + if returnArray.count >= amount { + break + } + } + } + return returnArray + } catch { + throw error + } + } + static func parseResponse(questions: [Manager.API.QuestionAPI]) -> [Question] { return questions.map { question in return Question( diff --git a/SwiftUIQuizz/Manager/Manager.AnswerTracker.swift b/SwiftUIQuizz/Manager/Manager.AnswerTracker.swift index 2ab1f87..ecc00fd 100644 --- a/SwiftUIQuizz/Manager/Manager.AnswerTracker.swift +++ b/SwiftUIQuizz/Manager/Manager.AnswerTracker.swift @@ -21,7 +21,7 @@ extension Manager { self.wrongAnswers = 0 } - func startQuiz(questionAmount: Int = 0) { + func resetTracker(questionAmount: Int = 0) { self.questionAmount = questionAmount self.correctAnswers = 0 self.wrongAnswers = 0 diff --git a/SwiftUIQuizz/Manager/Manager.QuestionStash.swift b/SwiftUIQuizz/Manager/Manager.QuestionStash.swift new file mode 100644 index 0000000..9d8deb0 --- /dev/null +++ b/SwiftUIQuizz/Manager/Manager.QuestionStash.swift @@ -0,0 +1,58 @@ +// +// Manager.QuestionStash.swift +// SwiftUIQuizz +// +// Created by Henrique Finger Zimerman on 26/05/22. +// + +import Foundation + +extension Manager { + class QuestionStash: Decodable, Encodable { + public static var shared = QuestionStash.loadFromStorage() + private static let fileName = "stash" + private static let limit = 5000 + + private var questionDict: [String: Bool] + private var questionArray: [String] + + private static func loadFromStorage() -> QuestionStash { + guard let answer: QuestionStash = try? SaveSystem.readObject(fileName: QuestionStash.fileName) + else { + return QuestionStash() + } + return answer + } + + private init() { + questionDict = [:] + questionArray = [] + } + + private func saveToStorage() { + do { + try SaveSystem.saveObject(object: self, fileName: QuestionStash.fileName) + } catch { + print(error) + } + } + + public func isRepeatedQuestion(question: String) -> Bool { + if questionDict[question] == nil { + return false + } + return true + } + + public func addQuestion(question: String, answerStatus: Bool = true) { + guard questionDict[question] == nil else {return} + questionDict[question] = answerStatus + questionArray.append(question) + if questionArray.count > QuestionStash.limit { + questionDict.removeValue(forKey: questionArray[0]) + _ = questionArray.remove(at: 0) + } + saveToStorage() + } + } +} diff --git a/SwiftUIQuizz/Manager/Manager.SessionManager.swift b/SwiftUIQuizz/Manager/Manager.SessionManager.swift new file mode 100644 index 0000000..139c6cc --- /dev/null +++ b/SwiftUIQuizz/Manager/Manager.SessionManager.swift @@ -0,0 +1,70 @@ +// +// Manager.SessionManager.swift +// SwiftUIQuizz +// +// Created by Henrique Finger Zimerman on 26/05/22. +// + +import Foundation + +extension Manager { + class SessionManager { + public static let shared = SessionManager() + private var questionArray: [Question] = [] + private var questionIndex: Int = 0 + + var correctAnswers: Int = 0 + var wrongAnswers: Int = 0 + var questionAmount: Int = 0 + + enum Errors: Error { + case sessionEnded + } + + private init() { + } + + func startSession( + category: Manager.API.QuestionCategory, + difficulty: Manager.API.Difficulty, + answerType: Manager.API.AnswerTypes, + amount: Int = 10 + ) async throws -> Bool { + questionIndex = 0 + correctAnswers = 0 + wrongAnswers = 0 + questionArray = try await Manager.API.shared.fetchQuestions(category: category, + difficulty: difficulty, + answerType: answerType, + amount: amount) + questionAmount = amount + return true + } + + func sessionEnded() -> Bool { + return questionIndex >= questionArray.count + } + + func currentQuestion() throws -> Question { + guard !sessionEnded() + else { + throw Errors.sessionEnded + } + return questionArray[questionIndex] + } + + func addResult(answerStatus: Bool) throws { + guard !sessionEnded() + else { + throw Errors.sessionEnded + } + if answerStatus { + correctAnswers += 1 + } else { + wrongAnswers += 1 + } + Manager.QuestionStash.shared.addQuestion(question: questionArray[questionIndex].question) + questionIndex += 1 + } + } +} diff --git a/SwiftUIQuizz/Views/Buttons/BooleanButton.swift b/SwiftUIQuizz/Views/Buttons/BooleanButton.swift index 3db435f..d4f0117 100644 --- a/SwiftUIQuizz/Views/Buttons/BooleanButton.swift +++ b/SwiftUIQuizz/Views/Buttons/BooleanButton.swift @@ -27,7 +27,9 @@ extension Views { action: { isAnimating = true Manager.SFX.playSound(sound: isCorrect ? .correct: .wrong) - Manager.AnswerTracker.shared.addResult(answerStatus: isCorrect) + do { + try Manager.SessionManager.shared.addResult(answerStatus: isCorrect) + } catch { print(error) } } ) { ZStack { diff --git a/SwiftUIQuizz/Views/Buttons/MultipleChoiceButton.swift b/SwiftUIQuizz/Views/Buttons/MultipleChoiceButton.swift index 6fc486a..e52ff06 100644 --- a/SwiftUIQuizz/Views/Buttons/MultipleChoiceButton.swift +++ b/SwiftUIQuizz/Views/Buttons/MultipleChoiceButton.swift @@ -27,7 +27,9 @@ extension Views { action: { isAnimating = true Manager.SFX.playSound(sound: isCorrect ? .correct: .wrong) - Manager.AnswerTracker.shared.addResult(answerStatus: isCorrect) + do { + try Manager.SessionManager.shared.addResult(answerStatus: isCorrect) + } catch { print(error) } } ) { HStack { diff --git a/SwiftUIQuizz/Views/ConclusionView.swift b/SwiftUIQuizz/Views/ConclusionView.swift index 22d8cde..45b0056 100644 --- a/SwiftUIQuizz/Views/ConclusionView.swift +++ b/SwiftUIQuizz/Views/ConclusionView.swift @@ -75,9 +75,9 @@ extension Views.ConclusionView { } class ViewModel: ObservableObject { - @Published var correctAnswers: Int = Manager.AnswerTracker.shared.correctAnswers - @Published var wrongAnswers: Int = Manager.AnswerTracker.shared.wrongAnswers - @Published var totalQuestions: Int = Manager.AnswerTracker.shared.questionAmount + @Published var correctAnswers: Int = Manager.SessionManager.shared.correctAnswers + @Published var wrongAnswers: Int = Manager.SessionManager.shared.wrongAnswers + @Published var totalQuestions: Int = Manager.SessionManager.shared.questionAmount func answerRate() -> Int { var rate: Double diff --git a/SwiftUIQuizz/Views/InitialView.swift b/SwiftUIQuizz/Views/InitialView.swift index 409f32e..e0cbbbe 100644 --- a/SwiftUIQuizz/Views/InitialView.swift +++ b/SwiftUIQuizz/Views/InitialView.swift @@ -58,14 +58,15 @@ extension Views { .navigationBarHidden(true) .task { do { - Manager.AnswerTracker.shared.startQuiz(questionAmount: viewModel.numberQuestions) - let questions = try await Manager.API.shared.fetchQuestions( + let success = try await Manager.SessionManager.shared.startSession( category: Manager.API.QuestionCategory.allCases[selectedCategoryIndex], difficulty: Manager.API.Difficulty.allCases[selectedDifficultyIndex], answerType: Manager.API.AnswerTypes.allCases[selectedTypeIndex], amount: viewModel.numberQuestions ) - questionsViewModel.update(question: questions.first!) + if success { + try questionsViewModel.update(question: Manager.SessionManager.shared.currentQuestion()) + } } catch { print(error) } diff --git a/SwiftUIQuizz/Views/QuestionView.swift b/SwiftUIQuizz/Views/QuestionView.swift index bbc0d77..09540e3 100644 --- a/SwiftUIQuizz/Views/QuestionView.swift +++ b/SwiftUIQuizz/Views/QuestionView.swift @@ -12,12 +12,7 @@ extension Views { struct QuestionView: View { @ObservedObject var viewModel: ViewModel @State var isAnimating: Bool = false - @State var currentQuestion: Int = 0 { - didSet { - viewModel.update(question: Manager.API.shared.questions[currentQuestion]) - isAnimating = false - } - } + var body: some View { ZStack { DesignSystem.Color.byCategory(category: viewModel.category).uiColor.edgesIgnoringSafeArea(.all) @@ -53,7 +48,6 @@ extension Views { MultipleChoiceButton( isAnimating: $isAnimating, isCorrect: viewModel.checkIfRightAnswer( - questionNumber: currentQuestion, index: index ), buttonText: viewModel.answers.count == 0 ? "" : viewModel.answers[index] @@ -63,16 +57,14 @@ extension Views { HStack { BooleanButton(isAnimating: $isAnimating, isCorrect: viewModel.checkBooleanQuestion( - answer: "True", - questionNumber: currentQuestion + answer: "True" ), buttonText: viewModel.answers.count == 0 ? "" : "True" ) Spacer() BooleanButton(isAnimating: $isAnimating, isCorrect: viewModel.checkBooleanQuestion( - answer: "False", - questionNumber: currentQuestion + answer: "False" ), buttonText: viewModel.answers.count == 0 ? "" : "False" ) @@ -80,12 +72,15 @@ extension Views { case .any: fatalError("Don't insert .any") } - if currentQuestion < Manager.API.shared.questions.count - 1 && self.isAnimating { - SwiftUI.Button(action: { currentQuestion += 1 }) { + if !Manager.SessionManager.shared.sessionEnded() && self.isAnimating { + SwiftUI.Button(action: { + viewModel.fetchQuestion() + self.isAnimating = false + }) { Text("Next Question") } - } else if currentQuestion >= Manager.API.shared.questions.count - 1 && self.isAnimating { - NavigationLink(destination: ConclusionView(viewModel: .init()).navigationBarHidden(true) + } else if Manager.SessionManager.shared.sessionEnded() && self.isAnimating { + NavigationLink(destination: ConclusionView(viewModel: .init()).navigationBarHidden(true) .onAppear { Manager.SFX.playSound(sound: .finished) } @@ -108,19 +103,36 @@ extension Views.QuestionView { @Published var question: String = "" @Published var answerType: Manager.API.AnswerTypes = .any @Published var answers: [String] = [] + @Published var questionObj: Question = Question(category: .all, + type: .any, + difficulty: "error", + question: "error", + correct_answer: "error", + incorrect_answers: ["", "", ""]) - public func checkIfRightAnswer(questionNumber: Int, index: Int) -> Bool { + public func checkIfRightAnswer(index: Int) -> Bool { if answers.count == 0 { return false } - return answers[index] == Manager.API.shared.questions[questionNumber].correct_answer + // print(questionObj.correct_answer, answers) + return answers[index] == questionObj.correct_answer + } + + public func checkBooleanQuestion(answer: String) -> Bool { + // print(answers) + // print(questionObj.correct_answer, answers) + return answer == questionObj.correct_answer ? true : false } - public func checkBooleanQuestion(answer: String, questionNumber: Int) -> Bool { - return answer == Manager.API.shared.questions[questionNumber].correct_answer ? true : false + public func fetchQuestion() { + do { + try questionObj = Manager.SessionManager.shared.currentQuestion() + update(question: questionObj) + } catch { print(error) } } public func update(question: Question) { + self.questionObj = question self.title = question.category.categoryName self.image = Image(question.category.categoryName) self.question = question.question