wip
This commit is contained in:
@ -5,11 +5,7 @@ import SwiftUI
|
||||
@Model
|
||||
final class Exercise {
|
||||
var name: String = ""
|
||||
var setup: String = ""
|
||||
var descr: String = ""
|
||||
var sets: Int = 0
|
||||
var reps: Int = 0
|
||||
var weight: Int = 0
|
||||
|
||||
@Relationship(deleteRule: .nullify, inverse: \ExerciseType.exercises)
|
||||
var type: ExerciseType?
|
||||
@ -23,13 +19,9 @@ final class Exercise {
|
||||
@Relationship(deleteRule: .nullify, inverse: \WorkoutLog.exercise)
|
||||
var logs: [WorkoutLog]? = []
|
||||
|
||||
init(name: String, setup: String, descr: String, sets: Int, reps: Int, weight: Int) {
|
||||
init(name: String, descr: String) {
|
||||
self.name = name
|
||||
self.setup = setup
|
||||
self.descr = descr
|
||||
self.sets = sets
|
||||
self.reps = reps
|
||||
self.weight = weight
|
||||
}
|
||||
|
||||
static let unnamed = "Unnamed Exercise"
|
||||
@ -37,7 +29,7 @@ final class Exercise {
|
||||
|
||||
extension Exercise: EditableEntity {
|
||||
static func createNew() -> Exercise {
|
||||
return Exercise(name: "", setup: "", descr: "", sets: 3, reps: 10, weight: 30)
|
||||
return Exercise(name: "", descr: "")
|
||||
}
|
||||
|
||||
static var navigationTitle: String {
|
||||
@ -76,20 +68,5 @@ fileprivate struct ExerciseFormView: View {
|
||||
TextEditor(text: $model.descr)
|
||||
.frame(minHeight: 100)
|
||||
}
|
||||
|
||||
Section(header: Text("Setup")) {
|
||||
TextEditor(text: $model.setup)
|
||||
.frame(minHeight: 100)
|
||||
}
|
||||
|
||||
Section(header: Text("Weight")) {
|
||||
HStack {
|
||||
Text("\(model.weight)")
|
||||
.bold()
|
||||
Text("lbs")
|
||||
Spacer()
|
||||
Stepper("", value: $model.weight, in: 0...1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,27 @@ import SwiftUI
|
||||
final class Split {
|
||||
var name: String = ""
|
||||
var intro: String = ""
|
||||
var color: String = "indigo"
|
||||
var systemImage: String = "dumbbell.fill"
|
||||
|
||||
// Returns the SwiftUI Color for the stored color name
|
||||
func getColor() -> Color {
|
||||
switch color {
|
||||
case "red": return .red
|
||||
case "orange": return .orange
|
||||
case "yellow": return .yellow
|
||||
case "green": return .green
|
||||
case "mint": return .mint
|
||||
case "teal": return .teal
|
||||
case "cyan": return .cyan
|
||||
case "blue": return .blue
|
||||
case "indigo": return .indigo
|
||||
case "purple": return .purple
|
||||
case "pink": return .pink
|
||||
case "brown": return .brown
|
||||
default: return .indigo
|
||||
}
|
||||
}
|
||||
|
||||
@Relationship(deleteRule: .cascade, inverse: \SplitExerciseAssignment.split)
|
||||
var exercises: [SplitExerciseAssignment]? = []
|
||||
@ -13,9 +34,11 @@ final class Split {
|
||||
@Relationship(deleteRule: .nullify, inverse: \Workout.split)
|
||||
var workouts: [Workout]? = []
|
||||
|
||||
init(name: String, intro: String) {
|
||||
init(name: String, intro: String, color: String = "indigo", systemImage: String = "dumbbell.fill") {
|
||||
self.name = name
|
||||
self.intro = intro
|
||||
self.color = color
|
||||
self.systemImage = systemImage
|
||||
}
|
||||
|
||||
static let unnamed = "Unnamed Split"
|
||||
@ -53,6 +76,12 @@ fileprivate struct SplitFormView: View {
|
||||
@State private var itemToEdit: SplitExerciseAssignment? = nil
|
||||
@State private var itemToDelete: SplitExerciseAssignment? = nil
|
||||
|
||||
// Available colors for splits
|
||||
private let availableColors = ["red", "orange", "yellow", "green", "mint", "teal", "cyan", "blue", "indigo", "purple", "pink", "brown"]
|
||||
|
||||
// Available system images for splits
|
||||
private let availableIcons = ["dumbbell.fill", "figure.strengthtraining.traditional", "figure.run", "figure.hiking", "figure.cooldown", "figure.boxing", "figure.wrestling", "figure.gymnastics", "figure.handball", "figure.core.training", "heart.fill", "bolt.fill"]
|
||||
|
||||
var body: some View {
|
||||
Section(header: Text("Name")) {
|
||||
TextField("Name", text: $model.name)
|
||||
@ -64,6 +93,32 @@ fileprivate struct SplitFormView: View {
|
||||
.frame(minHeight: 100)
|
||||
}
|
||||
|
||||
Section(header: Text("Appearance")) {
|
||||
Picker("Color", selection: $model.color) {
|
||||
ForEach(availableColors, id: \.self) { colorName in
|
||||
let tempSplit = Split(name: "", intro: "", color: colorName)
|
||||
HStack {
|
||||
Circle()
|
||||
.fill(tempSplit.getColor())
|
||||
.frame(width: 20, height: 20)
|
||||
Text(colorName.capitalized)
|
||||
}
|
||||
.tag(colorName)
|
||||
}
|
||||
}
|
||||
|
||||
Picker("Icon", selection: $model.systemImage) {
|
||||
ForEach(availableIcons, id: \.self) { iconName in
|
||||
HStack {
|
||||
Image(systemName: iconName)
|
||||
.frame(width: 24, height: 24)
|
||||
Text(iconName.replacingOccurrences(of: ".fill", with: "").replacingOccurrences(of: "figure.", with: "").capitalized)
|
||||
}
|
||||
.tag(iconName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section(header: Text("Exercises")) {
|
||||
NavigationLink {
|
||||
NavigationStack {
|
||||
@ -109,9 +164,9 @@ fileprivate struct SplitFormView: View {
|
||||
ExercisePickerView { exercise in
|
||||
itemToEdit = SplitExerciseAssignment(
|
||||
order: 0,
|
||||
sets: exercise.sets,
|
||||
reps: exercise.reps,
|
||||
weight: exercise.weight,
|
||||
sets: 3,
|
||||
reps: 10,
|
||||
weight: 40,
|
||||
split: model,
|
||||
exercise: exercise
|
||||
)
|
||||
|
@ -7,6 +7,9 @@ final class WorkoutLog {
|
||||
var sets: Int = 0
|
||||
var reps: Int = 0
|
||||
var weight: Int = 0
|
||||
var status: WorkoutStatus? = WorkoutStatus.notStarted
|
||||
var order: Int = 0
|
||||
|
||||
var completed: Bool = false
|
||||
|
||||
@Relationship(deleteRule: .nullify)
|
||||
@ -15,13 +18,15 @@ final class WorkoutLog {
|
||||
@Relationship(deleteRule: .nullify)
|
||||
var exercise: Exercise?
|
||||
|
||||
init(workout: Workout, exercise: Exercise, date: Date, sets: Int, reps: Int, weight: Int, completed: Bool) {
|
||||
init(workout: Workout, exercise: Exercise, date: Date, order: Int = 0, sets: Int, reps: Int, weight: Int, status: WorkoutStatus = .notStarted, completed: Bool = false) {
|
||||
self.date = date
|
||||
self.order = order
|
||||
self.sets = sets
|
||||
self.reps = reps
|
||||
self.weight = weight
|
||||
self.completed = completed
|
||||
self.status = status
|
||||
self.workout = workout
|
||||
self.exercise = exercise
|
||||
self.completed = completed
|
||||
}
|
||||
}
|
||||
|
22
Workouts/Models/WorkoutStatus.swift
Normal file
22
Workouts/Models/WorkoutStatus.swift
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// WorkoutStatus.swift
|
||||
// Workouts
|
||||
//
|
||||
// Created by rzen on 7/16/25 at 7:03 PM.
|
||||
//
|
||||
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
||||
//
|
||||
|
||||
enum WorkoutStatus: Int, Codable {
|
||||
case notStarted = 1
|
||||
case inProgress = 2
|
||||
case completed = 3
|
||||
|
||||
var checkboxStatus: CheckboxStatus {
|
||||
switch (self) {
|
||||
case .notStarted: .unchecked
|
||||
case .inProgress: .intermediate
|
||||
case .completed: .checked
|
||||
}
|
||||
}
|
||||
}
|
@ -92,8 +92,7 @@ struct DataLoader {
|
||||
// 4. Load Exercises
|
||||
let exerciseData = try loadJSON(forResource: "exercises", type: [ExerciseData].self)
|
||||
for data in exerciseData {
|
||||
let exercise = Exercise(name: data.name, setup: data.setup, descr: data.descr,
|
||||
sets: data.sets, reps: data.reps, weight: data.weight)
|
||||
let exercise = Exercise(name: data.name, descr: data.descr)
|
||||
|
||||
// Set exercise type
|
||||
if let type = exerciseTypes[data.type] {
|
||||
|
16
Workouts/Schema/SchemaV2.swift
Normal file
16
Workouts/Schema/SchemaV2.swift
Normal file
@ -0,0 +1,16 @@
|
||||
import SwiftData
|
||||
|
||||
enum SchemaV2: VersionedSchema {
|
||||
static var versionIdentifier: Schema.Version = .init(1, 0, 1)
|
||||
|
||||
static var models: [any PersistentModel.Type] = [
|
||||
Exercise.self,
|
||||
ExerciseType.self,
|
||||
Muscle.self,
|
||||
MuscleGroup.self,
|
||||
Split.self,
|
||||
SplitExerciseAssignment.self,
|
||||
Workout.self,
|
||||
WorkoutLog.self
|
||||
]
|
||||
}
|
16
Workouts/Schema/SchemaV3.swift
Normal file
16
Workouts/Schema/SchemaV3.swift
Normal file
@ -0,0 +1,16 @@
|
||||
import SwiftData
|
||||
|
||||
enum SchemaV3: VersionedSchema {
|
||||
static var versionIdentifier: Schema.Version = .init(1, 0, 2)
|
||||
|
||||
static var models: [any PersistentModel.Type] = [
|
||||
Exercise.self,
|
||||
ExerciseType.self,
|
||||
Muscle.self,
|
||||
MuscleGroup.self,
|
||||
Split.self,
|
||||
SplitExerciseAssignment.self,
|
||||
Workout.self,
|
||||
WorkoutLog.self
|
||||
]
|
||||
}
|
@ -2,6 +2,8 @@ import SwiftData
|
||||
|
||||
enum SchemaVersion: Int {
|
||||
case v1
|
||||
case v2
|
||||
case v3
|
||||
|
||||
static var current: SchemaVersion { .v1 }
|
||||
static var current: SchemaVersion { .v3 }
|
||||
}
|
||||
|
@ -8,9 +8,10 @@ final class WorkoutsContainer {
|
||||
)
|
||||
|
||||
static func create() -> ModelContainer {
|
||||
let schema = Schema(versionedSchema: SchemaV1.self)
|
||||
// Using the current models directly without migration plan to avoid reference errors
|
||||
let schema = Schema(SchemaV2.models)
|
||||
let configuration = ModelConfiguration(cloudKitDatabase: .automatic)
|
||||
let container = try! ModelContainer(for: schema, migrationPlan: WorkoutsMigrationPlan.self, configurations: [configuration])
|
||||
let container = try! ModelContainer(for: schema, configurations: configuration)
|
||||
return container
|
||||
}
|
||||
|
||||
@ -19,7 +20,7 @@ final class WorkoutsContainer {
|
||||
let configuration = ModelConfiguration(isStoredInMemoryOnly: true)
|
||||
|
||||
do {
|
||||
let schema = Schema(SchemaV1.models)
|
||||
let schema = Schema(SchemaV2.models)
|
||||
let container = try ModelContainer(for: schema, configurations: configuration)
|
||||
let context = ModelContext(container)
|
||||
|
||||
|
@ -2,10 +2,28 @@ import SwiftData
|
||||
|
||||
struct WorkoutsMigrationPlan: SchemaMigrationPlan {
|
||||
static var schemas: [VersionedSchema.Type] = [
|
||||
SchemaV1.self
|
||||
SchemaV1.self,
|
||||
SchemaV2.self
|
||||
]
|
||||
|
||||
static var stages: [MigrationStage] = [
|
||||
// Add migration stages here in the future
|
||||
// Migration from V1 to V2: Add status field to WorkoutLog
|
||||
MigrationStage.custom(
|
||||
fromVersion: SchemaV1.self,
|
||||
toVersion: SchemaV2.self,
|
||||
willMigrate: { context in
|
||||
// Get all WorkoutLog instances
|
||||
let workoutLogs = try? context.fetch(FetchDescriptor<WorkoutLog>())
|
||||
|
||||
// Update each WorkoutLog with appropriate status based on completed flag
|
||||
workoutLogs?.forEach { workoutLog in
|
||||
// If completed is true, set status to .completed, otherwise set to .notStarted
|
||||
workoutLog.status = workoutLog.completed ? WorkoutStatus.completed : WorkoutStatus.notStarted
|
||||
}
|
||||
},
|
||||
didMigrate: { _ in
|
||||
// No additional actions needed after migration
|
||||
}
|
||||
)
|
||||
]
|
||||
}
|
||||
|
68
Workouts/Utils/CheckboxListItem.swift
Normal file
68
Workouts/Utils/CheckboxListItem.swift
Normal file
@ -0,0 +1,68 @@
|
||||
//
|
||||
// ListItem.swift
|
||||
// Workouts
|
||||
//
|
||||
// Created by rzen on 7/13/25 at 10:42 AM.
|
||||
//
|
||||
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
enum CheckboxStatus {
|
||||
case checked
|
||||
case unchecked
|
||||
case intermediate
|
||||
|
||||
var color: Color {
|
||||
switch (self) {
|
||||
case .checked: .green
|
||||
case .unchecked: .gray
|
||||
case .intermediate: .yellow
|
||||
}
|
||||
}
|
||||
|
||||
var systemName: String {
|
||||
switch (self) {
|
||||
case .checked: "checkmark.circle.fill"
|
||||
case .unchecked: "circle"
|
||||
case .intermediate: "ellipsis.circle"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CheckboxListItem: View {
|
||||
var status: CheckboxStatus
|
||||
var title: String
|
||||
var subtitle: String?
|
||||
var count: Int?
|
||||
|
||||
var body: some View {
|
||||
HStack (alignment: .top) {
|
||||
Image(systemName: status.systemName)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 30)
|
||||
.foregroundStyle(status.color)
|
||||
VStack (alignment: .leading) {
|
||||
Text("\(title)")
|
||||
.font(.headline)
|
||||
HStack (alignment: .bottom) {
|
||||
if let subtitle = subtitle {
|
||||
Text("\(subtitle)")
|
||||
.font(.footnote)
|
||||
}
|
||||
}
|
||||
}
|
||||
if let count = count {
|
||||
Spacer()
|
||||
Text("\(count)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
}
|
||||
|
@ -22,44 +22,38 @@ struct SplitPickerView: View {
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
VStack {
|
||||
Form {
|
||||
Section (header: Text("This Split")) {
|
||||
List {
|
||||
ForEach(splits) { split in
|
||||
Button(action: {
|
||||
onSplitSelected(split)
|
||||
dismiss()
|
||||
}) {
|
||||
HStack {
|
||||
Text(split.name)
|
||||
.font(.headline)
|
||||
Spacer()
|
||||
Text("\(split.exercises?.count ?? 0)")
|
||||
.font(.caption)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
ScrollView {
|
||||
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) {
|
||||
ForEach(splits) { split in
|
||||
Button(action: {
|
||||
onSplitSelected(split)
|
||||
dismiss()
|
||||
}) {
|
||||
VStack {
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(split.getColor())
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.shadow(radius: 2)
|
||||
|
||||
Image(systemName: split.systemImage)
|
||||
.font(.system(size: 30))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
|
||||
Text(split.name)
|
||||
.font(.headline)
|
||||
.lineLimit(1)
|
||||
|
||||
Text("\(split.exercises?.count ?? 0) exercises")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
|
||||
// Section (header: Text("Additional Exercises")) {
|
||||
// List {
|
||||
// ForEach(exercises) { exercise in
|
||||
// Button(action: {
|
||||
// onExerciseSelected(exercise)
|
||||
// dismiss()
|
||||
// }) {
|
||||
// Text(exercise.name)
|
||||
// }
|
||||
// .contentShape(Rectangle())
|
||||
// .buttonStyle(.plain)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
|
@ -8,6 +8,7 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
struct WorkoutLogView: View {
|
||||
@Environment(\.modelContext) private var modelContext
|
||||
@ -21,7 +22,7 @@ struct WorkoutLogView: View {
|
||||
var sortedWorkoutLogs: [WorkoutLog] {
|
||||
if let logs = workout.logs {
|
||||
logs.sorted(by: {
|
||||
$0.completed == $1.completed ? $0.exercise!.name < $1.exercise!.name : !$0.completed
|
||||
$0.order == $1.order ? $0.exercise!.name < $1.exercise!.name : $0.order < $1.order
|
||||
})
|
||||
} else {
|
||||
[]
|
||||
@ -33,33 +34,54 @@ struct WorkoutLogView: View {
|
||||
Section (header: Text("\(workout.label)")) {
|
||||
List {
|
||||
ForEach (sortedWorkoutLogs) { log in
|
||||
let badges = log.completed ? [Badge(text: "Completed", color: .green)] : []
|
||||
ListItem(
|
||||
title: log.exercise?.name ?? "Untitled Exercise",
|
||||
subtitle: "\(log.sets) sets × \(log.reps) reps × \(log.weight) lbs",
|
||||
badges: badges
|
||||
// Handle optional status, defaulting to a status based on completed flag if nil
|
||||
let _ = print("DEBUG: workoutLog.status=\(log.status)")
|
||||
|
||||
let workoutLogStatus = log.status?.checkboxStatus ?? (log.completed ? CheckboxStatus.checked : CheckboxStatus.unchecked)
|
||||
|
||||
CheckboxListItem(
|
||||
status: workoutLogStatus,
|
||||
title: log.exercise?.name ?? Exercise.unnamed,
|
||||
subtitle: "\(log.sets) sets × \(log.reps) reps × \(log.weight) lbs"
|
||||
)
|
||||
|
||||
.swipeActions(edge: .leading, allowsFullSwipe: false) {
|
||||
if (log.completed) {
|
||||
let status = log.status ?? WorkoutStatus.notStarted
|
||||
|
||||
if [.inProgress,.completed].contains(status) {
|
||||
Button {
|
||||
withAnimation {
|
||||
log.completed = false
|
||||
log.status = .notStarted
|
||||
try? modelContext.save()
|
||||
}
|
||||
} label: {
|
||||
Label("Complete", systemImage: "circle.fill")
|
||||
Label("Not Started", systemImage: WorkoutStatus.notStarted.checkboxStatus.systemName)
|
||||
}
|
||||
.tint(.green)
|
||||
} else {
|
||||
.tint(WorkoutStatus.notStarted.checkboxStatus.color)
|
||||
}
|
||||
|
||||
if [.notStarted,.completed].contains(status) {
|
||||
Button {
|
||||
withAnimation {
|
||||
log.completed = true
|
||||
log.status = .inProgress
|
||||
try? modelContext.save()
|
||||
}
|
||||
} label: {
|
||||
Label("Reset", systemImage: "checkmark.circle.fill")
|
||||
Label("In Progress", systemImage: WorkoutStatus.inProgress.checkboxStatus.systemName)
|
||||
}
|
||||
.tint(.green)
|
||||
.tint(WorkoutStatus.inProgress.checkboxStatus.color)
|
||||
}
|
||||
|
||||
if [.notStarted,.inProgress].contains(status) {
|
||||
Button {
|
||||
withAnimation {
|
||||
log.status = .completed
|
||||
try? modelContext.save()
|
||||
}
|
||||
} label: {
|
||||
Label("Complete", systemImage: WorkoutStatus.completed.checkboxStatus.systemName)
|
||||
}
|
||||
.tint(WorkoutStatus.completed.checkboxStatus.color)
|
||||
}
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
|
||||
@ -89,13 +111,14 @@ struct WorkoutLogView: View {
|
||||
}
|
||||
.sheet(isPresented: $showingAddSheet) {
|
||||
ExercisePickerView { exercise in
|
||||
let setsRepsWeight = getSetsRepsWeight(exercise, in: modelContext)
|
||||
let workoutLog = WorkoutLog(
|
||||
workout: workout,
|
||||
exercise: exercise,
|
||||
date: Date(),
|
||||
sets: exercise.sets,
|
||||
reps: exercise.reps,
|
||||
weight: exercise.weight,
|
||||
sets: setsRepsWeight.sets,
|
||||
reps: setsRepsWeight.reps,
|
||||
weight: setsRepsWeight.weight,
|
||||
completed: false
|
||||
)
|
||||
workout.logs?.append(workoutLog)
|
||||
@ -130,4 +153,34 @@ struct WorkoutLogView: View {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getSetsRepsWeight(_ exercise: Exercise, in modelContext: ModelContext) -> SetsRepsWeight {
|
||||
// Use a single expression predicate that works with SwiftData
|
||||
let exerciseID = exercise.persistentModelID
|
||||
|
||||
print("Searching for exercise ID: \(exerciseID)")
|
||||
|
||||
var descriptor = FetchDescriptor<WorkoutLog>(
|
||||
predicate: #Predicate<WorkoutLog> { log in
|
||||
log.exercise?.persistentModelID == exerciseID
|
||||
},
|
||||
sortBy: [SortDescriptor(\WorkoutLog.date, order: .reverse)]
|
||||
)
|
||||
|
||||
descriptor.fetchLimit = 1
|
||||
|
||||
let results = try? modelContext.fetch(descriptor)
|
||||
|
||||
if let log = results?.first {
|
||||
return SetsRepsWeight(sets: log.sets, reps: log.reps, weight: log.weight)
|
||||
} else {
|
||||
return SetsRepsWeight(sets: 3, reps: 10, weight: 40)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SetsRepsWeight {
|
||||
let sets: Int
|
||||
let reps: Int
|
||||
let weight: Int
|
||||
}
|
||||
|
@ -100,10 +100,10 @@ struct WorkoutsView: View {
|
||||
workout: workout,
|
||||
exercise: exercise,
|
||||
date: Date(),
|
||||
order: assignment.order,
|
||||
sets: assignment.sets,
|
||||
reps: assignment.reps,
|
||||
weight: assignment.weight,
|
||||
completed: false
|
||||
weight: assignment.weight
|
||||
)
|
||||
modelContext.insert(workoutLog)
|
||||
} else {
|
||||
|
Reference in New Issue
Block a user