Hi Jay, thanks for the quick reply.
class AssignedLearningPath: EmbeddedObject, Identifiable {
@Persisted var id: UUID = UUID()
@Persisted var title: String = ""
@Persisted var skills: List<Skill>
// @Persisted var status: Status
@Persisted var assignedSkillIDs: List<UUID>
func setup(basedOn selectedLearningPath: LearningPath) {
self.title = selectedLearningPath.title
print("Title set to: \(self.title)")
self.skills.append(objectsIn: selectedLearningPath.skills)
print("Skills added: \n\(self.skills)")
}
}
//
// AssignedSkill.swift
// Grallify
//
// Created by Andrew Grall on 11/11/23.
//
import Foundation
import RealmSwift
/// An AssignedSkill is a skill that's assigned to a student.
/// Example:
/// Skill: C Major
/// Hands: enum (left / right / separate / together / notSet)
/// Focus: enum (identify / play / sing / listen / read / write / etc.)
///
class AssignedSkill: EmbeddedObject, Identifiable {
@Persisted var id: UUID = UUID() // Unique identifier. Not primaryKey since it's an embedded object.
@Persisted var skillID: ObjectId // Reference to the Skill ID
@Persisted var tasks: List<AssignedSkillTask>
@Persisted var hands: List<Hands> // left, right, both separately, both together
@Persisted var focus: List<Focus> // identify, play, sing, listen, read, etc.
@Persisted var tempo: String = "" // bpm @ quarter notes
@Persisted var chordPosition: List<ChordPosition> // root, first inversion, etc.
@Persisted var range: Range = .notSet // how many octaves, one through seven
// TODO: Notes needs to be an array. It holds current notes to the student about the assigned skill. Notes can be checked off as they're no longer relevant, or completed. See StudentSnapshot for examples in comments. ((Might have changed my mind about this))
@Persisted var notes: String = "" // notes about this particular assigned skill for this student.
@Persisted var status: Status = .assigned // set to assigned to begin with
@Persisted var dateAssigned: Date // date when skill was assigned (automatically set to date of AssignedSkill creation
@Persisted var dateCompleted: Date? // date when skill was completed (by definition will not be set on creation)
@Persisted var datePaused: Date? // date when skill was paused
var title: String {
if let skill = RealmManager.fetchSkill(withID: skillID) {
return skill.title
}
// else
return ""
}
var shortDescription: String {
var descriptionParts: [String] = []
if !hands.isEmpty {
descriptionParts.append("Hands: \(hands.map { $0.rawValue }.joined(separator: ", "))")
}
if !focus.isEmpty {
descriptionParts.append("Focus: \(focus.map { $0.rawValue }.joined(separator: ", "))")
}
if !tempo.isEmpty {
descriptionParts.append("Tempo: \(tempo)")
}
if !chordPosition.isEmpty {
descriptionParts.append("Position: \(chordPosition.map { $0.rawValue }.joined(separator: ", "))")
}
if range != .notSet {
descriptionParts.append("Range: \(range.rawValue)")
}
return descriptionParts.joined(separator: ", ")
}
convenience init(
skillID: ObjectId,
hands: [Hands] = [],
focus: [Focus] = [],
tempo: String = "",
chordPosition: [ChordPosition] = [],
range: Range = .notSet,
notes: String = "",
status: Status = .assigned,
dateAssigned: Date = Date(),
dateCompleted: Date? = nil,
datePaused: Date? = nil
) {
self.init()
self.skillID = skillID
if hands.isEmpty && focus.isEmpty && tempo.isEmpty && chordPosition.isEmpty && range == .notSet {
setDefaultValues()
} else {
self.hands.append(objectsIn: hands)
self.focus.append(objectsIn: focus)
self.tempo = tempo
self.chordPosition.append(objectsIn: chordPosition)
self.range = range
}
self.notes = notes
self.status = status
self.dateAssigned = dateAssigned
self.dateCompleted = dateCompleted
self.datePaused = datePaused
}
enum Status: String, PersistableEnum {
case assigned = "Assigned" // teacher has assigned but student hasn't begun working on it.
case inProgress = "In Progress" // student has done something from their side to actively start working on it. Teacher can set this manually or it can be set automatically by student interacting with it from their learning hub.
case completed = "Completed" // only set by teacher manually
case paused = "Paused" // Represents skills that are temporarily set aside
}
enum Hands: String, PersistableEnum, CaseIterable {
case left = "Left Hand"
case right = "Right Hand"
case together = "Both Hands Together"
var order: Int {
switch self {
case .right: return 0
case .left: return 1
case .together: return 2
}
}
}
enum Range: String, PersistableEnum, CaseIterable {
case oneOctave = "One Octave"
case twoOctaves = "Two Octaves"
case threeOctaves = "Three Octaves"
case fourOctaves = "Four Octaves"
case fiveOctaves = "Five Octaves"
case sixOctaves = "Six Octaves"
case sevenOctaves = "Seven Octaves"
case notSet = ""
}
enum ChordPosition: String, PersistableEnum, CaseIterable {
case rootPosition = "Root Position"
case firstInversion = "First Inversion"
case secondInversion = "Second Inversion"
case thirdInversion = "Third Inversion"
// declaring the order so when user sees positions listed on an assigned skill details view, they're in positional order instead of alphabetical or non-ordered.
var order: Int {
switch self {
case .rootPosition: return 0
case .firstInversion: return 1
case .secondInversion: return 2
case .thirdInversion: return 3
}
}
}
enum Focus: String, PersistableEnum, CaseIterable {
case identify = "Identify"
case play = "Play"
case memorize = "Memorize"
case sing = "Sing"
case listen = "Listen"
case read = "Read"
case write = "Write"
case improvise = "Improvise"
case analyze = "Analyze"
}
}
// MOCK SKILL
extension AssignedSkill {
static func mock() -> AssignedSkill {
let assignedSkill = AssignedSkill()
// Set the properties of assignedSkill as needed for the preview
assignedSkill.notes = "Sample notes for the assigned skill."
return assignedSkill
}
}
// SET DEFAULT VALUES
extension AssignedSkill {
func setDefaultValues() {
if let skill = RealmManager.fetchSkill(withID: skillID) {
switch skill.type {
case .chord:
self.hands.append(objectsIn: [.left, .right])
self.focus.append(objectsIn: [.play])
self.chordPosition.append(objectsIn: [.rootPosition])
self.range = .notSet
case .scale:
self.hands.append(objectsIn: [.left, .right])
self.focus.append(objectsIn: [.play])
self.range = .oneOctave
// Add cases for other skill types if needed
default:
break
}
}
}
}
//
// Student.swift
// Grallify
//
// Created by Andrew Grall on 11/8/23.
//
import Foundation
import RealmSwift
class Student: Object, Identifiable {
@Persisted(primaryKey: true) var id: ObjectId
@Persisted var firstName: String
@Persisted var lastName: String
@Persisted var isChild: Bool = true // students might be self-managed adults or parent-managed children
@Persisted var parents = List<Parent>() // if student is a self-managed adult, this list will be empty
@Persisted var email: String // email address
@Persisted var phoneNumber: String
@Persisted var address: String // physical address
@Persisted var assignedSkills = List<AssignedSkill>() // a list of AssignedSkills, which are embedded objects which reference Skill objects.
@Persisted var assignedMusicPieces = List<AssignedMusicPiece>() // a list of AssignedMusicPieces, which are embedded objects which reference MusicPiece objects.
@Persisted var assignedLearningPaths = List<AssignedLearningPath>() // a list of AssignedLearningPaths
@Persisted var currentNotes: String = "" // current notes about the student
@Persisted var dateCreated: Date = Date() // keep track of when this student was created
// if applicable, the student group this Student belongs to
@Persisted(originProperty: "students") var belongsToStudentGroup: LinkingObjects<StudentGroup>
@Persisted(originProperty: "students") var belongsToFamily: LinkingObjects<Family>
var fullName: String {
"\(firstName) \(lastName)"
}
// to reference in code when we need to display whatever property about the student we have available.
var shortName: String {
if firstName.count > 0 {
return firstName
}
else if lastName.count > 0 {
return lastName
}
else if email.count > 0 {
return email
}
else if phoneNumber.count > 0 {
return phoneNumber
}
else {
return ""
}
}
// return an Int of how many skills the student has completed
var completedSkillsCount: Int {
return assignedSkills.filter { $0.status == .completed }.count
}
// return an Int of how many assigned Music Pieces the student has completed
var completedMusicPiecesCount: Int {
return assignedMusicPieces.filter { $0.status == .completed }.count
}
override class func primaryKey() -> String? {
"id"
}
}
extension Student {
// Helper static method to create mock data for previews
static func mock() -> Student {
let student = Student()
student.firstName = "FirstName"
student.lastName = "LastName"
student.isChild = true
student.email = "sample@student.com"
student.phoneNumber = "123-456-7890"
student.address = "123 Main St"
student.currentNotes = "Here are some general notes about the student. He continues to progress. His focus is getting better each time. We are spending less time correcting behavior and more time focusing on the actual musical skills. His extra practice time at home is paying off."
// Create a mock Skill
let sampleSkill1 = Skill() // Assuming Skill has a default initializer
sampleSkill1.title = "Sample Skill 1"
sampleSkill1.descriptionText = "Sample description of the sample skill."
// Create a mock AssignedSkill
let assignedSkill1 = AssignedSkill()
assignedSkill1.skillID = sampleSkill1.id
student.assignedSkills.append(assignedSkill1)
// Create a mock Skill
let sampleSkill2 = Skill() // Assuming Skill has a default initializer
sampleSkill2.title = "Sample Skill 2"
sampleSkill2.descriptionText = "Sample description of the sample skill."
// Create a mock AssignedSkill
let assignedSkill2 = AssignedSkill()
assignedSkill2.skillID = sampleSkill2.id
student.assignedSkills.append(assignedSkill2)
// Create a mock Skill
let sampleSkill3 = Skill() // Assuming Skill has a default initializer
sampleSkill3.title = "Sample Skill 3"
sampleSkill3.descriptionText = "Sample description of the sample skill."
// Create a mock AssignedSkill
let assignedSkill3 = AssignedSkill()
assignedSkill3.skillID = sampleSkill3.id
student.assignedSkills.append(assignedSkill3)
return student
}
}