-
Notifications
You must be signed in to change notification settings - Fork 9
ADD - Event export possibility #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
b1b359d
270703b
d21ae6d
a6dbac6
d118aad
d52cdc3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
|
||
import HealthKit | ||
import MBLibrary | ||
import EventKit | ||
|
||
public protocol WorkoutDelegate: AnyObject { | ||
|
||
|
@@ -568,4 +569,169 @@ public class Workout: Equatable { | |
} | ||
} | ||
|
||
private let typeRow = 0 | ||
private let startRow = 1 | ||
private let endRow = 2 | ||
private let durationRow = 3 | ||
private var distanceRow: Int? { | ||
guard self.totalDistance != nil else { | ||
return nil | ||
} | ||
|
||
return 1 + durationRow | ||
} | ||
private var avgHeartRow: Int? { | ||
guard self.avgHeart != nil else { | ||
return nil | ||
} | ||
|
||
return 1 + (distanceRow ?? durationRow) | ||
} | ||
private var maxHeartRow: Int? { | ||
guard self.maxHeart != nil else { | ||
return nil | ||
} | ||
|
||
let base = [avgHeartRow, distanceRow].lazy.compactMap { $0 }.first ?? durationRow | ||
return 1 + base | ||
} | ||
private var paceRow: Int? { | ||
guard self.pace != nil else { | ||
return nil | ||
} | ||
|
||
let base = [maxHeartRow, avgHeartRow, distanceRow].lazy.compactMap { $0 }.first ?? durationRow | ||
return 1 + base | ||
} | ||
private var speedRow: Int? { | ||
guard self.speed != nil else { | ||
return nil | ||
} | ||
|
||
let base = [paceRow, maxHeartRow, avgHeartRow, distanceRow].lazy.compactMap { $0 }.first ?? durationRow | ||
return 1 + base | ||
} | ||
private var energyRow: Int? { | ||
guard self.totalEnergy != nil else { | ||
return nil | ||
} | ||
|
||
let base = [speedRow, paceRow, maxHeartRow, avgHeartRow, distanceRow].lazy.compactMap { $0 }.first ?? durationRow | ||
return 1 + base | ||
} | ||
private var elevationRow: Int? { | ||
let (asc, desc) = self.elevationChange | ||
guard asc != nil || desc != nil else { | ||
return nil | ||
} | ||
|
||
let base = [energyRow, speedRow, paceRow, maxHeartRow, avgHeartRow, distanceRow].lazy.compactMap { $0 }.first ?? durationRow | ||
return 1 + base | ||
} | ||
|
||
public func ICSexport(for systemOfUnits: SystemOfUnits, _ callback: @escaping (URL?) -> Void) { | ||
guard isLoaded, !hasError, | ||
EKEventStore.authorizationStatus(for: EKEntityType.event) != EKAuthorizationStatus.denied, | ||
EKEventStore.authorizationStatus(for: EKEntityType.event) != EKAuthorizationStatus.restricted | ||
else { | ||
callback(nil) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not a real failure, we should prompt the user to update the authorization for calendar access |
||
return | ||
} | ||
|
||
DispatchQueue.background.async { | ||
let eventStore = EKEventStore() | ||
|
||
var description = "" | ||
let nbLines = [self.typeRow, self.startRow, self.endRow, self.durationRow, self.distanceRow, self.avgHeartRow, self.maxHeartRow, self.paceRow, self.speedRow, self.energyRow, self.elevationRow].lazy.compactMap { $0 }.count | ||
for n in 0...nbLines { | ||
maxime-killinger marked this conversation as resolved.
Show resolved
Hide resolved
|
||
switch n { | ||
case self.typeRow: | ||
description += NSLocalizedString("WRKT_TYPE", comment: "Type") | ||
description += NSLocalizedString("WRKT_EVENT_SEPARATOR", comment: "Separator") | ||
description += self.name + "\n" | ||
case self.startRow: | ||
description += NSLocalizedString("WRKT_START", comment: "Start") | ||
description += NSLocalizedString("WRKT_EVENT_SEPARATOR", comment: "Separator") | ||
description += self.startDate.formattedDateTime + "\n" | ||
case self.endRow: | ||
description += NSLocalizedString("WRKT_END", comment: "End") | ||
description += NSLocalizedString("WRKT_EVENT_SEPARATOR", comment: "Separator") | ||
description += self.endDate.formattedDateTime + "\n" | ||
case self.durationRow: | ||
description += NSLocalizedString("WRKT_DURATION", comment: "Duration") | ||
description += NSLocalizedString("WRKT_EVENT_SEPARATOR", comment: "Separator") | ||
description += self.duration.formattedDuration + "\n" | ||
case self.distanceRow: | ||
description += NSLocalizedString("WRKT_DISTANCE", comment: "Distance") | ||
description += NSLocalizedString("WRKT_EVENT_SEPARATOR", comment: "Separator") | ||
description += (self.totalDistance?.formatAsDistance(withUnit: self.distanceUnit.unit(for: systemOfUnits)) ?? missingValueStr) + "\n" | ||
case self.avgHeartRow: | ||
description += NSLocalizedString("WRKT_AVG_HEART", comment: "Average Heart Rate") | ||
description += NSLocalizedString("WRKT_EVENT_SEPARATOR", comment: "Separator") | ||
description += (self.avgHeart?.formatAsHeartRate(withUnit: WorkoutUnit.heartRate.unit(for: systemOfUnits)) ?? missingValueStr) + "\n" | ||
case self.maxHeartRow: | ||
description += NSLocalizedString("WRKT_MAX_HEART", comment: "Max Heart Rate") | ||
description += NSLocalizedString("WRKT_EVENT_SEPARATOR", comment: "Separator") | ||
description += (self.maxHeart?.formatAsHeartRate(withUnit: WorkoutUnit.heartRate.unit(for: systemOfUnits)) ?? missingValueStr) + "\n" | ||
case self.paceRow: | ||
description += NSLocalizedString("WRKT_AVG_PACE", comment: "Average Pace") | ||
description += NSLocalizedString("WRKT_EVENT_SEPARATOR", comment: "Separator") | ||
description += (self.pace?.formatAsPace(withReferenceLength: self.paceUnit.unit(for: systemOfUnits)) ?? missingValueStr) + "\n" | ||
case self.speedRow: | ||
description += NSLocalizedString("WRKT_AVG_SPEED", comment: "Average Speed") | ||
description += NSLocalizedString("WRKT_EVENT_SEPARATOR", comment: "Separator") | ||
description += (self.speed?.formatAsSpeed(withUnit: self.speedUnit.unit(for: systemOfUnits)) ?? missingValueStr) + "\n" | ||
case self.energyRow: | ||
description += NSLocalizedString("WRKT_ENERGY", comment: "Energy") | ||
description += NSLocalizedString("WRKT_EVENT_SEPARATOR", comment: "Separator") | ||
if let total = self.totalEnergy { | ||
if let active = self.activeEnergy { | ||
description += String(format: NSLocalizedString("WRKT_SPLIT_CAL_%@_TOTAL_%@", comment: "Active/Total"), | ||
active.formatAsEnergy(withUnit: WorkoutUnit.calories.unit(for: systemOfUnits)), | ||
total.formatAsEnergy(withUnit: WorkoutUnit.calories.unit(for: systemOfUnits))) + "\n" | ||
} else { | ||
description += total.formatAsEnergy(withUnit: WorkoutUnit.calories.unit(for: systemOfUnits)) + "\n" | ||
} | ||
} else { | ||
description += missingValueStr + "\n" | ||
} | ||
case self.elevationRow: | ||
description += NSLocalizedString("WRKT_ELEVATION", comment: "Elevation Change") | ||
description += NSLocalizedString("WRKT_EVENT_SEPARATOR", comment: "Separator") | ||
|
||
let (asc, desc) = self.elevationChange | ||
for (v, dir) in [(asc, "↗ "), (desc, "↘ ")] { | ||
guard let v = v else { | ||
continue | ||
} | ||
|
||
description += dir + v.formatAsElevationChange(withUnit: WorkoutUnit.elevation.unit(for: systemOfUnits)) + "\n" | ||
} | ||
default: | ||
break | ||
} | ||
} | ||
description.removeLast() | ||
|
||
eventStore.requestAccess(to: .event, completion: { (granted, error) in | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think authorization to use the calendar should be handled by the main app, not |
||
if (granted) && (error == nil) { | ||
let event = EKEvent(eventStore: eventStore) | ||
|
||
event.title = "\(self.name) - \(self.duration.formattedDuration)" | ||
event.startDate = self.startDate | ||
event.endDate = self.endDate | ||
event.notes = description | ||
event.calendar = eventStore.calendar(withIdentifier: Preferences().defaultCalendarSelected) ?? eventStore.defaultCalendarForNewEvents | ||
do { | ||
try eventStore.save(event, span: .thisEvent) | ||
} catch let e as NSError { | ||
print(e) | ||
callback(nil) | ||
return | ||
} | ||
} | ||
}) | ||
callback(URL(string: "calshow:\(self.startDate.timeIntervalSinceReferenceDate)")!) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm not mistaken these are just copied from what
WorkoutTVC
uses to configure the first section. Instead of duplicating the code why not making these public and letWorkoutTVC
use these directly?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, but maybe I should create a new class for it like "WorkoutData", that will be used by the WorkoutTVC and Workout classes to access to theses data. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about this since
Workout
is already a wrapper for aHKWorkout
and the model forWorkoutTVC
.A possible direction could be creating a sub-class for
Workout
called something likeWorkout.DataIndex
(holding a weak reference to the workout in order to access what is nowself.avgHeart
and the like) exposing this properties and maybe the count of how many are non nil.We could make the init private so only the workout itself can initialize it.