diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a0aa842fb0..6dc27d1e9b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,10 +13,10 @@ repos: types: [file] files: \.(json|xcstrings)$ - id: pretty-format-json - args: ['--autofix', '--indent=4', '--top-keys=version,sourceLanguage'] + args: ["--autofix", "--indent=4", "--top-keys=version,sourceLanguage"] types: [file] files: \.(json|xcstrings)$ - exclude: '.vscode/settings.json' + exclude: '(.vscode/settings.json|\.jtd.json$)' - id: check-added-large-files - id: check-executables-have-shebangs - id: check-shebang-scripts-are-executable diff --git a/.swiftlint.yml b/.swiftlint.yml index 3d11622dc8..bb6e58aac1 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -29,6 +29,10 @@ identifier_name: - BLE - cmd - g + - hmi + - HMI + - hmis + - HMIs - i - i18n - id diff --git a/Modules/ContentKit/Examples/ContentKitExample/Resources/activity.yml b/Modules/ContentKit/Examples/ContentKitExample/Resources/activity.yml index ce582ce4d7..2a865b85df 100644 --- a/Modules/ContentKit/Examples/ContentKitExample/Resources/activity.yml +++ b/Modules/ContentKit/Examples/ContentKitExample/Resources/activity.yml @@ -25,6 +25,11 @@ locales: - en_US - fr_FR +hmi: + - robot + - magic_cards + - tablet_robot + l10n: - locale: fr_FR details: diff --git a/Modules/ContentKit/Examples/ContentKitExample/Sources/MainView.swift b/Modules/ContentKit/Examples/ContentKitExample/Sources/MainView.swift index fd38b4d0a0..d985a152ae 100644 --- a/Modules/ContentKit/Examples/ContentKitExample/Sources/MainView.swift +++ b/Modules/ContentKit/Examples/ContentKitExample/Sources/MainView.swift @@ -83,6 +83,20 @@ struct MainView: View { } } + DisclosureGroup("**HMI**") { + ForEach(self.activity?.hmi ?? [], id: \.self) { hmi in + let hmi = HMI.hmi(id: hmi)! + HStack { + Text(hmi.name) + Button { + self.selectedHMI = hmi + } label: { + Image(systemName: "info.circle") + } + } + } + } + DisclosureGroup("**Tags**") { ForEach(self.activity?.tags ?? [], id: \.self) { skill in Text(skill) @@ -108,6 +122,13 @@ struct MainView: View { Text(skill.description) } }) + .sheet(item: self.$selectedHMI, onDismiss: { self.selectedHMI = nil }, content: { hmi in + VStack(alignment: .leading) { + Text(hmi.name) + .font(.headline) + Text(hmi.description) + } + }) .onAppear { self.activity = ContentKit.decodeActivity("activity") print(self.activity ?? "not working") @@ -119,12 +140,21 @@ struct MainView: View { print("name: \(skill.name)") print("description: \(skill.description)") } + + let hmis = HMI.list + for (index, hmi) in hmis.enumerated() { + print("hmi \(index + 1)") + print("id: \(hmi.id)") + print("name: \(hmi.name)") + print("description: \(hmi.description)") + } } } // MARK: Private @State private var selectedSkill: Skill? + @State private var selectedHMI: HMIDetails? @State private var activity: Activity? } diff --git a/Modules/ContentKit/Resources/Content/hmi/hmi.yml b/Modules/ContentKit/Resources/Content/hmi/hmi.yml new file mode 100644 index 0000000000..409c6c52fe --- /dev/null +++ b/Modules/ContentKit/Resources/Content/hmi/hmi.yml @@ -0,0 +1,49 @@ +# Leka - iOS Monorepo +# Copyright APF France handicap +# SPDX-License-Identifier: Apache-2.0 + +version: 1.0.0 +list: + - id: robot + l10n: + - locale: fr_FR + name: Robot seul + description: | + L'utilisateur interagit directement avec le robot. + - locale: en_US + name: Robot alone + description: | + The user interacts directly with the robot. + + - id: magic_cards + l10n: + - locale: fr_FR + name: Robot et cartes magiques + description: | + L'utilisateur interagit avec le robot en utilisant les cartes magiques. + - locale: en_US + name: Robot and magic cards + description: | + The user interacts with the robot by using magic cards. + + - id: tablet_robot + l10n: + - locale: fr_FR + name: iPad et robot + description: | + L'utilisateur interagit avec l'iPad, le robot agit en tant que support pour observer avant de répondre. + - locale: en_US + name: iPad and robot + description: | + The user interacts with the iPad, and the robot serves as support for observation before providing answers. + + - id: tablet + l10n: + - locale: fr_FR + name: iPad (robot en renforçateur) + description: | + L'utilisateur interagit avec l'iPad, le robot agit en tant que renforçateur. + - locale: en_US + name: iPad (robot as reinforcer) + description: | + The user interacts with the iPad, and the robot serves as reinforcer. diff --git a/Modules/ContentKit/Sources/Activity/Activity.swift b/Modules/ContentKit/Sources/Activity/Activity.swift index 7024a71119..ccd5130297 100644 --- a/Modules/ContentKit/Sources/Activity/Activity.swift +++ b/Modules/ContentKit/Sources/Activity/Activity.swift @@ -19,6 +19,7 @@ public struct Activity: Codable, Identifiable { self.name = try container.decode(String.self, forKey: .name) self.authors = try container.decode([String].self, forKey: .authors) self.skills = try container.decode([String].self, forKey: .skills) + self.hmi = try container.decode([String].self, forKey: .hmi) self.tags = try container.decode([String].self, forKey: .tags) self.status = try container.decode(Status.self, forKey: .status) self.gameengine = try container.decode(GameEngine.self, forKey: .gameengine) @@ -39,6 +40,7 @@ public struct Activity: Codable, Identifiable { public let authors: [String] // TODO: (@ladislas) - implement authors public let skills: [String] // TODO: (@ladislas) - implement skills + public let hmi: [String] // TODO: (@ladislas) - implement hmi public let tags: [String] // TODO: (@ladislas) - implement tags public let locales: [Locale] @@ -71,6 +73,7 @@ public struct Activity: Codable, Identifiable { case name case authors case skills + case hmi case tags case status case locales diff --git a/Modules/ContentKit/Sources/Models/HMI.swift b/Modules/ContentKit/Sources/Models/HMI.swift new file mode 100644 index 0000000000..4bfbd4faef --- /dev/null +++ b/Modules/ContentKit/Sources/Models/HMI.swift @@ -0,0 +1,108 @@ +// Leka - iOS Monorepo +// Copyright APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +import Foundation +import LocalizationKit +import LogKit +import Version +import Yams + +// MARK: - HMI + +public class HMI: Codable { + // MARK: Lifecycle + + private init() { + self.container = Self.loadHMI() + } + + // MARK: Public + + public static var list: [HMIDetails] { + shared.container.list + } + + public static func hmi(id: String) -> HMIDetails? { + self.list.first(where: { $0.id == id }) + } + + // MARK: Private + + private struct HMIContainer: Codable { + let list: [HMIDetails] + } + + private static let shared: HMI = .init() + + private let container: HMIContainer + + private static func loadHMI() -> HMIContainer { + if let fileURL = Bundle.module.url(forResource: "hmi", withExtension: "yml") { + do { + let yamlString = try String(contentsOf: fileURL, encoding: .utf8) + let container = try YAMLDecoder().decode(HMIContainer.self, from: yamlString) + return container + } catch { + log.error("Failed to read YAML file: \(error)") + return HMIContainer(list: []) + } + } else { + log.error("hmi.yml not found") + return HMIContainer(list: []) + } + } +} + +// MARK: - HMIDetails + +public struct HMIDetails: Codable, Identifiable { + // MARK: Lifecycle + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(String.self, forKey: .id) + + self.l10n = try container.decode([HMIDetails.Localization].self, forKey: .l10n) + + let availableLocales = self.l10n.map(\.locale) + + let currentLocale = availableLocales.first(where: { + $0.language.languageCode == LocalizationKit.l10n.language + }) ?? Locale(identifier: "en_US") + + self.name = self.l10n.first(where: { $0.locale == currentLocale })!.name + self.description = self.l10n.first(where: { $0.locale == currentLocale })!.description + } + + // MARK: Public + + public let id: String + public let name: String + public let description: String + + // MARK: Private + + private let l10n: [Localization] +} + +// MARK: HMIDetails.Localization + +public extension HMIDetails { + struct Localization: Codable { + // MARK: Lifecycle + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.locale = try Locale(identifier: container.decode(String.self, forKey: .locale)) + self.name = try container.decode(String.self, forKey: .name) + self.description = try container.decode(String.self, forKey: .description) + } + + // MARK: Internal + + let locale: Locale + let name: String + let description: String + } +} diff --git a/Modules/ContentKit/Specs/jtd/activity.jtd.json b/Modules/ContentKit/Specs/jtd/activity.jtd.json index f3fea0e29d..c7c790bc09 100644 --- a/Modules/ContentKit/Specs/jtd/activity.jtd.json +++ b/Modules/ContentKit/Specs/jtd/activity.jtd.json @@ -19,6 +19,11 @@ "ref": "$skill" } }, + "hmi": { + "elements": { + "ref": "$hmi" + } + }, "tags": { "elements": { "ref": "$tag" @@ -58,17 +63,14 @@ "julie_tuil" ] }, + "$hmi": { + "enum": ["robot", "tablet", "magic_cards", "tablet_robot"] + }, "$locale": { - "enum": [ - "en_US", - "fr_FR" - ] + "enum": ["en_US", "fr_FR"] }, "$status": { - "enum": [ - "draft", - "published" - ] + "enum": ["draft", "published"] }, "$l10n": { "properties": { @@ -156,13 +158,10 @@ ] }, "$exercise/gameplay": { - "enum": [ - "findTheRightAnswers", - "associateCategories" - ] + "enum": ["findTheRightAnswers", "associateCategories"] }, "$exercise/payload": { "type": "string" } } -} \ No newline at end of file +} diff --git a/Modules/ContentKit/Specs/jtd/curriculum.jtd.json b/Modules/ContentKit/Specs/jtd/curriculum.jtd.json index de23854d06..67f2532a9e 100644 --- a/Modules/ContentKit/Specs/jtd/curriculum.jtd.json +++ b/Modules/ContentKit/Specs/jtd/curriculum.jtd.json @@ -19,6 +19,11 @@ "ref": "$skill" } }, + "hmi": { + "elements": { + "ref": "$hmi" + } + }, "tags": { "elements": { "ref": "$tag" @@ -48,17 +53,14 @@ "julie_tuil" ] }, + "$hmi": { + "enum": ["robot", "tablet", "magic_cards", "tablet_robot"] + }, "$status": { - "enum": [ - "draft", - "published" - ] + "enum": ["draft", "published"] }, "$locale": { - "enum": [ - "en_US", - "fr_FR" - ] + "enum": ["en_US", "fr_FR"] }, "$l10n": { "properties": { @@ -96,4 +98,4 @@ "type": "string" } } -} \ No newline at end of file +} diff --git a/Modules/ContentKit/Specs/tests/activity.yml b/Modules/ContentKit/Specs/tests/activity.yml index c3119648d9..a1db126b5f 100644 --- a/Modules/ContentKit/Specs/tests/activity.yml +++ b/Modules/ContentKit/Specs/tests/activity.yml @@ -16,6 +16,11 @@ skills: - skill_two - skill_three +hmi: + - robot + - magic_cards + - tablet_robot + tags: - tag_one - tag_two diff --git a/Modules/ContentKit/Specs/tests/curriculum.yml b/Modules/ContentKit/Specs/tests/curriculum.yml index cd634f3f2a..3398f2c2cd 100644 --- a/Modules/ContentKit/Specs/tests/curriculum.yml +++ b/Modules/ContentKit/Specs/tests/curriculum.yml @@ -20,6 +20,11 @@ skills: - skill_two - skill_three +hmi: + - robot + - magic_cards + - tablet_robot + tags: - tag_one - tag_two