├── .gitignore ├── README.md ├── Workout Companion.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ ├── Workout Companion.xcscheme │ │ └── WorkoutCompanionWidgetExtension.xcscheme └── xcuserdata │ └── marioeguiluz.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── Workout Companion ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── 100.png │ │ ├── 1024.png │ │ ├── 114.png │ │ ├── 120.png │ │ ├── 128.png │ │ ├── 144.png │ │ ├── 152.png │ │ ├── 16.png │ │ ├── 167.png │ │ ├── 172.png │ │ ├── 180.png │ │ ├── 196.png │ │ ├── 20.png │ │ ├── 216.png │ │ ├── 256.png │ │ ├── 29.png │ │ ├── 32.png │ │ ├── 40.png │ │ ├── 48.png │ │ ├── 50.png │ │ ├── 512.png │ │ ├── 55.png │ │ ├── 57.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 64.png │ │ ├── 72.png │ │ ├── 76.png │ │ ├── 80.png │ │ ├── 87.png │ │ ├── 88.png │ │ └── Contents.json │ ├── Colors │ │ ├── Card.colorset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── Main.colorset │ │ │ └── Contents.json │ │ └── MainHighlight.colorset │ │ │ └── Contents.json │ ├── Contents.json │ └── Workouts │ │ ├── Contents.json │ │ ├── cycling.imageset │ │ ├── Contents.json │ │ └── cycling.png │ │ ├── hiit.imageset │ │ ├── Contents.json │ │ └── hiit.png │ │ ├── other.imageset │ │ ├── Contents.json │ │ └── other.png │ │ ├── running.imageset │ │ ├── Contents.json │ │ └── running.png │ │ ├── strength.imageset │ │ ├── Contents.json │ │ └── strength.png │ │ └── walking.imageset │ │ ├── Contents.json │ │ └── walking.png ├── Constants.swift ├── Data │ └── WorkoutManager.swift ├── Extensions │ ├── Array+extensions.swift │ ├── Date+extensions.swift │ ├── HKWorkout+extensions.swift │ ├── HKWorkoutActivityType+extensions.swift │ ├── MKCoordinateRegion+extensions.swift │ ├── Text+extensions.swift │ └── View+extensions.swift ├── Info.plist ├── Models │ ├── MapWorkoutModel.swift │ ├── MenuModel.swift │ ├── WeekWorkoutModel.swift │ ├── WorkoutDayModel.swift │ └── WorkoutRowModel.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── Views │ ├── MapWorkoutWidget.swift │ ├── MenuView.swift │ ├── RecentWorkoutsWidgets.swift │ ├── WIP.swift │ ├── WeekWorkoutsWidget.swift │ └── WorkoutWidgets.swift ├── Workout Companion.entitlements └── Workout_CompanionApp.swift └── WorkoutCompanionWidget ├── Assets.xcassets ├── AccentColor.colorset │ └── Contents.json ├── AppIcon.appiconset │ └── Contents.json ├── Contents.json └── WidgetBackground.colorset │ └── Contents.json ├── Info.plist ├── WorkoutCompanionWidget.intentdefinition └── WorkoutCompanionWidget.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Welcome! wave gif 2 | 3 | 4 | 5 | ### ℹ️ What is this project? 6 | This project will try to help you understand the basics behind SwiftUI and Combine (+ some Widgets and HealthKit!). 7 | It contains different branches that will incrementally add functionality, from the most basic state, to the addition of widgets and more configuration. 8 | 9 | 10 | 11 | ### 🧐 Why did I started this project? 12 | I love Apple Fitness workout views but sadly (as today), those same views are not offered as widgets for the home screen. So I am replicating those ones, and adding the widget-capability myself. By open sourcing it, I thought It could be a great example for other developers to see how to deploy a basic structure of SwiftUI-Combine + Widgets and HealthKit with clear and concise code. 13 | Feel free to open PR or issues to improve it! 14 | 15 | 16 | 17 | ### 🏋️‍♀️ The workout widgets 18 | More and more views will be included. As of today, workout views are implemented and a example widget is already done: 19 | 20 | App demo 21 | 22 | 23 | 24 | ### 🧠 High level design 25 | The main app struct `Workout_CompanionApp`, contains a `WorkoutManager` which is responsible of: 26 | - Gathering `HealthKit` authorization 27 | - Gathering `HealthKit` data 28 | - Providing that data through `@Publisher`s into anyone observing 29 | 30 | The main app inits this `WorkoutManager` as a `@StateObject` property that will be injected via `environmentObject` into the main view tree. 31 | 32 | The main view will use specific publishers from the `WorkoutManager` to feed specific views/widgets down the UI tree. This publishers will emit specifc `ViewModels` for each view that needs to consume them. 33 | 34 | Screenshot 2021-05-10 at 11 43 02 35 | 36 | 37 | ### How to connect with me? 38 | - 🙌🏻 Say _hi_ and add me on my [LinkedIn](https://www.linkedin.com/in/marioeguiluzalebicto/) 39 | - 👾 Dare to challenge me @ Fifa or Warzone! 40 | - 🐦 Connect on my [Twitter](https://twitter.com/marioeguiluz) 41 | 42 | 43 | 44 | --- 45 | > _"It was a bright cold day in April, and the clocks were striking thirteen."_ 46 | --- 47 | -------------------------------------------------------------------------------- /Workout Companion.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 300B51042640AD22003AD7E8 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 300B51032640AD22003AD7E8 /* WidgetKit.framework */; }; 11 | 300B51062640AD22003AD7E8 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 300B51052640AD22003AD7E8 /* SwiftUI.framework */; }; 12 | 300B51092640AD22003AD7E8 /* WorkoutCompanionWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300B51082640AD22003AD7E8 /* WorkoutCompanionWidget.swift */; }; 13 | 300B510C2640AD25003AD7E8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 300B510B2640AD25003AD7E8 /* Assets.xcassets */; }; 14 | 300B510E2640AD25003AD7E8 /* WorkoutCompanionWidget.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 300B510A2640AD23003AD7E8 /* WorkoutCompanionWidget.intentdefinition */; }; 15 | 300B510F2640AD25003AD7E8 /* WorkoutCompanionWidget.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 300B510A2640AD23003AD7E8 /* WorkoutCompanionWidget.intentdefinition */; }; 16 | 300B51122640AD25003AD7E8 /* WorkoutCompanionWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 300B51022640AD22003AD7E8 /* WorkoutCompanionWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 17 | 300B51172640AF9E003AD7E8 /* WeekWorkoutsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300C4EF726395A9F007F60CC /* WeekWorkoutsWidget.swift */; }; 18 | 300B51182640AFB4003AD7E8 /* WeekWorkoutModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3023F93A263D74FE00405A24 /* WeekWorkoutModel.swift */; }; 19 | 300B51192640AFB4003AD7E8 /* MapWorkoutModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3023F93E263D79FF00405A24 /* MapWorkoutModel.swift */; }; 20 | 300B511A2640AFB4003AD7E8 /* WorkoutDayModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3023F938263D67BE00405A24 /* WorkoutDayModel.swift */; }; 21 | 300B511B2640B01A003AD7E8 /* MKCoordinateRegion+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307B7A1A263AB7E300A7F003 /* MKCoordinateRegion+extensions.swift */; }; 22 | 300B511C2640B01A003AD7E8 /* View+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3074ADB1263A0C6400DEB49A /* View+extensions.swift */; }; 23 | 300B511D2640B01A003AD7E8 /* Date+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300C4EFC26395AE9007F60CC /* Date+extensions.swift */; }; 24 | 300B511E2640B27E003AD7E8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 30EBEB4826376F5500A4E9AB /* Assets.xcassets */; }; 25 | 300C4EF826395A9F007F60CC /* WeekWorkoutsWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300C4EF726395A9F007F60CC /* WeekWorkoutsWidget.swift */; }; 26 | 300C4EFD26395AE9007F60CC /* Date+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300C4EFC26395AE9007F60CC /* Date+extensions.swift */; }; 27 | 3023F939263D67BE00405A24 /* WorkoutDayModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3023F938263D67BE00405A24 /* WorkoutDayModel.swift */; }; 28 | 3023F93B263D74FE00405A24 /* WeekWorkoutModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3023F93A263D74FE00405A24 /* WeekWorkoutModel.swift */; }; 29 | 3023F93F263D79FF00405A24 /* MapWorkoutModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3023F93E263D79FF00405A24 /* MapWorkoutModel.swift */; }; 30 | 3024CDB7263DAF5E004B8648 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 307B7A18263AACB900A7F003 /* HealthKit.framework */; }; 31 | 304917FD26387F7600AB1C1B /* WorkoutManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 304917FC26387F7600AB1C1B /* WorkoutManager.swift */; }; 32 | 304A9CD22640B6CE00E883F8 /* WorkoutManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 304917FC26387F7600AB1C1B /* WorkoutManager.swift */; }; 33 | 3074ADB2263A0C6400DEB49A /* View+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3074ADB1263A0C6400DEB49A /* View+extensions.swift */; }; 34 | 307B7A15263AA3D300A7F003 /* MapWorkoutWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307B7A14263AA3D300A7F003 /* MapWorkoutWidget.swift */; }; 35 | 307B7A1B263AB7E300A7F003 /* MKCoordinateRegion+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307B7A1A263AB7E300A7F003 /* MKCoordinateRegion+extensions.swift */; }; 36 | 3086E433263DDB8B00843006 /* WorkoutWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3086E432263DDB8B00843006 /* WorkoutWidgets.swift */; }; 37 | 3086E435263DDE8600843006 /* MenuModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3086E434263DDE8600843006 /* MenuModel.swift */; }; 38 | 30EBEB4526376F5400A4E9AB /* Workout_CompanionApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EBEB4426376F5400A4E9AB /* Workout_CompanionApp.swift */; }; 39 | 30EBEB4726376F5400A4E9AB /* MenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30EBEB4626376F5400A4E9AB /* MenuView.swift */; }; 40 | 30EBEB4926376F5500A4E9AB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 30EBEB4826376F5500A4E9AB /* Assets.xcassets */; }; 41 | 30EBEB4C26376F5500A4E9AB /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 30EBEB4B26376F5500A4E9AB /* Preview Assets.xcassets */; }; 42 | AC1CB5E32649352000AC3CB6 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1CB5E22649352000AC3CB6 /* Constants.swift */; }; 43 | AC1CB5E42649352000AC3CB6 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1CB5E22649352000AC3CB6 /* Constants.swift */; }; 44 | AC1CB5E6264937D500AC3CB6 /* WIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1CB5E5264937D500AC3CB6 /* WIP.swift */; }; 45 | AC1CB5E7264937D500AC3CB6 /* WIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1CB5E5264937D500AC3CB6 /* WIP.swift */; }; 46 | AC7186D8264497B50084C84B /* RecentWorkoutsWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC7186D7264497B50084C84B /* RecentWorkoutsWidgets.swift */; }; 47 | AC7186D9264497B50084C84B /* RecentWorkoutsWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC7186D7264497B50084C84B /* RecentWorkoutsWidgets.swift */; }; 48 | AC7186DB26449D5F0084C84B /* HKWorkoutActivityType+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC7186DA26449D5F0084C84B /* HKWorkoutActivityType+extensions.swift */; }; 49 | AC7186DC26449D5F0084C84B /* HKWorkoutActivityType+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC7186DA26449D5F0084C84B /* HKWorkoutActivityType+extensions.swift */; }; 50 | AC773E222646C6B600113FB1 /* Array+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC773E212646C6B600113FB1 /* Array+extensions.swift */; }; 51 | AC773E232646C6B600113FB1 /* Array+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC773E212646C6B600113FB1 /* Array+extensions.swift */; }; 52 | ACDD7AA42645D2C000D67F68 /* Text+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACDD7AA32645D2C000D67F68 /* Text+extensions.swift */; }; 53 | ACDD7AA52645D2C300D67F68 /* Text+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACDD7AA32645D2C000D67F68 /* Text+extensions.swift */; }; 54 | ACDD7AA72645D38300D67F68 /* WorkoutRowModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACDD7AA62645D38300D67F68 /* WorkoutRowModel.swift */; }; 55 | ACDD7AA82645D38300D67F68 /* WorkoutRowModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACDD7AA62645D38300D67F68 /* WorkoutRowModel.swift */; }; 56 | ACDD7AAA2645EF8100D67F68 /* HKWorkout+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACDD7AA92645EF8100D67F68 /* HKWorkout+extensions.swift */; }; 57 | ACDD7AAB2645EF8100D67F68 /* HKWorkout+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACDD7AA92645EF8100D67F68 /* HKWorkout+extensions.swift */; }; 58 | /* End PBXBuildFile section */ 59 | 60 | /* Begin PBXContainerItemProxy section */ 61 | 300B51102640AD25003AD7E8 /* PBXContainerItemProxy */ = { 62 | isa = PBXContainerItemProxy; 63 | containerPortal = 30EBEB3926376F5400A4E9AB /* Project object */; 64 | proxyType = 1; 65 | remoteGlobalIDString = 300B51012640AD22003AD7E8; 66 | remoteInfo = WorkoutCompanionWidgetExtension; 67 | }; 68 | /* End PBXContainerItemProxy section */ 69 | 70 | /* Begin PBXCopyFilesBuildPhase section */ 71 | 300B51132640AD25003AD7E8 /* Embed App Extensions */ = { 72 | isa = PBXCopyFilesBuildPhase; 73 | buildActionMask = 2147483647; 74 | dstPath = ""; 75 | dstSubfolderSpec = 13; 76 | files = ( 77 | 300B51122640AD25003AD7E8 /* WorkoutCompanionWidgetExtension.appex in Embed App Extensions */, 78 | ); 79 | name = "Embed App Extensions"; 80 | runOnlyForDeploymentPostprocessing = 0; 81 | }; 82 | /* End PBXCopyFilesBuildPhase section */ 83 | 84 | /* Begin PBXFileReference section */ 85 | 300B51022640AD22003AD7E8 /* WorkoutCompanionWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WorkoutCompanionWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 86 | 300B51032640AD22003AD7E8 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; 87 | 300B51052640AD22003AD7E8 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; 88 | 300B51082640AD22003AD7E8 /* WorkoutCompanionWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutCompanionWidget.swift; sourceTree = ""; }; 89 | 300B510A2640AD23003AD7E8 /* WorkoutCompanionWidget.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = WorkoutCompanionWidget.intentdefinition; sourceTree = ""; }; 90 | 300B510B2640AD25003AD7E8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 91 | 300B510D2640AD25003AD7E8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 92 | 300C4EF726395A9F007F60CC /* WeekWorkoutsWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeekWorkoutsWidget.swift; sourceTree = ""; }; 93 | 300C4EFC26395AE9007F60CC /* Date+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+extensions.swift"; sourceTree = ""; }; 94 | 3023F938263D67BE00405A24 /* WorkoutDayModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutDayModel.swift; sourceTree = ""; }; 95 | 3023F93A263D74FE00405A24 /* WeekWorkoutModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeekWorkoutModel.swift; sourceTree = ""; }; 96 | 3023F93E263D79FF00405A24 /* MapWorkoutModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapWorkoutModel.swift; sourceTree = ""; }; 97 | 304917FC26387F7600AB1C1B /* WorkoutManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutManager.swift; sourceTree = ""; }; 98 | 3074ADB1263A0C6400DEB49A /* View+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+extensions.swift"; sourceTree = ""; }; 99 | 307B7A14263AA3D300A7F003 /* MapWorkoutWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapWorkoutWidget.swift; sourceTree = ""; }; 100 | 307B7A16263AACB900A7F003 /* Workout Companion.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Workout Companion.entitlements"; sourceTree = ""; }; 101 | 307B7A18263AACB900A7F003 /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = System/Library/Frameworks/HealthKit.framework; sourceTree = SDKROOT; }; 102 | 307B7A1A263AB7E300A7F003 /* MKCoordinateRegion+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MKCoordinateRegion+extensions.swift"; sourceTree = ""; }; 103 | 3086E432263DDB8B00843006 /* WorkoutWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutWidgets.swift; sourceTree = ""; }; 104 | 3086E434263DDE8600843006 /* MenuModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuModel.swift; sourceTree = ""; }; 105 | 30EBEB4126376F5400A4E9AB /* Workout Companion.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Workout Companion.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 106 | 30EBEB4426376F5400A4E9AB /* Workout_CompanionApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Workout_CompanionApp.swift; sourceTree = ""; }; 107 | 30EBEB4626376F5400A4E9AB /* MenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuView.swift; sourceTree = ""; }; 108 | 30EBEB4826376F5500A4E9AB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 109 | 30EBEB4B26376F5500A4E9AB /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 110 | 30EBEB4D26376F5500A4E9AB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 111 | AC1CB5E22649352000AC3CB6 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 112 | AC1CB5E5264937D500AC3CB6 /* WIP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WIP.swift; sourceTree = ""; }; 113 | AC7186D7264497B50084C84B /* RecentWorkoutsWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentWorkoutsWidgets.swift; sourceTree = ""; }; 114 | AC7186DA26449D5F0084C84B /* HKWorkoutActivityType+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HKWorkoutActivityType+extensions.swift"; sourceTree = ""; }; 115 | AC773E212646C6B600113FB1 /* Array+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+extensions.swift"; sourceTree = ""; }; 116 | ACDD7AA32645D2C000D67F68 /* Text+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Text+extensions.swift"; sourceTree = ""; }; 117 | ACDD7AA62645D38300D67F68 /* WorkoutRowModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkoutRowModel.swift; sourceTree = ""; }; 118 | ACDD7AA92645EF8100D67F68 /* HKWorkout+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HKWorkout+extensions.swift"; sourceTree = ""; }; 119 | /* End PBXFileReference section */ 120 | 121 | /* Begin PBXFrameworksBuildPhase section */ 122 | 300B50FF2640AD22003AD7E8 /* Frameworks */ = { 123 | isa = PBXFrameworksBuildPhase; 124 | buildActionMask = 2147483647; 125 | files = ( 126 | 300B51062640AD22003AD7E8 /* SwiftUI.framework in Frameworks */, 127 | 300B51042640AD22003AD7E8 /* WidgetKit.framework in Frameworks */, 128 | ); 129 | runOnlyForDeploymentPostprocessing = 0; 130 | }; 131 | 30EBEB3E26376F5400A4E9AB /* Frameworks */ = { 132 | isa = PBXFrameworksBuildPhase; 133 | buildActionMask = 2147483647; 134 | files = ( 135 | 3024CDB7263DAF5E004B8648 /* HealthKit.framework in Frameworks */, 136 | ); 137 | runOnlyForDeploymentPostprocessing = 0; 138 | }; 139 | /* End PBXFrameworksBuildPhase section */ 140 | 141 | /* Begin PBXGroup section */ 142 | 300B51072640AD22003AD7E8 /* WorkoutCompanionWidget */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | 300B51082640AD22003AD7E8 /* WorkoutCompanionWidget.swift */, 146 | 300B510A2640AD23003AD7E8 /* WorkoutCompanionWidget.intentdefinition */, 147 | 300B510B2640AD25003AD7E8 /* Assets.xcassets */, 148 | 300B510D2640AD25003AD7E8 /* Info.plist */, 149 | ); 150 | path = WorkoutCompanionWidget; 151 | sourceTree = ""; 152 | }; 153 | 300C4EFB26395AD8007F60CC /* Extensions */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | 300C4EFC26395AE9007F60CC /* Date+extensions.swift */, 157 | 3074ADB1263A0C6400DEB49A /* View+extensions.swift */, 158 | 307B7A1A263AB7E300A7F003 /* MKCoordinateRegion+extensions.swift */, 159 | AC7186DA26449D5F0084C84B /* HKWorkoutActivityType+extensions.swift */, 160 | ACDD7AA32645D2C000D67F68 /* Text+extensions.swift */, 161 | ACDD7AA92645EF8100D67F68 /* HKWorkout+extensions.swift */, 162 | AC773E212646C6B600113FB1 /* Array+extensions.swift */, 163 | ); 164 | path = Extensions; 165 | sourceTree = ""; 166 | }; 167 | 3023F937263D679D00405A24 /* Models */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | 3023F938263D67BE00405A24 /* WorkoutDayModel.swift */, 171 | 3023F93A263D74FE00405A24 /* WeekWorkoutModel.swift */, 172 | 3023F93E263D79FF00405A24 /* MapWorkoutModel.swift */, 173 | ACDD7AA62645D38300D67F68 /* WorkoutRowModel.swift */, 174 | 3086E434263DDE8600843006 /* MenuModel.swift */, 175 | ); 176 | path = Models; 177 | sourceTree = ""; 178 | }; 179 | 3023F93C263D799100405A24 /* Views */ = { 180 | isa = PBXGroup; 181 | children = ( 182 | 30EBEB4626376F5400A4E9AB /* MenuView.swift */, 183 | 3086E432263DDB8B00843006 /* WorkoutWidgets.swift */, 184 | 300C4EF726395A9F007F60CC /* WeekWorkoutsWidget.swift */, 185 | 307B7A14263AA3D300A7F003 /* MapWorkoutWidget.swift */, 186 | AC7186D7264497B50084C84B /* RecentWorkoutsWidgets.swift */, 187 | AC1CB5E5264937D500AC3CB6 /* WIP.swift */, 188 | ); 189 | path = Views; 190 | sourceTree = ""; 191 | }; 192 | 3023F93D263D79A600405A24 /* Data */ = { 193 | isa = PBXGroup; 194 | children = ( 195 | 304917FC26387F7600AB1C1B /* WorkoutManager.swift */, 196 | ); 197 | path = Data; 198 | sourceTree = ""; 199 | }; 200 | 307B7A17263AACB900A7F003 /* Frameworks */ = { 201 | isa = PBXGroup; 202 | children = ( 203 | 307B7A18263AACB900A7F003 /* HealthKit.framework */, 204 | 300B51032640AD22003AD7E8 /* WidgetKit.framework */, 205 | 300B51052640AD22003AD7E8 /* SwiftUI.framework */, 206 | ); 207 | name = Frameworks; 208 | sourceTree = ""; 209 | }; 210 | 30EBEB3826376F5400A4E9AB = { 211 | isa = PBXGroup; 212 | children = ( 213 | 30EBEB4326376F5400A4E9AB /* Workout Companion */, 214 | 300B51072640AD22003AD7E8 /* WorkoutCompanionWidget */, 215 | 30EBEB4226376F5400A4E9AB /* Products */, 216 | 307B7A17263AACB900A7F003 /* Frameworks */, 217 | ); 218 | sourceTree = ""; 219 | }; 220 | 30EBEB4226376F5400A4E9AB /* Products */ = { 221 | isa = PBXGroup; 222 | children = ( 223 | 30EBEB4126376F5400A4E9AB /* Workout Companion.app */, 224 | 300B51022640AD22003AD7E8 /* WorkoutCompanionWidgetExtension.appex */, 225 | ); 226 | name = Products; 227 | sourceTree = ""; 228 | }; 229 | 30EBEB4326376F5400A4E9AB /* Workout Companion */ = { 230 | isa = PBXGroup; 231 | children = ( 232 | 30EBEB4426376F5400A4E9AB /* Workout_CompanionApp.swift */, 233 | 3023F937263D679D00405A24 /* Models */, 234 | 3023F93D263D79A600405A24 /* Data */, 235 | 3023F93C263D799100405A24 /* Views */, 236 | 300C4EFB26395AD8007F60CC /* Extensions */, 237 | 307B7A16263AACB900A7F003 /* Workout Companion.entitlements */, 238 | 30EBEB4826376F5500A4E9AB /* Assets.xcassets */, 239 | 30EBEB4D26376F5500A4E9AB /* Info.plist */, 240 | 30EBEB4A26376F5500A4E9AB /* Preview Content */, 241 | AC1CB5E22649352000AC3CB6 /* Constants.swift */, 242 | ); 243 | path = "Workout Companion"; 244 | sourceTree = ""; 245 | }; 246 | 30EBEB4A26376F5500A4E9AB /* Preview Content */ = { 247 | isa = PBXGroup; 248 | children = ( 249 | 30EBEB4B26376F5500A4E9AB /* Preview Assets.xcassets */, 250 | ); 251 | path = "Preview Content"; 252 | sourceTree = ""; 253 | }; 254 | /* End PBXGroup section */ 255 | 256 | /* Begin PBXNativeTarget section */ 257 | 300B51012640AD22003AD7E8 /* WorkoutCompanionWidgetExtension */ = { 258 | isa = PBXNativeTarget; 259 | buildConfigurationList = 300B51162640AD25003AD7E8 /* Build configuration list for PBXNativeTarget "WorkoutCompanionWidgetExtension" */; 260 | buildPhases = ( 261 | 300B50FE2640AD22003AD7E8 /* Sources */, 262 | 300B50FF2640AD22003AD7E8 /* Frameworks */, 263 | 300B51002640AD22003AD7E8 /* Resources */, 264 | ); 265 | buildRules = ( 266 | ); 267 | dependencies = ( 268 | ); 269 | name = WorkoutCompanionWidgetExtension; 270 | productName = WorkoutCompanionWidgetExtension; 271 | productReference = 300B51022640AD22003AD7E8 /* WorkoutCompanionWidgetExtension.appex */; 272 | productType = "com.apple.product-type.app-extension"; 273 | }; 274 | 30EBEB4026376F5400A4E9AB /* Workout Companion */ = { 275 | isa = PBXNativeTarget; 276 | buildConfigurationList = 30EBEB5026376F5500A4E9AB /* Build configuration list for PBXNativeTarget "Workout Companion" */; 277 | buildPhases = ( 278 | 30EBEB3D26376F5400A4E9AB /* Sources */, 279 | 30EBEB3E26376F5400A4E9AB /* Frameworks */, 280 | 30EBEB3F26376F5400A4E9AB /* Resources */, 281 | 300B51132640AD25003AD7E8 /* Embed App Extensions */, 282 | ); 283 | buildRules = ( 284 | ); 285 | dependencies = ( 286 | 300B51112640AD25003AD7E8 /* PBXTargetDependency */, 287 | ); 288 | name = "Workout Companion"; 289 | productName = "Workout Companion"; 290 | productReference = 30EBEB4126376F5400A4E9AB /* Workout Companion.app */; 291 | productType = "com.apple.product-type.application"; 292 | }; 293 | /* End PBXNativeTarget section */ 294 | 295 | /* Begin PBXProject section */ 296 | 30EBEB3926376F5400A4E9AB /* Project object */ = { 297 | isa = PBXProject; 298 | attributes = { 299 | LastSwiftUpdateCheck = 1250; 300 | LastUpgradeCheck = 1240; 301 | TargetAttributes = { 302 | 300B51012640AD22003AD7E8 = { 303 | CreatedOnToolsVersion = 12.5; 304 | }; 305 | 30EBEB4026376F5400A4E9AB = { 306 | CreatedOnToolsVersion = 12.4; 307 | }; 308 | }; 309 | }; 310 | buildConfigurationList = 30EBEB3C26376F5400A4E9AB /* Build configuration list for PBXProject "Workout Companion" */; 311 | compatibilityVersion = "Xcode 9.3"; 312 | developmentRegion = en; 313 | hasScannedForEncodings = 0; 314 | knownRegions = ( 315 | en, 316 | Base, 317 | ); 318 | mainGroup = 30EBEB3826376F5400A4E9AB; 319 | productRefGroup = 30EBEB4226376F5400A4E9AB /* Products */; 320 | projectDirPath = ""; 321 | projectRoot = ""; 322 | targets = ( 323 | 30EBEB4026376F5400A4E9AB /* Workout Companion */, 324 | 300B51012640AD22003AD7E8 /* WorkoutCompanionWidgetExtension */, 325 | ); 326 | }; 327 | /* End PBXProject section */ 328 | 329 | /* Begin PBXResourcesBuildPhase section */ 330 | 300B51002640AD22003AD7E8 /* Resources */ = { 331 | isa = PBXResourcesBuildPhase; 332 | buildActionMask = 2147483647; 333 | files = ( 334 | 300B511E2640B27E003AD7E8 /* Assets.xcassets in Resources */, 335 | 300B510C2640AD25003AD7E8 /* Assets.xcassets in Resources */, 336 | ); 337 | runOnlyForDeploymentPostprocessing = 0; 338 | }; 339 | 30EBEB3F26376F5400A4E9AB /* Resources */ = { 340 | isa = PBXResourcesBuildPhase; 341 | buildActionMask = 2147483647; 342 | files = ( 343 | 30EBEB4C26376F5500A4E9AB /* Preview Assets.xcassets in Resources */, 344 | 30EBEB4926376F5500A4E9AB /* Assets.xcassets in Resources */, 345 | ); 346 | runOnlyForDeploymentPostprocessing = 0; 347 | }; 348 | /* End PBXResourcesBuildPhase section */ 349 | 350 | /* Begin PBXSourcesBuildPhase section */ 351 | 300B50FE2640AD22003AD7E8 /* Sources */ = { 352 | isa = PBXSourcesBuildPhase; 353 | buildActionMask = 2147483647; 354 | files = ( 355 | 300B51182640AFB4003AD7E8 /* WeekWorkoutModel.swift in Sources */, 356 | AC1CB5E42649352000AC3CB6 /* Constants.swift in Sources */, 357 | AC7186D9264497B50084C84B /* RecentWorkoutsWidgets.swift in Sources */, 358 | 300B511B2640B01A003AD7E8 /* MKCoordinateRegion+extensions.swift in Sources */, 359 | 300B51092640AD22003AD7E8 /* WorkoutCompanionWidget.swift in Sources */, 360 | 300B511D2640B01A003AD7E8 /* Date+extensions.swift in Sources */, 361 | AC7186DC26449D5F0084C84B /* HKWorkoutActivityType+extensions.swift in Sources */, 362 | 300B511A2640AFB4003AD7E8 /* WorkoutDayModel.swift in Sources */, 363 | 300B510E2640AD25003AD7E8 /* WorkoutCompanionWidget.intentdefinition in Sources */, 364 | 300B51192640AFB4003AD7E8 /* MapWorkoutModel.swift in Sources */, 365 | ACDD7AAB2645EF8100D67F68 /* HKWorkout+extensions.swift in Sources */, 366 | ACDD7AA52645D2C300D67F68 /* Text+extensions.swift in Sources */, 367 | AC773E232646C6B600113FB1 /* Array+extensions.swift in Sources */, 368 | ACDD7AA82645D38300D67F68 /* WorkoutRowModel.swift in Sources */, 369 | 304A9CD22640B6CE00E883F8 /* WorkoutManager.swift in Sources */, 370 | 300B511C2640B01A003AD7E8 /* View+extensions.swift in Sources */, 371 | AC1CB5E7264937D500AC3CB6 /* WIP.swift in Sources */, 372 | 300B51172640AF9E003AD7E8 /* WeekWorkoutsWidget.swift in Sources */, 373 | ); 374 | runOnlyForDeploymentPostprocessing = 0; 375 | }; 376 | 30EBEB3D26376F5400A4E9AB /* Sources */ = { 377 | isa = PBXSourcesBuildPhase; 378 | buildActionMask = 2147483647; 379 | files = ( 380 | AC1CB5E6264937D500AC3CB6 /* WIP.swift in Sources */, 381 | 300C4EFD26395AE9007F60CC /* Date+extensions.swift in Sources */, 382 | ACDD7AAA2645EF8100D67F68 /* HKWorkout+extensions.swift in Sources */, 383 | 3023F93B263D74FE00405A24 /* WeekWorkoutModel.swift in Sources */, 384 | 30EBEB4726376F5400A4E9AB /* MenuView.swift in Sources */, 385 | 3023F939263D67BE00405A24 /* WorkoutDayModel.swift in Sources */, 386 | 307B7A15263AA3D300A7F003 /* MapWorkoutWidget.swift in Sources */, 387 | 300C4EF826395A9F007F60CC /* WeekWorkoutsWidget.swift in Sources */, 388 | AC7186D8264497B50084C84B /* RecentWorkoutsWidgets.swift in Sources */, 389 | ACDD7AA42645D2C000D67F68 /* Text+extensions.swift in Sources */, 390 | 30EBEB4526376F5400A4E9AB /* Workout_CompanionApp.swift in Sources */, 391 | 307B7A1B263AB7E300A7F003 /* MKCoordinateRegion+extensions.swift in Sources */, 392 | AC1CB5E32649352000AC3CB6 /* Constants.swift in Sources */, 393 | AC7186DB26449D5F0084C84B /* HKWorkoutActivityType+extensions.swift in Sources */, 394 | 3023F93F263D79FF00405A24 /* MapWorkoutModel.swift in Sources */, 395 | 3086E435263DDE8600843006 /* MenuModel.swift in Sources */, 396 | 3086E433263DDB8B00843006 /* WorkoutWidgets.swift in Sources */, 397 | ACDD7AA72645D38300D67F68 /* WorkoutRowModel.swift in Sources */, 398 | 3074ADB2263A0C6400DEB49A /* View+extensions.swift in Sources */, 399 | 304917FD26387F7600AB1C1B /* WorkoutManager.swift in Sources */, 400 | 300B510F2640AD25003AD7E8 /* WorkoutCompanionWidget.intentdefinition in Sources */, 401 | AC773E222646C6B600113FB1 /* Array+extensions.swift in Sources */, 402 | ); 403 | runOnlyForDeploymentPostprocessing = 0; 404 | }; 405 | /* End PBXSourcesBuildPhase section */ 406 | 407 | /* Begin PBXTargetDependency section */ 408 | 300B51112640AD25003AD7E8 /* PBXTargetDependency */ = { 409 | isa = PBXTargetDependency; 410 | target = 300B51012640AD22003AD7E8 /* WorkoutCompanionWidgetExtension */; 411 | targetProxy = 300B51102640AD25003AD7E8 /* PBXContainerItemProxy */; 412 | }; 413 | /* End PBXTargetDependency section */ 414 | 415 | /* Begin XCBuildConfiguration section */ 416 | 300B51142640AD25003AD7E8 /* Debug */ = { 417 | isa = XCBuildConfiguration; 418 | buildSettings = { 419 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 420 | ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; 421 | CODE_SIGN_IDENTITY = "Apple Development"; 422 | CODE_SIGN_STYLE = Automatic; 423 | DEVELOPMENT_TEAM = 4WZFDA97N8; 424 | INFOPLIST_FILE = WorkoutCompanionWidget/Info.plist; 425 | IPHONEOS_DEPLOYMENT_TARGET = 14.1; 426 | LD_RUNPATH_SEARCH_PATHS = ( 427 | "$(inherited)", 428 | "@executable_path/Frameworks", 429 | "@executable_path/../../Frameworks", 430 | ); 431 | PRODUCT_BUNDLE_IDENTIFIER = "MEZ.Workout-Companion.WorkoutCompanionWidget"; 432 | PRODUCT_NAME = "$(TARGET_NAME)"; 433 | PROVISIONING_PROFILE_SPECIFIER = ""; 434 | SKIP_INSTALL = YES; 435 | SWIFT_VERSION = 5.0; 436 | TARGETED_DEVICE_FAMILY = "1,2"; 437 | }; 438 | name = Debug; 439 | }; 440 | 300B51152640AD25003AD7E8 /* Release */ = { 441 | isa = XCBuildConfiguration; 442 | buildSettings = { 443 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 444 | ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; 445 | CODE_SIGN_IDENTITY = "Apple Development"; 446 | CODE_SIGN_STYLE = Automatic; 447 | DEVELOPMENT_TEAM = 4WZFDA97N8; 448 | INFOPLIST_FILE = WorkoutCompanionWidget/Info.plist; 449 | IPHONEOS_DEPLOYMENT_TARGET = 14.1; 450 | LD_RUNPATH_SEARCH_PATHS = ( 451 | "$(inherited)", 452 | "@executable_path/Frameworks", 453 | "@executable_path/../../Frameworks", 454 | ); 455 | PRODUCT_BUNDLE_IDENTIFIER = "MEZ.Workout-Companion.WorkoutCompanionWidget"; 456 | PRODUCT_NAME = "$(TARGET_NAME)"; 457 | PROVISIONING_PROFILE_SPECIFIER = ""; 458 | SKIP_INSTALL = YES; 459 | SWIFT_VERSION = 5.0; 460 | TARGETED_DEVICE_FAMILY = "1,2"; 461 | }; 462 | name = Release; 463 | }; 464 | 30EBEB4E26376F5500A4E9AB /* Debug */ = { 465 | isa = XCBuildConfiguration; 466 | buildSettings = { 467 | ALWAYS_SEARCH_USER_PATHS = NO; 468 | CLANG_ANALYZER_NONNULL = YES; 469 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 470 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 471 | CLANG_CXX_LIBRARY = "libc++"; 472 | CLANG_ENABLE_MODULES = YES; 473 | CLANG_ENABLE_OBJC_ARC = YES; 474 | CLANG_ENABLE_OBJC_WEAK = YES; 475 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 476 | CLANG_WARN_BOOL_CONVERSION = YES; 477 | CLANG_WARN_COMMA = YES; 478 | CLANG_WARN_CONSTANT_CONVERSION = YES; 479 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 480 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 481 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 482 | CLANG_WARN_EMPTY_BODY = YES; 483 | CLANG_WARN_ENUM_CONVERSION = YES; 484 | CLANG_WARN_INFINITE_RECURSION = YES; 485 | CLANG_WARN_INT_CONVERSION = YES; 486 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 487 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 488 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 489 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 490 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 491 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 492 | CLANG_WARN_STRICT_PROTOTYPES = YES; 493 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 494 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 495 | CLANG_WARN_UNREACHABLE_CODE = YES; 496 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 497 | COPY_PHASE_STRIP = NO; 498 | DEBUG_INFORMATION_FORMAT = dwarf; 499 | ENABLE_STRICT_OBJC_MSGSEND = YES; 500 | ENABLE_TESTABILITY = YES; 501 | GCC_C_LANGUAGE_STANDARD = gnu11; 502 | GCC_DYNAMIC_NO_PIC = NO; 503 | GCC_NO_COMMON_BLOCKS = YES; 504 | GCC_OPTIMIZATION_LEVEL = 0; 505 | GCC_PREPROCESSOR_DEFINITIONS = ( 506 | "DEBUG=1", 507 | "$(inherited)", 508 | ); 509 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 510 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 511 | GCC_WARN_UNDECLARED_SELECTOR = YES; 512 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 513 | GCC_WARN_UNUSED_FUNCTION = YES; 514 | GCC_WARN_UNUSED_VARIABLE = YES; 515 | IPHONEOS_DEPLOYMENT_TARGET = 14.4; 516 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 517 | MTL_FAST_MATH = YES; 518 | ONLY_ACTIVE_ARCH = YES; 519 | SDKROOT = iphoneos; 520 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 521 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 522 | }; 523 | name = Debug; 524 | }; 525 | 30EBEB4F26376F5500A4E9AB /* Release */ = { 526 | isa = XCBuildConfiguration; 527 | buildSettings = { 528 | ALWAYS_SEARCH_USER_PATHS = NO; 529 | CLANG_ANALYZER_NONNULL = YES; 530 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 531 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 532 | CLANG_CXX_LIBRARY = "libc++"; 533 | CLANG_ENABLE_MODULES = YES; 534 | CLANG_ENABLE_OBJC_ARC = YES; 535 | CLANG_ENABLE_OBJC_WEAK = YES; 536 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 537 | CLANG_WARN_BOOL_CONVERSION = YES; 538 | CLANG_WARN_COMMA = YES; 539 | CLANG_WARN_CONSTANT_CONVERSION = YES; 540 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 541 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 542 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 543 | CLANG_WARN_EMPTY_BODY = YES; 544 | CLANG_WARN_ENUM_CONVERSION = YES; 545 | CLANG_WARN_INFINITE_RECURSION = YES; 546 | CLANG_WARN_INT_CONVERSION = YES; 547 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 548 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 549 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 550 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 551 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 552 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 553 | CLANG_WARN_STRICT_PROTOTYPES = YES; 554 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 555 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 556 | CLANG_WARN_UNREACHABLE_CODE = YES; 557 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 558 | COPY_PHASE_STRIP = NO; 559 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 560 | ENABLE_NS_ASSERTIONS = NO; 561 | ENABLE_STRICT_OBJC_MSGSEND = YES; 562 | GCC_C_LANGUAGE_STANDARD = gnu11; 563 | GCC_NO_COMMON_BLOCKS = YES; 564 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 565 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 566 | GCC_WARN_UNDECLARED_SELECTOR = YES; 567 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 568 | GCC_WARN_UNUSED_FUNCTION = YES; 569 | GCC_WARN_UNUSED_VARIABLE = YES; 570 | IPHONEOS_DEPLOYMENT_TARGET = 14.4; 571 | MTL_ENABLE_DEBUG_INFO = NO; 572 | MTL_FAST_MATH = YES; 573 | SDKROOT = iphoneos; 574 | SWIFT_COMPILATION_MODE = wholemodule; 575 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 576 | VALIDATE_PRODUCT = YES; 577 | }; 578 | name = Release; 579 | }; 580 | 30EBEB5126376F5500A4E9AB /* Debug */ = { 581 | isa = XCBuildConfiguration; 582 | buildSettings = { 583 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 584 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 585 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 586 | CODE_SIGN_ENTITLEMENTS = "Workout Companion/Workout Companion.entitlements"; 587 | CODE_SIGN_IDENTITY = "Apple Development"; 588 | CODE_SIGN_STYLE = Automatic; 589 | DEVELOPMENT_ASSET_PATHS = "\"Workout Companion/Preview Content\""; 590 | DEVELOPMENT_TEAM = 4WZFDA97N8; 591 | ENABLE_PREVIEWS = YES; 592 | INFOPLIST_FILE = "Workout Companion/Info.plist"; 593 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 594 | LD_RUNPATH_SEARCH_PATHS = ( 595 | "$(inherited)", 596 | "@executable_path/Frameworks", 597 | ); 598 | PRODUCT_BUNDLE_IDENTIFIER = "MEZ.Workout-Companion"; 599 | PRODUCT_NAME = "$(TARGET_NAME)"; 600 | PROVISIONING_PROFILE_SPECIFIER = ""; 601 | SWIFT_VERSION = 5.0; 602 | TARGETED_DEVICE_FAMILY = 1; 603 | }; 604 | name = Debug; 605 | }; 606 | 30EBEB5226376F5500A4E9AB /* Release */ = { 607 | isa = XCBuildConfiguration; 608 | buildSettings = { 609 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 610 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 611 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 612 | CODE_SIGN_ENTITLEMENTS = "Workout Companion/Workout Companion.entitlements"; 613 | CODE_SIGN_IDENTITY = "Apple Development"; 614 | CODE_SIGN_STYLE = Automatic; 615 | DEVELOPMENT_ASSET_PATHS = "\"Workout Companion/Preview Content\""; 616 | DEVELOPMENT_TEAM = 4WZFDA97N8; 617 | ENABLE_PREVIEWS = YES; 618 | INFOPLIST_FILE = "Workout Companion/Info.plist"; 619 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 620 | LD_RUNPATH_SEARCH_PATHS = ( 621 | "$(inherited)", 622 | "@executable_path/Frameworks", 623 | ); 624 | PRODUCT_BUNDLE_IDENTIFIER = "MEZ.Workout-Companion"; 625 | PRODUCT_NAME = "$(TARGET_NAME)"; 626 | PROVISIONING_PROFILE_SPECIFIER = ""; 627 | SWIFT_VERSION = 5.0; 628 | TARGETED_DEVICE_FAMILY = 1; 629 | }; 630 | name = Release; 631 | }; 632 | /* End XCBuildConfiguration section */ 633 | 634 | /* Begin XCConfigurationList section */ 635 | 300B51162640AD25003AD7E8 /* Build configuration list for PBXNativeTarget "WorkoutCompanionWidgetExtension" */ = { 636 | isa = XCConfigurationList; 637 | buildConfigurations = ( 638 | 300B51142640AD25003AD7E8 /* Debug */, 639 | 300B51152640AD25003AD7E8 /* Release */, 640 | ); 641 | defaultConfigurationIsVisible = 0; 642 | defaultConfigurationName = Release; 643 | }; 644 | 30EBEB3C26376F5400A4E9AB /* Build configuration list for PBXProject "Workout Companion" */ = { 645 | isa = XCConfigurationList; 646 | buildConfigurations = ( 647 | 30EBEB4E26376F5500A4E9AB /* Debug */, 648 | 30EBEB4F26376F5500A4E9AB /* Release */, 649 | ); 650 | defaultConfigurationIsVisible = 0; 651 | defaultConfigurationName = Release; 652 | }; 653 | 30EBEB5026376F5500A4E9AB /* Build configuration list for PBXNativeTarget "Workout Companion" */ = { 654 | isa = XCConfigurationList; 655 | buildConfigurations = ( 656 | 30EBEB5126376F5500A4E9AB /* Debug */, 657 | 30EBEB5226376F5500A4E9AB /* Release */, 658 | ); 659 | defaultConfigurationIsVisible = 0; 660 | defaultConfigurationName = Release; 661 | }; 662 | /* End XCConfigurationList section */ 663 | }; 664 | rootObject = 30EBEB3926376F5400A4E9AB /* Project object */; 665 | } 666 | -------------------------------------------------------------------------------- /Workout Companion.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Workout Companion.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Workout Companion.xcodeproj/xcshareddata/xcschemes/Workout Companion.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Workout Companion.xcodeproj/xcshareddata/xcschemes/WorkoutCompanionWidgetExtension.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 16 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 45 | 46 | 47 | 48 | 60 | 63 | 69 | 70 | 71 | 72 | 78 | 79 | 80 | 81 | 85 | 86 | 90 | 91 | 95 | 96 | 97 | 98 | 105 | 107 | 113 | 114 | 115 | 116 | 118 | 119 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /Workout Companion.xcodeproj/xcuserdata/marioeguiluz.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Workout Companion.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | WorkoutCompanionWidgetExtension.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | 300B51012640AD22003AD7E8 21 | 22 | primary 23 | 24 | 25 | 30EBEB4026376F5400A4E9AB 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/128.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/16.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/172.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/172.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/196.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/216.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/216.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/256.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/32.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/48.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/512.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/55.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/64.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/88.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/AppIcon.appiconset/88.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | {"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"idiom":"watch","filename":"172.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"86x86","expected-size":"172","role":"quickLook"},{"idiom":"watch","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"40x40","expected-size":"80","role":"appLauncher"},{"idiom":"watch","filename":"88.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"40mm","scale":"2x","size":"44x44","expected-size":"88","role":"appLauncher"},{"idiom":"watch","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"44mm","scale":"2x","size":"50x50","expected-size":"100","role":"appLauncher"},{"idiom":"watch","filename":"196.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"42mm","scale":"2x","size":"98x98","expected-size":"196","role":"quickLook"},{"idiom":"watch","filename":"216.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"44mm","scale":"2x","size":"108x108","expected-size":"216","role":"quickLook"},{"idiom":"watch","filename":"48.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"24x24","expected-size":"48","role":"notificationCenter"},{"idiom":"watch","filename":"55.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"42mm","scale":"2x","size":"27.5x27.5","expected-size":"55","role":"notificationCenter"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch","role":"companionSettings","scale":"3x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch","role":"companionSettings","scale":"2x"},{"size":"1024x1024","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch-marketing","scale":"1x"},{"size":"128x128","expected-size":"128","filename":"128.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"256x256","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"128x128","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"256x256","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"512x512","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"16","filename":"16.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"64","filename":"64.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"512x512","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"}]} -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/Colors/Card.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.114", 9 | "green" : "0.106", 10 | "red" : "0.110" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.114", 27 | "green" : "0.106", 28 | "red" : "0.110" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/Colors/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/Colors/Main.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.075", 9 | "green" : "0.345", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.075", 27 | "green" : "0.345", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/Colors/MainHighlight.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.471", 9 | "green" : "0.745", 10 | "red" : "0.988" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.471", 27 | "green" : "0.745", 28 | "red" : "0.988" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/Workouts/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/Workouts/cycling.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "cycling.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/Workouts/cycling.imageset/cycling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/Workouts/cycling.imageset/cycling.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/Workouts/hiit.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "hiit.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/Workouts/hiit.imageset/hiit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/Workouts/hiit.imageset/hiit.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/Workouts/other.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "other.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/Workouts/other.imageset/other.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/Workouts/other.imageset/other.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/Workouts/running.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "running.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/Workouts/running.imageset/running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/Workouts/running.imageset/running.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/Workouts/strength.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "strength.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/Workouts/strength.imageset/strength.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/Workouts/strength.imageset/strength.png -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/Workouts/walking.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "walking.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Workout Companion/Assets.xcassets/Workouts/walking.imageset/walking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioeguiluz/swiftui-widgets-fitness/859a193063defab52132dfdd2f5e0b6cc634a38e/Workout Companion/Assets.xcassets/Workouts/walking.imageset/walking.png -------------------------------------------------------------------------------- /Workout Companion/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // Workout Companion 4 | // 5 | // Created by Mario Eguiluz on 10/05/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct Constants { 11 | static let widgetMediumHeight: CGFloat = 169 12 | static let widgetLargeHeight: CGFloat = 376 13 | } 14 | -------------------------------------------------------------------------------- /Workout Companion/Data/WorkoutManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WorkoutManager.swift 3 | // Workout Companion 4 | // 5 | // Created by Mario Eguiluz on 27/04/2021. 6 | // 7 | 8 | import Foundation 9 | import HealthKit 10 | import Combine 11 | import MapKit 12 | 13 | 14 | class WorkoutManager: NSObject, ObservableObject { 15 | 16 | @Published var weekWorkoutModel = WeekWorkoutModel(workouts: []) 17 | @Published var mapWorkoutModel: MapWorkoutModel? = nil 18 | @Published var recentWorkouts: [HKWorkout] = [] 19 | 20 | private var healthStore: HKHealthStore? 21 | 22 | init( 23 | weekWorkoutModel: WeekWorkoutModel = WeekWorkoutModel(workouts: []), 24 | mapWorkoutModel: MapWorkoutModel? = nil, 25 | recentWorkouts: [HKWorkout] = []) { 26 | 27 | self.weekWorkoutModel = weekWorkoutModel 28 | self.mapWorkoutModel = mapWorkoutModel 29 | self.recentWorkouts = recentWorkouts 30 | 31 | if HKHealthStore.isHealthDataAvailable() { 32 | healthStore = HKHealthStore() 33 | } 34 | } 35 | 36 | func requestAuthorization(onSuccess: @escaping () -> Void, onError: @escaping (Error?) -> Void) { 37 | if HKHealthStore.isHealthDataAvailable() { 38 | let typesToRead: Set = [ 39 | HKObjectType.workoutType(), 40 | HKSeriesType.workoutRoute(), 41 | HKQuantityType.quantityType(forIdentifier: .heartRate)!, 42 | HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned)!, 43 | HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning)! 44 | ] 45 | healthStore?.requestAuthorization(toShare: nil, read: typesToRead) { (result, error) in 46 | if let error = error { 47 | onError(error) 48 | return 49 | } 50 | guard result else { 51 | onError(nil) 52 | return 53 | } 54 | onSuccess() 55 | } 56 | } 57 | } 58 | 59 | func loadWorkoutData() { 60 | latestMapWorkout() 61 | latestWorkoutWeekDays() 62 | latestWorkouts() 63 | } 64 | 65 | // LAST WEEK WORKOUTS 66 | 67 | func latestWorkoutWeekDays(completion: ((WeekWorkoutModel) -> Void)? = nil) { 68 | let end = Date() 69 | let start = Calendar.current.date(byAdding: .day, value: -7, to: end)! 70 | let workoutPredicate = HKQuery.predicateForWorkouts(with: .greaterThanOrEqualTo, duration: 1) 71 | let datePredicate = HKQuery.predicateForSamples(withStart: start, end: end, options: []) 72 | let compound = NSCompoundPredicate(andPredicateWithSubpredicates:[workoutPredicate, datePredicate]) 73 | let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false) 74 | 75 | let query = HKSampleQuery( 76 | sampleType: .workoutType(), 77 | predicate: compound, 78 | limit: 0, 79 | sortDescriptors: [sortDescriptor]) { (query, samples, error) in 80 | DispatchQueue.main.async { 81 | guard let samples = samples as? [HKWorkout], error == nil else { 82 | let result = WeekWorkoutModel(workouts: []) 83 | self.weekWorkoutModel = result 84 | completion?(result) 85 | return 86 | } 87 | let result = WeekWorkoutModel(workouts: samples) 88 | self.weekWorkoutModel = result 89 | completion?(result) 90 | } 91 | } 92 | 93 | healthStore?.execute(query) 94 | } 95 | 96 | // MAP WORKOUT 97 | 98 | func latestMapWorkout() { 99 | let walkingPredicate = HKQuery.predicateForWorkouts(with: .walking) 100 | let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false) 101 | let query = HKSampleQuery(sampleType: .workoutType(), predicate: walkingPredicate, limit: 5, sortDescriptors: [sortDescriptor]) { (query, samples, error) in 102 | DispatchQueue.main.async { 103 | guard let samples = samples as? [HKWorkout], error == nil, let mapWorkout = samples.first else { 104 | self.mapWorkoutModel = nil 105 | return 106 | } 107 | self.publishMapWorkout(workout: mapWorkout) 108 | } 109 | } 110 | healthStore?.execute(query) 111 | } 112 | 113 | private func publishMapWorkout(workout: HKWorkout) { 114 | let workoutRouteType = HKSeriesType.workoutRoute() 115 | let workoutPredicate = HKQuery.predicateForObjects(from: workout) 116 | let workoutRoutesQuery = HKSampleQuery(sampleType: workoutRouteType, predicate: workoutPredicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { (query, samples, error) in 117 | 118 | guard let routeSamples = samples as? [HKWorkoutRoute] else { return } 119 | var accumulator: [CLLocation] = [] 120 | for routeSample in routeSamples { 121 | let locationQuery = HKWorkoutRouteQuery(route: routeSample) { (routeQuery, locations, done, error) in 122 | if let locations = locations { 123 | accumulator.append(contentsOf: locations) 124 | if done { 125 | let coordinates2D = accumulator.map { $0.coordinate } 126 | let region = MKCoordinateRegion(coordinates: coordinates2D) 127 | DispatchQueue.main.async { 128 | self.mapWorkoutModel = MapWorkoutModel(workout: workout, region: region, coordinates: coordinates2D) 129 | } 130 | } 131 | } 132 | } 133 | self.healthStore?.execute(locationQuery) 134 | } 135 | } 136 | healthStore?.execute(workoutRoutesQuery) 137 | } 138 | 139 | // RECENT WORKOUTS 140 | 141 | func latestWorkouts(completion: (([HKWorkout]) -> Void)? = nil) { 142 | let end = Date() 143 | let start = Calendar.current.date(byAdding: .day, value: -7, to: end)! 144 | let workoutPredicate = HKQuery.predicateForWorkouts(with: .greaterThanOrEqualTo, duration: 1) 145 | let datePredicate = HKQuery.predicateForSamples(withStart: start, end: end, options: []) 146 | let compound = NSCompoundPredicate(andPredicateWithSubpredicates:[workoutPredicate, datePredicate]) 147 | let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false) 148 | 149 | let query = HKSampleQuery( 150 | sampleType: .workoutType(), 151 | predicate: compound, 152 | limit: 0, 153 | sortDescriptors: [sortDescriptor]) { (query, samples, error) in 154 | DispatchQueue.main.async { 155 | guard let samples = samples as? [HKWorkout], error == nil else { 156 | self.recentWorkouts = [] 157 | completion?([]) 158 | return 159 | } 160 | self.recentWorkouts = samples 161 | completion?(samples) 162 | } 163 | } 164 | 165 | healthStore?.execute(query) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /Workout Companion/Extensions/Array+extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+extensions.swift 3 | // Workout Companion 4 | // 5 | // Created by Mario Eguiluz on 08/05/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Array { 11 | func batched(into size: Int) -> [[Element]] { 12 | return stride(from: 0, to: count, by: size).map { 13 | Array(self[$0 ..< Swift.min($0 + size, count)]) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Workout Companion/Extensions/Date+extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date+extensions.swift 3 | // Workout Companion 4 | // 5 | // Created by Mario Eguiluz on 28/04/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Date { 11 | 12 | func weekday() -> String { 13 | let weekDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] 14 | let myCalendar = Calendar(identifier: .gregorian) 15 | let weekDay = myCalendar.component(.weekday, from: self) 16 | return String(weekDays[weekDay-1].prefix(3)) 17 | } 18 | 19 | func day() -> Int { 20 | let myCalendar = Calendar(identifier: .gregorian) 21 | return myCalendar.component(.day, from: self) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Workout Companion/Extensions/HKWorkout+extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HKWorkout+extensions.swift 3 | // Workout Companion 4 | // 5 | // Created by Mario Eguiluz on 07/05/2021. 6 | // 7 | 8 | import HealthKit 9 | 10 | extension HKWorkout { 11 | static var data = [ 12 | generateWorkoutWithActivity(daysAgo: 0, activityType: .walking), 13 | generateWorkoutWithActivity(daysAgo: 0, activityType: .traditionalStrengthTraining), 14 | generateWorkoutWithActivity(daysAgo: 1, activityType: .tennis), 15 | generateWorkoutWithActivity(daysAgo: 3, activityType: .running) 16 | ] 17 | 18 | private static func generateWorkoutWithActivity(daysAgo: Int, activityType: HKWorkoutActivityType) -> HKWorkout { 19 | let totalEnergyBurned = Double.random(in: 350..<651) 20 | let totalDistance = Double.random(in: 1500..<6555) 21 | let duration = Double.random(in: 3500..<7500) 22 | let today = Date() 23 | let start = Calendar.current.date(byAdding: .day, value: -daysAgo, to: today)! 24 | let end = Calendar.current.date(byAdding: .minute, value: 60, to: start)! 25 | let workout = HKWorkout( 26 | activityType: activityType, 27 | start: start, 28 | end: end, 29 | duration: duration, 30 | totalEnergyBurned: HKQuantity(unit: .kilocalorie(), doubleValue: totalEnergyBurned), 31 | totalDistance: HKQuantity(unit: .meter(), doubleValue: totalDistance), 32 | device: nil, metadata: nil) 33 | return workout 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Workout Companion/Extensions/HKWorkoutActivityType+extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HKWorkoutActivityType+Descriptions.swift 3 | // George Green 4 | // 5 | // Created by George Green on 02/03/2020. 6 | // Copyright © 2020 George Green of London. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import HealthKit 11 | 12 | extension HKWorkoutActivityType { 13 | 14 | /* 15 | Simple mapping of available workout types to a human readable name. 16 | */ 17 | var name: String { 18 | switch self { 19 | case .americanFootball: return "American Football" 20 | case .archery: return "Archery" 21 | case .australianFootball: return "Australian Football" 22 | case .badminton: return "Badminton" 23 | case .baseball: return "Baseball" 24 | case .basketball: return "Basketball" 25 | case .bowling: return "Bowling" 26 | case .boxing: return "Boxing" 27 | case .climbing: return "Climbing" 28 | case .crossTraining: return "Cross Training" 29 | case .curling: return "Curling" 30 | case .cycling: return "Cycling" 31 | case .dance: return "Dance" 32 | case .danceInspiredTraining: return "Dance Inspired Training" 33 | case .elliptical: return "Elliptical" 34 | case .equestrianSports: return "Equestrian Sports" 35 | case .fencing: return "Fencing" 36 | case .fishing: return "Fishing" 37 | case .functionalStrengthTraining: return "Functional Strength Training" 38 | case .golf: return "Golf" 39 | case .gymnastics: return "Gymnastics" 40 | case .handball: return "Handball" 41 | case .hiking: return "Hiking" 42 | case .hockey: return "Hockey" 43 | case .hunting: return "Hunting" 44 | case .lacrosse: return "Lacrosse" 45 | case .martialArts: return "Martial Arts" 46 | case .mindAndBody: return "Mind and Body" 47 | case .mixedMetabolicCardioTraining: return "Mixed Metabolic Cardio Training" 48 | case .paddleSports: return "Paddle Sports" 49 | case .play: return "Play" 50 | case .preparationAndRecovery: return "Preparation and Recovery" 51 | case .racquetball: return "Racquetball" 52 | case .rowing: return "Rowing" 53 | case .rugby: return "Rugby" 54 | case .running: return "Running" 55 | case .sailing: return "Sailing" 56 | case .skatingSports: return "Skating Sports" 57 | case .snowSports: return "Snow Sports" 58 | case .soccer: return "Soccer" 59 | case .softball: return "Softball" 60 | case .squash: return "Squash" 61 | case .stairClimbing: return "Stair Climbing" 62 | case .surfingSports: return "Surfing Sports" 63 | case .swimming: return "Swimming" 64 | case .tableTennis: return "Table Tennis" 65 | case .tennis: return "Tennis" 66 | case .trackAndField: return "Track and Field" 67 | case .traditionalStrengthTraining: return "Traditional Strength Training" 68 | case .volleyball: return "Volleyball" 69 | case .walking: return "Walking" 70 | case .waterFitness: return "Water Fitness" 71 | case .waterPolo: return "Water Polo" 72 | case .waterSports: return "Water Sports" 73 | case .wrestling: return "Wrestling" 74 | case .yoga: return "Yoga" 75 | 76 | // iOS 10 77 | case .barre: return "Barre" 78 | case .coreTraining: return "Core Training" 79 | case .crossCountrySkiing: return "Cross Country Skiing" 80 | case .downhillSkiing: return "Downhill Skiing" 81 | case .flexibility: return "Flexibility" 82 | case .highIntensityIntervalTraining: return "High Intensity Interval Training" 83 | case .jumpRope: return "Jump Rope" 84 | case .kickboxing: return "Kickboxing" 85 | case .pilates: return "Pilates" 86 | case .snowboarding: return "Snowboarding" 87 | case .stairs: return "Stairs" 88 | case .stepTraining: return "Step Training" 89 | case .wheelchairWalkPace: return "Wheelchair Walk Pace" 90 | case .wheelchairRunPace: return "Wheelchair Run Pace" 91 | 92 | // iOS 11 93 | case .taiChi: return "Tai Chi" 94 | case .mixedCardio: return "Mixed Cardio" 95 | case .handCycling: return "Hand Cycling" 96 | 97 | // iOS 13 98 | case .discSports: return "Disc Sports" 99 | case .fitnessGaming: return "Fitness Gaming" 100 | 101 | // Catch-all 102 | default: return "Other" 103 | } 104 | } 105 | 106 | /* 107 | Additional mapping for common name for activity types where appropriate. 108 | */ 109 | var commonName: String { 110 | switch self { 111 | case .highIntensityIntervalTraining: return "HIIT" 112 | default: return name 113 | } 114 | } 115 | 116 | /* 117 | Mapping of available activity types to emojis, where an appropriate gender-agnostic emoji is available. 118 | */ 119 | var associatedEmoji: String? { 120 | switch self { 121 | case .americanFootball: return "🏈" 122 | case .archery: return "🏹" 123 | case .badminton: return "🏸" 124 | case .baseball: return "⚾️" 125 | case .basketball: return "🏀" 126 | case .bowling: return "🎳" 127 | case .boxing: return "🥊" 128 | case .curling: return "🥌" 129 | case .cycling: return "🚲" 130 | case .equestrianSports: return "🏇" 131 | case .fencing: return "🤺" 132 | case .fishing: return "🎣" 133 | case .functionalStrengthTraining: return "💪" 134 | case .golf: return "⛳️" 135 | case .hiking: return "🥾" 136 | case .hockey: return "🏒" 137 | case .lacrosse: return "🥍" 138 | case .martialArts: return "🥋" 139 | case .mixedMetabolicCardioTraining: return "❤️" 140 | case .paddleSports: return "🛶" 141 | case .rowing: return "🛶" 142 | case .rugby: return "🏉" 143 | case .sailing: return "⛵️" 144 | case .skatingSports: return "⛸" 145 | case .snowSports: return "🛷" 146 | case .soccer: return "⚽️" 147 | case .softball: return "🥎" 148 | case .tableTennis: return "🏓" 149 | case .tennis: return "🎾" 150 | case .traditionalStrengthTraining: return "🏋️‍♂️" 151 | case .volleyball: return "🏐" 152 | case .waterFitness, .waterSports: return "💧" 153 | 154 | // iOS 10 155 | case .barre: return "🥿" 156 | case .crossCountrySkiing: return "⛷" 157 | case .downhillSkiing: return "⛷" 158 | case .kickboxing: return "🥋" 159 | case .snowboarding: return "🏂" 160 | 161 | // iOS 11 162 | case .mixedCardio: return "❤️" 163 | 164 | // iOS 13 165 | case .discSports: return "🥏" 166 | case .fitnessGaming: return "🎮" 167 | 168 | // Catch-all 169 | default: return nil 170 | } 171 | } 172 | 173 | enum EmojiGender { 174 | case male 175 | case female 176 | } 177 | 178 | /* 179 | Mapping of available activity types to appropriate gender specific emojies. 180 | 181 | If a gender neutral symbol is available this simply returns the value of `associatedEmoji`. 182 | */ 183 | func associatedEmoji(for gender: EmojiGender) -> String? { 184 | switch self { 185 | case .climbing: 186 | switch gender { 187 | case .female: return "🧗‍♀️" 188 | case .male: return "🧗🏻‍♂️" 189 | } 190 | case .dance, .danceInspiredTraining: 191 | switch gender { 192 | case .female: return "💃" 193 | case .male: return "🕺🏿" 194 | } 195 | case .gymnastics: 196 | switch gender { 197 | case .female: return "🤸‍♀️" 198 | case .male: return "🤸‍♂️" 199 | } 200 | case .handball: 201 | switch gender { 202 | case .female: return "🤾‍♀️" 203 | case .male: return "🤾‍♂️" 204 | } 205 | case .mindAndBody, .yoga, .flexibility: 206 | switch gender { 207 | case .female: return "🧘‍♀️" 208 | case .male: return "🧘‍♂️" 209 | } 210 | case .preparationAndRecovery: 211 | switch gender { 212 | case .female: return "🙆‍♀️" 213 | case .male: return "🙆‍♂️" 214 | } 215 | case .running: 216 | switch gender { 217 | case .female: return "🏃‍♀️" 218 | case .male: return "🏃‍♂️" 219 | } 220 | case .surfingSports: 221 | switch gender { 222 | case .female: return "🏄‍♀️" 223 | case .male: return "🏄‍♂️" 224 | } 225 | case .swimming: 226 | switch gender { 227 | case .female: return "🏊‍♀️" 228 | case .male: return "🏊‍♂️" 229 | } 230 | case .walking: 231 | switch gender { 232 | case .female: return "🚶‍♀️" 233 | case .male: return "🚶‍♂️" 234 | } 235 | case .waterPolo: 236 | switch gender { 237 | case .female: return "🤽‍♀️" 238 | case .male: return "🤽‍♂️" 239 | } 240 | case .wrestling: 241 | switch gender { 242 | case .female: return "🤼‍♀️" 243 | case .male: return "🤼‍♂️" 244 | } 245 | 246 | // Catch-all 247 | default: return associatedEmoji 248 | } 249 | } 250 | 251 | var associatedImageName: String { 252 | switch self { 253 | case .running: return "running" 254 | case .walking: return "walking" 255 | case .cycling: return "cycling" 256 | case .functionalStrengthTraining: return "strength" 257 | case .traditionalStrengthTraining: return "strength" 258 | case .highIntensityIntervalTraining: return "hiit" 259 | 260 | // Catch-all 261 | default: return "other" 262 | } 263 | } 264 | 265 | } 266 | -------------------------------------------------------------------------------- /Workout Companion/Extensions/MKCoordinateRegion+extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MKCoordinateRegion+extensions.swift 3 | // Workout Companion 4 | // 5 | // Created by Mario Eguiluz on 29/04/2021. 6 | // 7 | 8 | import MapKit 9 | 10 | extension MKCoordinateRegion { 11 | init(coordinates: [CLLocationCoordinate2D]) { 12 | var minLat: CLLocationDegrees = 90.0 13 | var maxLat: CLLocationDegrees = -90.0 14 | var minLon: CLLocationDegrees = 180.0 15 | var maxLon: CLLocationDegrees = -180.0 16 | 17 | for coordinate in coordinates { 18 | let lat = Double(coordinate.latitude) 19 | let long = Double(coordinate.longitude) 20 | if lat < minLat { 21 | minLat = lat 22 | } 23 | if long < minLon { 24 | minLon = long 25 | } 26 | if lat > maxLat { 27 | maxLat = lat 28 | } 29 | if long > maxLon { 30 | maxLon = long 31 | } 32 | } 33 | 34 | let span = MKCoordinateSpan(latitudeDelta: (maxLat - minLat)*2.0, longitudeDelta: (maxLon - minLon)*2.0) 35 | let center = CLLocationCoordinate2DMake(maxLat - span.latitudeDelta / 4, maxLon - span.longitudeDelta / 4) 36 | self.init(center: center, span: span) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Workout Companion/Extensions/Text+extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Text+extensions.swift 3 | // Workout Companion 4 | // 5 | // Created by Mario Eguiluz on 07/05/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | extension Text { 11 | func workoutSubheadlineStyle() -> Text { 12 | self 13 | .font(.subheadline).bold().foregroundColor(Color(UIColor.systemGray)) 14 | } 15 | 16 | func workoutTitleStyle() -> Text { 17 | self 18 | .font(.title3).bold().foregroundColor(.white) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Workout Companion/Extensions/View+extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // View+extensions.swift 3 | // Workout Companion 4 | // 5 | // Created by Mario Eguiluz on 28/04/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | // From: https://www.avanderlee.com/swiftui/conditional-view-modifier/ 11 | 12 | extension View { 13 | /// Applies the given transform if the given condition evaluates to `true`. 14 | /// - Parameters: 15 | /// - condition: The condition to evaluate. 16 | /// - transform: The transform to apply to the source `View`. 17 | /// - Returns: Either the original `View` or the modified `View` if the condition is `true`. 18 | @ViewBuilder func `if`(_ condition: Bool, transform: (Self) -> Content) -> some View { 19 | if condition { 20 | transform(self) 21 | } else { 22 | self 23 | } 24 | } 25 | 26 | func cardStyle() -> some View { 27 | self 28 | .padding() 29 | .background(Color("Card")) 30 | .cornerRadius(15) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Workout Companion/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Workout Widgets 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSHealthShareUsageDescription 26 | Your workout related data will be used to display your saved workouts. 27 | UIApplicationSceneManifest 28 | 29 | UIApplicationSupportsMultipleScenes 30 | 31 | 32 | UIApplicationSupportsIndirectInputEvents 33 | 34 | UILaunchScreen 35 | 36 | UIRequiredDeviceCapabilities 37 | 38 | armv7 39 | 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UISupportedInterfaceOrientations~ipad 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationPortraitUpsideDown 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Workout Companion/Models/MapWorkoutModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapWorkout.swift 3 | // Workout Companion 4 | // 5 | // Created by Mario Eguiluz on 01/05/2021. 6 | // 7 | 8 | import HealthKit 9 | import MapKit 10 | 11 | struct MapWorkoutModel { 12 | let workout: HKWorkout 13 | let region: MKCoordinateRegion? 14 | let coordinates: [CLLocationCoordinate2D]? 15 | 16 | var summary: String { 17 | return "Your last walk was \(String(format:"%0.2f km", distance)) long with an average pace of \(pace) per km" 18 | } 19 | var distance: Double { 20 | return workout.totalDistance != nil ? workout.totalDistance!.doubleValue(for: HKUnit.meter())/1000 : 0 21 | } 22 | 23 | var durationHours: Int { 24 | return Int(floor(workout.duration / 3600)) 25 | } 26 | 27 | var durationMinutes: Int { 28 | return Int(floor(workout.duration.truncatingRemainder(dividingBy: 3600)) / 60) 29 | } 30 | 31 | var pace: String { 32 | let durationDecimal = workout.duration / 60 33 | let paceDecimal = durationDecimal / distance 34 | let paceMins = Int(floor(paceDecimal)) 35 | let paceSecs = Int(floor(paceDecimal * 60).truncatingRemainder(dividingBy: 60)) 36 | return String(format:"%d minutes, %02d seconds", paceMins, paceSecs) 37 | } 38 | 39 | var type: HKWorkoutActivityType { 40 | return workout.workoutActivityType 41 | } 42 | 43 | var kcal: Double { 44 | return workout.totalEnergyBurned != nil ? workout.totalEnergyBurned!.doubleValue(for: HKUnit.kilocalorie()) : 0 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Workout Companion/Models/MenuModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Menu.swift 3 | // Workout Companion 4 | // 5 | // Created by Mario Eguiluz on 01/05/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | enum MenuModel: CaseIterable, Identifiable { 11 | case workout 12 | case activeEnergy 13 | case heartRate 14 | case steps 15 | case sleep 16 | 17 | var id: String { return UUID().uuidString } 18 | 19 | func title() -> String { 20 | switch self { 21 | case .workout: 22 | return "Workout" 23 | case .activeEnergy: 24 | return "Active Energy" 25 | case .heartRate: 26 | return "Heart Rate" 27 | case .steps: 28 | return "Steps" 29 | case .sleep: 30 | return "Sleep" 31 | } 32 | } 33 | 34 | func imageSystemNames() -> String { 35 | switch self { 36 | case .workout: 37 | return "flame.fill" 38 | case .activeEnergy: 39 | return "bolt.fill" 40 | case .heartRate: 41 | return "heart.fill" 42 | case .steps: 43 | return "figure.walk.circle.fill" 44 | case .sleep: 45 | return "powersleep" 46 | } 47 | } 48 | 49 | func color() -> Color { 50 | switch self { 51 | case .workout: 52 | return .red 53 | case .activeEnergy: 54 | return .yellow 55 | case .heartRate: 56 | return .pink 57 | case .steps: 58 | return .orange 59 | case .sleep: 60 | return .purple 61 | } 62 | } 63 | } 64 | 65 | extension MenuModel { 66 | static var data = { 67 | return [ 68 | MenuModel.workout, 69 | MenuModel.activeEnergy, 70 | MenuModel.heartRate, 71 | MenuModel.steps, 72 | MenuModel.sleep 73 | ] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Workout Companion/Models/WeekWorkoutModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeekWorkouts.swift 3 | // Workout Companion 4 | // 5 | // Created by Mario Eguiluz on 01/05/2021. 6 | // 7 | 8 | import HealthKit 9 | 10 | struct WeekWorkoutModel { 11 | let workoutDays: [WorkoutDayModel] 12 | 13 | var summary: String { 14 | return "You worked out on \(countWeekWorkoutDays(workoutDays)) of the last 7 days." 15 | } 16 | 17 | init(workouts: [HKWorkout]) { 18 | let today = Date() 19 | var workoutDays: [WorkoutDayModel] = [] 20 | [0,1,2,3,4,5,6].forEach { e in 21 | let date = Calendar.current.date(byAdding: .day, value: -e, to: today)! 22 | let dateWorkouts = workouts.filter { Calendar.current.isDate($0.startDate, equalTo: date, toGranularity: .day) } 23 | workoutDays.append(WorkoutDayModel(date: date, workouts: dateWorkouts)) 24 | } 25 | self.workoutDays = workoutDays.reversed() 26 | } 27 | 28 | private func countWeekWorkoutDays(_ workouts: [WorkoutDayModel]) -> Int { 29 | let myCalendar = Calendar(identifier: .gregorian) 30 | var days = [false, false, false, false, false, false, false] 31 | workouts.forEach { (e) in 32 | if e.workouts.count > 0 { 33 | let weekDay = myCalendar.component(.weekday, from: e.workouts[0].startDate) - 1 34 | days[weekDay] = true 35 | } 36 | } 37 | let trainingDays = days.filter{$0} 38 | return trainingDays.count 39 | } 40 | } 41 | 42 | extension WeekWorkoutModel { 43 | static var data = WeekWorkoutModel(workouts: [ 44 | generateWorkout(daysAgo: 6), 45 | generateWorkout(daysAgo: 6), 46 | generateWorkout(daysAgo: 4), 47 | generateWorkout(daysAgo: 3), 48 | generateWorkout(daysAgo: 1), 49 | generateWorkout(daysAgo: 1) 50 | ]) 51 | 52 | private static func generateWorkout(daysAgo: Int) -> HKWorkout { 53 | let totalEnergyBurned = Double.random(in: 350..<651) 54 | let today = Date() 55 | let start = Calendar.current.date(byAdding: .day, value: -daysAgo, to: today)! 56 | let end = Calendar.current.date(byAdding: .minute, value: 60, to: start)! 57 | let workout = HKWorkout( 58 | activityType: .traditionalStrengthTraining, 59 | start: start, 60 | end: end, 61 | duration: 60, 62 | totalEnergyBurned: HKQuantity(unit: .kilocalorie(), doubleValue: totalEnergyBurned), 63 | totalDistance: nil, device: nil, metadata: nil) 64 | return workout 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Workout Companion/Models/WorkoutDayModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WorkoutDay.swift 3 | // Workout Companion 4 | // 5 | // Created by Mario Eguiluz on 01/05/2021. 6 | // 7 | 8 | import Foundation 9 | import HealthKit 10 | 11 | struct WorkoutDayModel: Identifiable { 12 | let id: UUID 13 | let date: Date 14 | let workouts: [HKWorkout] 15 | 16 | var day: String { 17 | return date.weekday() 18 | } 19 | 20 | var number: Int { 21 | return date.day() 22 | } 23 | 24 | var didWorkout: Bool { 25 | return workouts.count > 0 26 | } 27 | 28 | var isToday: Bool { 29 | return Calendar.current.isDateInToday(date) 30 | } 31 | 32 | init(date: Date, workouts: [HKWorkout]) { 33 | self.id = UUID() 34 | self.date = date 35 | self.workouts = workouts 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Workout Companion/Models/WorkoutRowModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WorkoutRowModel.swift 3 | // Workout Companion 4 | // 5 | // Created by Mario Eguiluz on 07/05/2021. 6 | // 7 | 8 | import HealthKit 9 | 10 | struct WorkoutRowModel { 11 | let workout: HKWorkout 12 | 13 | var activityName: String { 14 | return workout.workoutActivityType.name 15 | } 16 | 17 | var distance: String { 18 | return workout.totalDistance != nil ? String(format:"%0.2f", workout.totalDistance!.doubleValue(for: HKUnit.meter())/1000) : "-" 19 | } 20 | 21 | var durationHours: Int { 22 | return Int(floor(workout.duration / 3600)) 23 | } 24 | 25 | var durationMinutes: Int { 26 | return Int(floor(workout.duration.truncatingRemainder(dividingBy: 3600)) / 60) 27 | } 28 | 29 | var energyBurned: String { 30 | return workout.totalEnergyBurned != nil ? String(format:"%0.f", workout.totalEnergyBurned!.doubleValue(for: HKUnit.kilocalorie())) : "-" 31 | } 32 | 33 | var imageName: String { 34 | return workout.workoutActivityType.associatedImageName 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Workout Companion/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Workout Companion/Views/MapWorkoutWidget.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapWorkoutWidget.swift 3 | // Workout Companion 4 | // 5 | // Created by Mario Eguiluz on 29/04/2021. 6 | // 7 | 8 | import SwiftUI 9 | import HealthKit 10 | import MapKit 11 | 12 | struct MapWorkoutWidget: View { 13 | var mapWorkoutModel: MapWorkoutModel? 14 | 15 | var body: some View { 16 | VStack(alignment: .leading, spacing: 15) { 17 | TitleWorkouts() 18 | if let mapWorkoutModel = mapWorkoutModel { 19 | Text(mapWorkoutModel.summary) 20 | .font(Font.body.bold()) 21 | .foregroundColor(Color.white) 22 | Divider() 23 | .background(Color(UIColor.systemGray2)) 24 | if let region = mapWorkoutModel.region, let coordinates = mapWorkoutModel.coordinates { 25 | MapView(coordinates: coordinates, region: region) 26 | .frame(maxWidth: .infinity, idealHeight: 200) 27 | } 28 | MapViewFooter(mapWorkoutModel: mapWorkoutModel) 29 | } else { 30 | Text("Waiting for a walking workout...") 31 | .foregroundColor(Color.white) 32 | .frame(maxWidth: .infinity, alignment: .leading) 33 | } 34 | } 35 | .cardStyle() 36 | } 37 | } 38 | 39 | struct MapView: UIViewRepresentable { 40 | let coordinates: [CLLocationCoordinate2D] 41 | let region: MKCoordinateRegion 42 | 43 | func makeUIView(context: Context) -> MKMapView { 44 | let mapView = MKMapView() 45 | mapView.delegate = context.coordinator 46 | return mapView 47 | } 48 | 49 | func updateUIView(_ view: MKMapView, context: Context) { 50 | if coordinates.count > 0 { 51 | view.setRegion(region, animated: true) 52 | let polyline = MKPolyline(coordinates: coordinates, count: coordinates.count) 53 | view.addOverlay(polyline) 54 | } 55 | } 56 | 57 | func makeCoordinator() -> Coordinator { 58 | Coordinator(self) 59 | } 60 | 61 | class Coordinator: NSObject, MKMapViewDelegate { 62 | var parent: MapView 63 | 64 | init(_ parent: MapView) { 65 | self.parent = parent 66 | } 67 | 68 | func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { 69 | if overlay is MKPolyline { 70 | let polylineRenderer = MKPolylineRenderer(overlay: overlay) 71 | polylineRenderer.strokeColor = UIColor.red 72 | polylineRenderer.lineWidth = 5 73 | return polylineRenderer 74 | } 75 | 76 | return MKOverlayRenderer() 77 | } 78 | } 79 | } 80 | 81 | struct MapViewFooter: View { 82 | let mapWorkoutModel: MapWorkoutModel 83 | 84 | var body: some View { 85 | HStack(spacing: 15) { 86 | VStack(alignment: .leading, spacing: 5) { 87 | Text("Distance") 88 | .workoutSubheadlineStyle() 89 | Text(String(format:"%0.2f", mapWorkoutModel.distance)) 90 | .workoutTitleStyle() 91 | + Text(" km") 92 | .workoutSubheadlineStyle() 93 | } 94 | Divider() 95 | .background(Color(UIColor.systemGray2)) 96 | VStack(alignment: .leading, spacing: 5) { 97 | Text("Duration") 98 | .workoutSubheadlineStyle() 99 | Text("\(mapWorkoutModel.durationHours)") 100 | .workoutTitleStyle() 101 | + Text(" hr ") 102 | .workoutSubheadlineStyle() 103 | + Text("\(mapWorkoutModel.durationMinutes)") 104 | .workoutTitleStyle() 105 | + Text(" min") 106 | .workoutSubheadlineStyle() 107 | } 108 | Divider() 109 | .background(Color(UIColor.systemGray2)) 110 | VStack(alignment: .leading, spacing: 5) { 111 | Text("Energy") 112 | .workoutSubheadlineStyle() 113 | Text(String(format:"%.0f", mapWorkoutModel.kcal)) 114 | .workoutTitleStyle() 115 | + Text(" kcal") 116 | .workoutSubheadlineStyle() 117 | } 118 | } 119 | .frame(maxHeight: 50) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Workout Companion/Views/MenuView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Workout Companion 4 | // 5 | // Created by Mario Eguiluz on 26/04/2021. 6 | // 7 | 8 | import SwiftUI 9 | import HealthKit 10 | 11 | struct MenuView: View { 12 | var body: some View { 13 | List { 14 | ForEach(MenuModel.allCases) { item in 15 | NavigationLink(destination: destination(for: item)) { 16 | Label(item.title(), systemImage: item.imageSystemNames()) 17 | .foregroundColor(item.color()) 18 | .padding() 19 | } 20 | .listRowBackground(Color("Card")) 21 | } 22 | } 23 | .navigationTitle("Widgets") 24 | } 25 | 26 | @ViewBuilder 27 | private func destination(for menuItem: MenuModel) -> some View { 28 | switch menuItem { 29 | case .workout: 30 | WorkoutWidgets() 31 | default: 32 | WIP() 33 | } 34 | } 35 | } 36 | 37 | struct MenuView_Previews: PreviewProvider { 38 | static var previews: some View { 39 | MenuView().previewDevice("iPhone 12 Pro") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Workout Companion/Views/RecentWorkoutsWidgets.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LastWorkoutsWidget.swift 3 | // Workout Companion 4 | // 5 | // Created by Mario Eguiluz on 06/05/2021. 6 | // 7 | 8 | import SwiftUI 9 | import HealthKit 10 | 11 | struct RecentWorkoutsWidgets: View { 12 | let workouts: [HKWorkout] 13 | 14 | var body: some View { 15 | VStack(alignment: .leading, spacing: 15) { 16 | TitleWorkouts() 17 | Text("You did \(workouts.count) workouts in the last 7 days.") 18 | .font(Font.body.bold()) 19 | .foregroundColor(Color.white) 20 | Divider() 21 | .background(Color(UIColor.systemGray2)) 22 | ScrollView(.horizontal, showsIndicators: true) { 23 | HStack(spacing: 20) { 24 | ForEach(workouts.batched(into: 4), id: \.self) { items in 25 | ThreeRowWorkouts(workouts: items) 26 | } 27 | } 28 | } 29 | } 30 | .cardStyle() 31 | .frame(maxHeight: Constants.widgetLargeHeight) 32 | } 33 | } 34 | 35 | struct ThreeRowWorkouts: View { 36 | let workouts: [HKWorkout] 37 | 38 | var body: some View { 39 | VStack(alignment: .leading, spacing: 15) { 40 | ForEach(Array(workouts.enumerated()), id: \.offset) { (offset, element) in 41 | WorkoutRowView(workout: WorkoutRowModel(workout: element)) 42 | } 43 | Spacer() 44 | } 45 | } 46 | } 47 | 48 | struct WorkoutRowView: View { 49 | let workout: WorkoutRowModel 50 | 51 | var body: some View { 52 | HStack(alignment: .center, spacing: 10) { 53 | Image(workout.imageName) 54 | .resizable() 55 | .foregroundColor(Color(UIColor.systemGray)) 56 | .frame(width: 50, height: 50, alignment: .center) 57 | VStack(alignment: .leading, spacing: -5) { 58 | Text(workout.activityName) 59 | .font(.caption).bold().foregroundColor(Color(UIColor.systemGray)) 60 | HStack(spacing: 10) { 61 | VStack(alignment: .leading, spacing: 5) { 62 | Text("\(workout.durationHours)") 63 | .workoutTitleStyle() 64 | + Text(" hr ") 65 | .workoutSubheadlineStyle() 66 | + Text("\(workout.durationMinutes)") 67 | .workoutTitleStyle() 68 | + Text(" min") 69 | .workoutSubheadlineStyle() 70 | } 71 | Divider() 72 | .background(Color(UIColor.systemGray2)) 73 | VStack(alignment: .leading, spacing: 5) { 74 | Text(workout.energyBurned) 75 | .workoutTitleStyle() 76 | + Text(" kcal") 77 | .workoutSubheadlineStyle() 78 | } 79 | Divider() 80 | .background(Color(UIColor.systemGray2)) 81 | VStack(alignment: .leading, spacing: 5) { 82 | Text(workout.distance) 83 | .workoutTitleStyle() 84 | + Text(" km") 85 | .workoutSubheadlineStyle() 86 | } 87 | } 88 | .frame(maxHeight: 40) 89 | } 90 | } 91 | .foregroundColor(Color.white) 92 | } 93 | } 94 | 95 | struct RecentWorkoutsWidgets_Previews: PreviewProvider { 96 | static var previews: some View { 97 | RecentWorkoutsWidgets(workouts: HKWorkout.data) 98 | .previewDevice("iPhone 12 Pro") 99 | .preferredColorScheme(.dark) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Workout Companion/Views/WIP.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WIP.swift 3 | // Workout Companion 4 | // 5 | // Created by Mario Eguiluz on 10/05/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct WIP: View { 11 | var body: some View { 12 | Text("This content is still in development") 13 | } 14 | } 15 | 16 | struct WIP_Previews: PreviewProvider { 17 | static var previews: some View { 18 | WIP() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Workout Companion/Views/WeekWorkoutsWidget.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeekWorkoutsWidget.swift 3 | // Workout Companion 4 | // 5 | // Created by Mario Eguiluz on 28/04/2021. 6 | // 7 | 8 | import SwiftUI 9 | import HealthKit 10 | 11 | struct WeekWorkoutsWidget: View { 12 | let weekWorkoutModel: WeekWorkoutModel 13 | 14 | var body: some View { 15 | VStack(alignment: .leading, spacing: 8) { 16 | TitleWorkouts() 17 | Text(weekWorkoutModel.summary) 18 | .font(Font.body.bold()) 19 | .foregroundColor(Color.white) 20 | Divider() 21 | .background(Color(UIColor.systemGray2)) 22 | WeekWorkoutsView(weekWorkoutModel: weekWorkoutModel) 23 | } 24 | .cardStyle() 25 | .frame(maxHeight: Constants.widgetMediumHeight) 26 | } 27 | } 28 | 29 | struct TitleWorkouts: View { 30 | var body: some View { 31 | HStack(spacing: 3) { 32 | Image(systemName: "flame.fill") 33 | Text("Workouts") 34 | } 35 | .font(Font.body.bold()) 36 | .foregroundColor(Color("Main")) 37 | } 38 | } 39 | 40 | struct WeekWorkoutsView: View { 41 | let weekWorkoutModel: WeekWorkoutModel 42 | 43 | var body: some View { 44 | HStack { 45 | ForEach(weekWorkoutModel.workoutDays, id: \.id) { 46 | WorkoutDayRowView(workoutDayModel: $0) 47 | .frame(maxWidth: .infinity) 48 | } 49 | } 50 | } 51 | } 52 | 53 | struct WorkoutDayRowView: View { 54 | var workoutDayModel: WorkoutDayModel 55 | 56 | var body: some View { 57 | VStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/, spacing: 15) { 58 | Text(workoutDayModel.day) 59 | .font(Font.subheadline.bold()) 60 | .foregroundColor(Color(UIColor.systemGray)) 61 | Text("\(workoutDayModel.number)") 62 | .padding(5) 63 | .frame(width: 35, height: 35, alignment: .center) 64 | .background(workoutDayModel.didWorkout ? Color("Main") : Color(UIColor.systemGray2)) 65 | .foregroundColor(workoutDayModel.didWorkout ? Color(UIColor.white) : Color(UIColor.systemGray)) 66 | .clipShape( 67 | Circle() 68 | ) 69 | .if(workoutDayModel.isToday) { view in 70 | view.overlay( 71 | Circle() 72 | .stroke(workoutDayModel.didWorkout ? Color("MainHighlight") : Color(UIColor.systemGray), lineWidth: 4) 73 | .padding(1) 74 | ) 75 | } 76 | 77 | } 78 | .lineLimit(1) 79 | .fixedSize() 80 | } 81 | } 82 | 83 | struct WeekWorkoutsWidget_Previews: PreviewProvider { 84 | static var previews: some View { 85 | Group { 86 | WeekWorkoutsWidget(weekWorkoutModel: WeekWorkoutModel.data) 87 | .previewDevice("iPhone 12 Pro") 88 | .preferredColorScheme(.dark) 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Workout Companion/Views/WorkoutWidgets.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WorkoutWidgets.swift 3 | // Workout Companion 4 | // 5 | // Created by Mario Eguiluz on 01/05/2021. 6 | // 7 | 8 | import SwiftUI 9 | import HealthKit 10 | 11 | struct WorkoutWidgets: View { 12 | @EnvironmentObject var workoutManager: WorkoutManager 13 | 14 | var body: some View { 15 | ScrollView { 16 | VStack { 17 | WeekWorkoutsWidget(weekWorkoutModel: workoutManager.weekWorkoutModel) 18 | MapWorkoutWidget(mapWorkoutModel: workoutManager.mapWorkoutModel) 19 | RecentWorkoutsWidgets(workouts: workoutManager.recentWorkouts) 20 | } 21 | .padding() 22 | } 23 | .navigationTitle("Workout Widgets") 24 | .onAppear() { 25 | workoutManager.loadWorkoutData() 26 | } 27 | } 28 | } 29 | 30 | 31 | struct WorkoutWidgets_Previews: PreviewProvider { 32 | static var previews: some View { 33 | WorkoutWidgets().previewDevice("iPhone 12 Pro").environmentObject( 34 | WorkoutManager( 35 | weekWorkoutModel: WeekWorkoutModel.data, 36 | mapWorkoutModel: nil, 37 | recentWorkouts: HKWorkout.data 38 | ) 39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Workout Companion/Workout Companion.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.healthkit 6 | 7 | com.apple.developer.healthkit.access 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Workout Companion/Workout_CompanionApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Workout_CompanionApp.swift 3 | // Workout Companion 4 | // 5 | // Created by Mario Eguiluz on 26/04/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct Workout_CompanionApp: App { 12 | 13 | @State private var showingAlert = false 14 | @State private var errorMesage = "" 15 | 16 | @StateObject var workoutManager = WorkoutManager() 17 | 18 | var body: some Scene { 19 | WindowGroup { 20 | NavigationView { 21 | MenuView() 22 | .environmentObject(workoutManager) 23 | } 24 | .alert(isPresented: $showingAlert) { 25 | Alert(title: Text("Something went wrong..."), message: Text(errorMesage), dismissButton: .default(Text("Ok"))) 26 | } 27 | .onAppear() { 28 | workoutManager.requestAuthorization { 29 | workoutManager.loadWorkoutData() 30 | } onError: { error in 31 | if let error = error { 32 | errorMesage = error.localizedDescription 33 | } 34 | showingAlert = true 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /WorkoutCompanionWidget/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /WorkoutCompanionWidget/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /WorkoutCompanionWidget/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /WorkoutCompanionWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /WorkoutCompanionWidget/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | WorkoutCompanion 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | NSExtension 24 | 25 | NSExtensionPointIdentifier 26 | com.apple.widgetkit-extension 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /WorkoutCompanionWidget/WorkoutCompanionWidget.intentdefinition: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | INEnums 6 | 7 | INIntentDefinitionModelVersion 8 | 1.2 9 | INIntentDefinitionNamespace 10 | 88xZPY 11 | INIntentDefinitionSystemVersion 12 | 20A294 13 | INIntentDefinitionToolsBuildVersion 14 | 12A6144 15 | INIntentDefinitionToolsVersion 16 | 12.0 17 | INIntents 18 | 19 | 20 | INIntentCategory 21 | information 22 | INIntentDescriptionID 23 | tVvJ9c 24 | INIntentEligibleForWidgets 25 | 26 | INIntentIneligibleForSuggestions 27 | 28 | INIntentName 29 | Configuration 30 | INIntentResponse 31 | 32 | INIntentResponseCodes 33 | 34 | 35 | INIntentResponseCodeName 36 | success 37 | INIntentResponseCodeSuccess 38 | 39 | 40 | 41 | INIntentResponseCodeName 42 | failure 43 | 44 | 45 | 46 | INIntentTitle 47 | Configuration 48 | INIntentTitleID 49 | gpCwrM 50 | INIntentType 51 | Custom 52 | INIntentVerb 53 | View 54 | 55 | 56 | INTypes 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /WorkoutCompanionWidget/WorkoutCompanionWidget.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WorkoutCompanionWidget.swift 3 | // WorkoutCompanionWidget 4 | // 5 | // Created by Mario Eguiluz on 03/05/2021. 6 | // 7 | 8 | import WidgetKit 9 | import SwiftUI 10 | import Intents 11 | 12 | struct Provider: IntentTimelineProvider { 13 | let workoutManager = WorkoutManager() 14 | 15 | func placeholder(in context: Context) -> SimpleEntry { 16 | SimpleEntry(date: Date(), configuration: ConfigurationIntent(), weekWorkoutModel: WeekWorkoutModel.data) 17 | } 18 | 19 | func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) { 20 | let entry = SimpleEntry(date: Date(), configuration: configuration, weekWorkoutModel: WeekWorkoutModel.data) 21 | completion(entry) 22 | } 23 | 24 | func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline) -> ()) { 25 | workoutManager.latestWorkoutWeekDays { weekWorkoutModel in 26 | let entryDate = Date() 27 | let refreshDate = Calendar.current.date(byAdding: .hour, value: 4, to: entryDate)! 28 | let entry = SimpleEntry(date: entryDate, configuration: configuration, weekWorkoutModel: weekWorkoutModel) 29 | let timeline = Timeline(entries: [entry], policy: .after(refreshDate)) 30 | completion(timeline) 31 | } 32 | } 33 | } 34 | 35 | struct SimpleEntry: TimelineEntry { 36 | let date: Date 37 | let configuration: ConfigurationIntent 38 | let weekWorkoutModel: WeekWorkoutModel 39 | } 40 | 41 | struct WorkoutCompanionWidgetEntryView : View { 42 | var entry: Provider.Entry 43 | 44 | var body: some View { 45 | WeekWorkoutsWidget(weekWorkoutModel: entry.weekWorkoutModel) 46 | } 47 | } 48 | 49 | @main 50 | struct WorkoutCompanionWidget: Widget { 51 | let kind: String = "WorkoutCompanionWidget" 52 | 53 | var body: some WidgetConfiguration { 54 | IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in 55 | WorkoutCompanionWidgetEntryView(entry: entry) 56 | } 57 | .configurationDisplayName("Workout Companion Widget") 58 | .description("Widgets for workouts and others.") 59 | .supportedFamilies([.systemMedium]) 60 | } 61 | } 62 | 63 | struct WorkoutCompanionWidget_Previews: PreviewProvider { 64 | static var previews: some View { 65 | WorkoutCompanionWidgetEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent(), weekWorkoutModel: WeekWorkoutModel.data)) 66 | .previewContext(WidgetPreviewContext(family: .systemMedium)) 67 | } 68 | } 69 | --------------------------------------------------------------------------------