Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
2118529
Initial pass at creating a lock screen widget (aka. "Today Extension")
Nov 26, 2016
36235be
Use MAIN_APP_BUNDLE_IDENTIFIER instead of hardcoding com.loudnate
Nov 26, 2016
1b68117
Rename the widget to just "Loop" and remove the iOS 10.1 install requ…
Nov 26, 2016
e013975
Add basal, reservoir and battery data to the today extension.
Nov 26, 2016
7bcdf83
Improve the way that TodayExtensionContext handles optional sections
Nov 26, 2016
9a6845c
Clean up the UI
Nov 26, 2016
f068d02
Add support for net basal rates in the lock screen widget
Nov 26, 2016
0346dd1
Pass sensor info along to the extension.
Nov 27, 2016
c408bd5
Plumb the "eventual glucose" value through to a subtitle in the widget.
Nov 27, 2016
499f37b
Internationalize the eventual glucose string properly
Nov 27, 2016
1a3013d
Clean up TodayExtensionContext innards
Nov 27, 2016
3159a0b
Rename the widget and all references to 'Loop Status Extension'
Nov 28, 2016
e7de41d
Update version number to 1.1.2 to match container.
Nov 28, 2016
7e6653b
Rename scheme to match new extension name
Nov 28, 2016
d200128
Remove boilerplate comments
Nov 28, 2016
742bebe
Remove vestigial lastContext code
Nov 28, 2016
716ed58
Change eventual glucose to be a double value instead of a string, and
Nov 28, 2016
3a8ffa5
Squish a few small bugs in context data transfer and with preferred u…
Nov 28, 2016
8e75c7e
Merge remote-tracking branch 'origin/dev' into lock-screen-widget
Nov 28, 2016
75cc914
Correctly deserialize GlucoseTrend.
Nov 29, 2016
ef376ac
Merge branch 'dev' into lock-screen-widget
Dec 1, 2016
569cd21
Fix busted merge.
Dec 1, 2016
a0bfc18
Merge branch 'dev' into lock-screen-widget
Dec 1, 2016
f40050d
Remove unnecessary imports and frameworks to minimize exposure to
Dec 2, 2016
3722e7f
Change GlucoseHUDView.set() to stop taking GlucoseValue
Dec 2, 2016
5d68765
Elminate all framework dependencies from Loop Status Extension
Dec 2, 2016
693a264
Merge branch 'dev' into lock-screen-widget
Dec 2, 2016
f09ae0b
Resolve blown merge.
Dec 2, 2016
b41ceda
Add HKUnit extension to get preferredMinimumFractionDigits
Dec 3, 2016
35d12c3
Back out erroneous DEVELOPMENT_TEAM values.
Dec 4, 2016
1eafedf
Fix API nit
Dec 7, 2016
e9a786c
Rely on MAIN_APP_BUNDLE_IDENTIFIER
Dec 7, 2016
e3dfb00
Rename .shared to .appGroup and make it a class var (modeling .standard)
Dec 7, 2016
3b9738d
Move NetBasal calculations into its own class
Dec 8, 2016
8b18555
Move the app group suite name into a per-target Bundle extension
Dec 8, 2016
5180c45
Refactor the Bundle.appGroupSuiteName extension code for clarity
Dec 8, 2016
2168ad3
Switch to a struct; use let instead of var
Dec 8, 2016
a048e0b
Make all context struct members constant
Dec 9, 2016
00d47a3
Set the Loop Status Extension build to CURRENT_PROJECT_VERSION
Dec 9, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
334 changes: 334 additions & 0 deletions Loop Status Extension/Base.lproj/MainInterface.storyboard

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions Loop Status Extension/HKUnit.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// HKUnit.swift
// Loop
//
// Created by Bharat Mediratta on 12/2/16.
// Copyright © 2016 LoopKit Authors. All rights reserved.
//

import HealthKit

public extension HKUnit {
// A formatting helper for determining the preferred decimal style for a given unit
// This is similar to the LoopKit HKUnit extension, but copied here so that we can
// avoid a dependency on LoopKit from the Loop Status Extension.
var preferredMinimumFractionDigits: Int {
if self.unitString == "mg/dL" {
return 0
} else {
return 1
}
}
}
33 changes: 33 additions & 0 deletions Loop Status Extension/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>Loop</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.1.2</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>MainAppBundleIdentifier</key>
<string>$(MAIN_APP_BUNDLE_IDENTIFIER)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widget-extension</string>
</dict>
</dict>
</plist>
10 changes: 10 additions & 0 deletions Loop Status Extension/Loop Status Extension.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.$(MAIN_APP_BUNDLE_IDENTIFIER)</string>
</array>
</dict>
</plist>
163 changes: 163 additions & 0 deletions Loop Status Extension/StatusExtensionContext.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
//
// StatusExtensionContext.swift
// Loop Status Extension
//
// Created by Bharat Mediratta on 11/25/16.
// Copyright © 2016 LoopKit Authors. All rights reserved.
//
// This class allows Loop to pass context data to the Loop Status Extension.

import Foundation
import HealthKit

struct ReservoirContext {
let startDate: Date
let unitVolume: Double
let capacity: Int
}

struct LoopContext {
let dosingEnabled: Bool
let lastCompleted: Date?
}

struct NetBasalContext {
let rate: Double
let percentage: Double
let startDate: Date
}

struct SensorDisplayableContext: SensorDisplayable {
let isStateValid: Bool
let stateDescription: String
let trendType: GlucoseTrend?
let isLocal: Bool
}

struct GlucoseContext {
let quantity: Double
let startDate: Date
let sensor: SensorDisplayable?
}

final class StatusExtensionContext: NSObject, RawRepresentable {
typealias RawValue = [String: Any]
private let version = 1

var preferredUnitDisplayString: String?
var latestGlucose: GlucoseContext?
var reservoir: ReservoirContext?
var loop: LoopContext?
var netBasal: NetBasalContext?
var batteryPercentage: Double?
var eventualGlucose: Double?

override init() {
super.init()
}

required init?(rawValue: RawValue) {
super.init()
let raw = rawValue

if let preferredString = raw["preferredUnitDisplayString"] as? String,
let latestValue = raw["latestGlucose_value"] as? Double,
let startDate = raw["latestGlucose_startDate"] as? Date {

var sensor: SensorDisplayableContext? = nil
if let state = raw["latestGlucose_sensor_isStateValid"] as? Bool,
let desc = raw["latestGlucose_sensor_stateDescription"] as? String,
let local = raw["latestGlucose_sensor_isLocal"] as? Bool {

var glucoseTrend: GlucoseTrend?
if let trendType = raw["latestGlucose_sensor_trendType"] as? Int {
glucoseTrend = GlucoseTrend(rawValue: trendType)
}

sensor = SensorDisplayableContext(
isStateValid: state,
stateDescription: desc,
trendType: glucoseTrend,
isLocal: local)
}

preferredUnitDisplayString = preferredString
latestGlucose = GlucoseContext(
quantity: latestValue,
startDate: startDate,
sensor: sensor)
}

batteryPercentage = raw["batteryPercentage"] as? Double

if let startDate = raw["reservoir_startDate"] as? Date,
let unitVolume = raw["reservoir_unitVolume"] as? Double,
let capacity = raw["reservoir_capacity"] as? Int {
reservoir = ReservoirContext(startDate: startDate, unitVolume: unitVolume, capacity: capacity)
}

if let dosingEnabled = raw["loop_dosingEnabled"] as? Bool,
let lastCompleted = raw["loop_lastCompleted"] as? Date {
loop = LoopContext(dosingEnabled: dosingEnabled, lastCompleted: lastCompleted)
}

if let rate = raw["netBasal_rate"] as? Double,
let percentage = raw["netBasal_percentage"] as? Double,
let startDate = raw["netBasal_startDate"] as? Date {
netBasal = NetBasalContext(rate: rate, percentage: percentage, startDate: startDate)
}

eventualGlucose = raw["eventualGlucose"] as? Double
}

var rawValue: RawValue {
var raw: RawValue = [
"version": version
]

raw["preferredUnitDisplayString"] = preferredUnitDisplayString

if let glucose = latestGlucose,
preferredUnitDisplayString != nil {
raw["latestGlucose_value"] = glucose.quantity
raw["latestGlucose_startDate"] = glucose.startDate
}

if let sensor = latestGlucose?.sensor {
raw["latestGlucose_sensor_isStateValid"] = sensor.isStateValid
raw["latestGlucose_sensor_stateDescription"] = sensor.stateDescription
raw["latestGlucose_sensor_isLocal"] = sensor.isLocal

if let trendType = sensor.trendType {
raw["latestGlucose_sensor_trendType"] = trendType.rawValue
}
}

if let batteryPercentage = batteryPercentage {
raw["batteryPercentage"] = batteryPercentage
}

if let reservoir = reservoir {
raw["reservoir_startDate"] = reservoir.startDate
raw["reservoir_unitVolume"] = reservoir.unitVolume
raw["reservoir_capacity"] = reservoir.capacity
}

if let loop = loop {
raw["loop_dosingEnabled"] = loop.dosingEnabled
raw["loop_lastCompleted"] = loop.lastCompleted
}

if let netBasal = netBasal {
raw["netBasal_rate"] = netBasal.rate
raw["netBasal_percentage"] = netBasal.percentage
raw["netBasal_startDate"] = netBasal.startDate
}

if let eventualGlucose = eventualGlucose {
raw["eventualGlucose"] = eventualGlucose
}

return raw
}
}
95 changes: 95 additions & 0 deletions Loop Status Extension/StatusViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//
// StatusViewController.swift
// Loop Status Extension
//
// Created by Bharat Mediratta on 11/25/16.
// Copyright © 2016 LoopKit Authors. All rights reserved.
//

import UIKit
import NotificationCenter
import HealthKit
import CoreData

class StatusViewController: UIViewController, NCWidgetProviding {

@IBOutlet weak var loopCompletionHUD: LoopCompletionHUDView!
@IBOutlet weak var glucoseHUD: GlucoseHUDView!
@IBOutlet weak var basalRateHUD: BasalRateHUDView!
@IBOutlet weak var reservoirVolumeHUD: ReservoirVolumeHUDView!
@IBOutlet weak var batteryHUD: BatteryLevelHUDView!
@IBOutlet weak var subtitleLabel: UILabel!

override func viewDidLoad() {
super.viewDidLoad()
subtitleLabel.alpha = 0
subtitleLabel.textColor = UIColor.secondaryLabelColor
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}

func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) {
guard
let context = UserDefaults(suiteName: Bundle.main.appGroupSuiteName)?.statusExtensionContext
else {
completionHandler(NCUpdateResult.failed)
return
}

// We should never have the case where there's glucose values but no preferred
// unit. However, if that case were to happen we might show quantities against
// the wrong units and that could be very harmful. So unless there's a preferred
// unit, assume that none of the rest of the data is reliable.
guard
let preferredUnitDisplayString = context.preferredUnitDisplayString
else {
completionHandler(NCUpdateResult.failed)
return
}

if let glucose = context.latestGlucose {
glucoseHUD.set(glucoseQuantity: glucose.quantity,
at: glucose.startDate,
unitDisplayString: preferredUnitDisplayString,
from: glucose.sensor)
}

if let batteryPercentage = context.batteryPercentage {
batteryHUD.batteryLevel = Double(batteryPercentage)
}

if let reservoir = context.reservoir {
reservoirVolumeHUD.reservoirLevel = min(1, max(0, Double(reservoir.unitVolume / Double(reservoir.capacity))))
reservoirVolumeHUD.setReservoirVolume(volume: reservoir.unitVolume, at: reservoir.startDate)
}

if let netBasal = context.netBasal {
basalRateHUD.setNetBasalRate(netBasal.rate, percent: netBasal.percentage, at: netBasal.startDate)
}

if let loop = context.loop {
loopCompletionHUD.dosingEnabled = loop.dosingEnabled
loopCompletionHUD.lastLoopCompleted = loop.lastCompleted
}

if let eventualGlucose = context.eventualGlucose {
let quantity = HKQuantity(unit: HKUnit(from: preferredUnitDisplayString),
doubleValue: eventualGlucose.rounded())
subtitleLabel.text = String(
format: NSLocalizedString(
"Eventually %@",
comment: "The subtitle format describing eventual glucose. (1: localized glucose value description)"),
String(describing: quantity))
subtitleLabel.alpha = 1
} else {
subtitleLabel.alpha = 0
}

// Right now we always act as if there's new data.
// TODO: keep track of data changes and return .noData if necessary
completionHandler(NCUpdateResult.newData)
}

}
Loading