wip
This commit is contained in:
@ -9,10 +9,17 @@
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
import CloudKit
|
||||
|
||||
enum AppStorageKeys {
|
||||
static let iCloudSyncEnabled = "iCloudSyncEnabled"
|
||||
}
|
||||
|
||||
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>()) }
|
||||
@ -70,10 +77,81 @@ struct SettingsView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}) {
|
||||
HStack {
|
||||
Label("Clear All Data", systemImage: "trash")
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.foregroundColor(.red)
|
||||
.confirmationDialog(
|
||||
"Clear All Data?",
|
||||
isPresented: $showingClearAllDataConfirmation,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button("Clear All Data", role: .destructive) {
|
||||
clearAllData()
|
||||
}
|
||||
Button("Cancel", role: .cancel) {}
|
||||
} message: {
|
||||
Text("This action cannot be undone. All data will be permanently deleted.")
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Settings")
|
||||
}
|
||||
}
|
||||
|
||||
func deleteAllObjects<T: PersistentModel>(ofType type: T.Type, from context: ModelContext) throws {
|
||||
let descriptor = FetchDescriptor<T>()
|
||||
let allObjects = try context.fetch(descriptor)
|
||||
for object in allObjects {
|
||||
context.delete(object)
|
||||
}
|
||||
try context.save()
|
||||
}
|
||||
|
||||
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)
|
||||
try deleteAllObjects(ofType: WorkoutLog.self, from: modelContext)
|
||||
try modelContext.save()
|
||||
} catch {
|
||||
print("Failed to clear all data: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ExercisesListView: View {
|
||||
|
83
Workouts/Views/Workouts/WorkoutEditView.swift
Normal file
83
Workouts/Views/Workouts/WorkoutEditView.swift
Normal file
@ -0,0 +1,83 @@
|
||||
//
|
||||
// WorkoutEditView.swift
|
||||
// Workouts
|
||||
//
|
||||
// Created by rzen on 7/14/25 at 7:35 AM.
|
||||
//
|
||||
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
struct WorkoutEditView: View {
|
||||
@Environment(\.modelContext) private var modelContext
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@State var workout: Workout
|
||||
@State var endDateHidden: Bool = false
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
Section (header: Text("Split")) {
|
||||
Text("\(workout.split?.name ?? Split.unnamed)")
|
||||
}
|
||||
|
||||
Section (header: Text("Start/End")) {
|
||||
DatePicker("Started", selection: $workout.start)
|
||||
Toggle("Workout Ended", isOn: Binding(
|
||||
get: { workout.end != nil },
|
||||
set: { newValue in
|
||||
withAnimation {
|
||||
if newValue {
|
||||
workout.end = Date()
|
||||
endDateHidden = false
|
||||
} else {
|
||||
workout.end = nil
|
||||
endDateHidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
if !endDateHidden {
|
||||
DatePicker("Ended", selection: $workout.start)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
endDateHidden = workout.end == nil
|
||||
}
|
||||
|
||||
Section (header: Text("Workout Log")) {
|
||||
if let workoutLogs = workout.logs {
|
||||
List {
|
||||
ForEach (workoutLogs) { log in
|
||||
ListItem(
|
||||
title: log.exercise?.name ?? Exercise.unnamed,
|
||||
subtitle: "\(log.sets) sets × \(log.reps) reps × \(log.weight) lbs"
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text("No workout logs yet")
|
||||
}
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Cancel") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Save") {
|
||||
try? modelContext.save()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,64 +18,66 @@ struct WorkoutLogEditView: View {
|
||||
@State private var showingSaveConfirmation = false
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section (header: Text("Exercise")) {
|
||||
Text("\(workoutLog.exercise?.name ?? "Unnamed Exercise")")
|
||||
.font(.headline)
|
||||
}
|
||||
|
||||
Section(header: Text("Sets/Reps")) {
|
||||
Stepper("Sets: \(workoutLog.sets)", value: $workoutLog.sets, in: 1...10)
|
||||
Stepper("Reps: \(workoutLog.reps)", value: $workoutLog.reps, in: 1...50)
|
||||
}
|
||||
|
||||
Section(header: Text("Weight")) {
|
||||
HStack {
|
||||
VStack(alignment: .center) {
|
||||
Text("\(workoutLog.weight) lbs")
|
||||
.font(.headline)
|
||||
NavigationStack {
|
||||
Form {
|
||||
Section (header: Text("Exercise")) {
|
||||
Text("\(workoutLog.exercise?.name ?? Exercise.unnamed)")
|
||||
.font(.headline)
|
||||
}
|
||||
|
||||
Section(header: Text("Sets/Reps")) {
|
||||
Stepper("Sets: \(workoutLog.sets)", value: $workoutLog.sets, in: 1...10)
|
||||
Stepper("Reps: \(workoutLog.reps)", value: $workoutLog.reps, in: 1...50)
|
||||
}
|
||||
|
||||
Section(header: Text("Weight")) {
|
||||
HStack {
|
||||
VStack(alignment: .center) {
|
||||
Text("\(workoutLog.weight) lbs")
|
||||
.font(.headline)
|
||||
}
|
||||
Spacer()
|
||||
VStack(alignment: .trailing) {
|
||||
Stepper("±1", value: $workoutLog.weight, in: 0...1000)
|
||||
Stepper("±5", value: $workoutLog.weight, in: 0...1000, step: 5)
|
||||
}
|
||||
.frame(width: 130)
|
||||
}
|
||||
Spacer()
|
||||
VStack(alignment: .trailing) {
|
||||
Stepper("±1", value: $workoutLog.weight, in: 0...1000)
|
||||
Stepper("±5", value: $workoutLog.weight, in: 0...1000, step: 5)
|
||||
}
|
||||
.frame(width: 130)
|
||||
}
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Cancel") {
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Cancel") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Save") {
|
||||
showingSaveConfirmation = true
|
||||
}
|
||||
}
|
||||
}
|
||||
.confirmationDialog("Save Options", isPresented: $showingSaveConfirmation) {
|
||||
Button("Save Workout Log Only") {
|
||||
try? modelContext.save()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Save") {
|
||||
showingSaveConfirmation = true
|
||||
|
||||
Button("Save Workout Log and Update Split") {
|
||||
// Save the workout log
|
||||
try? modelContext.save()
|
||||
|
||||
// Update the split with this workout log's data
|
||||
// Note: Implementation depends on how splits are updated in your app
|
||||
updateSplit(from: workoutLog)
|
||||
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
.confirmationDialog("Save Options", isPresented: $showingSaveConfirmation) {
|
||||
Button("Save Workout Log Only") {
|
||||
try? modelContext.save()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
Button("Save Workout Log and Update Split") {
|
||||
// Save the workout log
|
||||
try? modelContext.save()
|
||||
|
||||
// Update the split with this workout log's data
|
||||
// Note: Implementation depends on how splits are updated in your app
|
||||
updateSplit(from: workoutLog)
|
||||
|
||||
dismiss()
|
||||
}
|
||||
|
||||
Button("Cancel", role: .cancel) {
|
||||
// Do nothing, dialog will dismiss
|
||||
Button("Cancel", role: .cancel) {
|
||||
// Do nothing, dialog will dismiss
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,50 +28,55 @@ struct WorkoutLogView: View {
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
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
|
||||
)
|
||||
.swipeActions(edge: .leading, allowsFullSwipe: false) {
|
||||
if (log.completed) {
|
||||
Button {
|
||||
log.completed = false
|
||||
try? modelContext.save()
|
||||
} label: {
|
||||
Label("Complete", systemImage: "circle.fill")
|
||||
Section {
|
||||
Text("Started \(workout.label)")
|
||||
}
|
||||
Section {
|
||||
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
|
||||
)
|
||||
.swipeActions(edge: .leading, allowsFullSwipe: false) {
|
||||
if (log.completed) {
|
||||
Button {
|
||||
log.completed = false
|
||||
try? modelContext.save()
|
||||
} label: {
|
||||
Label("Complete", systemImage: "circle.fill")
|
||||
}
|
||||
.tint(.green)
|
||||
} else {
|
||||
Button {
|
||||
log.completed = true
|
||||
try? modelContext.save()
|
||||
} label: {
|
||||
Label("Reset", systemImage: "checkmark.circle.fill")
|
||||
}
|
||||
.tint(.green)
|
||||
}
|
||||
.tint(.green)
|
||||
} else {
|
||||
Button {
|
||||
log.completed = true
|
||||
try? modelContext.save()
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
|
||||
Button(role: .destructive) {
|
||||
itemToDelete = log
|
||||
} label: {
|
||||
Label("Reset", systemImage: "checkmark.circle.fill")
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
.tint(.green)
|
||||
Button {
|
||||
itemToEdit = log
|
||||
} label: {
|
||||
Label("Edit", systemImage: "pencil")
|
||||
}
|
||||
.tint(.indigo)
|
||||
}
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
|
||||
Button(role: .destructive) {
|
||||
itemToDelete = log
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
Button {
|
||||
itemToEdit = log
|
||||
} label: {
|
||||
Label("Edit", systemImage: "pencil")
|
||||
}
|
||||
.tint(.indigo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Workout")
|
||||
.navigationTitle("\(workout.split?.name ?? Split.unnamed)")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button(action: { showingAddSheet.toggle() }) {
|
||||
|
@ -34,7 +34,10 @@ struct WorkoutsView: View {
|
||||
List {
|
||||
ForEach (workouts) { workout in
|
||||
NavigationLink(destination: WorkoutLogView(workout: workout)) {
|
||||
ListItem(title: workout.label)
|
||||
ListItem(
|
||||
title: workout.split?.name ?? Split.unnamed,
|
||||
subtitle: workout.label
|
||||
)
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
|
||||
Button(role: .destructive) {
|
||||
@ -61,9 +64,9 @@ struct WorkoutsView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
// .sheet(item: $itemToEdit) { item in
|
||||
// T.formView(for: item)
|
||||
// }
|
||||
.sheet(item: $itemToEdit) { item in
|
||||
WorkoutEditView(workout: item)
|
||||
}
|
||||
.confirmationDialog(
|
||||
"Delete?",
|
||||
isPresented: Binding<Bool>(
|
||||
|
Reference in New Issue
Block a user