diff --git a/Modules/ContentKit/Resources/Content/activities/templates/touch_to_select_action_observe-70A14CC4CD394CDBB4886A8DFE4C5CCC.activity.yml b/Modules/ContentKit/Resources/Content/activities/templates/touch_to_select_action_observe-70A14CC4CD394CDBB4886A8DFE4C5CCC.activity.yml index 9f69e9dccb..a434ef6377 100644 --- a/Modules/ContentKit/Resources/Content/activities/templates/touch_to_select_action_observe-70A14CC4CD394CDBB4886A8DFE4C5CCC.activity.yml +++ b/Modules/ContentKit/Resources/Content/activities/templates/touch_to_select_action_observe-70A14CC4CD394CDBB4886A8DFE4C5CCC.activity.yml @@ -8,7 +8,7 @@ uuid: 70A14CC4CD394CDBB4886A8DFE4C5CCC name: touch_to_select_action_observe created_at: "2024-06-17T17:38:12.804177" -last_edited_at: "2024-06-28T13:28:34.670550" +last_edited_at: "2024-09-16T15:27:15.658346" status: template @@ -72,7 +72,7 @@ exercises_payload: - group: - instructions: - locale: fr_FR - value: Touche les fruits similaires à celui présenter + value: Touche les fruits similaires à celui présenté - locale: en_US value: Tap the same fruits as the one shown interface: observeThenTouchToSelect @@ -95,3 +95,27 @@ exercises_payload: - value: pictograms-foods-fruits-banana_yellow-00FB type: image is_right_answer: true + + - instructions: + - locale: fr_FR + value: Touche les animaux similaires à celui présenté + - locale: en_US + value: Tap the same animals as the one shown + interface: observeThenTouchToSelect + gameplay: findTheRightAnswers + action: + type: ipad + value: + type: sfsymbol + value: fish + payload: + choices: + - value: fish + type: sfsymbol + is_right_answer: true + - value: cat + type: sfsymbol + - value: dog + type: sfsymbol + - value: bird + type: sfsymbol diff --git a/Modules/ContentKit/Sources/Exercise/Exercise+Action.swift b/Modules/ContentKit/Sources/Exercise/Exercise+Action.swift index 9763357acd..d96c28e20b 100644 --- a/Modules/ContentKit/Sources/Exercise/Exercise+Action.swift +++ b/Modules/ContentKit/Sources/Exercise/Exercise+Action.swift @@ -30,6 +30,9 @@ public extension Exercise { case .image: let image = try valueContainer.decode(String.self, forKey: .value) self = .ipad(type: .image(image)) + case .sfsymbol: + let symbol = try valueContainer.decode(String.self, forKey: .value) + self = .ipad(type: .sfsymbol(symbol)) case .audio: let audio = try valueContainer.decode(String.self, forKey: .value) self = .ipad(type: .audio(audio)) @@ -84,6 +87,7 @@ public extension Exercise { public enum ValueType: String, Codable { case color case image + case sfsymbol case audio case speech } diff --git a/Modules/GameEngineKit/Sources/Exercises/ActionButton/ActionButton+Observe.swift b/Modules/GameEngineKit/Sources/Exercises/ActionButton/ActionButton+Observe.swift index 7912bf6bce..1ce18542c2 100644 --- a/Modules/GameEngineKit/Sources/Exercises/ActionButton/ActionButton+Observe.swift +++ b/Modules/GameEngineKit/Sources/Exercises/ActionButton/ActionButton+Observe.swift @@ -45,16 +45,23 @@ struct ActionButtonObserve: View { // MARK: Lifecycle init(image: String, imageWasTapped: Binding) { - guard let image = Bundle.path(forImage: image) else { - fatalError("Image not found") + if let imagePath = Bundle.path(forImage: image) { + self.image = imagePath + self.isSFSymbol = false + } else if UIImage(systemName: image) != nil { + self.image = image + self.isSFSymbol = true + } else { + fatalError("Image not found: \(image)") } - self.image = image + self._imageWasTapped = imageWasTapped } // MARK: Internal let image: String + let isSFSymbol: Bool @Binding var imageWasTapped: Bool @@ -76,28 +83,44 @@ struct ActionButtonObserve: View { } .disabled(self.imageWasTapped) .background { - if !FileManager.default.fileExists(atPath: self.image) { - self.imageNotFound() - } - - if self.image.isRasterImageFile { - Image(uiImage: UIImage(named: self.image)!) + if self.isSFSymbol { + Image(systemName: self.image) .resizable() .clipShape(RoundedRectangle(cornerRadius: 10)) .scaledToFit() + .padding(60) .frame(width: 460, height: 460) - .clipShape(RoundedRectangle(cornerRadius: 10)) - .modifier(AnimatableBlur(blurRadius: self.imageWasTapped ? 0 : 20)) - .modifier(AnimatableSaturation(saturation: self.imageWasTapped ? 1 : 0)) - } - - if self.image.isVectorImageFile { - SVGView(contentsOf: URL(fileURLWithPath: self.image)) - .frame(width: 460, height: 460) + .foregroundStyle(.black) .background(self.choiceBackgroundColor) .clipShape(RoundedRectangle(cornerRadius: 10)) .modifier(AnimatableBlur(blurRadius: self.imageWasTapped ? 0 : 20)) .modifier(AnimatableSaturation(saturation: self.imageWasTapped ? 1 : 0)) + } else { + if !FileManager.default.fileExists(atPath: self.image) { + self.imageNotFound() + } + + if self.image.isRasterImageFile { + Image(uiImage: UIImage(named: self.image)!) + .resizable() + .clipShape(RoundedRectangle(cornerRadius: 10)) + .scaledToFit() + .frame(width: 460, height: 460) + .background(self.choiceBackgroundColor) + .clipShape(RoundedRectangle(cornerRadius: 10)) + .modifier(AnimatableBlur(blurRadius: self.imageWasTapped ? 0 : 20)) + .modifier(AnimatableSaturation(saturation: self.imageWasTapped ? 1 : 0)) + } + + if self.image.isVectorImageFile { + SVGView(contentsOf: URL(fileURLWithPath: self.image)) + .clipShape(RoundedRectangle(cornerRadius: 10)) + .frame(width: 460, height: 460) + .background(self.choiceBackgroundColor) + .clipShape(RoundedRectangle(cornerRadius: 10)) + .modifier(AnimatableBlur(blurRadius: self.imageWasTapped ? 0 : 20)) + .modifier(AnimatableSaturation(saturation: self.imageWasTapped ? 1 : 0)) + } } } } @@ -144,14 +167,24 @@ extension l10n { } #Preview { - struct ActionObserveButtonContainer: View { - @State var imageWasTapped = false - var body: some View { - ActionButtonObserve( - image: "placeholder-observe_then_touch_to_select", imageWasTapped: $imageWasTapped - ) + ScrollView { + VStack { + HStack { + ActionButtonObserve( + image: "4.circle", imageWasTapped: .constant(false) + ) + ActionButtonObserve( + image: "4.circle", imageWasTapped: .constant(true) + ) + } + HStack { + ActionButtonObserve( + image: "pictograms-foods-fruits-banana_yellow-00FB", imageWasTapped: .constant(false) + ) + ActionButtonObserve( + image: "pictograms-foods-fruits-banana_yellow-00FB", imageWasTapped: .constant(true) + ) + } } } - - return ActionObserveButtonContainer() } diff --git a/Modules/GameEngineKit/Sources/Exercises/Touch/ObserveThenTouchToSelect/ObserveThenTouchToSelect.swift b/Modules/GameEngineKit/Sources/Exercises/Touch/ObserveThenTouchToSelect/ObserveThenTouchToSelect.swift index 555d62f59a..f11eba2763 100644 --- a/Modules/GameEngineKit/Sources/Exercises/Touch/ObserveThenTouchToSelect/ObserveThenTouchToSelect.swift +++ b/Modules/GameEngineKit/Sources/Exercises/Touch/ObserveThenTouchToSelect/ObserveThenTouchToSelect.swift @@ -15,17 +15,23 @@ public struct ObserveThenTouchToSelectView: View { } public init(exercise: Exercise, data: ExerciseSharedData? = nil) { - guard let payload = exercise.payload as? TouchToSelect.Payload, - case let .ipad(type: .image(name)) = exercise.action - else { - log.error("Exercise payload is not .selection and/or Exercise does not contain iPad image action") - fatalError("💥 Exercise payload is not .selection and/or Exercise does not contain iPad image action") + guard let payload = exercise.payload as? TouchToSelect.Payload else { + log.error("Invalid payload type: expected TouchToSelect.Payload, got \(type(of: exercise.payload))") + fatalError("💥 Invalid payload type: expected TouchToSelect.Payload, got \(type(of: exercise.payload))") + } + + switch exercise.action { + case let .ipad(type: .image(name)): + self.image = name + case let .ipad(type: .sfsymbol(name)): + self.image = name + default: + log.error("Invalid action type: expected iPad image or sfsymbol, got \(String(describing: exercise.action))") + fatalError("💥 Invalid action type: expected iPad image or sfsymbol, got \(String(describing: exercise.action))") } _viewModel = StateObject( wrappedValue: TouchToSelectViewViewModel(choices: payload.choices, shuffle: payload.shuffleChoices, shared: data)) - - self.image = name } // MARK: Public