diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..c2bfa8d
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,16 @@
+# See https://pre-commit.com for more information
+# See https://pre-commit.com/hooks.html for more hooks
+repos:
+- repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v3.2.0
+ hooks:
+ - id: trailing-whitespace
+ - id: end-of-file-fixer
+ - id: check-added-large-files
+ args: ['--maxkb=80000']
+- repo: https://github.com/nicklockwood/SwiftFormat
+ rev: 0.47.3
+ hooks:
+ - id: swiftformat
+ name: SwiftFormat
+ types: [swift]
diff --git a/README.md b/README.md
index 96344bc..d7b8411 100644
--- a/README.md
+++ b/README.md
@@ -2,14 +2,19 @@
**An Apple Watch App that randomly selects quick exercises.**
-![Swift](https://img.shields.io/badge/Swift-Swift_Project-FA7343.svg?style=flat&logo=swift)
-![iOS](https://img.shields.io/badge/watchOS-App-999999.svg?style=flat&logo=apple)
-[![jhc github](https://img.shields.io/badge/GitHub-jhrcook-181717.svg?style=flat&logo=github)](https://github.com/jhrcook)
-[![jhc twitter](https://img.shields.io/badge/Twitter-@JoshDoesA-00aced.svg?style=flat&logo=twitter)](https://twitter.com/JoshDoesa)
-[![jhc website](https://img.shields.io/badge/Website-Joshua_Cook-5087B2.svg?style=flat&logo=telegram)](https://joshuacook.netlify.com)
+![Swift](https://img.shields.io/badge/Swift-App-FA7343.svg?style=flat&logo=swift)
+![iOS](https://img.shields.io/badge/watchOS-App-999999.svg?style=flat&logo=apple&logoColor=white)
+
+---
+
+[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit)
+[![SwiftFormat](https://img.shields.io/badge/SwfitFormat-enabled-A166E6)](https://github.com/nicklockwood/SwiftFormat)
+[![jhc github](https://img.shields.io/badge/GitHub-jhrcook-181717.svg?style=flat&logo=github)](https://github.com/jhrcook)
+[![jhc twitter](https://img.shields.io/badge/Twitter-@JoshDoesA-00aced.svg?style=flat&logo=twitter)](https://twitter.com/JoshDoesa)
+[![jhc website](https://img.shields.io/badge/Website-Joshua_Cook-5087B2.svg?style=flat&logo=telegram)](https://joshuacook.netlify.com)
diff --git a/Workout Spinner WatchKit App/Base.lproj/Interface.storyboard b/Workout Spinner WatchKit App/Base.lproj/Interface.storyboard
index aa3bd9c..2c80e11 100644
--- a/Workout Spinner WatchKit App/Base.lproj/Interface.storyboard
+++ b/Workout Spinner WatchKit App/Base.lproj/Interface.storyboard
@@ -9,8 +9,8 @@
-
+
diff --git a/Workout Spinner WatchKit Extension/ComplicationController.swift b/Workout Spinner WatchKit Extension/ComplicationController.swift
index 42ee5af..424a2d6 100644
--- a/Workout Spinner WatchKit Extension/ComplicationController.swift
+++ b/Workout Spinner WatchKit Extension/ComplicationController.swift
@@ -8,49 +8,46 @@
import ClockKit
-
class ComplicationController: NSObject, CLKComplicationDataSource {
-
// MARK: - Timeline Configuration
-
- func getSupportedTimeTravelDirections(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimeTravelDirections) -> Void) {
+
+ func getSupportedTimeTravelDirections(for _: CLKComplication, withHandler handler: @escaping (CLKComplicationTimeTravelDirections) -> Void) {
handler([.forward, .backward])
}
-
- func getTimelineStartDate(for complication: CLKComplication, withHandler handler: @escaping (Date?) -> Void) {
+
+ func getTimelineStartDate(for _: CLKComplication, withHandler handler: @escaping (Date?) -> Void) {
handler(nil)
}
-
- func getTimelineEndDate(for complication: CLKComplication, withHandler handler: @escaping (Date?) -> Void) {
+
+ func getTimelineEndDate(for _: CLKComplication, withHandler handler: @escaping (Date?) -> Void) {
handler(nil)
}
-
- func getPrivacyBehavior(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationPrivacyBehavior) -> Void) {
+
+ func getPrivacyBehavior(for _: CLKComplication, withHandler handler: @escaping (CLKComplicationPrivacyBehavior) -> Void) {
handler(.showOnLockScreen)
}
-
+
// MARK: - Timeline Population
-
- func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
+
+ func getCurrentTimelineEntry(for _: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
// Call the handler with the current timeline entry
handler(nil)
}
-
- func getTimelineEntries(for complication: CLKComplication, before date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
+
+ func getTimelineEntries(for _: CLKComplication, before _: Date, limit _: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
// Call the handler with the timeline entries prior to the given date
handler(nil)
}
-
- func getTimelineEntries(for complication: CLKComplication, after date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
+
+ func getTimelineEntries(for _: CLKComplication, after _: Date, limit _: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
// Call the handler with the timeline entries after to the given date
handler(nil)
}
-
+
// MARK: - Placeholder Templates
-
- func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {
+
+ func getLocalizableSampleTemplate(for _: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {
// This method will be called once per supported complication, and the results will be cached
handler(nil)
}
-
}
diff --git a/Workout Spinner WatchKit Extension/ExtensionDelegate.swift b/Workout Spinner WatchKit Extension/ExtensionDelegate.swift
index 3375600..1ffed72 100644
--- a/Workout Spinner WatchKit Extension/ExtensionDelegate.swift
+++ b/Workout Spinner WatchKit Extension/ExtensionDelegate.swift
@@ -9,7 +9,6 @@
import WatchKit
class ExtensionDelegate: NSObject, WKExtensionDelegate {
-
func applicationDidFinishLaunching() {
// Perform any final initialization of your application.
}
@@ -52,5 +51,4 @@ class ExtensionDelegate: NSObject, WKExtensionDelegate {
}
}
}
-
}
diff --git a/Workout Spinner WatchKit Extension/Extensions/Color-extension.swift b/Workout Spinner WatchKit Extension/Extensions/Color-extension.swift
index f4da0dd..2592fde 100644
--- a/Workout Spinner WatchKit Extension/Extensions/Color-extension.swift
+++ b/Workout Spinner WatchKit Extension/Extensions/Color-extension.swift
@@ -9,38 +9,34 @@
import SwiftUI
extension Color {
-
init(red: Int, green: Int, blue: Int) {
func f(_ x: Int) -> Double {
return Double(x) / 255.0
}
self.init(red: f(red), green: f(green), blue: f(blue))
}
-
+
static func randomPastelColor() -> Color {
- let pastelRange = 140...255
+ let pastelRange = 140 ... 255
return Color(red: Int.random(in: pastelRange), green: Int.random(in: pastelRange), blue: Int.random(in: pastelRange))
}
}
-
struct CustomColors_Previews: PreviewProvider {
-
static var customColors: [Color] = [.workoutGreen, .darkGray, .deepRed, .deepRed2, .pastelDarkRed, .pastelDarkRed2]
-
+
static var previews: some View {
Group {
- ForEach(0..<10) { _ in
+ ForEach(0 ..< 10) { _ in
Color.randomPastelColor().previewLayout(.fixed(width: 50, height: 50))
}
- ForEach(0.. {
override var body: ContentView {
diff --git a/Workout Spinner WatchKit Extension/Models/BodyPartSelections.swift b/Workout Spinner WatchKit Extension/Models/BodyPartSelections.swift
index 49448d7..d3a640c 100644
--- a/Workout Spinner WatchKit Extension/Models/BodyPartSelections.swift
+++ b/Workout Spinner WatchKit Extension/Models/BodyPartSelections.swift
@@ -8,7 +8,6 @@
import SwiftUI
-
struct BodyPartSelection: Identifiable {
let bodypart: ExerciseBodyPart
var enabled: Bool
@@ -17,46 +16,41 @@ struct BodyPartSelection: Identifiable {
}
}
-
class BodyPartSelections: ObservableObject {
-
@Published var bodyparts = [BodyPartSelection]()
-
+
/// Extract the body parts from exeristing exercise information.
/// - Parameter exerciseInfo: The exercise information.
init(fromExerciseInfo exerciseInfo: ExerciseInfo) {
- self.bodyparts = ExerciseBodyPart.allCases
+ bodyparts = ExerciseBodyPart.allCases
.sorted { $0.rawValue < $1.rawValue }
.map { BodyPartSelection(bodypart: $0, enabled: exerciseInfo.bodyParts.contains($0)) }
}
-
-
+
/// Initialize with specificed body parts.
init(bodyparts: [BodyPartSelection]) {
self.bodyparts = bodyparts
}
-
-
+
enum DefaultBodyPartsSelection {
case none, all, userDefaults
}
-
+
/// Initialize with some default selection of body parts.
/// - Parameter selection: The section of body parts to use.
init(with selection: DefaultBodyPartsSelection) {
if selection == .userDefaults {
let userDefaultsData = BodyPartSelections.readUserDefaults()
- self.bodyparts = ExerciseBodyPart.allCases
+ bodyparts = ExerciseBodyPart.allCases
.sorted { $0.rawValue < $1.rawValue }
- .map{ BodyPartSelection(bodypart: $0, enabled: userDefaultsData[$0.rawValue] ?? true) }
+ .map { BodyPartSelection(bodypart: $0, enabled: userDefaultsData[$0.rawValue] ?? true) }
} else {
- self.bodyparts = ExerciseBodyPart.allCases
+ bodyparts = ExerciseBodyPart.allCases
.sorted { $0.rawValue < $1.rawValue }
.map { BodyPartSelection(bodypart: $0, enabled: selection == .all) }
}
}
-
-
+
static func readUserDefaults() -> [String: Bool] {
if let data = UserDefaults.standard.dictionary(forKey: UserDefaultsKeys.activeBodyParts.rawValue) as? [String: Bool] {
return data
@@ -64,8 +58,7 @@ class BodyPartSelections: ObservableObject {
return [String: Bool]()
}
}
-
-
+
func saveDataToUserDefaults() {
var data = [String: Bool]()
for bp in bodyparts {
diff --git a/Workout Spinner WatchKit Extension/Models/CrownVelocityCalculator.swift b/Workout Spinner WatchKit Extension/Models/CrownVelocityCalculator.swift
index 1eaa13f..24f3f00 100644
--- a/Workout Spinner WatchKit Extension/Models/CrownVelocityCalculator.swift
+++ b/Workout Spinner WatchKit Extension/Models/CrownVelocityCalculator.swift
@@ -11,52 +11,51 @@ import Foundation
class CrownVelocityCalculator {
private var history = [Double]()
var currentVelocity: Double = 0.0
-
+
var velocityThreshold: Double = 500.0
private(set) var didPassThreshold: Bool = false
-
+
var memory: Int = 10
-
+
init() {}
-
+
init(memory: Int) {
self.memory = memory
}
-
+
init(velocityThreshold: Double) {
self.velocityThreshold = velocityThreshold
}
-
+
init(velocityThreshold: Double, memory: Int) {
self.velocityThreshold = velocityThreshold
self.memory = memory
}
-
+
func update(newValue x: Double) {
- self.history.append(x)
+ history.append(x)
if history.count < memory { return }
-
+
if history.count > memory {
history = history.suffix(memory)
}
-
+
var diffs: Double = 0.0
- for i in 0..<(history.count - 1) {
- diffs += history[i+1] - history[i]
+ for i in 0 ..< (history.count - 1) {
+ diffs += history[i + 1] - history[i]
}
-
+
currentVelocity = diffs / Double(history.count - 1)
checkThreshold()
}
-
+
func checkThreshold() {
if !didPassThreshold {
didPassThreshold = abs(currentVelocity) > velocityThreshold
}
}
-
+
func resetThreshold() {
didPassThreshold = false
}
-
}
diff --git a/Workout Spinner WatchKit Extension/Models/Exercise enums.swift b/Workout Spinner WatchKit Extension/Models/Exercise enums.swift
index 64d21c3..2f7b807 100644
--- a/Workout Spinner WatchKit Extension/Models/Exercise enums.swift
+++ b/Workout Spinner WatchKit Extension/Models/Exercise enums.swift
@@ -16,12 +16,10 @@ func == (lhs: ExerciseIntensity, rhs: ExerciseIntensity) -> Bool {
return lhs.rawValue == rhs.rawValue
}
-
enum ExerciseType: String, Codable, CaseIterable {
case count, time
}
-
enum ExerciseBodyPart: String, Codable, CaseIterable {
case arms, core, legs, back, cardio, neck
}
diff --git a/Workout Spinner WatchKit Extension/Models/ExerciseInfo.swift b/Workout Spinner WatchKit Extension/Models/ExerciseInfo.swift
index 7ba04dc..8d32992 100644
--- a/Workout Spinner WatchKit Extension/Models/ExerciseInfo.swift
+++ b/Workout Spinner WatchKit Extension/Models/ExerciseInfo.swift
@@ -15,7 +15,7 @@ struct ExerciseInfo: Identifiable, Codable, Equatable {
let bodyParts: [ExerciseBodyPart]
let workoutValue: [String: Float]
var active: Bool = true
-
+
static func == (lhs: ExerciseInfo, rhs: ExerciseInfo) -> Bool {
return lhs.id == rhs.id
}
diff --git a/Workout Spinner WatchKit Extension/Models/ExerciseOptions.swift b/Workout Spinner WatchKit Extension/Models/ExerciseOptions.swift
index 0d7ee54..347ae17 100644
--- a/Workout Spinner WatchKit Extension/Models/ExerciseOptions.swift
+++ b/Workout Spinner WatchKit Extension/Models/ExerciseOptions.swift
@@ -9,21 +9,16 @@
import Foundation
class ExerciseOptions: NSObject, ObservableObject {
-
@Published var allExercises = [ExerciseInfo]()
-
+
var exercises: [ExerciseInfo] {
- get {
- return allExercises.filter { $0.active }
- }
+ return allExercises.filter { $0.active }
}
-
+
var exercisesBlacklistFiltered: [ExerciseInfo] {
- get {
- return filterBlacklistedBodyParts()
- }
+ return filterBlacklistedBodyParts()
}
-
+
override init() {
super.init()
allExercises = loadExercises()
@@ -31,7 +26,7 @@ class ExerciseOptions: NSObject, ObservableObject {
resetExerciseOptions()
}
}
-
+
/// Write exercise array to disk.
func saveExercises() {
let encoder = JSONEncoder()
@@ -42,8 +37,7 @@ class ExerciseOptions: NSObject, ObservableObject {
print("Error when encoding exercises to JSON: \(error.localizedDescription)")
}
}
-
-
+
/// Read in exercise array from disk.
/// - Returns: Array of exercises.
func loadExercises() -> [ExerciseInfo] {
@@ -57,14 +51,13 @@ class ExerciseOptions: NSObject, ObservableObject {
}
return []
}
-
-
+
func filterBlacklistedBodyParts() -> [ExerciseInfo] {
let inactiveBodyparts: [ExerciseBodyPart] = BodyPartSelections(with: .userDefaults)
.bodyparts
.filter { !$0.enabled }
.map { $0.bodypart }
-
+
return exercises.filter { exercise in
if let _ = exercise.bodyParts.first(where: { inactiveBodyparts.contains($0) }) {
return false
@@ -74,16 +67,15 @@ class ExerciseOptions: NSObject, ObservableObject {
}
}
-
-
// MARK: - Editing options array
+
extension ExerciseOptions {
/// Add a new exercise.
func append(_ exercise: ExerciseInfo) {
allExercises.append(exercise)
saveExercises()
}
-
+
/// Replace one exercise with another.
func replace(_ exercise: ExerciseInfo, with newExercise: ExerciseInfo) {
if let idx = allExercises.firstIndex(where: { $0 == exercise }) {
@@ -91,7 +83,7 @@ extension ExerciseOptions {
saveExercises()
}
}
-
+
/// Remove an exercise.
func remove(_ exercise: ExerciseInfo) {
let startCount = allExercises.count
@@ -100,7 +92,7 @@ extension ExerciseOptions {
saveExercises()
}
}
-
+
/// Remove multiple exercises.
func remove(_ exercisesToRemove: [ExerciseInfo]) {
if exercisesToRemove.count == 0 { return }
@@ -112,7 +104,7 @@ extension ExerciseOptions {
saveExercises()
}
}
-
+
/// Update an existing exercise or append it to the end of the options.
func updateOrAppend(_ exercise: ExerciseInfo) {
if let idx = allExercises.firstIndex(where: { $0 == exercise }) {
@@ -122,7 +114,7 @@ extension ExerciseOptions {
}
saveExercises()
}
-
+
/// Resest the list of exercises to default options.
func resetExerciseOptions() {
do {
@@ -134,9 +126,8 @@ extension ExerciseOptions {
}
}
-
-
// MARK: - Reading in default exercise JSON.
+
extension ExerciseOptions {
/// Parse the JSON data to an array of workouts.
/// - Parameter jsonData: JSON data as a `Data` object.
@@ -149,7 +140,7 @@ extension ExerciseOptions {
throw error
}
}
-
+
/// Read in data from a JSON file.
/// - Parameter name: Local file name.
/// - Throws: Will throw an error if the file in unreachable or cannot be converted to a `Data` object.
@@ -169,14 +160,14 @@ extension ExerciseOptions {
throw DataReadingError.fileDoesNotExist(name)
}
}
-
+
enum DataReadingError: Error, LocalizedError {
case fileDoesNotExist(String)
case cannotConvertToData
-
+
var errorDescription: String? {
switch self {
- case .fileDoesNotExist(let fileName):
+ case let .fileDoesNotExist(fileName):
return NSLocalizedString("No such file in bundle: '\(fileName)'", comment: "")
case .cannotConvertToData:
return NSLocalizedString("Cannot convert text in file to data.", comment: "")
@@ -184,8 +175,3 @@ extension ExerciseOptions {
}
}
}
-
-
-
-
-
diff --git a/Workout Spinner WatchKit Extension/Models/HeartRateGraphData.swift b/Workout Spinner WatchKit Extension/Models/HeartRateGraphData.swift
index 34b8db9..9327291 100644
--- a/Workout Spinner WatchKit Extension/Models/HeartRateGraphData.swift
+++ b/Workout Spinner WatchKit Extension/Models/HeartRateGraphData.swift
@@ -15,27 +15,29 @@ struct HeartRateGraphDatum {
}
struct HeartRateGraphData {
-
var data: [HeartRateGraphDatum]
-
+
var minX: Double {
return data.map { $0.x }.min()!
}
+
var minY: Double {
return data.map { $0.y }.min()!
}
+
var maxX: Double {
return data.map { $0.x }.max()!
}
+
var maxY: Double {
return data.map { $0.y }.max()!
}
-
+
init(workoutTraker: WorkoutTracker) {
- self.data = HeartRateGraphData.workoutTrackerDataToGraphData(workoutTraker)
+ data = HeartRateGraphData.workoutTrackerDataToGraphData(workoutTraker)
// self.data = HeartRateGraphData.mockHeartRateData()
}
-
+
static func workoutTrackerDataToGraphData(_ workoutTracker: WorkoutTracker) -> [HeartRateGraphDatum] {
var HRData = [HeartRateGraphDatum]()
var previousEndingX = 0.0
@@ -51,35 +53,34 @@ struct HeartRateGraphData {
}
return HRData
}
-
+
func convertedXData(toMin: Double, toMax: Double) -> [Double] {
return data.map { convert(x: $0.x, toMin: toMin, toMax: toMax) }
}
-
+
func convertedYData(toMin: Double, toMax: Double) -> [Double] {
return data.map { convert(y: $0.y, toMin: toMin, toMax: toMax) }
}
-
+
func convert(x: Double, toMin: Double, toMax: Double) -> Double {
- return x.rangeMap(inMin: self.minX, inMax: self.maxX, outMin: toMin, outMax: toMax)
+ return x.rangeMap(inMin: minX, inMax: maxX, outMin: toMin, outMax: toMax)
}
-
+
func convert(y: Double, toMin: Double, toMax: Double) -> Double {
- return y.rangeMap(inMin: self.minY, inMax: self.maxY, outMin: toMin, outMax: toMax)
+ return y.rangeMap(inMin: minY, inMax: maxY, outMin: toMin, outMax: toMax)
}
}
-
extension HeartRateGraphData {
static func mockHeartRateData() -> [HeartRateGraphDatum] {
var d = [HeartRateGraphDatum]()
var x = 0.0
- for i in 0..<5 {
- let n = (5...25).randomElement()!
+ for i in 0 ..< 5 {
+ let n = (5 ... 25).randomElement()!
var y = 50.0
- for _ in 0.. Int {
- let runningTime: Int = Int(-1 * (self.start.timeIntervalSinceNow))
- return self.accumulatedTime + runningTime
+ let runningTime = Int(-1 * start.timeIntervalSinceNow)
+ return accumulatedTime + runningTime
}
-
-
+
/// Request authorization to access HealthKit.
func requestAuthorization() {
// The quantity type to write to the health store.
let typesToShare: Set = [
- HKQuantityType.workoutType()
+ HKQuantityType.workoutType(),
]
-
+
// The quantity types to read from the health store.
let typesToRead: Set = [
HKQuantityType.quantityType(forIdentifier: .heartRate)!,
- HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned)!
+ HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned)!,
]
-
+
// Request authorization for those quantity types.
- healthStore.requestAuthorization(toShare: typesToShare, read: typesToRead) { (success, error) in
+ healthStore.requestAuthorization(toShare: typesToShare, read: typesToRead) { _, error in
if let error = error {
print("Error requesting data read/share authorization: \(error.localizedDescription)")
return
@@ -104,8 +100,7 @@ class WorkoutManager: NSObject, ObservableObject {
print("Successfully requesting authoriation for data reading and sharing.")
}
}
-
-
+
/// Provide the workout configuration.
internal func workoutConfiguration() -> HKWorkoutConfiguration {
let configuration = HKWorkoutConfiguration()
@@ -113,8 +108,7 @@ class WorkoutManager: NSObject, ObservableObject {
configuration.locationType = .indoor
return configuration
}
-
-
+
/// Start the workout.
internal func setupWorkout() {
// Create the session and obtain the workout builder.
@@ -126,16 +120,16 @@ class WorkoutManager: NSObject, ObservableObject {
print("Error creating workout: \(error.localizedDescription)")
return
}
-
+
// Setup session and builder.
session.delegate = self
builder.delegate = self
-
+
// Set the workout builder's data source.
builder.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore,
workoutConfiguration: workoutConfiguration())
}
-
+
/// Start a workout
func startWorkout() {
// Start the timer.
@@ -146,7 +140,7 @@ class WorkoutManager: NSObject, ObservableObject {
// Start the workout session and begin data collection.
session.startActivity(with: Date())
}
-
+
/// Pause a workout.
func pauseWorkout() {
// Pause the workout.
@@ -157,7 +151,7 @@ class WorkoutManager: NSObject, ObservableObject {
accumulatedTime = elapsedSeconds
active = false
}
-
+
/// Resume a previously started workout.
func resumeWorkout() {
// Start the timer.
@@ -166,7 +160,7 @@ class WorkoutManager: NSObject, ObservableObject {
// Resume the workout.
session.resume()
}
-
+
/// Reset all of the informational variables.
func resetTrackedInformation() {
accumulatedTime = 0
@@ -175,12 +169,12 @@ class WorkoutManager: NSObject, ObservableObject {
activeCalories = 0
elapsedSeconds = 0
}
-
+
/// End a workout.
func endWorkout() {
print("Ending workout session.")
-
- builder.endCollection(withEnd: Date()) { (success, error) in
+
+ builder.endCollection(withEnd: Date()) { success, error in
if let error = error {
print("Data collection ended with error: \(error.localizedDescription)")
} else if success {
@@ -189,69 +183,66 @@ class WorkoutManager: NSObject, ObservableObject {
print("Data collection did not end successfully (but without error).")
}
}
-
+
session.end()
active = false
resetTrackedInformation()
}
-
-
+
// MARK: - Update the UI
+
// Update the published values.
internal func updateForStatistics(_ statistics: HKStatistics?) {
guard let statistics = statistics else { return }
-
+
DispatchQueue.main.async {
switch statistics.quantityType {
case HKQuantityType.quantityType(forIdentifier: .heartRate):
let heartRateUnit = HKUnit.count().unitDivided(by: HKUnit.minute())
let value = statistics.mostRecentQuantity()?.doubleValue(for: heartRateUnit)
- let roundedValue = Double( round( 1 * value! ) / 1 )
+ let roundedValue = Double(round(1 * value!) / 1)
self.allHeartRateReadings.append(HeartRateReading(heartRate: value!))
self.heartrate = roundedValue
return
case HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned):
let energyUnit = HKUnit.kilocalorie()
let value = statistics.sumQuantity()?.doubleValue(for: energyUnit)
- self.activeCalories = Double( round( 1 * value! ) / 1 )
+ self.activeCalories = Double(round(1 * value!) / 1)
return
default:
return
}
}
}
-
+
enum MockWorkoutStatistics {
case heartRate, activeEnergyBurned
}
-
+
internal func updateMockStatistics(quantityType: MockWorkoutStatistics) {
DispatchQueue.main.async {
switch quantityType {
case .heartRate:
- self.heartrate += Double.random(in: self.heartrate == 0 ? 100...120 : -5...5)
+ self.heartrate += Double.random(in: self.heartrate == 0 ? 100 ... 120 : -5 ... 5)
self.allHeartRateReadings.append(HeartRateReading(heartRate: self.heartrate))
case .activeEnergyBurned:
- self.activeCalories += Double.random(in: 1...3)
+ self.activeCalories += Double.random(in: 1 ... 3)
}
}
}
-
}
-
-
-
// MARK: - HKWorkoutSessionDelegate
+
extension WorkoutManager: HKWorkoutSessionDelegate {
-
- func workoutSession(_ workoutSession: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState,
- from fromState: HKWorkoutSessionState, date: Date) {
+ func workoutSession(_: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState,
+ from fromState: HKWorkoutSessionState, date _: Date)
+ {
print("Workout session did change state: \(workoutStateDescription(fromState)) -> \(workoutStateDescription(toState))")
-
+
if toState == .ended {
// Wait for the session to transition states before ending the builder.
- builder.finishWorkout { (workout, error) in
+ builder.finishWorkout { _, error in
// Optionally display a workout summary to the user.
if let error = error {
print("Builder did finish with error: \(error.localizedDescription)")
@@ -260,13 +251,11 @@ extension WorkoutManager: HKWorkoutSessionDelegate {
}
}
}
-
-
- func workoutSession(_ workoutSession: HKWorkoutSession, didFailWithError error: Error) {
+
+ func workoutSession(_: HKWorkoutSession, didFailWithError error: Error) {
print("Workout session failed: \(error.localizedDescription)")
}
-
-
+
/// Return a description for the workout session state.
/// - Parameter state: A workout state.
/// - Returns: A descriptive string.
@@ -278,7 +267,7 @@ extension WorkoutManager: HKWorkoutSessionDelegate {
return "not started"
case .paused:
return "paused"
- case.prepared:
+ case .prepared:
return "prepared"
case .running:
return "running"
@@ -290,26 +279,22 @@ extension WorkoutManager: HKWorkoutSessionDelegate {
}
}
-
-
-
// MARK: - HKLiveWorkoutBuilderDelegate
+
extension WorkoutManager: HKLiveWorkoutBuilderDelegate {
-
- func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) {
+ func workoutBuilderDidCollectEvent(_: HKLiveWorkoutBuilder) {
// Does nothing for now.
}
-
-
+
func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, didCollectDataOf collectedTypes: Set) {
for type in collectedTypes {
guard let quantityType = type as? HKQuantityType else {
return // Nothing to do.
}
-
+
/// - Tag: GetStatistics
let statistics = workoutBuilder.statistics(for: quantityType)
-
+
// Update the published values.
updateForStatistics(statistics)
}
diff --git a/Workout Spinner WatchKit Extension/Models/WorkoutTracker.swift b/Workout Spinner WatchKit Extension/Models/WorkoutTracker.swift
index fb0af44..fe8f8d1 100644
--- a/Workout Spinner WatchKit Extension/Models/WorkoutTracker.swift
+++ b/Workout Spinner WatchKit Extension/Models/WorkoutTracker.swift
@@ -8,32 +8,30 @@
import Foundation
-
class WorkoutTracker: NSObject, ObservableObject {
-
/// Collected data of exercises.
var data = [WorkoutTrackerDatum]()
-
+
/// Number of exercises tracked.
var numberOfExercises: Int {
data.count
}
-
+
/// Total active calories.
var totalActiveCalories: Double {
return data.map { $0.activeCalories }.reduce(0, +)
}
-
+
/// Total duration.
var totalDuration: Double {
return data.map { $0.duration }.reduce(0, +)
}
-
+
/// Average heart rate reading.
var averageHeartRate: Double? {
if data.count < 1 { return nil }
let HRtotal = data
- .map { $0.heartRate.map({ $0.heartRate }).reduce(0, +) }
+ .map { $0.heartRate.map { $0.heartRate }.reduce(0, +) }
.reduce(0, +)
let HRcount = data
.map { $0.heartRate.count }
@@ -41,27 +39,26 @@ class WorkoutTracker: NSObject, ObservableObject {
if HRcount == 0 { return nil }
return HRtotal / Double(HRcount)
}
-
+
/// Maximum heart rate reading.
var maxHeartRate: Double? {
if data.count < 1 { return nil }
let x = data
- .map { ($0.heartRate.map({ $0.heartRate }).max()) ?? 0 }
+ .map { ($0.heartRate.map { $0.heartRate }.max()) ?? 0 }
.max() ?? 0
return x == 0.0 ? nil : x
}
-
+
/// Minimum heart rate reading.
var minHeartRate: Double? {
if data.count < 1 { return nil }
let x = data
.filter { $0.heartRate.count > 0 }
- .map { ($0.heartRate.map({ $0.heartRate }).min()) ?? 0 }
+ .map { ($0.heartRate.map { $0.heartRate }.min()) ?? 0 }
.min() ?? 0
return x == 0.0 ? nil : x
}
-
-
+
/// Add a new data point.
/// - Parameters:
/// - info: Exercise information object
@@ -72,14 +69,13 @@ class WorkoutTracker: NSObject, ObservableObject {
let newData = WorkoutTrackerDatum(exerciseInfo: info, duration: duration, activeCalories: activeCalories, heartRate: heartRate)
data.append(newData)
}
-
+
/// Clear the tracked data.
func clear() {
data = []
}
}
-
struct WorkoutTrackerDatum: Identifiable {
let id = UUID()
let exerciseInfo: ExerciseInfo
diff --git a/Workout Spinner WatchKit Extension/NotificationController.swift b/Workout Spinner WatchKit Extension/NotificationController.swift
index 40baa87..726caa4 100644
--- a/Workout Spinner WatchKit Extension/NotificationController.swift
+++ b/Workout Spinner WatchKit Extension/NotificationController.swift
@@ -6,12 +6,11 @@
// Copyright © 2020 Joshua Cook. All rights reserved.
//
-import WatchKit
import SwiftUI
import UserNotifications
+import WatchKit
class NotificationController: WKUserNotificationHostingController {
-
override var body: NotificationView {
return NotificationView()
}
@@ -26,7 +25,7 @@ class NotificationController: WKUserNotificationHostingController some View {
configuration.label
.padding(padding)
@@ -27,7 +25,6 @@ struct DoneButtonStyle: ButtonStyle {
}
}
-
struct ButtonStyles_Previews: PreviewProvider {
static var previews: some View {
Group {
diff --git a/Workout Spinner WatchKit Extension/View Modifiers/SpinnerRotationModifier.swift b/Workout Spinner WatchKit Extension/View Modifiers/SpinnerRotationModifier.swift
index 2958287..49bda54 100644
--- a/Workout Spinner WatchKit Extension/View Modifiers/SpinnerRotationModifier.swift
+++ b/Workout Spinner WatchKit Extension/View Modifiers/SpinnerRotationModifier.swift
@@ -9,47 +9,43 @@
import SwiftUI
struct SpinnerRotationModifier: AnimatableModifier {
-
var rotation: Angle
- var onFinishedRotationAnimation: () -> () = {}
+ var onFinishedRotationAnimation: () -> Void = {}
var completionTolerance: Double = 0.00001
-
+
private var targetRotation: Angle
-
+
init(rotation: Angle) {
self.rotation = rotation
- self.targetRotation = rotation
+ targetRotation = rotation
}
-
- init(rotation: Angle, onFinishedRotationAnimation: @escaping () -> ()) {
+
+ init(rotation: Angle, onFinishedRotationAnimation: @escaping () -> Void) {
self.rotation = rotation
- self.targetRotation = rotation
+ targetRotation = rotation
self.onFinishedRotationAnimation = onFinishedRotationAnimation
}
-
- init(rotation: Angle, onFinishedRotationAnimation: @escaping () -> (), completionTolerance: Double) {
+
+ init(rotation: Angle, onFinishedRotationAnimation: @escaping () -> Void, completionTolerance: Double) {
self.rotation = rotation
- self.targetRotation = rotation
+ targetRotation = rotation
self.onFinishedRotationAnimation = onFinishedRotationAnimation
self.completionTolerance = completionTolerance
}
-
-
+
var animatableData: Double {
get { rotation.degrees }
- set {
- rotation = .degrees(newValue)
- checkIfFinished()
- }
+ set {
+ rotation = .degrees(newValue)
+ checkIfFinished()
+ }
}
-
-
+
func body(content: Content) -> some View {
content
.rotationEffect(rotation)
}
-
-
+
func checkIfFinished() {
if abs(rotation.degrees - targetRotation.degrees) < completionTolerance {
DispatchQueue.main.async { self.onFinishedRotationAnimation() }
@@ -57,7 +53,6 @@ struct SpinnerRotationModifier: AnimatableModifier {
}
}
-
struct SpinnerRotationModifier_Previews: PreviewProvider {
static var previews: some View {
ZStack {
diff --git a/Workout Spinner WatchKit Extension/Views/ContentView.swift b/Workout Spinner WatchKit Extension/Views/ContentView.swift
index 81b9a86..5f2c249 100644
--- a/Workout Spinner WatchKit Extension/Views/ContentView.swift
+++ b/Workout Spinner WatchKit Extension/Views/ContentView.swift
@@ -9,11 +9,10 @@
import SwiftUI
struct ContentView: View {
-
let workoutManager = WorkoutManager()
let workoutTracker = WorkoutTracker()
let exerciseOptions = ExerciseOptions()
-
+
var body: some View {
WelcomeView(workoutManager: workoutManager, workoutTracker: workoutTracker, exerciseOptions: exerciseOptions)
}
diff --git a/Workout Spinner WatchKit Extension/Views/ExerciseFinishView.swift b/Workout Spinner WatchKit Extension/Views/ExerciseFinishView.swift
index ce2ea8b..bd19269 100644
--- a/Workout Spinner WatchKit Extension/Views/ExerciseFinishView.swift
+++ b/Workout Spinner WatchKit Extension/Views/ExerciseFinishView.swift
@@ -12,9 +12,9 @@ struct SmallImageAndTextView: View {
let imageName: String
let text: String
let imageColor: Color
-
+
let font: Font = .system(size: 14)
-
+
var body: some View {
HStack {
Image(systemName: imageName).font(font).foregroundColor(imageColor)
@@ -24,13 +24,12 @@ struct SmallImageAndTextView: View {
}
struct ExerciseDataRowView: View {
-
let data: WorkoutTrackerDatum
-
+
var averageHeartRate: Int {
- return Int(average(data.heartRate.map({ $0.heartRate })).rounded())
+ return Int(average(data.heartRate.map { $0.heartRate }).rounded())
}
-
+
var body: some View {
VStack {
HStack {
@@ -39,7 +38,7 @@ struct ExerciseDataRowView: View {
}.padding(.bottom, 2)
HStack {
Spacer()
- SmallImageAndTextView(imageName: "heart", text: data.heartRate.count == 0 ? "NA" : "\(averageHeartRate)", imageColor: .red)
+ SmallImageAndTextView(imageName: "heart", text: data.heartRate.count == 0 ? "NA" : "\(averageHeartRate)", imageColor: .red)
Spacer()
SmallImageAndTextView(imageName: "flame", text: "\(Int(data.activeCalories.rounded()))", imageColor: .yellow)
Spacer()
@@ -48,7 +47,7 @@ struct ExerciseDataRowView: View {
}
}
}
-
+
internal func average(_ x: [Double]) -> Double {
if x.count == 0 {
return 0
@@ -58,9 +57,8 @@ struct ExerciseDataRowView: View {
}
struct ExerciseFinishView: View {
-
@ObservedObject var workoutTracker: WorkoutTracker
-
+
var body: some View {
List {
ForEach(workoutTracker.data) { data in
diff --git a/Workout Spinner WatchKit Extension/Views/ExercisePicker.swift b/Workout Spinner WatchKit Extension/Views/ExercisePicker.swift
index 6b82949..7e5f627 100644
--- a/Workout Spinner WatchKit Extension/Views/ExercisePicker.swift
+++ b/Workout Spinner WatchKit Extension/Views/ExercisePicker.swift
@@ -6,34 +6,33 @@
// Copyright © 2020 Joshua Cook. All rights reserved.
//
-import SwiftUI
import Combine
+import SwiftUI
struct ExercisePicker: View {
-
@ObservedObject var workoutManager: WorkoutManager
@ObservedObject var exerciseOptions: ExerciseOptions
@State internal var crownRotation = 0.0
-
+
var numExercises: Int {
return exerciseOptions.exercisesBlacklistFiltered.count
}
-
+
var crownVelocity = CrownVelocityCalculator(velocityThreshold: 50, memory: 20)
-
+
@Binding internal var exerciseSelected: Bool
@State internal var selectedExerciseIndex: Int = 0
-
+
var spinDirection: Double {
return WKInterfaceDevice().crownOrientation == .left ? 1.0 : -1.0
}
-
+
init(workoutManager: WorkoutManager, exerciseOptions: ExerciseOptions, exerciseSelected: Binding) {
self.workoutManager = workoutManager
self.exerciseOptions = exerciseOptions
- self._exerciseSelected = exerciseSelected
+ _exerciseSelected = exerciseSelected
}
-
+
var body: some View {
ZStack {
VStack(spacing: 0) {
@@ -41,12 +40,12 @@ struct ExercisePicker: View {
GeometryReader { geo in
ZStack {
ZStack {
- ForEach(0..) {
self.workoutManager = workoutManager
- self._exerciseCanceled = exerciseCanceled
+ _exerciseCanceled = exerciseCanceled
exerciseInfo = workoutManager.exerciseInfo
}
-
+
var body: some View {
VStack {
-
Text(exerciseInfo?.displayName ?? "(no workout selected)")
.lineLimit(1)
.font(.system(size: 25, weight: .regular, design: .rounded))
-
+
Spacer()
-
+
Text(displayDuration)
- .font(.system(size: 40, weight: .semibold , design: .rounded))
+ .font(.system(size: 40, weight: .semibold, design: .rounded))
.foregroundColor(.yellow)
-
+
Spacer()
-
-
+
HStack {
Text("Starting in")
Text("\(timeRemaining)")
@@ -69,20 +66,18 @@ struct ExerciseStartView: View {
.padding(EdgeInsets(top: 5, leading: 15, bottom: 5, trailing: 15))
.background(Color.gray.opacity(0.3))
.clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
-
+
Spacer()
-
+
Text("Double tap to cancel").foregroundColor(.gray).font(.footnote)
-
}
- .onReceive(timer) { time in
+ .onReceive(timer) { _ in
if self.timeRemaining > 0 {
self.timeRemaining -= 1
} else if self.timeRemaining <= 0 {
exerciseCanceled = false
self.presentationMode.wrappedValue.dismiss()
}
-
}
.edgesIgnoringSafeArea(.bottom)
.onTapGesture(count: 2) {
@@ -96,7 +91,6 @@ struct ExerciseStartView: View {
}
}
-
extension ExerciseStartView {
static func loadExerciseIntensity() -> ExerciseIntensity {
if let intensityString = UserDefaults.standard.string(forKey: UserDefaultsKeys.exerciseIntensity.rawValue) {
@@ -108,10 +102,7 @@ extension ExerciseStartView {
}
}
-
-
struct WorkoutStartView_Previews: PreviewProvider {
-
static var workouts: ExerciseOptions {
let ws = ExerciseOptions()
let i = ws.exercises.first { $0.type == .count }!
@@ -119,7 +110,7 @@ struct WorkoutStartView_Previews: PreviewProvider {
ws.allExercises = [i, j]
return ws
}
-
+
static var previews: some View {
Group {
ForEach(workouts.exercises) { info in
diff --git a/Workout Spinner WatchKit Extension/Views/ExerciseView.swift b/Workout Spinner WatchKit Extension/Views/ExerciseView.swift
index 3406356..1675a62 100644
--- a/Workout Spinner WatchKit Extension/Views/ExerciseView.swift
+++ b/Workout Spinner WatchKit Extension/Views/ExerciseView.swift
@@ -9,23 +9,22 @@
import SwiftUI
struct ExerciseView: View {
-
@ObservedObject var workoutManager: WorkoutManager
@ObservedObject var workoutTracker: WorkoutTracker
@Binding private var exerciseComplete: Bool
var workoutInfo: ExerciseInfo?
-
+
let intensity: ExerciseIntensity = ExerciseStartView.loadExerciseIntensity()
-
+
init(workoutManager: WorkoutManager, workoutTracker: WorkoutTracker, exerciseComplete: Binding) {
self.workoutManager = workoutManager
self.workoutTracker = workoutTracker
- self._exerciseComplete = exerciseComplete
+ _exerciseComplete = exerciseComplete
workoutInfo = workoutManager.exerciseInfo
}
-
+
let infoFontSize: CGFloat = 18
-
+
/// The duration as "MM:SS".
var displayDuration: String {
guard let info = workoutManager.exerciseInfo else { return "" }
@@ -38,12 +37,12 @@ struct ExerciseView: View {
}
return intensity.rawValue
}
-
+
/// The heart rate information to display.
var displayHeartRate: String {
workoutManager.allHeartRateReadings.count == 0 ? "--" : "\(Int(workoutManager.heartrate))"
}
-
+
var body: some View {
VStack {
Text(workoutInfo?.displayName ?? "(no workout)")
@@ -51,44 +50,42 @@ struct ExerciseView: View {
.padding(.bottom, 2)
Text(displayDuration).font(.system(size: 20, weight: .regular, design: .rounded))
.padding(.bottom, 5)
-
+
HStack {
Spacer()
HStack {
Image(systemName: "heart").foregroundColor(.red).font(.system(size: infoFontSize))
Text(displayHeartRate).font(.system(size: infoFontSize))
}
-
+
Spacer()
-
+
HStack {
Image(systemName: "flame").foregroundColor(.yellow).font(.system(size: infoFontSize))
Text("\(Int(workoutManager.activeCalories))").font(.system(size: infoFontSize))
}
-
+
Spacer()
}
.padding(.bottom, 5)
-
+
HStack {
Image(systemName: "stopwatch").foregroundColor(.blue).font(.system(size: infoFontSize))
Text("\(convertNumberSecondsToTimeFormat(Double(workoutManager.elapsedSeconds)))").font(.system(size: infoFontSize))
}
-
-
-
+
Spacer()
-
+
Button(action: finishExercise, label: {
Text("Done").foregroundColor(.green).bold()
})
}
}
-
+
func convertNumberSecondsToTimeFormat(_ seconds: Double) -> String {
let minutes = (seconds / 60.0).rounded(.down)
let remainderSeconds = seconds.truncatingRemainder(dividingBy: 60.0).rounded()
-
+
func doubleToPaddedString(_ x: Double, paddingLength: Int = 2) -> String {
var s = "\(Int(x))"
if s.count < paddingLength {
@@ -96,31 +93,23 @@ struct ExerciseView: View {
}
return s
}
-
+
return "\(doubleToPaddedString(minutes)):\(doubleToPaddedString(remainderSeconds))"
}
}
-
extension ExerciseView {
/// Called when the exercise is complete and the 'Done" button is tapped.
- internal func finishExercise() {
+ func finishExercise() {
// Add data from exercise to the workout tracker and clear the info from the workout manager.
workoutTracker.addData(info: workoutManager.exerciseInfo!, duration: Double(workoutManager.elapsedSeconds), activeCalories: workoutManager.activeCalories, heartRate: workoutManager.allHeartRateReadings)
workoutManager.resetTrackedInformation()
-
+
exerciseComplete = true
}
}
-
-
-
-
-
-
struct WorkoutView_Previews: PreviewProvider {
-
static var workoutOptions: ExerciseOptions {
let ws = ExerciseOptions()
let i = ws.exercises.first { $0.type == .count }!
@@ -128,7 +117,7 @@ struct WorkoutView_Previews: PreviewProvider {
ws.allExercises = [i, j]
return ws
}
-
+
static var previews: some View {
Group {
ForEach(workoutOptions.exercises) { info in
diff --git a/Workout Spinner WatchKit Extension/Views/General Components/Buttons.swift b/Workout Spinner WatchKit Extension/Views/General Components/Buttons.swift
index ee39082..fadac63 100644
--- a/Workout Spinner WatchKit Extension/Views/General Components/Buttons.swift
+++ b/Workout Spinner WatchKit Extension/Views/General Components/Buttons.swift
@@ -9,10 +9,9 @@
import SwiftUI
struct ListViewTextButton: View {
-
var label: String
var action: () -> Void
-
+
var body: some View {
Button(action: action) {
Text(label)
diff --git a/Workout Spinner WatchKit Extension/Views/General Components/SectionHeader.swift b/Workout Spinner WatchKit Extension/Views/General Components/SectionHeader.swift
index d98f173..6d4aea9 100644
--- a/Workout Spinner WatchKit Extension/Views/General Components/SectionHeader.swift
+++ b/Workout Spinner WatchKit Extension/Views/General Components/SectionHeader.swift
@@ -11,7 +11,7 @@ import SwiftUI
struct SectionHeader: View {
let imageName: String
let text: String
-
+
var body: some View {
HStack {
Image(systemName: imageName)
@@ -20,7 +20,6 @@ struct SectionHeader: View {
}
}
-
struct SectionHeader_Previews: PreviewProvider {
static var previews: some View {
SectionHeader(imageName: "mustache", text: "Text")
diff --git a/Workout Spinner WatchKit Extension/Views/HeartRateGraph/GraphBackgroundSegment.swift b/Workout Spinner WatchKit Extension/Views/HeartRateGraph/GraphBackgroundSegment.swift
index ad7655e..a0d0a32 100644
--- a/Workout Spinner WatchKit Extension/Views/HeartRateGraph/GraphBackgroundSegment.swift
+++ b/Workout Spinner WatchKit Extension/Views/HeartRateGraph/GraphBackgroundSegment.swift
@@ -9,61 +9,57 @@
import SwiftUI
struct GraphBackgroundSegment: View {
-
var graphData: HeartRateGraphData
var size: CGSize
var segmentData: [RectangleSegment]
-
+
var totalSegmentWidth: Double {
- return segmentData.reduce(0, { $0 + $1.width })
+ return segmentData.reduce(0) { $0 + $1.width }
}
-
+
var maxSegmentHeight: Double {
return segmentData.map { $0.height }.max()!
}
-
+
init(graphData: HeartRateGraphData, size: CGSize) {
self.graphData = graphData
self.size = size
segmentData = GraphBackgroundSegment.makeSegmentData(from: graphData)
}
-
+
var body: some View {
HStack(alignment: .center, spacing: 0) {
- ForEach(0.. CGFloat {
CGFloat(width.rangeMap(inMin: 0, inMax: totalSegmentWidth, outMin: 0, outMax: Double(size.width)))
}
-
-
+
/// Scale a value to the height of the entire frame.
/// - Parameter height: Height to be scaled.
/// - Returns: Scaled height.
func scaleHeight(_ height: Double) -> CGFloat {
CGFloat(height.rangeMap(inMin: 0, inMax: maxSegmentHeight, outMin: 0, outMax: Double(size.height)))
}
-
-
+
struct RectangleSegment {
let width: Double
let height: Double
let color: Color
}
-
-
+
/// Create the data for building the segments.
/// - Parameter graphData: The graph data.
/// - Returns: The segment data.
@@ -71,7 +67,7 @@ struct GraphBackgroundSegment: View {
var data = [RectangleSegment]()
let groupIndices = Array(Set(graphData.data.map { $0.groupIndex })).sorted()
let colors: [Color] = colorArray(numberOfColors: groupIndices.count)
-
+
for idx in groupIndices {
let xValues = graphData.data.filter { $0.groupIndex == idx }.map { $0.x }
let minX = xValues.min()!
@@ -80,30 +76,29 @@ struct GraphBackgroundSegment: View {
let maxY = graphData.maxY
data.append(RectangleSegment(width: maxX - minX, height: maxY - minY, color: colors[idx]))
}
-
+
return data
}
-
-
+
/// Create an array of colors across the complete rainbow.
/// - Parameter n: The number of colors to create.
/// - Returns: An array of SwiftUI `Color` views.
static func colorArray(numberOfColors n: Int) -> [Color] {
var colors = [Color]()
-
+
let jump = 3.0 / Double(n)
var val: Double = 0
- for _ in 0.. Path {
var path = Path()
-
+
let width = Double(rect.size.width)
let height = Double(rect.size.height)
-
+
let xData = graphData.convertedXData(toMin: 0.0, toMax: width)
let yData = graphData.convertedYData(toMin: height, toMax: 0.0)
-
+
path.move(to: CGPoint(x: xData[0], y: yData[0]))
for (x, y) in zip(xData, yData) {
path.addLine(to: CGPoint(x: x, y: y))
}
-
+
return path
}
}
diff --git a/Workout Spinner WatchKit Extension/Views/HeartRateGraph/HeartRateGraphView.swift b/Workout Spinner WatchKit Extension/Views/HeartRateGraph/HeartRateGraphView.swift
index e8cf0da..57bc8ba 100644
--- a/Workout Spinner WatchKit Extension/Views/HeartRateGraph/HeartRateGraphView.swift
+++ b/Workout Spinner WatchKit Extension/Views/HeartRateGraph/HeartRateGraphView.swift
@@ -9,29 +9,28 @@
import SwiftUI
struct HeartRateGraphView: View {
-
var graphData: HeartRateGraphData
-
+
let numYGridLines = 3
var yGridValues: [Double] {
let min = graphData.minY.rounded(.down)
let max = graphData.maxY.rounded(.up)
var values = [min, max]
let gap = ((max - min) / Double(numYGridLines + 1))
- for i in 1...numYGridLines {
+ for i in 1 ... numYGridLines {
values.append(min + (gap * Double(i)))
}
values.sort { $0 > $1 }
return values
}
-
+
var gridTextWidth: CGFloat = 25
var verticalPadding: CGFloat = 10
-
- init (workoutTracker: WorkoutTracker) {
+
+ init(workoutTracker: WorkoutTracker) {
graphData = HeartRateGraphData(workoutTraker: workoutTracker)
}
-
+
var body: some View {
GeometryReader { geo in
HStack(alignment: .center, spacing: 0) {
@@ -64,8 +63,7 @@ struct HeartRateGraphView: View {
}
}
}
-
-
+
func reduceGeoSize(_ geo: GeometryProxy, widthBy widthAdjust: CGFloat = 0, heightBy heightAdjust: CGFloat = 0) -> CGSize {
var modSize = geo.size
modSize.width = modSize.width - widthAdjust
@@ -74,7 +72,6 @@ struct HeartRateGraphView: View {
}
}
-
struct HeartRateGraphView_Previews: PreviewProvider {
static var previews: some View {
HeartRateGraphView(workoutTracker: WorkoutTracker())
@@ -82,4 +79,3 @@ struct HeartRateGraphView_Previews: PreviewProvider {
.previewLayout(.fixed(width: 300, height: 200))
}
}
-
diff --git a/Workout Spinner WatchKit Extension/Views/HeartRateGraph/PlotHVGridText.swift b/Workout Spinner WatchKit Extension/Views/HeartRateGraph/PlotHVGridText.swift
index 4be2633..1cec6a4 100644
--- a/Workout Spinner WatchKit Extension/Views/HeartRateGraph/PlotHVGridText.swift
+++ b/Workout Spinner WatchKit Extension/Views/HeartRateGraph/PlotHVGridText.swift
@@ -9,14 +9,13 @@
import SwiftUI
struct PlotHVGridText: View {
-
let value: Double
let horizontal: Bool
let graphData: HeartRateGraphData
let size: CGSize
-
+
let fontSize: CGFloat = 10
-
+
var position: CGPoint {
var mappedValue = 0.0
if horizontal {
diff --git a/Workout Spinner WatchKit Extension/Views/HeartRateGraph/PlotHVLines.swift b/Workout Spinner WatchKit Extension/Views/HeartRateGraph/PlotHVLines.swift
index aace71e..9d25f5a 100644
--- a/Workout Spinner WatchKit Extension/Views/HeartRateGraph/PlotHVLines.swift
+++ b/Workout Spinner WatchKit Extension/Views/HeartRateGraph/PlotHVLines.swift
@@ -9,27 +9,26 @@
import SwiftUI
struct PlotHVLines: Shape {
-
let value: Double
let horizontal: Bool
let graphData: HeartRateGraphData
-
+
func path(in rect: CGRect) -> Path {
var path = Path()
-
+
let width = Double(rect.size.width)
let height = Double(rect.size.height)
var mappedValue = 0.0
-
+
if horizontal {
mappedValue = graphData.convert(y: value, toMin: height, toMax: 0)
} else {
mappedValue = graphData.convert(x: value, toMin: 0, toMax: width)
}
-
+
path.move(to: CGPoint(x: horizontal ? 0 : mappedValue, y: horizontal ? mappedValue : 0))
path.addLine(to: CGPoint(x: horizontal ? width : 0, y: horizontal ? mappedValue : 0))
-
+
return path
}
}
diff --git a/Workout Spinner WatchKit Extension/Views/Settings Views/BodyPartSelectionListView.swift b/Workout Spinner WatchKit Extension/Views/Settings Views/BodyPartSelectionListView.swift
index 2d0ed49..602b7c1 100644
--- a/Workout Spinner WatchKit Extension/Views/Settings Views/BodyPartSelectionListView.swift
+++ b/Workout Spinner WatchKit Extension/Views/Settings Views/BodyPartSelectionListView.swift
@@ -8,14 +8,12 @@
import SwiftUI
-
struct BodyPartSelectionListView: View {
-
@ObservedObject var bodyparts = BodyPartSelections(with: .userDefaults)
-
+
var body: some View {
List {
- ForEach(0.. Int {
if value == nil {
return defaultValue
@@ -83,8 +78,7 @@ struct EditExerciseView: View {
return Int(value!)
}
}
-
-
+
var exerciseCountsHeader: String {
switch ExerciseType.allCases[exerciseTypeIndex] {
case .count:
@@ -93,28 +87,27 @@ struct EditExerciseView: View {
return "duration"
}
}
-
-
+
var body: some View {
Form {
Section(header: Text("Exercise name")) {
TextField("Exercise name", text: $name)
}
-
+
Section {
Toggle("Include exercise", isOn: $active)
}
-
+
Section(header: Text("Exercise type")) {
Picker(selection: $exerciseTypeIndex, label: Text("Exercise type")) {
- ForEach(0.. Int {
let exerciseIntensity = UserDefaults.standard.string(forKey: UserDefaultsKeys.exerciseIntensity.rawValue)
for (idx, intensity) in ExerciseIntensity.allCases.enumerated() {
@@ -107,9 +101,6 @@ extension Settings {
}
}
-
-
-
struct Settings_Previews: PreviewProvider {
static var previews: some View {
Settings(exerciseOptions: ExerciseOptions())
diff --git a/Workout Spinner WatchKit Extension/Views/Spinner Subviews/SpinnerSlice.swift b/Workout Spinner WatchKit Extension/Views/Spinner Subviews/SpinnerSlice.swift
index 4d6c9cb..dbc59f9 100644
--- a/Workout Spinner WatchKit Extension/Views/Spinner Subviews/SpinnerSlice.swift
+++ b/Workout Spinner WatchKit Extension/Views/Spinner Subviews/SpinnerSlice.swift
@@ -9,14 +9,13 @@
import SwiftUI
struct SpinnerSliceShape: Shape {
-
let radius: CGFloat
let angle: Angle
-
+
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: rect.midX, y: rect.midY))
- path.addArc(center: CGPoint(x: rect.midX, y:rect.midY),
+ path.addArc(center: CGPoint(x: rect.midX, y: rect.midY),
radius: radius,
startAngle: angle * 0.5,
endAngle: angle * (-0.5),
@@ -25,21 +24,19 @@ struct SpinnerSliceShape: Shape {
}
}
-
struct SpinnerSlice: View {
-
let idx: Int
let numberOfSlices: Int
let width: CGFloat
-
+
var sliceAngle: Angle {
Angle.degrees(360.0 / Double(numberOfSlices))
}
-
+
var rotationAngle: Angle {
Angle.degrees(Double(idx) * 360.0 / Double(numberOfSlices))
}
-
+
var body: some View {
ZStack {
Color.darkGray
@@ -52,11 +49,10 @@ struct SpinnerSlice: View {
}
}
-
struct SpinnerSlice_Previews: PreviewProvider {
static var previews: some View {
VStack {
- ForEach(3..<8) { i in
+ ForEach(3 ..< 8) { i in
SpinnerSlice(idx: 1, numberOfSlices: i, width: 100)
.padding()
}
diff --git a/Workout Spinner WatchKit Extension/Views/Spinner Subviews/WorkoutSlice.swift b/Workout Spinner WatchKit Extension/Views/Spinner Subviews/WorkoutSlice.swift
index 92c7a28..efae44b 100644
--- a/Workout Spinner WatchKit Extension/Views/Spinner Subviews/WorkoutSlice.swift
+++ b/Workout Spinner WatchKit Extension/Views/Spinner Subviews/WorkoutSlice.swift
@@ -9,21 +9,20 @@
import SwiftUI
struct WorkoutSlice: View {
-
let workoutInfo: ExerciseInfo
let idx: Int
let numberOfWorkouts: Int
let size: CGFloat
-
+
let offset: CGFloat = 8.0
var halfOffset: CGFloat {
(offset / 2.0) - 2.0
}
-
+
var rotationAngle: Angle {
.degrees(Double(idx) * 360.0 / Double(numberOfWorkouts))
}
-
+
var body: some View {
Text(workoutInfo.displayName)
.rotationEffect(.degrees(180))
@@ -37,14 +36,13 @@ struct WorkoutSlice: View {
}
}
-
struct WorkoutSlice_Previews: PreviewProvider {
static var previews: some View {
ZStack {
Color.white
-
+
VStack {
- ForEach(3..<8) { i in
+ ForEach(3 ..< 8) { i in
WorkoutSlice(workoutInfo: ExerciseOptions().exercises[0], idx: 1, numberOfWorkouts: i, size: 200)
.padding()
}
diff --git a/Workout Spinner WatchKit Extension/Views/WelcomeView.swift b/Workout Spinner WatchKit Extension/Views/WelcomeView.swift
index 94da1e2..63de664 100644
--- a/Workout Spinner WatchKit Extension/Views/WelcomeView.swift
+++ b/Workout Spinner WatchKit Extension/Views/WelcomeView.swift
@@ -8,7 +8,6 @@
import SwiftUI
-
struct StartWorkoutButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
@@ -21,19 +20,17 @@ struct StartWorkoutButtonStyle: ButtonStyle {
}
struct WelcomeView: View {
-
@ObservedObject var workoutManager: WorkoutManager
@ObservedObject var workoutTracker: WorkoutTracker
@ObservedObject var exerciseOptions: ExerciseOptions
-
+
@State private var startWorkout = false
@State private var presentSettingsView = false
-
+
var body: some View {
VStack {
-
Spacer(minLength: 0)
-
+
NavigationLink(destination: WorkoutPagingView(workoutManager: workoutManager, workoutTracker: workoutTracker, exerciseOptions: exerciseOptions)) {
Text("Start Workout")
.font(.system(size: 30))
@@ -43,14 +40,14 @@ struct WelcomeView: View {
}
.buttonStyle(StartWorkoutButtonStyle())
.padding(.bottom, 5)
-
+
Text("Press and hold the spinner to finish the workout.")
.font(.system(size: 14))
.foregroundColor(.gray)
.multilineTextAlignment(.leading)
-
+
Spacer(minLength: 0)
-
+
Button(action: {
presentSettingsView = true
}) {
diff --git a/Workout Spinner WatchKit Extension/Views/WorkoutFinishView.swift b/Workout Spinner WatchKit Extension/Views/WorkoutFinishView.swift
index dd36a3d..48d400d 100644
--- a/Workout Spinner WatchKit Extension/Views/WorkoutFinishView.swift
+++ b/Workout Spinner WatchKit Extension/Views/WorkoutFinishView.swift
@@ -8,12 +8,11 @@
import SwiftUI
-
struct InfoRowView: View {
let title: String
let titleColor: Color
let value: String
-
+
var body: some View {
VStack {
HStack {
@@ -32,11 +31,11 @@ struct LinkedInfoRowView: View {
let title: String
let titleColor: Color
let value: String
-
+
var showEllipsis = true
-
+
let action: () -> Void
-
+
var body: some View {
Button(action: action) {
ZStack {
@@ -53,32 +52,30 @@ struct LinkedInfoRowView: View {
}
}
-
-
struct WorkoutFinishView: View {
-
@ObservedObject var workoutManager: WorkoutManager
@ObservedObject var workoutTracker: WorkoutTracker
-
+
@Environment(\.presentationMode) var presentationMode
-
+
private var averageHR: String {
return valueAsIntStringOrNA(workoutTracker.averageHeartRate)
}
+
private var minHR: String {
return valueAsIntStringOrNA(workoutTracker.minHeartRate)
}
+
private var maxHR: String {
return valueAsIntStringOrNA(workoutTracker.maxHeartRate)
}
-
+
@State private var showAllExercises: Bool = false
@State private var showHeartRateChartView: Bool = false
-
+
var body: some View {
VStack {
List {
-
LinkedInfoRowView(title: "Number of exercises", titleColor: .green, value: "\(workoutTracker.numberOfExercises)") {
showAllExercises = true
}
@@ -92,9 +89,9 @@ struct WorkoutFinishView: View {
}
})
}
-
+
InfoRowView(title: "Active Calories", titleColor: .yellow, value: "\(Int(workoutTracker.totalActiveCalories))")
-
+
LinkedInfoRowView(title: "Average heart rate", titleColor: .red, value: averageHR, showEllipsis: averageHR != "NA") {
if averageHR != "NA" {
showHeartRateChartView = true
@@ -110,9 +107,9 @@ struct WorkoutFinishView: View {
}
})
}
-
+
InfoRowView(title: "Min/Max heart rate", titleColor: .red, value: "\(minHR) / \(maxHR)")
-
+
ListViewTextButton(label: "Done") {
presentationMode.wrappedValue.dismiss()
workoutTracker.clear()
@@ -122,7 +119,7 @@ struct WorkoutFinishView: View {
}
.navigationBarBackButtonHidden(true)
}
-
+
func valueAsIntStringOrNA(_ x: Double?) -> String {
return x == nil ? "NA" : "\(Int(x!.rounded()))"
}
diff --git a/Workout Spinner WatchKit Extension/Views/WorkoutPagingView.swift b/Workout Spinner WatchKit Extension/Views/WorkoutPagingView.swift
index 4cc8a9a..c13b654 100644
--- a/Workout Spinner WatchKit Extension/Views/WorkoutPagingView.swift
+++ b/Workout Spinner WatchKit Extension/Views/WorkoutPagingView.swift
@@ -9,19 +9,18 @@
import SwiftUI
struct WorkoutPagingView: View {
-
@ObservedObject var workoutManager: WorkoutManager
@ObservedObject var workoutTracker: WorkoutTracker
@ObservedObject var exerciseOptions: ExerciseOptions
-
+
@State private var currentPageIndex: Int = 0
@State private var exerciseSelectedByPicker = false
@State private var exerciseCanceled = false
@State private var exerciseComplete = false
@State private var confirmFinishWorkout = false
-
+
@Environment(\.presentationMode) var presentationMode
-
+
var body: some View {
ZStack {
if currentPageIndex == 0 {
@@ -71,9 +70,7 @@ struct WorkoutPagingView: View {
}
}
-
extension WorkoutPagingView {
-
/// Complete a single exercise.
func finishExercise() {
switch workoutManager.session.state {
@@ -83,7 +80,7 @@ extension WorkoutPagingView {
break
}
}
-
+
/// Start an exercise.
func startExercise() {
switch workoutManager.session.state {
@@ -95,7 +92,7 @@ extension WorkoutPagingView {
break
}
}
-
+
/// Complete the entire workout session.
func finishWorkout() {
workoutManager.endWorkout()
@@ -107,7 +104,6 @@ extension WorkoutPagingView {
}
}
-
struct WorkoutPagingView_Previews: PreviewProvider {
static var previews: some View {
WorkoutPagingView(workoutManager: WorkoutManager(), workoutTracker: WorkoutTracker(), exerciseOptions: ExerciseOptions())