diff --git a/Cartfile.resolved b/Cartfile.resolved index 908fc64d14..aa6357ee5f 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,8 +1,8 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "7417605dd898bf89378171941126c85d85dc642c" github "LoopKit/G4ShareSpy" "e62d296067180c6659166272ff9cc406f470ec9e" -github "LoopKit/LoopKit" "18a5a04afd310e945ac54f8c43a44838a16503c2" +github "LoopKit/LoopKit" "a08405ec7a4e38fa96e39ae079114db30f393a2a" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" github "LoopKit/dexcom-share-client-swift" "c4f3d48e56e5b3ad786486ccd1bbc753034096d2" github "i-schuetz/SwiftCharts" "0.6.5" -github "ps2/rileylink_ios" "4b756c126317320209474f34e909ecf254d8a5b9" +github "ps2/rileylink_ios" "51d6a20cc63c0c0b2071a4a78b4065e9a11ba81f" diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index d37871dcfb..75190c3519 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -370,6 +370,7 @@ C136AA2423109CC6008A320D /* LoopPlugins.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DA84122E8E112008624C2 /* LoopPlugins.swift */; }; C13BAD941E8009B000050CB5 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0B31E45C1BE00FF19A9 /* NumberFormatter.swift */; }; C15713821DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */; }; + C165B8CE23302C5D0004112E /* RemoteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C165B8CD23302C5D0004112E /* RemoteCommand.swift */; }; C16DA84222E8E112008624C2 /* LoopPlugins.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DA84122E8E112008624C2 /* LoopPlugins.swift */; }; C178249A1E1999FA00D9D25C /* CaseCountable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17824991E1999FA00D9D25C /* CaseCountable.swift */; }; C17824A01E19CF9800D9D25C /* GlucoseThresholdTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C178249F1E19CF9800D9D25C /* GlucoseThresholdTableViewController.swift */; }; @@ -1024,6 +1025,7 @@ C12CB9B823106A6300F84978 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Intents.strings; sourceTree = ""; }; C12F21A61DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_temp_basal_very_low_end_in_range.json; sourceTree = ""; }; C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MealBolusNightscoutTreatment.swift; sourceTree = ""; }; + C165B8CD23302C5D0004112E /* RemoteCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteCommand.swift; sourceTree = ""; }; C16DA84122E8E112008624C2 /* LoopPlugins.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopPlugins.swift; sourceTree = ""; }; C17824991E1999FA00D9D25C /* CaseCountable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaseCountable.swift; sourceTree = ""; }; C178249F1E19CF9800D9D25C /* GlucoseThresholdTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseThresholdTableViewController.swift; sourceTree = ""; }; @@ -1239,6 +1241,7 @@ 438D42F81D7C88BC003244B0 /* PredictionInputEffect.swift */, 43441A9B1EDB34810087958C /* StatusExtensionContext+LoopKit.swift */, 4328E0311CFC068900E199AA /* WatchContext+LoopKit.swift */, + C165B8CD23302C5D0004112E /* RemoteCommand.swift */, ); path = Models; sourceTree = ""; @@ -2604,6 +2607,7 @@ 434FF1EE1CF27EEF000DB779 /* UITableViewCell.swift in Sources */, 439BED2A1E76093C00B0AED5 /* CGMManager.swift in Sources */, C18C8C511D5A351900E043FB /* NightscoutDataManager.swift in Sources */, + C165B8CE23302C5D0004112E /* RemoteCommand.swift in Sources */, 438849EA1D297CB6003B3F23 /* NightscoutService.swift in Sources */, 438172D91F4E9E37003C3328 /* NewPumpEvent.swift in Sources */, 4389916B1E91B689000EEF90 /* ChartSettings+Loop.swift in Sources */, diff --git a/Loop/AppDelegate.swift b/Loop/AppDelegate.swift index ed8d9e6333..df2430f732 100644 --- a/Loop/AppDelegate.swift +++ b/Loop/AppDelegate.swift @@ -26,12 +26,18 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { NotificationManager.authorize(delegate: self) - + log.info(#function) AnalyticsManager.shared.application(application, didFinishLaunchingWithOptions: launchOptions) rootViewController.rootViewController.deviceManager = deviceManager + + let notificationOption = launchOptions?[.remoteNotification] + + if let notification = notificationOption as? [String: AnyObject] { + deviceManager.handleRemoteNotification(notification) + } return true } @@ -74,6 +80,32 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { return false } } + + // MARK: - Remote notifications + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) } + let token = tokenParts.joined() + log.default("RemoteNotifications device token: \(token)") + deviceManager.loopManager.settings.deviceToken = deviceToken + } + + func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { + log.error("Failed to register: \(error)") + } + + func application(_ application: UIApplication, + didReceiveRemoteNotification userInfo: [AnyHashable : Any], + fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void + ) { + guard let notification = userInfo as? [String: AnyObject] else { + completionHandler(.failed) + return + } + + deviceManager.handleRemoteNotification(notification) + completionHandler(.noData) + } + } @@ -102,4 +134,5 @@ extension AppDelegate: UNUserNotificationCenterDelegate { func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { completionHandler([.badge, .sound, .alert]) } + } diff --git a/Loop/Info.plist b/Loop/Info.plist index 7c55571e92..f8907e07d0 100644 --- a/Loop/Info.plist +++ b/Loop/Info.plist @@ -62,6 +62,7 @@ UIBackgroundModes bluetooth-central + remote-notification UILaunchStoryboardName LaunchScreen diff --git a/Loop/Loop.entitlements b/Loop/Loop.entitlements index d590778126..a6fd580f0b 100644 --- a/Loop/Loop.entitlements +++ b/Loop/Loop.entitlements @@ -2,6 +2,8 @@ + aps-environment + development com.apple.developer.healthkit com.apple.developer.healthkit.access diff --git a/Loop/Managers/AnalyticsManager.swift b/Loop/Managers/AnalyticsManager.swift index 5dca68162f..78d94d8966 100644 --- a/Loop/Managers/AnalyticsManager.swift +++ b/Loop/Managers/AnalyticsManager.swift @@ -150,8 +150,8 @@ final class AnalyticsManager: IdentifiableClass { logEvent("CGM Fetch", outOfSession: true) } - func loopDidSucceed() { - logEvent("Loop success", outOfSession: true) + func loopDidSucceed(_ duration: TimeInterval) { + logEvent("Loop success", withProperties: ["duration": duration], outOfSession: true) } func loopDidError() { diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index 73f973525f..67a9174a94 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -653,3 +653,21 @@ extension Notification.Name { static let PumpEventsAdded = Notification.Name(rawValue: "com.loopKit.notification.PumpEventsAdded") } +// MARK: - Remote Notification Handling +extension DeviceDataManager { + func handleRemoteNotification(_ notification: [String: AnyObject]) { + + if let command = RemoteCommand(notification: notification, allowedPresets: loopManager.settings.overridePresets) { + switch command { + case .temporaryScheduleOverride(let override): + log.default("Enacting remote temporary override: \(override)") + loopManager.settings.scheduleOverride = override + case .cancelTemporaryOverride: + log.default("Canceling temporary override from remote command") + loopManager.settings.scheduleOverride = nil + } + } else { + log.info("Unhandled remote notification: \(notification)") + } + } +} diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index a7f02eaae9..aac8e72500 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -216,6 +216,8 @@ final class LoopDataManager { private let lockedBasalDeliveryState: Locked fileprivate var lastRequestedBolus: DoseEntry? + + private var lastLoopStarted: Date? /// The last date at which a loop completed, from prediction to dose (if dosing is enabled) var lastLoopCompleted: Date? { @@ -224,11 +226,6 @@ final class LoopDataManager { } set { lockedLastLoopCompleted.value = newValue - - NotificationManager.clearLoopNotRunningNotifications() - NotificationManager.scheduleLoopNotRunningNotifications() - AnalyticsManager.shared.loopDidSucceed() - NotificationCenter.default.post(name: .LoopCompleted, object: self) } } private let lockedLastLoopCompleted: Locked @@ -269,6 +266,15 @@ final class LoopDataManager { backgroundTask = .invalid } } + + private func loopDidComplete(date: Date, duration: TimeInterval) { + lastLoopCompleted = date + NotificationManager.clearLoopNotRunningNotifications() + NotificationManager.scheduleLoopNotRunningNotifications() + AnalyticsManager.shared.loopDidSucceed(duration) + NotificationCenter.default.post(name: .LoopCompleted, object: self) + + } } // MARK: Background task management @@ -635,6 +641,7 @@ extension LoopDataManager { NotificationCenter.default.post(name: .LoopRunning, object: self) self.lastLoopError = nil + let startDate = Date() do { try self.update() @@ -646,7 +653,7 @@ extension LoopDataManager { if let error = error { self.logger.error(error) } else { - self.lastLoopCompleted = Date() + self.loopDidComplete(date: Date(), duration: -startDate.timeIntervalSinceNow) } self.logger.default("Loop ended") self.notify(forChange: .tempBasal) @@ -655,7 +662,7 @@ extension LoopDataManager { // Delay the notification until we know the result of the temp basal return } else { - self.lastLoopCompleted = Date() + self.loopDidComplete(date: Date(), duration: -startDate.timeIntervalSinceNow) } } catch let error { self.lastLoopError = error diff --git a/Loop/Managers/NightscoutDataManager.swift b/Loop/Managers/NightscoutDataManager.swift index 320f1a174a..ea8899ea15 100644 --- a/Loop/Managers/NightscoutDataManager.swift +++ b/Loop/Managers/NightscoutDataManager.swift @@ -27,6 +27,9 @@ final class NightscoutDataManager { // Last time settings were updated var lastSettingsUpdate: Date = .distantPast + + // Override history query anchor + var overrideHistoryQueryAnchor: TemporaryScheduleOverrideHistory.QueryAnchor? init(deviceDataManager: DeviceDataManager) { self.deviceManager = deviceDataManager @@ -47,12 +50,44 @@ final class NightscoutDataManager { lastSettingsUpdate = Date() uploadSettings() + uploadOverridesUpdates() + } + + private func uploadOverridesUpdates() { + guard let uploader = deviceManager.remoteDataManager.nightscoutService.uploader else { + return + } + + let (overrides, deletedOverrides, newAnchor) = deviceManager.loopManager.overrideHistory.queryByAnchor(overrideHistoryQueryAnchor) + + let updates = overrides.map { OverrideTreatment(override: $0) } + + let deletions = deletedOverrides.map { $0.syncIdentifier.uuidString } + uploader.deleteTreatmentsByClientId(deletions, completionHandler: { (error) in + if let error = error { + self.log.error("Overrides deletions failed to delete %{public}@: %{public}@", String(describing: deletions), String(describing: error)) + } else { + if deletions.count > 0 { + self.log.debug("Deleted ids: %@", deletions) + } + uploader.upload(updates) { (result) in + switch result { + case .failure(let error): + self.log.error("Failed to upload overrides %{public}@: %{public}@", String(describing: updates.map {$0.dictionaryRepresentation}), String(describing: error)) + case .success: + self.log.debug("Uploaded overrides %@", String(describing: updates.map {$0.dictionaryRepresentation})) + self.overrideHistoryQueryAnchor = newAnchor + } + } + } + }) } private func uploadSettings() { + let settings = deviceManager.loopManager.settings + guard let uploader = deviceManager.remoteDataManager.nightscoutService.uploader, - let settings = UserDefaults.appGroup?.loopSettings, let basalRateSchedule = UserDefaults.appGroup?.basalRateSchedule, let insulinModelSettings = UserDefaults.appGroup?.insulinModelSettings, let carbRatioSchedule = UserDefaults.appGroup?.carbRatioSchedule, @@ -63,7 +98,7 @@ final class NightscoutDataManager { log.default("Not uploading due to incomplete configuration") return } - + let targetLowItems = correctionSchedule.items.map { (item) -> ProfileSet.ScheduleItem in return ProfileSet.ScheduleItem(offset: item.startTime, value: item.value.minValue) } @@ -90,7 +125,9 @@ final class NightscoutDataManager { minimumBGGuard: settings.suspendThreshold?.quantity.doubleValue(for: preferredUnit), preMealTargetRange: nsPreMealTargetRange, maximumBasalRatePerHour: settings.maximumBasalRatePerHour, - maximumBolus: settings.maximumBolus) + maximumBolus: settings.maximumBolus, + deviceToken: settings.deviceToken, + bundleIdentifier: Bundle.main.bundleIdentifier) let profile = ProfileSet.Profile( timezone: basalRateSchedule.timeZone, @@ -170,6 +207,8 @@ final class NightscoutDataManager { if self.lastSettingsUpdate > self.lastSettingsUpload { self.uploadSettings() } + + self.uploadOverridesUpdates() } } } @@ -281,8 +320,8 @@ final class NightscoutDataManager { } else { pumpStatus = nil } + //add overrideStatus - let overrideStatus: NightscoutUploadKit.OverrideStatus? let settings = deviceManager.loopManager.settings let unit: HKUnit = settings.glucoseTargetRangeSchedule?.unit ?? HKUnit.milligramsPerDeciliter @@ -326,7 +365,6 @@ final class NightscoutDataManager { } log.default("Uploading loop status") upload(pumpStatus: pumpStatus, loopStatus: loopStatus, deviceName: nil, firmwareVersion: nil, uploaderStatus: getUploaderStatus(), overrideStatus: overrideStatus) - } private func getUploaderStatus() -> UploaderStatus { @@ -410,15 +448,16 @@ private extension Array where Element == RepeatingScheduleValue { } } +// Likely this will be deprecated, in favor of override history uploading to NS treatments private extension LoopKit.TemporaryScheduleOverride { func nsScheduleOverride(for unit: HKUnit) -> NightscoutUploadKit.TemporaryScheduleOverride { - let nsTargetRange: ClosedRange? + let nsCorrectionRange: ClosedRange? if let targetRange = settings.targetRange { - nsTargetRange = ClosedRange(uncheckedBounds: ( + nsCorrectionRange = ClosedRange(uncheckedBounds: ( lower: targetRange.lowerBound.doubleValue(for: unit), upper: targetRange.upperBound.doubleValue(for: unit))) } else { - nsTargetRange = nil + nsCorrectionRange = nil } let nsDuration: TimeInterval @@ -448,23 +487,23 @@ private extension LoopKit.TemporaryScheduleOverride { } return NightscoutUploadKit.TemporaryScheduleOverride( - targetRange: nsTargetRange, + duration: nsDuration, + targetRange: nsCorrectionRange, insulinNeedsScaleFactor: settings.insulinNeedsScaleFactor, symbol: symbol, - duration: nsDuration, name: name) } } private extension LoopKit.TemporaryScheduleOverridePreset { func nsScheduleOverride(for unit: HKUnit) -> NightscoutUploadKit.TemporaryScheduleOverride { - let nsTargetRange: ClosedRange? + let nsCorrectionRange: ClosedRange? if let targetRange = settings.targetRange { - nsTargetRange = ClosedRange(uncheckedBounds: ( + nsCorrectionRange = ClosedRange(uncheckedBounds: ( lower: targetRange.lowerBound.doubleValue(for: unit), upper: targetRange.upperBound.doubleValue(for: unit))) } else { - nsTargetRange = nil + nsCorrectionRange = nil } let nsDuration: TimeInterval @@ -476,10 +515,59 @@ private extension LoopKit.TemporaryScheduleOverridePreset { } return NightscoutUploadKit.TemporaryScheduleOverride( - targetRange: nsTargetRange, + duration: nsDuration, + targetRange: nsCorrectionRange, insulinNeedsScaleFactor: settings.insulinNeedsScaleFactor, symbol: self.symbol, - duration: nsDuration, name: self.name) } } + +private extension OverrideTreatment { + convenience init(override: LoopKit.TemporaryScheduleOverride) { + + // NS Treatments should be in mg/dL + let unit: HKUnit = .milligramsPerDeciliter + + let nsTargetRange: ClosedRange? + if let targetRange = override.settings.targetRange { + nsTargetRange = ClosedRange(uncheckedBounds: ( + lower: targetRange.lowerBound.doubleValue(for: unit), + upper: targetRange.upperBound.doubleValue(for: unit))) + } else { + nsTargetRange = nil + } + + let reason: String + switch override.context { + case .custom: + reason = NSLocalizedString("Custom Override", comment: "Name of custom override") + case .legacyWorkout: + reason = NSLocalizedString("Workout", comment: "Name of legacy workout override") + case .preMeal: + reason = NSLocalizedString("Pre-Meal", comment: "Name of pre-meal workout override") + case .preset(let preset): + reason = preset.symbol + " " + preset.name + } + + let remoteAddress: String? + let enteredBy: String + if case .remote(let address) = override.enactTrigger { + remoteAddress = address + enteredBy = "Loop (via remote command)" + } else { + remoteAddress = nil + enteredBy = "Loop" + } + + let duration: OverrideTreatment.Duration + switch override.duration { + case .finite(let time): + duration = .finite(time) + case .indefinite: + duration = .indefinite + } + + self.init(startDate: override.startDate, enteredBy: enteredBy, reason: reason, duration: duration, correctionRange: nsTargetRange, insulinNeedsScaleFactor: override.settings.insulinNeedsScaleFactor, remoteAddress:remoteAddress, id: override.syncIdentifier.uuidString) + } +} diff --git a/Loop/Managers/NotificationManager.swift b/Loop/Managers/NotificationManager.swift index 8f93f322ec..967be1cb1d 100644 --- a/Loop/Managers/NotificationManager.swift +++ b/Loop/Managers/NotificationManager.swift @@ -44,9 +44,18 @@ struct NotificationManager { let center = UNUserNotificationCenter.current() center.delegate = delegate - center.requestAuthorization(options: [.badge, .sound, .alert], completionHandler: { _, _ in }) + center.requestAuthorization(options: [.badge, .sound, .alert]) { (granted, error) in + guard granted else { return } + UNUserNotificationCenter.current().getNotificationSettings { settings in + guard settings.authorizationStatus == .authorized else { return } + DispatchQueue.main.async { + UIApplication.shared.registerForRemoteNotifications() + } + } + } center.setNotificationCategories(notificationCategories) } + // MARK: - Notifications diff --git a/Loop/Models/RemoteCommand.swift b/Loop/Models/RemoteCommand.swift new file mode 100644 index 0000000000..20a80b166f --- /dev/null +++ b/Loop/Models/RemoteCommand.swift @@ -0,0 +1,44 @@ +// +// RemoteCommand.swift +// Loop +// +// Created by Pete Schwamb on 9/16/19. +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import Foundation +import LoopKit + +public enum RemoteCommandError: Error { + case expired +} + + +enum RemoteCommand { + typealias RawValue = [String: Any] + + case temporaryScheduleOverride(TemporaryScheduleOverride) + case cancelTemporaryOverride +} + + +// Push Notifications +extension RemoteCommand { + init?(notification: [String: Any], allowedPresets: [TemporaryScheduleOverridePreset]) { + if let overrideEnactName = notification["override-name"] as? String, + let preset = allowedPresets.first(where: { $0.name == overrideEnactName }), + let remoteAddress = notification["remote-address"] as? String + { + var override = preset.createOverride(enactTrigger: .remote(remoteAddress)) + if let overrideDurationMinutes = notification["override-duration-minutes"] as? Double { + override.duration = .finite(TimeInterval(minutes: overrideDurationMinutes)) + } + self = .temporaryScheduleOverride(override) + } else if let _ = notification["cancel-temporary-override"] as? String { + self = .cancelTemporaryOverride + } + else { + return nil + } + } +} diff --git a/LoopCore/LoopSettings.swift b/LoopCore/LoopSettings.swift index 17f84c13af..b524e3495c 100644 --- a/LoopCore/LoopSettings.swift +++ b/LoopCore/LoopSettings.swift @@ -54,7 +54,11 @@ public struct LoopSettings: Equatable { public var glucoseUnit: HKUnit? { return glucoseTargetRangeSchedule?.unit } - + + // MARK - Push Notifications + + public var deviceToken: Data? + // MARK - Guardrails public func allowedSensitivityValues(for unit: HKUnit) -> [Double] { @@ -136,7 +140,9 @@ extension LoopSettings { context: .preMeal, settings: TemporaryScheduleOverrideSettings(unit: unit, targetRange: premealTargetRange), startDate: date, - duration: .finite(duration) + duration: .finite(duration), + enactTrigger: .local, + syncIdentifier: UUID() ) } @@ -152,7 +158,9 @@ extension LoopSettings { context: .legacyWorkout, settings: TemporaryScheduleOverrideSettings(unit: unit, targetRange: legacyWorkoutTargetRange), startDate: date, - duration: duration.isInfinite ? .indefinite : .finite(duration) + duration: duration.isInfinite ? .indefinite : .finite(duration), + enactTrigger: .local, + syncIdentifier: UUID() ) } diff --git a/WatchApp Extension/Controllers/ActionHUDController.swift b/WatchApp Extension/Controllers/ActionHUDController.swift index 5dc6e3b496..039886722e 100644 --- a/WatchApp Extension/Controllers/ActionHUDController.swift +++ b/WatchApp Extension/Controllers/ActionHUDController.swift @@ -164,7 +164,7 @@ final class ActionHUDController: HUDInterfaceController { extension ActionHUDController: OverrideSelectionControllerDelegate { func overrideSelectionController(_ controller: OverrideSelectionController, didSelectPreset preset: TemporaryScheduleOverridePreset) { - let override = preset.createOverride() + let override = preset.createOverride(enactTrigger: .local) sendOverride(override) } } diff --git a/WatchApp Extension/Scenes/GlucoseChartScene.swift b/WatchApp Extension/Scenes/GlucoseChartScene.swift index 0188ab6d75..c9b8315a99 100644 --- a/WatchApp Extension/Scenes/GlucoseChartScene.swift +++ b/WatchApp Extension/Scenes/GlucoseChartScene.swift @@ -259,7 +259,14 @@ class GlucoseChartScene: SKScene { if rangeHashable.end < spannedInterval.end { let extendedDuration = spannedInterval.end.timeIntervalSince(rangeHashable.start) - let extendedRange = TemporaryScheduleOverride(context: rangeHashable.override.context, settings: rangeHashable.override.settings, startDate: rangeHashable.start, duration: .finite(extendedDuration)) + let extendedRange = TemporaryScheduleOverride( + context: rangeHashable.override.context, + settings: rangeHashable.override.settings, + startDate: rangeHashable.start, + duration: .finite(extendedDuration), + enactTrigger: .local, + syncIdentifier: UUID() + ) let extendedRangeHashable = TemporaryScheduleOverrideHashable(extendedRange)! // Target range already known to be non-nil let (sprite2, created) = getSprite(forHash: extendedRangeHashable.chartHashValue) sprite2.color = UIColor.glucose.withAlphaComponent(0.25)