From 2bd354e010de703228ab64f2d7b8396320cd0b3b Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 5 Oct 2018 00:14:18 -0500 Subject: [PATCH 1/3] Watch Feature Updates - Update IOB and COB immediately after carb/bolus send - Adjust complication push budget to spend evenly before midnight - Adjustments for Series 4, including rounded action buttons, correct text margin insets, and using system scroll haptics - Improve efficiency of the chart redraw --- Common/Extensions/Double.swift | 6 +- Loop/Managers/DeviceDataManager.swift | 2 + Loop/Managers/WatchDataManager.swift | 143 +++++++++++++----- .../Base.lproj/Localizable.strings | 3 + .../Controllers/ActionHUDController.swift | 12 +- .../AddCarbsInterfaceController.swift | 21 ++- .../BolusInterfaceController.swift | 28 +++- .../Controllers/ChartHUDController.swift | 20 ++- .../Controllers/HUDInterfaceController.swift | 14 +- WatchApp Extension/Extensions/WCSession.swift | 10 +- .../Managers/LoopDataManager.swift | 12 ++ .../Scenes/GlucoseChartScene.swift | 44 ++++-- .../AppIcon.appiconset/Contents.json | 23 ++- .../1-hour-graph.imageset/Contents.json | 10 ++ .../2-hour-graph.imageset/Contents.json | 10 ++ .../3-hour-graph.imageset/Contents.json | 10 ++ .../bolus.imageset/Contents.json | 10 ++ .../carbs.imageset/Contents.json | 10 ++ .../loop/loop_aging.imageset/Contents.json | 16 +- .../loop/loop_fresh.imageset/Contents.json | 16 +- .../loop/loop_stale.imageset/Contents.json | 16 +- .../loop/loop_unknown.imageset/Contents.json | 16 +- .../pre-meal.imageset/Contents.json | 10 ++ .../workout.imageset/Contents.json | 10 ++ WatchApp/Base.lproj/Interface.storyboard | 73 ++++++--- 25 files changed, 419 insertions(+), 126 deletions(-) diff --git a/Common/Extensions/Double.swift b/Common/Extensions/Double.swift index 2247409097..3fbc5bdc9f 100644 --- a/Common/Extensions/Double.swift +++ b/Common/Extensions/Double.swift @@ -8,8 +8,8 @@ import Foundation -extension Double { - func floored(to increment: Double) -> Double { +extension FloatingPoint { + func floored(to increment: Self) -> Self { if increment == 0 { return self } @@ -17,7 +17,7 @@ extension Double { return floor(self / increment) * increment } - func ceiled(to increment: Double) -> Double { + func ceiled(to increment: Self) -> Self { if increment == 0 { return self } diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index 95c3c28647..8f7b774343 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -370,6 +370,8 @@ extension DeviceDataManager: CustomDebugStringConvertible { "", pumpManager != nil ? String(reflecting: pumpManager!) : "pumpManager: nil", "", + String(reflecting: watchManager!), + "", String(reflecting: statusExtensionManager!), ].joined(separator: "\n") } diff --git a/Loop/Managers/WatchDataManager.swift b/Loop/Managers/WatchDataManager.swift index e7c055e7dd..6f3fc32866 100644 --- a/Loop/Managers/WatchDataManager.swift +++ b/Loop/Managers/WatchDataManager.swift @@ -42,33 +42,18 @@ final class WatchDataManager: NSObject { @objc private func updateWatch(_ notification: Notification) { guard let rawUpdateContext = notification.userInfo?[LoopDataManager.LoopUpdateContextKey] as? LoopDataManager.LoopUpdateContext.RawValue, - let updateContext = LoopDataManager.LoopUpdateContext(rawValue: rawUpdateContext), - let session = watchSession + let updateContext = LoopDataManager.LoopUpdateContext(rawValue: rawUpdateContext) else { return } switch updateContext { - case .glucose: - break - case .tempBasal: - break + case .glucose, .tempBasal: + sendWatchContextIfNeeded() case .preferences: sendSettingsIfNeeded() - return default: - return - } - - switch session.activationState { - case .notActivated, .inactive: - session.activate() - case .activated: - createWatchContext { (context) in - if let context = context { - self.sendWatchContext(context) - } - } + break } } @@ -98,6 +83,17 @@ final class WatchDataManager: NSObject { log.default("Transferring LoopSettingsUserInfo") session.transferUserInfo(LoopSettingsUserInfo(settings: settings).rawValue) + } + + private func sendWatchContextIfNeeded() { + guard let session = watchSession, session.isPaired, session.isWatchAppInstalled else { + return + } + + guard case .activated = session.activationState else { + session.activate() + return + } createWatchContext { (context) in if let context = context { @@ -107,32 +103,39 @@ final class WatchDataManager: NSObject { } private func sendWatchContext(_ context: WatchContext) { - if let session = watchSession, session.isPaired && session.isWatchAppInstalled { - let complicationShouldUpdate: Bool + guard let session = watchSession, session.isPaired, session.isWatchAppInstalled else { + return + } + + guard case .activated = session.activationState else { + session.activate() + return + } - if let lastContext = lastComplicationContext, - let lastGlucose = lastContext.glucose, let lastGlucoseDate = lastContext.glucoseDate, - let newGlucose = context.glucose, let newGlucoseDate = context.glucoseDate - { - let enoughTimePassed = newGlucoseDate.timeIntervalSince(lastGlucoseDate).minutes >= 30 - let enoughTrendDrift = abs(newGlucose.doubleValue(for: minTrendUnit) - lastGlucose.doubleValue(for: minTrendUnit)) >= minTrendDrift + let complicationShouldUpdate: Bool - complicationShouldUpdate = enoughTimePassed || enoughTrendDrift - } else { - complicationShouldUpdate = true - } + if let lastContext = lastComplicationContext, + let lastGlucose = lastContext.glucose, let lastGlucoseDate = lastContext.glucoseDate, + let newGlucose = context.glucose, let newGlucoseDate = context.glucoseDate + { + let enoughTimePassed = newGlucoseDate.timeIntervalSince(lastGlucoseDate) >= session.complicationUserInfoTransferInterval + let enoughTrendDrift = abs(newGlucose.doubleValue(for: minTrendUnit) - lastGlucose.doubleValue(for: minTrendUnit)) >= minTrendDrift - if session.isComplicationEnabled && complicationShouldUpdate { - log.default("transferCurrentComplicationUserInfo") - session.transferCurrentComplicationUserInfo(context.rawValue) - lastComplicationContext = context - } else { - do { - log.default("updateApplicationContext") - try session.updateApplicationContext(context.rawValue) - } catch let error { - log.error(error) - } + complicationShouldUpdate = enoughTimePassed || enoughTrendDrift + } else { + complicationShouldUpdate = true + } + + if session.isComplicationEnabled && complicationShouldUpdate { + log.default("transferCurrentComplicationUserInfo") + session.transferCurrentComplicationUserInfo(context.rawValue) + lastComplicationContext = context + } else { + do { + log.default("updateApplicationContext") + try session.updateApplicationContext(context.rawValue) + } catch let error { + log.error(error) } } } @@ -266,6 +269,7 @@ extension WatchDataManager: WCSessionDelegate { log.error(error) } else { sendSettingsIfNeeded() + sendWatchContextIfNeeded() } case .inactive, .notActivated: break @@ -292,6 +296,7 @@ extension WatchDataManager: WCSessionDelegate { } func sessionDidDeactivate(_ session: WCSession) { + lastSentSettings = nil watchSession = WCSession.default watchSession?.delegate = self watchSession?.activate() @@ -301,3 +306,57 @@ extension WatchDataManager: WCSessionDelegate { sendSettingsIfNeeded() } } + + +extension WatchDataManager { + override var debugDescription: String { + var items = [ + "## WatchDataManager", + "lastSentSettings: \(String(describing: lastSentSettings))", + "lastComplicationContext: \(String(describing: lastComplicationContext))", + ] + + if let session = watchSession { + items.append(String(reflecting: session)) + } else { + items.append(contentsOf: [ + "watchSession: nil" + ]) + } + + return items.joined(separator: "\n") + } +} + + +extension WCSession { + open override var debugDescription: String { + return [ + "\(self)", + "* hasContentPending: \(hasContentPending)", + "* isComplicationEnabled: \(isComplicationEnabled)", + "* isPaired: \(isPaired)", + "* isReachable: \(isReachable)", + "* isWatchAppInstalled: \(isWatchAppInstalled)", + "* outstandingFileTransfers: \(outstandingFileTransfers)", + "* outstandingUserInfoTransfers: \(outstandingUserInfoTransfers)", + "* receivedApplicationContext: \(receivedApplicationContext)", + "* remainingComplicationUserInfoTransfers: \(remainingComplicationUserInfoTransfers)", + "* complicationUserInfoTransferInterval: \(round(complicationUserInfoTransferInterval.minutes)) min", + "* watchDirectoryURL: \(watchDirectoryURL?.absoluteString ?? "nil")", + ].joined(separator: "\n") + } + + fileprivate var complicationUserInfoTransferInterval: TimeInterval { + let now = Date() + let timeUntilMidnight: TimeInterval + + if let midnight = Calendar.current.nextDate(after: now, matching: DateComponents(hour: 0), matchingPolicy: .nextTime) { + timeUntilMidnight = midnight.timeIntervalSince(now) + } else { + timeUntilMidnight = .hours(24) + } + + return timeUntilMidnight / Double(remainingComplicationUserInfoTransfers + 1) + } +} diff --git a/WatchApp Extension/Base.lproj/Localizable.strings b/WatchApp Extension/Base.lproj/Localizable.strings index eec161a5e6..b26c9f6d64 100644 --- a/WatchApp Extension/Base.lproj/Localizable.strings +++ b/WatchApp Extension/Base.lproj/Localizable.strings @@ -1,3 +1,6 @@ +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Add Carb Entry"; + /* The title of the alert controller displayed after a bolus attempt fails */ "Bolus Failed" = "Bolus Failed"; diff --git a/WatchApp Extension/Controllers/ActionHUDController.swift b/WatchApp Extension/Controllers/ActionHUDController.swift index e5bfd49e68..71eef5a0f7 100644 --- a/WatchApp Extension/Controllers/ActionHUDController.swift +++ b/WatchApp Extension/Controllers/ActionHUDController.swift @@ -36,7 +36,7 @@ final class ActionHUDController: HUDInterfaceController { override func update() { super.update() - let schedule = loopManager?.settings.glucoseTargetRangeSchedule + let schedule = loopManager.settings.glucoseTargetRangeSchedule let activeOverrideContext: GlucoseRangeSchedule.Override.Context? if let glucoseRangeScheduleOverride = schedule?.override, glucoseRangeScheduleOverride.isActive() { @@ -82,7 +82,7 @@ final class ActionHUDController: HUDInterfaceController { // MARK: - Menu Items @IBAction func togglePreMealMode() { - guard var glucoseTargetRangeSchedule = loopManager?.settings.glucoseTargetRangeSchedule else { + guard var glucoseTargetRangeSchedule = loopManager.settings.glucoseTargetRangeSchedule else { return } if preMealButtonGroup.state == .on { @@ -97,7 +97,7 @@ final class ActionHUDController: HUDInterfaceController { } @IBAction func toggleWorkoutMode() { - guard var glucoseTargetRangeSchedule = loopManager?.settings.glucoseTargetRangeSchedule else { + guard var glucoseTargetRangeSchedule = loopManager.settings.glucoseTargetRangeSchedule else { return } if workoutButtonGroup.state == .on { @@ -127,11 +127,11 @@ final class ActionHUDController: HUDInterfaceController { if let error = error { if self.pendingMessageResponses == 0 { ExtensionDelegate.shared().present(error) - self.updateForOverrideContext(self.loopManager?.settings.glucoseTargetRangeSchedule?.override?.context) + self.updateForOverrideContext(self.loopManager.settings.glucoseTargetRangeSchedule?.override?.context) } } else { if self.pendingMessageResponses == 0 { - self.loopManager?.settings.glucoseTargetRangeSchedule = schedule + self.loopManager.settings.glucoseTargetRangeSchedule = schedule } } } @@ -139,7 +139,7 @@ final class ActionHUDController: HUDInterfaceController { } catch { pendingMessageResponses -= 1 if pendingMessageResponses == 0 { - updateForOverrideContext(self.loopManager?.settings.glucoseTargetRangeSchedule?.override?.context) + updateForOverrideContext(self.loopManager.settings.glucoseTargetRangeSchedule?.override?.context) } presentAlert( withTitle: NSLocalizedString("Send Failed", comment: "The title of the alert controller displayed after a glucose range override send attempt fails"), diff --git a/WatchApp Extension/Controllers/AddCarbsInterfaceController.swift b/WatchApp Extension/Controllers/AddCarbsInterfaceController.swift index 43e66d96e2..dbd84b4c89 100644 --- a/WatchApp Extension/Controllers/AddCarbsInterfaceController.swift +++ b/WatchApp Extension/Controllers/AddCarbsInterfaceController.swift @@ -181,6 +181,9 @@ final class AddCarbsInterfaceController: WKInterfaceController, IdentifiableClas replyHandler: { (suggestion) in DispatchQueue.main.async { WKInterfaceDevice.current().play(.success) + + ExtensionDelegate.shared().loopManager.addConfirmedCarbEntry(entry) + WKExtension.shared().rootInterfaceController?.presentController(withName: BolusInterfaceController.className, context: suggestion) } }, @@ -236,10 +239,20 @@ extension AddCarbsInterfaceController: WKCrownDelegate { } } - if delta > 0 { - WKInterfaceDevice.current().play(.click) - } else if delta < 0 { - WKInterfaceDevice.current().play(.click) + let isHapticFeedbackEnabled: Bool + + if #available(watchOSApplicationExtension 5.0, *), let crownSequencer = crownSequencer { + isHapticFeedbackEnabled = !crownSequencer.isHapticFeedbackEnabled + } else { + isHapticFeedbackEnabled = false + } + + if !isHapticFeedbackEnabled { + if delta > 0 { + WKInterfaceDevice.current().play(.click) + } else if delta < 0 { + WKInterfaceDevice.current().play(.click) + } } accumulatedRotation = remainder diff --git a/WatchApp Extension/Controllers/BolusInterfaceController.swift b/WatchApp Extension/Controllers/BolusInterfaceController.swift index e3015a67ca..74bf8daf54 100644 --- a/WatchApp Extension/Controllers/BolusInterfaceController.swift +++ b/WatchApp Extension/Controllers/BolusInterfaceController.swift @@ -109,6 +109,10 @@ final class BolusInterfaceController: WKInterfaceController, IdentifiableClass { override func willActivate() { // This method is called when watch view controller is about to be visible to user super.willActivate() + } + + override func didAppear() { + super.didAppear() crownSequencer.focus() } @@ -141,7 +145,11 @@ final class BolusInterfaceController: WKInterfaceController, IdentifiableClass { do { try WCSession.default.sendBolusMessage(bolus) { (error) in DispatchQueue.main.async { - ExtensionDelegate.shared().present(error) + if let error = error { + ExtensionDelegate.shared().present(error) + } else { + ExtensionDelegate.shared().loopManager.addConfirmedBolus(bolus) + } } } } catch { @@ -180,10 +188,20 @@ extension BolusInterfaceController: WKCrownDelegate { delta = 0 } - if delta > 0 { - WKInterfaceDevice.current().play(.click) - } else if delta < 0 { - WKInterfaceDevice.current().play(.click) + let isHapticFeedbackEnabled: Bool + + if #available(watchOSApplicationExtension 5.0, *), let crownSequencer = crownSequencer { + isHapticFeedbackEnabled = !crownSequencer.isHapticFeedbackEnabled + } else { + isHapticFeedbackEnabled = false + } + + if !isHapticFeedbackEnabled { + if delta > 0 { + WKInterfaceDevice.current().play(.click) + } else if delta < 0 { + WKInterfaceDevice.current().play(.click) + } } accumulatedRotation = remainder diff --git a/WatchApp Extension/Controllers/ChartHUDController.swift b/WatchApp Extension/Controllers/ChartHUDController.swift index 2545739dc7..1297a0ecc6 100644 --- a/WatchApp Extension/Controllers/ChartHUDController.swift +++ b/WatchApp Extension/Controllers/ChartHUDController.swift @@ -34,12 +34,13 @@ final class ChartHUDController: HUDInterfaceController, WKCrownDelegate { } } private let log = OSLog(category: "ChartHUDController") + private var hasInitialActivation = false override init() { super.init() loopManager = ExtensionDelegate.shared().loopManager - NotificationCenter.default.addObserver(forName: .GlucoseSamplesDidChange, object: loopManager?.glucoseStore, queue: nil) { [weak self] (note) in + NotificationCenter.default.addObserver(forName: .GlucoseSamplesDidChange, object: loopManager.glucoseStore, queue: nil) { [weak self] (note) in self?.log.default("Received GlucoseSamplesDidChange notification: %{public}@. Updating chart", String(describing: note.userInfo ?? [:])) DispatchQueue.main.async { @@ -99,7 +100,14 @@ final class ChartHUDController: HUDInterfaceController, WKCrownDelegate { log.default("willActivate() unpausing") } - loopManager?.requestGlucoseBackfillIfNecessary() + if !hasInitialActivation && UserDefaults.standard.startOnChartPage { + log.default("Switching to startOnChartPage") + becomeCurrentPage() + } + + hasInitialActivation = true + + loopManager.requestGlucoseBackfillIfNecessary() } override func didDeactivate() { @@ -112,7 +120,7 @@ final class ChartHUDController: HUDInterfaceController, WKCrownDelegate { override func update() { super.update() - guard let activeContext = loopManager?.activeContext else { + guard let activeContext = loopManager.activeContext else { return } @@ -179,15 +187,15 @@ final class ChartHUDController: HUDInterfaceController, WKCrownDelegate { } func updateGlucoseChart() { - guard let activeContext = loopManager?.activeContext else { + guard let activeContext = loopManager.activeContext else { return } scene.predictedGlucose = activeContext.predictedGlucose?.values - scene.correctionRange = loopManager?.settings.glucoseTargetRangeSchedule + scene.correctionRange = loopManager.settings.glucoseTargetRangeSchedule scene.unit = activeContext.preferredGlucoseUnit - loopManager?.glucoseStore.getCachedGlucoseSamples(start: .earliestGlucoseCutoff) { (samples) in + loopManager.glucoseStore.getCachedGlucoseSamples(start: .earliestGlucoseCutoff) { (samples) in DispatchQueue.main.async { self.scene.historicalGlucose = samples self.scene.setNeedsUpdate() diff --git a/WatchApp Extension/Controllers/HUDInterfaceController.swift b/WatchApp Extension/Controllers/HUDInterfaceController.swift index 55eb8299d7..efb35dc50c 100644 --- a/WatchApp Extension/Controllers/HUDInterfaceController.swift +++ b/WatchApp Extension/Controllers/HUDInterfaceController.swift @@ -16,11 +16,7 @@ class HUDInterfaceController: WKInterfaceController { @IBOutlet weak var glucoseLabel: WKInterfaceLabel! @IBOutlet weak var eventualGlucoseLabel: WKInterfaceLabel! - weak var loopManager: LoopDataManager? - - override init() { - loopManager = ExtensionDelegate.shared().loopManager - } + var loopManager = ExtensionDelegate.shared().loopManager override func willActivate() { super.willActivate() @@ -46,7 +42,9 @@ class HUDInterfaceController: WKInterfaceController { } func update() { - guard let activeContext = loopManager?.activeContext, let date = activeContext.loopLastRunDate else { + guard let activeContext = loopManager.activeContext, + let date = activeContext.loopLastRunDate + else { loopHUDImage.setLoopImage(.unknown) loopTimer.setHidden(true) return @@ -91,10 +89,10 @@ class HUDInterfaceController: WKInterfaceController { } @IBAction func setBolus() { - var context = loopManager?.activeContext?.bolusSuggestion ?? BolusSuggestionUserInfo(recommendedBolus: nil) + var context = loopManager.activeContext?.bolusSuggestion ?? BolusSuggestionUserInfo(recommendedBolus: nil) if context.maxBolus == nil { - context.maxBolus = loopManager?.settings.maximumBolus + context.maxBolus = loopManager.settings.maximumBolus } presentController(withName: BolusInterfaceController.className, context: context) diff --git a/WatchApp Extension/Extensions/WCSession.swift b/WatchApp Extension/Extensions/WCSession.swift index b135fede49..72055d4245 100644 --- a/WatchApp Extension/Extensions/WCSession.swift +++ b/WatchApp Extension/Extensions/WCSession.swift @@ -49,7 +49,7 @@ extension WCSession { ) } - func sendBolusMessage(_ userInfo: SetBolusUserInfo, errorHandler: @escaping (Error) -> Void) throws { + func sendBolusMessage(_ userInfo: SetBolusUserInfo, completionHandler: @escaping (Error?) -> Void) throws { guard activationState == .activated else { throw MessageError.activation } @@ -59,8 +59,12 @@ extension WCSession { } sendMessage(userInfo.rawValue, - replyHandler: { reply in }, - errorHandler: errorHandler + replyHandler: { reply in + completionHandler(nil) + }, + errorHandler: { error in + completionHandler(error) + } ) } diff --git a/WatchApp Extension/Managers/LoopDataManager.swift b/WatchApp Extension/Managers/LoopDataManager.swift index b8ac90d1f5..7f2b5f9e70 100644 --- a/WatchApp Extension/Managers/LoopDataManager.swift +++ b/WatchApp Extension/Managers/LoopDataManager.swift @@ -69,6 +69,18 @@ extension LoopDataManager { } } + func addConfirmedBolus(_ bolus: SetBolusUserInfo) { + dispatchPrecondition(condition: .onQueue(.main)) + + activeContext?.iob = (activeContext?.iob ?? 0) + bolus.value + } + + func addConfirmedCarbEntry(_ entry: CarbEntryUserInfo) { + dispatchPrecondition(condition: .onQueue(.main)) + + activeContext?.cob = (activeContext?.cob ?? 0) + entry.value + } + func sendDidUpdateContextNotificationIfNecessary() { dispatchPrecondition(condition: .onQueue(.main)) diff --git a/WatchApp Extension/Scenes/GlucoseChartScene.swift b/WatchApp Extension/Scenes/GlucoseChartScene.swift index 26264c64d7..ae6f6f7915 100644 --- a/WatchApp Extension/Scenes/GlucoseChartScene.swift +++ b/WatchApp Extension/Scenes/GlucoseChartScene.swift @@ -42,7 +42,7 @@ private extension SKLabelNode { private extension SKSpriteNode { func move(to rect: CGRect, animated: Bool) { - if parent == nil || animated == false { + if parent == nil || animated == false || (size.equalTo(rect.size) && position.equalTo(rect.origin)) { size = rect.size position = rect.origin } else { @@ -55,6 +55,19 @@ private extension SKSpriteNode { } } +extension CGRect { + fileprivate func alignedToScreenScale(_ screenScale: CGFloat) -> CGRect { + let factor = 1 / screenScale + + return CGRect( + x: origin.x.floored(to: factor), + y: origin.y.floored(to: factor), + width: size.width.ceiled(to: factor), + height: size.height.ceiled(to: factor) + ) + } +} + private struct Scaler { let dates: DateInterval let glucoseMin: Double @@ -81,7 +94,7 @@ private struct Scaler { let a = point(max(dates.start, range.start), minY) let b = point(min(dates.end, range.end), maxY) let size = CGSize(width: b.x - a.x, height: max(b.y - a.y, minHeight)) - return CGRect(origin: CGPoint(x: a.x + size.width / 2, y: a.y + size.height / 2), size: size) + return CGRect(origin: CGPoint(x: a.x + size.width / 2, y: a.y + size.height / 2), size: size).alignedToScreenScale(WKInterfaceDevice.current().screenScale) } } @@ -110,6 +123,8 @@ private extension HKUnit { class GlucoseChartScene: SKScene { let log = OSLog(category: "GlucoseChartScene") + var textInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5) + var unit: HKUnit? var correctionRange: GlucoseRangeSchedule? var historicalGlucose: [SampleValue]? { @@ -174,7 +189,7 @@ class GlucoseChartScene: SKScene { private var nodes: [Int: SKSpriteNode] = [:] private var predictedPathNode: SKShapeNode? - private var needsUpdate = false + private var needsUpdate = true private var shouldAnimatePredictionPath = false private lazy var dateFormatter: DateComponentsFormatter = { @@ -188,7 +203,16 @@ class GlucoseChartScene: SKScene { // 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 = width < 150 ? 68 : 86 + let height: CGFloat + + switch width { + case let x where x < 150: // 38mm + height = 68 + case let x where x > 180: // 44mm + height = 106 + default: + height = 86 + } super.init(size: CGSize(width: screen.width, height: height)) } @@ -206,14 +230,14 @@ class GlucoseChartScene: SKScene { now.zPosition = NodePlane.lines.zPosition addChild(now) - hoursLabel = SKLabelNode.basic(at: CGPoint(x: 5, y: size.height - 5)) + hoursLabel = SKLabelNode.basic(at: CGPoint(x: textInsets.left, y: size.height - textInsets.top)) addChild(hoursLabel) - maxBGLabel = SKLabelNode.basic(at: CGPoint(x: size.width - 5, y: size.height - 5)) + maxBGLabel = SKLabelNode.basic(at: CGPoint(x: size.width - textInsets.right, y: size.height - textInsets.top)) maxBGLabel.horizontalAlignmentMode = .right addChild(maxBGLabel) - minBGLabel = SKLabelNode.basic(at: CGPoint(x: size.width - 5, y: 5)) + minBGLabel = SKLabelNode.basic(at: CGPoint(x: size.width - textInsets.right, y: textInsets.bottom)) minBGLabel.horizontalAlignmentMode = .right minBGLabel.verticalAlignmentMode = .bottom addChild(minBGLabel) @@ -242,8 +266,6 @@ class GlucoseChartScene: SKScene { } } - log.default("childNodesHaveActions: %d", childNodesHaveActions) - return childNodesHaveActions } @@ -275,7 +297,7 @@ class GlucoseChartScene: SKScene { needsUpdate = true if isPaused { - log.default("updateNodes(animated:) unpausing") + log.default("setNeedsUpdate() unpausing") isPaused = false } } @@ -340,7 +362,7 @@ class GlucoseChartScene: SKScene { let (sprite, created) = getSprite(forHash: $0.chartHashValue) sprite.color = .glucose sprite.zPosition = NodePlane.values.zPosition - sprite.move(to: CGRect(origin: origin, size: size), animated: !created) + sprite.move(to: CGRect(origin: origin, size: size).alignedToScreenScale(WKInterfaceDevice.current().screenScale), animated: !created) inactiveNodes.removeValue(forKey: $0.chartHashValue) } diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/WatchApp/Assets.xcassets/AppIcon.appiconset/Contents.json index a8d3101a64..736d59705c 100644 --- a/WatchApp/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/WatchApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -38,6 +38,20 @@ "role" : "appLauncher", "subtype" : "38mm" }, + { + "size" : "44x44", + "idiom" : "watch", + "scale" : "2x", + "role" : "appLauncher", + "subtype" : "40mm" + }, + { + "size" : "50x50", + "idiom" : "watch", + "scale" : "2x", + "role" : "appLauncher", + "subtype" : "44mm" + }, { "size" : "86x86", "idiom" : "watch", @@ -54,6 +68,13 @@ "role" : "quickLook", "subtype" : "42mm" }, + { + "size" : "108x108", + "idiom" : "watch", + "scale" : "2x", + "role" : "quickLook", + "subtype" : "44mm" + }, { "size" : "1024x1024", "idiom" : "watch-marketing", @@ -65,4 +86,4 @@ "version" : 1, "author" : "xcode" } -} +} \ No newline at end of file diff --git a/WatchApp/Assets.xcassets/Graph menu icons/1-hour-graph.imageset/Contents.json b/WatchApp/Assets.xcassets/Graph menu icons/1-hour-graph.imageset/Contents.json index e835ece7e8..ad1e630c38 100644 --- a/WatchApp/Assets.xcassets/Graph menu icons/1-hour-graph.imageset/Contents.json +++ b/WatchApp/Assets.xcassets/Graph menu icons/1-hour-graph.imageset/Contents.json @@ -6,11 +6,21 @@ "screen-width" : "<=145", "scale" : "2x" }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, { "idiom" : "watch", "filename" : "1-hour-graph-42mm.png", "screen-width" : ">145", "scale" : "2x" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" } ], "info" : { diff --git a/WatchApp/Assets.xcassets/Graph menu icons/2-hour-graph.imageset/Contents.json b/WatchApp/Assets.xcassets/Graph menu icons/2-hour-graph.imageset/Contents.json index b2b2284ab7..7a15fc41f4 100644 --- a/WatchApp/Assets.xcassets/Graph menu icons/2-hour-graph.imageset/Contents.json +++ b/WatchApp/Assets.xcassets/Graph menu icons/2-hour-graph.imageset/Contents.json @@ -6,11 +6,21 @@ "screen-width" : "<=145", "scale" : "2x" }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, { "idiom" : "watch", "filename" : "2-hour-graph-42mm.png", "screen-width" : ">145", "scale" : "2x" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" } ], "info" : { diff --git a/WatchApp/Assets.xcassets/Graph menu icons/3-hour-graph.imageset/Contents.json b/WatchApp/Assets.xcassets/Graph menu icons/3-hour-graph.imageset/Contents.json index 17343c647c..d82030f923 100644 --- a/WatchApp/Assets.xcassets/Graph menu icons/3-hour-graph.imageset/Contents.json +++ b/WatchApp/Assets.xcassets/Graph menu icons/3-hour-graph.imageset/Contents.json @@ -6,11 +6,21 @@ "screen-width" : "<=145", "scale" : "2x" }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, { "idiom" : "watch", "filename" : "3-hour-graph-42mm.png", "screen-width" : ">145", "scale" : "2x" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" } ], "info" : { diff --git a/WatchApp/Assets.xcassets/bolus.imageset/Contents.json b/WatchApp/Assets.xcassets/bolus.imageset/Contents.json index 2dbb853d5c..c2810020f3 100644 --- a/WatchApp/Assets.xcassets/bolus.imageset/Contents.json +++ b/WatchApp/Assets.xcassets/bolus.imageset/Contents.json @@ -6,11 +6,21 @@ "screen-width" : "<=145", "scale" : "2x" }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, { "idiom" : "watch", "filename" : "Bolus Icon 42mm.png", "screen-width" : ">145", "scale" : "2x" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" } ], "info" : { diff --git a/WatchApp/Assets.xcassets/carbs.imageset/Contents.json b/WatchApp/Assets.xcassets/carbs.imageset/Contents.json index ecb8ce2515..5e0d2deaa5 100644 --- a/WatchApp/Assets.xcassets/carbs.imageset/Contents.json +++ b/WatchApp/Assets.xcassets/carbs.imageset/Contents.json @@ -6,11 +6,21 @@ "screen-width" : "<=145", "scale" : "2x" }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, { "idiom" : "watch", "filename" : "Carbs Icon 42mm.png", "screen-width" : ">145", "scale" : "2x" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" } ], "info" : { diff --git a/WatchApp/Assets.xcassets/loop/loop_aging.imageset/Contents.json b/WatchApp/Assets.xcassets/loop/loop_aging.imageset/Contents.json index cb4811aff0..f049e5e770 100644 --- a/WatchApp/Assets.xcassets/loop/loop_aging.imageset/Contents.json +++ b/WatchApp/Assets.xcassets/loop/loop_aging.imageset/Contents.json @@ -2,6 +2,7 @@ "images" : [ { "idiom" : "watch", + "filename" : "loop-aging@42mm.png", "scale" : "2x" }, { @@ -12,9 +13,18 @@ }, { "idiom" : "watch", - "filename" : "loop-aging@42mm.png", - "screen-width" : ">145", - "scale" : "2x" + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" } ], "info" : { diff --git a/WatchApp/Assets.xcassets/loop/loop_fresh.imageset/Contents.json b/WatchApp/Assets.xcassets/loop/loop_fresh.imageset/Contents.json index 585f56d7fa..f4a4a1fadd 100644 --- a/WatchApp/Assets.xcassets/loop/loop_fresh.imageset/Contents.json +++ b/WatchApp/Assets.xcassets/loop/loop_fresh.imageset/Contents.json @@ -2,6 +2,7 @@ "images" : [ { "idiom" : "watch", + "filename" : "loop-fresh@42mm.png", "scale" : "2x" }, { @@ -12,9 +13,18 @@ }, { "idiom" : "watch", - "filename" : "loop-fresh@42mm.png", - "screen-width" : ">145", - "scale" : "2x" + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" } ], "info" : { diff --git a/WatchApp/Assets.xcassets/loop/loop_stale.imageset/Contents.json b/WatchApp/Assets.xcassets/loop/loop_stale.imageset/Contents.json index d849fa9344..abeb896280 100644 --- a/WatchApp/Assets.xcassets/loop/loop_stale.imageset/Contents.json +++ b/WatchApp/Assets.xcassets/loop/loop_stale.imageset/Contents.json @@ -2,6 +2,7 @@ "images" : [ { "idiom" : "watch", + "filename" : "loop-stale@42mm.png", "scale" : "2x" }, { @@ -12,9 +13,18 @@ }, { "idiom" : "watch", - "filename" : "loop-stale@42mm.png", - "screen-width" : ">145", - "scale" : "2x" + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" } ], "info" : { diff --git a/WatchApp/Assets.xcassets/loop/loop_unknown.imageset/Contents.json b/WatchApp/Assets.xcassets/loop/loop_unknown.imageset/Contents.json index 91ea68fbd0..b474a60c23 100644 --- a/WatchApp/Assets.xcassets/loop/loop_unknown.imageset/Contents.json +++ b/WatchApp/Assets.xcassets/loop/loop_unknown.imageset/Contents.json @@ -2,6 +2,7 @@ "images" : [ { "idiom" : "watch", + "filename" : "loop-unknown@42mm.png", "scale" : "2x" }, { @@ -12,9 +13,18 @@ }, { "idiom" : "watch", - "filename" : "loop-unknown@42mm.png", - "screen-width" : ">145", - "scale" : "2x" + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" } ], "info" : { diff --git a/WatchApp/Assets.xcassets/pre-meal.imageset/Contents.json b/WatchApp/Assets.xcassets/pre-meal.imageset/Contents.json index c8be362ad1..581fbb49aa 100644 --- a/WatchApp/Assets.xcassets/pre-meal.imageset/Contents.json +++ b/WatchApp/Assets.xcassets/pre-meal.imageset/Contents.json @@ -6,11 +6,21 @@ "screen-width" : "<=145", "scale" : "2x" }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, { "idiom" : "watch", "filename" : "Pre-Meal 42mm.png", "screen-width" : ">145", "scale" : "2x" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" } ], "info" : { diff --git a/WatchApp/Assets.xcassets/workout.imageset/Contents.json b/WatchApp/Assets.xcassets/workout.imageset/Contents.json index d5d7862146..cc9ab23361 100644 --- a/WatchApp/Assets.xcassets/workout.imageset/Contents.json +++ b/WatchApp/Assets.xcassets/workout.imageset/Contents.json @@ -6,11 +6,21 @@ "screen-width" : "<=145", "scale" : "2x" }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">161" + }, { "idiom" : "watch", "filename" : "Workout 42mm.png", "screen-width" : ">145", "scale" : "2x" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" } ], "info" : { diff --git a/WatchApp/Base.lproj/Interface.storyboard b/WatchApp/Base.lproj/Interface.storyboard index 8da55eb2df..23d08d8cf7 100644 --- a/WatchApp/Base.lproj/Interface.storyboard +++ b/WatchApp/Base.lproj/Interface.storyboard @@ -1,18 +1,18 @@ - - + + - - + + - + @@ -98,14 +98,22 @@ - + + + + + + + + + @@ -153,6 +161,8 @@ + + @@ -269,7 +279,7 @@ - + @@ -313,14 +323,22 @@ - + + + + + + + + + @@ -334,7 +352,7 @@ - + @@ -361,10 +379,12 @@ - + + @@ -373,16 +393,19 @@ + - + - + + From ea7986ac757eca03b4eadc0f84d8f39fe0fa696a Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 5 Oct 2018 00:25:43 -0500 Subject: [PATCH 2/3] Disable crown haptics --- .../AddCarbsInterfaceController.swift | 30 +------------------ .../BolusInterfaceController.swift | 24 +-------------- 2 files changed, 2 insertions(+), 52 deletions(-) diff --git a/WatchApp Extension/Controllers/AddCarbsInterfaceController.swift b/WatchApp Extension/Controllers/AddCarbsInterfaceController.swift index dbd84b4c89..5b46a63147 100644 --- a/WatchApp Extension/Controllers/AddCarbsInterfaceController.swift +++ b/WatchApp Extension/Controllers/AddCarbsInterfaceController.swift @@ -218,41 +218,13 @@ extension AddCarbsInterfaceController: WKCrownDelegate { func crownDidRotate(_ crownSequencer: WKCrownSequencer?, rotationalDelta: Double) { accumulatedRotation += rotationalDelta let remainder = accumulatedRotation.truncatingRemainder(dividingBy: rotationsPerIncrement) - var delta = Int((accumulatedRotation - remainder) / rotationsPerIncrement) + let delta = Int((accumulatedRotation - remainder) / rotationsPerIncrement) switch inputMode { case .value: - let oldValue = carbValue carbValue += delta - - // If we didn't change, adjust the delta to prevent the haptic - if oldValue == carbValue { - delta = 0 - } case .date: - let oldValue = date date += TimeInterval(minutes: Double(delta)) - - // If we didn't change, adjust the delta to prevent the haptic - if oldValue == date { - delta = 0 - } - } - - let isHapticFeedbackEnabled: Bool - - if #available(watchOSApplicationExtension 5.0, *), let crownSequencer = crownSequencer { - isHapticFeedbackEnabled = !crownSequencer.isHapticFeedbackEnabled - } else { - isHapticFeedbackEnabled = false - } - - if !isHapticFeedbackEnabled { - if delta > 0 { - WKInterfaceDevice.current().play(.click) - } else if delta < 0 { - WKInterfaceDevice.current().play(.click) - } } accumulatedRotation = remainder diff --git a/WatchApp Extension/Controllers/BolusInterfaceController.swift b/WatchApp Extension/Controllers/BolusInterfaceController.swift index 74bf8daf54..6d83a3da29 100644 --- a/WatchApp Extension/Controllers/BolusInterfaceController.swift +++ b/WatchApp Extension/Controllers/BolusInterfaceController.swift @@ -178,32 +178,10 @@ extension BolusInterfaceController: WKCrownDelegate { accumulatedRotation += rotationalDelta let remainder = accumulatedRotation.truncatingRemainder(dividingBy: rotationsPerValue) - var delta = Int((accumulatedRotation - remainder) / rotationsPerValue) + let delta = Int((accumulatedRotation - remainder) / rotationsPerValue) - let oldValue = pickerValue pickerValue += delta - // If we didn't change, adjust the delta to prevent the haptic - if oldValue == pickerValue { - delta = 0 - } - - let isHapticFeedbackEnabled: Bool - - if #available(watchOSApplicationExtension 5.0, *), let crownSequencer = crownSequencer { - isHapticFeedbackEnabled = !crownSequencer.isHapticFeedbackEnabled - } else { - isHapticFeedbackEnabled = false - } - - if !isHapticFeedbackEnabled { - if delta > 0 { - WKInterfaceDevice.current().play(.click) - } else if delta < 0 { - WKInterfaceDevice.current().play(.click) - } - } - accumulatedRotation = remainder } } From 232a0297bec1cec2fc1ae59c2eadca49d47e228d Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 5 Oct 2018 08:04:33 -0500 Subject: [PATCH 3/3] Redraw immediately --- WatchApp Extension/Scenes/GlucoseChartScene.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/WatchApp Extension/Scenes/GlucoseChartScene.swift b/WatchApp Extension/Scenes/GlucoseChartScene.swift index ae6f6f7915..b5a11687ee 100644 --- a/WatchApp Extension/Scenes/GlucoseChartScene.swift +++ b/WatchApp Extension/Scenes/GlucoseChartScene.swift @@ -182,7 +182,11 @@ class GlucoseChartScene: SKScene { return lowerBound..