wip
This commit is contained in:
@ -9,6 +9,9 @@
|
|||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
A45FA1FE2E27171B00581607 /* Worksouts Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = A45FA1F12E27171A00581607 /* Worksouts Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
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 */; };
|
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 */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -71,7 +74,10 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
A45FA2822E2A933B00581607 /* SwiftUIReorderableForEach in Frameworks */,
|
||||||
A45FA2732E29B12500581607 /* Yams in Frameworks */,
|
A45FA2732E29B12500581607 /* Yams in Frameworks */,
|
||||||
|
A45FA27F2E2A930900581607 /* SwiftUIReorderableForEach in Frameworks */,
|
||||||
|
A45FA2872E2AC66B00581607 /* SwiftUIReorderableForEach in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -126,6 +132,9 @@
|
|||||||
name = Workouts;
|
name = Workouts;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
A45FA2722E29B12500581607 /* Yams */,
|
A45FA2722E29B12500581607 /* Yams */,
|
||||||
|
A45FA27E2E2A930900581607 /* SwiftUIReorderableForEach */,
|
||||||
|
A45FA2812E2A933B00581607 /* SwiftUIReorderableForEach */,
|
||||||
|
A45FA2862E2AC66B00581607 /* SwiftUIReorderableForEach */,
|
||||||
);
|
);
|
||||||
productName = Workouts;
|
productName = Workouts;
|
||||||
productReference = A45FA0912E21B3DD00581607 /* Workouts.app */;
|
productReference = A45FA0912E21B3DD00581607 /* Workouts.app */;
|
||||||
@ -182,6 +191,7 @@
|
|||||||
minimizedProjectReferenceProxies = 1;
|
minimizedProjectReferenceProxies = 1;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
A45FA26B2E297F5B00581607 /* XCRemoteSwiftPackageReference "Yams" */,
|
A45FA26B2E297F5B00581607 /* XCRemoteSwiftPackageReference "Yams" */,
|
||||||
|
A45FA2852E2AC66B00581607 /* XCLocalSwiftPackageReference "../swiftui-reorderable-foreach" */,
|
||||||
);
|
);
|
||||||
preferredProjectObjectVersion = 77;
|
preferredProjectObjectVersion = 77;
|
||||||
productRefGroup = A45FA0922E21B3DD00581607 /* Products */;
|
productRefGroup = A45FA0922E21B3DD00581607 /* Products */;
|
||||||
@ -512,6 +522,13 @@
|
|||||||
};
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
|
/* Begin XCLocalSwiftPackageReference section */
|
||||||
|
A45FA2852E2AC66B00581607 /* XCLocalSwiftPackageReference "../swiftui-reorderable-foreach" */ = {
|
||||||
|
isa = XCLocalSwiftPackageReference;
|
||||||
|
relativePath = "../swiftui-reorderable-foreach";
|
||||||
|
};
|
||||||
|
/* End XCLocalSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference section */
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
A45FA26B2E297F5B00581607 /* XCRemoteSwiftPackageReference "Yams" */ = {
|
A45FA26B2E297F5B00581607 /* XCRemoteSwiftPackageReference "Yams" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
@ -529,6 +546,18 @@
|
|||||||
package = A45FA26B2E297F5B00581607 /* XCRemoteSwiftPackageReference "Yams" */;
|
package = A45FA26B2E297F5B00581607 /* XCRemoteSwiftPackageReference "Yams" */;
|
||||||
productName = Yams;
|
productName = Yams;
|
||||||
};
|
};
|
||||||
|
A45FA27E2E2A930900581607 /* SwiftUIReorderableForEach */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = SwiftUIReorderableForEach;
|
||||||
|
};
|
||||||
|
A45FA2812E2A933B00581607 /* SwiftUIReorderableForEach */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = SwiftUIReorderableForEach;
|
||||||
|
};
|
||||||
|
A45FA2862E2AC66B00581607 /* SwiftUIReorderableForEach */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = SwiftUIReorderableForEach;
|
||||||
|
};
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
rootObject = A45FA0892E21B3DC00581607 /* Project object */;
|
rootObject = A45FA0892E21B3DC00581607 /* Project object */;
|
||||||
|
@ -15,33 +15,31 @@ struct ContentView: View {
|
|||||||
@Environment(\.modelContext) private var modelContext
|
@Environment(\.modelContext) private var modelContext
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
TabView {
|
||||||
TabView {
|
SplitsView()
|
||||||
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")
|
|
||||||
}
|
|
||||||
.tabItem {
|
.tabItem {
|
||||||
Label("Reports", systemImage: "chart.bar")
|
Label("Workouts", systemImage: "figure.strengthtraining.traditional")
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsView()
|
WorkoutsView()
|
||||||
.tabItem {
|
.tabItem {
|
||||||
Label("Settings", systemImage: "gear")
|
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()
|
.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 Foundation
|
||||||
import SwiftData
|
import SwiftData
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
@Model
|
@Model
|
||||||
final class Split {
|
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
|
// MARK: - Private Form View
|
||||||
|
|
||||||
fileprivate struct SplitFormView: 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 SwiftUI
|
||||||
|
import SwiftData
|
||||||
|
|
||||||
struct SplitExercisesListView: View {
|
struct SplitExercisesListView: View {
|
||||||
@Environment(\.modelContext) private var modelContext
|
@Environment(\.modelContext) private var modelContext
|
||||||
@ -18,6 +19,7 @@ struct SplitExercisesListView: View {
|
|||||||
@State private var showingAddSheet: Bool = false
|
@State private var showingAddSheet: Bool = false
|
||||||
@State private var itemToEdit: SplitExerciseAssignment? = nil
|
@State private var itemToEdit: SplitExerciseAssignment? = nil
|
||||||
@State private var itemToDelete: SplitExerciseAssignment? = nil
|
@State private var itemToDelete: SplitExerciseAssignment? = nil
|
||||||
|
@State private var createdWorkout: Workout? = nil
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
@ -67,24 +69,59 @@ struct SplitExercisesListView: View {
|
|||||||
.navigationTitle("\(model.name)")
|
.navigationTitle("\(model.name)")
|
||||||
}
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .primaryAction) {
|
||||||
Button(action: { showingAddSheet.toggle() }) {
|
Button("Start This Split") {
|
||||||
Image(systemName: "plus")
|
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) {
|
.navigationDestination(item: $createdWorkout, destination: { workout in
|
||||||
ExercisePickerView { exerciseName in
|
WorkoutLogView(workout: workout)
|
||||||
itemToEdit = SplitExerciseAssignment(
|
})
|
||||||
split: model,
|
// .sheet(item: $createdWorkout) { workout in
|
||||||
exerciseName: exerciseName,
|
// NavigationStack {
|
||||||
order: 0,
|
// WorkoutLogView(workout: workout)
|
||||||
sets: 3,
|
// }
|
||||||
reps: 10,
|
// }
|
||||||
weight: 40
|
// .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
|
.sheet(item: $itemToEdit) { item in
|
||||||
SplitExerciseAssignmentAddEditView(model: item)
|
SplitExerciseAssignmentAddEditView(model: item)
|
||||||
}
|
}
|
||||||
|
@ -14,83 +14,49 @@ struct SplitsView: View {
|
|||||||
@Environment(\.modelContext) private var modelContext
|
@Environment(\.modelContext) private var modelContext
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
@Query(sort: [
|
@State var splits: [Split] = []
|
||||||
SortDescriptor(\Split.order),
|
|
||||||
SortDescriptor(\Split.name)
|
|
||||||
]) private var splits: [Split]
|
|
||||||
|
|
||||||
@State private var showingAddSheet: Bool = false
|
@State private var showingAddSheet: Bool = false
|
||||||
|
@State private var allowSorting: Bool = true
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) {
|
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) {
|
||||||
ForEach(splits) { split in
|
SortableForEach($splits, allowReordering: $allowSorting) { split, dragging in
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
SplitExercisesListView(model: split)
|
SplitExercisesListView(model: split)
|
||||||
} label: {
|
} label: {
|
||||||
VStack {
|
DraggableSplitItem(
|
||||||
ZStack(alignment: .bottom) {
|
name: split.name,
|
||||||
// Golden ratio rectangle (1:1.618)
|
color: Color.color(from: split.color),
|
||||||
RoundedRectangle(cornerRadius: 12)
|
systemImageName: split.systemImage,
|
||||||
.fill(
|
exerciseCount: split.exercises?.count ?? 0
|
||||||
LinearGradient(
|
)
|
||||||
gradient: Gradient(colors: [split.getColor(), split.getColor().darker(by: 0.2)]),
|
.overlay(dragging ? Color.white.opacity(0.8) : Color.clear)
|
||||||
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()
|
.padding()
|
||||||
}
|
}
|
||||||
.navigationTitle("Splits")
|
.navigationTitle("Splits")
|
||||||
}
|
.onAppear {
|
||||||
.toolbar {
|
do {
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
self.splits = try modelContext.fetch(FetchDescriptor<Split>(
|
||||||
Button(action: { showingAddSheet.toggle() }) {
|
sortBy: [
|
||||||
Image(systemName: "plus")
|
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")
|
.navigationTitle("Workouts")
|
||||||
.toolbar {
|
// .toolbar {
|
||||||
ToolbarItem(placement: .primaryAction) {
|
// ToolbarItem(placement: .primaryAction) {
|
||||||
Button("Start Workout") {
|
// Button("Start Workout") {
|
||||||
showingSplitPicker = true
|
// showingSplitPicker = true
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
.sheet(item: $itemToEdit) { item in
|
// .sheet(item: $itemToEdit) { item in
|
||||||
WorkoutEditView(workout: item)
|
// WorkoutEditView(workout: item)
|
||||||
}
|
// }
|
||||||
.confirmationDialog(
|
.confirmationDialog(
|
||||||
"Delete?",
|
"Delete?",
|
||||||
isPresented: Binding<Bool>(
|
isPresented: Binding<Bool>(
|
||||||
|
Reference in New Issue
Block a user