wip
This commit is contained in:
@ -18,14 +18,9 @@ enum AppStorageKeys {
|
||||
struct SettingsView: View {
|
||||
@Environment(\.modelContext) private var modelContext
|
||||
|
||||
@State private var showingPopulateData = false
|
||||
@State private var showingClearAllDataConfirmation = false
|
||||
|
||||
var splitsCount: Int? { try? modelContext.fetchCount(FetchDescriptor<Split>()) }
|
||||
var musclesCount: Int? { try? modelContext.fetchCount(FetchDescriptor<Muscle>()) }
|
||||
var muscleGroupsCount: Int? { try? modelContext.fetchCount(FetchDescriptor<MuscleGroup>()) }
|
||||
var exerciseTypeCount: Int? { try? modelContext.fetchCount(FetchDescriptor<ExerciseType>()) }
|
||||
var exercisesCount: Int? { try? modelContext.fetchCount(FetchDescriptor<Exercise>()) }
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
@ -40,67 +35,9 @@ struct SettingsView: View {
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
NavigationLink(destination: MuscleGroupsListView()) {
|
||||
HStack {
|
||||
Text("Muscle Groups")
|
||||
Spacer()
|
||||
Text("\(muscleGroupsCount ?? 0)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
NavigationLink(destination: MusclesListView()) {
|
||||
HStack {
|
||||
Text("Muscles")
|
||||
Spacer()
|
||||
Text("\(musclesCount ?? 0)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
NavigationLink(destination: ExerciseTypeListView()) {
|
||||
HStack {
|
||||
Text("Exercise Types")
|
||||
Spacer()
|
||||
Text("\(exerciseTypeCount ?? 0)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
NavigationLink(destination: ExercisesListView()) {
|
||||
HStack {
|
||||
Text("Exercises")
|
||||
Spacer()
|
||||
Text("\(exercisesCount ?? 0)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section(header: Text("Developer")) {
|
||||
|
||||
Button(action: {
|
||||
showingPopulateData = true
|
||||
}) {
|
||||
HStack {
|
||||
Label("Populate Data", systemImage: "plus")
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.confirmationDialog(
|
||||
"Populate Data?",
|
||||
isPresented: $showingPopulateData,
|
||||
titleVisibility: .hidden
|
||||
) {
|
||||
Button("Populate Data") {
|
||||
DataLoader.create(modelContext: modelContext)
|
||||
}
|
||||
Button("Cancel", role: .cancel) {}
|
||||
// } message: {
|
||||
// Text("This action cannot be undone. All data will be permanently deleted.")
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
showingClearAllDataConfirmation = true
|
||||
}) {
|
||||
@ -139,10 +76,6 @@ struct SettingsView: View {
|
||||
|
||||
private func clearAllData () {
|
||||
do {
|
||||
try deleteAllObjects(ofType: ExerciseType.self, from: modelContext)
|
||||
try deleteAllObjects(ofType: Exercise.self, from: modelContext)
|
||||
try deleteAllObjects(ofType: Muscle.self, from: modelContext)
|
||||
try deleteAllObjects(ofType: MuscleGroup.self, from: modelContext)
|
||||
try deleteAllObjects(ofType: Split.self, from: modelContext)
|
||||
try deleteAllObjects(ofType: SplitExerciseAssignment.self, from: modelContext)
|
||||
try deleteAllObjects(ofType: Workout.self, from: modelContext)
|
||||
@ -154,30 +87,6 @@ struct SettingsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct ExercisesListView: View {
|
||||
var body: some View {
|
||||
EntityListView<Exercise>(sort: [SortDescriptor(\Exercise.name)])
|
||||
}
|
||||
}
|
||||
|
||||
struct ExerciseTypeListView: View {
|
||||
var body: some View {
|
||||
EntityListView<ExerciseType>(sort: [SortDescriptor(\ExerciseType.name)])
|
||||
}
|
||||
}
|
||||
|
||||
struct MuscleGroupsListView: View {
|
||||
var body: some View {
|
||||
EntityListView<MuscleGroup>(sort: [SortDescriptor(\MuscleGroup.name)])
|
||||
}
|
||||
}
|
||||
|
||||
struct MusclesListView: View {
|
||||
var body: some View {
|
||||
EntityListView<Muscle>(sort: [SortDescriptor(\Muscle.name)])
|
||||
}
|
||||
}
|
||||
|
||||
struct SplitsListView: View {
|
||||
var body: some View {
|
||||
EntityListView<Split>(sort: [SortDescriptor(\Split.name)])
|
||||
|
@ -12,15 +12,24 @@ import SwiftUI
|
||||
struct SplitExerciseAssignmentAddEditView: View {
|
||||
@Environment(\.modelContext) private var modelContext
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State private var showingExercisePicker = false
|
||||
|
||||
@State var model: SplitExerciseAssignment
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
Section (header: Text("Setup")) {
|
||||
TextEditor(text: $model.setup)
|
||||
.frame(minHeight: 60)
|
||||
Section(header: Text("Exercise")) {
|
||||
Button(action: {
|
||||
showingExercisePicker = true
|
||||
}) {
|
||||
HStack {
|
||||
Text(model.exerciseName.isEmpty ? "Select Exercise" : model.exerciseName)
|
||||
Spacer()
|
||||
Image(systemName: "chevron.right")
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section (header: Text("Sets/Reps")) {
|
||||
@ -44,7 +53,12 @@ struct SplitExerciseAssignmentAddEditView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("\(model.exercise?.name ?? Exercise.unnamed)")
|
||||
.sheet(isPresented: $showingExercisePicker) {
|
||||
ExercisePickerView { exerciseName in
|
||||
model.exerciseName = exerciseName
|
||||
}
|
||||
}
|
||||
.navigationTitle(model.exerciseName.isEmpty ? "New Exercise" : model.exerciseName)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Cancel") {
|
||||
|
64
Workouts/Views/Splits/SplitAddEditView.swift
Normal file
64
Workouts/Views/Splits/SplitAddEditView.swift
Normal file
@ -0,0 +1,64 @@
|
||||
//
|
||||
// SplitAddEditView.swift
|
||||
// Workouts
|
||||
//
|
||||
// Created by rzen on 7/18/25 at 9:42 AM.
|
||||
//
|
||||
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SplitAddEditView: View {
|
||||
@State var model: Split
|
||||
|
||||
private let availableColors = ["red", "orange", "yellow", "green", "mint", "teal", "cyan", "blue", "indigo", "purple", "pink", "brown"]
|
||||
|
||||
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 {
|
||||
Form {
|
||||
Section(header: Text("Name")) {
|
||||
TextField("Name", text: $model.name)
|
||||
.bold()
|
||||
}
|
||||
|
||||
Section(header: Text("Appearance")) {
|
||||
Picker("Color", selection: $model.color) {
|
||||
ForEach(availableColors, id: \.self) { colorName in
|
||||
let tempSplit = Split(name: "", 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 {
|
||||
SplitExercisesListView(model: model)
|
||||
} label: {
|
||||
ListItem(
|
||||
text: "Exercises",
|
||||
count: model.exercises?.count ?? 0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
108
Workouts/Views/Splits/SplitExercisesListView.swift
Normal file
108
Workouts/Views/Splits/SplitExercisesListView.swift
Normal file
@ -0,0 +1,108 @@
|
||||
//
|
||||
// SplitExercisesListView.swift
|
||||
// Workouts
|
||||
//
|
||||
// Created by rzen on 7/18/25 at 8:38 AM.
|
||||
//
|
||||
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SplitExercisesListView: View {
|
||||
@Environment(\.modelContext) private var modelContext
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
var model: Split
|
||||
|
||||
@State private var showingAddSheet: Bool = false
|
||||
@State private var itemToEdit: SplitExerciseAssignment? = nil
|
||||
@State private var itemToDelete: SplitExerciseAssignment? = nil
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
List {
|
||||
if let assignments = model.exercises, !assignments.isEmpty {
|
||||
let sortedAssignments = assignments.sorted(by: { $0.order == $1.order ? $0.exerciseName < $1.exerciseName : $0.order < $1.order })
|
||||
|
||||
ForEach(sortedAssignments) { item in
|
||||
ListItem(
|
||||
title: item.exerciseName,
|
||||
subtitle: "\(item.sets) × \(item.reps) × \(item.weight) lbs \(item.order)"
|
||||
)
|
||||
.swipeActions {
|
||||
Button {
|
||||
itemToDelete = item
|
||||
} label: {
|
||||
Label("Delete", systemImage: "circle")
|
||||
}
|
||||
Button {
|
||||
itemToEdit = item
|
||||
} label: {
|
||||
Label("Edit", systemImage: "pencil")
|
||||
}
|
||||
.tint(.indigo)
|
||||
}
|
||||
}
|
||||
.onMove(perform: { indices, destination in
|
||||
var exerciseArray = Array(sortedAssignments)
|
||||
exerciseArray.move(fromOffsets: indices, toOffset: destination)
|
||||
for (index, exercise) in exerciseArray.enumerated() {
|
||||
exercise.order = index
|
||||
}
|
||||
if let modelContext = exerciseArray.first?.modelContext {
|
||||
do {
|
||||
try modelContext.save()
|
||||
} catch {
|
||||
print("Error saving after reordering: \(error)")
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Text("No exercises added yet.")
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("\(model.name)")
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button(action: { showingAddSheet.toggle() }) {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
.sheet (isPresented: $showingAddSheet) {
|
||||
ExercisePickerView { exerciseName in
|
||||
itemToEdit = SplitExerciseAssignment(
|
||||
split: model,
|
||||
exerciseName: exerciseName,
|
||||
order: 0,
|
||||
sets: 3,
|
||||
reps: 10,
|
||||
weight: 40
|
||||
)
|
||||
}
|
||||
}
|
||||
.sheet(item: $itemToEdit) { item in
|
||||
SplitExerciseAssignmentAddEditView(model: item)
|
||||
}
|
||||
.confirmationDialog(
|
||||
"Delete Exercise?",
|
||||
isPresented: .constant(itemToDelete != nil),
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Delete", role: .destructive) {
|
||||
if let item = itemToDelete {
|
||||
withAnimation {
|
||||
modelContext.delete(item)
|
||||
try? modelContext.save()
|
||||
itemToDelete = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
102
Workouts/Views/Splits/SplitsView.swift
Normal file
102
Workouts/Views/Splits/SplitsView.swift
Normal file
@ -0,0 +1,102 @@
|
||||
//
|
||||
// SplitsView.swift
|
||||
// Workouts
|
||||
//
|
||||
// Created by rzen on 7/17/25 at 6:55 PM.
|
||||
//
|
||||
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
struct SplitsView: View {
|
||||
@Environment(\.modelContext) private var modelContext
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@Query(sort: [
|
||||
SortDescriptor(\Split.order),
|
||||
SortDescriptor(\Split.name)
|
||||
]) private var splits: [Split]
|
||||
|
||||
@State private var showingAddSheet: Bool = false
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ScrollView {
|
||||
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) {
|
||||
ForEach(splits) { split in
|
||||
NavigationLink {
|
||||
SplitExercisesListView(model: split)
|
||||
} label: {
|
||||
VStack {
|
||||
ZStack(alignment: .bottom) {
|
||||
// Golden ratio rectangle (1:1.618)
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(
|
||||
LinearGradient(
|
||||
gradient: Gradient(colors: [split.getColor(), split.getColor().darker(by: 0.2)]),
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
)
|
||||
.aspectRatio(1.618, contentMode: .fit)
|
||||
.shadow(radius: 2)
|
||||
|
||||
VStack {
|
||||
// Icon in the center
|
||||
Image(systemName: split.systemImage)
|
||||
.font(.system(size: 40, weight: .medium))
|
||||
.foregroundColor(.white)
|
||||
.offset(y: -15)
|
||||
|
||||
// Name at the bottom inside the rectangle
|
||||
Text(split.name)
|
||||
.font(.headline)
|
||||
.foregroundColor(.white)
|
||||
.lineLimit(1)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.bottom, 8)
|
||||
}
|
||||
}
|
||||
|
||||
// Exercise count below the rectangle
|
||||
Text("\(split.exercises?.count ?? 0) exercises")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onMove(perform: { indices, destination in
|
||||
var splitArray = Array(splits)
|
||||
splitArray.move(fromOffsets: indices, toOffset: destination)
|
||||
for (index, split) in splitArray.enumerated() {
|
||||
split.order = index
|
||||
}
|
||||
if let modelContext = splitArray.first?.modelContext {
|
||||
do {
|
||||
try modelContext.save()
|
||||
} catch {
|
||||
print("Error saving after reordering: \(error)")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.navigationTitle("Splits")
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button(action: { showingAddSheet.toggle() }) {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
}
|
||||
}
|
||||
.sheet (isPresented: $showingAddSheet) {
|
||||
SplitAddEditView(model: Split(name: "New Split"))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
89
Workouts/Views/Workouts/CalendarListItem.swift
Normal file
89
Workouts/Views/Workouts/CalendarListItem.swift
Normal file
@ -0,0 +1,89 @@
|
||||
//
|
||||
// CalendarListItem.swift
|
||||
// Workouts
|
||||
//
|
||||
// Created by rzen on 7/18/25 at 8:44 AM.
|
||||
//
|
||||
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct CalendarListItem: View {
|
||||
var date: Date
|
||||
var title: String
|
||||
var subtitle: String?
|
||||
var count: Int?
|
||||
|
||||
var body: some View {
|
||||
HStack (alignment: .top) {
|
||||
ZStack {
|
||||
VStack {
|
||||
Text("\(date.abbreviatedWeekday)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
Text("\(date.dayOfMonth)")
|
||||
.font(.headline)
|
||||
.foregroundColor(.accentColor)
|
||||
Text("\(date.abbreviatedMonth)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding([.trailing], 10)
|
||||
}
|
||||
HStack {
|
||||
VStack (alignment: .leading) {
|
||||
Text("\(title)")
|
||||
.font(.headline)
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
extension Date {
|
||||
private static let monthFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.locale = Locale.current
|
||||
formatter.dateFormat = "MMM"
|
||||
return formatter
|
||||
}()
|
||||
|
||||
private static let dayFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.locale = Locale.current
|
||||
formatter.dateFormat = "d"
|
||||
return formatter
|
||||
}()
|
||||
|
||||
private static let weekdayFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.locale = Locale.current
|
||||
formatter.dateFormat = "E"
|
||||
return formatter
|
||||
}()
|
||||
|
||||
var abbreviatedMonth: String {
|
||||
Date.monthFormatter.string(from: self)
|
||||
}
|
||||
|
||||
var dayOfMonth: String {
|
||||
Date.dayFormatter.string(from: self)
|
||||
}
|
||||
|
||||
var abbreviatedWeekday: String {
|
||||
Date.weekdayFormatter.string(from: self)
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
//
|
||||
// SplitPickerView.swift
|
||||
// ExercisePickerView.swift
|
||||
// Workouts
|
||||
//
|
||||
// Created by rzen on 7/13/25 at 7:17 PM.
|
||||
// Created by rzen on 7/13/25 at 7:17 PM.
|
||||
//
|
||||
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
||||
//
|
||||
@ -11,37 +11,65 @@ import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
struct ExercisePickerView: View {
|
||||
@Environment(\.modelContext) private var modelContext
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@Query(sort: [SortDescriptor(\ExerciseType.name)]) private var exerciseTypes: [ExerciseType]
|
||||
@State private var exerciseLists: [String: ExerciseList] = [:]
|
||||
@State private var selectedListName: String? = nil
|
||||
|
||||
// @Query(sort: [SortDescriptor(\Exercise.name)]) private var exercises: [Exercise]
|
||||
|
||||
var onExerciseSelected: (Exercise) -> Void
|
||||
var onExerciseSelected: (String) -> Void
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
VStack {
|
||||
Form {
|
||||
ForEach (exerciseTypes) { exerciseType in
|
||||
if let exercises = exerciseType.exercises, !exercises.isEmpty {
|
||||
let sortedExercises = exercises.sorted(by: { $0.name < $1.name })
|
||||
Section (header: Text("\(exerciseType.name)")) {
|
||||
List {
|
||||
ForEach(sortedExercises) { exercise in
|
||||
Button(action: {
|
||||
onExerciseSelected(exercise)
|
||||
dismiss()
|
||||
}) {
|
||||
ListItem(text: exercise.name)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
Group {
|
||||
if selectedListName == nil {
|
||||
// Show list of exercise list files
|
||||
List {
|
||||
ForEach(Array(exerciseLists.keys.sorted()), id: \.self) { fileName in
|
||||
if let list = exerciseLists[fileName] {
|
||||
Button(action: {
|
||||
selectedListName = fileName
|
||||
}) {
|
||||
VStack(alignment: .leading) {
|
||||
Text(list.name)
|
||||
.font(.headline)
|
||||
Text(list.source)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
Text("\(list.exercises.count) exercises")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.navigationTitle("Exercise Lists")
|
||||
} else if let fileName = selectedListName, let list = exerciseLists[fileName] {
|
||||
// Show exercises in the selected list
|
||||
List {
|
||||
ForEach(list.exercises) { exercise in
|
||||
Button(action: {
|
||||
onExerciseSelected(exercise.name)
|
||||
dismiss()
|
||||
}) {
|
||||
VStack(alignment: .leading) {
|
||||
Text(exercise.name)
|
||||
.font(.headline)
|
||||
Text(exercise.type)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding(.vertical, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(list.name)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Back") {
|
||||
selectedListName = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -53,5 +81,12 @@ struct ExercisePickerView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
loadExerciseLists()
|
||||
}
|
||||
}
|
||||
|
||||
private func loadExerciseLists() {
|
||||
exerciseLists = ExerciseListLoader.loadExerciseLists()
|
||||
}
|
||||
}
|
||||
|
@ -15,10 +15,8 @@ struct SplitPickerView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@Query(sort: [SortDescriptor(\Split.name)]) private var splits: [Split]
|
||||
@Query(sort: [SortDescriptor(\Exercise.name)]) private var exercises: [Exercise]
|
||||
|
||||
var onSplitSelected: (Split) -> Void
|
||||
// var onExerciseSelected: (Exercise) -> Void
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
|
@ -49,7 +49,7 @@ struct WorkoutEditView: View {
|
||||
// List {
|
||||
// ForEach (workoutLogs) { log in
|
||||
// ListItem(
|
||||
// title: log.exercise?.name ?? Exercise.unnamed,
|
||||
// title: log.exerciseName,
|
||||
// subtitle: "\(log.sets) sets × \(log.reps) reps × \(log.weight) lbs"
|
||||
// )
|
||||
// }
|
||||
|
@ -21,7 +21,7 @@ struct WorkoutLogEditView: View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
Section (header: Text("Exercise")) {
|
||||
Text("\(workoutLog.exercise?.name ?? Exercise.unnamed)")
|
||||
Text(workoutLog.exerciseName)
|
||||
.font(.headline)
|
||||
}
|
||||
|
||||
@ -86,20 +86,20 @@ struct WorkoutLogEditView: View {
|
||||
let split = workoutLog.workout?.split
|
||||
|
||||
// Find the matching exercise in split.exercises by name
|
||||
// if let exercises = split?.exercises {
|
||||
// for exerciseAssignment in exercises {
|
||||
// if exerciseAssignment.exercise.name == workoutLog.exercise.name {
|
||||
// // Update the sets, reps, and weight in the split exercise assignment
|
||||
// exerciseAssignment.sets = workoutLog.sets
|
||||
// exerciseAssignment.reps = workoutLog.reps
|
||||
// exerciseAssignment.weight = workoutLog.weight
|
||||
//
|
||||
// // Save the changes to the split
|
||||
// try? modelContext.save()
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
if let exercises = split?.exercises {
|
||||
for exerciseAssignment in exercises {
|
||||
if exerciseAssignment.exerciseName == workoutLog.exerciseName {
|
||||
// Update the sets, reps, and weight in the split exercise assignment
|
||||
exerciseAssignment.sets = workoutLog.sets
|
||||
exerciseAssignment.reps = workoutLog.reps
|
||||
exerciseAssignment.weight = workoutLog.weight
|
||||
|
||||
// Save the changes to the split
|
||||
try? modelContext.save()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ struct WorkoutLogView: View {
|
||||
var sortedWorkoutLogs: [WorkoutLog] {
|
||||
if let logs = workout.logs {
|
||||
logs.sorted(by: {
|
||||
$0.order == $1.order ? $0.exercise!.name < $1.exercise!.name : $0.order < $1.order
|
||||
$0.order == $1.order ? $0.exerciseName < $1.exerciseName : $0.order < $1.order
|
||||
})
|
||||
} else {
|
||||
[]
|
||||
@ -41,7 +41,7 @@ struct WorkoutLogView: View {
|
||||
|
||||
CheckboxListItem(
|
||||
status: workoutLogStatus,
|
||||
title: log.exercise?.name ?? Exercise.unnamed,
|
||||
title: log.exerciseName,
|
||||
subtitle: "\(log.sets) sets × \(log.reps) reps × \(log.weight) lbs"
|
||||
)
|
||||
|
||||
@ -85,11 +85,12 @@ struct WorkoutLogView: View {
|
||||
}
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
|
||||
Button(role: .destructive) {
|
||||
Button {
|
||||
itemToDelete = log
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
.tint(.secondary)
|
||||
Button {
|
||||
itemToEdit = log
|
||||
} label: {
|
||||
@ -110,11 +111,11 @@ struct WorkoutLogView: View {
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showingAddSheet) {
|
||||
ExercisePickerView { exercise in
|
||||
let setsRepsWeight = getSetsRepsWeight(exercise, in: modelContext)
|
||||
ExercisePickerView { exerciseName in
|
||||
let setsRepsWeight = getSetsRepsWeight(exerciseName, in: modelContext)
|
||||
let workoutLog = WorkoutLog(
|
||||
workout: workout,
|
||||
exercise: exercise,
|
||||
exerciseName: exerciseName,
|
||||
date: Date(),
|
||||
sets: setsRepsWeight.sets,
|
||||
reps: setsRepsWeight.reps,
|
||||
@ -149,20 +150,18 @@ struct WorkoutLogView: View {
|
||||
itemToDelete = nil
|
||||
}
|
||||
} message: {
|
||||
Text("Are you sure you want to delete workout started \(itemToDelete?.exercise?.name ?? "this item")?")
|
||||
Text("Are you sure you want to delete workout started \(itemToDelete?.exerciseName ?? "this item")?")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getSetsRepsWeight(_ exercise: Exercise, in modelContext: ModelContext) -> SetsRepsWeight {
|
||||
func getSetsRepsWeight(_ exerciseName: String, in modelContext: ModelContext) -> SetsRepsWeight {
|
||||
// Use a single expression predicate that works with SwiftData
|
||||
let exerciseID = exercise.persistentModelID
|
||||
|
||||
print("Searching for exercise ID: \(exerciseID)")
|
||||
print("Searching for exercise name: \(exerciseName)")
|
||||
|
||||
var descriptor = FetchDescriptor<WorkoutLog>(
|
||||
predicate: #Predicate<WorkoutLog> { log in
|
||||
log.exercise?.persistentModelID == exerciseID
|
||||
log.exerciseName == exerciseName
|
||||
},
|
||||
sortBy: [SortDescriptor(\WorkoutLog.date, order: .reverse)]
|
||||
)
|
||||
|
@ -34,7 +34,8 @@ struct WorkoutsView: View {
|
||||
List {
|
||||
ForEach (workouts) { workout in
|
||||
NavigationLink(destination: WorkoutLogView(workout: workout)) {
|
||||
ListItem(
|
||||
CalendarListItem(
|
||||
date: workout.start,
|
||||
title: workout.split?.name ?? Split.unnamed,
|
||||
subtitle: workout.label
|
||||
)
|
||||
@ -95,23 +96,18 @@ struct WorkoutsView: View {
|
||||
SplitPickerView { split in
|
||||
let workout = Workout(start: Date(), split: split)
|
||||
modelContext.insert(workout)
|
||||
|
||||
if let exercises = split.exercises {
|
||||
for assignment in exercises {
|
||||
if let exercise = assignment.exercise {
|
||||
let workoutLog = WorkoutLog(
|
||||
workout: workout,
|
||||
exercise: exercise,
|
||||
date: Date(),
|
||||
order: assignment.order,
|
||||
sets: assignment.sets,
|
||||
reps: assignment.reps,
|
||||
weight: assignment.weight
|
||||
)
|
||||
modelContext.insert(workoutLog)
|
||||
} else {
|
||||
logger.debug("An exercise entity for a split is nil")
|
||||
}
|
||||
let workoutLog = WorkoutLog(
|
||||
workout: workout,
|
||||
exerciseName: assignment.exerciseName,
|
||||
date: Date(),
|
||||
order: assignment.order,
|
||||
sets: assignment.sets,
|
||||
reps: assignment.reps,
|
||||
weight: assignment.weight
|
||||
)
|
||||
modelContext.insert(workoutLog)
|
||||
}
|
||||
}
|
||||
try? modelContext.save()
|
||||
|
Reference in New Issue
Block a user