diff --git a/Cartfile b/Cartfile index 19c5b4d835..422b481a17 100644 --- a/Cartfile +++ b/Cartfile @@ -2,7 +2,7 @@ github "LoopKit/LoopKit" "dev" github "LoopKit/CGMBLEKit" "dev" github "i-schuetz/SwiftCharts" == 0.6.2 -github "LoopKit/dexcom-share-client-swift" "carthage-recursive" -github "LoopKit/G4ShareSpy" "fix-cartfile" +github "LoopKit/dexcom-share-client-swift" "dev" +github "LoopKit/G4ShareSpy" "dev" github "ps2/rileylink_ios" "dev" github "LoopKit/Amplitude-iOS" "decreepify" diff --git a/Cartfile.resolved b/Cartfile.resolved index 46a30a96dc..f32f060e27 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,7 +1,7 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" -github "LoopKit/CGMBLEKit" "bc7c6bb2aaea48e501ecdbb379cd3a533f5df42e" -github "LoopKit/G4ShareSpy" "67208c66abfbb3bff813f047b6c40612338a335b" -github "LoopKit/LoopKit" "1c23bb5fd54c0b0aaa81dbabcb35f2d4c9c2e8cf" -github "LoopKit/dexcom-share-client-swift" "27fd38c28dcb16093ddf660c2b6b84ae34733352" +github "LoopKit/CGMBLEKit" "f868f1366c8a464763fb86ca077e768282a9b197" +github "LoopKit/G4ShareSpy" "3c7040cc93f28b778b6d265e3224974b4ea31483" +github "LoopKit/LoopKit" "1dc7b3ec4b7c6bb9225545f5d8fe599bfe783a5c" +github "LoopKit/dexcom-share-client-swift" "13e0f5cfd98dd4bfdf5eff413ba3ed36617cbb08" github "i-schuetz/SwiftCharts" "0.6.2" -github "ps2/rileylink_ios" "5df08f32f11ac2cb6097ba1411349f05b9171e72" +github "ps2/rileylink_ios" "f3597c78ae00da2f35ef98269056126af4ce070f" diff --git a/Loop/Extensions/NightscoutUploader.swift b/Loop/Extensions/NightscoutUploader.swift index ab81c2f85f..b4bad0158a 100644 --- a/Loop/Extensions/NightscoutUploader.swift +++ b/Loop/Extensions/NightscoutUploader.swift @@ -75,11 +75,13 @@ extension NightscoutUploader { var treatments = [NightscoutTreatment]() for event in events { + + objectIDURLs.append(event.objectIDURL) + guard let treatment = event.treatment(enteredBy: source) else { continue } - objectIDURLs.append(event.objectIDURL) treatments.append(treatment) } diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index 8f7b774343..b4d83d3e35 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -132,6 +132,8 @@ extension DeviceDataManager: CGMManagerDelegate { /// TODO: Isolate to queue switch result { case .newData(let values): + log.default("CGMManager:\(type(of: manager)) did update with new data") + loopManager.addGlucose(values) { result in if manager.shouldSyncToRemoteService { switch result { @@ -145,8 +147,12 @@ extension DeviceDataManager: CGMManagerDelegate { self.pumpManager?.assertCurrentPumpData() } case .noData: + log.default("CGMManager:\(type(of: manager)) did update with no data") + pumpManager?.assertCurrentPumpData() case .error(let error): + log.default("CGMManager:\(type(of: manager)) did update with error: \(error)") + self.setLastError(error: error) pumpManager?.assertCurrentPumpData() } @@ -162,10 +168,14 @@ extension DeviceDataManager: CGMManagerDelegate { extension DeviceDataManager: PumpManagerDelegate { func pumpManager(_ pumpManager: PumpManager, didAdjustPumpClockBy adjustment: TimeInterval) { + log.default("PumpManager:\(type(of: pumpManager)) did adjust pump block by \(adjustment)s") + AnalyticsManager.shared.pumpTimeDidDrift(adjustment) } func pumpManagerDidUpdatePumpBatteryChargeRemaining(_ pumpManager: PumpManager, oldValue: Double?) { + log.default("PumpManager:\(type(of: pumpManager)) did update pump battery from \(String(describing: oldValue))") + if let newValue = pumpManager.pumpBatteryChargeRemaining { if newValue == 0 { NotificationManager.sendPumpBatteryLowNotification() @@ -180,10 +190,14 @@ extension DeviceDataManager: PumpManagerDelegate { } func pumpManagerDidUpdateState(_ pumpManager: PumpManager) { + log.default("PumpManager:\(type(of: pumpManager)) did update state") + UserDefaults.appGroup.pumpManager = pumpManager } func pumpManagerBLEHeartbeatDidFire(_ pumpManager: PumpManager) { + log.default("PumpManager:\(type(of: pumpManager)) did fire BLE heartbeat") + cgmManager?.fetchNewDataIfNeeded { (result) in if case .newData = result { AnalyticsManager.shared.didFetchNewCGMData() @@ -201,6 +215,8 @@ extension DeviceDataManager: PumpManagerDelegate { } func pumpManager(_ pumpManager: PumpManager, didUpdateStatus status: PumpManagerStatus) { + log.default("PumpManager:\(type(of: pumpManager)) did update status") + loopManager.doseStore.device = status.device // Update the pump-schedule based settings loopManager.setScheduleTimeZone(status.timeZone) @@ -208,20 +224,28 @@ extension DeviceDataManager: PumpManagerDelegate { } func pumpManagerWillDeactivate(_ pumpManager: PumpManager) { + log.default("PumpManager:\(type(of: pumpManager)) will deactivate") + loopManager.doseStore.resetPumpData() self.pumpManager = nil } func pumpManager(_ pumpManager: PumpManager, didUpdatePumpRecordsBasalProfileStartEvents pumpRecordsBasalProfileStartEvents: Bool) { + log.default("PumpManager:\(type(of: pumpManager)) did update pumpRecordsBasalProfileStartEvents to \(pumpRecordsBasalProfileStartEvents)") + loopManager.doseStore.pumpRecordsBasalProfileStartEvents = pumpRecordsBasalProfileStartEvents } func pumpManager(_ pumpManager: PumpManager, didError error: PumpManagerError) { + log.error("PumpManager:\(type(of: pumpManager)) did error: \(error)") + setLastError(error: error) nightscoutDataManager.uploadLoopStatus(loopError: error) } func pumpManager(_ pumpManager: PumpManager, didReadPumpEvents events: [NewPumpEvent], completion: @escaping (_ error: Error?) -> Void) { + log.default("PumpManager:\(type(of: pumpManager)) did read pump events") + loopManager.addPumpEvents(events) { (error) in if let error = error { self.log.error("Failed to addPumpEvents to DoseStore: \(error)") @@ -232,10 +256,12 @@ extension DeviceDataManager: PumpManagerDelegate { } func pumpManager(_ pumpManager: PumpManager, didReadReservoirValue units: Double, at date: Date, completion: @escaping (_ result: PumpManagerResult<(newValue: ReservoirValue, lastValue: ReservoirValue?, areStoredValuesContinuous: Bool)>) -> Void) { + log.default("PumpManager:\(type(of: pumpManager)) did read reservoir value") + loopManager.addReservoirValue(units, at: date) { (result) in switch result { case .failure(let error): - self.logger.addError(error, fromSource: "Bolus") + self.log.error("Failed to addReservoirValue: \(error)") completion(.failure(error)) case .success(let (newValue, lastValue, areStoredValuesContinuous)): completion(.success((newValue: newValue, lastValue: lastValue, areStoredValuesContinuous: areStoredValuesContinuous))) @@ -247,22 +273,19 @@ extension DeviceDataManager: PumpManagerDelegate { return } - var didSendLowNotification = false let warningThresholds: [Double] = [10, 20, 30] for threshold in warningThresholds { if newValue.unitVolume <= threshold && previousVolume > threshold { NotificationManager.sendPumpReservoirLowNotificationForAmount(newValue.unitVolume, andTimeRemaining: nil) - didSendLowNotification = true + break } } - if !didSendLowNotification { - NotificationManager.clearPumpReservoirNotification() - } - if newValue.unitVolume > previousVolume + 1 { AnalyticsManager.shared.reservoirWasRewound() + + NotificationManager.clearPumpReservoirNotification() } } } @@ -270,6 +293,7 @@ extension DeviceDataManager: PumpManagerDelegate { } func pumpManagerRecommendsLoop(_ pumpManager: PumpManager) { + log.default("PumpManager:\(type(of: pumpManager)) recommends loop") loopManager.loop() } @@ -341,6 +365,8 @@ extension DeviceDataManager: LoopDataManagerDelegate { return } + log.default("LoopManager did recommend basal change") + pumpManager.enactTempBasal( unitsPerHour: basal.recommendation.unitsPerHour, for: basal.recommendation.duration, diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 32a455df17..010b1ac993 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -554,6 +554,7 @@ extension LoopDataManager { /// temporary basal rate. func loop() { self.dataAccessQueue.async { + self.logger.default("Loop running") NotificationCenter.default.post(name: .LoopRunning, object: self) self.lastLoopError = nil @@ -582,6 +583,7 @@ extension LoopDataManager { self.lastLoopError = error } + self.logger.default("Loop ended") self.notify(forChange: .tempBasal) } } diff --git a/Loop/Managers/StatusChartsManager+LoopKit.swift b/Loop/Managers/StatusChartsManager+LoopKit.swift index 2f54ca31ac..46b4cd0fe6 100644 --- a/Loop/Managers/StatusChartsManager+LoopKit.swift +++ b/Loop/Managers/StatusChartsManager+LoopKit.swift @@ -83,7 +83,7 @@ extension StatusChartsManager { for entry in doseEntries { let time = entry.endDate.timeIntervalSince(entry.startDate) - if entry.type == .bolus && entry.netBasalUnits > 0 && time < .minutes(5) { + if entry.type == .bolus && entry.netBasalUnits > 0 && time < .minutes(10) { let x = ChartAxisValueDate(date: entry.startDate, formatter: dateFormatter) let y = ChartAxisValueDoubleLog(actualDouble: entry.units, unitString: "U", formatter: doseFormatter) diff --git a/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json b/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json index faf72e9052..637251bc8a 100644 --- a/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json +++ b/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json @@ -7,7 +7,7 @@ }, { "idiom" : "watch", - "filename" : "Icon-AppleWatch-32x32@2x.png", + "filename" : "Icon-Complication-20x20@2x.png", "screen-width" : ">161", "scale" : "2x" }, @@ -18,7 +18,7 @@ }, { "idiom" : "watch", - "filename" : "Icon-AppleWatch-36x36@2x.png", + "filename" : "Icon-Complication-22x22@2x.png", "screen-width" : ">183", "scale" : "2x" } diff --git a/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Icon-AppleWatch-32x32@2x.png b/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Icon-AppleWatch-32x32@2x.png deleted file mode 100644 index e8eefb523c..0000000000 Binary files a/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Icon-AppleWatch-32x32@2x.png and /dev/null differ diff --git a/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Icon-AppleWatch-36x36@2x.png b/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Icon-AppleWatch-36x36@2x.png deleted file mode 100644 index b3abdc0592..0000000000 Binary files a/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Icon-AppleWatch-36x36@2x.png and /dev/null differ diff --git a/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Icon-Complication-20x20@2x.png b/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Icon-Complication-20x20@2x.png new file mode 100644 index 0000000000..3e64959cdc Binary files /dev/null and b/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Icon-Complication-20x20@2x.png differ diff --git a/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Icon-Complication-22x22@2x.png b/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Icon-Complication-22x22@2x.png new file mode 100644 index 0000000000..1593033216 Binary files /dev/null and b/WatchApp Extension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Icon-Complication-22x22@2x.png differ diff --git a/WatchApp Extension/Base.lproj/Localizable.strings b/WatchApp Extension/Base.lproj/Localizable.strings index b26c9f6d64..a6fae0a4f3 100644 --- a/WatchApp Extension/Base.lproj/Localizable.strings +++ b/WatchApp Extension/Base.lproj/Localizable.strings @@ -37,4 +37,5 @@ /* The short unit display string for international units of insulin */ "U" = "U"; - +/* The short unit display string for international units of insulin delivery per hour */ +"U/hr" = "U/hr"; diff --git a/WatchApp Extension/Controllers/ChartHUDController.swift b/WatchApp Extension/Controllers/ChartHUDController.swift index e6d25cb4f2..b182073ed5 100644 --- a/WatchApp Extension/Controllers/ChartHUDController.swift +++ b/WatchApp Extension/Controllers/ChartHUDController.swift @@ -10,21 +10,23 @@ import WatchKit import WatchConnectivity import CGMBLEKit import LoopKit +import HealthKit import SpriteKit import os.log final class ChartHUDController: HUDInterfaceController, WKCrownDelegate { - @IBOutlet weak var basalLabel: WKInterfaceLabel! - @IBOutlet weak var iobLabel: WKInterfaceLabel! - @IBOutlet weak var cobLabel: WKInterfaceLabel! - @IBOutlet weak var glucoseScene: WKInterfaceSKScene! - @IBAction func setChartWindow1Hour() { + @IBOutlet private weak var tableGroup: WKInterfaceGroup! + @IBOutlet private weak var basalLabel: WKInterfaceLabel! + @IBOutlet private weak var iobLabel: WKInterfaceLabel! + @IBOutlet private weak var cobLabel: WKInterfaceLabel! + @IBOutlet private weak var glucoseScene: WKInterfaceSKScene! + @IBAction private func setChartWindow1Hour() { scene.visibleDuration = .hours(2) } - @IBAction func setChartWindow2Hours() { + @IBAction private func setChartWindow2Hours() { scene.visibleDuration = .hours(4) } - @IBAction func setChartWindow3Hours() { + @IBAction private func setChartWindow3Hours() { scene.visibleDuration = .hours(6) } private let scene = GlucoseChartScene() @@ -54,19 +56,27 @@ final class ChartHUDController: HUDInterfaceController, WKCrownDelegate { override func didAppear() { super.didAppear() - log.default("didAppear") + if glucoseScene.isPaused { + log.default("didAppear() unpausing") + glucoseScene.isPaused = false + } else { + log.default("didAppear() not paused") + glucoseScene.isPaused = false + } // Force an update when our pixels need to move let pixelsWide = scene.size.width * WKInterfaceDevice.current().screenScale let pixelInterval = scene.visibleDuration / TimeInterval(pixelsWide) timer = Timer.scheduledTimer(withTimeInterval: pixelInterval, repeats: true) { [weak self] _ in + self?.log.default("Timer fired, triggering update") self?.scene.setNeedsUpdate() } if #available(watchOSApplicationExtension 5.0, *) { scene.textInsets.left = max(scene.textInsets.left, systemMinimumLayoutMargins.leading) scene.textInsets.right = max(scene.textInsets.right, systemMinimumLayoutMargins.trailing) + tableGroup.setContentInset(UIEdgeInsets(top: 0, left: systemMinimumLayoutMargins.leading, bottom: 0, right: systemMinimumLayoutMargins.trailing)) } } @@ -85,7 +95,7 @@ final class ChartHUDController: HUDInterfaceController, WKCrownDelegate { log.default("willActivate() unpausing") glucoseScene.isPaused = false } else { - log.default("willActivate() unpausing") + log.default("willActivate()") } if !hasInitialActivation && UserDefaults.standard.startOnChartPage { @@ -112,58 +122,44 @@ final class ChartHUDController: HUDInterfaceController, WKCrownDelegate { return } - let insulinFormatter: NumberFormatter = { - let numberFormatter = NumberFormatter() - - numberFormatter.numberStyle = .decimal - numberFormatter.minimumFractionDigits = 1 - numberFormatter.maximumFractionDigits = 1 - - return numberFormatter - }() - - iobLabel.setHidden(true) - if let activeInsulin = activeContext.iob, let valueStr = insulinFormatter.string(from: activeInsulin) { - iobLabel.setText(String(format: NSLocalizedString( - "IOB %1$@ U", - comment: "The subtitle format describing units of active insulin. (1: localized insulin value description)" - ), - valueStr - )) - iobLabel.setHidden(false) + if let activeInsulin = activeContext.activeInsulin { + let insulinFormatter: QuantityFormatter = { + let insulinFormatter = QuantityFormatter() + insulinFormatter.numberFormatter.minimumFractionDigits = 1 + insulinFormatter.numberFormatter.maximumFractionDigits = 1 + + return insulinFormatter + }() + + iobLabel.setText(insulinFormatter.string(from: activeInsulin, for: .internationalUnit())) + } else { + iobLabel.setText("—") } - cobLabel.setHidden(true) - if let carbsOnBoard = activeContext.cob { - let carbFormatter = NumberFormatter() - carbFormatter.numberStyle = .decimal - carbFormatter.maximumFractionDigits = 0 - let valueStr = carbFormatter.string(from: carbsOnBoard) - - cobLabel.setText(String(format: NSLocalizedString( - "COB %1$@ g", - comment: "The subtitle format describing grams of active carbs. (1: localized carb value description)" - ), - valueStr! - )) - cobLabel.setHidden(false) + if let carbsOnBoard = activeContext.activeCarbohydrates { + let carbFormatter = QuantityFormatter() + carbFormatter.numberFormatter.maximumFractionDigits = 0 + + cobLabel.setText(carbFormatter.string(from: carbsOnBoard, for: .gram())) + } else { + cobLabel.setText("—") } - basalLabel.setHidden(true) if let tempBasal = activeContext.lastNetTempBasalDose { let basalFormatter = NumberFormatter() basalFormatter.numberStyle = .decimal basalFormatter.minimumFractionDigits = 1 basalFormatter.maximumFractionDigits = 3 basalFormatter.positivePrefix = basalFormatter.plusSign - let valueStr = basalFormatter.string(from: tempBasal) - - let basalLabelText = String(format: NSLocalizedString( - "%1$@ U/hr", - comment: "The subtitle format describing the current temp basal rate. (1: localized basal rate description)"), - valueStr!) - basalLabel.setText(basalLabelText) - basalLabel.setHidden(false) + + let unit = NSLocalizedString( + "U/hr", + comment: "The short unit display string for international units of insulin delivery per hour" + ) + + basalLabel.setText(basalFormatter.string(from: tempBasal, unit: unit)) + } else { + basalLabel.setText("—") } if glucoseScene.isPaused { diff --git a/WatchApp Extension/Controllers/HUDInterfaceController.swift b/WatchApp Extension/Controllers/HUDInterfaceController.swift index efb35dc50c..ab1b62a381 100644 --- a/WatchApp Extension/Controllers/HUDInterfaceController.swift +++ b/WatchApp Extension/Controllers/HUDInterfaceController.swift @@ -12,7 +12,6 @@ class HUDInterfaceController: WKInterfaceController { private var activeContextObserver: NSObjectProtocol? @IBOutlet weak var loopHUDImage: WKInterfaceImage! - @IBOutlet weak var loopTimer: WKInterfaceTimer! @IBOutlet weak var glucoseLabel: WKInterfaceLabel! @IBOutlet weak var eventualGlucoseLabel: WKInterfaceLabel! @@ -46,14 +45,9 @@ class HUDInterfaceController: WKInterfaceController { let date = activeContext.loopLastRunDate else { loopHUDImage.setLoopImage(.unknown) - loopTimer.setHidden(true) return } - loopTimer.setDate(date) - loopTimer.setHidden(false) - loopTimer.start() - glucoseLabel.setHidden(true) eventualGlucoseLabel.setHidden(true) if let glucose = activeContext.glucose, let unit = activeContext.preferredGlucoseUnit { diff --git a/WatchApp Extension/Extensions/NSUserDefaults+WatchApp.swift b/WatchApp Extension/Extensions/NSUserDefaults+WatchApp.swift index 012d29b1f2..301a4964da 100644 --- a/WatchApp Extension/Extensions/NSUserDefaults+WatchApp.swift +++ b/WatchApp Extension/Extensions/NSUserDefaults+WatchApp.swift @@ -12,6 +12,7 @@ import Foundation extension UserDefaults { private enum Key: String { case StartOnChartPage = "com.loudnate.Naterade.StartOnChartPage" + case VisibleDuration = "com.loudnate.Naterade.VisibleDuration" } var startOnChartPage: Bool { @@ -22,4 +23,16 @@ extension UserDefaults { set(newValue, forKey: Key.StartOnChartPage.rawValue) } } + + var visibleDuration: TimeInterval { + get { + if let value = object(forKey: Key.VisibleDuration.rawValue) as? TimeInterval { + return value + } + return TimeInterval (hours: 6) + } + set { + set(newValue.rawValue, forKey: Key.VisibleDuration.rawValue) + } + } } diff --git a/WatchApp Extension/Extensions/WatchContext+WatchApp.swift b/WatchApp Extension/Extensions/WatchContext+WatchApp.swift index 9212c5068e..bc38ac52c7 100644 --- a/WatchApp Extension/Extensions/WatchContext+WatchApp.swift +++ b/WatchApp Extension/Extensions/WatchContext+WatchApp.swift @@ -7,16 +7,31 @@ // import Foundation +import HealthKit import LoopKit extension WatchContext { var glucoseTrend: GlucoseTrend? { - get { - if let glucoseTrendRawValue = glucoseTrendRawValue { - return GlucoseTrend(rawValue: glucoseTrendRawValue) - } else { - return nil - } + if let glucoseTrendRawValue = glucoseTrendRawValue { + return GlucoseTrend(rawValue: glucoseTrendRawValue) + } else { + return nil } } + + var activeInsulin: HKQuantity? { + guard let value = iob else { + return nil + } + + return HKQuantity(unit: .internationalUnit(), doubleValue: value) + } + + var activeCarbohydrates: HKQuantity? { + guard let value = cob else { + return nil + } + + return HKQuantity(unit: .gram(), doubleValue: value) + } } diff --git a/WatchApp Extension/Scenes/GlucoseChartScene.swift b/WatchApp Extension/Scenes/GlucoseChartScene.swift index b53a6faaf2..b233401623 100644 --- a/WatchApp Extension/Scenes/GlucoseChartScene.swift +++ b/WatchApp Extension/Scenes/GlucoseChartScene.swift @@ -13,6 +13,11 @@ import LoopKit import WatchKit import os.log +private extension TimeInterval { + static let moveAnimationDuration: TimeInterval = 0.25 + static let fadeAnimationDuration: TimeInterval = 0.75 +} + private enum NodePlane: Int { case lines = 0 case ranges @@ -38,18 +43,38 @@ private extension SKLabelNode { basic.zPosition = NodePlane.labels.zPosition return basic } + + func move(to position: CGPoint) { + guard !self.position.equalTo(position) else { + return + } + + self.position = position + } } private extension SKSpriteNode { func move(to rect: CGRect, animated: Bool) { - if parent == nil || animated == false || (size.equalTo(rect.size) && position.equalTo(rect.origin)) { + guard !size.equalTo(rect.size) || !position.equalTo(rect.origin) else { + return + } + + if parent == nil || !animated { size = rect.size position = rect.origin + + if parent != nil { + alpha = 0 + run(.sequence([ + .wait(forDuration: .moveAnimationDuration), + .fadeIn(withDuration: .fadeAnimationDuration) + ])) + } } else { run(.group([ - .move(to: rect.origin, duration: 0.25), - .resize(toWidth: rect.size.width, duration: 0.25), - .resize(toHeight: rect.size.height, duration: 0.25) + .move(to: rect.origin, duration: .moveAnimationDuration), + .resize(toWidth: rect.size.width, duration: .moveAnimationDuration), + .resize(toHeight: rect.size.height, duration: .moveAnimationDuration) ])) } } @@ -58,7 +83,11 @@ private extension SKSpriteNode { class GlucoseChartScene: SKScene { let log = OSLog(category: "GlucoseChartScene") - var textInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5) + var textInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5) { + didSet { + setNeedsUpdate() + } + } var data: GlucoseChartData? { didSet { @@ -70,9 +99,10 @@ class GlucoseChartScene: SKScene { } } - var visibleDuration = TimeInterval(hours: 6) { + var visibleDuration = UserDefaults.standard.visibleDuration { didSet { setNeedsUpdate() + UserDefaults.standard.visibleDuration = visibleDuration } } @@ -95,16 +125,15 @@ class GlucoseChartScene: SKScene { override init() { // Use the fixed sizes specified in the storyboard, based on our guess of the model size let screen = WKInterfaceDevice.current().screenBounds - let width = screen.width - 2 /* insets */ let height: CGFloat - switch width { + switch screen.width { case let x where x < 150: // 38mm - height = 68 + height = 73 case let x where x > 180: // 44mm - height = 106 + height = 111 default: - height = 86 + height = 90 } super.init(size: CGSize(width: screen.width, height: height)) @@ -199,8 +228,11 @@ class GlucoseChartScene: SKScene { let numberFormatter = NumberFormatter.glucoseFormatter(for: unit) minBGLabel.text = numberFormatter.string(from: glucoseRange.lowerBound.doubleValue(for: unit)) + minBGLabel.move(to: CGPoint(x: size.width - textInsets.right, y: textInsets.bottom)) maxBGLabel.text = numberFormatter.string(from: glucoseRange.upperBound.doubleValue(for: unit)) + maxBGLabel.move(to: CGPoint(x: size.width - textInsets.right, y: size.height - textInsets.top)) hoursLabel.text = dateFormatter.string(from: visibleDuration) + hoursLabel.move(to: CGPoint(x: textInsets.left, y: size.height - textInsets.top)) // Keep track of the nodes we started this pass with so we can expire obsolete nodes at the end var inactiveNodes = nodes @@ -262,8 +294,8 @@ class GlucoseChartScene: SKScene { // SKShapeNode paths cannot be easily animated. Make it vanish, then fade in at the new location. predictedPathNode!.alpha = 0 predictedPathNode!.run(.sequence([ - .wait(forDuration: 0.25), - .fadeIn(withDuration: 0.75) + .wait(forDuration: .moveAnimationDuration), + .fadeIn(withDuration: .fadeAnimationDuration) ]), withKey: "move" ) diff --git a/WatchApp/Base.lproj/Interface.storyboard b/WatchApp/Base.lproj/Interface.storyboard index 23d08d8cf7..c5473c1cde 100644 --- a/WatchApp/Base.lproj/Interface.storyboard +++ b/WatchApp/Base.lproj/Interface.storyboard @@ -1,12 +1,12 @@ - + - - + + @@ -130,17 +130,13 @@ - + - @@ -157,113 +153,119 @@ - - + - + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + - + - - + + + + + + + + + + + + + + - @@ -352,17 +354,13 @@ - + - @@ -379,33 +377,68 @@ - - + - - + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + @@ -427,19 +460,20 @@ + - - + + - + - + - +