Skip to content

Commit a014caf

Browse files
Merge pull request #50 from mariusbackes/main
Added feature to shuffle choices in a question
2 parents 8a3a9f6 + 11785ad commit a014caf

File tree

6 files changed

+94
-24
lines changed

6 files changed

+94
-24
lines changed

CloudMaster/Features/Exam/Views/ExamView.swift

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,24 +44,27 @@ struct ExamView: View {
4444
}
4545
.padding(.horizontal)
4646

47+
let question = questions[currentQuestionIndex]
48+
4749
QuestionView(
4850
mode: .exam,
49-
question: questions[currentQuestionIndex],
50-
selectedChoices: selectedChoices[questions[currentQuestionIndex].id] ?? [],
51-
isMultipleResponse: questions[currentQuestionIndex].multipleResponse,
51+
question: question,
52+
selectedChoices: selectedChoices[question.id] ?? [],
53+
isMultipleResponse: question.multipleResponse,
5254
isResultShown: false, // Exam mode does not show result immediately
5355
onChoiceSelected: { choiceId in
54-
if questions[currentQuestionIndex].multipleResponse {
55-
if selectedChoices[questions[currentQuestionIndex].id]?.contains(choiceId) == true {
56-
selectedChoices[questions[currentQuestionIndex].id]?.remove(choiceId)
56+
if question.multipleResponse {
57+
if selectedChoices[question.id]?.contains(choiceId) == true {
58+
selectedChoices[question.id]?.remove(choiceId)
5759
} else {
58-
selectedChoices[questions[currentQuestionIndex].id, default: []].insert(choiceId)
60+
selectedChoices[question.id, default: []].insert(choiceId)
5961
}
6062
} else {
61-
selectedChoices[questions[currentQuestionIndex].id] = [choiceId]
63+
selectedChoices[question.id] = [choiceId]
6264
}
6365
}
6466
)
67+
.id(question.id)
6568

6669
Button(action: {
6770
if currentQuestionIndex < questions.count - 1 {

CloudMaster/Features/Settings/Views/SettingsView.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@ struct SettingsView: View {
2626
Text("Delete all previous exams")
2727
}
2828
}
29+
30+
Section(header: Text("Preferences")) {
31+
Toggle("Shuffle question choices", isOn: Binding(
32+
get: {
33+
UserDefaults.standard.bool(forKey: "shuffleQuestionChoices")
34+
},
35+
set: { shuffleQuestionChoices in
36+
UserDefaults.standard.set(shuffleQuestionChoices, forKey: "shuffleQuestionChoices")
37+
}
38+
))
39+
}
2940
}
3041
.navigationBarTitle("Settings", displayMode: .inline)
3142
.confirmPopup(isPresented: $showAlert, title: alertTitle, message: alertMessage, confirmAction: {

CloudMaster/Features/Shared/Components/QuestionImages.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import SwiftUI
22

33
struct QuestionImages: View {
4-
let images: [Question.ImageInfo]
4+
let images: [ImageInfo]
55
@Binding var currentImageIndex: Int
66
@Binding var isFullscreenImageShown: Bool
77
@Binding var selectedImageIndex: Int
@@ -53,7 +53,7 @@ struct QuestionImages: View {
5353

5454

5555
struct FullscreenImageView: View {
56-
let images: [Question.ImageInfo]
56+
let images: [ImageInfo]
5757
@Binding var selectedImageIndex: Int
5858
@Binding var isShown: Bool
5959

CloudMaster/Features/Shared/Components/QuestionView.swift

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,25 @@ struct QuestionView: View {
1818
@State private var currentImageIndex = 0
1919
@State private var isFullscreenImageShown = false
2020
@State private var selectedImageIndex = 0
21+
@State private var shuffledQuestion: Question?
22+
23+
@State private var shuffleQuestionChoices: Bool = UserDefaults.standard.bool(forKey: "shuffleQuestionChoices")
2124

2225
var body: some View {
26+
let displayQuestion = shuffledQuestion ?? question
27+
2328
ScrollView {
2429
VStack(alignment: .leading, spacing: 16) {
25-
Text(question.question)
26-
.font(.system(size: adjustedFontSize(for: question.question), weight: .bold))
30+
Text(displayQuestion.question)
31+
.font(.system(size: adjustedFontSize(for: displayQuestion.question), weight: .bold))
2732
.minimumScaleFactor(0.5)
2833
.lineLimit(nil)
2934
.fixedSize(horizontal: false, vertical: true)
3035
.padding(.horizontal)
3136
.multilineTextAlignment(.leading)
3237
.lineSpacing(2)
3338

34-
QuestionImages(images: question.images,
39+
QuestionImages(images: displayQuestion.images,
3540
currentImageIndex: $currentImageIndex,
3641
isFullscreenImageShown: $isFullscreenImageShown,
3742
selectedImageIndex: $selectedImageIndex)
@@ -41,7 +46,7 @@ struct QuestionView: View {
4146

4247
if isMultipleResponse {
4348
VStack {
44-
Text("Multiple response - Pick \(question.responseCount)")
49+
Text("Multiple response - Pick \(displayQuestion.responseCount)")
4550
.font(.subheadline)
4651
.multilineTextAlignment(.center)
4752
.opacity(0.7)
@@ -53,7 +58,7 @@ struct QuestionView: View {
5358
.padding(.horizontal)
5459
}
5560

56-
ForEach(question.choices) { choice in
61+
ForEach(displayQuestion.choices) { choice in
5762
if mode == .training {
5863
TrainingChoice(
5964
choice: choice,
@@ -75,17 +80,59 @@ struct QuestionView: View {
7580
}
7681
}
7782
}
83+
.onAppear {
84+
shuffleCurrentQuestionChoices()
85+
}
86+
.onChange(of: question.id) { _ in
87+
shuffleCurrentQuestionChoices()
88+
}
7889
.padding()
7990
}
8091
.overlay(
8192
Group {
8293
if isFullscreenImageShown {
83-
FullscreenImageView(images: question.images, selectedImageIndex: $selectedImageIndex, isShown: $isFullscreenImageShown)
94+
FullscreenImageView(images: displayQuestion.images, selectedImageIndex: $selectedImageIndex, isShown: $isFullscreenImageShown)
8495
}
8596
}
8697
)
8798
}
8899

100+
private func shuffleCurrentQuestionChoices() -> Void {
101+
if (!shuffleQuestionChoices) {
102+
shuffledQuestion = question;
103+
return;
104+
}
105+
106+
let choices = getShuffledChoices(choices: question.choices, images: question.images, mode: mode)
107+
shuffledQuestion = Question(
108+
id: question.id,
109+
question: question.question,
110+
choices: choices,
111+
multipleResponse: question.multipleResponse,
112+
responseCount: question.responseCount,
113+
images: question.images
114+
)
115+
}
116+
117+
private func getShuffledChoices(choices: [Choice], images: [ImageInfo], mode: Mode) -> [Choice] {
118+
if (mode == .bookmarked) {
119+
return choices; // Do not shuffle bookmarked choices
120+
}
121+
122+
// Do not shuffle choices which belongs to an image (an image is available for each choice), because reference in the answer is lost
123+
if (images.count > 1) {
124+
return choices;
125+
}
126+
127+
var shuffledChoices = choices;
128+
shuffledChoices.shuffle();
129+
return shuffledChoices;
130+
}
131+
132+
private func _debug(question: Question) {
133+
print(question)
134+
}
135+
89136
private func adjustedFontSize(for text: String) -> CGFloat {
90137
let baseFontSize: CGFloat = 20
91138
let minFontSize: CGFloat = 14

CloudMaster/Features/Training/Views/TrainingView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ struct TrainingView: View {
4141
handleChoiceSelection(choiceID, question)
4242
}
4343
)
44+
.id(question.id)
4445
.navigationBarItems(trailing: bookmarkButton)
4546
.onAppear {
4647
updateBookmarkState()

CloudMaster/Utilities/QuestionLoader.swift

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
11
import Foundation
22

33
struct Question: Identifiable, Codable {
4-
let id = UUID()
4+
var id = UUID()
55
let question: String
6-
let choices: [Choice]
6+
var choices: [Choice]
77
var multipleResponse: Bool
88
var responseCount: Int
9-
let images: [ImageInfo]
9+
var images: [ImageInfo]
1010

1111
enum CodingKeys: String, CodingKey {
1212
case question, choices, multipleResponse = "multiple_response", responseCount = "response_count", images
1313
}
1414

15-
struct ImageInfo: Codable {
16-
let path: String
17-
let url: String?
18-
}
19-
2015
init(from decoder: Decoder) throws {
2116
let container = try decoder.container(keyedBy: CodingKeys.self)
2217
question = try container.decode(String.self, forKey: .question)
@@ -25,8 +20,21 @@ struct Question: Identifiable, Codable {
2520
responseCount = try container.decodeIfPresent(Int.self, forKey: .responseCount) ?? 0
2621
images = try container.decodeIfPresent([ImageInfo].self, forKey: .images) ?? []
2722
}
23+
24+
init(id: UUID, question: String, choices: [Choice], multipleResponse: Bool, responseCount: Int, images: [ImageInfo]) {
25+
self.id = id;
26+
self.question = question;
27+
self.choices = choices;
28+
self.multipleResponse = multipleResponse;
29+
self.responseCount = responseCount;
30+
self.images = images;
31+
}
2832
}
2933

34+
struct ImageInfo: Codable {
35+
let path: String
36+
let url: String?
37+
}
3038

3139
struct Choice: Identifiable, Codable {
3240
var id = UUID()

0 commit comments

Comments
 (0)