wip
This commit is contained in:
		@@ -9,6 +9,9 @@
 | 
			
		||||
/* Begin PBXBuildFile section */
 | 
			
		||||
		A45FA1FE2E27171B00581607 /* Worksouts Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = A45FA1F12E27171A00581607 /* Worksouts Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
 | 
			
		||||
		A45FA2732E29B12500581607 /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = A45FA2722E29B12500581607 /* Yams */; };
 | 
			
		||||
		A45FA27F2E2A930900581607 /* SwiftUIReorderableForEach in Frameworks */ = {isa = PBXBuildFile; productRef = A45FA27E2E2A930900581607 /* SwiftUIReorderableForEach */; };
 | 
			
		||||
		A45FA2822E2A933B00581607 /* SwiftUIReorderableForEach in Frameworks */ = {isa = PBXBuildFile; productRef = A45FA2812E2A933B00581607 /* SwiftUIReorderableForEach */; };
 | 
			
		||||
		A45FA2872E2AC66B00581607 /* SwiftUIReorderableForEach in Frameworks */ = {isa = PBXBuildFile; productRef = A45FA2862E2AC66B00581607 /* SwiftUIReorderableForEach */; };
 | 
			
		||||
/* End PBXBuildFile section */
 | 
			
		||||
 | 
			
		||||
/* Begin PBXContainerItemProxy section */
 | 
			
		||||
@@ -71,7 +74,10 @@
 | 
			
		||||
			isa = PBXFrameworksBuildPhase;
 | 
			
		||||
			buildActionMask = 2147483647;
 | 
			
		||||
			files = (
 | 
			
		||||
				A45FA2822E2A933B00581607 /* SwiftUIReorderableForEach in Frameworks */,
 | 
			
		||||
				A45FA2732E29B12500581607 /* Yams in Frameworks */,
 | 
			
		||||
				A45FA27F2E2A930900581607 /* SwiftUIReorderableForEach in Frameworks */,
 | 
			
		||||
				A45FA2872E2AC66B00581607 /* SwiftUIReorderableForEach in Frameworks */,
 | 
			
		||||
			);
 | 
			
		||||
			runOnlyForDeploymentPostprocessing = 0;
 | 
			
		||||
		};
 | 
			
		||||
@@ -126,6 +132,9 @@
 | 
			
		||||
			name = Workouts;
 | 
			
		||||
			packageProductDependencies = (
 | 
			
		||||
				A45FA2722E29B12500581607 /* Yams */,
 | 
			
		||||
				A45FA27E2E2A930900581607 /* SwiftUIReorderableForEach */,
 | 
			
		||||
				A45FA2812E2A933B00581607 /* SwiftUIReorderableForEach */,
 | 
			
		||||
				A45FA2862E2AC66B00581607 /* SwiftUIReorderableForEach */,
 | 
			
		||||
			);
 | 
			
		||||
			productName = Workouts;
 | 
			
		||||
			productReference = A45FA0912E21B3DD00581607 /* Workouts.app */;
 | 
			
		||||
@@ -182,6 +191,7 @@
 | 
			
		||||
			minimizedProjectReferenceProxies = 1;
 | 
			
		||||
			packageReferences = (
 | 
			
		||||
				A45FA26B2E297F5B00581607 /* XCRemoteSwiftPackageReference "Yams" */,
 | 
			
		||||
				A45FA2852E2AC66B00581607 /* XCLocalSwiftPackageReference "../swiftui-reorderable-foreach" */,
 | 
			
		||||
			);
 | 
			
		||||
			preferredProjectObjectVersion = 77;
 | 
			
		||||
			productRefGroup = A45FA0922E21B3DD00581607 /* Products */;
 | 
			
		||||
@@ -512,6 +522,13 @@
 | 
			
		||||
		};
 | 
			
		||||
/* End XCConfigurationList section */
 | 
			
		||||
 | 
			
		||||
/* Begin XCLocalSwiftPackageReference section */
 | 
			
		||||
		A45FA2852E2AC66B00581607 /* XCLocalSwiftPackageReference "../swiftui-reorderable-foreach" */ = {
 | 
			
		||||
			isa = XCLocalSwiftPackageReference;
 | 
			
		||||
			relativePath = "../swiftui-reorderable-foreach";
 | 
			
		||||
		};
 | 
			
		||||
/* End XCLocalSwiftPackageReference section */
 | 
			
		||||
 | 
			
		||||
/* Begin XCRemoteSwiftPackageReference section */
 | 
			
		||||
		A45FA26B2E297F5B00581607 /* XCRemoteSwiftPackageReference "Yams" */ = {
 | 
			
		||||
			isa = XCRemoteSwiftPackageReference;
 | 
			
		||||
@@ -529,6 +546,18 @@
 | 
			
		||||
			package = A45FA26B2E297F5B00581607 /* XCRemoteSwiftPackageReference "Yams" */;
 | 
			
		||||
			productName = Yams;
 | 
			
		||||
		};
 | 
			
		||||
		A45FA27E2E2A930900581607 /* SwiftUIReorderableForEach */ = {
 | 
			
		||||
			isa = XCSwiftPackageProductDependency;
 | 
			
		||||
			productName = SwiftUIReorderableForEach;
 | 
			
		||||
		};
 | 
			
		||||
		A45FA2812E2A933B00581607 /* SwiftUIReorderableForEach */ = {
 | 
			
		||||
			isa = XCSwiftPackageProductDependency;
 | 
			
		||||
			productName = SwiftUIReorderableForEach;
 | 
			
		||||
		};
 | 
			
		||||
		A45FA2862E2AC66B00581607 /* SwiftUIReorderableForEach */ = {
 | 
			
		||||
			isa = XCSwiftPackageProductDependency;
 | 
			
		||||
			productName = SwiftUIReorderableForEach;
 | 
			
		||||
		};
 | 
			
		||||
/* End XCSwiftPackageProductDependency section */
 | 
			
		||||
	};
 | 
			
		||||
	rootObject = A45FA0892E21B3DC00581607 /* Project object */;
 | 
			
		||||
 
 | 
			
		||||
@@ -15,33 +15,31 @@ struct ContentView: View {
 | 
			
		||||
    @Environment(\.modelContext) private var modelContext
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        NavigationView {
 | 
			
		||||
            TabView {
 | 
			
		||||
                SplitsView()
 | 
			
		||||
                    .tabItem {
 | 
			
		||||
                        Label("Workouts", systemImage: "figure.strengthtraining.traditional")
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                WorkoutsView()
 | 
			
		||||
                    .tabItem {
 | 
			
		||||
                        Label("Logs", systemImage: "list.bullet.clipboard.fill")
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                
 | 
			
		||||
                // Reports Tab
 | 
			
		||||
                NavigationStack {
 | 
			
		||||
                    Text("Reports Placeholder")
 | 
			
		||||
                        .navigationTitle("Reports")
 | 
			
		||||
                }
 | 
			
		||||
        TabView {
 | 
			
		||||
            SplitsView()
 | 
			
		||||
                .tabItem {
 | 
			
		||||
                    Label("Reports", systemImage: "chart.bar")
 | 
			
		||||
                    Label("Workouts", systemImage: "figure.strengthtraining.traditional")
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                SettingsView()
 | 
			
		||||
                    .tabItem {
 | 
			
		||||
                        Label("Settings", systemImage: "gear")
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
            WorkoutsView()
 | 
			
		||||
                .tabItem {
 | 
			
		||||
                    Label("Logs", systemImage: "list.bullet.clipboard.fill")
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            
 | 
			
		||||
            // Reports Tab
 | 
			
		||||
            NavigationStack {
 | 
			
		||||
                Text("Reports Placeholder")
 | 
			
		||||
                    .navigationTitle("Reports")
 | 
			
		||||
            }
 | 
			
		||||
            .tabItem {
 | 
			
		||||
                Label("Reports", systemImage: "chart.bar")
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            SettingsView()
 | 
			
		||||
                .tabItem {
 | 
			
		||||
                    Label("Settings", systemImage: "gear")
 | 
			
		||||
                }
 | 
			
		||||
        }
 | 
			
		||||
        .observeCloudKitChanges()
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										30
									
								
								Workouts/Models/OrderableItem.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								Workouts/Models/OrderableItem.swift
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
//
 | 
			
		||||
// OrderableItem.swift
 | 
			
		||||
// Workouts
 | 
			
		||||
//
 | 
			
		||||
// Created by rzen on 7/18/25 at 5:19 PM.
 | 
			
		||||
//
 | 
			
		||||
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import Foundation
 | 
			
		||||
 | 
			
		||||
/// Protocol for items that can be ordered in a sequence
 | 
			
		||||
protocol OrderableItem {
 | 
			
		||||
    /// Updates the order of the item to the specified index
 | 
			
		||||
    func updateOrder(to index: Int)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Extension to make Split conform to OrderableItem
 | 
			
		||||
extension Split: OrderableItem {
 | 
			
		||||
    func updateOrder(to index: Int) {
 | 
			
		||||
        self.order = index
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Extension to make SplitExerciseAssignment conform to OrderableItem
 | 
			
		||||
extension SplitExerciseAssignment: OrderableItem {
 | 
			
		||||
    func updateOrder(to index: Int) {
 | 
			
		||||
        self.order = index
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import Foundation
 | 
			
		||||
import SwiftData
 | 
			
		||||
import SwiftUI
 | 
			
		||||
import UniformTypeIdentifiers
 | 
			
		||||
 | 
			
		||||
@Model
 | 
			
		||||
final class Split {
 | 
			
		||||
@@ -53,6 +54,16 @@ extension Split: EditableEntity {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MARK: - Identifiable Conformance
 | 
			
		||||
 | 
			
		||||
extension Split: Identifiable {
 | 
			
		||||
    public var id: String {
 | 
			
		||||
        // Use the name as a unique identifier for the split
 | 
			
		||||
        // This is sufficient for UI purposes
 | 
			
		||||
        return self.name
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MARK: - Private Form View
 | 
			
		||||
 | 
			
		||||
fileprivate struct SplitFormView: View {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										56
									
								
								Workouts/Views/Splits/DraggableSplitItem.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								Workouts/Views/Splits/DraggableSplitItem.swift
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
//
 | 
			
		||||
// DraggableSplitItem.swift
 | 
			
		||||
// Workouts
 | 
			
		||||
//
 | 
			
		||||
// Created by rzen on 7/18/25 at 2:45 PM.
 | 
			
		||||
//
 | 
			
		||||
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct DraggableSplitItem: View {
 | 
			
		||||
    
 | 
			
		||||
    var name: String
 | 
			
		||||
    var color: Color
 | 
			
		||||
    var systemImageName: String
 | 
			
		||||
    var exerciseCount: Int
 | 
			
		||||
    
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        VStack {
 | 
			
		||||
            ZStack(alignment: .bottom) {
 | 
			
		||||
                // Golden ratio rectangle (1:1.618)
 | 
			
		||||
                RoundedRectangle(cornerRadius: 12)
 | 
			
		||||
                    .fill(
 | 
			
		||||
                        LinearGradient(
 | 
			
		||||
                            gradient: Gradient(colors: [color, color.darker(by: 0.2)]),
 | 
			
		||||
                            startPoint: .topLeading,
 | 
			
		||||
                            endPoint: .bottomTrailing
 | 
			
		||||
                        )
 | 
			
		||||
                    )
 | 
			
		||||
                    .aspectRatio(1.618, contentMode: .fit)
 | 
			
		||||
                    .shadow(radius: 2)
 | 
			
		||||
                
 | 
			
		||||
                VStack {
 | 
			
		||||
                    // Icon in the center
 | 
			
		||||
                    Image(systemName: systemImageName)
 | 
			
		||||
                        .font(.system(size: 40, weight: .bold))
 | 
			
		||||
                        .offset(y: -15)
 | 
			
		||||
                    
 | 
			
		||||
                    // Name at the bottom inside the rectangle
 | 
			
		||||
                    Text(name)
 | 
			
		||||
                        .font(.headline)
 | 
			
		||||
                        .lineLimit(1)
 | 
			
		||||
                        .padding(.horizontal, 8)
 | 
			
		||||
 | 
			
		||||
                    Text("\(exerciseCount) exercises")
 | 
			
		||||
                        .font(.caption)
 | 
			
		||||
                        .padding(.bottom, 8)
 | 
			
		||||
                }
 | 
			
		||||
                .foregroundColor(.white)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										99
									
								
								Workouts/Views/Splits/SortableForEach.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								Workouts/Views/Splits/SortableForEach.swift
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,99 @@
 | 
			
		||||
//
 | 
			
		||||
// SortableForEach.swift
 | 
			
		||||
// Workouts
 | 
			
		||||
//
 | 
			
		||||
// Created by rzen on 7/18/25 at 2:04 PM.
 | 
			
		||||
//
 | 
			
		||||
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import SwiftUI
 | 
			
		||||
import UniformTypeIdentifiers
 | 
			
		||||
 | 
			
		||||
public struct SortableForEach<Data, Content>: View where Data: Hashable, Content: View {
 | 
			
		||||
    @Binding var data: [Data]
 | 
			
		||||
    @Binding var allowReordering: Bool
 | 
			
		||||
    private let content: (Data, Bool) -> Content
 | 
			
		||||
    
 | 
			
		||||
    @State private var draggedItem: Data?
 | 
			
		||||
    @State private var hasChangedLocation: Bool = false
 | 
			
		||||
    
 | 
			
		||||
    public init(_ data: Binding<[Data]>,
 | 
			
		||||
                allowReordering: Binding<Bool>,
 | 
			
		||||
                @ViewBuilder content: @escaping (Data, Bool) -> Content) {
 | 
			
		||||
        _data = data
 | 
			
		||||
        _allowReordering = allowReordering
 | 
			
		||||
        self.content = content
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public var body: some View {
 | 
			
		||||
        ForEach(data, id: \.self) { item in
 | 
			
		||||
            if allowReordering {
 | 
			
		||||
                content(item, hasChangedLocation && draggedItem == item)
 | 
			
		||||
                    .onDrag {
 | 
			
		||||
                        draggedItem = item
 | 
			
		||||
                        return NSItemProvider(object: "\(item.hashValue)" as NSString)
 | 
			
		||||
                    }
 | 
			
		||||
                    .onDrop(of: [UTType.plainText], delegate: DragRelocateDelegate(
 | 
			
		||||
                        item: item,
 | 
			
		||||
                        data: $data,
 | 
			
		||||
                        draggedItem: $draggedItem,
 | 
			
		||||
                        hasChangedLocation: $hasChangedLocation))
 | 
			
		||||
            } else {
 | 
			
		||||
                content(item, false)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    struct DragRelocateDelegate<ItemType>: DropDelegate where ItemType : Equatable {
 | 
			
		||||
        let item: ItemType
 | 
			
		||||
        @Binding var data: [ItemType]
 | 
			
		||||
        @Binding var draggedItem: ItemType?
 | 
			
		||||
        @Binding var hasChangedLocation: Bool
 | 
			
		||||
        
 | 
			
		||||
        func dropEntered(info: DropInfo) {
 | 
			
		||||
            guard item != draggedItem,
 | 
			
		||||
                  let current = draggedItem,
 | 
			
		||||
                  let from = data.firstIndex(of: current),
 | 
			
		||||
                  let to = data.firstIndex(of: item)
 | 
			
		||||
            else {
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            hasChangedLocation = true
 | 
			
		||||
            
 | 
			
		||||
            if data[to] != current {
 | 
			
		||||
                withAnimation {
 | 
			
		||||
                    data.move(
 | 
			
		||||
                        fromOffsets: IndexSet(integer: from),
 | 
			
		||||
                        toOffset: (to > from) ? to + 1 : to
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        func dropUpdated(info: DropInfo) -> DropProposal? {
 | 
			
		||||
            DropProposal(operation: .move)
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        func performDrop(info: DropInfo) -> Bool {
 | 
			
		||||
            // Update the order property of each item to match its position in the array
 | 
			
		||||
            updateItemOrders()
 | 
			
		||||
            
 | 
			
		||||
            hasChangedLocation = false
 | 
			
		||||
            draggedItem = nil
 | 
			
		||||
            return true
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Helper method to update the order property of each item
 | 
			
		||||
        private func updateItemOrders() {
 | 
			
		||||
            // Only update orders if we're working with items that have an 'order' property
 | 
			
		||||
            for (index, item) in data.enumerated() {
 | 
			
		||||
                // Use key path and dynamic member lookup to set the order if available
 | 
			
		||||
                if let orderableItem = item as? any OrderableItem {
 | 
			
		||||
                    orderableItem.updateOrder(to: index)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -8,6 +8,7 @@
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import SwiftUI
 | 
			
		||||
import SwiftData
 | 
			
		||||
 | 
			
		||||
struct SplitExercisesListView: View {
 | 
			
		||||
    @Environment(\.modelContext) private var modelContext
 | 
			
		||||
@@ -18,6 +19,7 @@ struct SplitExercisesListView: View {
 | 
			
		||||
    @State private var showingAddSheet: Bool = false
 | 
			
		||||
    @State private var itemToEdit: SplitExerciseAssignment? = nil
 | 
			
		||||
    @State private var itemToDelete: SplitExerciseAssignment? = nil
 | 
			
		||||
    @State private var createdWorkout: Workout? = nil
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        NavigationStack {
 | 
			
		||||
@@ -67,24 +69,59 @@ struct SplitExercisesListView: View {
 | 
			
		||||
            .navigationTitle("\(model.name)")
 | 
			
		||||
        }
 | 
			
		||||
        .toolbar {
 | 
			
		||||
            ToolbarItem(placement: .navigationBarTrailing) {
 | 
			
		||||
                Button(action: { showingAddSheet.toggle() }) {
 | 
			
		||||
                    Image(systemName: "plus")
 | 
			
		||||
            ToolbarItem(placement: .primaryAction) {
 | 
			
		||||
                Button("Start This Split") {
 | 
			
		||||
                    let split = model
 | 
			
		||||
                    let workout = Workout(start: Date(), split: split)
 | 
			
		||||
                    modelContext.insert(workout)
 | 
			
		||||
                    if let exercises = split.exercises {
 | 
			
		||||
                        for assignment in exercises {
 | 
			
		||||
                            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()
 | 
			
		||||
                    
 | 
			
		||||
                    // Set the created workout to trigger navigation
 | 
			
		||||
                    createdWorkout = workout
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        .sheet (isPresented: $showingAddSheet) {
 | 
			
		||||
            ExercisePickerView { exerciseName in
 | 
			
		||||
                itemToEdit = SplitExerciseAssignment(
 | 
			
		||||
                    split: model,
 | 
			
		||||
                    exerciseName: exerciseName,
 | 
			
		||||
                    order: 0,
 | 
			
		||||
                    sets: 3,
 | 
			
		||||
                    reps: 10,
 | 
			
		||||
                    weight: 40
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        .navigationDestination(item: $createdWorkout, destination: { workout in
 | 
			
		||||
            WorkoutLogView(workout: workout)
 | 
			
		||||
        })
 | 
			
		||||
//        .sheet(item: $createdWorkout) { workout in
 | 
			
		||||
//            NavigationStack {
 | 
			
		||||
//                WorkoutLogView(workout: workout)
 | 
			
		||||
//            }
 | 
			
		||||
//        }
 | 
			
		||||
//        .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)
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -14,83 +14,49 @@ 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 var splits: [Split] = []
 | 
			
		||||
    
 | 
			
		||||
    @State private var showingAddSheet: Bool = false
 | 
			
		||||
    @State private var allowSorting: Bool = true
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        NavigationStack {
 | 
			
		||||
            ScrollView {
 | 
			
		||||
                LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) {
 | 
			
		||||
                    ForEach(splits) { split in
 | 
			
		||||
                    SortableForEach($splits, allowReordering: $allowSorting) { split, dragging 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)
 | 
			
		||||
                            }
 | 
			
		||||
                            DraggableSplitItem(
 | 
			
		||||
                                name: split.name,
 | 
			
		||||
                                color: Color.color(from: split.color),
 | 
			
		||||
                                systemImageName: split.systemImage,
 | 
			
		||||
                                exerciseCount: split.exercises?.count ?? 0
 | 
			
		||||
                            )
 | 
			
		||||
                            .overlay(dragging ? Color.white.opacity(0.8) : Color.clear)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    .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")
 | 
			
		||||
            .onAppear {
 | 
			
		||||
                do {
 | 
			
		||||
                    self.splits = try modelContext.fetch(FetchDescriptor<Split>(
 | 
			
		||||
                        sortBy: [
 | 
			
		||||
                            SortDescriptor(\Split.order),
 | 
			
		||||
                            SortDescriptor(\Split.name)
 | 
			
		||||
                        ]
 | 
			
		||||
                    ))
 | 
			
		||||
                } catch {
 | 
			
		||||
                    print("ERROR: failed to load splits \(error)")
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .toolbar {
 | 
			
		||||
                ToolbarItem(placement: .navigationBarTrailing) {
 | 
			
		||||
                    Button(action: { showingAddSheet.toggle() }) {
 | 
			
		||||
                        Image(systemName: "plus")
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -59,16 +59,16 @@ struct WorkoutsView: View {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .navigationTitle("Workouts")
 | 
			
		||||
            .toolbar {
 | 
			
		||||
                ToolbarItem(placement: .primaryAction) {
 | 
			
		||||
                    Button("Start Workout") {
 | 
			
		||||
                        showingSplitPicker = true
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .sheet(item: $itemToEdit) { item in
 | 
			
		||||
                WorkoutEditView(workout: item)
 | 
			
		||||
            }
 | 
			
		||||
//            .toolbar {
 | 
			
		||||
//                ToolbarItem(placement: .primaryAction) {
 | 
			
		||||
//                    Button("Start Workout") {
 | 
			
		||||
//                        showingSplitPicker = true
 | 
			
		||||
//                    }
 | 
			
		||||
//                }
 | 
			
		||||
//            }
 | 
			
		||||
//            .sheet(item: $itemToEdit) { item in
 | 
			
		||||
//                WorkoutEditView(workout: item)
 | 
			
		||||
//            }
 | 
			
		||||
            .confirmationDialog(
 | 
			
		||||
                "Delete?",
 | 
			
		||||
                isPresented: Binding<Bool>(
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user