From 48f23f8e775c4af2f6cb4c3f2354002861c88e8e Mon Sep 17 00:00:00 2001 From: Trey t Date: Fri, 23 Jun 2023 13:34:29 -0500 Subject: [PATCH] WIP --- Werkout-ios-Info.plist | 4 + Werkout-watch-Watch-App-Info.plist | 12 + Werkout_ios.xcodeproj/project.pbxproj | 237 +++++++++++++++++- .../xcschemes/xcschememanagement.plist | 5 + Werkout_ios/BridgeModule.swift | 69 ++++- .../WorkoutDetail/WorkoutDetailView.swift | 6 +- Werkout_ios/WatchPackageModel.swift | 15 ++ .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 13 + .../Assets.xcassets/Contents.json | 6 + Werkout_watch Watch App/ContentView.swift | 44 ++++ .../Preview Assets.xcassets/Contents.json | 6 + .../WatchMainViewModel.swift | 195 ++++++++++++++ .../Werkout_watch Watch App.entitlements | 10 + .../Werkout_watchApp.swift | 17 ++ 15 files changed, 644 insertions(+), 6 deletions(-) create mode 100644 Werkout-watch-Watch-App-Info.plist create mode 100644 Werkout_ios/WatchPackageModel.swift create mode 100644 Werkout_watch Watch App/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 Werkout_watch Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Werkout_watch Watch App/Assets.xcassets/Contents.json create mode 100644 Werkout_watch Watch App/ContentView.swift create mode 100644 Werkout_watch Watch App/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 Werkout_watch Watch App/WatchMainViewModel.swift create mode 100644 Werkout_watch Watch App/Werkout_watch Watch App.entitlements create mode 100644 Werkout_watch Watch App/Werkout_watchApp.swift diff --git a/Werkout-ios-Info.plist b/Werkout-ios-Info.plist index 6a6654d..fc47036 100644 --- a/Werkout-ios-Info.plist +++ b/Werkout-ios-Info.plist @@ -2,6 +2,10 @@ + NSHealthShareUsageDescription + Read your heart reate + NSHealthUpdateUsageDescription + Read your heart reate NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/Werkout-watch-Watch-App-Info.plist b/Werkout-watch-Watch-App-Info.plist new file mode 100644 index 0000000..2b0f574 --- /dev/null +++ b/Werkout-watch-Watch-App-Info.plist @@ -0,0 +1,12 @@ + + + + + UIBackgroundModes + + WKBackgroundModes + + workout-processing + + + diff --git a/Werkout_ios.xcodeproj/project.pbxproj b/Werkout_ios.xcodeproj/project.pbxproj index 1defcc8..42b8769 100644 --- a/Werkout_ios.xcodeproj/project.pbxproj +++ b/Werkout_ios.xcodeproj/project.pbxproj @@ -43,8 +43,53 @@ 1CF65A852A43E8060042FFBD /* CompletedWorkout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A842A43E8060042FFBD /* CompletedWorkout.swift */; }; 1CF65A872A4400E10042FFBD /* ToDo in Resources */ = {isa = PBXBuildFile; fileRef = 1CF65A862A4400E10042FFBD /* ToDo */; }; 1CF65A8E2A44B78B0042FFBD /* CompletedWorkoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A8D2A44B78B0042FFBD /* CompletedWorkoutView.swift */; }; + 1CF65A962A452D270042FFBD /* Werkout_watchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A952A452D270042FFBD /* Werkout_watchApp.swift */; }; + 1CF65A982A452D270042FFBD /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A972A452D270042FFBD /* ContentView.swift */; }; + 1CF65A9A2A452D290042FFBD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1CF65A992A452D290042FFBD /* Assets.xcassets */; }; + 1CF65A9D2A452D290042FFBD /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1CF65A9C2A452D290042FFBD /* Preview Assets.xcassets */; }; + 1CF65AA12A452D290042FFBD /* Werkout_watch Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 1CF65A932A452D270042FFBD /* Werkout_watch Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 1CF65AA52A452D9C0042FFBD /* CompletedWorkout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A842A43E8060042FFBD /* CompletedWorkout.swift */; }; + 1CF65AA62A452D9C0042FFBD /* Equipment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A5A2A3BF4BE0042FFBD /* Equipment.swift */; }; + 1CF65AA72A452D9C0042FFBD /* Muscle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A582A3BF4B60042FFBD /* Muscle.swift */; }; + 1CF65AA82A452D9C0042FFBD /* Exercise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A442A39FB550042FFBD /* Exercise.swift */; }; + 1CF65AA92A452D9C0042FFBD /* Workout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A422A39FB410042FFBD /* Workout.swift */; }; + 1CF65AAA2A452D9C0042FFBD /* RegisteredUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A462A39FB6C0042FFBD /* RegisteredUser.swift */; }; + 1CF65AAB2A452DAC0042FFBD /* PreviewWorkout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A512A3A90A00042FFBD /* PreviewWorkout.swift */; }; + 1CF65AAC2A452DF50042FFBD /* WorkoutOne.json in Resources */ = {isa = PBXBuildFile; fileRef = 1CF65A492A39FBB10042FFBD /* WorkoutOne.json */; }; + 1CF65AAD2A452DF50042FFBD /* Exercises.json in Resources */ = {isa = PBXBuildFile; fileRef = 1CF65A662A3BFF840042FFBD /* Exercises.json */; }; + 1CF65AAE2A452DF50042FFBD /* AllWorkouts.json in Resources */ = {isa = PBXBuildFile; fileRef = 1CF65A642A3BF6BE0042FFBD /* AllWorkouts.json */; }; + 1CF65AAF2A452DF50042FFBD /* AllMuscles.json in Resources */ = {isa = PBXBuildFile; fileRef = 1CF65A562A3BF3830042FFBD /* AllMuscles.json */; }; + 1CF65AB02A452DF50042FFBD /* Equipment.json in Resources */ = {isa = PBXBuildFile; fileRef = 1CF65A5E2A3BF5A60042FFBD /* Equipment.json */; }; + 1CF65AB12A452E1A0042FFBD /* BridgeModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65A4F2A3A1EA90042FFBD /* BridgeModule.swift */; }; + 1CF65AB32A452F360042FFBD /* WatchPackageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65AB22A452F360042FFBD /* WatchPackageModel.swift */; }; + 1CF65AB42A4530200042FFBD /* WatchPackageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65AB22A452F360042FFBD /* WatchPackageModel.swift */; }; + 1CF65AB62A4532940042FFBD /* WatchMainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CF65AB52A4532940042FFBD /* WatchMainViewModel.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 1CF65A9E2A452D290042FFBD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1CF65A1A2A3972840042FFBD /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1CF65A922A452D270042FFBD; + remoteInfo = "Werkout_watch Watch App"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 1CF65AA02A452D290042FFBD /* Embed Watch Content */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(CONTENTS_FOLDER_PATH)/Watch"; + dstSubfolderSpec = 16; + files = ( + 1CF65AA12A452D290042FFBD /* Werkout_watch Watch App.app in Embed Watch Content */, + ); + name = "Embed Watch Content"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 1CF65A222A3972840042FFBD /* Werkout_ios.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Werkout_ios.app; sourceTree = BUILT_PRODUCTS_DIR; }; 1CF65A252A3972840042FFBD /* Werkout_iosApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Werkout_iosApp.swift; sourceTree = ""; }; @@ -85,6 +130,15 @@ 1CF65A842A43E8060042FFBD /* CompletedWorkout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletedWorkout.swift; sourceTree = ""; }; 1CF65A862A4400E10042FFBD /* ToDo */ = {isa = PBXFileReference; lastKnownFileType = text; path = ToDo; sourceTree = ""; }; 1CF65A8D2A44B78B0042FFBD /* CompletedWorkoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletedWorkoutView.swift; sourceTree = ""; }; + 1CF65A932A452D270042FFBD /* Werkout_watch Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Werkout_watch Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 1CF65A952A452D270042FFBD /* Werkout_watchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Werkout_watchApp.swift; sourceTree = ""; }; + 1CF65A972A452D270042FFBD /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 1CF65A992A452D290042FFBD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 1CF65A9C2A452D290042FFBD /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 1CF65AB22A452F360042FFBD /* WatchPackageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchPackageModel.swift; sourceTree = ""; }; + 1CF65AB52A4532940042FFBD /* WatchMainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchMainViewModel.swift; sourceTree = ""; }; + 1CF65AB72A4534DC0042FFBD /* Werkout_watch Watch App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Werkout_watch Watch App.entitlements"; sourceTree = ""; }; + 1CF65AB82A45387B0042FFBD /* Werkout-watch-Watch-App-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "Werkout-watch-Watch-App-Info.plist"; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -95,6 +149,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 1CF65A902A452D270042FFBD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -102,6 +163,7 @@ isa = PBXGroup; children = ( 1CF65A242A3972840042FFBD /* Werkout_ios */, + 1CF65A942A452D270042FFBD /* Werkout_watch Watch App */, 1CF65A232A3972840042FFBD /* Products */, ); sourceTree = ""; @@ -110,6 +172,7 @@ isa = PBXGroup; children = ( 1CF65A222A3972840042FFBD /* Werkout_ios.app */, + 1CF65A932A452D270042FFBD /* Werkout_watch Watch App.app */, ); name = Products; sourceTree = ""; @@ -120,6 +183,7 @@ 1CF65A552A3AA6800042FFBD /* Werkout-ios-Info.plist */, 1CF65A482A39FB910042FFBD /* JSON */, 1CF65A252A3972840042FFBD /* Werkout_iosApp.swift */, + 1CF65AB22A452F360042FFBD /* WatchPackageModel.swift */, 1CF65A822A42347D0042FFBD /* Extensions.swift */, 1CF65A272A3972840042FFBD /* Persistence.swift */, 1CF65A4F2A3A1EA90042FFBD /* BridgeModule.swift */, @@ -247,6 +311,28 @@ path = CompletedWorkout; sourceTree = ""; }; + 1CF65A942A452D270042FFBD /* Werkout_watch Watch App */ = { + isa = PBXGroup; + children = ( + 1CF65AB82A45387B0042FFBD /* Werkout-watch-Watch-App-Info.plist */, + 1CF65AB72A4534DC0042FFBD /* Werkout_watch Watch App.entitlements */, + 1CF65A952A452D270042FFBD /* Werkout_watchApp.swift */, + 1CF65A972A452D270042FFBD /* ContentView.swift */, + 1CF65AB52A4532940042FFBD /* WatchMainViewModel.swift */, + 1CF65A992A452D290042FFBD /* Assets.xcassets */, + 1CF65A9B2A452D290042FFBD /* Preview Content */, + ); + path = "Werkout_watch Watch App"; + sourceTree = ""; + }; + 1CF65A9B2A452D290042FFBD /* Preview Content */ = { + isa = PBXGroup; + children = ( + 1CF65A9C2A452D290042FFBD /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -257,14 +343,33 @@ 1CF65A1E2A3972840042FFBD /* Sources */, 1CF65A1F2A3972840042FFBD /* Frameworks */, 1CF65A202A3972840042FFBD /* Resources */, + 1CF65AA02A452D290042FFBD /* Embed Watch Content */, + ); + buildRules = ( + ); + dependencies = ( + 1CF65A9F2A452D290042FFBD /* PBXTargetDependency */, + ); + name = Werkout_ios; + productName = Werkout_ios; + productReference = 1CF65A222A3972840042FFBD /* Werkout_ios.app */; + productType = "com.apple.product-type.application"; + }; + 1CF65A922A452D270042FFBD /* Werkout_watch Watch App */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1CF65AA22A452D290042FFBD /* Build configuration list for PBXNativeTarget "Werkout_watch Watch App" */; + buildPhases = ( + 1CF65A8F2A452D270042FFBD /* Sources */, + 1CF65A902A452D270042FFBD /* Frameworks */, + 1CF65A912A452D270042FFBD /* Resources */, ); buildRules = ( ); dependencies = ( ); - name = Werkout_ios; - productName = Werkout_ios; - productReference = 1CF65A222A3972840042FFBD /* Werkout_ios.app */; + name = "Werkout_watch Watch App"; + productName = "Werkout_watch Watch App"; + productReference = 1CF65A932A452D270042FFBD /* Werkout_watch Watch App.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -280,6 +385,9 @@ 1CF65A212A3972840042FFBD = { CreatedOnToolsVersion = 14.3; }; + 1CF65A922A452D270042FFBD = { + CreatedOnToolsVersion = 14.3; + }; }; }; buildConfigurationList = 1CF65A1D2A3972840042FFBD /* Build configuration list for PBXProject "Werkout_ios" */; @@ -296,6 +404,7 @@ projectRoot = ""; targets = ( 1CF65A212A3972840042FFBD /* Werkout_ios */, + 1CF65A922A452D270042FFBD /* Werkout_watch Watch App */, ); }; /* End PBXProject section */ @@ -317,6 +426,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 1CF65A912A452D270042FFBD /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1CF65AAF2A452DF50042FFBD /* AllMuscles.json in Resources */, + 1CF65AAC2A452DF50042FFBD /* WorkoutOne.json in Resources */, + 1CF65AAD2A452DF50042FFBD /* Exercises.json in Resources */, + 1CF65AB02A452DF50042FFBD /* Equipment.json in Resources */, + 1CF65A9D2A452D290042FFBD /* Preview Assets.xcassets in Resources */, + 1CF65A9A2A452D290042FFBD /* Assets.xcassets in Resources */, + 1CF65AAE2A452DF50042FFBD /* AllWorkouts.json in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -334,6 +457,7 @@ 1CF65A692A3C018F0042FFBD /* AccountView.swift in Sources */, 1CF65A4E2A39FF200042FFBD /* WorkoutDetailViewModel.swift in Sources */, 1CF65A472A39FB6C0042FFBD /* RegisteredUser.swift in Sources */, + 1CF65AB32A452F360042FFBD /* WatchPackageModel.swift in Sources */, 1CF65A522A3A90A00042FFBD /* PreviewWorkout.swift in Sources */, 1CF65A852A43E8060042FFBD /* CompletedWorkout.swift in Sources */, 1CF65A6E2A3F60480042FFBD /* CreateViewModels.swift in Sources */, @@ -354,8 +478,35 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 1CF65A8F2A452D270042FFBD /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1CF65A982A452D270042FFBD /* ContentView.swift in Sources */, + 1CF65A962A452D270042FFBD /* Werkout_watchApp.swift in Sources */, + 1CF65AA92A452D9C0042FFBD /* Workout.swift in Sources */, + 1CF65AA62A452D9C0042FFBD /* Equipment.swift in Sources */, + 1CF65AB12A452E1A0042FFBD /* BridgeModule.swift in Sources */, + 1CF65AAA2A452D9C0042FFBD /* RegisteredUser.swift in Sources */, + 1CF65AB62A4532940042FFBD /* WatchMainViewModel.swift in Sources */, + 1CF65AA72A452D9C0042FFBD /* Muscle.swift in Sources */, + 1CF65AAB2A452DAC0042FFBD /* PreviewWorkout.swift in Sources */, + 1CF65AA52A452D9C0042FFBD /* CompletedWorkout.swift in Sources */, + 1CF65AA82A452D9C0042FFBD /* Exercise.swift in Sources */, + 1CF65AB42A4530200042FFBD /* WatchPackageModel.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 1CF65A9F2A452D290042FFBD /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 1CF65A922A452D270042FFBD /* Werkout_watch Watch App */; + targetProxy = 1CF65A9E2A452D290042FFBD /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ 1CF65A342A3972850042FFBD /* Debug */ = { isa = XCBuildConfiguration; @@ -469,6 +620,7 @@ 1CF65A372A3972850042FFBD /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Werkout_ios/Werkout_ios.entitlements; @@ -508,6 +660,7 @@ 1CF65A382A3972850042FFBD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Werkout_ios/Werkout_ios.entitlements; @@ -544,6 +697,75 @@ }; name = Release; }; + 1CF65AA32A452D290042FFBD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "Werkout_watch Watch App/Werkout_watch Watch App.entitlements"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Werkout_watch Watch App/Preview Content\""; + DEVELOPMENT_TEAM = V3PF3M6B6U; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Werkout-watch-Watch-App-Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = Werkout_watch; + INFOPLIST_KEY_NSHealthShareUsageDescription = "Read your heart reate"; + INFOPLIST_KEY_NSHealthUpdateUsageDescription = "Read your heart reate"; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_WKCompanionAppBundleIdentifier = "com.t-t.Werkout-ios"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.t-t.Werkout-ios.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 = 9.4; + }; + name = Debug; + }; + 1CF65AA42A452D290042FFBD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "Werkout_watch Watch App/Werkout_watch Watch App.entitlements"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Werkout_watch Watch App/Preview Content\""; + DEVELOPMENT_TEAM = V3PF3M6B6U; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Werkout-watch-Watch-App-Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = Werkout_watch; + INFOPLIST_KEY_NSHealthShareUsageDescription = "Read your heart reate"; + INFOPLIST_KEY_NSHealthUpdateUsageDescription = "Read your heart reate"; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + INFOPLIST_KEY_WKCompanionAppBundleIdentifier = "com.t-t.Werkout-ios"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.t-t.Werkout-ios.watchkitapp"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + VALIDATE_PRODUCT = YES; + WATCHOS_DEPLOYMENT_TARGET = 9.4; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -565,6 +787,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 1CF65AA22A452D290042FFBD /* Build configuration list for PBXNativeTarget "Werkout_watch Watch App" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1CF65AA32A452D290042FFBD /* Debug */, + 1CF65AA42A452D290042FFBD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCVersionGroup section */ diff --git a/Werkout_ios.xcodeproj/xcuserdata/treyt.xcuserdatad/xcschemes/xcschememanagement.plist b/Werkout_ios.xcodeproj/xcuserdata/treyt.xcuserdatad/xcschemes/xcschememanagement.plist index 8aad30a..2076216 100644 --- a/Werkout_ios.xcodeproj/xcuserdata/treyt.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Werkout_ios.xcodeproj/xcuserdata/treyt.xcuserdatad/xcschemes/xcschememanagement.plist @@ -9,6 +9,11 @@ orderHint 0 + Werkout_watch Watch App.xcscheme_^#shared#^_ + + orderHint + 1 + diff --git a/Werkout_ios/BridgeModule.swift b/Werkout_ios/BridgeModule.swift index 85ddbbe..aee132f 100644 --- a/Werkout_ios/BridgeModule.swift +++ b/Werkout_ios/BridgeModule.swift @@ -6,8 +6,15 @@ // import Foundation +import WatchConnectivity + +enum WatchActions: String, Codable { + case nextExercise +} + +class BridgeModule: NSObject, ObservableObject { + private let kMessageKey = "message" -class BridgeModule: ObservableObject { static let shared = BridgeModule() @Published var isShowingOnExternalDisplay = false @@ -19,6 +26,7 @@ class BridgeModule: ObservableObject { var currentExerciseIdx: Int = -1 var workoutStartDate: Date? + var workoutEndDate: Date? @Published var currentWorkoutRunTimeInSeconds: Int = -1 private var currentWorkoutRunTimer: Timer? @@ -36,6 +44,11 @@ class BridgeModule: ObservableObject { startWorkoutTimer() workoutStartDate = Date() isInWorkout = true + + if WCSession.isSupported() { + WCSession.default.delegate = self + WCSession.default.activate() + } } func resetCurrentWorkout() { @@ -52,8 +65,14 @@ class BridgeModule: ObservableObject { currentExercise = nil currentWorkout = nil + if isInWorkout { + let watchModel = WatchPackageModel(currentExerciseName: currentExercise?.exercise.name ?? "-", currentTimeLeft: timeLeft, workoutStartDate: workoutStartDate ?? Date(), workoutEndDate: Date()) + let data = try! JSONEncoder().encode(watchModel) + send(data) + } isInWorkout = false workoutStartDate = nil + workoutEndDate = nil } private func startWorkoutTimer() { @@ -82,6 +101,11 @@ class BridgeModule: ObservableObject { @objc func updateCounter() { if timeLeft > 0 { timeLeft -= 1 + + let watchModel = WatchPackageModel(currentExerciseName: currentExercise?.exercise.name ?? "-", currentTimeLeft: timeLeft, workoutStartDate: workoutStartDate ?? Date()) + let data = try! JSONEncoder().encode(watchModel) + send(data) + } else { timer?.invalidate() timer = nil @@ -114,3 +138,46 @@ class BridgeModule: ObservableObject { } } } + +extension BridgeModule: WCSessionDelegate { + func session(_ session: WCSession, didReceiveMessageData messageData: Data) { + if let model = try? JSONDecoder().decode(WatchActions.self, from: messageData) { + switch model { + case .nextExercise: + nextExercise() + } + } + } + + + func session(_ session: WCSession, + activationDidCompleteWith activationState: WCSessionActivationState, + error: Error?) {} + +#if os(iOS) + func sessionDidBecomeInactive(_ session: WCSession) {} + func sessionDidDeactivate(_ session: WCSession) { + session.activate() + } +#endif + + + func send(_ data: Data) { + guard WCSession.default.activationState == .activated else { + return + } +#if os(iOS) + guard WCSession.default.isWatchAppInstalled else { + return + } +#else + guard WCSession.default.isCompanionAppInstalled else { + return + } +#endif + WCSession.default.sendMessageData(data, replyHandler: nil) + { error in + print("Cannot send message: \(String(describing: error))") + } + } +} diff --git a/Werkout_ios/Views/WorkoutDetail/WorkoutDetailView.swift b/Werkout_ios/Views/WorkoutDetail/WorkoutDetailView.swift index aa998ab..a04b8ea 100644 --- a/Werkout_ios/Views/WorkoutDetail/WorkoutDetailView.swift +++ b/Werkout_ios/Views/WorkoutDetail/WorkoutDetailView.swift @@ -33,11 +33,11 @@ struct WorkoutDetailView: View { CountdownView() ExerciseListView(workout: workout) ActionsView(completedWorkout: { + bridgeModule.workoutEndDate = Date() if let workoutData = createWorkoutData() { presentedSheet = .completedWorkout(workoutData) bridgeModule.resetCurrentWorkout() } - }, workout: workout) .frame(height: 44) @@ -59,12 +59,14 @@ struct WorkoutDetailView: View { func createWorkoutData() -> [String:Any]? { guard let workoutid = bridgeModule.currentWorkout?.id, - let startTime = bridgeModule.workoutStartDate?.timeFormatForUpload else { + let startTime = bridgeModule.workoutStartDate?.timeFormatForUpload, + let endTime = bridgeModule.workoutEndDate?.timeFormatForUpload else { return nil } let postBody = [ "difficulty": 1, "workout_start_time": startTime, + "workout_end_time": endTime, "workout": workoutid, "total_time": bridgeModule.currentWorkoutRunTimeInSeconds ] as [String : Any] diff --git a/Werkout_ios/WatchPackageModel.swift b/Werkout_ios/WatchPackageModel.swift new file mode 100644 index 0000000..2b55273 --- /dev/null +++ b/Werkout_ios/WatchPackageModel.swift @@ -0,0 +1,15 @@ +// +// WatchPackageModel.swift +// Werkout_ios +// +// Created by Trey Tartt on 6/22/23. +// + +import Foundation + +struct WatchPackageModel: Codable { + var currentExerciseName: String + var currentTimeLeft: Int + var workoutStartDate: Date + var workoutEndDate: Date? +} diff --git a/Werkout_watch Watch App/Assets.xcassets/AccentColor.colorset/Contents.json b/Werkout_watch Watch App/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Werkout_watch Watch App/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Werkout_watch Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json b/Werkout_watch Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..49c81cd --- /dev/null +++ b/Werkout_watch Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "watchos", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Werkout_watch Watch App/Assets.xcassets/Contents.json b/Werkout_watch Watch App/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Werkout_watch Watch App/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Werkout_watch Watch App/ContentView.swift b/Werkout_watch Watch App/ContentView.swift new file mode 100644 index 0000000..2990cd7 --- /dev/null +++ b/Werkout_watch Watch App/ContentView.swift @@ -0,0 +1,44 @@ +// +// ContentView.swift +// Werkout_watch Watch App +// +// Created by Trey Tartt on 6/22/23. +// + +import SwiftUI + +struct ContentView: View { + let exercise = PreviewWorkout.parseEquipment()[2] + @StateObject var vm = WatchMainViewModel() + + var body: some View { + VStack { + if let model = vm.watchPackageModel { + Text(model.currentExerciseName) + + Text("\(model.currentTimeLeft )") + } + + if let heartValue = vm.heartValue { + HStack { + Image(systemName: "heart.fill") + Text("\(heartValue)") + } + } + Button(action: { + vm.nextExercise() + }, label: { + Image(systemName: "arrow.forward") + .font(.title) + .frame(maxWidth: .infinity, maxHeight: .infinity) + }) + } + .padding() + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} diff --git a/Werkout_watch Watch App/Preview Content/Preview Assets.xcassets/Contents.json b/Werkout_watch Watch App/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Werkout_watch Watch App/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Werkout_watch Watch App/WatchMainViewModel.swift b/Werkout_watch Watch App/WatchMainViewModel.swift new file mode 100644 index 0000000..966e594 --- /dev/null +++ b/Werkout_watch Watch App/WatchMainViewModel.swift @@ -0,0 +1,195 @@ +// +// WatchMainViewMoel.swift +// Werkout_watch Watch App +// +// Created by Trey Tartt on 6/22/23. +// + +import Foundation +import WatchConnectivity +import SwiftUI +import HealthKit + +class WatchMainViewModel: NSObject, ObservableObject { + var session: WCSession + @Published var watchPackageModel: WatchPackageModel? + @Published var heartValue: Int? + + let healthStore = HKHealthStore() + var hkWorkoutSession: HKWorkoutSession? { + didSet { + print("here") + } + } + var hkBuilder: HKLiveWorkoutBuilder? + + override init() { + session = WCSession.default + super.init() + + session.delegate = self + session.activate() + autorizeHealthKit() + } + + func autorizeHealthKit() { + let healthKitTypes: Set = [ + HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!, + HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)!, + HKQuantityType.workoutType() + ] + healthStore.requestAuthorization(toShare: healthKitTypes, read: healthKitTypes) { (succ, error) in + if !succ { + fatalError("Error requesting authorization from health store: \(String(describing: error)))") + } + } + } + + private func startHeartRateQuery(quantityTypeIdentifier: HKQuantityTypeIdentifier) { + let devicePredicate = HKQuery.predicateForObjects(from: [HKDevice.local()]) + let updateHandler: (HKAnchoredObjectQuery, [HKSample]?, [HKDeletedObject]?, HKQueryAnchor?, Error?) -> Void = { + query, samples, deletedObjects, queryAnchor, error in + guard let samples = samples as? [HKQuantitySample] else { + return + } + } + + let query = HKAnchoredObjectQuery(type: HKObjectType.quantityType(forIdentifier: quantityTypeIdentifier)!, predicate: devicePredicate, anchor: nil, limit: HKObjectQueryNoLimit, resultsHandler: updateHandler) + + query.updateHandler = updateHandler + healthStore.execute(query) + } + + func nextExercise() { + let nextExerciseAction = WatchActions.nextExercise + let data = try! JSONEncoder().encode(nextExerciseAction) + send(data) + } +} + +extension WatchMainViewModel: WCSessionDelegate { + func session(_ session: WCSession, didReceiveMessageData messageData: Data) { + if let model = try? JSONDecoder().decode(WatchPackageModel.self, from: messageData) { + DispatchQueue.main.async { + if self.watchPackageModel?.workoutEndDate != nil { + self.watchPackageModel = nil + self.stopWorkout() + } else if self.watchPackageModel == nil { + self.startWorkout() + } + self.watchPackageModel = model + } + } + } + + func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { + + } + + func send(_ data: Data) { + guard WCSession.default.activationState == .activated else { + return + } +#if os(iOS) + guard WCSession.default.isWatchAppInstalled else { + return + } +#else + guard WCSession.default.isCompanionAppInstalled else { + return + } +#endif + WCSession.default.sendMessageData(data, replyHandler: nil) + { error in + print("Cannot send message: \(String(describing: error))") + } + } +} + +extension WatchMainViewModel: HKWorkoutSessionDelegate, HKLiveWorkoutBuilderDelegate { + func initWorkout() { + let configuration = HKWorkoutConfiguration() + configuration.activityType = .functionalStrengthTraining + + do { + hkWorkoutSession = try HKWorkoutSession(healthStore: healthStore, configuration: configuration) + hkBuilder = hkWorkoutSession?.associatedWorkoutBuilder() + } catch { + fatalError("Unable to create the workout session!") + } + guard let hkWorkoutSession = hkWorkoutSession, let hkBuilder = hkBuilder else { + return + } + // Setup session and builder. + hkWorkoutSession.delegate = self + hkBuilder.delegate = self + + /// Set the workout builder's data source. + hkBuilder.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore, + workoutConfiguration: configuration) + } + + func startWorkout() { + // Initialize our workout + initWorkout() + + guard let hkWorkoutSession = hkWorkoutSession, let hkBuilder = hkBuilder else { + return + } + + // Start the workout session and begin data collection + hkWorkoutSession.startActivity(with: Date()) + hkBuilder.beginCollection(withStart: Date()) { (succ, error) in + if !succ { + fatalError("Error beginning collection from builder: \(String(describing: error)))") + } + } + } + + func stopWorkout() { + guard let hkWorkoutSession = hkWorkoutSession, let hkBuilder = hkBuilder else { + return + } + + hkWorkoutSession.end() + hkBuilder.endCollection(withEnd: Date()) { (success, error) in + hkBuilder.finishWorkout { (workout, error) in + DispatchQueue.main.async() { + self.hkWorkoutSession = nil + self.hkBuilder = nil + } + } + } + } + + func workoutSession(_ workoutSession: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date) { + print("[workoutSession] Changed State: \(toState.rawValue)") + } + + func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: Error) { + print("[workoutSession] Encountered an error: \(error)") + } + + func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, didCollectDataOf collectedTypes: Set) { + for type in collectedTypes { + guard let quantityType = type as? HKQuantityType else { + return + } + switch quantityType { + case HKQuantityType.quantityType(forIdentifier: .heartRate): + let statistics = workoutBuilder.statistics(for: quantityType) + let heartRateUnit = HKUnit.count().unitDivided(by: HKUnit.minute()) + let value = statistics!.mostRecentQuantity()?.doubleValue(for: heartRateUnit) + heartValue = Int(Double(round(1 * value!) / 1)) + print("[workoutBuilder] Heart Rate: \(String(describing: heartValue))") + default: + return + } + } + } + + func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) { + guard let workoutEventType = workoutBuilder.workoutEvents.last?.type else { return } + print("[workoutBuilderDidCollectEvent] Workout Builder changed event: \(workoutEventType.rawValue)") + } +} diff --git a/Werkout_watch Watch App/Werkout_watch Watch App.entitlements b/Werkout_watch Watch App/Werkout_watch Watch App.entitlements new file mode 100644 index 0000000..2ab14a2 --- /dev/null +++ b/Werkout_watch Watch App/Werkout_watch Watch App.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.healthkit + + com.apple.developer.healthkit.access + + + diff --git a/Werkout_watch Watch App/Werkout_watchApp.swift b/Werkout_watch Watch App/Werkout_watchApp.swift new file mode 100644 index 0000000..ff19861 --- /dev/null +++ b/Werkout_watch Watch App/Werkout_watchApp.swift @@ -0,0 +1,17 @@ +// +// Werkout_watchApp.swift +// Werkout_watch Watch App +// +// Created by Trey Tartt on 6/22/23. +// + +import SwiftUI + +@main +struct Werkout_watch_Watch_AppApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +}