wip
This commit is contained in:
2
UI.md
2
UI.md
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
Each tab is a root of its own navigation stack.
|
Each tab is a root of its own navigation stack.
|
||||||
|
|
||||||
- Workout Log
|
- Workouts
|
||||||
- Reports
|
- Reports
|
||||||
- Settings
|
- Settings
|
||||||
|
|
||||||
|
@ -6,8 +6,37 @@
|
|||||||
objectVersion = 77;
|
objectVersion = 77;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
A45FA1FE2E27171B00581607 /* Worksouts Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = A45FA1F12E27171A00581607 /* Worksouts Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
A45FA1FC2E27171B00581607 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = A45FA0892E21B3DC00581607 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = A45FA1F02E27171A00581607;
|
||||||
|
remoteInfo = "Worksouts Watch App";
|
||||||
|
};
|
||||||
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
A45FA2022E27171B00581607 /* Embed Watch Content */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "$(CONTENTS_FOLDER_PATH)/Watch";
|
||||||
|
dstSubfolderSpec = 16;
|
||||||
|
files = (
|
||||||
|
A45FA1FE2E27171B00581607 /* Worksouts Watch App.app in Embed Watch Content */,
|
||||||
|
);
|
||||||
|
name = "Embed Watch Content";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
A45FA0912E21B3DD00581607 /* Workouts.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Workouts.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
A45FA0912E21B3DD00581607 /* Workouts.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Workouts.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
A45FA1F12E27171A00581607 /* Worksouts Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Worksouts Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
@ -29,6 +58,11 @@
|
|||||||
path = Workouts;
|
path = Workouts;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
A45FA1F22E27171A00581607 /* Worksouts Watch App */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
path = "Worksouts Watch App";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -39,6 +73,13 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
A45FA1EE2E27171A00581607 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
@ -46,6 +87,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
A45FA0932E21B3DD00581607 /* Workouts */,
|
A45FA0932E21B3DD00581607 /* Workouts */,
|
||||||
|
A45FA1F22E27171A00581607 /* Worksouts Watch App */,
|
||||||
A45FA0922E21B3DD00581607 /* Products */,
|
A45FA0922E21B3DD00581607 /* Products */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -54,6 +96,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
A45FA0912E21B3DD00581607 /* Workouts.app */,
|
A45FA0912E21B3DD00581607 /* Workouts.app */,
|
||||||
|
A45FA1F12E27171A00581607 /* Worksouts Watch App.app */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -68,10 +111,12 @@
|
|||||||
A45FA08D2E21B3DD00581607 /* Sources */,
|
A45FA08D2E21B3DD00581607 /* Sources */,
|
||||||
A45FA08E2E21B3DD00581607 /* Frameworks */,
|
A45FA08E2E21B3DD00581607 /* Frameworks */,
|
||||||
A45FA08F2E21B3DD00581607 /* Resources */,
|
A45FA08F2E21B3DD00581607 /* Resources */,
|
||||||
|
A45FA2022E27171B00581607 /* Embed Watch Content */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
|
A45FA1FD2E27171B00581607 /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
fileSystemSynchronizedGroups = (
|
fileSystemSynchronizedGroups = (
|
||||||
A45FA0932E21B3DD00581607 /* Workouts */,
|
A45FA0932E21B3DD00581607 /* Workouts */,
|
||||||
@ -83,6 +128,28 @@
|
|||||||
productReference = A45FA0912E21B3DD00581607 /* Workouts.app */;
|
productReference = A45FA0912E21B3DD00581607 /* Workouts.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
};
|
};
|
||||||
|
A45FA1F02E27171A00581607 /* Worksouts Watch App */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = A45FA1FF2E27171B00581607 /* Build configuration list for PBXNativeTarget "Worksouts Watch App" */;
|
||||||
|
buildPhases = (
|
||||||
|
A45FA1ED2E27171A00581607 /* Sources */,
|
||||||
|
A45FA1EE2E27171A00581607 /* Frameworks */,
|
||||||
|
A45FA1EF2E27171A00581607 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
fileSystemSynchronizedGroups = (
|
||||||
|
A45FA1F22E27171A00581607 /* Worksouts Watch App */,
|
||||||
|
);
|
||||||
|
name = "Worksouts Watch App";
|
||||||
|
packageProductDependencies = (
|
||||||
|
);
|
||||||
|
productName = "Worksouts Watch App";
|
||||||
|
productReference = A45FA1F12E27171A00581607 /* Worksouts Watch App.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
/* End PBXNativeTarget section */
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
/* Begin PBXProject section */
|
/* Begin PBXProject section */
|
||||||
@ -96,6 +163,9 @@
|
|||||||
A45FA0902E21B3DD00581607 = {
|
A45FA0902E21B3DD00581607 = {
|
||||||
CreatedOnToolsVersion = 16.2;
|
CreatedOnToolsVersion = 16.2;
|
||||||
};
|
};
|
||||||
|
A45FA1F02E27171A00581607 = {
|
||||||
|
CreatedOnToolsVersion = 16.2;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
buildConfigurationList = A45FA08C2E21B3DC00581607 /* Build configuration list for PBXProject "Workouts" */;
|
buildConfigurationList = A45FA08C2E21B3DC00581607 /* Build configuration list for PBXProject "Workouts" */;
|
||||||
@ -113,6 +183,7 @@
|
|||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
A45FA0902E21B3DD00581607 /* Workouts */,
|
A45FA0902E21B3DD00581607 /* Workouts */,
|
||||||
|
A45FA1F02E27171A00581607 /* Worksouts Watch App */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
@ -125,6 +196,13 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
A45FA1EF2E27171A00581607 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
@ -135,8 +213,23 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
A45FA1ED2E27171A00581607 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
A45FA1FD2E27171B00581607 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = A45FA1F02E27171A00581607 /* Worksouts Watch App */;
|
||||||
|
targetProxy = A45FA1FC2E27171B00581607 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
A45FA0A32E21B3DE00581607 /* Debug */ = {
|
A45FA0A32E21B3DE00581607 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
@ -319,6 +412,68 @@
|
|||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
A45FA2002E27171B00581607 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = "Worksouts Watch App/Worksouts Watch App.entitlements";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_ASSET_PATHS = "\"Worksouts Watch App/Preview Content\"";
|
||||||
|
DEVELOPMENT_TEAM = C32Z8JNLG6;
|
||||||
|
ENABLE_PREVIEWS = YES;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = Worksouts;
|
||||||
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
|
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = dev.rzen.indie.Workouts;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = dev.rzen.indie.Workouts.watchkitapp;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SDKROOT = watchos;
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = 4;
|
||||||
|
WATCHOS_DEPLOYMENT_TARGET = 11.2;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
A45FA2012E27171B00581607 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = "Worksouts Watch App/Worksouts Watch App.entitlements";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_ASSET_PATHS = "\"Worksouts Watch App/Preview Content\"";
|
||||||
|
DEVELOPMENT_TEAM = C32Z8JNLG6;
|
||||||
|
ENABLE_PREVIEWS = YES;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = Worksouts;
|
||||||
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
|
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = dev.rzen.indie.Workouts;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = dev.rzen.indie.Workouts.watchkitapp;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SDKROOT = watchos;
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = 4;
|
||||||
|
WATCHOS_DEPLOYMENT_TARGET = 11.2;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
/* End XCBuildConfiguration section */
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
/* Begin XCConfigurationList section */
|
||||||
@ -340,6 +495,15 @@
|
|||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
|
A45FA1FF2E27171B00581607 /* Build configuration list for PBXNativeTarget "Worksouts Watch App" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
A45FA2002E27171B00581607 /* Debug */,
|
||||||
|
A45FA2012E27171B00581607 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
};
|
};
|
||||||
rootObject = A45FA0892E21B3DC00581607 /* Project object */;
|
rootObject = A45FA0892E21B3DC00581607 /* Project object */;
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Bucket
|
||||||
|
uuid = "EEDBFE9D-0066-4E41-BFBC-8B95DBCF47E3"
|
||||||
|
type = "1"
|
||||||
|
version = "2.0">
|
||||||
|
</Bucket>
|
@ -9,6 +9,11 @@
|
|||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>0</integer>
|
<integer>0</integer>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>Worksouts Watch App.xcscheme_^#shared#^_</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -5,11 +5,7 @@ import SwiftUI
|
|||||||
@Model
|
@Model
|
||||||
final class Exercise {
|
final class Exercise {
|
||||||
var name: String = ""
|
var name: String = ""
|
||||||
var setup: String = ""
|
|
||||||
var descr: String = ""
|
var descr: String = ""
|
||||||
var sets: Int = 0
|
|
||||||
var reps: Int = 0
|
|
||||||
var weight: Int = 0
|
|
||||||
|
|
||||||
@Relationship(deleteRule: .nullify, inverse: \ExerciseType.exercises)
|
@Relationship(deleteRule: .nullify, inverse: \ExerciseType.exercises)
|
||||||
var type: ExerciseType?
|
var type: ExerciseType?
|
||||||
@ -23,13 +19,9 @@ final class Exercise {
|
|||||||
@Relationship(deleteRule: .nullify, inverse: \WorkoutLog.exercise)
|
@Relationship(deleteRule: .nullify, inverse: \WorkoutLog.exercise)
|
||||||
var logs: [WorkoutLog]? = []
|
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.name = name
|
||||||
self.setup = setup
|
|
||||||
self.descr = descr
|
self.descr = descr
|
||||||
self.sets = sets
|
|
||||||
self.reps = reps
|
|
||||||
self.weight = weight
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static let unnamed = "Unnamed Exercise"
|
static let unnamed = "Unnamed Exercise"
|
||||||
@ -37,7 +29,7 @@ final class Exercise {
|
|||||||
|
|
||||||
extension Exercise: EditableEntity {
|
extension Exercise: EditableEntity {
|
||||||
static func createNew() -> Exercise {
|
static func createNew() -> Exercise {
|
||||||
return Exercise(name: "", setup: "", descr: "", sets: 3, reps: 10, weight: 30)
|
return Exercise(name: "", descr: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var navigationTitle: String {
|
static var navigationTitle: String {
|
||||||
@ -76,20 +68,5 @@ fileprivate struct ExerciseFormView: View {
|
|||||||
TextEditor(text: $model.descr)
|
TextEditor(text: $model.descr)
|
||||||
.frame(minHeight: 100)
|
.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 {
|
final class Split {
|
||||||
var name: String = ""
|
var name: String = ""
|
||||||
var intro: 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)
|
@Relationship(deleteRule: .cascade, inverse: \SplitExerciseAssignment.split)
|
||||||
var exercises: [SplitExerciseAssignment]? = []
|
var exercises: [SplitExerciseAssignment]? = []
|
||||||
@ -13,9 +34,11 @@ final class Split {
|
|||||||
@Relationship(deleteRule: .nullify, inverse: \Workout.split)
|
@Relationship(deleteRule: .nullify, inverse: \Workout.split)
|
||||||
var workouts: [Workout]? = []
|
var workouts: [Workout]? = []
|
||||||
|
|
||||||
init(name: String, intro: String) {
|
init(name: String, intro: String, color: String = "indigo", systemImage: String = "dumbbell.fill") {
|
||||||
self.name = name
|
self.name = name
|
||||||
self.intro = intro
|
self.intro = intro
|
||||||
|
self.color = color
|
||||||
|
self.systemImage = systemImage
|
||||||
}
|
}
|
||||||
|
|
||||||
static let unnamed = "Unnamed Split"
|
static let unnamed = "Unnamed Split"
|
||||||
@ -53,6 +76,12 @@ fileprivate struct SplitFormView: View {
|
|||||||
@State private var itemToEdit: SplitExerciseAssignment? = nil
|
@State private var itemToEdit: SplitExerciseAssignment? = nil
|
||||||
@State private var itemToDelete: 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 {
|
var body: some View {
|
||||||
Section(header: Text("Name")) {
|
Section(header: Text("Name")) {
|
||||||
TextField("Name", text: $model.name)
|
TextField("Name", text: $model.name)
|
||||||
@ -64,6 +93,32 @@ fileprivate struct SplitFormView: View {
|
|||||||
.frame(minHeight: 100)
|
.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")) {
|
Section(header: Text("Exercises")) {
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
@ -109,9 +164,9 @@ fileprivate struct SplitFormView: View {
|
|||||||
ExercisePickerView { exercise in
|
ExercisePickerView { exercise in
|
||||||
itemToEdit = SplitExerciseAssignment(
|
itemToEdit = SplitExerciseAssignment(
|
||||||
order: 0,
|
order: 0,
|
||||||
sets: exercise.sets,
|
sets: 3,
|
||||||
reps: exercise.reps,
|
reps: 10,
|
||||||
weight: exercise.weight,
|
weight: 40,
|
||||||
split: model,
|
split: model,
|
||||||
exercise: exercise
|
exercise: exercise
|
||||||
)
|
)
|
||||||
|
@ -7,6 +7,9 @@ final class WorkoutLog {
|
|||||||
var sets: Int = 0
|
var sets: Int = 0
|
||||||
var reps: Int = 0
|
var reps: Int = 0
|
||||||
var weight: Int = 0
|
var weight: Int = 0
|
||||||
|
var status: WorkoutStatus? = WorkoutStatus.notStarted
|
||||||
|
var order: Int = 0
|
||||||
|
|
||||||
var completed: Bool = false
|
var completed: Bool = false
|
||||||
|
|
||||||
@Relationship(deleteRule: .nullify)
|
@Relationship(deleteRule: .nullify)
|
||||||
@ -15,13 +18,15 @@ final class WorkoutLog {
|
|||||||
@Relationship(deleteRule: .nullify)
|
@Relationship(deleteRule: .nullify)
|
||||||
var exercise: Exercise?
|
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.date = date
|
||||||
|
self.order = order
|
||||||
self.sets = sets
|
self.sets = sets
|
||||||
self.reps = reps
|
self.reps = reps
|
||||||
self.weight = weight
|
self.weight = weight
|
||||||
self.completed = completed
|
self.status = status
|
||||||
self.workout = workout
|
self.workout = workout
|
||||||
self.exercise = exercise
|
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
|
// 4. Load Exercises
|
||||||
let exerciseData = try loadJSON(forResource: "exercises", type: [ExerciseData].self)
|
let exerciseData = try loadJSON(forResource: "exercises", type: [ExerciseData].self)
|
||||||
for data in exerciseData {
|
for data in exerciseData {
|
||||||
let exercise = Exercise(name: data.name, setup: data.setup, descr: data.descr,
|
let exercise = Exercise(name: data.name, descr: data.descr)
|
||||||
sets: data.sets, reps: data.reps, weight: data.weight)
|
|
||||||
|
|
||||||
// Set exercise type
|
// Set exercise type
|
||||||
if let type = exerciseTypes[data.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 {
|
enum SchemaVersion: Int {
|
||||||
case v1
|
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 {
|
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 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
|
return container
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,7 +20,7 @@ final class WorkoutsContainer {
|
|||||||
let configuration = ModelConfiguration(isStoredInMemoryOnly: true)
|
let configuration = ModelConfiguration(isStoredInMemoryOnly: true)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let schema = Schema(SchemaV1.models)
|
let schema = Schema(SchemaV2.models)
|
||||||
let container = try ModelContainer(for: schema, configurations: configuration)
|
let container = try ModelContainer(for: schema, configurations: configuration)
|
||||||
let context = ModelContext(container)
|
let context = ModelContext(container)
|
||||||
|
|
||||||
|
@ -2,10 +2,28 @@ import SwiftData
|
|||||||
|
|
||||||
struct WorkoutsMigrationPlan: SchemaMigrationPlan {
|
struct WorkoutsMigrationPlan: SchemaMigrationPlan {
|
||||||
static var schemas: [VersionedSchema.Type] = [
|
static var schemas: [VersionedSchema.Type] = [
|
||||||
SchemaV1.self
|
SchemaV1.self,
|
||||||
|
SchemaV2.self
|
||||||
]
|
]
|
||||||
|
|
||||||
static var stages: [MigrationStage] = [
|
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 {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
VStack {
|
ScrollView {
|
||||||
Form {
|
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) {
|
||||||
Section (header: Text("This Split")) {
|
ForEach(splits) { split in
|
||||||
List {
|
Button(action: {
|
||||||
ForEach(splits) { split in
|
onSplitSelected(split)
|
||||||
Button(action: {
|
dismiss()
|
||||||
onSplitSelected(split)
|
}) {
|
||||||
dismiss()
|
VStack {
|
||||||
}) {
|
ZStack {
|
||||||
HStack {
|
RoundedRectangle(cornerRadius: 12)
|
||||||
Text(split.name)
|
.fill(split.getColor())
|
||||||
.font(.headline)
|
.aspectRatio(1, contentMode: .fit)
|
||||||
Spacer()
|
.shadow(radius: 2)
|
||||||
Text("\(split.exercises?.count ?? 0)")
|
|
||||||
.font(.caption)
|
Image(systemName: split.systemImage)
|
||||||
}
|
.font(.system(size: 30))
|
||||||
.contentShape(Rectangle())
|
.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 {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigationBarLeading) {
|
ToolbarItem(placement: .navigationBarLeading) {
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import SwiftData
|
||||||
|
|
||||||
struct WorkoutLogView: View {
|
struct WorkoutLogView: View {
|
||||||
@Environment(\.modelContext) private var modelContext
|
@Environment(\.modelContext) private var modelContext
|
||||||
@ -21,7 +22,7 @@ struct WorkoutLogView: View {
|
|||||||
var sortedWorkoutLogs: [WorkoutLog] {
|
var sortedWorkoutLogs: [WorkoutLog] {
|
||||||
if let logs = workout.logs {
|
if let logs = workout.logs {
|
||||||
logs.sorted(by: {
|
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 {
|
} else {
|
||||||
[]
|
[]
|
||||||
@ -33,33 +34,54 @@ struct WorkoutLogView: View {
|
|||||||
Section (header: Text("\(workout.label)")) {
|
Section (header: Text("\(workout.label)")) {
|
||||||
List {
|
List {
|
||||||
ForEach (sortedWorkoutLogs) { log in
|
ForEach (sortedWorkoutLogs) { log in
|
||||||
let badges = log.completed ? [Badge(text: "Completed", color: .green)] : []
|
// Handle optional status, defaulting to a status based on completed flag if nil
|
||||||
ListItem(
|
let _ = print("DEBUG: workoutLog.status=\(log.status)")
|
||||||
title: log.exercise?.name ?? "Untitled Exercise",
|
|
||||||
subtitle: "\(log.sets) sets × \(log.reps) reps × \(log.weight) lbs",
|
let workoutLogStatus = log.status?.checkboxStatus ?? (log.completed ? CheckboxStatus.checked : CheckboxStatus.unchecked)
|
||||||
badges: badges
|
|
||||||
|
CheckboxListItem(
|
||||||
|
status: workoutLogStatus,
|
||||||
|
title: log.exercise?.name ?? Exercise.unnamed,
|
||||||
|
subtitle: "\(log.sets) sets × \(log.reps) reps × \(log.weight) lbs"
|
||||||
)
|
)
|
||||||
|
|
||||||
.swipeActions(edge: .leading, allowsFullSwipe: false) {
|
.swipeActions(edge: .leading, allowsFullSwipe: false) {
|
||||||
if (log.completed) {
|
let status = log.status ?? WorkoutStatus.notStarted
|
||||||
|
|
||||||
|
if [.inProgress,.completed].contains(status) {
|
||||||
Button {
|
Button {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
log.completed = false
|
log.status = .notStarted
|
||||||
try? modelContext.save()
|
try? modelContext.save()
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label("Complete", systemImage: "circle.fill")
|
Label("Not Started", systemImage: WorkoutStatus.notStarted.checkboxStatus.systemName)
|
||||||
}
|
}
|
||||||
.tint(.green)
|
.tint(WorkoutStatus.notStarted.checkboxStatus.color)
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if [.notStarted,.completed].contains(status) {
|
||||||
Button {
|
Button {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
log.completed = true
|
log.status = .inProgress
|
||||||
try? modelContext.save()
|
try? modelContext.save()
|
||||||
}
|
}
|
||||||
} label: {
|
} 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) {
|
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
|
||||||
@ -89,13 +111,14 @@ struct WorkoutLogView: View {
|
|||||||
}
|
}
|
||||||
.sheet(isPresented: $showingAddSheet) {
|
.sheet(isPresented: $showingAddSheet) {
|
||||||
ExercisePickerView { exercise in
|
ExercisePickerView { exercise in
|
||||||
|
let setsRepsWeight = getSetsRepsWeight(exercise, in: modelContext)
|
||||||
let workoutLog = WorkoutLog(
|
let workoutLog = WorkoutLog(
|
||||||
workout: workout,
|
workout: workout,
|
||||||
exercise: exercise,
|
exercise: exercise,
|
||||||
date: Date(),
|
date: Date(),
|
||||||
sets: exercise.sets,
|
sets: setsRepsWeight.sets,
|
||||||
reps: exercise.reps,
|
reps: setsRepsWeight.reps,
|
||||||
weight: exercise.weight,
|
weight: setsRepsWeight.weight,
|
||||||
completed: false
|
completed: false
|
||||||
)
|
)
|
||||||
workout.logs?.append(workoutLog)
|
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,
|
workout: workout,
|
||||||
exercise: exercise,
|
exercise: exercise,
|
||||||
date: Date(),
|
date: Date(),
|
||||||
|
order: assignment.order,
|
||||||
sets: assignment.sets,
|
sets: assignment.sets,
|
||||||
reps: assignment.reps,
|
reps: assignment.reps,
|
||||||
weight: assignment.weight,
|
weight: assignment.weight
|
||||||
completed: false
|
|
||||||
)
|
)
|
||||||
modelContext.insert(workoutLog)
|
modelContext.insert(workoutLog)
|
||||||
} else {
|
} else {
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "watchos",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
6
Worksouts Watch App/Assets.xcassets/Contents.json
Normal file
6
Worksouts Watch App/Assets.xcassets/Contents.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
27
Worksouts Watch App/ContentView.swift
Normal file
27
Worksouts Watch App/ContentView.swift
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// ContentView.swift
|
||||||
|
// Workouts
|
||||||
|
//
|
||||||
|
// Created by rzen on 7/15/25 at 7:09 PM.
|
||||||
|
//
|
||||||
|
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ContentView: View {
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
Image(systemName: "globe")
|
||||||
|
.imageScale(.large)
|
||||||
|
.foregroundStyle(.tint)
|
||||||
|
Text("Hello, world!")
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ContentView()
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
16
Worksouts Watch App/Worksouts Watch App.entitlements
Normal file
16
Worksouts Watch App/Worksouts Watch App.entitlements
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>aps-environment</key>
|
||||||
|
<string>development</string>
|
||||||
|
<key>com.apple.developer.icloud-container-identifiers</key>
|
||||||
|
<array>
|
||||||
|
<string>iCloud.com.dev.rzen.indie.Workouts</string>
|
||||||
|
</array>
|
||||||
|
<key>com.apple.developer.icloud-services</key>
|
||||||
|
<array>
|
||||||
|
<string>CloudKit</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
20
Worksouts Watch App/WorksoutsApp.swift
Normal file
20
Worksouts Watch App/WorksoutsApp.swift
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// WorksoutsApp.swift
|
||||||
|
// Workouts
|
||||||
|
//
|
||||||
|
// Created by rzen on 7/15/25 at 7:09 PM.
|
||||||
|
//
|
||||||
|
// Copyright 2025 Rouslan Zenetl. All Rights Reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct Worksouts_Watch_AppApp: App {
|
||||||
|
var body: some Scene {
|
||||||
|
WindowGroup {
|
||||||
|
ContentView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user