Add CoreData-based workout tracking app with iOS and watchOS targets

- Migrate from SwiftData to CoreData with CloudKit sync
- Add core models: Split, Exercise, Workout, WorkoutLog
- Implement tab-based UI: Workout Logs, Splits, Settings
- Add SF Symbols picker for split icons
- Add exercise picker filtered by split with exclusion of added exercises
- Integrate IndieAbout for settings/about section
- Add Yams for YAML exercise definition parsing
- Include starter exercise libraries (bodyweight, Planet Fitness)
- Add Date extensions for formatting (formattedTime, isSameDay)
- Format workout date ranges to show time-only for same-day end dates
- Add build number update script
- Add app icons
This commit is contained in:
2026-01-19 06:42:15 -05:00
parent 2bfeb6a165
commit 13313a32d3
77 changed files with 3876 additions and 48 deletions

View File

@@ -0,0 +1,71 @@
import Foundation
import CoreData
@objc(Workout)
public class Workout: NSManagedObject, Identifiable {
@NSManaged public var start: Date
@NSManaged public var end: Date?
@NSManaged private var statusRaw: String
@NSManaged public var split: Split?
@NSManaged public var logs: NSSet?
public var id: NSManagedObjectID { objectID }
var status: WorkoutStatus {
get { WorkoutStatus(rawValue: statusRaw) ?? .notStarted }
set { statusRaw = newValue.rawValue }
}
var label: String {
if status == .completed, let endDate = end {
return "\(start.formattedDate())\(endDate.formattedDate())"
} else {
return start.formattedDate()
}
}
var statusName: String {
return status.displayName
}
}
// MARK: - Convenience Accessors
extension Workout {
var logsArray: [WorkoutLog] {
let set = logs as? Set<WorkoutLog> ?? []
return set.sorted { $0.order < $1.order }
}
func addToLogs(_ log: WorkoutLog) {
let items = mutableSetValue(forKey: "logs")
items.add(log)
}
func removeFromLogs(_ log: WorkoutLog) {
let items = mutableSetValue(forKey: "logs")
items.remove(log)
}
}
// MARK: - Fetch Request
extension Workout {
@nonobjc public class func fetchRequest() -> NSFetchRequest<Workout> {
return NSFetchRequest<Workout>(entityName: "Workout")
}
static func recentFetchRequest() -> NSFetchRequest<Workout> {
let request = fetchRequest()
request.sortDescriptors = [NSSortDescriptor(keyPath: \Workout.start, ascending: false)]
return request
}
static func fetchRequest(for split: Split) -> NSFetchRequest<Workout> {
let request = fetchRequest()
request.predicate = NSPredicate(format: "split == %@", split)
request.sortDescriptors = [NSSortDescriptor(keyPath: \Workout.start, ascending: false)]
return request
}
}