From a6596389914c33d6b0fab19dc8e7f92aaed1f918 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 11 Dec 2016 14:04:43 -0600 Subject: [PATCH 1/5] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 182c0f3b27..e4abe59f8b 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,9 @@ Bluetooth LE communication with Minimed pumps is enabled by the [RileyLink](http [Sign up for the Loop Users announcement list](https://groups.google.com/forum/#!forum/loop-ios-users) to stay informed of critical issues that may arise. -[Please visit the Wiki for a "Guide to Loop" setup, installation, FAQs, and use.](https://github.com/LoopKit/Loop/wiki) +Please use the [Guide to Loop](https://github.com/LoopKit/Loop/wiki/Guide) for building, installation, and configuration instructions. + +For FAQs and other tips, refer to the [Wiki](https://github.com/LoopKit/Loop/wiki) (Note: there is also a tab for the Wiki at the top of this page) From 86380195076b11f1170b52ec8234ba528483fa63 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 24 Dec 2016 12:47:37 -0600 Subject: [PATCH 2/5] add more complication families and improve the display (adding trends) --- Loop.xcodeproj/project.pbxproj | 30 ++++---- .../Managers/StatusExtensionDataManager.swift | 1 + .../A307227B-6EFF-4242-A538-2C9AC617E041.json | 15 ---- .../Base.lproj/ckcomplication.strings | 8 +- .../complicationManifest.json | 6 +- .../Base.lproj/ckcomplication.strings | 8 +- .../ComplicationController.swift | 73 ++++++++++++------- .../Extensions/CLKComplicationTemplate.swift | 66 +++++++++++++++-- WatchApp Extension/Info.plist | 4 + 9 files changed, 141 insertions(+), 70 deletions(-) delete mode 100644 Loop/gallery.ckcomplication/A307227B-6EFF-4242-A538-2C9AC617E041.json diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 63a8d36dcc..71b1498b67 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -7,9 +7,12 @@ objects = { /* Begin PBXBuildFile section */ + 43027F0E1DFE0EC200C51989 /* NSNumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 436A0E7A1D7DE13400D6475D /* NSNumberFormatter.swift */; }; + 43027F0F1DFE0EC900C51989 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F526D5E1DF2459000A04910 /* HKUnit.swift */; }; 4302F4E11D4E9C8900F0FCAF /* TextFieldTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4302F4E01D4E9C8900F0FCAF /* TextFieldTableViewController.swift */; }; 4302F4E31D4EA54200F0FCAF /* InsulinDeliveryTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4302F4E21D4EA54200F0FCAF /* InsulinDeliveryTableViewController.swift */; }; 4302F4E51D4EA75100F0FCAF /* DoseStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4302F4E41D4EA75100F0FCAF /* DoseStore.swift */; }; + 43076BF31DFDBC4B0012A723 /* it.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 43076BF21DFDBC4B0012A723 /* it.lproj */; }; 430DA58E1D4AEC230097D1CA /* NSBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430DA58D1D4AEC230097D1CA /* NSBundle.swift */; }; 430DA5901D4B0E4C0097D1CA /* MySentryPumpStatusMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430DA58F1D4B0E4C0097D1CA /* MySentryPumpStatusMessageBody.swift */; }; 4313EDE01D8A6BF90060FA79 /* ChartContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4313EDDF1D8A6BF90060FA79 /* ChartContentView.swift */; }; @@ -184,7 +187,6 @@ C15713821DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */; }; C17884631D51A7A400405663 /* BatteryIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17884621D51A7A400405663 /* BatteryIndicator.swift */; }; C18C8C511D5A351900E043FB /* NightscoutDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */; }; - C1C73EF71DE3D0230022FC89 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1C73EF91DE3D0230022FC89 /* InfoPlist.strings */; }; C1C73F021DE3D0250022FC89 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1C73F041DE3D0250022FC89 /* Localizable.strings */; }; C1C73F081DE3D0260022FC89 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1C73F0A1DE3D0260022FC89 /* InfoPlist.strings */; }; C1C73F0D1DE3D0270022FC89 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1C73F0F1DE3D0270022FC89 /* InfoPlist.strings */; }; @@ -292,6 +294,7 @@ 4302F4E01D4E9C8900F0FCAF /* TextFieldTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldTableViewController.swift; sourceTree = ""; }; 4302F4E21D4EA54200F0FCAF /* InsulinDeliveryTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InsulinDeliveryTableViewController.swift; sourceTree = ""; }; 4302F4E41D4EA75100F0FCAF /* DoseStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoseStore.swift; sourceTree = ""; }; + 43076BF21DFDBC4B0012A723 /* it.lproj */ = {isa = PBXFileReference; lastKnownFileType = folder; path = it.lproj; sourceTree = ""; }; 430DA58D1D4AEC230097D1CA /* NSBundle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSBundle.swift; sourceTree = ""; }; 430DA58F1D4B0E4C0097D1CA /* MySentryPumpStatusMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MySentryPumpStatusMessageBody.swift; sourceTree = ""; }; 4313EDDF1D8A6BF90060FA79 /* ChartContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartContentView.swift; sourceTree = ""; }; @@ -453,7 +456,6 @@ C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MealBolusNightscoutTreatment.swift; sourceTree = ""; }; C17884621D51A7A400405663 /* BatteryIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryIndicator.swift; sourceTree = ""; }; C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutDataManager.swift; sourceTree = ""; }; - C1C73EF81DE3D0230022FC89 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; C1C73F031DE3D0250022FC89 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; C1C73F091DE3D0260022FC89 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; C1C73F0E1DE3D0270022FC89 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -600,16 +602,16 @@ 43776F8E1B8022E90074EA36 /* Loop */ = { isa = PBXGroup; children = ( - C1C73F0A1DE3D0260022FC89 /* InfoPlist.strings */, - C1C73F041DE3D0250022FC89 /* Localizable.strings */, 43846AD81D8FA84B00799272 /* gallery.ckcomplication */, 43EDEE6B1CF2E12A00393BE3 /* Loop.entitlements */, 43F5C2D41B92A4A6003EB13D /* Info.plist */, 43776F8F1B8022E90074EA36 /* AppDelegate.swift */, 43776F981B8022E90074EA36 /* Assets.xcassets */, - 43E344A01B9E144300C85C07 /* Extensions */, + C1C73F0A1DE3D0260022FC89 /* InfoPlist.strings */, + C1C73F041DE3D0250022FC89 /* Localizable.strings */, 43776F9A1B8022E90074EA36 /* LaunchScreen.storyboard */, 43776F951B8022E90074EA36 /* Main.storyboard */, + 43E344A01B9E144300C85C07 /* Extensions */, 43F5C2E41B93C5D4003EB13D /* Managers */, 43757D131C06F26C00910CB9 /* Models */, 43F5C2CE1B92A2A0003EB13D /* View Controllers */, @@ -645,9 +647,9 @@ 43A943821B926B7B0051FA24 /* WatchApp Extension */ = { isa = PBXGroup; children = ( - C1C73EF91DE3D0230022FC89 /* InfoPlist.strings */, 43D533BB1CFD1DD7009E3085 /* WatchApp Extension.entitlements */, 43846AD41D8FA67800799272 /* Base.lproj */, + 43076BF21DFDBC4B0012A723 /* it.lproj */, 43A943911B926B7B0051FA24 /* Info.plist */, 43A9438D1B926B7B0051FA24 /* ComplicationController.swift */, 43A943871B926B7B0051FA24 /* ExtensionDelegate.swift */, @@ -1090,7 +1092,7 @@ buildActionMask = 2147483647; files = ( 43846AD51D8FA67800799272 /* Base.lproj in Resources */, - C1C73EF71DE3D0230022FC89 /* InfoPlist.strings in Resources */, + 43076BF31DFDBC4B0012A723 /* it.lproj in Resources */, 43A943901B926B7B0051FA24 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1277,6 +1279,8 @@ 43846ADB1D91057000799272 /* ContextUpdatable.swift in Sources */, 43EA285F1D50ED3D001BC233 /* GlucoseTrend.swift in Sources */, 4328E0261CFBE2C500E199AA /* IdentifiableClass.swift in Sources */, + 43027F0E1DFE0EC200C51989 /* NSNumberFormatter.swift in Sources */, + 43027F0F1DFE0EC900C51989 /* HKUnit.swift in Sources */, 43CB2B2B1D924D450079823D /* WCSession.swift in Sources */, 43DE925A1C5479E4001FFDE1 /* CarbEntryUserInfo.swift in Sources */, 4328E0301CFBFAEB00E199AA /* NSTimeInterval.swift in Sources */, @@ -1389,14 +1393,6 @@ name = MainInterface.storyboard; sourceTree = ""; }; - C1C73EF91DE3D0230022FC89 /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - C1C73EF81DE3D0230022FC89 /* it */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; C1C73F041DE3D0250022FC89 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( @@ -1472,7 +1468,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 3.0.1; TARGETED_DEVICE_FAMILY = "1,2"; WARNING_CFLAGS = "-Wall"; }; @@ -1519,7 +1515,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 3.0.1; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; WARNING_CFLAGS = "-Wall"; diff --git a/Loop/Managers/StatusExtensionDataManager.swift b/Loop/Managers/StatusExtensionDataManager.swift index 3bd89be2d5..6eebba7976 100644 --- a/Loop/Managers/StatusExtensionDataManager.swift +++ b/Loop/Managers/StatusExtensionDataManager.swift @@ -109,6 +109,7 @@ extension StatusExtensionDataManager: CustomDebugStringConvertible { var debugDescription: String { return [ "## StatusExtensionDataManager", + "appGroupName: \(Bundle.main.appGroupSuiteName)", "statusExtensionContext: \(String(reflecting: defaults?.statusExtensionContext))" ].joined(separator: "\n") } diff --git a/Loop/gallery.ckcomplication/A307227B-6EFF-4242-A538-2C9AC617E041.json b/Loop/gallery.ckcomplication/A307227B-6EFF-4242-A538-2C9AC617E041.json deleted file mode 100644 index 58580ff1b3..0000000000 --- a/Loop/gallery.ckcomplication/A307227B-6EFF-4242-A538-2C9AC617E041.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "class" : "CLKComplicationTemplateModularSmallStackText", - "highlightLine2" : false, - "line2TextProvider" : { - "class" : "CLKLocalizableSimpleTextProvider", - "text" : "mg\/dL" - }, - "line1TextProvider" : { - "shortText" : "--", - "class" : "CLKSimpleTextProvider", - "text" : "--", - "accessibilityLabel" : "No glucose value available" - }, - "version" : 30000 -} \ No newline at end of file diff --git a/Loop/gallery.ckcomplication/Base.lproj/ckcomplication.strings b/Loop/gallery.ckcomplication/Base.lproj/ckcomplication.strings index 63987e6900..7f66681420 100644 --- a/Loop/gallery.ckcomplication/Base.lproj/ckcomplication.strings +++ b/Loop/gallery.ckcomplication/Base.lproj/ckcomplication.strings @@ -7,4 +7,10 @@ */ /* The complication template example unit string */ -"mg/dL" = "mg/dL" +"120↘︎" = "120↘︎"; + +/* Utilitarian small flat format string (1: Glucose & Trend symbol) (2: Time) */ +"UtilitarianSmallFlat" = "%1@ %2@"; + +/* Utilitarian small flat format string (1: Glucose & Trend symbol) (2: Eventual Glucose) (3: Time) */ +"UtilitarianLargeFlat" = "%1@%2@ %3@"; diff --git a/Loop/gallery.ckcomplication/complicationManifest.json b/Loop/gallery.ckcomplication/complicationManifest.json index f7de58b5c8..4f49690039 100644 --- a/Loop/gallery.ckcomplication/complicationManifest.json +++ b/Loop/gallery.ckcomplication/complicationManifest.json @@ -1,6 +1,10 @@ { "supported complication families" : { - "0" : "A307227B-6EFF-4242-A538-2C9AC617E041.json" + "3" : "CFB33AC2-7CD0-43A0-804F-F9A0EEC37480.json", + "6" : "E87B02EB-57B2-4E9D-B371-E0BA2DD55C19.json", + "4" : "B922BD0B-9601-45C9-B84D-ECB9C8235322.json", + "0" : "69B78E1F-415D-4E9E-8E56-E486517658FC.json", + "7" : "AB5AF86D-FF8F-493B-A738-4BB590C8A45E.json" }, "client ID" : "com.loudnate.Loop.watchkitapp.watchkitextension" } \ No newline at end of file diff --git a/WatchApp Extension/Base.lproj/ckcomplication.strings b/WatchApp Extension/Base.lproj/ckcomplication.strings index 63987e6900..7f66681420 100644 --- a/WatchApp Extension/Base.lproj/ckcomplication.strings +++ b/WatchApp Extension/Base.lproj/ckcomplication.strings @@ -7,4 +7,10 @@ */ /* The complication template example unit string */ -"mg/dL" = "mg/dL" +"120↘︎" = "120↘︎"; + +/* Utilitarian small flat format string (1: Glucose & Trend symbol) (2: Time) */ +"UtilitarianSmallFlat" = "%1@ %2@"; + +/* Utilitarian small flat format string (1: Glucose & Trend symbol) (2: Eventual Glucose) (3: Time) */ +"UtilitarianLargeFlat" = "%1@%2@ %3@"; diff --git a/WatchApp Extension/ComplicationController.swift b/WatchApp Extension/ComplicationController.swift index 63884fbbf0..559e620ae4 100644 --- a/WatchApp Extension/ComplicationController.swift +++ b/WatchApp Extension/ComplicationController.swift @@ -43,21 +43,14 @@ final class ComplicationController: NSObject, CLKComplicationDataSource { private lazy var formatter = NumberFormatter() func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: (@escaping (CLKComplicationTimelineEntry?) -> Void)) { - - switch complication.family { - case .modularSmall: - if let context = ExtensionDelegate.shared().lastContext, - let glucose = context.glucose, - let unit = context.preferredGlucoseUnit, - let glucoseString = formatter.string(from: NSNumber(value: glucose.doubleValue(for: unit))), - let date = context.glucoseDate, date.timeIntervalSinceNow.minutes >= -15, - let template = CLKComplicationTemplateModularSmallStackText(line1: glucoseString, date: date) - { - handler(CLKComplicationTimelineEntry(date: date, complicationTemplate: template)) - } else { - handler(nil) - } - default: + if let context = ExtensionDelegate.shared().lastContext, + let glucoseDate = context.glucoseDate, + glucoseDate.timeIntervalSinceNow.minutes >= -15, + let template = CLKComplicationTemplate.templateForFamily(complication.family, from: context) + { + template.tintColor = UIColor.tintColor + handler(CLKComplicationTimelineEntry(date: glucoseDate, complicationTemplate: template)) + } else { handler(nil) } } @@ -69,13 +62,12 @@ final class ComplicationController: NSObject, CLKComplicationDataSource { func getTimelineEntries(for complication: CLKComplication, after date: Date, limit: Int, withHandler handler: (@escaping ([CLKComplicationTimelineEntry]?) -> Void)) { // Call the handler with the timeline entries after to the given date - if let context = ExtensionDelegate.shared().lastContext, - let glucose = context.glucose, - let unit = context.preferredGlucoseUnit, - let glucoseString = formatter.string(from: NSNumber(value: glucose.doubleValue(for: unit))), - let glucoseDate = context.glucoseDate, glucoseDate.timeIntervalSince(date) > 0, - let template = CLKComplicationTemplateModularSmallStackText(line1: glucoseString, date: glucoseDate) + if let context = ExtensionDelegate.shared().lastContext, + let glucoseDate = context.glucoseDate, + glucoseDate.timeIntervalSince(date) > 0, + let template = CLKComplicationTemplate.templateForFamily(complication.family, from: context) { + template.tintColor = UIColor.tintColor handler([CLKComplicationTimelineEntry(date: glucoseDate, complicationTemplate: template)]) } else { handler(nil) @@ -85,16 +77,41 @@ final class ComplicationController: NSObject, CLKComplicationDataSource { // MARK: - Placeholder Templates func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) { - switch complication.family { - case .modularSmall: - let template = CLKComplicationTemplateModularSmallStackText() - template.line1TextProvider = CLKSimpleTextProvider(text: "--", shortText: "--", accessibilityLabel: "No glucose value available") - template.line2TextProvider = CLKSimpleTextProvider.localizableTextProvider(withStringsFileTextKey: "mg/dL") + let template: CLKComplicationTemplate? + + let glucoseText = CLKSimpleTextProvider.localizableTextProvider(withStringsFileTextKey: "120↘︎", shortTextKey: "120") + let timeText = CLKTimeTextProvider(date: Date()) - handler(template) + switch complication.family { + case .modularSmall: + let modularSmall = CLKComplicationTemplateModularSmallStackText() + modularSmall.line1TextProvider = glucoseText + modularSmall.line2TextProvider = timeText + template = modularSmall + case .circularSmall: + let circularSmall = CLKComplicationTemplateCircularSmallSimpleText() + circularSmall.textProvider = glucoseText + template = circularSmall + case .extraLarge: + let extraLarge = CLKComplicationTemplateExtraLargeStackText() + extraLarge.line1TextProvider = glucoseText + extraLarge.line2TextProvider = timeText + template = extraLarge + case .utilitarianSmallFlat: + let utilitarianSmallFlat = CLKComplicationTemplateUtilitarianSmallFlat() + utilitarianSmallFlat.textProvider = CLKSimpleTextProvider.localizableTextProvider(withStringsFileFormatKey: "UtilitarianSmallFlat", textProviders: [glucoseText, timeText]) + template = utilitarianSmallFlat + case .utilitarianLarge: + let utilitarianLarge = CLKComplicationTemplateUtilitarianLargeFlat() + let eventualGlucoseText = CLKSimpleTextProvider.localizableTextProvider(withStringsFileTextKey: "75") + utilitarianLarge.textProvider = CLKSimpleTextProvider.localizableTextProvider(withStringsFileFormatKey: "UtilitarianLargeFlat", textProviders: [glucoseText, eventualGlucoseText, timeText]) + template = utilitarianLarge default: - handler(nil) + template = nil } + + template?.tintColor = UIColor.tintColor + handler(template) } } diff --git a/WatchApp Extension/Extensions/CLKComplicationTemplate.swift b/WatchApp Extension/Extensions/CLKComplicationTemplate.swift index cd30289801..a55543b0e9 100644 --- a/WatchApp Extension/Extensions/CLKComplicationTemplate.swift +++ b/WatchApp Extension/Extensions/CLKComplicationTemplate.swift @@ -10,17 +10,69 @@ import ClockKit import Foundation -extension CLKComplicationTemplateModularSmallStackText { +extension CLKComplicationTemplate { - convenience init?(line1: String?, date: Date?) { - guard let line1 = line1, let date = date else { + static func templateForFamily(_ family: CLKComplicationFamily, from context: WatchContext) -> CLKComplicationTemplate? { + + guard let glucose = context.glucose, + let unit = context.preferredGlucoseUnit + else { return nil } - self.init() + let formatter = NumberFormatter.glucoseFormatter(for: unit) - line1TextProvider = CLKSimpleTextProvider(text: line1) - line2TextProvider = CLKTimeTextProvider(date: date) - } + guard let glucoseString = formatter.string(from: NSNumber(value: glucose.doubleValue(for: unit))), + let date = context.glucoseDate else + { + return nil + } + + var glucoseStrings = [glucoseString] + var accessibilityStrings = [glucoseString] + var eventualGlucoseText: CLKSimpleTextProvider? + + if let trend = context.glucoseTrend { + glucoseStrings.append(trend.symbol) + accessibilityStrings.append(trend.localizedDescription) + } + if let eventualGlucose = context.eventualGlucose, + let eventualGlucoseString = formatter.string(from: NSNumber(value: eventualGlucose.doubleValue(for: unit))) + { + eventualGlucoseText = CLKSimpleTextProvider(text: eventualGlucoseString) + } + + let glucoseText = CLKSimpleTextProvider(text: glucoseStrings.joined(), shortText: glucoseString, accessibilityLabel: accessibilityStrings.joined(separator: ", ")) + let timeText = CLKTimeTextProvider(date: date) + + switch family { + case .modularSmall: + let template = CLKComplicationTemplateModularSmallStackText() + template.line1TextProvider = glucoseText + template.line2TextProvider = timeText + return template + case .circularSmall: + let template = CLKComplicationTemplateCircularSmallSimpleText() + template.textProvider = glucoseText + return template + case .extraLarge: + let template = CLKComplicationTemplateExtraLargeStackText() + template.line1TextProvider = glucoseText + template.line2TextProvider = timeText + return template + case .utilitarianSmallFlat: + let template = CLKComplicationTemplateUtilitarianSmallFlat() + template.textProvider = CLKSimpleTextProvider.localizableTextProvider(withStringsFileFormatKey: "UtilitarianSmallFlat", textProviders: [glucoseText, timeText]) + return template + case .utilitarianLarge: + let template = CLKComplicationTemplateUtilitarianLargeFlat() + let providers: [CLKTextProvider?] = [glucoseText, eventualGlucoseText, timeText] + + template.textProvider = CLKSimpleTextProvider.localizableTextProvider(withStringsFileFormatKey: "UtilitarianLargeFlat", textProviders: providers.flatMap({ $0 })) + return template + default: + return nil + } + } } diff --git a/WatchApp Extension/Info.plist b/WatchApp Extension/Info.plist index 50f23199d8..06d294cf12 100644 --- a/WatchApp Extension/Info.plist +++ b/WatchApp Extension/Info.plist @@ -27,6 +27,10 @@ CLKComplicationSupportedFamilies CLKComplicationFamilyModularSmall + CLKComplicationFamilyCircularSmall + CLKComplicationFamilyExtraLarge + CLKComplicationFamilyUtilitarianSmallFlat + CLKComplicationFamilyUtilitarianLarge NSExtension From aac86be56bc5577d0559ea15e9b03e1a24f4f32b Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Sat, 11 Feb 2017 14:04:41 -0800 Subject: [PATCH 3/5] Only allow one active glucose data source (#364) * Only allow one active glucose data source Separate CGM sources out into their own section and enforce zero or one active sources at any given time. This requires a new boolean value to track whether the G5 transmitter is enabled independently of the actual transmitter ID. While we're at it, create a separate config section for the pump to group it with the pump battery type since those are not really configuration values and go together. * Improve animation mode for showing G5 Transmitter ID The top animation is a clearer indication that this is an extension of the G5 Transmitter enabled switch. * Be more specific with the key name * Use 'CGM' instead of 'Cgm' * Mark target-action methods with @objc to make sure that they're using dynamic dispatch. Also make the internal methods private for good measure. --- Loop/Extensions/NSUserDefaults.swift | 17 ++ Loop/Managers/DeviceDataManager.swift | 15 +- .../SettingsTableViewController.swift | 262 +++++++++++++----- 3 files changed, 226 insertions(+), 68 deletions(-) diff --git a/Loop/Extensions/NSUserDefaults.swift b/Loop/Extensions/NSUserDefaults.swift index cc13e51eb5..e05f4ffc51 100644 --- a/Loop/Extensions/NSUserDefaults.swift +++ b/Loop/Extensions/NSUserDefaults.swift @@ -20,6 +20,7 @@ extension UserDefaults { case InsulinActionDuration = "com.loudnate.Naterade.InsulinActionDuration" case InsulinSensitivitySchedule = "com.loudnate.Naterade.InsulinSensitivitySchedule" case G4ReceiverEnabled = "com.loudnate.Loop.G4ReceiverEnabled" + case G5TransmitterEnabled = "com.loopkit.Loop.G5TransmitterEnabled" case G5TransmitterID = "com.loudnate.Naterade.TransmitterID" case GlucoseTargetRangeSchedule = "com.loudnate.Naterade.GlucoseTargetRangeSchedule" case MaximumBasalRatePerHour = "com.loudnate.Naterade.MaximumBasalRatePerHour" @@ -223,6 +224,22 @@ extension UserDefaults { } } + var transmitterEnabled: Bool { + get { + if object(forKey: Key.G5TransmitterEnabled.rawValue) == nil { + // Old versions of Loop used the existence of transmitterID to indicate + // that the transmitter is enabled. Upgrade to the new format now. The + // transmitter is enabled if there's a 6 character transmitter ID + set(transmitterID?.characters.count == 6, forKey: Key.G5TransmitterEnabled.rawValue) + } + + return bool(forKey: Key.G5TransmitterEnabled.rawValue) + } + set { + set(newValue, forKey: Key.G5TransmitterEnabled.rawValue) + } + } + var transmitterID: String? { get { return string(forKey: Key.G5TransmitterID.rawValue) diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index 4dad875a39..d2d00d1114 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -54,6 +54,15 @@ final class DeviceDataManager: CarbStoreDelegate, CarbStoreSyncDelegate, DoseSto } } + var transmitterEnabled: Bool { + get { + return UserDefaults.standard.transmitterEnabled + } + set { + return UserDefaults.standard.transmitterEnabled = newValue + } + } + var sensorInfo: SensorDisplayable? { return latestGlucoseG5 ?? latestGlucoseG4 ?? latestGlucoseFromShare ?? latestPumpStatusFromMySentry } @@ -1050,7 +1059,10 @@ final class DeviceDataManager: CarbStoreDelegate, CarbStoreSyncDelegate, DoseSto receiver?.delegate = self } - if let transmitterID = UserDefaults.standard.transmitterID, transmitterID.characters.count == 6 { + if UserDefaults.standard.transmitterEnabled, + let transmitterID = UserDefaults.standard.transmitterID, + transmitterID.characters.count == 6 { + transmitter = Transmitter(ID: transmitterID, passiveModeEnabled: true) transmitter?.delegate = self } @@ -1071,6 +1083,7 @@ extension DeviceDataManager: CustomDebugStringConvertible { "latestGlucoseG4: \(latestGlucoseG4)", "pumpState: \(String(reflecting: pumpState))", "preferredInsulinDataSource: \(preferredInsulinDataSource)", + "transmitterEnabled: \(transmitterEnabled)", "transmitterID: \(transmitterID)", "glucoseTargetRangeSchedule: \(glucoseTargetRangeSchedule?.debugDescription ?? "")", "workoutModeEnabled: \(workoutModeEnabled)", diff --git a/Loop/View Controllers/SettingsTableViewController.swift b/Loop/View Controllers/SettingsTableViewController.swift index 5250817f37..8a3b094795 100644 --- a/Loop/View Controllers/SettingsTableViewController.swift +++ b/Loop/View Controllers/SettingsTableViewController.swift @@ -47,7 +47,7 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu dataManager.rileyLinkManager.deviceScanningEnabled = true - if dataManager.transmitterID != nil || dataManager.receiverEnabled, let glucoseStore = dataManager.glucoseStore, glucoseStore.authorizationRequired { + if dataManager.transmitterEnabled || dataManager.receiverEnabled, let glucoseStore = dataManager.glucoseStore, glucoseStore.authorizationRequired { glucoseStore.authorize({ (success, error) -> Void in // Do nothing for now }) @@ -79,10 +79,12 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu fileprivate enum Section: Int { case loop = 0 case devices + case pump + case cgm case configuration case services - static let count = 4 + static let count = 6 } fileprivate enum LoopRow: Int { @@ -93,20 +95,31 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu static let count = 3 } - fileprivate enum ConfigurationRow: Int { + fileprivate enum PumpRow: Int { case pumpID = 0 - case transmitterID - case receiverEnabled - case glucoseTargetRange + case batteryChemistry + + static let count = 2 + } + + fileprivate enum CGMRow: Int { + case receiverEnabled = 0 + case transmitterEnabled + case transmitterID // optional, only displayed if transmitterEnabled + + static let count = 3 + } + + fileprivate enum ConfigurationRow: Int { + case glucoseTargetRange = 0 case insulinActionDuration case basalRate case carbRatio case insulinSensitivity case maxBasal case maxBolus - case batteryChemistry - static let count = 11 + static let count = 7 } fileprivate enum ServiceRow: Int { @@ -138,6 +151,14 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu switch Section(rawValue: section)! { case .loop: return LoopRow.count + case .pump: + return PumpRow.count + case .cgm: + if dataManager.transmitterEnabled { + return CGMRow.count + } else { + return CGMRow.count - 1 + } case .configuration: return ConfigurationRow.count case .devices: @@ -177,8 +198,19 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu return cell } - case .configuration: - if case .receiverEnabled = ConfigurationRow(rawValue: indexPath.row)! { + case .pump: + let configCell = tableView.dequeueReusableCell(withIdentifier: ConfigCellIdentifier, for: indexPath) + switch PumpRow(rawValue: indexPath.row)! { + case .pumpID: + configCell.textLabel?.text = NSLocalizedString("Pump ID", comment: "The title text for the pump ID config value") + configCell.detailTextLabel?.text = dataManager.pumpID ?? TapToSetString + case .batteryChemistry: + configCell.textLabel?.text = NSLocalizedString("Pump Battery Type", comment: "The title text for the battery type value") + configCell.detailTextLabel?.text = String(describing: dataManager.batteryChemistry) + } + cell = configCell + case .cgm: + if case .receiverEnabled = CGMRow(rawValue: indexPath.row)! { let switchCell = tableView.dequeueReusableCell(withIdentifier: SwitchTableViewCell.className, for: indexPath) as! SwitchTableViewCell switchCell.`switch`?.isOn = dataManager.receiverEnabled @@ -189,17 +221,33 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu return switchCell } - let configCell = tableView.dequeueReusableCell(withIdentifier: ConfigCellIdentifier, for: indexPath) + if case .transmitterEnabled = CGMRow(rawValue: indexPath.row)! { + let switchCell = tableView.dequeueReusableCell(withIdentifier: SwitchTableViewCell.className, for: indexPath) as! SwitchTableViewCell - switch ConfigurationRow(rawValue: indexPath.row)! { - case .pumpID: - configCell.textLabel?.text = NSLocalizedString("Pump ID", comment: "The title text for the pump ID config value") - configCell.detailTextLabel?.text = dataManager.pumpID ?? TapToSetString + switchCell.`switch`?.isOn = dataManager.transmitterEnabled + switchCell.titleLabel.text = NSLocalizedString("G5 Transmitter", comment: "The title text for the G5 Transmitter enabled switch cell") + + switchCell.`switch`?.addTarget(self, action: #selector(transmitterEnabledChanged(_:)), for: .valueChanged) + + return switchCell + + } + + let configCell = tableView.dequeueReusableCell(withIdentifier: ConfigCellIdentifier, for: indexPath) + switch CGMRow(rawValue: indexPath.row)! { + case .transmitterEnabled: + break case .transmitterID: configCell.textLabel?.text = NSLocalizedString("G5 Transmitter ID", comment: "The title text for the Dexcom G5 transmitter ID config value") configCell.detailTextLabel?.text = dataManager.transmitterID ?? TapToSetString case .receiverEnabled: break + } + cell = configCell + case .configuration: + let configCell = tableView.dequeueReusableCell(withIdentifier: ConfigCellIdentifier, for: indexPath) + + switch ConfigurationRow(rawValue: indexPath.row)! { case .basalRate: configCell.textLabel?.text = NSLocalizedString("Basal Rates", comment: "The title text for the basal rate schedule") @@ -274,14 +322,6 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu } else { configCell.detailTextLabel?.text = TapToSetString } - case .batteryChemistry: - configCell.textLabel?.text = NSLocalizedString("Pump Battery Type", comment: "The title text for the battery type value") - configCell.detailTextLabel?.text = String(describing: dataManager.batteryChemistry) -// if let sentrySupported = dataManager.pumpState?.pumpModel?.hasMySentry, sentrySupported { -// configCell.textLabel?.isEnabled = false -// configCell.detailTextLabel?.isEnabled = false -// configCell.isUserInteractionEnabled = false -// } } cell = configCell @@ -333,6 +373,10 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu case .loop: let bundle = Bundle.main return bundle.localizedNameAndVersion + case .pump: + return NSLocalizedString("Pump", comment: "The title of the pump section in settings") + case .cgm: + return NSLocalizedString("Continuous Glucose Monitor", comment: "The title of the continuous glucose monitor section in settings") case .configuration: return NSLocalizedString("Configuration", comment: "The title of the configuration section in settings") case .devices: @@ -348,17 +392,59 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu let sender = tableView.cellForRow(at: indexPath) switch Section(rawValue: indexPath.section)! { - case .configuration: - let row = ConfigurationRow(rawValue: indexPath.row)! + case .pump: + let row = PumpRow(rawValue: indexPath.row)! switch row { - case .pumpID, .transmitterID, .insulinActionDuration, .maxBasal, .maxBolus: + case .pumpID: let vc: TextFieldTableViewController - switch row { case .pumpID: vc = PumpIDTableViewController(pumpID: dataManager.pumpID, region: dataManager.pumpState?.pumpRegion) + default: + fatalError() + } + vc.title = sender?.textLabel?.text + vc.indexPath = indexPath + vc.delegate = self + + show(vc, sender: indexPath) + case .batteryChemistry: + let vc = RadioSelectionTableViewController.batteryChemistryType(dataManager.batteryChemistry) + vc.title = sender?.textLabel?.text + vc.delegate = self + + show(vc, sender: sender) + } + case .cgm: + let row = CGMRow(rawValue: indexPath.row)! + switch row { + case .transmitterEnabled: + break + case .transmitterID: + let vc: TextFieldTableViewController + + switch row { case .transmitterID: vc = .transmitterID(dataManager.transmitterID) + default: + fatalError() + } + + vc.title = sender?.textLabel?.text + vc.indexPath = indexPath + vc.delegate = self + + show(vc, sender: indexPath) + case .receiverEnabled: + break + } + case .configuration: + let row = ConfigurationRow(rawValue: indexPath.row)! + switch row { + case .insulinActionDuration, .maxBasal, .maxBolus: + let vc: TextFieldTableViewController + + switch row { case .insulinActionDuration: vc = .insulinActionDuration(dataManager.insulinActionDuration) case .maxBasal: @@ -464,14 +550,6 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu } else { show(scheduleVC, sender: sender) } - case .receiverEnabled: - break - case .batteryChemistry: - let vc = RadioSelectionTableViewController.batteryChemistryType(dataManager.batteryChemistry) - vc.title = sender?.textLabel?.text - vc.delegate = self - - show(vc, sender: sender) } case .devices: let vc = RileyLinkDeviceTableViewController() @@ -544,18 +622,18 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu switch Section(rawValue: section)! { case .devices: return devicesSectionTitleView - case .loop, .configuration, .services: + case .loop, .pump, .cgm, .configuration, .services: return nil } } // MARK: - Device mangement - func dosingEnabledChanged(_ sender: UISwitch) { + @objc private func dosingEnabledChanged(_ sender: UISwitch) { dataManager.loopManager.dosingEnabled = sender.isOn } - func deviceConnectionChanged(_ connectSwitch: UISwitch) { + @objc private func deviceConnectionChanged(_ connectSwitch: UISwitch) { let switchOrigin = connectSwitch.convert(CGPoint.zero, to: tableView) if let indexPath = tableView.indexPathForRow(at: switchOrigin), indexPath.section == Section.devices.rawValue @@ -570,8 +648,43 @@ final class SettingsTableViewController: UITableViewController, DailyValueSchedu } } - func receiverEnabledChanged(_ sender: UISwitch) { + @objc private func transmitterEnabledChanged(_ sender: UISwitch) { + if sender.isOn { + enableTransmitter() + } else { + disableTransmitter() + } + } + + private func enableTransmitter() { + if dataManager.transmitterEnabled == false { + dataManager.transmitterEnabled = true + disableReceiver() + tableView.insertRows(at: [IndexPath(row: CGMRow.transmitterID.rawValue, section:Section.cgm.rawValue)], with: .top) + } + } + + private func disableTransmitter() { + if dataManager.transmitterEnabled { + dataManager.transmitterEnabled = false + let switchCell = tableView.cellForRow(at: IndexPath(row: CGMRow.transmitterEnabled.rawValue, section: Section.cgm.rawValue)) as! SwitchTableViewCell + switchCell.`switch`?.setOn(false, animated: true) + tableView.deleteRows(at: [IndexPath(row: CGMRow.transmitterID.rawValue, section:Section.cgm.rawValue)], with: .top) + } + } + + @objc private func receiverEnabledChanged(_ sender: UISwitch) { dataManager.receiverEnabled = sender.isOn + + if sender.isOn { + disableTransmitter() + } + } + + private func disableReceiver() { + dataManager.receiverEnabled = false + let switchCell = tableView.cellForRow(at: IndexPath(row: CGMRow.receiverEnabled.rawValue, section: Section.cgm.rawValue)) as! SwitchTableViewCell + switchCell.`switch`?.setOn(false, animated: true) } // MARK: - DailyValueScheduleTableViewControllerDelegate @@ -627,13 +740,13 @@ extension SettingsTableViewController: RadioSelectionTableViewControllerDelegate assertionFailure() } - case .configuration: - switch ConfigurationRow(rawValue: indexPath.row)! { + case .pump: + switch PumpRow(rawValue: indexPath.row)! { case .batteryChemistry: if let selectedIndex = controller.selectedIndex, let dataSource = BatteryChemistryType(rawValue: selectedIndex) { dataManager.batteryChemistry = dataSource - tableView.reloadRows(at: [IndexPath(row: ConfigurationRow.batteryChemistry.rawValue, section: Section.configuration.rawValue)], with: .none) + tableView.reloadRows(at: [IndexPath(row: PumpRow.batteryChemistry.rawValue, section: Section.configuration.rawValue)], with: .none) } default: assertionFailure() @@ -648,34 +761,49 @@ extension SettingsTableViewController: RadioSelectionTableViewControllerDelegate extension SettingsTableViewController: TextFieldTableViewControllerDelegate { func textFieldTableViewControllerDidEndEditing(_ controller: TextFieldTableViewController) { if let indexPath = controller.indexPath { - switch ConfigurationRow(rawValue: indexPath.row)! { - case .pumpID: - dataManager.pumpID = controller.value + switch Section(rawValue: indexPath.section)! { + case .pump: + switch PumpRow(rawValue: indexPath.row)! { + case .pumpID: + dataManager.pumpID = controller.value - if let controller = controller as? PumpIDTableViewController, - let region = controller.region - { - dataManager.pumpState?.pumpRegion = region - } - case .transmitterID: - dataManager.transmitterID = controller.value - case .insulinActionDuration: - if let value = controller.value, let duration = valueNumberFormatter.number(from: value)?.doubleValue { - dataManager.insulinActionDuration = TimeInterval(hours: duration) - } else { - dataManager.insulinActionDuration = nil + if let controller = controller as? PumpIDTableViewController, + let region = controller.region + { + dataManager.pumpState?.pumpRegion = region + } + default: + assertionFailure() } - case .maxBasal: - if let value = controller.value, let rate = valueNumberFormatter.number(from: value)?.doubleValue { - dataManager.maximumBasalRatePerHour = rate - } else { - dataManager.maximumBasalRatePerHour = nil + case .cgm: + switch CGMRow(rawValue: indexPath.row)! { + case .transmitterID: + dataManager.transmitterID = controller.value + default: + assertionFailure() } - case .maxBolus: - if let value = controller.value, let units = valueNumberFormatter.number(from: value)?.doubleValue { - dataManager.maximumBolus = units - } else { - dataManager.maximumBolus = nil + case .configuration: + switch ConfigurationRow(rawValue: indexPath.row)! { + case .insulinActionDuration: + if let value = controller.value, let duration = valueNumberFormatter.number(from: value)?.doubleValue { + dataManager.insulinActionDuration = TimeInterval(hours: duration) + } else { + dataManager.insulinActionDuration = nil + } + case .maxBasal: + if let value = controller.value, let rate = valueNumberFormatter.number(from: value)?.doubleValue { + dataManager.maximumBasalRatePerHour = rate + } else { + dataManager.maximumBasalRatePerHour = nil + } + case .maxBolus: + if let value = controller.value, let units = valueNumberFormatter.number(from: value)?.doubleValue { + dataManager.maximumBolus = units + } else { + dataManager.maximumBolus = nil + } + default: + assertionFailure() } default: assertionFailure() From f1e434278ee011fee1684d3493c0c8d3d078aeb3 Mon Sep 17 00:00:00 2001 From: Bharat Mediratta Date: Sun, 12 Feb 2017 19:17:43 -0800 Subject: [PATCH 4/5] Upgrade project to 0820 (#376) Signing frameworks at build time is not recommended so disable code signing for LoopUI. All changes performed automatically by XCode --- Loop.xcodeproj/project.pbxproj | 6 +++--- .../xcshareddata/xcschemes/Complication - WatchApp.xcscheme | 2 +- .../xcshareddata/xcschemes/DoseMathTests.xcscheme | 2 +- .../xcshareddata/xcschemes/Loop Status Extension.xcscheme | 2 +- Loop.xcodeproj/xcshareddata/xcschemes/Loop.xcscheme | 2 +- Loop.xcodeproj/xcshareddata/xcschemes/LoopTests.xcscheme | 2 +- .../xcshareddata/xcschemes/Notification - WatchApp.xcscheme | 2 +- Loop.xcodeproj/xcshareddata/xcschemes/WatchApp.xcscheme | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 96a426f520..aa2332a7e4 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -1085,7 +1085,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0810; + LastUpgradeCheck = 0820; ORGANIZATIONNAME = "LoopKit Authors"; TargetAttributes = { 43776F8B1B8022E90074EA36 = { @@ -1899,7 +1899,7 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1926,7 +1926,7 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; diff --git a/Loop.xcodeproj/xcshareddata/xcschemes/Complication - WatchApp.xcscheme b/Loop.xcodeproj/xcshareddata/xcschemes/Complication - WatchApp.xcscheme index bf7d22a5ca..e9d45e8f1f 100644 --- a/Loop.xcodeproj/xcshareddata/xcschemes/Complication - WatchApp.xcscheme +++ b/Loop.xcodeproj/xcshareddata/xcschemes/Complication - WatchApp.xcscheme @@ -1,6 +1,6 @@ Date: Wed, 15 Feb 2017 20:07:23 -0500 Subject: [PATCH 5/5] Merge complications changes back into dev (they were accidentally merged to master) --- Loop.xcodeproj/project.pbxproj | 49 ++++++------- .../Managers/StatusExtensionDataManager.swift | 1 + .../A307227B-6EFF-4242-A538-2C9AC617E041.json | 15 ---- .../Base.lproj/ckcomplication.strings | 8 +- .../complicationManifest.json | 6 +- README.md | 4 +- .../Base.lproj/ckcomplication.strings | 8 +- .../ComplicationController.swift | 73 ++++++++++++------- .../Extensions/CLKComplicationTemplate.swift | 66 +++++++++++++++-- WatchApp Extension/Info.plist | 4 + 10 files changed, 153 insertions(+), 81 deletions(-) delete mode 100644 Loop/gallery.ckcomplication/A307227B-6EFF-4242-A538-2C9AC617E041.json diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index aa2332a7e4..69b4ea1a3a 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -7,9 +7,12 @@ objects = { /* Begin PBXBuildFile section */ + 43027F0E1DFE0EC200C51989 /* NSNumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 436A0E7A1D7DE13400D6475D /* NSNumberFormatter.swift */; }; + 43027F0F1DFE0EC900C51989 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F526D5E1DF2459000A04910 /* HKUnit.swift */; }; 4302F4E11D4E9C8900F0FCAF /* TextFieldTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4302F4E01D4E9C8900F0FCAF /* TextFieldTableViewController.swift */; }; 4302F4E31D4EA54200F0FCAF /* InsulinDeliveryTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4302F4E21D4EA54200F0FCAF /* InsulinDeliveryTableViewController.swift */; }; 4302F4E51D4EA75100F0FCAF /* DoseStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4302F4E41D4EA75100F0FCAF /* DoseStore.swift */; }; + 43076BF31DFDBC4B0012A723 /* it.lproj in Resources */ = {isa = PBXBuildFile; fileRef = 43076BF21DFDBC4B0012A723 /* it.lproj */; }; 430DA58E1D4AEC230097D1CA /* NSBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430DA58D1D4AEC230097D1CA /* NSBundle.swift */; }; 430DA5901D4B0E4C0097D1CA /* MySentryPumpStatusMessageBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430DA58F1D4B0E4C0097D1CA /* MySentryPumpStatusMessageBody.swift */; }; 4313EDE01D8A6BF90060FA79 /* ChartContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4313EDDF1D8A6BF90060FA79 /* ChartContentView.swift */; }; @@ -182,7 +185,6 @@ C15713821DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */; }; C17884631D51A7A400405663 /* BatteryIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17884621D51A7A400405663 /* BatteryIndicator.swift */; }; C18C8C511D5A351900E043FB /* NightscoutDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */; }; - C1C73EF71DE3D0230022FC89 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1C73EF91DE3D0230022FC89 /* InfoPlist.strings */; }; C1C73F021DE3D0250022FC89 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1C73F041DE3D0250022FC89 /* Localizable.strings */; }; C1C73F081DE3D0260022FC89 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1C73F0A1DE3D0260022FC89 /* InfoPlist.strings */; }; C1C73F0D1DE3D0270022FC89 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1C73F0F1DE3D0270022FC89 /* InfoPlist.strings */; }; @@ -305,7 +307,8 @@ 4302F4E01D4E9C8900F0FCAF /* TextFieldTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldTableViewController.swift; sourceTree = ""; }; 4302F4E21D4EA54200F0FCAF /* InsulinDeliveryTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InsulinDeliveryTableViewController.swift; sourceTree = ""; }; 4302F4E41D4EA75100F0FCAF /* DoseStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoseStore.swift; sourceTree = ""; }; - 430DA58D1D4AEC230097D1CA /* NSBundle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NSBundle.swift; path = Common/Extensions/NSBundle.swift; sourceTree = ""; }; + 43076BF21DFDBC4B0012A723 /* it.lproj */ = {isa = PBXFileReference; lastKnownFileType = folder; path = it.lproj; sourceTree = ""; }; + 430DA58D1D4AEC230097D1CA /* NSBundle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSBundle.swift; sourceTree = ""; }; 430DA58F1D4B0E4C0097D1CA /* MySentryPumpStatusMessageBody.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MySentryPumpStatusMessageBody.swift; sourceTree = ""; }; 4313EDDF1D8A6BF90060FA79 /* ChartContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartContentView.swift; sourceTree = ""; }; 4315D2861CA5CC3B00589052 /* CarbEntryEditTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CarbEntryEditTableViewController.swift; sourceTree = ""; }; @@ -331,14 +334,14 @@ 4346D1EF1C781BEA00ABAFE3 /* SwiftCharts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftCharts.framework; path = Carthage/Build/iOS/SwiftCharts.framework; sourceTree = ""; }; 4346D1F51C78501000ABAFE3 /* ChartPoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartPoint.swift; sourceTree = ""; }; 434AB0B11CBB4C3300422F4A /* RileyLinkBLEKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RileyLinkBLEKit.framework; path = Carthage/Build/iOS/RileyLinkBLEKit.framework; sourceTree = ""; }; - 434F54561D287FDB002A9274 /* NibLoadable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NibLoadable.swift; path = Common/Extensions/NibLoadable.swift; sourceTree = ""; }; + 434F54561D287FDB002A9274 /* NibLoadable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NibLoadable.swift; sourceTree = ""; }; 434F54581D28805E002A9274 /* ButtonTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ButtonTableViewCell.xib; sourceTree = ""; }; 434F545A1D2880D4002A9274 /* AuthenticationTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AuthenticationTableViewCell.xib; sourceTree = ""; }; 434F545E1D288345002A9274 /* ShareService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShareService.swift; sourceTree = ""; }; 434F54601D28859B002A9274 /* ServiceCredential.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceCredential.swift; sourceTree = ""; }; 434F54621D28DD80002A9274 /* ValidatingIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidatingIndicatorView.swift; sourceTree = ""; }; 434FB6451D68F1CD007B9C70 /* Amplitude.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Amplitude.framework; path = Carthage/Build/iOS/Amplitude.framework; sourceTree = ""; }; - 434FF1E91CF26C29000DB779 /* IdentifiableClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = IdentifiableClass.swift; path = Common/Extensions/IdentifiableClass.swift; sourceTree = ""; }; + 434FF1E91CF26C29000DB779 /* IdentifiableClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentifiableClass.swift; sourceTree = ""; }; 434FF1ED1CF27EEF000DB779 /* UITableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITableViewCell.swift; sourceTree = ""; }; 43523EDA1CC35083001850F1 /* RileyLinkKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RileyLinkKit.framework; path = Carthage/Build/iOS/RileyLinkKit.framework; sourceTree = ""; }; 435400301C9F744E00D5819C /* BolusSuggestionUserInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BolusSuggestionUserInfo.swift; sourceTree = ""; }; @@ -375,7 +378,7 @@ 438D42F81D7C88BC003244B0 /* PredictionInputEffect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PredictionInputEffect.swift; sourceTree = ""; }; 438D42FA1D7D11A4003244B0 /* PredictionInputEffectTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PredictionInputEffectTableViewCell.swift; sourceTree = ""; }; 438DADC71CDE8F8B007697A5 /* LoopStateView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoopStateView.swift; sourceTree = ""; }; - 439897341CD2F7DE00223065 /* NSTimeInterval.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NSTimeInterval.swift; path = Common/Extensions/NSTimeInterval.swift; sourceTree = ""; }; + 439897341CD2F7DE00223065 /* NSTimeInterval.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSTimeInterval.swift; sourceTree = ""; }; 439897361CD2F80600223065 /* AnalyticsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AnalyticsManager.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 4398973A1CD2FC2000223065 /* NSDateFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSDateFormatter.swift; sourceTree = ""; }; 43A567681C94880B00334FAC /* LoopDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = LoopDataManager.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; @@ -453,7 +456,7 @@ 4F2C15921E09BF2C00E160D4 /* HUDView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HUDView.swift; sourceTree = ""; }; 4F2C15941E09BF3C00E160D4 /* HUDView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HUDView.xib; sourceTree = ""; }; 4F2C15961E09E94E00E160D4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 4F526D5E1DF2459000A04910 /* HKUnit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HKUnit.swift; path = Common/Extensions/HKUnit.swift; sourceTree = ""; }; + 4F526D5E1DF2459000A04910 /* HKUnit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = ""; }; 4F526D601DF8D9A900A04910 /* NetBasal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetBasal.swift; sourceTree = ""; }; 4F70C1DC1DE8DCA7006380B7 /* Loop Status Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Loop Status Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 4F70C1DD1DE8DCA7006380B7 /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; @@ -466,14 +469,13 @@ 4F75288B1DFE1DC600C322D6 /* LoopUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LoopUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4F75288D1DFE1DC600C322D6 /* LoopUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LoopUI.h; sourceTree = ""; }; 4F75288E1DFE1DC600C322D6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 4FC8C8001DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "NSUserDefaults+StatusExtension.swift"; path = "Common/Extensions/NSUserDefaults+StatusExtension.swift"; sourceTree = ""; }; + 4FC8C8001DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSUserDefaults+StatusExtension.swift"; sourceTree = ""; }; 4FF4D0FF1E18374700846527 /* WatchContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchContext.swift; sourceTree = ""; }; C10428961D17BAD400DD539A /* NightscoutUploadKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NightscoutUploadKit.framework; path = Carthage/Build/iOS/NightscoutUploadKit.framework; sourceTree = ""; }; C12F21A61DFA79CB00748193 /* recommend_tamp_basal_very_low_end_in_range.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_tamp_basal_very_low_end_in_range.json; sourceTree = ""; }; C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MealBolusNightscoutTreatment.swift; sourceTree = ""; }; C17884621D51A7A400405663 /* BatteryIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryIndicator.swift; sourceTree = ""; }; C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutDataManager.swift; sourceTree = ""; }; - C1C73EF81DE3D0230022FC89 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; C1C73F031DE3D0250022FC89 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; C1C73F091DE3D0260022FC89 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; C1C73F0E1DE3D0270022FC89 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -631,16 +633,16 @@ 43776F8E1B8022E90074EA36 /* Loop */ = { isa = PBXGroup; children = ( - C1C73F0A1DE3D0260022FC89 /* InfoPlist.strings */, - C1C73F041DE3D0250022FC89 /* Localizable.strings */, 43846AD81D8FA84B00799272 /* gallery.ckcomplication */, 43EDEE6B1CF2E12A00393BE3 /* Loop.entitlements */, 43F5C2D41B92A4A6003EB13D /* Info.plist */, 43776F8F1B8022E90074EA36 /* AppDelegate.swift */, 43776F981B8022E90074EA36 /* Assets.xcassets */, - 43E344A01B9E144300C85C07 /* Extensions */, + C1C73F0A1DE3D0260022FC89 /* InfoPlist.strings */, + C1C73F041DE3D0250022FC89 /* Localizable.strings */, 43776F9A1B8022E90074EA36 /* LaunchScreen.storyboard */, 43776F951B8022E90074EA36 /* Main.storyboard */, + 43E344A01B9E144300C85C07 /* Extensions */, 43F5C2E41B93C5D4003EB13D /* Managers */, 43757D131C06F26C00910CB9 /* Models */, 43F5C2CE1B92A2A0003EB13D /* View Controllers */, @@ -676,9 +678,9 @@ 43A943821B926B7B0051FA24 /* WatchApp Extension */ = { isa = PBXGroup; children = ( - C1C73EF91DE3D0230022FC89 /* InfoPlist.strings */, 43D533BB1CFD1DD7009E3085 /* WatchApp Extension.entitlements */, 43846AD41D8FA67800799272 /* Base.lproj */, + 43076BF21DFDBC4B0012A723 /* it.lproj */, 43A943911B926B7B0051FA24 /* Info.plist */, 43A9438D1B926B7B0051FA24 /* ComplicationController.swift */, 43A943871B926B7B0051FA24 /* ExtensionDelegate.swift */, @@ -879,7 +881,7 @@ 4FF4D0FC1E1834CC00846527 /* Extensions */, 4FF4D0FB1E1834C400846527 /* Models */, ); - name = Common; + path = Common; sourceTree = ""; }; 4FF4D0FB1E1834C400846527 /* Models */ = { @@ -892,8 +894,7 @@ 4F70C2111DE900EA006380B7 /* StatusExtensionContext.swift */, 4FF4D0FF1E18374700846527 /* WatchContext.swift */, ); - name = Models; - path = Common/Models; + path = Models; sourceTree = ""; }; 4FF4D0FC1E1834CC00846527 /* Extensions */ = { @@ -906,7 +907,7 @@ 439897341CD2F7DE00223065 /* NSTimeInterval.swift */, 4FC8C8001DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift */, ); - name = Extensions; + path = Extensions; sourceTree = ""; }; 968DCD53F724DE56FFE51920 /* Frameworks */ = { @@ -1211,7 +1212,7 @@ buildActionMask = 2147483647; files = ( 43846AD51D8FA67800799272 /* Base.lproj in Resources */, - C1C73EF71DE3D0230022FC89 /* InfoPlist.strings in Resources */, + 43076BF31DFDBC4B0012A723 /* it.lproj in Resources */, 43A943901B926B7B0051FA24 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1397,6 +1398,8 @@ 4328E01E1CFBE25F00E199AA /* AddCarbsInterfaceController.swift in Sources */, 43846ADB1D91057000799272 /* ContextUpdatable.swift in Sources */, 4328E0261CFBE2C500E199AA /* IdentifiableClass.swift in Sources */, + 43027F0E1DFE0EC200C51989 /* NSNumberFormatter.swift in Sources */, + 43027F0F1DFE0EC900C51989 /* HKUnit.swift in Sources */, 43CB2B2B1D924D450079823D /* WCSession.swift in Sources */, 43DE925A1C5479E4001FFDE1 /* CarbEntryUserInfo.swift in Sources */, 43A9438E1B926B7B0051FA24 /* ComplicationController.swift in Sources */, @@ -1528,14 +1531,6 @@ name = MainInterface.storyboard; sourceTree = ""; }; - C1C73EF91DE3D0230022FC89 /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - C1C73EF81DE3D0230022FC89 /* it */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; C1C73F041DE3D0250022FC89 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( @@ -1611,7 +1606,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 3.0.1; TARGETED_DEVICE_FAMILY = "1,2"; WARNING_CFLAGS = "-Wall"; }; @@ -1658,7 +1653,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 3.0.1; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; WARNING_CFLAGS = "-Wall"; diff --git a/Loop/Managers/StatusExtensionDataManager.swift b/Loop/Managers/StatusExtensionDataManager.swift index 130480ea02..daa72eede0 100644 --- a/Loop/Managers/StatusExtensionDataManager.swift +++ b/Loop/Managers/StatusExtensionDataManager.swift @@ -109,6 +109,7 @@ extension StatusExtensionDataManager: CustomDebugStringConvertible { var debugDescription: String { return [ "## StatusExtensionDataManager", + "appGroupName: \(Bundle.main.appGroupSuiteName)", "statusExtensionContext: \(String(reflecting: defaults?.statusExtensionContext))" ].joined(separator: "\n") } diff --git a/Loop/gallery.ckcomplication/A307227B-6EFF-4242-A538-2C9AC617E041.json b/Loop/gallery.ckcomplication/A307227B-6EFF-4242-A538-2C9AC617E041.json deleted file mode 100644 index 58580ff1b3..0000000000 --- a/Loop/gallery.ckcomplication/A307227B-6EFF-4242-A538-2C9AC617E041.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "class" : "CLKComplicationTemplateModularSmallStackText", - "highlightLine2" : false, - "line2TextProvider" : { - "class" : "CLKLocalizableSimpleTextProvider", - "text" : "mg\/dL" - }, - "line1TextProvider" : { - "shortText" : "--", - "class" : "CLKSimpleTextProvider", - "text" : "--", - "accessibilityLabel" : "No glucose value available" - }, - "version" : 30000 -} \ No newline at end of file diff --git a/Loop/gallery.ckcomplication/Base.lproj/ckcomplication.strings b/Loop/gallery.ckcomplication/Base.lproj/ckcomplication.strings index 63987e6900..7f66681420 100644 --- a/Loop/gallery.ckcomplication/Base.lproj/ckcomplication.strings +++ b/Loop/gallery.ckcomplication/Base.lproj/ckcomplication.strings @@ -7,4 +7,10 @@ */ /* The complication template example unit string */ -"mg/dL" = "mg/dL" +"120↘︎" = "120↘︎"; + +/* Utilitarian small flat format string (1: Glucose & Trend symbol) (2: Time) */ +"UtilitarianSmallFlat" = "%1@ %2@"; + +/* Utilitarian small flat format string (1: Glucose & Trend symbol) (2: Eventual Glucose) (3: Time) */ +"UtilitarianLargeFlat" = "%1@%2@ %3@"; diff --git a/Loop/gallery.ckcomplication/complicationManifest.json b/Loop/gallery.ckcomplication/complicationManifest.json index f7de58b5c8..4f49690039 100644 --- a/Loop/gallery.ckcomplication/complicationManifest.json +++ b/Loop/gallery.ckcomplication/complicationManifest.json @@ -1,6 +1,10 @@ { "supported complication families" : { - "0" : "A307227B-6EFF-4242-A538-2C9AC617E041.json" + "3" : "CFB33AC2-7CD0-43A0-804F-F9A0EEC37480.json", + "6" : "E87B02EB-57B2-4E9D-B371-E0BA2DD55C19.json", + "4" : "B922BD0B-9601-45C9-B84D-ECB9C8235322.json", + "0" : "69B78E1F-415D-4E9E-8E56-E486517658FC.json", + "7" : "AB5AF86D-FF8F-493B-A738-4BB590C8A45E.json" }, "client ID" : "com.loudnate.Loop.watchkitapp.watchkitextension" } \ No newline at end of file diff --git a/README.md b/README.md index 182c0f3b27..e4abe59f8b 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,9 @@ Bluetooth LE communication with Minimed pumps is enabled by the [RileyLink](http [Sign up for the Loop Users announcement list](https://groups.google.com/forum/#!forum/loop-ios-users) to stay informed of critical issues that may arise. -[Please visit the Wiki for a "Guide to Loop" setup, installation, FAQs, and use.](https://github.com/LoopKit/Loop/wiki) +Please use the [Guide to Loop](https://github.com/LoopKit/Loop/wiki/Guide) for building, installation, and configuration instructions. + +For FAQs and other tips, refer to the [Wiki](https://github.com/LoopKit/Loop/wiki) (Note: there is also a tab for the Wiki at the top of this page) diff --git a/WatchApp Extension/Base.lproj/ckcomplication.strings b/WatchApp Extension/Base.lproj/ckcomplication.strings index 63987e6900..7f66681420 100644 --- a/WatchApp Extension/Base.lproj/ckcomplication.strings +++ b/WatchApp Extension/Base.lproj/ckcomplication.strings @@ -7,4 +7,10 @@ */ /* The complication template example unit string */ -"mg/dL" = "mg/dL" +"120↘︎" = "120↘︎"; + +/* Utilitarian small flat format string (1: Glucose & Trend symbol) (2: Time) */ +"UtilitarianSmallFlat" = "%1@ %2@"; + +/* Utilitarian small flat format string (1: Glucose & Trend symbol) (2: Eventual Glucose) (3: Time) */ +"UtilitarianLargeFlat" = "%1@%2@ %3@"; diff --git a/WatchApp Extension/ComplicationController.swift b/WatchApp Extension/ComplicationController.swift index 63884fbbf0..559e620ae4 100644 --- a/WatchApp Extension/ComplicationController.swift +++ b/WatchApp Extension/ComplicationController.swift @@ -43,21 +43,14 @@ final class ComplicationController: NSObject, CLKComplicationDataSource { private lazy var formatter = NumberFormatter() func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: (@escaping (CLKComplicationTimelineEntry?) -> Void)) { - - switch complication.family { - case .modularSmall: - if let context = ExtensionDelegate.shared().lastContext, - let glucose = context.glucose, - let unit = context.preferredGlucoseUnit, - let glucoseString = formatter.string(from: NSNumber(value: glucose.doubleValue(for: unit))), - let date = context.glucoseDate, date.timeIntervalSinceNow.minutes >= -15, - let template = CLKComplicationTemplateModularSmallStackText(line1: glucoseString, date: date) - { - handler(CLKComplicationTimelineEntry(date: date, complicationTemplate: template)) - } else { - handler(nil) - } - default: + if let context = ExtensionDelegate.shared().lastContext, + let glucoseDate = context.glucoseDate, + glucoseDate.timeIntervalSinceNow.minutes >= -15, + let template = CLKComplicationTemplate.templateForFamily(complication.family, from: context) + { + template.tintColor = UIColor.tintColor + handler(CLKComplicationTimelineEntry(date: glucoseDate, complicationTemplate: template)) + } else { handler(nil) } } @@ -69,13 +62,12 @@ final class ComplicationController: NSObject, CLKComplicationDataSource { func getTimelineEntries(for complication: CLKComplication, after date: Date, limit: Int, withHandler handler: (@escaping ([CLKComplicationTimelineEntry]?) -> Void)) { // Call the handler with the timeline entries after to the given date - if let context = ExtensionDelegate.shared().lastContext, - let glucose = context.glucose, - let unit = context.preferredGlucoseUnit, - let glucoseString = formatter.string(from: NSNumber(value: glucose.doubleValue(for: unit))), - let glucoseDate = context.glucoseDate, glucoseDate.timeIntervalSince(date) > 0, - let template = CLKComplicationTemplateModularSmallStackText(line1: glucoseString, date: glucoseDate) + if let context = ExtensionDelegate.shared().lastContext, + let glucoseDate = context.glucoseDate, + glucoseDate.timeIntervalSince(date) > 0, + let template = CLKComplicationTemplate.templateForFamily(complication.family, from: context) { + template.tintColor = UIColor.tintColor handler([CLKComplicationTimelineEntry(date: glucoseDate, complicationTemplate: template)]) } else { handler(nil) @@ -85,16 +77,41 @@ final class ComplicationController: NSObject, CLKComplicationDataSource { // MARK: - Placeholder Templates func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) { - switch complication.family { - case .modularSmall: - let template = CLKComplicationTemplateModularSmallStackText() - template.line1TextProvider = CLKSimpleTextProvider(text: "--", shortText: "--", accessibilityLabel: "No glucose value available") - template.line2TextProvider = CLKSimpleTextProvider.localizableTextProvider(withStringsFileTextKey: "mg/dL") + let template: CLKComplicationTemplate? + + let glucoseText = CLKSimpleTextProvider.localizableTextProvider(withStringsFileTextKey: "120↘︎", shortTextKey: "120") + let timeText = CLKTimeTextProvider(date: Date()) - handler(template) + switch complication.family { + case .modularSmall: + let modularSmall = CLKComplicationTemplateModularSmallStackText() + modularSmall.line1TextProvider = glucoseText + modularSmall.line2TextProvider = timeText + template = modularSmall + case .circularSmall: + let circularSmall = CLKComplicationTemplateCircularSmallSimpleText() + circularSmall.textProvider = glucoseText + template = circularSmall + case .extraLarge: + let extraLarge = CLKComplicationTemplateExtraLargeStackText() + extraLarge.line1TextProvider = glucoseText + extraLarge.line2TextProvider = timeText + template = extraLarge + case .utilitarianSmallFlat: + let utilitarianSmallFlat = CLKComplicationTemplateUtilitarianSmallFlat() + utilitarianSmallFlat.textProvider = CLKSimpleTextProvider.localizableTextProvider(withStringsFileFormatKey: "UtilitarianSmallFlat", textProviders: [glucoseText, timeText]) + template = utilitarianSmallFlat + case .utilitarianLarge: + let utilitarianLarge = CLKComplicationTemplateUtilitarianLargeFlat() + let eventualGlucoseText = CLKSimpleTextProvider.localizableTextProvider(withStringsFileTextKey: "75") + utilitarianLarge.textProvider = CLKSimpleTextProvider.localizableTextProvider(withStringsFileFormatKey: "UtilitarianLargeFlat", textProviders: [glucoseText, eventualGlucoseText, timeText]) + template = utilitarianLarge default: - handler(nil) + template = nil } + + template?.tintColor = UIColor.tintColor + handler(template) } } diff --git a/WatchApp Extension/Extensions/CLKComplicationTemplate.swift b/WatchApp Extension/Extensions/CLKComplicationTemplate.swift index cd30289801..a55543b0e9 100644 --- a/WatchApp Extension/Extensions/CLKComplicationTemplate.swift +++ b/WatchApp Extension/Extensions/CLKComplicationTemplate.swift @@ -10,17 +10,69 @@ import ClockKit import Foundation -extension CLKComplicationTemplateModularSmallStackText { +extension CLKComplicationTemplate { - convenience init?(line1: String?, date: Date?) { - guard let line1 = line1, let date = date else { + static func templateForFamily(_ family: CLKComplicationFamily, from context: WatchContext) -> CLKComplicationTemplate? { + + guard let glucose = context.glucose, + let unit = context.preferredGlucoseUnit + else { return nil } - self.init() + let formatter = NumberFormatter.glucoseFormatter(for: unit) - line1TextProvider = CLKSimpleTextProvider(text: line1) - line2TextProvider = CLKTimeTextProvider(date: date) - } + guard let glucoseString = formatter.string(from: NSNumber(value: glucose.doubleValue(for: unit))), + let date = context.glucoseDate else + { + return nil + } + + var glucoseStrings = [glucoseString] + var accessibilityStrings = [glucoseString] + var eventualGlucoseText: CLKSimpleTextProvider? + + if let trend = context.glucoseTrend { + glucoseStrings.append(trend.symbol) + accessibilityStrings.append(trend.localizedDescription) + } + if let eventualGlucose = context.eventualGlucose, + let eventualGlucoseString = formatter.string(from: NSNumber(value: eventualGlucose.doubleValue(for: unit))) + { + eventualGlucoseText = CLKSimpleTextProvider(text: eventualGlucoseString) + } + + let glucoseText = CLKSimpleTextProvider(text: glucoseStrings.joined(), shortText: glucoseString, accessibilityLabel: accessibilityStrings.joined(separator: ", ")) + let timeText = CLKTimeTextProvider(date: date) + + switch family { + case .modularSmall: + let template = CLKComplicationTemplateModularSmallStackText() + template.line1TextProvider = glucoseText + template.line2TextProvider = timeText + return template + case .circularSmall: + let template = CLKComplicationTemplateCircularSmallSimpleText() + template.textProvider = glucoseText + return template + case .extraLarge: + let template = CLKComplicationTemplateExtraLargeStackText() + template.line1TextProvider = glucoseText + template.line2TextProvider = timeText + return template + case .utilitarianSmallFlat: + let template = CLKComplicationTemplateUtilitarianSmallFlat() + template.textProvider = CLKSimpleTextProvider.localizableTextProvider(withStringsFileFormatKey: "UtilitarianSmallFlat", textProviders: [glucoseText, timeText]) + return template + case .utilitarianLarge: + let template = CLKComplicationTemplateUtilitarianLargeFlat() + let providers: [CLKTextProvider?] = [glucoseText, eventualGlucoseText, timeText] + + template.textProvider = CLKSimpleTextProvider.localizableTextProvider(withStringsFileFormatKey: "UtilitarianLargeFlat", textProviders: providers.flatMap({ $0 })) + return template + default: + return nil + } + } } diff --git a/WatchApp Extension/Info.plist b/WatchApp Extension/Info.plist index 50f23199d8..06d294cf12 100644 --- a/WatchApp Extension/Info.plist +++ b/WatchApp Extension/Info.plist @@ -27,6 +27,10 @@ CLKComplicationSupportedFamilies CLKComplicationFamilyModularSmall + CLKComplicationFamilyCircularSmall + CLKComplicationFamilyExtraLarge + CLKComplicationFamilyUtilitarianSmallFlat + CLKComplicationFamilyUtilitarianLarge NSExtension