├── .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!
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 |
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 |
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 |
--------------------------------------------------------------------------------