Skip to content

Commit

Permalink
Merge pull request #104 from LoopKit/dev
Browse files Browse the repository at this point in the history
Version 1.2.0
  • Loading branch information
ps2 committed Feb 20, 2017
2 parents 86c756f + 97199e2 commit 1b16cc9
Show file tree
Hide file tree
Showing 38 changed files with 525 additions and 241 deletions.
12 changes: 6 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
language: objective-c
osx_image: xcode8
xcode_sdk: iphonesimulator10.0
osx_image: xcode8.2
xcode_sdk: iphonesimulator10.2
xcode_project: LoopKit.xcodeproj
xcode_scheme:
- LoopKit
- CarbKit
- InsulinKit
- GlucoseKit
script:
- xcodebuild -project LoopKit.xcodeproj -scheme LoopKit -sdk iphonesimulator10.0 build -destination 'name=iPhone SE' test
- xcodebuild -project LoopKit.xcodeproj -scheme CarbKit -sdk iphonesimulator10.0 build -destination 'name=iPhone SE' test
- xcodebuild -project LoopKit.xcodeproj -scheme InsulinKit -sdk iphonesimulator10.0 build -destination 'name=iPhone SE' test
- xcodebuild -project LoopKit.xcodeproj -scheme GlucoseKit -sdk iphonesimulator10.0 build -destination 'name=iPhone SE' test
- xcodebuild -project LoopKit.xcodeproj -scheme LoopKit -sdk iphonesimulator10.2 build -destination 'id=DB794781-65A7-4884-8D00-AAC3CBD39A44' test
- xcodebuild -project LoopKit.xcodeproj -scheme CarbKit -sdk iphonesimulator10.2 build -destination 'id=DB794781-65A7-4884-8D00-AAC3CBD39A44' test
- xcodebuild -project LoopKit.xcodeproj -scheme InsulinKit -sdk iphonesimulator10.2 build -destination 'id=DB794781-65A7-4884-8D00-AAC3CBD39A44' test
- xcodebuild -project LoopKit.xcodeproj -scheme GlucoseKit -sdk iphonesimulator10.2 build -destination 'id=DB794781-65A7-4884-8D00-AAC3CBD39A44' test
121 changes: 53 additions & 68 deletions CarbKit/CarbStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ public protocol CarbStoreDelegate: class {
///
/// - parameter carbStore: The carb store
/// - parameter error: The error describing the issue
///
/// - returns: <#return value description#>
func carbStore(_ carbStore: CarbStore, didError error: CarbStore.CarbStoreError)
}

Expand Down Expand Up @@ -193,9 +191,6 @@ public final class CarbStore: HealthKitSampleStore {
/// All active observer queries
private var observerQueries: [HKObserverQuery] = []

/// All active anchored object queries, by sample type
private var anchoredObjectQueries: [HKSampleType: HKAnchoredObjectQuery] = [:]

/// The last-retreived anchor for each anchored object query, by sample type
private var queryAnchors: [HKObjectType: HKQueryAnchor] = [:]

Expand All @@ -209,13 +204,9 @@ public final class CarbStore: HealthKitSampleStore {
self.delegate?.carbStore(self, didError: .healthStoreError(error))
} else {
self.dataAccessQueue.async {
if self.anchoredObjectQueries[type] == nil {
let anchoredObjectQuery = HKAnchoredObjectQuery(type: type, predicate: predicate, anchor: self.queryAnchors[type], limit: Int(HKObjectQueryNoLimit), resultsHandler: self.processResultsFromAnchoredQuery)
anchoredObjectQuery.updateHandler = self.processResultsFromAnchoredQuery
let anchoredObjectQuery = HKAnchoredObjectQuery(type: type, predicate: predicate, anchor: self.queryAnchors[type], limit: Int(HKObjectQueryNoLimit), resultsHandler: self.processResultsFromAnchoredQuery)

self.anchoredObjectQueries[type] = anchoredObjectQuery
self.healthStore.execute(anchoredObjectQuery)
}
self.healthStore.execute(anchoredObjectQuery)
}
}

Expand All @@ -231,10 +222,6 @@ public final class CarbStore: HealthKitSampleStore {
for query in observerQueries {
healthStore.stop(query)
}

for query in anchoredObjectQueries.values {
healthStore.stop(query)
}
}

// MARK: - Background management
Expand Down Expand Up @@ -319,33 +306,32 @@ public final class CarbStore: HealthKitSampleStore {
}

dataAccessQueue.async {
// Prune the sample data based on the startDate and deletedSamples array
let cutoffDate = Date(timeIntervalSinceNow: -self.maximumAbsorptionTimeInterval)
var notificationRequired = false

self.carbEntryCache = Set(self.carbEntryCache.filter { (entry) in
if entry.startDate < cutoffDate {
return false
} else if let deletedSamples = deletedSamples, deletedSamples.contains(where: { $0.uuid == entry.sampleUUID as UUID }) {
notificationRequired = true
return false
} else {
return true
}
})

// Append the new samples
if let samples = newSamples as? [HKQuantitySample] {
for sample in samples {
let entry = StoredCarbEntry(sample: sample)

if entry.startDate >= cutoffDate && !self.carbEntryCache.contains(entry) {
if !self.carbEntryCache.contains(entry) {
notificationRequired = true
self.carbEntryCache.insert(entry)
}
}
}

// Remove deleted samples
for sample in deletedSamples ?? [] {
if let index = self.carbEntryCache.index(where: { $0.sampleUUID == sample.uuid }) {
self.carbEntryCache.remove(at: index)
notificationRequired = true
}
}

// Filter old samples
self.carbEntryCache = Set(self.carbEntryCache.filter { $0.startDate >= cutoffDate })

// Update the anchor
self.queryAnchors[query.objectType!] = anchor

Expand Down Expand Up @@ -374,13 +360,6 @@ public final class CarbStore: HealthKitSampleStore {
return HKQuery.predicateForSamples(withStart: startDate ?? recentSamplesStartDate, end: endDate ?? Date.distantFuture, options: [.strictStartDate])
}

private func getCachedCarbSamples(startDate: Date? = nil, endDate: Date? = nil, resultsHandler: @escaping (_ entries: [StoredCarbEntry], _ error: CarbStoreError?) -> Void) {
dataAccessQueue.async {
let entries = self.carbEntryCache.filterDateRange(startDate, endDate)
resultsHandler(entries, nil)
}
}

private func getRecentCarbSamples(startDate: Date? = nil, endDate: Date? = nil, resultsHandler: @escaping (_ entries: [StoredCarbEntry], _ error: CarbStoreError?) -> Void) {
if UIApplication.shared.isProtectedDataAvailable {
let predicate = recentSamplesPredicate(startDate: startDate, endDate: endDate)
Expand All @@ -389,20 +368,24 @@ public final class CarbStore: HealthKitSampleStore {
let query = HKSampleQuery(sampleType: carbType, predicate: predicate, limit: Int(HKObjectQueryNoLimit), sortDescriptors: sortDescriptors) { (_, samples, error) -> Void in

if let error = error as? NSError, error.code == HKError.errorDatabaseInaccessible.rawValue {
self.getCachedCarbSamples(startDate: startDate, endDate: endDate, resultsHandler: resultsHandler)
self.dataAccessQueue.async {
resultsHandler(self.carbEntryCache.filterDateRange(startDate, endDate), nil)
}
} else {
resultsHandler(
(samples as? [HKQuantitySample])?.map {
StoredCarbEntry(sample: $0)
} ?? [],
} ?? [],
error != nil ? .healthStoreError(error!) : nil
)
}
}

healthStore.execute(query)
} else {
getCachedCarbSamples(startDate: startDate, endDate: endDate, resultsHandler: resultsHandler)
dataAccessQueue.async {
resultsHandler(self.carbEntryCache.filterDateRange(startDate, endDate), nil)
}
}
}

Expand Down Expand Up @@ -558,10 +541,12 @@ public final class CarbStore: HealthKitSampleStore {
UserDefaults.standard.carbEntryCache = Array<StoredCarbEntry>(carbEntryCache)
}

/// *This method should only be called from the `dataAccessQueue`*
private func persistModifiedCarbEntries() {
UserDefaults.standard.modifiedCarbEntries = Array<StoredCarbEntry>(self.modifiedCarbEntries)
}

/// *This method should only be called from the `dataAccessQueue`*
private func persistDeletedCarbEntryIds() {
UserDefaults.standard.deletedCarbEntryIds = Array<String>(self.deletedCarbEntryIds)
}
Expand Down Expand Up @@ -601,20 +586,15 @@ public final class CarbStore: HealthKitSampleStore {
startDate: Date? = nil,
endDate: Date? = nil,
resultHandler: @escaping (_ values: [CarbValue], _ error: Error?) -> Void) {

dataAccessQueue.async { [unowned self] in
if self.carbsOnBoardCache == nil {
self.getCachedCarbSamples { (entries, error) -> Void in
if error == nil {
self.carbsOnBoardCache = CarbMath.carbsOnBoardForCarbEntries(entries,
defaultAbsorptionTime: self.defaultAbsorptionTimes.medium,
delay: self.delay,
delta: self.delta
)
}
self.carbsOnBoardCache = CarbMath.carbsOnBoardForCarbEntries(self.carbEntryCache,
defaultAbsorptionTime: self.defaultAbsorptionTimes.medium,
delay: self.delay,
delta: self.delta
)

resultHandler(self.carbsOnBoardCache?.filterDateRange(startDate, endDate).map { $0 } ?? [], error)
}
resultHandler(self.carbsOnBoardCache?.filterDateRange(startDate, endDate).map { $0 } ?? [], nil)
} else {
resultHandler(self.carbsOnBoardCache?.filterDateRange(startDate, endDate) ?? [], nil)
}
Expand All @@ -626,33 +606,29 @@ public final class CarbStore: HealthKitSampleStore {
This operation is performed asynchronously and the completion will be executed on an arbitrary background queue.
- parameter startDate: The earliest date of effects to retrieve. The default, and earliest supported value, is the previous midnight in the current time zone.
- parameter startDate: The earliest date of effects to retrieve. The earliest supported value is the previous midnight in the current time zone.
- parameter endDate: The latest date of effects to retrieve. Defaults to the distant future.
- parameter resultHandler: A closure called once the effects have been retrieved. The closure takes two arguments:
- effects: The retrieved timeline of effects
- error: An error object explaining why the retrieval failed
*/
public func getGlucoseEffects(
startDate: Date? = nil,
startDate: Date,
endDate: Date? = nil,
resultHandler: @escaping (_ effects: [GlucoseEffect], _ error: CarbStoreError?) -> Void) {

resultHandler: @escaping (_ effects: [GlucoseEffect], _ error: CarbStoreError?) -> Void)
{
dataAccessQueue.async {
if self.glucoseEffectsCache == nil {
if let carbRatioSchedule = self.carbRatioSchedule, let insulinSensitivitySchedule = self.insulinSensitivitySchedule {
self.getCachedCarbSamples { (entries, error) -> Void in
if error == nil {
self.glucoseEffectsCache = CarbMath.glucoseEffectsForCarbEntries(entries,
carbRatios: carbRatioSchedule,
insulinSensitivities: insulinSensitivitySchedule,
defaultAbsorptionTime: self.defaultAbsorptionTimes.medium,
delay: self.delay,
delta: self.delta
)
}
self.glucoseEffectsCache = CarbMath.glucoseEffectsForCarbEntries(self.carbEntryCache,
carbRatios: carbRatioSchedule,
insulinSensitivities: insulinSensitivitySchedule,
defaultAbsorptionTime: self.defaultAbsorptionTimes.medium,
delay: self.delay,
delta: self.delta
)

resultHandler(self.glucoseEffectsCache?.filterDateRange(startDate, endDate) ?? [], error)
}
resultHandler(self.glucoseEffectsCache?.filterDateRange(startDate, endDate) ?? [], nil)
} else {
resultHandler([], .configurationError)
}
Expand Down Expand Up @@ -685,6 +661,10 @@ public final class CarbStore: HealthKitSampleStore {
///
/// - parameter completionHandler: A closure called once the report has been generated. The closure takes a single argument of the report string.
public func generateDiagnosticReport(_ completionHandler: @escaping (_ report: String) -> Void) {
func entryReport(_ entry: CarbEntry) -> String {
return "* \(entry.startDate), \(entry.quantity), \(entry.absorptionTime ?? self.defaultAbsorptionTimes.medium), \(entry.createdByCurrentApp ? "" : "External")"
}

var report: [String] = [
"## CarbStore",
"",
Expand All @@ -693,9 +673,15 @@ public final class CarbStore: HealthKitSampleStore {
"* insulinSensitivitySchedule: \(insulinSensitivitySchedule?.debugDescription ?? "")",
"* delay: \(delay)",
"* authorizationRequired: \(authorizationRequired)",
"* isBackgroundDeliveryEnabled: \(isBackgroundDeliveryEnabled)"
"* isBackgroundDeliveryEnabled: \(isBackgroundDeliveryEnabled)",
"",
"### carbEntryCache"
]

for entry in carbEntryCache {
report.append(entryReport(entry))
}

getRecentCarbEntries { (entries, error) in
report.append("")
report.append("### getRecentCarbEntries")
Expand All @@ -705,7 +691,7 @@ public final class CarbStore: HealthKitSampleStore {
} else {
report.append("")
for entry in entries {
report.append("* \(entry.startDate), \(entry.quantity), \(entry.absorptionTime ?? self.defaultAbsorptionTimes.medium), \(entry.createdByCurrentApp ? "" : "External")")
report.append(entryReport(entry))
}
}

Expand Down Expand Up @@ -735,7 +721,6 @@ public final class CarbStore: HealthKitSampleStore {
}

dataAccessQueue.async {

if self.modifiedCarbEntries.count > 0 {
self.syncDelegate?.carbStore(self, hasModifiedEntries: Array<StoredCarbEntry>(self.modifiedCarbEntries), withCompletion: { (uploadedEntries) in
if uploadedEntries.count == self.modifiedCarbEntries.count {
Expand Down
2 changes: 1 addition & 1 deletion CarbKit/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.1.0</string>
<string>1.2.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
Expand Down
40 changes: 37 additions & 3 deletions CarbKit/UI/CarbEntryEditViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import HealthKit


public final class CarbEntryEditViewController: UITableViewController, DatePickerTableViewCellDelegate, TextFieldTableViewCellDelegate {


static let SaveUnwindSegue = "CarbEntrySaveUnwind"

var navigationDelegate: CarbEntryNavigationDelegate = CarbEntryNavigationDelegate()

public var defaultAbsorptionTimes: CarbStore.DefaultAbsorptionTimes? {
didSet {
if originalCarbEntry == nil, let times = defaultAbsorptionTimes {
Expand Down Expand Up @@ -40,6 +44,8 @@ public final class CarbEntryEditViewController: UITableViewController, DatePicke
private var foodType: String?

private var absorptionTime: TimeInterval?

private let maxAbsorptionTime = TimeInterval(minutes: 999)

public var updatedCarbEntry: CarbEntry? {
if let quantity = quantity,
Expand All @@ -48,7 +54,7 @@ public final class CarbEntryEditViewController: UITableViewController, DatePicke
if let o = originalCarbEntry, o.quantity == quantity && o.startDate == date && o.foodType == foodType && o.absorptionTime == absorptionTime {
return nil // No changes were made
}

return NewCarbEntry(quantity: quantity, startDate: date, foodType: foodType, absorptionTime: absorptionTime, externalId: originalCarbEntry?.externalId)
} else {
return nil
Expand All @@ -74,10 +80,30 @@ public final class CarbEntryEditViewController: UITableViewController, DatePicke
@IBOutlet var segmentedControlInputAccessoryView: SegmentedControlInputAccessoryView!

@IBOutlet weak var saveButtonItem: UIBarButtonItem!

@IBAction func saveButtonPressed(_ sender: Any) {
//do validation for absorption time
let absorptionTimeIndex = IndexPath(row: Row.absorptionTime.rawValue, section: 0)
if let absorptionCell = tableView.cellForRow(at: absorptionTimeIndex) as? AbsorptionTimeTextFieldTableViewCell,
let absorptionNumber = absorptionCell.number {

let enteredAbsorptionTime = TimeInterval(minutes: absorptionNumber.doubleValue)

if validateAbsorptionTime(enteredAbsorptionTime) {
// perform unwind segue if it passes

// values will update during the unwind segue
navigationDelegate.performSegue(withIdentifier: CarbEntryEditViewController.SaveUnwindSegue, sender: self.saveButtonItem, for: self)
return
} else {
showAbsorptionTimeValidationWarning()
}
}
}

// MARK: - Table view data source

private enum Row: Int {
enum Row: Int {
case value
case date
case absorptionTime
Expand Down Expand Up @@ -184,4 +210,12 @@ public final class CarbEntryEditViewController: UITableViewController, DatePicke
break
}
}

func validateAbsorptionTime(_ absorptionTime: TimeInterval) -> Bool {
return absorptionTime <= maxAbsorptionTime
}

func showAbsorptionTimeValidationWarning() {
self.navigationDelegate.showAbsorptionTimeValidationWarning(for: self)
}
}
20 changes: 20 additions & 0 deletions CarbKit/UI/CarbEntryValidationNavigationDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// CarbEntryNavigationDelegate.swift
// LoopKit
//
// Created by Jaim Zuber on 2/7/17.
// Copyright © 2017 LoopKit Authors. All rights reserved.
//

import Foundation

class CarbEntryNavigationDelegate {

func performSegue(withIdentifier identifier: String, sender: Any?, for viewController: UIViewController) {
viewController.performSegue(withIdentifier: identifier, sender: sender)
}

func showAbsorptionTimeValidationWarning(for viewController: UIViewController) {
viewController.presentAlertController(withTitle: NSLocalizedString("Warning", comment:"Title of the warning displayed after entering a carb absorption time greater than the max"), message: NSLocalizedString("That's a long time for absorption. Try a number below 999", comment:"Warning message body displayed after entering a carb absorption time greater than the max"))
}
}
Loading

0 comments on commit 1b16cc9

Please sign in to comment.