Replaces the app-local copies of the extracted storage core with the IndieSync 0.1.0 package (pinned from the new tag): ICloudFileManager -> DocumentFileStore + TombstoneStore, ICloudFileMonitor -> MetadataObserver (batched events with the content-date churn gate), and the shared ULID / DocumentCoder / Tombstone / VersionedDocument pieces. SyncEngine stays app-specific and is rewired onto the package types; documents rename currentSchema -> currentSchemaVersion to adopt the package protocol. ULID.make() remains as a shim minting the string form the app keys on. Behavior-preserving: identical JSON bytes (same coder config), same stub wire format (kind encodes as the same strings), same soft-delete ordering, same 30-day prune. WorkoutDocument keeps its local-calendar month bucketing rather than adopting TimeBucketedLayout's UTC paths -- changing derivation would strand existing files. The watch target links the package too (ULID + DocumentCoder via Shared); iOS, watchOS, and widget targets all build.
85 lines
3.7 KiB
Swift
85 lines
3.7 KiB
Swift
import IndieSync
|
|
import Foundation
|
|
import SwiftData
|
|
|
|
/// Builds the bundled machine-based starter routine (Upper Body / Core / Lower
|
|
/// Body). Written on demand (never auto-seeded) through the SyncEngine — an empty
|
|
/// cache at launch can't be told apart from an iCloud library that simply hasn't
|
|
/// downloaded yet.
|
|
enum SplitSeeder {
|
|
/// One starter exercise: name plus its default starting weight (lbs).
|
|
private struct SeedExercise {
|
|
let name: String
|
|
let weight: Int
|
|
}
|
|
|
|
/// One starter split: visual theme, activity type, plus its ordered exercises.
|
|
private struct SeedSplit {
|
|
let name: String
|
|
let color: String
|
|
let icon: String
|
|
let activity: WorkoutActivityType
|
|
let exercises: [SeedExercise]
|
|
}
|
|
|
|
/// Sets/reps shared by every starter exercise.
|
|
private static let defaultSets = 4
|
|
private static let defaultReps = 10
|
|
|
|
/// Starter splits in display order, with sensible machine starting weights.
|
|
/// Users adjust weights from the exercise screen.
|
|
private static let starterSplits: [SeedSplit] = [
|
|
SeedSplit(name: "Upper Body", color: "blue", icon: "figure.strengthtraining.traditional", activity: .traditionalStrength, exercises: [
|
|
SeedExercise(name: "Lat Pull Down", weight: 110),
|
|
SeedExercise(name: "Tricep Press", weight: 100),
|
|
SeedExercise(name: "Chest Press", weight: 40),
|
|
SeedExercise(name: "Seated Row", weight: 90),
|
|
]),
|
|
SeedSplit(name: "Core", color: "orange", icon: "figure.core.training", activity: .coreTraining, exercises: [
|
|
SeedExercise(name: "Abdominal", weight: 0),
|
|
SeedExercise(name: "Rotary", weight: 0),
|
|
]),
|
|
SeedSplit(name: "Lower Body", color: "green", icon: "figure.run", activity: .traditionalStrength, exercises: [
|
|
SeedExercise(name: "Abductor", weight: 140),
|
|
SeedExercise(name: "Adductor", weight: 140),
|
|
SeedExercise(name: "Leg Press", weight: 160),
|
|
SeedExercise(name: "Leg Curl", weight: 70),
|
|
]),
|
|
]
|
|
|
|
/// Builds the default split documents (fresh ULIDs each call).
|
|
static func defaultSplitDocuments() -> [SplitDocument] {
|
|
starterSplits.enumerated().map { order, split in
|
|
let exercises = split.exercises.enumerated().map { idx, item in
|
|
ExerciseDocument(
|
|
id: ULID.make(), name: item.name, order: idx,
|
|
sets: defaultSets, reps: defaultReps, weight: item.weight,
|
|
loadType: LoadType.weight.rawValue,
|
|
durationSeconds: 0, weightLastUpdated: nil, weightReminderWeeks: 2
|
|
)
|
|
}
|
|
return SplitDocument(
|
|
schemaVersion: SplitDocument.currentSchemaVersion, id: ULID.make(),
|
|
name: split.name, color: split.color, systemImage: split.icon, order: order,
|
|
createdAt: Date(), updatedAt: Date(), exercises: exercises,
|
|
activityType: split.activity.rawValue
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Writes any starter splits whose name doesn't already exist, appended after
|
|
/// existing splits. Idempotent against double-taps / partial prior seeds.
|
|
@MainActor
|
|
static func seedDefaults(into context: ModelContext, using sync: SyncEngine) async {
|
|
let existing = (try? context.fetch(FetchDescriptor<Split>())) ?? []
|
|
let existingNames = Set(existing.map(\.name))
|
|
let base = existing.count
|
|
|
|
let fresh = defaultSplitDocuments().filter { !existingNames.contains($0.name) }
|
|
for (offset, var doc) in fresh.enumerated() {
|
|
doc.order = base + offset
|
|
await sync.save(split: doc)
|
|
}
|
|
}
|
|
}
|