Skip to content

Commit 8cc368a

Browse files
committed
optimised handling selected answers
1 parent 54f3195 commit 8cc368a

5 files changed

Lines changed: 98 additions & 97 deletions

File tree

GoInfoGame/GoInfoGame/quests/LongQuests/Components/QuestOptions.swift

Lines changed: 47 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,7 @@ struct QuestOptions: View {
1111

1212
let options: [QuestAnswerChoice]
1313

14-
@Binding var selectedAnswerId: UUID?
15-
16-
var onChoiceSelected: (QuestAnswerChoice) -> ()
17-
18-
@Binding var currentAnswer: String?
14+
@Binding var selectedChoice: QuestAnswerChoice?
1915

2016
var questType: QuestType
2117

@@ -26,22 +22,18 @@ struct QuestOptions: View {
2622
case .exclusiveChoice:
2723
ExclusiveChoiceView(
2824
options: options,
29-
selectedAnswerId: $selectedAnswerId,
30-
currentAnswer: $currentAnswer,
31-
onChoiceSelected: onChoiceSelected,
25+
selectedChoice: $selectedChoice,
3226
uploadPhoto: uploadPhoto
3327
)
3428
case .multipleChoice:
3529
MultipleChoiceView(
3630
options: options,
37-
currentAnswer: $currentAnswer,
38-
onChoiceSelected: onChoiceSelected
31+
selectedChoice: $selectedChoice
3932
)
4033

4134
case .numeric:
4235
NumericInputView(
43-
currentAnswer: $currentAnswer,
44-
onChoiceSelected: onChoiceSelected
36+
selectedChoice: $selectedChoice
4537
)
4638
}
4739
}
@@ -52,9 +44,7 @@ private extension QuestOptions {
5244

5345
struct ExclusiveChoiceView: View {
5446
let options: [QuestAnswerChoice]
55-
@Binding var selectedAnswerId: UUID?
56-
@Binding var currentAnswer: String?
57-
var onChoiceSelected: (QuestAnswerChoice) -> ()
47+
@Binding var selectedChoice: QuestAnswerChoice?
5848
var uploadPhoto: (Bool) -> ()
5949

6050
@State private var selectedImageURL: String? = nil
@@ -87,9 +77,7 @@ private extension QuestOptions {
8777
ForEach(options, id: \.id) { option in
8878
OptionButton(
8979
option: option,
90-
selectedAnswerId: $selectedAnswerId,
91-
currentAnswer: $currentAnswer,
92-
onChoiceSelected: onChoiceSelected,
80+
selectedChoice: $selectedChoice,
9381
onLongPress: {
9482
if let imageUrl = option.imageURL, !imageUrl.isEmpty {
9583
selectedImageURL = imageUrl
@@ -102,7 +90,7 @@ private extension QuestOptions {
10290

10391
FollowUpButton(
10492
options: options,
105-
selectedAnswerId: selectedAnswerId,
93+
selectedChoice: $selectedChoice,
10694
uploadPhoto: uploadPhoto
10795
)
10896
}
@@ -114,8 +102,7 @@ private extension QuestOptions {
114102

115103
struct MultipleChoiceView: View {
116104
let options: [QuestAnswerChoice]
117-
@Binding var currentAnswer: String?
118-
let onChoiceSelected: (QuestAnswerChoice) -> Void
105+
@Binding var selectedChoice: QuestAnswerChoice?
119106

120107
@State private var selectedValues: Set<String> = []
121108
@State private var selectedImageURL: String? = nil
@@ -167,16 +154,18 @@ private extension QuestOptions {
167154
}
168155
.onAppear(perform: initializeSelectedValues)
169156
.onChange(of: selectedValues) { _ in
170-
updateCurrentAnswerAndNotify()
157+
updateSelectedChoice()
158+
}
159+
.onChange(of: selectedChoice) { _ in
160+
initializeSelectedValues()
171161
}
172162
}
173163

174164
private func initializeSelectedValues() {
175-
guard let answer = currentAnswer, !answer.isEmpty else {
176-
selectedValues = []
177-
return
165+
let currentSelectedValues = Set((selectedChoice?.value ?? "").components(separatedBy: ", ").filter { !$0.isEmpty })
166+
if selectedValues != currentSelectedValues {
167+
selectedValues = currentSelectedValues
178168
}
179-
selectedValues = Set(answer.components(separatedBy: ", ").filter { !$0.isEmpty })
180169
}
181170

182171
private func toggleSelection(for option: QuestAnswerChoice) {
@@ -187,27 +176,38 @@ private extension QuestOptions {
187176
}
188177
}
189178

190-
private func updateCurrentAnswerAndNotify() {
179+
private func updateSelectedChoice() {
191180
let combinedValue = selectedValues.sorted().joined(separator: ", ")
192-
currentAnswer = combinedValue.isEmpty ? nil : combinedValue
193-
194-
let fakeAnswer = QuestAnswerChoice(value: combinedValue, choiceText: combinedValue, imageURL: nil, choiceFollowUp: nil)
195-
onChoiceSelected(fakeAnswer)
181+
182+
if combinedValue.isEmpty {
183+
if selectedChoice != nil {
184+
selectedChoice = nil
185+
}
186+
} else {
187+
if selectedChoice?.value != combinedValue {
188+
let fakeAnswer = QuestAnswerChoice(value: combinedValue, choiceText: combinedValue, imageURL: nil, choiceFollowUp: nil)
189+
selectedChoice = fakeAnswer
190+
}
191+
}
196192
}
197193
}
198194

199195
struct NumericInputView: View {
200-
@Binding var currentAnswer: String?
201-
var onChoiceSelected: (QuestAnswerChoice) -> ()
196+
@Binding var selectedChoice: QuestAnswerChoice?
202197

203198
var body: some View {
204199
HStack {
205200
TextField("Enter value", text: Binding(
206-
get: { currentAnswer ?? "" },
201+
get: { selectedChoice?.value ?? "" },
207202
set: { newValue in
208-
currentAnswer = newValue
209-
let answer = QuestAnswerChoice(value: newValue, choiceText: newValue, imageURL: "", choiceFollowUp: "")
210-
onChoiceSelected(answer)
203+
if newValue.isEmpty {
204+
selectedChoice = nil
205+
} else {
206+
if selectedChoice?.value != newValue {
207+
let answer = QuestAnswerChoice(value: newValue, choiceText: newValue, imageURL: nil, choiceFollowUp: nil)
208+
selectedChoice = answer
209+
}
210+
}
211211
}
212212
))
213213
.frame(width: 100)
@@ -249,23 +249,17 @@ private extension QuestOptions {
249249

250250
struct OptionButton: View {
251251
let option: QuestAnswerChoice
252-
@Binding var selectedAnswerId: UUID?
253-
@Binding var currentAnswer: String?
254-
let onChoiceSelected: (QuestAnswerChoice) -> Void
252+
@Binding var selectedChoice: QuestAnswerChoice?
255253
let onLongPress: () -> Void
256254

257255
var body: some View {
258256
Button(action: {
259-
if selectedAnswerId == option.id {
257+
if selectedChoice == option {
260258
// Deselect
261-
selectedAnswerId = nil
262-
currentAnswer = nil
263-
// Don't call onChoiceSelected if you want to ignore blank assignment
259+
selectedChoice = nil
264260
} else {
265261
// Select
266-
selectedAnswerId = option.id
267-
currentAnswer = option.value
268-
onChoiceSelected(option)
262+
selectedChoice = option
269263
}
270264
}) {
271265
VStack(spacing: 8) {
@@ -283,7 +277,7 @@ private extension QuestOptions {
283277
}
284278
.overlay(
285279
RoundedRectangle(cornerRadius: 8)
286-
.stroke(currentAnswer == option.value ? Asset.Colors.accentPink.swiftUIColor : Color.clear, lineWidth: 3)
280+
.stroke(selectedChoice == option ? Asset.Colors.accentPink.swiftUIColor : Color.clear, lineWidth: 3)
287281
)
288282
.onLongPressGesture(perform: onLongPress)
289283
}
@@ -343,13 +337,11 @@ private extension QuestOptions {
343337

344338
struct FollowUpButton: View {
345339
let options: [QuestAnswerChoice]
346-
let selectedAnswerId: UUID?
340+
@Binding var selectedChoice: QuestAnswerChoice?
347341
let uploadPhoto: (Bool) -> Void
348342

349343
var body: some View {
350-
if let selected = selectedAnswerId.flatMap({ id in
351-
options.first(where: { $0.id == id && $0.choiceFollowUp != nil })
352-
}) {
344+
if let selected = selectedChoice, selected.choiceFollowUp != nil {
353345
Button(action: {
354346
uploadPhoto(true)
355347
}) {
@@ -406,9 +398,9 @@ private extension QuestOptions {
406398

407399
#Preview {
408400
QuestOptions(options: [QuestAnswerChoice(value: "asphalt", choiceText: "Asphalt", imageURL: "https://raw.githubusercontent.com/TaskarCenterAtUW/tdei-tools/refs/heads/main/images/sidewalk/surface/asphalt_landscape.png", choiceFollowUp: nil),
409-
QuestAnswerChoice(value: "no", choiceText: "No, this roadway is too wide to cross safely.", imageURL: nil, choiceFollowUp: nil)], selectedAnswerId: .constant(UUID()), onChoiceSelected: { qa in
410-
411-
}, currentAnswer: .constant("Binding<String?>"), questType: GoInfoGame.QuestType.exclusiveChoice) { s in
401+
QuestAnswerChoice(value: "no", choiceText: "No, this roadway is too wide to cross safely.", imageURL: nil, choiceFollowUp: nil)],
402+
selectedChoice: .constant(QuestAnswerChoice(value: "no", choiceText: "No, this roadway is too wide to cross safely.", imageURL: nil, choiceFollowUp: nil)),
403+
questType: GoInfoGame.QuestType.exclusiveChoice) { s in
412404

413405
}
414406
}

GoInfoGame/GoInfoGame/quests/LongQuests/Model/LongFormModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ struct LongQuest: Codable, Identifiable {
102102
}
103103

104104
// MARK: - QuestAnswerChoice
105-
struct QuestAnswerChoice: Codable, Identifiable {
105+
struct QuestAnswerChoice: Codable, Identifiable, Equatable {
106106
let id = UUID()
107107
let value, choiceText: String
108108
let imageURL: String?

GoInfoGame/GoInfoGame/quests/LongQuests/View/LongForm.swift

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ import Combine
1111

1212
struct LongForm: View, QuestForm {
1313

14-
@State private var selectedAnswers: [UUID: UUID] = [:]
15-
1614
@ObservedObject private var viewModel = LongFormViewModel()
1715

1816
var elementName: String?
@@ -166,14 +164,9 @@ struct LongForm: View, QuestForm {
166164
if let quests = questsForLongForm() {
167165
ForEach(quests, id: \.questID) { quest in
168166
if viewModel.shouldShowQuest(quest) {
169-
LongQuestView(selectedAnswers: $selectedAnswers, quest: quest, onChoiceSelected: { selectedAnswerChoice in
170-
viewModel.updateAnswers(quest: quest, selectedAnswerChoice: selectedAnswerChoice)
171-
}, uploadPhoto: { result in
172-
if result {
173-
isCameraPresented = true
174-
}
175-
},currentAnswer: $viewModel
176-
.answersToBeSubmitted[quest.questTag])
167+
LongQuestView(quest: quest, selectedChoice: binding(for: quest), uploadPhoto: { result in
168+
if result { isCameraPresented = true }
169+
})
177170
}
178171
}
179172
VStack {
@@ -205,13 +198,13 @@ struct LongForm: View, QuestForm {
205198
.hideKeyboardOnTap()
206199

207200
Button(action: {
208-
if !viewModel.answersToBeSubmitted.isEmpty {
201+
var answersToSubmit = viewModel.getAnswersForSubmission()
202+
if !answersToSubmit.isEmpty {
209203
if let action = action {
210204
if !uploadedPhotos.isEmpty {
211-
viewModel.answersToBeSubmitted["ext:kartaview_url"] = uploadedPhotos.joined(separator: ", ")
205+
answersToSubmit["ext:kartaview_url"] = uploadedPhotos.joined(separator: ", ")
212206
}
213-
214-
action(viewModel.answersToBeSubmitted)
207+
action(answersToSubmit)
215208
}
216209
} else {
217210
self.showSubmitAlert = true
@@ -230,6 +223,9 @@ struct LongForm: View, QuestForm {
230223
.frame(maxWidth: .infinity)
231224

232225
}
226+
.onChange(of: viewModel.selectedChoices) { _ in
227+
viewModel.clearAnswersForHiddenQuests()
228+
}
233229
.onChange(of: capturedImage) { newValue in
234230
if newValue != nil {
235231
uploadImageToKartaView()
@@ -331,6 +327,15 @@ struct LongForm: View, QuestForm {
331327
func questsForLongForm() -> [LongQuest]? {
332328
return QuestsRepository.shared.questsForQuery(query ?? "")
333329
}
330+
331+
private func binding(for quest: LongQuest) -> Binding<QuestAnswerChoice?> {
332+
return Binding(
333+
get: { viewModel.selectedChoices[quest.questID, default: nil] },
334+
set: {
335+
viewModel.selectedChoices[quest.questID] = $0
336+
}
337+
)
338+
}
334339
}
335340

336341
#Preview {

GoInfoGame/GoInfoGame/quests/LongQuests/View/LongQuestView.swift

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,16 @@ import SwiftUI
99

1010
struct LongQuestView: View {
1111

12-
@Binding var selectedAnswers: [UUID: UUID]
13-
1412
var quest: LongQuest
15-
16-
var onChoiceSelected: (QuestAnswerChoice) -> ()
17-
13+
14+
@Binding var selectedChoice: QuestAnswerChoice?
15+
1816
var uploadPhoto: (Bool) -> ()
1917

2018
var questOptions: [QuestAnswerChoice] {
2119
return quest.questAnswerChoices ?? []
2220
}
2321

24-
@Binding var currentAnswer:String?
25-
2622
@State private var isImageExpanded: Bool = false
2723

2824
var body: some View {
@@ -50,9 +46,7 @@ struct LongQuestView: View {
5046
.font(.custom("Lato-Regular", size: 12))
5147
.foregroundColor(Color(red: 131/255, green: 135/255, blue: 155/255))
5248

53-
QuestOptions(options: questOptions, selectedAnswerId: $selectedAnswers[quest.id], onChoiceSelected: { selectedChoice in
54-
onChoiceSelected(selectedChoice)
55-
}, currentAnswer: $currentAnswer, questType: quest.questType, uploadPhoto: uploadPhoto)
49+
QuestOptions(options: questOptions, selectedChoice: $selectedChoice, questType: quest.questType, uploadPhoto: uploadPhoto)
5650
}
5751
.padding(.vertical, 5)
5852
}
@@ -69,10 +63,7 @@ struct LongQuestView: View {
6963
QuestAnswerChoice(value: "no", choiceText: "No, this roadway is too wide to cross safely.", imageURL: nil, choiceFollowUp: nil)], questImageURL: nil, questAnswerValidation: nil, questAnswerDependency: nil, questUserAnswer: nil)
7064

7165

72-
73-
LongQuestView(selectedAnswers: .constant([UUID(): UUID()]), quest: longQeust, onChoiceSelected: { qa in
74-
75-
}, uploadPhoto: { s in
66+
LongQuestView(quest: longQeust, selectedChoice: .constant(nil), uploadPhoto: { s in
7667

77-
}, currentAnswer: .constant(nil))
68+
})
7869
}

0 commit comments

Comments
 (0)