From 574ecbc3e94a01f60b1d28754175897fe9907a98 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 27 Jan 2019 11:19:28 -0600 Subject: [PATCH 001/132] Bump version for dev --- DoseMathTests/Info.plist | 2 +- Loop Status Extension/Info.plist | 2 +- Loop.xcodeproj/project.pbxproj | 12 ++++++------ Loop/Info.plist | 2 +- LoopTests/Info.plist | 2 +- LoopUI/Info.plist | 2 +- WatchApp Extension/Info.plist | 2 +- WatchApp/Info.plist | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/DoseMathTests/Info.plist b/DoseMathTests/Info.plist index e23ad55583..d8eec4e475 100644 --- a/DoseMathTests/Info.plist +++ b/DoseMathTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.9.4 + 1.9.5dev CFBundleSignature ???? CFBundleVersion diff --git a/Loop Status Extension/Info.plist b/Loop Status Extension/Info.plist index 613009f16d..ae0fccf75f 100644 --- a/Loop Status Extension/Info.plist +++ b/Loop Status Extension/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.9.3 + 1.9.5dev CFBundleVersion $(CURRENT_PROJECT_VERSION) AppGroupIdentifier diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 6dd4294362..7176d7070b 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -2534,7 +2534,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer: loudnate@gmail.com (XZN842LDLT)"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 55; + CURRENT_PROJECT_VERSION = 56; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -2606,7 +2606,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer: loudnate@gmail.com (XZN842LDLT)"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 55; + CURRENT_PROJECT_VERSION = 56; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -2864,11 +2864,11 @@ CLANG_WARN_SUSPICIOUS_MOVES = YES; CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - CURRENT_PROJECT_VERSION = 55; + CURRENT_PROJECT_VERSION = 56; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 55; + DYLIB_CURRENT_VERSION = 56; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = LoopUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -2891,11 +2891,11 @@ CLANG_WARN_SUSPICIOUS_MOVES = YES; CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - CURRENT_PROJECT_VERSION = 55; + CURRENT_PROJECT_VERSION = 56; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 55; + DYLIB_CURRENT_VERSION = 56; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = LoopUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/Loop/Info.plist b/Loop/Info.plist index 0f690c2c7c..1958ca3708 100644 --- a/Loop/Info.plist +++ b/Loop/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.9.4 + 1.9.5dev CFBundleSignature ???? CFBundleURLTypes diff --git a/LoopTests/Info.plist b/LoopTests/Info.plist index e23ad55583..d8eec4e475 100644 --- a/LoopTests/Info.plist +++ b/LoopTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.9.4 + 1.9.5dev CFBundleSignature ???? CFBundleVersion diff --git a/LoopUI/Info.plist b/LoopUI/Info.plist index 831b652409..375a053dd0 100644 --- a/LoopUI/Info.plist +++ b/LoopUI/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.9.4 + 1.9.5dev CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/WatchApp Extension/Info.plist b/WatchApp Extension/Info.plist index 30a1965f7c..94f1c25d75 100644 --- a/WatchApp Extension/Info.plist +++ b/WatchApp Extension/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.9.4 + 1.9.5dev CFBundleSignature ???? CFBundleVersion diff --git a/WatchApp/Info.plist b/WatchApp/Info.plist index 0c76794f4c..ff356a41f9 100644 --- a/WatchApp/Info.plist +++ b/WatchApp/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.9.4 + 1.9.5dev CFBundleSignature ???? CFBundleVersion From 8de3796d39b8a40445be40cb2f120c4288faf99b Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 19 Feb 2019 16:07:10 -0600 Subject: [PATCH 002/132] Pumpmanager changes (#877) * Omnikit * Add OmniKit frameworks * fix cartfile * Track lastAddedPumpData, including lastAddedPumpEvents * Fix carthage copy frameworks script * rebase * update deps * Initialize setupViewController with existing pump settings * Suspend/Resume * Use normal separator behavior * Reload pump section after adding pumpmanager * LoopKit PumpManagerStatus updates * PumpManagerUI vended HUD views * Add missing file * Update to LoopKit naming changes * Push instead of present pumpmanager vended vc in response to HUD view tap * Add prep_workspace.sh script * HUDProvider updates * Fix hud clearing after removing pump manager. Show boluses that take > 5 minutes * PumpManager returns DoseEntry for requested bolus, including duration * Example jumpstart * Suspend/Resume with new PumpManager status updates * Fix issue with DeviceDataManager missing from issue report * Handle LoopKit and rileylink_ios updates * Cleanup testing code * Update Loop today widget when HUD views change * Renaming updates from LoopKit * Updates for recent LoopKit pumpmanager-changes branch * update prep script for CGMBLEKit branch change * Updates for hud provider delegate change * Pumpmanager updates * Update prep script * Fix permissions on prep workspace script * Pumpmanager changes * Update to pumpmanager-updates * Back out DEVELOPMENT_TEAM settings * Context object does not need HUDProvider reference * Include dose in enactBolus completion * Bump dependency revisions * Update HUD set of views only on PumpManager change * Remove unused file * synchronize deviceDataManager.pumpManagerStatus access * Bump dep revisions * Remove unused files * Remove unneeded reloadSections calls * Fix build issue when carthage is run from Xcode * Use bolus end date to clear lastRequestedBolus * present instead of show settings vc * small refactorings * Improvements from @mpangburn's review * Update active flag on HUDProvider when hud is visible and app is active * Remove signing team changes * HUDProvider active -> visible * Use LoopKit dev * Use constant for battery replacement detection threshold * Force git protocol 1 to fix travis builds --- .travis.yml | 1 + Cartfile | 11 +- Cartfile.resolved | 10 +- .../Models}/PumpManager.swift | 0 Common/Models/PumpManagerUI.swift | 37 ++++ Common/Models/StatusExtensionContext.swift | 34 +++ Loop Status Extension/StateColorPalette.swift | 1 + .../StatusViewController.swift | 32 ++- Loop.xcodeproj/project.pbxproj | 66 +++--- Loop/Extensions/NewPumpEvent.swift | 18 +- Loop/Extensions/StateColorPalette.swift | 1 + Loop/Extensions/UIAlertController.swift | 10 +- Loop/Extensions/UIImage.swift | 8 - Loop/Managers/DeviceDataManager.swift | 106 +++++++--- Loop/Managers/LoopDataManager.swift | 35 ++-- Loop/Managers/NightscoutDataManager.swift | 101 ++++----- .../Managers/StatusExtensionDataManager.swift | 10 +- .../BolusViewController.swift | 6 +- .../CarbAbsorptionViewController.swift | 4 +- .../ChartsTableViewController.swift | 9 +- .../SettingsTableViewController.swift | 66 +++--- .../StatusTableViewController.swift | 194 +++++++++++++----- Loop/Views/SettingsImageTableViewCell.swift | 2 +- .../Contents.json | 0 .../battery/Contents.json | 0 .../battery/battery.imageset/Contents.json | 0 .../battery/battery.imageset/battery.pdf | Bin .../battery_mask.imageset/Contents.json | 0 .../battery_mask.imageset/battery_mask.pdf | Bin .../reservoir/Contents.json | 0 .../reservoir.imageset/Contents.json | 0 .../reservoir.imageset/reservoir.pdf | Bin .../reservoir_mask.imageset/Contents.json | 0 .../reservoir_mask.pdf | Bin LoopUI/HUDView.xib | 149 ++------------ LoopUI/Models/StateColorPalette.swift | 24 --- LoopUI/Views/BasalRateHUDView.swift | 6 +- LoopUI/Views/BaseHUDView.swift | 19 -- LoopUI/Views/BatteryLevelHUDView.swift | 34 --- LoopUI/Views/GlucoseHUDView.swift | 13 +- LoopUI/Views/HUDView.swift | 26 ++- LoopUI/Views/LevelHUDView.swift | 52 ----- LoopUI/Views/LevelMaskView.swift | 85 -------- LoopUI/Views/LoopCompletionHUDView.swift | 12 +- LoopUI/Views/ReservoirVolumeHUDView.swift | 65 ------ 45 files changed, 525 insertions(+), 722 deletions(-) rename {Loop/Managers => Common/Models}/PumpManager.swift (100%) create mode 100644 Common/Models/PumpManagerUI.swift rename LoopUI/{Assets.xcassets => HUDAssets.xcassets}/Contents.json (100%) rename LoopUI/{Assets.xcassets => HUDAssets.xcassets}/battery/Contents.json (100%) rename LoopUI/{Assets.xcassets => HUDAssets.xcassets}/battery/battery.imageset/Contents.json (100%) rename LoopUI/{Assets.xcassets => HUDAssets.xcassets}/battery/battery.imageset/battery.pdf (100%) rename LoopUI/{Assets.xcassets => HUDAssets.xcassets}/battery/battery_mask.imageset/Contents.json (100%) rename LoopUI/{Assets.xcassets => HUDAssets.xcassets}/battery/battery_mask.imageset/battery_mask.pdf (100%) rename LoopUI/{Assets.xcassets => HUDAssets.xcassets}/reservoir/Contents.json (100%) rename LoopUI/{Assets.xcassets => HUDAssets.xcassets}/reservoir/reservoir.imageset/Contents.json (100%) rename LoopUI/{Assets.xcassets => HUDAssets.xcassets}/reservoir/reservoir.imageset/reservoir.pdf (100%) rename LoopUI/{Assets.xcassets => HUDAssets.xcassets}/reservoir/reservoir_mask.imageset/Contents.json (100%) rename LoopUI/{Assets.xcassets => HUDAssets.xcassets}/reservoir/reservoir_mask.imageset/reservoir_mask.pdf (100%) delete mode 100644 LoopUI/Models/StateColorPalette.swift delete mode 100644 LoopUI/Views/BaseHUDView.swift delete mode 100644 LoopUI/Views/BatteryLevelHUDView.swift delete mode 100644 LoopUI/Views/LevelHUDView.swift delete mode 100644 LoopUI/Views/LevelMaskView.swift delete mode 100644 LoopUI/Views/ReservoirVolumeHUDView.swift diff --git a/.travis.yml b/.travis.yml index d695f5dfe4..ae15c2a041 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ osx_image: xcode10 # xcode_project: Loop.xcodeproj # xcode_scheme: Loop before_script: + - git config --global protocol.version 1 - set -o pipefail && xcodebuild -project Loop.xcodeproj -target Cartfile script: # Build the app target diff --git a/Cartfile b/Cartfile index f5db9d0aed..caac804a02 100644 --- a/Cartfile +++ b/Cartfile @@ -1,8 +1,7 @@ - -github "LoopKit/LoopKit" == 2.2.1 -github "LoopKit/CGMBLEKit" == 3.0 +github "LoopKit/LoopKit" "dev" +github "LoopKit/CGMBLEKit" "dev" github "i-schuetz/SwiftCharts" == 0.6.2 -github "LoopKit/dexcom-share-client-swift" == 1.0 -github "LoopKit/G4ShareSpy" == 1.0 -github "ps2/rileylink_ios" == 2.1.0 +github "LoopKit/dexcom-share-client-swift" "dev" +github "LoopKit/G4ShareSpy" "dev" +github "ps2/rileylink_ios" "dev" github "LoopKit/Amplitude-iOS" "decreepify" diff --git a/Cartfile.resolved b/Cartfile.resolved index eaad8dc7a3..1ded1a366c 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,7 +1,7 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" -github "LoopKit/CGMBLEKit" "v3.0" -github "LoopKit/G4ShareSpy" "v1.0" -github "LoopKit/LoopKit" "v2.2.1" -github "LoopKit/dexcom-share-client-swift" "v1.0" +github "LoopKit/CGMBLEKit" "ea1267791c66e884f1013fffd36faf4555cc6eaf" +github "LoopKit/G4ShareSpy" "fed5a389e3e47e3a1953878dd21852aa5f44b360" +github "LoopKit/LoopKit" "bf4166bc77f89e22971f2030ad006967881ff082" +github "LoopKit/dexcom-share-client-swift" "b0419edf55c7f389b36cb47dd5c376bbd3d03d69" github "i-schuetz/SwiftCharts" "0.6.2" -github "ps2/rileylink_ios" "v2.1.0" +github "ps2/rileylink_ios" "46ed2c845e7c4b88c721994d8e8432e86be4c699" diff --git a/Loop/Managers/PumpManager.swift b/Common/Models/PumpManager.swift similarity index 100% rename from Loop/Managers/PumpManager.swift rename to Common/Models/PumpManager.swift diff --git a/Common/Models/PumpManagerUI.swift b/Common/Models/PumpManagerUI.swift new file mode 100644 index 0000000000..085af7eaf5 --- /dev/null +++ b/Common/Models/PumpManagerUI.swift @@ -0,0 +1,37 @@ +// +// PumpManagerUI.swift +// Loop +// +// Created by Pete Schwamb on 10/18/18. +// Copyright © 2018 LoopKit Authors. All rights reserved. +// + +import Foundation +import LoopKit +import LoopKitUI +import MinimedKit +import MinimedKitUI + +private let managersByIdentifier: [String: PumpManagerUI.Type] = allPumpManagers.compactMap{ $0 as? PumpManagerUI.Type}.reduce(into: [:]) { (map, Type) in + map[Type.managerIdentifier] = Type +} + +typealias PumpManagerHUDViewsRawValue = [String: Any] + +func PumpManagerHUDViewsFromRawValue(_ rawValue: PumpManagerHUDViewsRawValue) -> [BaseHUDView]? { + guard let rawState = rawValue["hudProviderViews"] as? HUDProvider.HUDViewsRawState, + let managerIdentifier = rawValue["managerIdentifier"] as? String, + let manager = managersByIdentifier[managerIdentifier] + else { + return nil + } + + return manager.createHUDViews(rawValue: rawState) +} + +func PumpManagerHUDViewsRawValueFromHudProvider(_ hudProvider: HUDProvider) -> PumpManagerHUDViewsRawValue { + return [ + "managerIdentifier": hudProvider.managerIdentifier, + "hudProviderViews": hudProvider.hudViewsRawState + ] +} diff --git a/Common/Models/StatusExtensionContext.swift b/Common/Models/StatusExtensionContext.swift index f385cf30cc..c838e258eb 100644 --- a/Common/Models/StatusExtensionContext.swift +++ b/Common/Models/StatusExtensionContext.swift @@ -10,6 +10,7 @@ import Foundation import HealthKit import LoopKit +import LoopKitUI struct NetBasalContext { @@ -151,6 +152,32 @@ extension PredictedGlucoseContext: RawRepresentable { } } +struct PumpManagerHUDViewsContext: RawRepresentable { + typealias RawValue = [String: Any] + + let pumpManagerHUDViewsRawValue: PumpManagerHUDViewsRawValue + + var hudViews: [BaseHUDView]? { + return PumpManagerHUDViewsFromRawValue(pumpManagerHUDViewsRawValue) + } + + init(pumpManagerHUDViewsRawValue: PumpManagerHUDViewsRawValue) { + self.pumpManagerHUDViewsRawValue = pumpManagerHUDViewsRawValue + } + + init?(rawValue: RawValue) { + if let pumpManagerHUDViewsRawValue = rawValue["pumpManagerHUDViewsRawValue"] as? PumpManagerHUDViewsRawValue { + self.pumpManagerHUDViewsRawValue = pumpManagerHUDViewsRawValue + } else { + return nil + } + } + + var rawValue: RawValue { + return ["pumpManagerHUDViewsRawValue": pumpManagerHUDViewsRawValue] + } +} + struct StatusExtensionContext: RawRepresentable { typealias RawValue = [String: Any] private let version = 5 @@ -161,6 +188,7 @@ struct StatusExtensionContext: RawRepresentable { var batteryPercentage: Double? var reservoirCapacity: Double? var sensor: SensorDisplayableContext? + var pumpManagerHUDViewsContext: PumpManagerHUDViewsContext? init() { } @@ -184,6 +212,10 @@ struct StatusExtensionContext: RawRepresentable { if let rawValue = rawValue["sensor"] as? SensorDisplayableContext.RawValue { sensor = SensorDisplayableContext(rawValue: rawValue) } + + if let rawPumpManagerHUDViewsContext = rawValue["pumpManagerHUDViewsContext"] as? PumpManagerHUDViewsContext.RawValue { + pumpManagerHUDViewsContext = PumpManagerHUDViewsContext(rawValue: rawPumpManagerHUDViewsContext) + } } var rawValue: RawValue { @@ -197,6 +229,8 @@ struct StatusExtensionContext: RawRepresentable { raw["batteryPercentage"] = batteryPercentage raw["reservoirCapacity"] = reservoirCapacity raw["sensor"] = sensor?.rawValue + raw["pumpManagerHUDViewsContext"] = pumpManagerHUDViewsContext?.rawValue + return raw } } diff --git a/Loop Status Extension/StateColorPalette.swift b/Loop Status Extension/StateColorPalette.swift index c4e67ba9c4..e6f18b436a 100644 --- a/Loop Status Extension/StateColorPalette.swift +++ b/Loop Status Extension/StateColorPalette.swift @@ -6,6 +6,7 @@ // import LoopUI +import LoopKitUI extension StateColorPalette { static let loopStatus = StateColorPalette(unknown: .unknownColor, normal: .freshColor, warning: .agingColor, error: .staleColor) diff --git a/Loop Status Extension/StatusViewController.swift b/Loop Status Extension/StatusViewController.swift index 68d1856898..29176c2a2a 100644 --- a/Loop Status Extension/StatusViewController.swift +++ b/Loop Status Extension/StatusViewController.swift @@ -9,6 +9,7 @@ import CoreData import HealthKit import LoopKit +import LoopKitUI import LoopUI import NotificationCenter import UIKit @@ -22,8 +23,6 @@ class StatusViewController: UIViewController, NCWidgetProviding { hudView.glucoseHUD.stateColors = .cgmStatus hudView.glucoseHUD.tintColor = .glucoseTintColor hudView.basalRateHUD.tintColor = .doseTintColor - hudView.reservoirVolumeHUD.stateColors = .pumpStatus - hudView.batteryHUD.stateColors = .pumpStatus } } @IBOutlet weak var subtitleLabel: UILabel! @@ -159,7 +158,6 @@ class StatusViewController: UIViewController, NCWidgetProviding { let group = DispatchGroup() var activeInsulin: Double? - var lastReservoirValue: ReservoirValue? var glucose: [StoredGlucoseSample] = [] group.enter() @@ -173,17 +171,6 @@ class StatusViewController: UIViewController, NCWidgetProviding { group.leave() } - group.enter() - doseStore.getReservoirValues(since: .distantPast, limit: 1) { (result) in - switch result { - case .success(let values): - lastReservoirValue = values.first - case .failure: - lastReservoirValue = nil - } - group.leave() - } - charts.startDate = Calendar.current.nextDate(after: Date(timeIntervalSinceNow: .minutes(-5)), matching: DateComponents(minute: 0), matchingPolicy: .strict, direction: .backward) ?? Date() // Showing the whole history plus full prediction in the glucose plot @@ -201,13 +188,20 @@ class StatusViewController: UIViewController, NCWidgetProviding { return } - if let batteryPercentage = context.batteryPercentage { - self.hudView.batteryHUD.batteryLevel = Double(batteryPercentage) + let hudViews: [BaseHUDView] + + if let hudViewsContext = context.pumpManagerHUDViewsContext, + let contextHUDViews = hudViewsContext.hudViews + { + hudViews = contextHUDViews + } else { + hudViews = [ReservoirVolumeHUDView.instantiate(), BatteryLevelHUDView.instantiate()] } - if let reservoir = lastReservoirValue, let capacity = context.reservoirCapacity { - self.hudView.reservoirVolumeHUD.reservoirLevel = min(1, max(0, Double(reservoir.unitVolume / capacity))) - self.hudView.reservoirVolumeHUD.setReservoirVolume(volume: reservoir.unitVolume, at: reservoir.startDate) + self.hudView.removePumpManagerProvidedViews() + for view in hudViews { + view.stateColors = .pumpStatus + self.hudView.addHUDView(view) } if let netBasal = context.netBasal { diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 7176d7070b..4422013483 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -153,8 +153,6 @@ 43BFF0C51E465A2D00FF19A9 /* UIColor+HIG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0C31E4659E700FF19A9 /* UIColor+HIG.swift */; }; 43BFF0C61E465A4400FF19A9 /* UIColor+HIG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0C31E4659E700FF19A9 /* UIColor+HIG.swift */; }; 43BFF0C71E465A4F00FF19A9 /* UIColor+HIG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0C31E4659E700FF19A9 /* UIColor+HIG.swift */; }; - 43BFF0C91E465B0A00FF19A9 /* StateColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0C81E465B0A00FF19A9 /* StateColorPalette.swift */; }; - 43BFF0CB1E466C0900FF19A9 /* StateColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0CA1E466C0900FF19A9 /* StateColorPalette.swift */; }; 43BFF0CD1E466C8400FF19A9 /* StateColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0CC1E466C8400FF19A9 /* StateColorPalette.swift */; }; 43C0944A1CACCC73001F6403 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C094491CACCC73001F6403 /* NotificationManager.swift */; }; 43C246A81D89990F0031F8D1 /* Crypto.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43C246A71D89990F0031F8D1 /* Crypto.framework */; }; @@ -177,7 +175,6 @@ 43DE925A1C5479E4001FFDE1 /* CarbEntryUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DE92581C5479E4001FFDE1 /* CarbEntryUserInfo.swift */; }; 43DE92611C555C26001FFDE1 /* AbsorptionTimeType+CarbKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DE92601C555C26001FFDE1 /* AbsorptionTimeType+CarbKit.swift */; }; 43DFB62320D4CAE7008A7BAE /* PumpManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C3B6F620BBCAA30026CAFA /* PumpManager.swift */; }; - 43E0F0A51E46D1670064F7CE /* LevelHUDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43E0F0A41E46D1670064F7CE /* LevelHUDView.swift */; }; 43E2D8C81D208D5B004DA55F /* KeychainManager+Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43E2D8C71D208D5B004DA55F /* KeychainManager+Loop.swift */; }; 43E2D8D41D20BF42004DA55F /* DoseMathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43E2D8D31D20BF42004DA55F /* DoseMathTests.swift */; }; 43E2D8DB1D20C03B004DA55F /* NSTimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439897341CD2F7DE00223065 /* NSTimeInterval.swift */; }; @@ -217,7 +214,6 @@ 4F11D3C220DD80B3006E072C /* WatchHistoricalGlucose.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F11D3C120DD80B3006E072C /* WatchHistoricalGlucose.swift */; }; 4F11D3C320DD84DB006E072C /* GlucoseBackfillRequestUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F11D3BF20DCBEEC006E072C /* GlucoseBackfillRequestUserInfo.swift */; }; 4F11D3C420DD881A006E072C /* WatchHistoricalGlucose.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F11D3C120DD80B3006E072C /* WatchHistoricalGlucose.swift */; }; - 4F20AE621E6B879C00D07A06 /* ReservoirVolumeHUDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 437CEEC71CD84CBB003C8C80 /* ReservoirVolumeHUDView.swift */; }; 4F20AE631E6B87B100D07A06 /* ChartContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4313EDDF1D8A6BF90060FA79 /* ChartContainerView.swift */; }; 4F2C15741E0209F500E160D4 /* NSTimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439897341CD2F7DE00223065 /* NSTimeInterval.swift */; }; 4F2C15811E0495B200E160D4 /* WatchContext+WatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F2C15801E0495B200E160D4 /* WatchContext+WatchApp.swift */; }; @@ -226,7 +222,7 @@ 4F2C15851E075B8700E160D4 /* LoopUI.h in Headers */ = {isa = PBXBuildFile; fileRef = 4F75288D1DFE1DC600C322D6 /* LoopUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4F2C15931E09BF2C00E160D4 /* HUDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F2C15921E09BF2C00E160D4 /* HUDView.swift */; }; 4F2C15951E09BF3C00E160D4 /* HUDView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4F2C15941E09BF3C00E160D4 /* HUDView.xib */; }; - 4F2C15971E09E94E00E160D4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4F2C15961E09E94E00E160D4 /* Assets.xcassets */; }; + 4F2C15971E09E94E00E160D4 /* HUDAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4F2C15961E09E94E00E160D4 /* HUDAssets.xcassets */; }; 4F2C159A1E0C9E5600E160D4 /* LoopUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4F75288B1DFE1DC600C322D6 /* LoopUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 4F526D611DF8D9A900A04910 /* NetBasal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F526D601DF8D9A900A04910 /* NetBasal.swift */; }; 4F526D621DF9D95200A04910 /* NSBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430DA58D1D4AEC230097D1CA /* NSBundle.swift */; }; @@ -241,13 +237,10 @@ 4F7528941DFE1E9500C322D6 /* LoopUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F75288B1DFE1DC600C322D6 /* LoopUI.framework */; }; 4F7528951DFE1E9B00C322D6 /* LoopUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F75288B1DFE1DC600C322D6 /* LoopUI.framework */; }; 4F75289A1DFE1F6000C322D6 /* BasalRateHUDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 437CEEBF1CD6FCD8003C8C80 /* BasalRateHUDView.swift */; }; - 4F75289B1DFE1F6000C322D6 /* BatteryLevelHUDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 437CEEC91CD84DB7003C8C80 /* BatteryLevelHUDView.swift */; }; 4F75289C1DFE1F6000C322D6 /* GlucoseHUDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4337615E1D52F487004A3647 /* GlucoseHUDView.swift */; }; - 4F75289D1DFE1F6000C322D6 /* BaseHUDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 437CEEBB1CD6DE6A003C8C80 /* BaseHUDView.swift */; }; 4F75289E1DFE1F6000C322D6 /* LoopCompletionHUDView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 437CEEBD1CD6E0CB003C8C80 /* LoopCompletionHUDView.swift */; }; 4F7528A01DFE1F9D00C322D6 /* LoopStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 438DADC71CDE8F8B007697A5 /* LoopStateView.swift */; }; 4F7528A11DFE200B00C322D6 /* BasalStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43B371851CE583890013C5A6 /* BasalStateView.swift */; }; - 4F7528A21DFE200B00C322D6 /* LevelMaskView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43FBEDD71D73843700B21F22 /* LevelMaskView.swift */; }; 4F7528A51DFE208C00C322D6 /* NSTimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439897341CD2F7DE00223065 /* NSTimeInterval.swift */; }; 4F7528AA1DFE215100C322D6 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F526D5E1DF2459000A04910 /* HKUnit.swift */; }; 4F75F00220FCFE8C00B5570E /* GlucoseChartScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F75F00120FCFE8C00B5570E /* GlucoseChartScene.swift */; }; @@ -301,6 +294,8 @@ C12F21A71DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json in Resources */ = {isa = PBXBuildFile; fileRef = C12F21A61DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json */; }; C13BAD941E8009B000050CB5 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0B31E45C1BE00FF19A9 /* NumberFormatter.swift */; }; C15713821DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */; }; + C168C40621B0D53E00ADE90E /* MinimedKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C168C40521B0D53E00ADE90E /* MinimedKit.framework */; }; + C168C40821B0D53E00ADE90E /* MinimedKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C168C40721B0D53E00ADE90E /* MinimedKitUI.framework */; }; C178249A1E1999FA00D9D25C /* CaseCountable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17824991E1999FA00D9D25C /* CaseCountable.swift */; }; C17824A01E19CF9800D9D25C /* GlucoseThresholdTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C178249F1E19CF9800D9D25C /* GlucoseThresholdTableViewController.swift */; }; C17824A31E19EAB600D9D25C /* recommend_temp_basal_start_very_low_end_high.json in Resources */ = {isa = PBXBuildFile; fileRef = C17824A21E19EAB600D9D25C /* recommend_temp_basal_start_very_low_end_high.json */; }; @@ -310,6 +305,10 @@ C18C8C511D5A351900E043FB /* NightscoutDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */; }; C1C6591C1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json in Resources */ = {isa = PBXBuildFile; fileRef = C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */; }; C1C73F0D1DE3D0270022FC89 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1C73F0F1DE3D0270022FC89 /* InfoPlist.strings */; }; + C1FB428C217806A400FAB378 /* StateColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428B217806A300FAB378 /* StateColorPalette.swift */; }; + C1FB428D21791D2500FAB378 /* PumpManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C3B6F620BBCAA30026CAFA /* PumpManager.swift */; }; + C1FB428F217921D600FAB378 /* PumpManagerUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428E217921D600FAB378 /* PumpManagerUI.swift */; }; + C1FB4290217922A100FAB378 /* PumpManagerUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428E217921D600FAB378 /* PumpManagerUI.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -524,11 +523,8 @@ 43785EA32122774B0057DED1 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Intents.strings; sourceTree = ""; }; 4379CFEF21112CF700AADC79 /* ShareClientUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ShareClientUI.framework; path = Carthage/Build/iOS/ShareClientUI.framework; sourceTree = SOURCE_ROOT; }; 437AFEE6203688CF008C4892 /* LoopKitUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LoopKitUI.framework; path = Carthage/Build/iOS/LoopKitUI.framework; sourceTree = ""; }; - 437CEEBB1CD6DE6A003C8C80 /* BaseHUDView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseHUDView.swift; sourceTree = ""; }; 437CEEBD1CD6E0CB003C8C80 /* LoopCompletionHUDView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoopCompletionHUDView.swift; sourceTree = ""; }; 437CEEBF1CD6FCD8003C8C80 /* BasalRateHUDView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasalRateHUDView.swift; sourceTree = ""; }; - 437CEEC71CD84CBB003C8C80 /* ReservoirVolumeHUDView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReservoirVolumeHUDView.swift; sourceTree = ""; }; - 437CEEC91CD84DB7003C8C80 /* BatteryLevelHUDView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatteryLevelHUDView.swift; sourceTree = ""; }; 437CEEE31CDE5C0A003C8C80 /* UIImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = ""; }; 437D9BA11D7B5203007245E8 /* Loop.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Loop.xcconfig; sourceTree = ""; }; 437D9BA21D7BC977007245E8 /* PredictionTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PredictionTableViewController.swift; sourceTree = ""; }; @@ -568,8 +564,6 @@ 43BFF0BB1E45C80600FF19A9 /* UIColor+Loop.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Loop.swift"; sourceTree = ""; }; 43BFF0BE1E45C8EA00FF19A9 /* UIColor+Widget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Widget.swift"; sourceTree = ""; }; 43BFF0C31E4659E700FF19A9 /* UIColor+HIG.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+HIG.swift"; sourceTree = ""; }; - 43BFF0C81E465B0A00FF19A9 /* StateColorPalette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateColorPalette.swift; sourceTree = ""; }; - 43BFF0CA1E466C0900FF19A9 /* StateColorPalette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateColorPalette.swift; sourceTree = ""; }; 43BFF0CC1E466C8400FF19A9 /* StateColorPalette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateColorPalette.swift; sourceTree = ""; }; 43C094491CACCC73001F6403 /* NotificationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = ""; }; 43C246A71D89990F0031F8D1 /* Crypto.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Crypto.framework; path = Carthage/Build/iOS/Crypto.framework; sourceTree = SOURCE_ROOT; }; @@ -592,7 +586,6 @@ 43DBF0581C93F73800B3C386 /* CarbEntryTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CarbEntryTableViewController.swift; sourceTree = ""; }; 43DE92581C5479E4001FFDE1 /* CarbEntryUserInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CarbEntryUserInfo.swift; sourceTree = ""; }; 43DE92601C555C26001FFDE1 /* AbsorptionTimeType+CarbKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AbsorptionTimeType+CarbKit.swift"; sourceTree = ""; }; - 43E0F0A41E46D1670064F7CE /* LevelHUDView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LevelHUDView.swift; sourceTree = ""; }; 43E2D8C71D208D5B004DA55F /* KeychainManager+Loop.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "KeychainManager+Loop.swift"; sourceTree = ""; }; 43E2D8C91D20B9E7004DA55F /* KeychainManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainManagerTests.swift; sourceTree = ""; }; 43E2D8D11D20BF42004DA55F /* DoseMathTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DoseMathTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -622,7 +615,6 @@ 43F64DD81D9C92C900D24DC6 /* TitleSubtitleTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TitleSubtitleTableViewCell.swift; sourceTree = ""; }; 43F78D251C8FC000002152D1 /* DoseMath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoseMath.swift; sourceTree = ""; }; 43F78D4B1C914197002152D1 /* LoopKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LoopKit.framework; path = Carthage/Build/iOS/LoopKit.framework; sourceTree = SOURCE_ROOT; }; - 43FBEDD71D73843700B21F22 /* LevelMaskView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LevelMaskView.swift; sourceTree = ""; }; 4D3B40021D4A9DFE00BC6334 /* G4ShareSpy.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = G4ShareSpy.framework; path = Carthage/Build/iOS/G4ShareSpy.framework; sourceTree = SOURCE_ROOT; }; 4F08DE7C1E7BB6E5006741EA /* ChartAxisValueDoubleLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartAxisValueDoubleLog.swift; sourceTree = ""; }; 4F08DE7D1E7BB6E5006741EA /* ChartAxisValueDoubleUnit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartAxisValueDoubleUnit.swift; sourceTree = ""; }; @@ -635,7 +627,7 @@ 4F2C15801E0495B200E160D4 /* WatchContext+WatchApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "WatchContext+WatchApp.swift"; sourceTree = ""; }; 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 = ""; }; + 4F2C15961E09E94E00E160D4 /* HUDAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = HUDAssets.xcassets; 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 = ""; }; 4F6663931E905FD2009E74FC /* ChartColorPalette+Loop.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ChartColorPalette+Loop.swift"; sourceTree = ""; }; @@ -839,13 +831,18 @@ C10B28451EA9BA5E006EA1FC /* far_future_high_bg_forecast.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = far_future_high_bg_forecast.json; sourceTree = ""; }; C12F21A61DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_temp_basal_very_low_end_in_range.json; sourceTree = ""; }; C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MealBolusNightscoutTreatment.swift; sourceTree = ""; }; + C168C40521B0D53E00ADE90E /* MinimedKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MinimedKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C168C40721B0D53E00ADE90E /* MinimedKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MinimedKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C17824991E1999FA00D9D25C /* CaseCountable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaseCountable.swift; sourceTree = ""; }; C178249F1E19CF9800D9D25C /* GlucoseThresholdTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseThresholdTableViewController.swift; sourceTree = ""; }; C17824A21E19EAB600D9D25C /* recommend_temp_basal_start_very_low_end_high.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_temp_basal_start_very_low_end_high.json; sourceTree = ""; }; C17824A41E1AD4D100D9D25C /* BolusRecommendation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BolusRecommendation.swift; sourceTree = ""; }; C18852E12082AB1A00BECC8C /* RileyLinkKitUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RileyLinkKitUI.framework; path = Carthage/Build/iOS/RileyLinkKitUI.framework; sourceTree = ""; }; C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutDataManager.swift; sourceTree = ""; }; + C1C108C22152F46D00EA5165 /* CGMBLEKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CGMBLEKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_temp_basal_dropping_then_rising.json; sourceTree = ""; }; + C1FB428B217806A300FAB378 /* StateColorPalette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateColorPalette.swift; sourceTree = ""; }; + C1FB428E217921D600FAB378 /* PumpManagerUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpManagerUI.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -911,6 +908,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C168C40621B0D53E00ADE90E /* MinimedKit.framework in Frameworks */, + C168C40821B0D53E00ADE90E /* MinimedKitUI.framework in Frameworks */, 4F7528951DFE1E9B00C322D6 /* LoopUI.framework in Frameworks */, 437AFEE520352591008C4892 /* NotificationCenter.framework in Frameworks */, ); @@ -1142,7 +1141,7 @@ 438172D81F4E9E37003C3328 /* NewPumpEvent.swift */, 43CEE6E51E56AFD400CB9116 /* NightscoutUploader.swift */, 43DACFFF20A2736F000F8529 /* PersistedPumpEvent.swift */, - 43BFF0CA1E466C0900FF19A9 /* StateColorPalette.swift */, + C1FB428B217806A300FAB378 /* StateColorPalette.swift */, 43F41C361D3BF32400C11ED6 /* UIAlertController.swift */, 43BFF0BB1E45C80600FF19A9 /* UIColor+Loop.swift */, 437CEEE31CDE5C0A003C8C80 /* UIImage.swift */, @@ -1204,7 +1203,6 @@ 43A567681C94880B00334FAC /* LoopDataManager.swift */, C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */, 43C094491CACCC73001F6403 /* NotificationManager.swift */, - 43C3B6F620BBCAA30026CAFA /* PumpManager.swift */, 432E73CA1D24B3D6009AD15D /* RemoteDataManager.swift */, 430C1ABC1E5568A80067F1AE /* StatusChartsManager+LoopKit.swift */, 4F70C20F1DE8FAC5006380B7 /* StatusExtensionDataManager.swift */, @@ -1251,7 +1249,7 @@ 4F75288D1DFE1DC600C322D6 /* LoopUI.h */, 4F75288E1DFE1DC600C322D6 /* Info.plist */, 4F2C15941E09BF3C00E160D4 /* HUDView.xib */, - 4F2C15961E09E94E00E160D4 /* Assets.xcassets */, + 4F2C15961E09E94E00E160D4 /* HUDAssets.xcassets */, ); path = LoopUI; sourceTree = ""; @@ -1261,19 +1259,14 @@ children = ( 437CEEBF1CD6FCD8003C8C80 /* BasalRateHUDView.swift */, 43B371851CE583890013C5A6 /* BasalStateView.swift */, - 437CEEBB1CD6DE6A003C8C80 /* BaseHUDView.swift */, - 437CEEC91CD84DB7003C8C80 /* BatteryLevelHUDView.swift */, 4313EDDF1D8A6BF90060FA79 /* ChartContainerView.swift */, 4369618F1F19C86400447E89 /* ChartPointsContextFillLayer.swift */, 4F08DE831E7BB70B006741EA /* ChartPointsScatterDownTrianglesLayer.swift */, 4F08DE841E7BB70B006741EA /* ChartPointsTouchHighlightLayerViewCache.swift */, 4337615E1D52F487004A3647 /* GlucoseHUDView.swift */, 4F2C15921E09BF2C00E160D4 /* HUDView.swift */, - 43E0F0A41E46D1670064F7CE /* LevelHUDView.swift */, - 43FBEDD71D73843700B21F22 /* LevelMaskView.swift */, 437CEEBD1CD6E0CB003C8C80 /* LoopCompletionHUDView.swift */, 438DADC71CDE8F8B007697A5 /* LoopStateView.swift */, - 437CEEC71CD84CBB003C8C80 /* ReservoirVolumeHUDView.swift */, ); path = Views; sourceTree = ""; @@ -1285,7 +1278,6 @@ 4F08DE7D1E7BB6E5006741EA /* ChartAxisValueDoubleUnit.swift */, 4FB76FCD1E8C835D00B39636 /* ChartColorPalette.swift */, 4326BA631F3A44D9007CCAD4 /* ChartLineModel.swift */, - 43BFF0C81E465B0A00FF19A9 /* StateColorPalette.swift */, ); path = Models; sourceTree = ""; @@ -1351,6 +1343,8 @@ 4FF4D0FF1E18374700846527 /* WatchContext.swift */, 4F11D3C120DD80B3006E072C /* WatchHistoricalGlucose.swift */, 4F7E8AC620E2AC0300AEA65E /* WatchPredictedGlucose.swift */, + 43C3B6F620BBCAA30026CAFA /* PumpManager.swift */, + C1FB428E217921D600FAB378 /* PumpManagerUI.swift */, ); path = Models; sourceTree = ""; @@ -1400,6 +1394,9 @@ 968DCD53F724DE56FFE51920 /* Frameworks */ = { isa = PBXGroup; children = ( + C168C40521B0D53E00ADE90E /* MinimedKit.framework */, + C168C40721B0D53E00ADE90E /* MinimedKitUI.framework */, + C1C108C22152F46D00EA5165 /* CGMBLEKitUI.framework */, 434FB6451D68F1CD007B9C70 /* Amplitude.framework */, 4344628420A7A3BE00C4BE6F /* CGMBLEKit.framework */, 438A95A71D8B9B24009D12E1 /* CGMBLEKit.framework */, @@ -1782,7 +1779,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 4F2C15971E09E94E00E160D4 /* Assets.xcassets in Resources */, + 4F2C15971E09E94E00E160D4 /* HUDAssets.xcassets in Resources */, 7D70764A1FE06EE1004AC8EA /* Localizable.strings in Resources */, 7D7076451FE06EE0004AC8EA /* InfoPlist.strings in Resources */, 4F2C15951E09BF3C00E160D4 /* HUDView.xib in Resources */, @@ -1804,7 +1801,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if [ -f $PROJECT_DIR/.gitmodules ]; then\n echo \"Skipping checkout due to presence of .gitmodules file\"\n if [ $ACTION = \"install\" ]; then\n echo \"You're installing: Make sure to keep all submodules up-to-date and run carthage build after changes.\"\n fi\nelse\n echo \"Bootstrapping carthage dependencies\"\n /usr/local/bin/carthage bootstrap --project-directory \"$SRCROOT\" --cache-builds\nfi"; + shellScript = "if [ -f $PROJECT_DIR/.gitmodules ]; then\n echo \"Skipping checkout due to presence of .gitmodules file\"\n if [ $ACTION = \"install\" ]; then\n echo \"You're installing: Make sure to keep all submodules up-to-date and run carthage build after changes.\"\n fi\nelse\n echo \"Bootstrapping carthage dependencies\"\n unset LLVM_TARGET_TRIPLE_SUFFIX\n /usr/local/bin/carthage bootstrap --project-directory \"$SRCROOT\" --cache-builds\nfi\n"; }; 432CF88220D8BCD90066B889 /* Homebrew & Carthage Setup */ = { isa = PBXShellScriptBuildPhase; @@ -1848,7 +1845,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "carthage_frameworks_dir=${SRCROOT}/Carthage/Build/iOS\nif [ -f $PROJECT_DIR/.gitmodules && [ $ACTION != \"install\" ]; then\nfor varname in ${!SCRIPT_INPUT_FILE_*}\ndo\nexport ${varname}=${!varname/$carthage_frameworks_dir/$BUILT_PRODUCTS_DIR}\ndone\nfi\n/usr/local/bin/carthage copy-frameworks\n"; + shellScript = "carthage_frameworks_dir=${SRCROOT}/Carthage/Build/iOS\nif [ -f $PROJECT_DIR/.gitmodules ] && [ $ACTION != \"install\" ]; then\nfor varname in ${!SCRIPT_INPUT_FILE_*}\ndo\necho using $BUILT_PRODUCTS_DIR for ${varname}\nexport ${varname}=${!varname/$carthage_frameworks_dir/$BUILT_PRODUCTS_DIR}\ndone\nfi\n/usr/local/bin/carthage copy-frameworks\n"; }; 43FF3DF620A8EFE800F8E62C /* Copy Frameworks with Carthage */ = { isa = PBXShellScriptBuildPhase; @@ -1868,7 +1865,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "carthage_frameworks_dir=${SRCROOT}/Carthage/Build/iOS\nif [ -f $PROJECT_DIR/.gitmodules && [ $ACTION != \"install\" ]; then\nfor varname in ${!SCRIPT_INPUT_FILE_*}\ndo\nexport ${varname}=${!varname/$carthage_frameworks_dir/$BUILT_PRODUCTS_DIR}\ndone\nfi\n/usr/local/bin/carthage copy-frameworks"; + shellScript = "carthage_frameworks_dir=${SRCROOT}/Carthage/Build/iOS\nif [ -f $PROJECT_DIR/.gitmodules ] && [ $ACTION != \"install\" ]; then\nfor varname in ${!SCRIPT_INPUT_FILE_*}\ndo\nexport ${varname}=${!varname/$carthage_frameworks_dir/$BUILT_PRODUCTS_DIR}\ndone\nfi\n/usr/local/bin/carthage copy-frameworks\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -1887,6 +1884,7 @@ 4F2C15821E074FC600E160D4 /* NSTimeInterval.swift in Sources */, 4311FB9B1F37FE1B00D4C0A7 /* TitleSubtitleTextFieldTableViewCell.swift in Sources */, 430DA58E1D4AEC230097D1CA /* NSBundle.swift in Sources */, + C1FB428F217921D600FAB378 /* PumpManagerUI.swift in Sources */, 43C513191E864C4E001547C7 /* GlucoseRangeSchedule.swift in Sources */, 43A51E1F1EB6D62A000736CC /* CarbAbsorptionViewController.swift in Sources */, 43776F901B8022E90074EA36 /* AppDelegate.swift in Sources */, @@ -1894,7 +1892,6 @@ 430B29932041F5B300BA9F93 /* UserDefaults+Loop.swift in Sources */, 4341F4EB1EDB92AC001C936B /* LogglyService.swift in Sources */, 43CE7CDE1CA8B63E003CC1B0 /* Data.swift in Sources */, - 43BFF0CB1E466C0900FF19A9 /* StateColorPalette.swift in Sources */, 439A7942211F631C0041B75F /* RootNavigationController.swift in Sources */, 4F11D3C020DCBEEC006E072C /* GlucoseBackfillRequestUserInfo.swift in Sources */, 43F5C2DB1B92A5E1003EB13D /* SettingsTableViewController.swift in Sources */, @@ -1943,6 +1940,7 @@ 43DAD00020A2736F000F8529 /* PersistedPumpEvent.swift in Sources */, 438849EC1D29EC34003B3F23 /* AmplitudeService.swift in Sources */, 43E93FB61E469A4000EAB8DB /* NumberFormatter.swift in Sources */, + C1FB428C217806A400FAB378 /* StateColorPalette.swift in Sources */, 4F08DE8F1E7BB871006741EA /* CollectionType+Loop.swift in Sources */, 435400341C9F878D00D5819C /* SetBolusUserInfo.swift in Sources */, 437D9BA31D7BC977007245E8 /* PredictionTableViewController.swift in Sources */, @@ -2076,7 +2074,9 @@ 4FB76FBA1E8C42CE00B39636 /* UIColor.swift in Sources */, 4F2C15831E0757E600E160D4 /* HKUnit.swift in Sources */, 430B29902041F57000BA9F93 /* GlucoseThreshold.swift in Sources */, + C1FB4290217922A100FAB378 /* PumpManagerUI.swift in Sources */, 434B2887206B4F07000EE07B /* WalshInsulinModel.swift in Sources */, + C1FB428D21791D2500FAB378 /* PumpManager.swift in Sources */, 43E93FB51E4675E800EAB8DB /* NumberFormatter.swift in Sources */, 43BFF0CD1E466C8400FF19A9 /* StateColorPalette.swift in Sources */, 430B29912041F57200BA9F93 /* LoopSettings.swift in Sources */, @@ -2096,7 +2096,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 4F20AE621E6B879C00D07A06 /* ReservoirVolumeHUDView.swift in Sources */, 4FB76FB91E8C42B000B39636 /* CollectionType.swift in Sources */, 7D23667D21250C7E0028B67D /* LocalizedString.swift in Sources */, 4FF4D0F91E17268800846527 /* IdentifiableClass.swift in Sources */, @@ -2116,18 +2115,13 @@ 43F1C31A1F5DC87700395429 /* ChartPoint.swift in Sources */, 4F7528A11DFE200B00C322D6 /* BasalStateView.swift in Sources */, 4F20AE631E6B87B100D07A06 /* ChartContainerView.swift in Sources */, - 4F7528A21DFE200B00C322D6 /* LevelMaskView.swift in Sources */, 43BFF0C61E465A4400FF19A9 /* UIColor+HIG.swift in Sources */, 4F7528A01DFE1F9D00C322D6 /* LoopStateView.swift in Sources */, 4FB76FCE1E8C835D00B39636 /* ChartColorPalette.swift in Sources */, 4FB76FB51E8C41E200B39636 /* ChartPointsScatterDownTrianglesLayer.swift in Sources */, 4F75289A1DFE1F6000C322D6 /* BasalRateHUDView.swift in Sources */, - 4F75289B1DFE1F6000C322D6 /* BatteryLevelHUDView.swift in Sources */, 4F75289C1DFE1F6000C322D6 /* GlucoseHUDView.swift in Sources */, 4FB76FB81E8C429D00B39636 /* CGPoint.swift in Sources */, - 4F75289D1DFE1F6000C322D6 /* BaseHUDView.swift in Sources */, - 43BFF0C91E465B0A00FF19A9 /* StateColorPalette.swift in Sources */, - 43E0F0A51E46D1670064F7CE /* LevelHUDView.swift in Sources */, 4F75289E1DFE1F6000C322D6 /* LoopCompletionHUDView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Loop/Extensions/NewPumpEvent.swift b/Loop/Extensions/NewPumpEvent.swift index 52432cf229..af0e9fe9f3 100644 --- a/Loop/Extensions/NewPumpEvent.swift +++ b/Loop/Extensions/NewPumpEvent.swift @@ -10,28 +10,14 @@ import LoopKit extension NewPumpEvent { - /* - It takes a MM pump about 40s to deliver 1 Unit while bolusing - See: http://www.healthline.com/diabetesmine/ask-dmine-speed-insulin-pumps#3 - */ - private static let deliveryUnitsPerMinute = 1.5 - /// Constructs a pump event placeholder representing a bolus just enacted. /// /// - Parameters: /// - units: The units of insulin requested /// - date: The date the bolus was enacted - static func enactedBolus(units: Double, at date: Date) -> NewPumpEvent { - let dose = DoseEntry( - type: .bolus, - startDate: date, - endDate: date.addingTimeInterval(.minutes(units / NewPumpEvent.deliveryUnitsPerMinute)), - value: units, - unit: .units - ) - + static func enactedBolus(dose: DoseEntry) -> NewPumpEvent { return self.init( - date: date, + date: dose.startDate, dose: dose, isMutable: true, raw: Data(), // This can be empty, as mutable events aren't persisted diff --git a/Loop/Extensions/StateColorPalette.swift b/Loop/Extensions/StateColorPalette.swift index c4e67ba9c4..e6f18b436a 100644 --- a/Loop/Extensions/StateColorPalette.swift +++ b/Loop/Extensions/StateColorPalette.swift @@ -6,6 +6,7 @@ // import LoopUI +import LoopKitUI extension StateColorPalette { static let loopStatus = StateColorPalette(unknown: .unknownColor, normal: .freshColor, warning: .agingColor, error: .staleColor) diff --git a/Loop/Extensions/UIAlertController.swift b/Loop/Extensions/UIAlertController.swift index 0e352f9794..b841b08e4b 100644 --- a/Loop/Extensions/UIAlertController.swift +++ b/Loop/Extensions/UIAlertController.swift @@ -18,7 +18,7 @@ extension UIAlertController { - parameter handler: A closure to execute when the sheet is dismissed after selection. The closure has a single argument: - endDate: The date at which the user selected the workout to end */ - convenience init(workoutDurationSelectionHandler handler: @escaping (_ endDate: Date) -> Void) { + internal convenience init(workoutDurationSelectionHandler handler: @escaping (_ endDate: Date) -> Void) { self.init( title: NSLocalizedString("Use Workout Glucose Targets", comment: "The title of the alert controller used to select a duration for workout targets"), message: nil, @@ -51,7 +51,7 @@ extension UIAlertController { /// - cgmManagers: An array of PumpManagers /// - selectionHandler: A closure to execute when a manager is selected /// - manager: The selected manager - convenience init(pumpManagers: [PumpManagerUI.Type], selectionHandler: @escaping (_ manager: PumpManagerUI.Type) -> Void) { + internal convenience init(pumpManagers: [PumpManagerUI.Type], selectionHandler: @escaping (_ manager: PumpManagerUI.Type) -> Void) { self.init( title: NSLocalizedString("Add Pump", comment: "Action sheet title selecting Pump"), message: nil, @@ -77,7 +77,7 @@ extension UIAlertController { /// - selectionHandler: A closure to execute when either a new CGMManager or the current PumpManager is selected /// - cgmManager: The selected CGMManager type /// - pumpManager: The selected PumpManager instance - convenience init(cgmManagers: [CGMManagerUI.Type], pumpManager: CGMManager?, selectionHandler: @escaping (_ cgmManager: CGMManagerUI.Type?, _ pumpManager: CGMManager?) -> Void) { + internal convenience init(cgmManagers: [CGMManagerUI.Type], pumpManager: CGMManager?, selectionHandler: @escaping (_ cgmManager: CGMManagerUI.Type?, _ pumpManager: CGMManager?) -> Void) { self.init( title: NSLocalizedString("Add CGM", comment: "Action sheet title selecting CGM"), message: nil, @@ -105,7 +105,7 @@ extension UIAlertController { } } - convenience init(deleteCGMManagerHandler handler: @escaping (_ isDeleted: Bool) -> Void) { + internal convenience init(deleteCGMManagerHandler handler: @escaping (_ isDeleted: Bool) -> Void) { self.init( title: nil, message: NSLocalizedString("Are you sure you want to delete this CGM?", comment: "Confirmation message for deleting a CGM"), @@ -125,7 +125,7 @@ extension UIAlertController { } } - func addCancelAction(handler: ((UIAlertAction) -> Void)? = nil) { + internal func addCancelAction(handler: ((UIAlertAction) -> Void)? = nil) { let cancel = NSLocalizedString("Cancel", comment: "The title of the cancel action in an action sheet") addAction(UIAlertAction(title: cancel, style: .cancel, handler: handler)) } diff --git a/Loop/Extensions/UIImage.swift b/Loop/Extensions/UIImage.swift index 74103bfea5..4508689393 100644 --- a/Loop/Extensions/UIImage.swift +++ b/Loop/Extensions/UIImage.swift @@ -31,14 +31,6 @@ extension UIImage { return suffix } - static func batteryHUDImageWithLevel(_ level: Double?) -> UIImage? { - return UIImage(named: "battery_\(imageSuffixForLevel(level))") - } - - static func reservoirHUDImageWithLevel(_ level: Double?) -> UIImage? { - return UIImage(named: "reservoir_\(imageSuffixForLevel(level))") - } - static func preMealImage(selected: Bool) -> UIImage? { return UIImage(named: selected ? "Pre-Meal Selected" : "Pre-Meal") } diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index b4d83d3e35..b7cb3408a5 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -10,23 +10,30 @@ import HealthKit import LoopKit import LoopKitUI - final class DeviceDataManager { private let queue = DispatchQueue(label: "com.loopkit.DeviceManagerQueue", qos: .utility) var pumpManager: PumpManagerUI? { + didSet { + // If the current CGMManager is a PumpManager, we clear it out. if cgmManager is PumpManagerUI { cgmManager = nil } + pumpManagerHUDProvider = pumpManager?.hudProvider() + setupPump() + + NotificationCenter.default.post(name: .PumpManagerChanged, object: self, userInfo: nil) UserDefaults.appGroup.pumpManager = pumpManager } } + + var pumpManagerHUDProvider: HUDProvider? let logger = DiagnosticLogger.shared @@ -58,6 +65,43 @@ final class DeviceDataManager { } } + private let lockedPumpManagerStatus: Locked = Locked(nil) + + static let batteryReplacementDetectionThreshold = 0.5 + + var pumpManagerStatus: PumpManagerStatus? { + get { + return lockedPumpManagerStatus.value + } + set { + let oldValue = lockedPumpManagerStatus.value + lockedPumpManagerStatus.value = newValue + + if let status = newValue { + + loopManager.doseStore.device = status.device + + if let newBatteryValue = status.pumpBatteryChargeRemaining { + if newBatteryValue == 0 { + NotificationManager.sendPumpBatteryLowNotification() + } else { + NotificationManager.clearPumpBatteryLowNotification() + } + + if let oldBatteryValue = oldValue?.pumpBatteryChargeRemaining, newBatteryValue - oldBatteryValue >= DeviceDataManager.batteryReplacementDetectionThreshold { + AnalyticsManager.shared.pumpBatteryWasReplaced() + } + } + + // Update the pump-schedule based settings + loopManager.setScheduleTimeZone(status.timeZone) + + } else { + loopManager.doseStore.device = nil + } + } + } + /// TODO: Isolate to queue private func setupCGM() { cgmManager?.cgmManagerDelegate = self @@ -68,6 +112,12 @@ final class DeviceDataManager { private func setupPump() { pumpManager?.pumpManagerDelegate = self + + if let pumpManager = pumpManager { + self.pumpManagerStatus = pumpManager.status + self.loopManager.doseStore.device = self.pumpManagerStatus?.device + self.pumpManagerHUDProvider = pumpManager.hudProvider() + } // Proliferate PumpModel preferences to DoseStore if let pumpRecordsBasalProfileStartEvents = pumpManager?.pumpRecordsBasalProfileStartEvents { @@ -91,12 +141,13 @@ final class DeviceDataManager { init() { pumpManager = UserDefaults.appGroup.pumpManager as? PumpManagerUI + if let cgmManager = UserDefaults.appGroup.cgmManager { self.cgmManager = cgmManager } else if UserDefaults.appGroup.isCGMManagerValidPumpManager { self.cgmManager = pumpManager as? CGMManager } - + remoteDataManager.delegate = self statusExtensionManager = StatusExtensionDataManager(deviceDataManager: self) loopManager = LoopDataManager( @@ -167,28 +218,13 @@ extension DeviceDataManager: CGMManagerDelegate { extension DeviceDataManager: PumpManagerDelegate { + func pumpManager(_ pumpManager: PumpManager, didAdjustPumpClockBy adjustment: TimeInterval) { log.default("PumpManager:\(type(of: pumpManager)) did adjust pump block by \(adjustment)s") AnalyticsManager.shared.pumpTimeDidDrift(adjustment) } - func pumpManagerDidUpdatePumpBatteryChargeRemaining(_ pumpManager: PumpManager, oldValue: Double?) { - log.default("PumpManager:\(type(of: pumpManager)) did update pump battery from \(String(describing: oldValue))") - - if let newValue = pumpManager.pumpBatteryChargeRemaining { - if newValue == 0 { - NotificationManager.sendPumpBatteryLowNotification() - } else { - NotificationManager.clearPumpBatteryLowNotification() - } - - if let oldValue = oldValue, newValue - oldValue >= 0.5 { - AnalyticsManager.shared.pumpBatteryWasReplaced() - } - } - } - func pumpManagerDidUpdateState(_ pumpManager: PumpManager) { log.default("PumpManager:\(type(of: pumpManager)) did update state") @@ -214,13 +250,8 @@ extension DeviceDataManager: PumpManagerDelegate { return !(cgmManager?.providesBLEHeartbeat == true) } - func pumpManager(_ pumpManager: PumpManager, didUpdateStatus status: PumpManagerStatus) { - log.default("PumpManager:\(type(of: pumpManager)) did update status") - - loopManager.doseStore.device = status.device - // Update the pump-schedule based settings - loopManager.setScheduleTimeZone(status.timeZone) - nightscoutDataManager.upload(pumpStatus: status) + func pumpManager(_ pumpManager: PumpManager, didUpdate status: PumpManagerStatus) { + self.pumpManagerStatus = status } func pumpManagerWillDeactivate(_ pumpManager: PumpManager) { @@ -291,7 +322,7 @@ extension DeviceDataManager: PumpManagerDelegate { } } } - + func pumpManagerRecommendsLoop(_ pumpManager: PumpManager) { log.default("PumpManager:\(type(of: pumpManager)) recommends loop") loopManager.loop() @@ -304,6 +335,7 @@ extension DeviceDataManager: PumpManagerDelegate { func startDateToFilterNewReservoirEvents(for manager: PumpManager) -> Date { return loopManager.doseStore.lastReservoirValue?.startDate ?? .distantPast } + } @@ -338,15 +370,16 @@ extension DeviceDataManager { return } - pumpManager.enactBolus(units: units, at: startDate, willRequest: { (units, date) in - self.loopManager.addRequestedBolus(units: units, at: date, completion: nil) - }) { (error) in - if let error = error { + pumpManager.enactBolus(units: units, at: startDate, willRequest: { (dose) in + self.loopManager.addRequestedBolus(dose, completion: nil) + }) { (result) in + switch result { + case .failure(let error): self.log.error(error) NotificationManager.sendBolusFailureNotification(for: error, units: units, at: startDate) completion(error) - } else { - self.loopManager.addConfirmedBolus(units: units, at: Date()) { + case .success(let dose): + self.loopManager.addConfirmedBolus(dose) { completion(nil) } } @@ -389,8 +422,8 @@ extension DeviceDataManager: CustomDebugStringConvertible { Bundle.main.localizedNameAndVersion, "", "## DeviceDataManager", - "launchDate: \(launchDate)", - "lastError: \(String(describing: lastError))", + "* launchDate: \(launchDate)", + "* lastError: \(String(describing: lastError))", "", cgmManager != nil ? String(reflecting: cgmManager!) : "cgmManager: nil", "", @@ -402,3 +435,8 @@ extension DeviceDataManager: CustomDebugStringConvertible { ].joined(separator: "\n") } } + +extension Notification.Name { + static let PumpManagerChanged = Notification.Name(rawValue: "com.loopKit.notification.PumpManagerChanged") +} + diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 9a9347c292..8e2c60c512 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -172,7 +172,7 @@ final class LoopDataManager { fileprivate var carbsOnBoard: CarbValue? fileprivate var lastTempBasal: DoseEntry? - fileprivate var lastRequestedBolus: (units: Double, date: Date)? + fileprivate var lastRequestedBolus: DoseEntry? /// The last date at which a loop completed, from prediction to dose (if dosing is enabled) var lastLoopCompleted: Date? { @@ -451,11 +451,10 @@ extension LoopDataManager { /// Adds a bolus requested of the pump, but not confirmed. /// /// - Parameters: - /// - units: The bolus amount, in units - /// - date: The date the bolus was requested - func addRequestedBolus(units: Double, at date: Date, completion: (() -> Void)?) { + /// - dose: The DoseEntry representing the requested bolus + func addRequestedBolus(_ dose: DoseEntry, completion: (() -> Void)?) { dataAccessQueue.async { - self.lastRequestedBolus = (units: units, date: date) + self.lastRequestedBolus = dose self.notify(forChange: .bolus) completion?() @@ -465,10 +464,9 @@ extension LoopDataManager { /// Adds a bolus enacted by the pump, but not fully delivered. /// /// - Parameters: - /// - units: The bolus amount, in units - /// - date: The date the bolus was enacted - func addConfirmedBolus(units: Double, at date: Date, completion: (() -> Void)?) { - self.doseStore.addPendingPumpEvent(.enactedBolus(units: units, at: date)) { + /// - dose: The DoseEntry representing the confirmed bolus + func addConfirmedBolus(_ dose: DoseEntry, completion: (() -> Void)?) { + self.doseStore.addPendingPumpEvent(.enactedBolus(dose: dose)) { self.dataAccessQueue.async { self.lastRequestedBolus = nil self.insulinEffect = nil @@ -491,7 +489,8 @@ extension LoopDataManager { if error == nil { self.insulinEffect = nil // Expire any bolus values now represented in the insulin data - if let bolusDate = self.lastRequestedBolus?.date, bolusDate.timeIntervalSinceNow < TimeInterval(minutes: -5) { + // TODO: Ask pumpManager if dose represented in data + if let bolusEndDate = self.lastRequestedBolus?.endDate, bolusEndDate < Date() { self.lastRequestedBolus = nil } } @@ -519,7 +518,8 @@ extension LoopDataManager { self.dataAccessQueue.async { self.insulinEffect = nil // Expire any bolus values now represented in the insulin data - if areStoredValuesContinuous, let bolusDate = self.lastRequestedBolus?.date, bolusDate.timeIntervalSinceNow < TimeInterval(minutes: -5) { + // TODO: Ask pumpManager if dose represented in data + if areStoredValuesContinuous, let bolusEndDate = self.lastRequestedBolus?.endDate, bolusEndDate < Date() { self.lastRequestedBolus = nil } @@ -841,11 +841,8 @@ extension LoopDataManager { throw LoopError.missingDataError(.glucose) } - guard let pumpStatusDate = doseStore.lastReservoirValue?.startDate else { - self.predictedGlucose = nil - throw LoopError.missingDataError(.reservoir) - } - + let pumpStatusDate = doseStore.lastAddedPumpData + let startDate = Date() guard startDate.timeIntervalSince(glucose.startDate) <= settings.recencyInterval else { @@ -1179,3 +1176,9 @@ protocol LoopDataManagerDelegate: class { /// - result: The enacted basal func loopDataManager(_ manager: LoopDataManager, didRecommendBasalChange basal: (recommendation: TempBasalRecommendation, date: Date), completion: @escaping (_ result: Result) -> Void) -> Void } + +extension DoseStore { + var lastAddedPumpData: Date { + return max(lastReservoirValue?.startDate ?? .distantPast, lastAddedPumpEvents) + } +} diff --git a/Loop/Managers/NightscoutDataManager.swift b/Loop/Managers/NightscoutDataManager.swift index 09c2a7483b..9e74600a4f 100644 --- a/Loop/Managers/NightscoutDataManager.swift +++ b/Loop/Managers/NightscoutDataManager.swift @@ -129,8 +129,43 @@ final class NightscoutDataManager { let loopVersion = Bundle.main.shortVersionString let loopStatus = LoopStatus(name: loopName, version: loopVersion, timestamp: statusTime, iob: iob, cob: cob, predicted: predicted, recommendedTempBasal: recommended, recommendedBolus: recommendedBolus, enacted: loopEnacted, failureReason: loopError) + - upload(pumpStatus: nil, loopStatus: loopStatus, deviceName: nil, firmwareVersion: nil, lastValidFrequency: nil, lastTuned: nil, uploaderStatus: getUploaderStatus()) + + let pumpStatus: NightscoutUploadKit.PumpStatus? + + if let pumpManagerStatus = deviceManager.pumpManagerStatus + { + + let battery: BatteryStatus? + + if let chargeRemaining = pumpManagerStatus.pumpBatteryChargeRemaining { + battery = BatteryStatus(percent: Int(round(chargeRemaining * 100)), voltage: nil, status: nil) + } else { + battery = nil + } + + let bolusing: Bool + if case .inProgress = pumpManagerStatus.bolusState { + bolusing = true + } else { + bolusing = false + } + + pumpStatus = NightscoutUploadKit.PumpStatus( + clock: Date(), + pumpID: pumpManagerStatus.device.localIdentifier ?? "Unknown", + iob: nil, + battery: battery, + suspended: pumpManagerStatus.basalDeliveryState == .suspended, + bolusing: bolusing, + reservoir: deviceManager.loopManager.doseStore.lastReservoirValue?.unitVolume, + secondsFromGMT: pumpManagerStatus.timeZone.secondsFromGMT()) + } else { + pumpStatus = nil + } + + upload(pumpStatus: pumpStatus, loopStatus: loopStatus, deviceName: nil, firmwareVersion: nil, uploaderStatus: getUploaderStatus()) } @@ -147,21 +182,11 @@ final class NightscoutDataManager { return UploaderStatus(name: uploaderDevice.name, timestamp: Date(), battery: battery) } - func upload(pumpStatus: PumpManagerStatus) { - upload( - pumpStatus: pumpStatus.pumpStatus, - deviceName: pumpStatus.device?.name, - firmwareVersion: pumpStatus.device?.firmwareVersion, - lastValidFrequency: pumpStatus.lastValidFrequency, - lastTuned: pumpStatus.lastTuned - ) - } - - func upload(pumpStatus: NightscoutUploadKit.PumpStatus?, deviceName: String?, firmwareVersion: String?, lastValidFrequency: Measurement?, lastTuned: Date?) { - upload(pumpStatus: pumpStatus, loopStatus: nil, deviceName: deviceName, firmwareVersion: firmwareVersion, lastValidFrequency: lastValidFrequency, lastTuned: lastTuned, uploaderStatus: nil) + func upload(pumpStatus: NightscoutUploadKit.PumpStatus?, deviceName: String?, firmwareVersion: String?) { + upload(pumpStatus: pumpStatus, loopStatus: nil, deviceName: deviceName, firmwareVersion: firmwareVersion, uploaderStatus: nil) } - private func upload(pumpStatus: NightscoutUploadKit.PumpStatus?, loopStatus: LoopStatus?, deviceName: String?, firmwareVersion: String?, lastValidFrequency: Measurement?, lastTuned: Date?, uploaderStatus: UploaderStatus?) { + private func upload(pumpStatus: NightscoutUploadKit.PumpStatus?, loopStatus: LoopStatus?, deviceName: String?, firmwareVersion: String?, uploaderStatus: UploaderStatus?) { guard let uploader = deviceManager.remoteDataManager.nightscoutService.uploader else { return @@ -176,22 +201,8 @@ final class NightscoutDataManager { let uploaderDevice = UIDevice.current - var radioAdapter: NightscoutUploadKit.RadioAdapter? = nil - - if let firmwareVersion = firmwareVersion { - radioAdapter = NightscoutUploadKit.RadioAdapter( - hardware: "RileyLink", - frequency: lastValidFrequency?.value, - name: deviceName ?? "Unknown", - lastTuned: lastTuned, - firmwareVersion: firmwareVersion, - RSSI: nil, // TODO: device.RSSI, - pumpRSSI: nil // TODO: device.pumpRSSI - ) - } - // Build DeviceStatus - let deviceStatus = DeviceStatus(device: "loop://\(uploaderDevice.name)", timestamp: Date(), pumpStatus: pumpStatus, uploaderStatus: uploaderStatus, loopStatus: loopStatus, radioAdapter: radioAdapter) + let deviceStatus = DeviceStatus(device: "loop://\(uploaderDevice.name)", timestamp: Date(), pumpStatus: pumpStatus, uploaderStatus: uploaderStatus, loopStatus: loopStatus, radioAdapter: nil) self.lastDeviceStatusUpload = Date() uploader.uploadDeviceStatus(deviceStatus) @@ -231,35 +242,3 @@ final class NightscoutDataManager { } } - -private extension PumpManagerStatus { - var batteryStatus: NightscoutUploadKit.BatteryStatus? { - return NightscoutUploadKit.BatteryStatus( - percent: battery?.percent != nil ? Int(battery!.percent! * 100) : nil, - voltage: battery?.voltage?.converted(to: .volts).value, - status: { - switch battery?.state { - case .normal?: - return .normal - case .low?: - return .low - case .none: - return nil - } - }() - ) - } - - var pumpStatus: NightscoutUploadKit.PumpStatus { - return PumpStatus( - clock: date, - pumpID: device?.localIdentifier ?? "", - iob: nil, - battery: batteryStatus, - suspended: isSuspended, - bolusing: isBolusing, - reservoir: remainingReservoir?.doubleValue(for: .internationalUnit()), - secondsFromGMT: timeZone.secondsFromGMT() - ) - } -} diff --git a/Loop/Managers/StatusExtensionDataManager.swift b/Loop/Managers/StatusExtensionDataManager.swift index ae5a1c13f8..f22b350059 100644 --- a/Loop/Managers/StatusExtensionDataManager.swift +++ b/Loop/Managers/StatusExtensionDataManager.swift @@ -18,6 +18,7 @@ final class StatusExtensionDataManager { self.deviceManager = deviceDataManager NotificationCenter.default.addObserver(self, selector: #selector(update(_:)), name: .LoopDataUpdated, object: deviceDataManager.loopManager) + NotificationCenter.default.addObserver(self, selector: #selector(update(_:)), name: .PumpManagerChanged, object: nil) } fileprivate var defaults: UserDefaults? { @@ -98,8 +99,9 @@ final class StatusExtensionDataManager { context.netBasal = NetBasalContext(rate: netBasal.rate, percentage: netBasal.percent, start: netBasal.start, end: netBasal.end) } - - context.batteryPercentage = dataManager.pumpManager?.pumpBatteryChargeRemaining + + + context.batteryPercentage = dataManager.pumpManager?.status.pumpBatteryChargeRemaining context.reservoirCapacity = dataManager.pumpManager?.pumpReservoirCapacity if let sensorInfo = dataManager.cgmManager?.sensorState { @@ -110,6 +112,10 @@ final class StatusExtensionDataManager { isLocal: sensorInfo.isLocal ) } + + if let pumpManagerHUDProvider = dataManager.pumpManagerHUDProvider { + context.pumpManagerHUDViewsContext = PumpManagerHUDViewsContext(pumpManagerHUDViewsRawValue: PumpManagerHUDViewsRawValueFromHudProvider(pumpManagerHUDProvider)) + } completionHandler(context) } diff --git a/Loop/View Controllers/BolusViewController.swift b/Loop/View Controllers/BolusViewController.swift index 793bf5b747..3a58b7f3be 100644 --- a/Loop/View Controllers/BolusViewController.swift +++ b/Loop/View Controllers/BolusViewController.swift @@ -182,7 +182,11 @@ final class BolusViewController: UITableViewController, IdentifiableClass, UITex } guard bolus <= maxBolus else { - presentAlertController(withTitle: NSLocalizedString("Exceeds Maximum Bolus", comment: "The title of the alert describing a maximum bolus validation error"), message: String(format: NSLocalizedString("The maximum bolus amount is %@ Units", comment: "Body of the alert describing a maximum bolus validation error. (1: The localized max bolus value)"), bolusUnitsFormatter.string(from: maxBolus) ?? "")) + let alert = UIAlertController( + title: NSLocalizedString("Exceeds Maximum Bolus", comment: "The title of the alert describing a maximum bolus validation error"), + message: String(format: NSLocalizedString("The maximum bolus amount is %@ Units", comment: "Body of the alert describing a maximum bolus validation error. (1: The localized max bolus value)"), bolusUnitsFormatter.string(from: maxBolus) ?? ""), + preferredStyle: .alert) + present(alert, animated: true) return } diff --git a/Loop/View Controllers/CarbAbsorptionViewController.swift b/Loop/View Controllers/CarbAbsorptionViewController.swift index b6398ab177..afab2f835f 100644 --- a/Loop/View Controllers/CarbAbsorptionViewController.swift +++ b/Loop/View Controllers/CarbAbsorptionViewController.swift @@ -436,7 +436,7 @@ final class CarbAbsorptionViewController: ChartsTableViewController, Identifiabl break // Notification will trigger update case .failure(let error): self.refreshContext.update(with: .carbs) - self.presentAlertController(with: error) + self.present(UIAlertController(with: error), animated: true) } } } @@ -535,7 +535,7 @@ final class CarbAbsorptionViewController: ChartsTableViewController, Identifiabl case .failure(let error): // Ignore bolus wizard errors if error is CarbStore.CarbStoreError { - self.presentAlertController(with: error) + self.present(UIAlertController(with: error), animated: true) } } } diff --git a/Loop/View Controllers/ChartsTableViewController.swift b/Loop/View Controllers/ChartsTableViewController.swift index 3e519b2b0b..3642a25628 100644 --- a/Loop/View Controllers/ChartsTableViewController.swift +++ b/Loop/View Controllers/ChartsTableViewController.swift @@ -94,6 +94,8 @@ class ChartsTableViewController: UITableViewController, UIGestureRecognizerDeleg } ] + active = UIApplication.shared.applicationState == .active + let gestureRecognizer = UILongPressGestureRecognizer() gestureRecognizer.delegate = self gestureRecognizer.minimumPressDuration = 0.1 @@ -166,11 +168,8 @@ class ChartsTableViewController: UITableViewController, UIGestureRecognizerDeleg // References to registered notification center observers var notificationObservers: [Any] = [] - var active: Bool { - get { - return UIApplication.shared.applicationState == .active - } - set { + var active: Bool = false { + didSet { log.debug("[reloadData] for app change to active: %d", active) reloadData() } diff --git a/Loop/View Controllers/SettingsTableViewController.swift b/Loop/View Controllers/SettingsTableViewController.swift index 48b76fca8d..d60733f335 100644 --- a/Loop/View Controllers/SettingsTableViewController.swift +++ b/Loop/View Controllers/SettingsTableViewController.swift @@ -109,6 +109,16 @@ final class SettingsTableViewController: UITableViewController { } } + func configuredSetupViewController(for pumpManager: PumpManagerUI.Type) -> (UIViewController & PumpManagerSetupViewController & CompletionNotifying) { + var setupViewController = pumpManager.setupViewController() + setupViewController.setupDelegate = self + setupViewController.completionDelegate = self + setupViewController.basalSchedule = dataManager.loopManager.basalRateSchedule + setupViewController.maxBolusUnits = dataManager.loopManager.settings.maximumBolus + setupViewController.maxBasalRateUnitsPerHour = dataManager.loopManager.settings.maximumBasalRatePerHour + return setupViewController + } + // MARK: - UITableViewDataSource override func numberOfSections(in tableView: UITableView) -> Int { @@ -122,7 +132,7 @@ final class SettingsTableViewController: UITableViewController { case .pump: return PumpRow.count case .cgm: - return 1 + return CGMRow.count case .configuration: return ConfigurationRow.count case .services: @@ -160,7 +170,6 @@ final class SettingsTableViewController: UITableViewController { cell.imageView?.image = pumpManager.smallImage cell.textLabel?.text = pumpManager.localizedTitle cell.detailTextLabel?.text = nil - cell.accessoryType = .disclosureIndicator return cell } else { let cell = tableView.dequeueReusableCell(withIdentifier: TextButtonTableViewCell.className, for: indexPath) @@ -179,9 +188,6 @@ final class SettingsTableViewController: UITableViewController { } cell.textLabel?.text = cgmManager.localizedTitle cell.detailTextLabel?.text = nil - if cgmManagerUI != nil { - cell.accessoryType = .disclosureIndicator - } return cell } else { let cell = tableView.dequeueReusableCell(withIdentifier: TextButtonTableViewCell.className, for: indexPath) @@ -319,8 +325,9 @@ final class SettingsTableViewController: UITableViewController { case .pump: switch PumpRow(rawValue: indexPath.row)! { case .pumpSettings: - if let settings = dataManager.pumpManager?.settingsViewController() { - show(settings, sender: sender) + if var settings = dataManager.pumpManager?.settingsViewController() { + settings.completionDelegate = self + present(settings, animated: true) } else { // Add new pump let pumpManagers = allPumpManagers.compactMap({ $0 as? PumpManagerUI.Type }) @@ -328,15 +335,15 @@ final class SettingsTableViewController: UITableViewController { switch pumpManagers.count { case 1: if let PumpManagerType = pumpManagers.first { - var setupViewController = PumpManagerType.setupViewController() - setupViewController.setupDelegate = self + let setupViewController = configuredSetupViewController(for: PumpManagerType) present(setupViewController, animated: true, completion: nil) } case let x where x > 1: let alert = UIAlertController(pumpManagers: pumpManagers) { [weak self] (manager) in - var setupViewController = manager.setupViewController() - setupViewController.setupDelegate = self - self?.present(setupViewController, animated: true, completion: nil) + if let self = self { + let setupViewController = self.configuredSetupViewController(for: manager) + self.present(setupViewController, animated: true, completion: nil) + } } alert.addCancelAction { (_) in @@ -352,7 +359,9 @@ final class SettingsTableViewController: UITableViewController { case .cgm: if let cgmManager = dataManager.cgmManager as? CGMManagerUI { if let unit = dataManager.loopManager.glucoseStore.preferredUnit { - show(cgmManager.settingsViewController(for: unit), sender: sender) + var settings = cgmManager.settingsViewController(for: unit) + settings.completionDelegate = self + present(settings, animated: true) } } else if dataManager.cgmManager is PumpManagerUI { // The pump manager is providing glucose, but allow reverting the CGM @@ -406,7 +415,7 @@ final class SettingsTableViewController: UITableViewController { scheduleVC.timeZone = schedule.timeZone scheduleVC.scheduleItems = schedule.items scheduleVC.unit = schedule.unit - } else if let timeZone = dataManager.pumpManager?.pumpTimeZone { + } else if let timeZone = dataManager.pumpManager?.status.timeZone { scheduleVC.timeZone = timeZone } @@ -424,7 +433,7 @@ final class SettingsTableViewController: UITableViewController { show(scheduleVC, sender: sender) } else { - if let timeZone = dataManager.pumpManager?.pumpTimeZone { + if let timeZone = dataManager.pumpManager?.status.timeZone { scheduleVC.timeZone = timeZone } @@ -447,7 +456,7 @@ final class SettingsTableViewController: UITableViewController { show(scheduleVC, sender: sender) } else { - if let timeZone = dataManager.pumpManager?.pumpTimeZone { + if let timeZone = dataManager.pumpManager?.status.timeZone { scheduleVC.timeZone = timeZone } @@ -489,7 +498,7 @@ final class SettingsTableViewController: UITableViewController { if let profile = dataManager.loopManager.basalRateSchedule { vc.scheduleItems = profile.items vc.timeZone = profile.timeZone - } else if let timeZone = dataManager.pumpManager?.pumpTimeZone { + } else if let timeZone = dataManager.pumpManager?.status.timeZone { vc.timeZone = timeZone } @@ -550,7 +559,7 @@ final class SettingsTableViewController: UITableViewController { case .loop: break case .pump: - tableView.reloadRows(at: [indexPath], with: .fade) + tableView.reloadSections([Section.pump.rawValue], with: .fade) tableView.reloadRows(at: [[Section.cgm.rawValue, CGMRow.cgmSettings.rawValue]], with: .fade) case .cgm: tableView.reloadRows(at: [indexPath], with: .fade) @@ -568,6 +577,14 @@ final class SettingsTableViewController: UITableViewController { } } +extension SettingsTableViewController: CompletionDelegate { + func completionNotifyingDidComplete(_ object: CompletionNotifying) { + if let vc = object as? UIViewController { + vc.dismiss(animated: true, completion: nil) + } + } +} + extension SettingsTableViewController: PumpManagerSetupViewControllerDelegate { func pumpManagerSetupViewController(_ pumpManagerSetupViewController: PumpManagerSetupViewController, didSetUpPumpManager pumpManager: PumpManagerUI) { @@ -588,13 +605,6 @@ extension SettingsTableViewController: PumpManagerSetupViewControllerDelegate { dataManager.loopManager.settings.maximumBolus = maxBolusUnits tableView.reloadRows(at: [[Section.configuration.rawValue, ConfigurationRow.deliveryLimits.rawValue]], with: .none) } - - show(pumpManager.settingsViewController(), sender: nil) - dismiss(animated: true, completion: nil) - } - - func pumpManagerSetupViewControllerDidCancel(_ pumpManagerSetupViewController: PumpManagerSetupViewController) { - dismiss(animated: true, completion: nil) } } @@ -603,6 +613,7 @@ extension SettingsTableViewController: CGMManagerSetupViewControllerDelegate { fileprivate func setupCGMManager(_ CGMManagerType: CGMManagerUI.Type, indexPath: IndexPath) { if var setupViewController = CGMManagerType.setupViewController() { setupViewController.setupDelegate = self + setupViewController.completionDelegate = self present(setupViewController, animated: true, completion: nil) } else { completeCGMManagerSetup(CGMManagerType.init(rawState: [:]), indexPath: indexPath) @@ -618,11 +629,6 @@ extension SettingsTableViewController: CGMManagerSetupViewControllerDelegate { func cgmManagerSetupViewController(_ cgmManagerSetupViewController: CGMManagerSetupViewController, didSetUpCGMManager cgmManager: CGMManagerUI) { dataManager.cgmManager = cgmManager tableView.selectRow(at: IndexPath(row: CGMRow.cgmSettings.rawValue, section: Section.cgm.rawValue), animated: false, scrollPosition: .none) - show(cgmManager.settingsViewController(for: dataManager.loopManager.glucoseStore.preferredUnit ?? .milligramsPerDeciliter), sender: nil) - dismiss(animated: true, completion: nil) - } - - func cgmManagerSetupViewControllerDidCancel(_ cgmManagerSetupViewController: CGMManagerSetupViewController) { dismiss(animated: true, completion: nil) } } diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index e6730f4cd9..ed29d842c5 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -25,7 +25,6 @@ private enum BolusState { case enacting } - private extension RefreshContext { static let all: Set = [.status, .glucose, .insulin, .carbs, .targets] } @@ -42,6 +41,11 @@ final class StatusTableViewController: ChartsTableViewController { min: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 100), max: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 175) ) + + if let pumpManager = deviceManager.pumpManager { + self.basalDeliveryState = pumpManager.status.basalDeliveryState + pumpManager.addStatusObserver(self) + } let notificationCenter = NotificationCenter.default @@ -72,7 +76,13 @@ final class StatusTableViewController: ChartsTableViewController { DispatchQueue.main.async { self?.hudView?.loopCompletionHUD.loopInProgress = true } + }, + notificationCenter.addObserver(forName: .PumpManagerChanged, object: deviceManager, queue: nil) { [weak self] (notification: Notification) in + DispatchQueue.main.async { + self?.configurePumpManagerHUDViews() + } } + ] if let gestureRecognizer = charts.gestureRecognizer { @@ -125,12 +135,16 @@ final class StatusTableViewController: ChartsTableViewController { } } + onscreen = true + AnalyticsManager.shared.didDisplayStatusScreen() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) + onscreen = false + if presentedViewController == nil { navigationController?.setNavigationBarHidden(false, animated: animated) } @@ -145,12 +159,17 @@ final class StatusTableViewController: ChartsTableViewController { // MARK: - State override var active: Bool { - get { - return super.active - } - set { - super.active = newValue + didSet { hudView?.loopCompletionHUD.assertTimer(active) + updateHUDActive() + } + } + + // This is similar to the visible property, but is set later, on viewDidAppear, to be + // suitable for animations that should be seen in their entirety. + var onscreen: Bool = false { + didSet { + updateHUDActive() } } @@ -167,6 +186,16 @@ final class StatusTableViewController: ChartsTableViewController { } } + private func updateHUDActive() { + deviceManager.pumpManagerHUDProvider?.visible = active && onscreen + } + + public var basalDeliveryState: PumpManagerStatus.BasalDeliveryState = .active { + didSet { + refreshContext.update(with: .status) + } + } + // Toggles the display mode based on the screen aspect ratio. Should not be updated outside of reloadData(). private var landscapeMode = false @@ -231,7 +260,6 @@ final class StatusTableViewController: ChartsTableViewController { reloading = true let reloadGroup = DispatchGroup() - var lastReservoirValue: ReservoirValue? var newRecommendedTempBasal: (recommendation: TempBasalRecommendation, date: Date)? var glucoseValues: [StoredGlucoseSample]? var predictedGlucoseValues: [GlucoseValue]? @@ -344,18 +372,6 @@ final class StatusTableViewController: ChartsTableViewController { reloadGroup.leave() } - - reloadGroup.enter() - deviceManager.loopManager.doseStore.getReservoirValues(since: Date(timeIntervalSinceNow: .minutes(-30)), limit: 1) { (result) in - switch result { - case .success(let values): - lastReservoirValue = values.first - case .failure: - retryContext.update(with: .insulin) - } - - reloadGroup.leave() - } } workoutMode = deviceManager.loopManager.settings.glucoseTargetRangeSchedule?.overrideEnabledForContext(.workout) @@ -418,31 +434,25 @@ final class StatusTableViewController: ChartsTableViewController { sensor: self.deviceManager.cgmManager?.sensorState ) } - - // Reservoir HUD - if let reservoir = lastReservoirValue { - if let capacity = self.deviceManager.pumpManager?.pumpReservoirCapacity { - hudView.reservoirVolumeHUD.reservoirLevel = min(1, max(0, reservoir.unitVolume / capacity)) - } - - hudView.reservoirVolumeHUD.setReservoirVolume(volume: reservoir.unitVolume, at: reservoir.startDate) - } - - // Battery HUD - hudView.batteryHUD.batteryLevel = self.deviceManager.pumpManager?.pumpBatteryChargeRemaining ?? UserDefaults.appGroup.statusExtensionContext?.batteryPercentage } // Show/hide the table view rows let statusRowMode: StatusRowMode? - - switch bolusState { - case .recommended?, .enacting?: - statusRowMode = nil - case .none: - if let (recommendation: tempBasal, date: date) = newRecommendedTempBasal { - statusRowMode = .recommendedTempBasal(tempBasal: tempBasal, at: date, enacting: false) - } else { - statusRowMode = .hidden + + if self.basalDeliveryState == .suspended { + statusRowMode = .pumpSuspended(resuming: false) + } else if self.basalDeliveryState == .resuming { + statusRowMode = .pumpSuspended(resuming: true) + } else { + switch bolusState { + case .recommended?, .enacting?: + statusRowMode = nil + case .none: + if let (recommendation: tempBasal, date: date) = newRecommendedTempBasal { + statusRowMode = .recommendedTempBasal(tempBasal: tempBasal, at: date, enacting: false) + } else { + statusRowMode = .hidden + } } } @@ -510,6 +520,7 @@ final class StatusTableViewController: ChartsTableViewController { case hidden case recommendedTempBasal(tempBasal: TempBasalRecommendation, at: Date, enacting: Bool) case enactingBolus + case pumpSuspended(resuming: Bool) var hasRow: Bool { switch self { @@ -577,6 +588,14 @@ final class StatusTableViewController: ChartsTableViewController { } case (.enactingBolus, .enactingBolus): break + case (.pumpSuspended(resuming: let wasResuming), .pumpSuspended(resuming: let isResuming)): + if isResuming, !wasResuming, let cell = tableView.cellForRow(at: statusIndexPath) as? TitleSubtitleTableViewCell { + let indicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray) + indicatorView.startAnimating() + cell.accessoryView = indicatorView + cell.subtitleLabel.text = nil + } + break default: self.tableView.reloadRows(at: [statusIndexPath], with: animated ? .fade : .none) } @@ -727,6 +746,10 @@ final class StatusTableViewController: ChartsTableViewController { let indicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray) indicatorView.startAnimating() cell.accessoryView = indicatorView + case .pumpSuspended: + cell.titleLabel.text = NSLocalizedString("Pump Suspended", comment: "The title of the cell indicating the pump is suspended") + cell.subtitleLabel.text = NSLocalizedString("Tap to Resume", comment: "The subtitle of the cell displaying an action to resume insulin delivery") + cell.selectionStyle = .default } } @@ -816,7 +839,8 @@ final class StatusTableViewController: ChartsTableViewController { case .status: tableView.deselectRow(at: indexPath, animated: true) - if case .recommendedTempBasal(tempBasal: let tempBasal, at: let date, enacting: let enacting) = statusRowMode, !enacting { + switch statusRowMode { + case .recommendedTempBasal(tempBasal: let tempBasal, at: let date, enacting: let enacting) where !enacting: self.updateHUDandStatusRows(statusRowMode: .recommendedTempBasal(tempBasal: tempBasal, at: date, enacting: true), newSize: nil, animated: true) self.deviceManager.loopManager.enactRecommendedTempBasal { (error) in @@ -825,7 +849,7 @@ final class StatusTableViewController: ChartsTableViewController { if let error = error { self.deviceManager.logger.addError(error, fromSource: "TempBasal") - self.presentAlertController(with: error) + self.present(UIAlertController(with: error), animated: true) } else { self.refreshContext.update(with: .status) self.log.debug("[reloadData] after manually enacting temp basal") @@ -833,6 +857,18 @@ final class StatusTableViewController: ChartsTableViewController { } } } + case .pumpSuspended(let resuming) where !resuming: + self.updateHUDandStatusRows(statusRowMode: .pumpSuspended(resuming: true) , newSize: nil, animated: true) + self.deviceManager.pumpManager?.resumeDelivery() { (error) in + DispatchQueue.main.async { + if let error = error { + let alert = UIAlertController(with: error, title: NSLocalizedString("Error Resuming", comment: "The alert title for a resume error")) + self.present(alert, animated: true, completion: nil) + } + } + } + default: + break } } case .hud: @@ -918,7 +954,7 @@ final class StatusTableViewController: ChartsTableViewController { case .failure(let error): // Ignore bolus wizard errors if error is CarbStore.CarbStoreError { - self.presentAlertController(with: error) + self.present(UIAlertController(with: error), animated: true) } else { self.deviceManager.logger.addError(error, fromSource: "Bolus") } @@ -1015,28 +1051,57 @@ final class StatusTableViewController: ChartsTableViewController { if deviceManager.cgmManager?.appURL != nil { hudView.glucoseHUD.accessibilityHint = NSLocalizedString("Launches CGM app", comment: "Glucose HUD accessibility hint") } - + + configurePumpManagerHUDViews() + hudView.loopCompletionHUD.stateColors = .loopStatus hudView.glucoseHUD.stateColors = .cgmStatus hudView.glucoseHUD.tintColor = .glucoseTintColor hudView.basalRateHUD.tintColor = .doseTintColor - hudView.reservoirVolumeHUD.stateColors = .pumpStatus - hudView.batteryHUD.stateColors = .pumpStatus refreshContext.update(with: .status) self.log.debug("[reloadData] after hudView loaded") reloadData() } } + + private func configurePumpManagerHUDViews() { + if let hudView = hudView { + hudView.removePumpManagerProvidedViews() + if var pumpManagerHUDProvider = deviceManager.pumpManagerHUDProvider + { + let views = pumpManagerHUDProvider.createHUDViews() + for view in views { + addViewToHUD(view) + } + pumpManagerHUDProvider.visible = active && onscreen + } else { + let reservoirView = ReservoirVolumeHUDView.instantiate() + let batteryView = BatteryLevelHUDView.instantiate() + for view in [reservoirView, batteryView] { + addViewToHUD(view) + } + } + } + } + + private func addViewToHUD(_ view: BaseHUDView) { + if let hudView = hudView { + let hudTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(hudViewTapped(_:))) + view.addGestureRecognizer(hudTapGestureRecognizer) + view.stateColors = .pumpStatus + hudView.addHUDView(view) + } + } @objc private func showLastError(_: Any) { // First, check whether we have a device error after the most recent completion date if let deviceError = deviceManager.lastError, deviceError.date > (hudView?.loopCompletionHUD.lastLoopCompleted ?? .distantPast) { - self.presentAlertController(with: deviceError.error) + self.present(UIAlertController(with: deviceError.error), animated: true) } else if let lastLoopError = lastLoopError { - self.presentAlertController(with: lastLoopError) + self.present(UIAlertController(with: lastLoopError), animated: true) } } @@ -1045,4 +1110,37 @@ final class StatusTableViewController: ChartsTableViewController { UIApplication.shared.open(url) } } + + @objc private func hudViewTapped(_ sender: UIGestureRecognizer) { + if let hudSubView = sender.view as? BaseHUDView, + let pumpManagerHUDProvider = deviceManager.pumpManagerHUDProvider, + let action = pumpManagerHUDProvider.didTapOnHUDView(hudSubView) + { + switch action { + case .presentViewController(let vc): + var completionNotifyingVC = vc + completionNotifyingVC.completionDelegate = self + self.present(vc, animated: true, completion: nil) + case .openAppURL(let url): + UIApplication.shared.open(url) + } + } + } +} + +extension StatusTableViewController: CompletionDelegate { + func completionNotifyingDidComplete(_ object: CompletionNotifying) { + if let vc = object as? UIViewController { + vc.dismiss(animated: true, completion: nil) + } + } +} + +extension StatusTableViewController: PumpManagerStatusObserver { + func pumpManager(_ pumpManager: PumpManager, didUpdate status: PumpManagerStatus) { + DispatchQueue.main.async { + self.basalDeliveryState = status.basalDeliveryState + self.reloadData(animated: true) + } + } } diff --git a/Loop/Views/SettingsImageTableViewCell.swift b/Loop/Views/SettingsImageTableViewCell.swift index e99ea9a968..1b997ae9fb 100644 --- a/Loop/Views/SettingsImageTableViewCell.swift +++ b/Loop/Views/SettingsImageTableViewCell.swift @@ -25,7 +25,7 @@ class SettingsImageTableViewCell: UITableViewCell { guard let textLabel = textLabel, let imageView = imageView else { return } - + textLabel.adjustsFontForContentSizeCategory = true textLabel.font = UIFont.preferredFont(forTextStyle: .body) textLabel.translatesAutoresizingMaskIntoConstraints = false diff --git a/LoopUI/Assets.xcassets/Contents.json b/LoopUI/HUDAssets.xcassets/Contents.json similarity index 100% rename from LoopUI/Assets.xcassets/Contents.json rename to LoopUI/HUDAssets.xcassets/Contents.json diff --git a/LoopUI/Assets.xcassets/battery/Contents.json b/LoopUI/HUDAssets.xcassets/battery/Contents.json similarity index 100% rename from LoopUI/Assets.xcassets/battery/Contents.json rename to LoopUI/HUDAssets.xcassets/battery/Contents.json diff --git a/LoopUI/Assets.xcassets/battery/battery.imageset/Contents.json b/LoopUI/HUDAssets.xcassets/battery/battery.imageset/Contents.json similarity index 100% rename from LoopUI/Assets.xcassets/battery/battery.imageset/Contents.json rename to LoopUI/HUDAssets.xcassets/battery/battery.imageset/Contents.json diff --git a/LoopUI/Assets.xcassets/battery/battery.imageset/battery.pdf b/LoopUI/HUDAssets.xcassets/battery/battery.imageset/battery.pdf similarity index 100% rename from LoopUI/Assets.xcassets/battery/battery.imageset/battery.pdf rename to LoopUI/HUDAssets.xcassets/battery/battery.imageset/battery.pdf diff --git a/LoopUI/Assets.xcassets/battery/battery_mask.imageset/Contents.json b/LoopUI/HUDAssets.xcassets/battery/battery_mask.imageset/Contents.json similarity index 100% rename from LoopUI/Assets.xcassets/battery/battery_mask.imageset/Contents.json rename to LoopUI/HUDAssets.xcassets/battery/battery_mask.imageset/Contents.json diff --git a/LoopUI/Assets.xcassets/battery/battery_mask.imageset/battery_mask.pdf b/LoopUI/HUDAssets.xcassets/battery/battery_mask.imageset/battery_mask.pdf similarity index 100% rename from LoopUI/Assets.xcassets/battery/battery_mask.imageset/battery_mask.pdf rename to LoopUI/HUDAssets.xcassets/battery/battery_mask.imageset/battery_mask.pdf diff --git a/LoopUI/Assets.xcassets/reservoir/Contents.json b/LoopUI/HUDAssets.xcassets/reservoir/Contents.json similarity index 100% rename from LoopUI/Assets.xcassets/reservoir/Contents.json rename to LoopUI/HUDAssets.xcassets/reservoir/Contents.json diff --git a/LoopUI/Assets.xcassets/reservoir/reservoir.imageset/Contents.json b/LoopUI/HUDAssets.xcassets/reservoir/reservoir.imageset/Contents.json similarity index 100% rename from LoopUI/Assets.xcassets/reservoir/reservoir.imageset/Contents.json rename to LoopUI/HUDAssets.xcassets/reservoir/reservoir.imageset/Contents.json diff --git a/LoopUI/Assets.xcassets/reservoir/reservoir.imageset/reservoir.pdf b/LoopUI/HUDAssets.xcassets/reservoir/reservoir.imageset/reservoir.pdf similarity index 100% rename from LoopUI/Assets.xcassets/reservoir/reservoir.imageset/reservoir.pdf rename to LoopUI/HUDAssets.xcassets/reservoir/reservoir.imageset/reservoir.pdf diff --git a/LoopUI/Assets.xcassets/reservoir/reservoir_mask.imageset/Contents.json b/LoopUI/HUDAssets.xcassets/reservoir/reservoir_mask.imageset/Contents.json similarity index 100% rename from LoopUI/Assets.xcassets/reservoir/reservoir_mask.imageset/Contents.json rename to LoopUI/HUDAssets.xcassets/reservoir/reservoir_mask.imageset/Contents.json diff --git a/LoopUI/Assets.xcassets/reservoir/reservoir_mask.imageset/reservoir_mask.pdf b/LoopUI/HUDAssets.xcassets/reservoir/reservoir_mask.imageset/reservoir_mask.pdf similarity index 100% rename from LoopUI/Assets.xcassets/reservoir/reservoir_mask.imageset/reservoir_mask.pdf rename to LoopUI/HUDAssets.xcassets/reservoir/reservoir_mask.imageset/reservoir_mask.pdf diff --git a/LoopUI/HUDView.xib b/LoopUI/HUDView.xib index 919a91a921..ee27795580 100644 --- a/LoopUI/HUDView.xib +++ b/LoopUI/HUDView.xib @@ -1,22 +1,19 @@ - + - - + - - @@ -24,16 +21,16 @@ - + - + @@ -64,28 +61,28 @@ - + - + - + @@ -163,130 +160,12 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/LoopUI/Models/StateColorPalette.swift b/LoopUI/Models/StateColorPalette.swift deleted file mode 100644 index 20052f10b2..0000000000 --- a/LoopUI/Models/StateColorPalette.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// StateColorPalette.swift -// Loop -// -// Copyright © 2017 LoopKit Authors. All rights reserved. -// - -import UIKit - - -/// A collection of colors for displaying state -public struct StateColorPalette { - public let unknown: UIColor - public let normal: UIColor - public let warning: UIColor - public let error: UIColor - - public init(unknown: UIColor, normal: UIColor, warning: UIColor, error: UIColor) { - self.unknown = unknown - self.normal = normal - self.warning = warning - self.error = error - } -} diff --git a/LoopUI/Views/BasalRateHUDView.swift b/LoopUI/Views/BasalRateHUDView.swift index 0c44579a5e..0a4283d9cd 100644 --- a/LoopUI/Views/BasalRateHUDView.swift +++ b/LoopUI/Views/BasalRateHUDView.swift @@ -7,9 +7,13 @@ // import UIKit - +import LoopKitUI public final class BasalRateHUDView: BaseHUDView { + + override public var orderPriority: HUDViewOrderPriority { + return 3 + } @IBOutlet private weak var basalStateView: BasalStateView! diff --git a/LoopUI/Views/BaseHUDView.swift b/LoopUI/Views/BaseHUDView.swift deleted file mode 100644 index a0d2ad853e..0000000000 --- a/LoopUI/Views/BaseHUDView.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// HUDView.swift -// Naterade -// -// Created by Nathan Racklyeft on 5/1/16. -// Copyright © 2016 Nathan Racklyeft. All rights reserved. -// - -import UIKit - -public class BaseHUDView: UIView { - - @IBOutlet weak var caption: UILabel! { - didSet { - caption?.text = "—" - } - } - -} diff --git a/LoopUI/Views/BatteryLevelHUDView.swift b/LoopUI/Views/BatteryLevelHUDView.swift deleted file mode 100644 index e200c54530..0000000000 --- a/LoopUI/Views/BatteryLevelHUDView.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// BatteryLevelHUDView.swift -// Naterade -// -// Created by Nathan Racklyeft on 5/2/16. -// Copyright © 2016 Nathan Racklyeft. All rights reserved. -// - -import UIKit - - -public final class BatteryLevelHUDView: LevelHUDView { - - private lazy var numberFormatter: NumberFormatter = { - let formatter = NumberFormatter() - formatter.numberStyle = .percent - - return formatter - }() - - public var batteryLevel: Double? { - didSet { - if let value = batteryLevel, let level = numberFormatter.string(from: value) { - caption.text = level - accessibilityValue = level - } else { - caption.text = nil - } - - level = batteryLevel - } - } - -} diff --git a/LoopUI/Views/GlucoseHUDView.swift b/LoopUI/Views/GlucoseHUDView.swift index 9a7acf26c9..802ee17de0 100644 --- a/LoopUI/Views/GlucoseHUDView.swift +++ b/LoopUI/Views/GlucoseHUDView.swift @@ -9,9 +9,13 @@ import UIKit import HealthKit import LoopKit - +import LoopKitUI public final class GlucoseHUDView: BaseHUDView { + + override public var orderPriority: HUDViewOrderPriority { + return 2 + } @IBOutlet private weak var unitLabel: UILabel! { didSet { @@ -43,10 +47,9 @@ public final class GlucoseHUDView: BaseHUDView { glucoseLabel.textColor = tintColor } - public var stateColors: StateColorPalette? { - didSet { - updateColor() - } + override public func stateColorsDidUpdate() { + super.stateColorsDidUpdate() + updateColor() } private func updateColor() { diff --git a/LoopUI/Views/HUDView.swift b/LoopUI/Views/HUDView.swift index af89b39068..3dc832c4a8 100644 --- a/LoopUI/Views/HUDView.swift +++ b/LoopUI/Views/HUDView.swift @@ -7,16 +7,17 @@ // import UIKit +import LoopKitUI public class HUDView: UIView, NibLoadable { @IBOutlet public weak var loopCompletionHUD: LoopCompletionHUDView! @IBOutlet public weak var glucoseHUD: GlucoseHUDView! @IBOutlet public weak var basalRateHUD: BasalRateHUDView! - @IBOutlet public weak var reservoirVolumeHUD: ReservoirVolumeHUDView! - @IBOutlet public weak var batteryHUD: BatteryLevelHUDView! + + private var stackView: UIStackView! func setup() { - let stackView = HUDView.nib().instantiate(withOwner: self, options: nil)[0] as! UIStackView + stackView = (HUDView.nib().instantiate(withOwner: self, options: nil)[0] as! UIStackView) self.addSubview(stackView) // Use AutoLayout to have the stack view fill its entire container. @@ -26,6 +27,25 @@ public class HUDView: UIView, NibLoadable { let heightConstraint = NSLayoutConstraint(item: stackView, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 1, constant: 0) self.addConstraints([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint]) } + + public func removePumpManagerProvidedViews() { + let standardViews: [UIView] = [loopCompletionHUD, glucoseHUD, basalRateHUD] + let pumpManagerViews = stackView.subviews.filter { !standardViews.contains($0) } + for view in pumpManagerViews { + view.removeFromSuperview() + } + } + + public func addHUDView(_ viewToAdd: BaseHUDView) { + let insertIndex = stackView.arrangedSubviews.firstIndex { (view) -> Bool in + guard let hudView = view as? BaseHUDView else { + return false + } + return viewToAdd.orderPriority <= hudView.orderPriority + } + + stackView.insertArrangedSubview(viewToAdd, at: insertIndex ?? stackView.arrangedSubviews.count) + } public override init(frame: CGRect) { super.init(frame: frame) diff --git a/LoopUI/Views/LevelHUDView.swift b/LoopUI/Views/LevelHUDView.swift deleted file mode 100644 index aec0371fd8..0000000000 --- a/LoopUI/Views/LevelHUDView.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// LevelHUDView.swift -// Loop -// -// Created by Nate Racklyeft on 2/4/17. -// Copyright © 2017 LoopKit Authors. All rights reserved. -// - -import UIKit - -public class LevelHUDView: BaseHUDView { - - @IBOutlet private weak var levelMaskView: LevelMaskView! - - override public func awakeFromNib() { - super.awakeFromNib() - - updateColor() - - accessibilityValue = LocalizedString("Unknown", comment: "Accessibility value for an unknown value") - } - - public var stateColors: StateColorPalette? { - didSet { - updateColor() - } - } - - private func updateColor() { - levelMaskView.tintColor = nil - - switch level { - case .none: - tintColor = stateColors?.unknown - case let x? where x > 0.25: - tintColor = stateColors?.normal - case let x? where x > 0.10: - tintColor = stateColors?.normal - levelMaskView.tintColor = stateColors?.warning - default: - tintColor = stateColors?.error - } - } - - internal var level: Double? { - didSet { - levelMaskView.value = level ?? 1.0 - updateColor() - } - } - -} diff --git a/LoopUI/Views/LevelMaskView.swift b/LoopUI/Views/LevelMaskView.swift deleted file mode 100644 index ec7840fd71..0000000000 --- a/LoopUI/Views/LevelMaskView.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -// LevelMaskView.swift -// Loop -// -// Created by Nate Racklyeft on 8/28/16. -// Copyright © 2016 Nathan Racklyeft. All rights reserved. -// - -import UIKit - -// Displays a variable-height level indicator, masked by an image. -// Inspired by https://github.com/carekit-apple/CareKit/blob/master/CareKit/CareCard/OCKHeartView.h - -public class LevelMaskView: UIView { - var firstDataUpdate = true - - var value: Double = 1.0 { - didSet { - animateFill(duration: firstDataUpdate ? 0 : 1.25) - firstDataUpdate = false - } - } - - @IBInspectable var maskImage: UIImage? { - didSet { - fillView?.removeFromSuperview() - mask?.removeFromSuperview() - maskImageView?.removeFromSuperview() - - guard let maskImage = maskImage else { return } - - mask = UIView() - maskImageView = UIImageView(image: maskImage) - maskImageView!.contentMode = .center - mask!.addSubview(maskImageView!) - - clipsToBounds = true - - fillView = UIView() - fillView!.backgroundColor = tintColor - addSubview(fillView!) - } - } - - private var fillView: UIView? - - private var maskImageView: UIView? - - override public func layoutSubviews() { - super.layoutSubviews() - - guard let maskImage = maskImage else { return } - - let maskImageSize = maskImage.size - - mask?.frame = CGRect(origin: .zero, size: maskImageSize) - mask?.center = CGPoint(x: bounds.midX, y: bounds.midY) - maskImageView?.frame = mask?.bounds ?? bounds - - if (fillView?.layer.animationKeys()?.count ?? 0) == 0 { - updateFillViewFrame() - } - } - - override public func tintColorDidChange() { - super.tintColorDidChange() - - fillView?.backgroundColor = tintColor - } - - private func animateFill(duration: TimeInterval) { - UIView.animate(withDuration: duration, delay: 0, options: .beginFromCurrentState, animations: { - self.updateFillViewFrame() - }, completion: nil) - } - - private func updateFillViewFrame() { - guard let maskViewFrame = mask?.frame else { return } - - var fillViewFrame = maskViewFrame - fillViewFrame.origin.y = maskViewFrame.maxY - fillViewFrame.size.height = -CGFloat(value) * maskViewFrame.height - fillView?.frame = fillViewFrame - } -} diff --git a/LoopUI/Views/LoopCompletionHUDView.swift b/LoopUI/Views/LoopCompletionHUDView.swift index 077f699998..3fbdd1aff4 100644 --- a/LoopUI/Views/LoopCompletionHUDView.swift +++ b/LoopUI/Views/LoopCompletionHUDView.swift @@ -7,10 +7,15 @@ // import UIKit +import LoopKitUI public final class LoopCompletionHUDView: BaseHUDView { @IBOutlet private weak var loopStateView: LoopStateView! + + override public var orderPriority: HUDViewOrderPriority { + return 1 + } enum Freshness { case fresh @@ -64,10 +69,9 @@ public final class LoopCompletionHUDView: BaseHUDView { } } - public var stateColors: StateColorPalette? { - didSet { - updateTintColor() - } + override public func stateColorsDidUpdate() { + super.stateColorsDidUpdate() + updateTintColor() } private func updateTintColor() { diff --git a/LoopUI/Views/ReservoirVolumeHUDView.swift b/LoopUI/Views/ReservoirVolumeHUDView.swift deleted file mode 100644 index 6f57fe09d1..0000000000 --- a/LoopUI/Views/ReservoirVolumeHUDView.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// ReservoirVolumeHUDView.swift -// Naterade -// -// Created by Nathan Racklyeft on 5/2/16. -// Copyright © 2016 Nathan Racklyeft. All rights reserved. -// - -import UIKit - -public final class ReservoirVolumeHUDView: LevelHUDView { - - @IBOutlet private weak var volumeLabel: UILabel! - - override public func awakeFromNib() { - super.awakeFromNib() - - volumeLabel.isHidden = true - } - - public var reservoirLevel: Double? { - didSet { - level = reservoirLevel - - switch reservoirLevel { - case .none: - volumeLabel.isHidden = true - case let x? where x > 0.25: - volumeLabel.isHidden = true - case let x? where x > 0.10: - volumeLabel.textColor = tintColor - volumeLabel.isHidden = false - default: - volumeLabel.textColor = tintColor - volumeLabel.isHidden = false - } - } - } - - private lazy var timeFormatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateStyle = .none - formatter.timeStyle = .short - - return formatter - }() - - private lazy var numberFormatter: NumberFormatter = { - let formatter = NumberFormatter() - formatter.numberStyle = .decimal - formatter.maximumFractionDigits = 0 - - return formatter - }() - - public func setReservoirVolume(volume: Double, at date: Date) { - if let units = numberFormatter.string(from: volume) { - volumeLabel.text = String(format: LocalizedString("%@U", comment: "Format string for reservoir volume. (1: The localized volume)"), units) - let time = timeFormatter.string(from: date) - caption?.text = time - - accessibilityValue = String(format: LocalizedString("%1$@ units remaining at %2$@", comment: "Accessibility format string for (1: localized volume)(2: time)"), units, time) - } - } -} From 9591ddfdc5c476e3086f32f63c03638c0411e543 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 4 Mar 2019 16:39:04 -0600 Subject: [PATCH 003/132] Use new basal picker interface --- .../SettingsTableViewController.swift | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Loop/View Controllers/SettingsTableViewController.swift b/Loop/View Controllers/SettingsTableViewController.swift index d60733f335..1ed62dfe69 100644 --- a/Loop/View Controllers/SettingsTableViewController.swift +++ b/Loop/View Controllers/SettingsTableViewController.swift @@ -493,18 +493,23 @@ final class SettingsTableViewController: UITableViewController { show(vc, sender: sender) case .basalRate: - let vc = SingleValueScheduleTableViewController(style: .grouped) + guard let pumpManager = dataManager.pumpManager else { + // Not allowing basal schedule entry without a configured pump. + tableView.deselectRow(at: indexPath, animated: true) + return + } + let vc = BasalScheduleTableViewController(allowedBasalRates: pumpManager.supportedBasalRates, maximumScheduleItemCount: pumpManager.maximumBasalScheduleEntryCount, minimumTimeInterval: pumpManager.minimumBasalScheduleEntryDuration) if let profile = dataManager.loopManager.basalRateSchedule { vc.scheduleItems = profile.items vc.timeZone = profile.timeZone - } else if let timeZone = dataManager.pumpManager?.status.timeZone { - vc.timeZone = timeZone + } else { + vc.timeZone = pumpManager.status.timeZone } vc.title = NSLocalizedString("Basal Rates", comment: "The title of the basal rate profile screen") vc.delegate = self - vc.syncSource = dataManager.pumpManager + vc.syncSource = pumpManager show(vc, sender: sender) } @@ -648,7 +653,7 @@ extension SettingsTableViewController: DailyValueScheduleTableViewControllerDele dataManager.loopManager.settings.glucoseTargetRangeSchedule = GlucoseRangeSchedule(unit: controller.unit, dailyItems: controller.scheduleItems, timeZone: controller.timeZone, overrideRanges: controller.overrideRanges, override: dataManager.loopManager.settings.glucoseTargetRangeSchedule?.override) } case .basalRate: - if let controller = controller as? SingleValueScheduleTableViewController { + if let controller = controller as? BasalScheduleTableViewController { dataManager.loopManager.basalRateSchedule = BasalRateSchedule(dailyItems: controller.scheduleItems, timeZone: controller.timeZone) } case let row: From b03d42b0ddff89bf5f45172bfac6eea659db400c Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 5 Mar 2019 19:58:10 -0600 Subject: [PATCH 004/132] Back active flag with actual applicationState (#892) --- Loop/View Controllers/ChartsTableViewController.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Loop/View Controllers/ChartsTableViewController.swift b/Loop/View Controllers/ChartsTableViewController.swift index 3642a25628..ca936104d0 100644 --- a/Loop/View Controllers/ChartsTableViewController.swift +++ b/Loop/View Controllers/ChartsTableViewController.swift @@ -86,7 +86,7 @@ class ChartsTableViewController: UITableViewController, UIGestureRecognizerDeleg let notificationCenter = NotificationCenter.default notificationObservers += [ - notificationCenter.addObserver(forName: .UIApplicationWillResignActive, object: UIApplication.shared, queue: .main) { [weak self] _ in + notificationCenter.addObserver(forName: .UIApplicationDidEnterBackground, object: UIApplication.shared, queue: .main) { [weak self] _ in self?.active = false }, notificationCenter.addObserver(forName: .UIApplicationDidBecomeActive, object: UIApplication.shared, queue: .main) { [weak self] _ in @@ -94,8 +94,6 @@ class ChartsTableViewController: UITableViewController, UIGestureRecognizerDeleg } ] - active = UIApplication.shared.applicationState == .active - let gestureRecognizer = UILongPressGestureRecognizer() gestureRecognizer.delegate = self gestureRecognizer.minimumPressDuration = 0.1 @@ -168,8 +166,11 @@ class ChartsTableViewController: UITableViewController, UIGestureRecognizerDeleg // References to registered notification center observers var notificationObservers: [Any] = [] - var active: Bool = false { - didSet { + var active: Bool { + get { + return UIApplication.shared.applicationState == .active + } + set { log.debug("[reloadData] for app change to active: %d", active) reloadData() } From ad179b6a4ff9f16b47140c0a888b2d9259248b57 Mon Sep 17 00:00:00 2001 From: Eric Jensen Date: Tue, 5 Mar 2019 20:59:57 -0500 Subject: [PATCH 005/132] Only present non-zero bolus suggestion on watch after carb entry (#893) --- .../Controllers/AddCarbsInterfaceController.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/WatchApp Extension/Controllers/AddCarbsInterfaceController.swift b/WatchApp Extension/Controllers/AddCarbsInterfaceController.swift index 5b46a63147..51be151f37 100644 --- a/WatchApp Extension/Controllers/AddCarbsInterfaceController.swift +++ b/WatchApp Extension/Controllers/AddCarbsInterfaceController.swift @@ -184,7 +184,9 @@ final class AddCarbsInterfaceController: WKInterfaceController, IdentifiableClas ExtensionDelegate.shared().loopManager.addConfirmedCarbEntry(entry) - WKExtension.shared().rootInterfaceController?.presentController(withName: BolusInterfaceController.className, context: suggestion) + if let recommendedBolus = suggestion.recommendedBolus?.rawValue, recommendedBolus > 0.0 { + WKExtension.shared().rootInterfaceController?.presentController(withName: BolusInterfaceController.className, context: suggestion) + } } }, errorHandler: { (error) in From 1c54ae6f4d76c82b6497d45dbdcc316cf2d3b47c Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Wed, 6 Mar 2019 09:19:00 -0600 Subject: [PATCH 006/132] Update dependency revs --- Cartfile.resolved | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 1ded1a366c..f157ff4757 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,7 +1,7 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "ea1267791c66e884f1013fffd36faf4555cc6eaf" github "LoopKit/G4ShareSpy" "fed5a389e3e47e3a1953878dd21852aa5f44b360" -github "LoopKit/LoopKit" "bf4166bc77f89e22971f2030ad006967881ff082" +github "LoopKit/LoopKit" "797a4fffeb0b56c8ce47543ea6516b3b8c8e0857" github "LoopKit/dexcom-share-client-swift" "b0419edf55c7f389b36cb47dd5c376bbd3d03d69" github "i-schuetz/SwiftCharts" "0.6.2" -github "ps2/rileylink_ios" "46ed2c845e7c4b88c721994d8e8432e86be4c699" +github "ps2/rileylink_ios" "23af71639041ca3a2a1b4120d3cd0a06c5d3fa01" From 93c20c5b30abf9f6dafe2b55a1cff2e5fd3025b9 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 9 Mar 2019 06:52:24 -0600 Subject: [PATCH 007/132] Limit recommended temp basals to supported rates. (#5) * Limit recommended temp basals to supported rates. * Use pumpmanager provided rounding * Fix tests, and update to PumpManager function name change * Update naming from review comments --- DoseMathTests/DoseMathTests.swift | 16 +++++++++++---- Loop/Managers/DeviceDataManager.swift | 18 +++++++++++++++++ Loop/Managers/DoseMath.swift | 25 ++++++++++++----------- Loop/Managers/LoopDataManager.swift | 29 +++++++++++++++++++++++++-- 4 files changed, 70 insertions(+), 18 deletions(-) diff --git a/DoseMathTests/DoseMathTests.swift b/DoseMathTests/DoseMathTests.swift index 9b2718701a..7bde9f85e5 100644 --- a/DoseMathTests/DoseMathTests.swift +++ b/DoseMathTests/DoseMathTests.swift @@ -54,6 +54,8 @@ class RecommendTempBasalTests: XCTestCase { fileprivate let maxBasalRate = 3.0 + fileprivate let fortyIncrementsPerUnitRounder = { round($0 * 40) / 40 } + func loadGlucoseValueFixture(_ resourceName: String) -> [GlucoseValue] { let fixture: [JSONDictionary] = loadFixture(resourceName) let dateFormatter = ISO8601DateFormatter.localTimeDateFormatter() @@ -440,6 +442,8 @@ class RecommendBolusTests: XCTestCase { fileprivate let maxBolus = 10.0 + fileprivate let fortyIncrementsPerUnitRounder = { round($0 * 40) / 40 } + func loadGlucoseValueFixture(_ resourceName: String) -> [GlucoseValue] { let fixture: [JSONDictionary] = loadFixture(resourceName) let dateFormatter = ISO8601DateFormatter.localTimeDateFormatter() @@ -560,7 +564,8 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: insulinModel, pendingInsulin: 0, - maxBolus: maxBolus + maxBolus: maxBolus, + volumeRounder: fortyIncrementsPerUnitRounder ) XCTAssertEqual(1.575, dose.amount) @@ -628,7 +633,8 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: insulinModel, pendingInsulin: 0, - maxBolus: maxBolus + maxBolus: maxBolus, + volumeRounder: fortyIncrementsPerUnitRounder ) XCTAssertEqual(1.4, dose.amount) @@ -646,7 +652,8 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: insulinModel, pendingInsulin: 1, - maxBolus: maxBolus + maxBolus: maxBolus, + volumeRounder: fortyIncrementsPerUnitRounder ) XCTAssertEqual(0.575, dose.amount) @@ -740,7 +747,8 @@ class RecommendBolusTests: XCTestCase { sensitivity: insulinSensitivitySchedule, model: ExponentialInsulinModel(actionDuration: 21600.0, peakActivityTime: 4500.0), pendingInsulin: 0, - maxBolus: maxBolus + maxBolus: maxBolus, + volumeRounder: fortyIncrementsPerUnitRounder ) XCTAssertEqual(0.275, dose.amount) diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index b7cb3408a5..8fe8026f53 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -388,6 +388,22 @@ extension DeviceDataManager { } extension DeviceDataManager: LoopDataManagerDelegate { + func loopDataManager(_ manager: LoopDataManager, roundBasalRate unitsPerHour: Double) -> Double { + guard let pumpManager = pumpManager else { + return unitsPerHour + } + + return pumpManager.roundToSupportedBasalRate(unitsPerHour: unitsPerHour) + } + + func loopDataManager(_ manager: LoopDataManager, roundBolusVolume units: Double) -> Double { + guard let pumpManager = pumpManager else { + return units + } + + return pumpManager.roundToSupportedBolusVolume(units: units) + } + func loopDataManager( _ manager: LoopDataManager, didRecommendBasalChange basal: (recommendation: TempBasalRecommendation, date: Date), @@ -413,6 +429,8 @@ extension DeviceDataManager: LoopDataManagerDelegate { } ) } + + } diff --git a/Loop/Managers/DoseMath.swift b/Loop/Managers/DoseMath.swift index 3366170c4a..61ba33ecf6 100644 --- a/Loop/Managers/DoseMath.swift +++ b/Loop/Managers/DoseMath.swift @@ -38,13 +38,13 @@ extension InsulinCorrection { /// - scheduledBasalRate: The scheduled basal rate at the time the correction is delivered /// - maxBasalRate: The maximum allowed basal rate /// - duration: The duration of the temporary basal - /// - minimumProgrammableIncrementPerUnit: The smallest fraction of a unit supported in basal delivery + /// - rateRounder: The smallest fraction of a unit supported in basal delivery /// - Returns: A temp basal recommendation fileprivate func asTempBasal( scheduledBasalRate: Double, maxBasalRate: Double, duration: TimeInterval, - minimumProgrammableIncrementPerUnit: Double + rateRounder: ((Double) -> Double)? ) -> TempBasalRecommendation { var rate = units / (duration / TimeInterval(hours: 1)) // units/hour switch self { @@ -55,7 +55,8 @@ extension InsulinCorrection { } rate = Swift.min(maxBasalRate, Swift.max(0, rate)) - rate = round(rate * minimumProgrammableIncrementPerUnit) / minimumProgrammableIncrementPerUnit + + rate = rateRounder?(rate) ?? rate return TempBasalRecommendation( unitsPerHour: rate, @@ -83,16 +84,16 @@ extension InsulinCorrection { /// - Parameters: /// - pendingInsulin: The number of units expected to be delivered, but not yet reflected in the correction /// - maxBolus: The maximum allowable bolus value in units - /// - minimumProgrammableIncrementPerUnit: The smallest fraction of a unit supported in bolus delivery + /// - volumeRounder: The smallest fraction of a unit supported in bolus delivery /// - Returns: A bolus recommendation fileprivate func asBolus( pendingInsulin: Double, maxBolus: Double, - minimumProgrammableIncrementPerUnit: Double + volumeRounder: ((Double) -> Double)? ) -> BolusRecommendation { var units = self.units - pendingInsulin units = Swift.min(maxBolus, Swift.max(0, units)) - units = round(units * minimumProgrammableIncrementPerUnit) / minimumProgrammableIncrementPerUnit + units = volumeRounder?(units) ?? units return BolusRecommendation( amount: units, @@ -340,8 +341,8 @@ extension Collection where Element == GlucoseValue { /// - basalRates: The schedule of basal rates /// - maxBasalRate: The maximum allowed basal rate /// - lastTempBasal: The previously set temp basal + /// - rateRounder: Closure that rounds recommendation to nearest supported rate. If nil, no rounding is performed /// - duration: The duration of the temporary basal - /// - minimumProgrammableIncrementPerUnit: The smallest fraction of a unit supported in basal delivery /// - continuationInterval: The duration of time before an ongoing temp basal should be continued with a new command /// - Returns: The recommended temporary basal rate and duration func recommendedTempBasal( @@ -353,8 +354,8 @@ extension Collection where Element == GlucoseValue { basalRates: BasalRateSchedule, maxBasalRate: Double, lastTempBasal: DoseEntry?, + rateRounder: ((Double) -> Double)? = nil, duration: TimeInterval = .minutes(30), - minimumProgrammableIncrementPerUnit: Double = 40, continuationInterval: TimeInterval = .minutes(11) ) -> TempBasalRecommendation? { let correction = self.insulinCorrection( @@ -379,7 +380,7 @@ extension Collection where Element == GlucoseValue { scheduledBasalRate: scheduledBasalRate, maxBasalRate: maxBasalRate, duration: duration, - minimumProgrammableIncrementPerUnit: minimumProgrammableIncrementPerUnit + rateRounder: rateRounder ) return temp?.ifNecessary( @@ -400,7 +401,7 @@ extension Collection where Element == GlucoseValue { /// - model: The insulin absorption model /// - pendingInsulin: The number of units expected to be delivered, but not yet reflected in the correction /// - maxBolus: The maximum bolus to return - /// - minimumProgrammableIncrementPerUnit: The smallest fraction of a unit supported in bolus delivery + /// - volumeRounder: Closure that rounds recommendation to nearest supported bolus volume. If nil, no rounding is performed /// - Returns: A bolus recommendation func recommendedBolus( to correctionRange: GlucoseRangeSchedule, @@ -410,7 +411,7 @@ extension Collection where Element == GlucoseValue { model: InsulinModel, pendingInsulin: Double, maxBolus: Double, - minimumProgrammableIncrementPerUnit: Double = 40 + volumeRounder: ((Double) -> Double)? = nil ) -> BolusRecommendation { guard let correction = self.insulinCorrection( to: correctionRange, @@ -425,7 +426,7 @@ extension Collection where Element == GlucoseValue { var bolus = correction.asBolus( pendingInsulin: pendingInsulin, maxBolus: maxBolus, - minimumProgrammableIncrementPerUnit: minimumProgrammableIncrementPerUnit + volumeRounder: volumeRounder ) // Handle the "current BG below target" notice here diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 8e2c60c512..6ae172a1f4 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -167,6 +167,7 @@ final class LoopDataManager { } fileprivate var recommendedTempBasal: (recommendation: TempBasalRecommendation, date: Date)? + fileprivate var recommendedBolus: (recommendation: BolusRecommendation, date: Date)? fileprivate var carbsOnBoard: CarbValue? @@ -893,6 +894,10 @@ extension LoopDataManager { recommendedTempBasal = nil return } + + let rateRounder = { (_ rate: Double) in + return self.delegate?.loopDataManager(self, roundBasalRate: rate) ?? rate + } let tempBasal = predictedGlucose.recommendedTempBasal( to: glucoseTargetRange, @@ -901,7 +906,8 @@ extension LoopDataManager { model: model, basalRates: basalRates, maxBasalRate: maxBasal, - lastTempBasal: lastTempBasal + lastTempBasal: lastTempBasal, + rateRounder: rateRounder ) if let temp = tempBasal { @@ -912,13 +918,18 @@ extension LoopDataManager { let pendingInsulin = try self.getPendingInsulin() + let volumeRounder = { (_ units: Double) in + return self.delegate?.loopDataManager(self, roundBolusVolume: units) ?? units + } + let recommendation = predictedGlucose.recommendedBolus( to: glucoseTargetRange, suspendThreshold: settings.suspendThreshold?.quantity, sensitivity: insulinSensitivity, model: model, pendingInsulin: pendingInsulin, - maxBolus: maxBolus + maxBolus: maxBolus, + volumeRounder: volumeRounder ) recommendedBolus = (recommendation: recommendation, date: startDate) } @@ -1175,6 +1186,20 @@ protocol LoopDataManagerDelegate: class { /// - completion: A closure called once on completion /// - result: The enacted basal func loopDataManager(_ manager: LoopDataManager, didRecommendBasalChange basal: (recommendation: TempBasalRecommendation, date: Date), completion: @escaping (_ result: Result) -> Void) -> Void + + /// Asks the delegate to round a recommended basal rate to a supported rate + /// + /// - Parameters: + /// - rate: The recommended rate in U/hr + /// - Returns: a supported rate of delivery in Units/hr. The rate returned should not be larger than the passed in rate. + func loopDataManager(_ manager: LoopDataManager, roundBasalRate unitsPerHour: Double) -> Double + + /// Asks the delegate to round a recommended bolus volume to a supported volume + /// + /// - Parameters: + /// - units: The recommended bolus in U + /// - Returns: a supported bolus volume in U. The volume returned should not be larger than the passed in rate. + func loopDataManager(_ manager: LoopDataManager, roundBolusVolume units: Double) -> Double } extension DoseStore { From 8a22271e07a2f07aaf2a594300df5ac7d09efeaf Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 9 Mar 2019 13:53:51 -0600 Subject: [PATCH 008/132] Learn app target and LoopCore framework (#900) * Learn app target and LoopCore framework * Update carthage revs --- .travis.yml | 1 + Cartfile.resolved | 4 +- Common/Extensions/HKUnit.swift | 9 +- Common/Extensions/NSTimeInterval.swift | 8 + .../Extensions/NewCarbEntryIntent+Loop.swift | 1 + .../Extensions}/TextFieldTableViewCell.swift | 1 + .../ExponentialInsulinModelPreset.swift | 89 -- Common/Models/LoopSettingsUserInfo.swift | 2 + DoseMathTests/DoseMathTests.swift | 1 + Learn/AppDelegate.swift | 61 ++ .../AppIcon.appiconset/Contents.json | 98 ++ Learn/Assets.xcassets/Contents.json | 6 + Learn/Base.lproj/LaunchScreen.storyboard | 25 + Learn/Base.lproj/Main.storyboard | 124 +++ Learn/Configuration/DateEntry.swift | 43 + Learn/Configuration/NumberEntry.swift | 110 +++ Learn/Configuration/NumberRangeEntry.swift | 66 ++ Learn/Configuration/QuantityRangeEntry.swift | 77 ++ Learn/Display/DatesAndNumberCell.swift | 43 + Learn/Display/TextCell.swift | 51 + Learn/Extensions/Date.swift | 21 + Learn/Extensions/DateIntervalFormatter.swift | 17 + Learn/Extensions/NSNumber.swift | 26 + Learn/Extensions/UITableViewCell.swift | 16 + Learn/Info.plist | 53 + Learn/Learn.entitlements | 14 + Learn/Lesson.swift | 53 + Learn/Lessons/TimeInRangeLesson.swift | 265 +++++ Learn/Managers/DataManager.swift | 133 +++ .../LessonConfigurationViewController.swift | 161 ++++ .../LessonResultsViewController.swift | 58 ++ .../LessonsViewController.swift | 46 + .../Base.lproj/Localizable.strings | 24 - .../StatusViewController.swift | 3 +- .../de.lproj/Localizable.strings | 25 - .../es.lproj/Localizable.strings | 25 - .../fr.lproj/Localizable.strings | 25 - .../it.lproj/Localizable.strings | 25 - .../nb.lproj/Localizable.strings | 24 - .../nl.lproj/Localizable.strings | 25 - .../pl.lproj/Localizable.strings | 24 - .../ru.lproj/Localizable.strings | 25 - .../zh-Hans.lproj/Localizable.strings | 25 - Loop.xcodeproj/project.pbxproj | 908 ++++++++++++++++-- .../xcshareddata/xcschemes/Cartfile.xcscheme | 80 ++ .../Complication - WatchApp.xcscheme | 2 +- .../xcschemes/DoseMathTests.xcscheme | 2 +- .../xcshareddata/xcschemes/Learn.xcscheme | 91 ++ .../xcschemes/Loop Status Extension.xcscheme | 2 +- .../xcshareddata/xcschemes/Loop.xcscheme | 2 +- .../xcshareddata/xcschemes/LoopTests.xcscheme | 2 +- .../Notification - WatchApp.xcscheme | 2 +- .../xcshareddata/xcschemes/WatchApp.xcscheme | 2 +- .../InsulinModelSettings+Loop.swift | 57 ++ Loop/Extensions/UITableViewCell.swift | 1 + Loop/Extensions/UserDefaults+Loop.swift | 27 - Loop/Loop.entitlements | 2 + Loop/Managers/AnalyticsManager.swift | 1 + Loop/Managers/DeviceDataManager.swift | 15 +- Loop/Managers/LoopDataManager.swift | 21 +- Loop/Managers/WatchDataManager.swift | 2 + Loop/Models/LoopSettings+Loop.swift | 1 + .../BolusViewController.swift | 1 + .../CarbAbsorptionViewController.swift | 1 + .../CarbEntryEditTableViewController.swift | 1 + .../CarbEntryTableViewController.swift | 1 + .../InsulinDeliveryTableViewController.swift | 1 + .../InsulinModelSettingsViewController.swift | 1 + .../PredictionTableViewController.swift | 1 + .../SettingsTableViewController.swift | 1 + .../StatusTableViewController.swift | 1 + .../GlucoseThreshold.swift | 12 +- LoopCore/HKUnit.swift | 20 + .../IdentifiableClass.swift | 4 +- LoopCore/Info.plist | 22 + .../ExponentialInsulinModelPreset.swift | 63 ++ .../Insulin/InsulinModelSettings.swift | 23 +- .../Insulin/WalshInsulinModel.swift | 12 - {Loop/Models => LoopCore}/Locked.swift | 8 +- LoopCore/LoopCore.h | 18 + .../Models => LoopCore}/LoopSettings.swift | 50 +- .../NSUserDefaults.swift | 12 +- .../PersistenceController.swift | 6 +- {Loop/Models => LoopCore}/Result.swift | 2 +- .../Extensions/NibLoadable.swift | 5 +- LoopUI/Managers/StatusChartsManager.swift | 1 + .../Controllers/ActionHUDController.swift | 1 + .../Managers/LoopDataManager.swift | 1 + .../Models/GlucoseChartData.swift | 4 +- 89 files changed, 2873 insertions(+), 553 deletions(-) rename {Loop/Views => Common/Extensions}/TextFieldTableViewCell.swift (92%) delete mode 100644 Common/Models/Insulin/ExponentialInsulinModelPreset.swift create mode 100644 Learn/AppDelegate.swift create mode 100644 Learn/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Learn/Assets.xcassets/Contents.json create mode 100644 Learn/Base.lproj/LaunchScreen.storyboard create mode 100644 Learn/Base.lproj/Main.storyboard create mode 100644 Learn/Configuration/DateEntry.swift create mode 100644 Learn/Configuration/NumberEntry.swift create mode 100644 Learn/Configuration/NumberRangeEntry.swift create mode 100644 Learn/Configuration/QuantityRangeEntry.swift create mode 100644 Learn/Display/DatesAndNumberCell.swift create mode 100644 Learn/Display/TextCell.swift create mode 100644 Learn/Extensions/Date.swift create mode 100644 Learn/Extensions/DateIntervalFormatter.swift create mode 100644 Learn/Extensions/NSNumber.swift create mode 100644 Learn/Extensions/UITableViewCell.swift create mode 100644 Learn/Info.plist create mode 100644 Learn/Learn.entitlements create mode 100644 Learn/Lesson.swift create mode 100644 Learn/Lessons/TimeInRangeLesson.swift create mode 100644 Learn/Managers/DataManager.swift create mode 100644 Learn/View Controllers/LessonConfigurationViewController.swift create mode 100644 Learn/View Controllers/LessonResultsViewController.swift create mode 100644 Learn/View Controllers/LessonsViewController.swift create mode 100644 Loop.xcodeproj/xcshareddata/xcschemes/Cartfile.xcscheme create mode 100644 Loop.xcodeproj/xcshareddata/xcschemes/Learn.xcscheme create mode 100644 Loop/Extensions/InsulinModelSettings+Loop.swift rename {Common/Models => LoopCore}/GlucoseThreshold.swift (75%) create mode 100644 LoopCore/HKUnit.swift rename {Common/Extensions => LoopCore}/IdentifiableClass.swift (79%) create mode 100644 LoopCore/Info.plist create mode 100644 LoopCore/Insulin/ExponentialInsulinModelPreset.swift rename {Common/Models => LoopCore}/Insulin/InsulinModelSettings.swift (82%) rename {Common/Models => LoopCore}/Insulin/WalshInsulinModel.swift (58%) rename {Loop/Models => LoopCore}/Locked.swift (82%) create mode 100644 LoopCore/LoopCore.h rename {Common/Models => LoopCore}/LoopSettings.swift (56%) rename {Common/Extensions => LoopCore}/NSUserDefaults.swift (92%) rename {Common/Extensions => LoopCore}/PersistenceController.swift (81%) rename {Loop/Models => LoopCore}/Result.swift (85%) rename {Common => LoopUI}/Extensions/NibLoadable.swift (73%) diff --git a/.travis.yml b/.travis.yml index ae15c2a041..bd1ad9d3b4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ before_script: script: # Build the app target - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme Loop build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO | xcpretty + - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme Learn build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO | xcpretty # Run the test target - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme LoopTests -destination 'name=iPhone SE' test | xcpretty - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme DoseMathTests -destination 'name=iPhone SE' test | xcpretty diff --git a/Cartfile.resolved b/Cartfile.resolved index f157ff4757..ddbfd0d55e 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,7 +1,7 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "ea1267791c66e884f1013fffd36faf4555cc6eaf" github "LoopKit/G4ShareSpy" "fed5a389e3e47e3a1953878dd21852aa5f44b360" -github "LoopKit/LoopKit" "797a4fffeb0b56c8ce47543ea6516b3b8c8e0857" +github "LoopKit/LoopKit" "c6e9f9200deec9a401ab1725e888451b17dee98a" github "LoopKit/dexcom-share-client-swift" "b0419edf55c7f389b36cb47dd5c376bbd3d03d69" github "i-schuetz/SwiftCharts" "0.6.2" -github "ps2/rileylink_ios" "23af71639041ca3a2a1b4120d3cd0a06c5d3fa01" +github "ps2/rileylink_ios" "8418b57c1983bdefaec7ccb456c86d5efacf40e7" diff --git a/Common/Extensions/HKUnit.swift b/Common/Extensions/HKUnit.swift index 961414a265..52b933a2c3 100644 --- a/Common/Extensions/HKUnit.swift +++ b/Common/Extensions/HKUnit.swift @@ -7,6 +7,7 @@ // import HealthKit +import LoopCore // Code in this extension is duplicated from: // https://github.com/LoopKit/LoopKit/blob/master/LoopKit/HKUnit.swift @@ -21,14 +22,6 @@ extension HKUnit { } } - static let milligramsPerDeciliter: HKUnit = { - return HKUnit.gramUnit(with: .milli).unitDivided(by: HKUnit.literUnit(with: .deci)) - }() - - static let millimolesPerLiter: HKUnit = { - return HKUnit.moleUnit(with: .milli, molarMass: HKUnitMolarMassBloodGlucose).unitDivided(by: HKUnit.liter()) - }() - var localizedShortUnitString: String { if self == HKUnit.millimolesPerLiter { return NSLocalizedString("mmol/L", comment: "The short unit display string for millimoles of glucose per liter") diff --git a/Common/Extensions/NSTimeInterval.swift b/Common/Extensions/NSTimeInterval.swift index be19ded20d..d24b718fd8 100644 --- a/Common/Extensions/NSTimeInterval.swift +++ b/Common/Extensions/NSTimeInterval.swift @@ -22,6 +22,10 @@ extension TimeInterval { return TimeInterval(hours: hours) } + static func days(_ days: Double) -> TimeInterval { + return TimeInterval(days: days) + } + init(minutes: Double) { self.init(minutes * 60) } @@ -30,6 +34,10 @@ extension TimeInterval { self.init(minutes: hours * 60) } + init(days: Double) { + self.init(hours: days * 24) + } + var minutes: Double { return self / 60.0 } diff --git a/Common/Extensions/NewCarbEntryIntent+Loop.swift b/Common/Extensions/NewCarbEntryIntent+Loop.swift index 11d21f0297..0f2419ffa9 100644 --- a/Common/Extensions/NewCarbEntryIntent+Loop.swift +++ b/Common/Extensions/NewCarbEntryIntent+Loop.swift @@ -6,6 +6,7 @@ // import Foundation +import LoopCore @available(iOS 12.0, watchOSApplicationExtension 5.0, *) extension NewCarbEntryIntent: IdentifiableClass { } diff --git a/Loop/Views/TextFieldTableViewCell.swift b/Common/Extensions/TextFieldTableViewCell.swift similarity index 92% rename from Loop/Views/TextFieldTableViewCell.swift rename to Common/Extensions/TextFieldTableViewCell.swift index 4334e6f4ea..82c5867cc3 100644 --- a/Loop/Views/TextFieldTableViewCell.swift +++ b/Common/Extensions/TextFieldTableViewCell.swift @@ -6,6 +6,7 @@ // import LoopKitUI +import LoopUI extension TextFieldTableViewCell: NibLoadable { } diff --git a/Common/Models/Insulin/ExponentialInsulinModelPreset.swift b/Common/Models/Insulin/ExponentialInsulinModelPreset.swift deleted file mode 100644 index 66644da1b8..0000000000 --- a/Common/Models/Insulin/ExponentialInsulinModelPreset.swift +++ /dev/null @@ -1,89 +0,0 @@ -// -// ExponentialInsulinModelPreset.swift -// Loop -// -// Copyright © 2017 LoopKit Authors. All rights reserved. -// - -import LoopKit - - -enum ExponentialInsulinModelPreset: String { - case humalogNovologAdult - case humalogNovologChild - case fiasp -} - - -// MARK: - Model generation -extension ExponentialInsulinModelPreset { - var actionDuration: TimeInterval { - switch self { - case .humalogNovologAdult: - return .minutes(360) - case .humalogNovologChild: - return .minutes(360) - case .fiasp: - return .minutes(360) - } - } - - var peakActivity: TimeInterval { - switch self { - case .humalogNovologAdult: - return .minutes(75) - case .humalogNovologChild: - return .minutes(65) - case .fiasp: - return .minutes(55) - } - } - - var model: InsulinModel { - return ExponentialInsulinModel(actionDuration: actionDuration, peakActivityTime: peakActivity) - } -} - - -// MARK: - Localization -extension ExponentialInsulinModelPreset { - var title: String { - switch self { - case .humalogNovologAdult: - return NSLocalizedString("Rapid-Acting – Adults", comment: "Title of insulin model preset") - case .humalogNovologChild: - return NSLocalizedString("Rapid-Acting – Children", comment: "Title of insulin model preset") - case .fiasp: - return NSLocalizedString("Fiasp", comment: "Title of insulin model preset") - } - } - - var subtitle: String? { - switch self { - case .humalogNovologAdult: - return NSLocalizedString("A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults.", comment: "Subtitle of Rapid-Acting – Adult preset") - case .humalogNovologChild: - return NSLocalizedString("An adjustment to the adult model based on empirical effects in children.", comment: "Subtitle of Rapid-Acting – Children preset") - case .fiasp: - return NSLocalizedString("A model based on the published absorption of Fiasp insulin.", comment: "Subtitle of Fiasp preset") - } - } -} - - -extension ExponentialInsulinModelPreset: InsulinModel { - var effectDuration: TimeInterval { - return model.effectDuration - } - - func percentEffectRemaining(at time: TimeInterval) -> Double { - return model.percentEffectRemaining(at: time) - } -} - - -extension ExponentialInsulinModelPreset: CustomDebugStringConvertible { - var debugDescription: String { - return "\(self.rawValue)(\(String(reflecting: model))" - } -} diff --git a/Common/Models/LoopSettingsUserInfo.swift b/Common/Models/LoopSettingsUserInfo.swift index 06efd35087..a6123825d8 100644 --- a/Common/Models/LoopSettingsUserInfo.swift +++ b/Common/Models/LoopSettingsUserInfo.swift @@ -5,6 +5,8 @@ // Copyright © 2018 LoopKit Authors. All rights reserved. // +import LoopCore + struct LoopSettingsUserInfo { let settings: LoopSettings diff --git a/DoseMathTests/DoseMathTests.swift b/DoseMathTests/DoseMathTests.swift index 7bde9f85e5..e69f5d34e5 100644 --- a/DoseMathTests/DoseMathTests.swift +++ b/DoseMathTests/DoseMathTests.swift @@ -9,6 +9,7 @@ import XCTest import HealthKit import LoopKit +import LoopCore extension XCTestCase { diff --git a/Learn/AppDelegate.swift b/Learn/AppDelegate.swift new file mode 100644 index 0000000000..7b1a7fe712 --- /dev/null +++ b/Learn/AppDelegate.swift @@ -0,0 +1,61 @@ +// +// AppDelegate.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + guard + let nav = window?.rootViewController as? UINavigationController, + let lessonsVC = nav.topViewController as? LessonsViewController + else { + return false + } + + let dataManager = DataManager() + dataManager.authorize({ + DispatchQueue.main.async { + lessonsVC.lessons = [ + TimeInRangeLesson(dataManager: dataManager) + ] + } + }) + + return true + } + + func applicationWillResignActive(_ application: UIApplication) { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. + } + + func applicationDidEnterBackground(_ application: UIApplication) { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } + + func applicationWillEnterForeground(_ application: UIApplication) { + // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(_ application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } + + func applicationWillTerminate(_ application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } + + +} + diff --git a/Learn/Assets.xcassets/AppIcon.appiconset/Contents.json b/Learn/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..d8db8d65fd --- /dev/null +++ b/Learn/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Learn/Assets.xcassets/Contents.json b/Learn/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/Learn/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Learn/Base.lproj/LaunchScreen.storyboard b/Learn/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000..bfa3612941 --- /dev/null +++ b/Learn/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Learn/Base.lproj/Main.storyboard b/Learn/Base.lproj/Main.storyboard new file mode 100644 index 0000000000..e11dcdf980 --- /dev/null +++ b/Learn/Base.lproj/Main.storyboard @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Learn/Configuration/DateEntry.swift b/Learn/Configuration/DateEntry.swift new file mode 100644 index 0000000000..a36a9c5b98 --- /dev/null +++ b/Learn/Configuration/DateEntry.swift @@ -0,0 +1,43 @@ +// +// DateEntry.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import LoopKitUI + + +class DateEntry { + private(set) var date: Date + let title: String + let mode: UIDatePicker.Mode + + init(date: Date, title: String, mode: UIDatePicker.Mode) { + self.date = date + self.title = title + self.mode = mode + } +} + +extension DateEntry: LessonCellProviding { + func registerCell(for tableView: UITableView) { + tableView.register(DateAndDurationTableViewCell.nib(), forCellReuseIdentifier: DateAndDurationTableViewCell.className) + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: DateAndDurationTableViewCell.className, for: indexPath) as! DateAndDurationTableViewCell + cell.delegate = self + cell.titleLabel.text = title + cell.datePicker.isEnabled = true + cell.datePicker.datePickerMode = mode + cell.date = date + return cell + } +} + +extension DateEntry: DatePickerTableViewCellDelegate { + func datePickerTableViewCellDidUpdateDate(_ cell: DatePickerTableViewCell) { + date = cell.datePicker.date + } +} diff --git a/Learn/Configuration/NumberEntry.swift b/Learn/Configuration/NumberEntry.swift new file mode 100644 index 0000000000..50eaaa04e4 --- /dev/null +++ b/Learn/Configuration/NumberEntry.swift @@ -0,0 +1,110 @@ +// +// NumberEntry.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import Foundation +import LoopKitUI + + +class TextEntry: TextFieldTableViewCellDelegate { + + func registerCell(for tableView: UITableView) { + tableView.register(TextFieldTableViewCell.nib(), forCellReuseIdentifier: TextFieldTableViewCell.className) + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> TextFieldTableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: TextFieldTableViewCell.className, for: indexPath) as! TextFieldTableViewCell + cell.delegate = self + return cell + } + + // MARK: - TextFieldTableViewCellDelegate + + func textFieldTableViewCellDidBeginEditing(_ cell: TextFieldTableViewCell) { + + } + + func textFieldTableViewCellDidEndEditing(_ cell: TextFieldTableViewCell) { + + } +} + + +class NumberEntry: TextEntry, LessonCellProviding { + + let formatter: NumberFormatter + private(set) var number: NSNumber? + let keyboardType: UIKeyboardType + let placeholder: String? + let unitString: String? + + init(number: NSNumber?, formatter: NumberFormatter, placeholder: String?, unitString: String?, keyboardType: UIKeyboardType) { + self.number = number + self.formatter = formatter + self.placeholder = placeholder + self.unitString = unitString + self.keyboardType = keyboardType + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = super.tableView(tableView, cellForRowAt: indexPath) + cell.textField.placeholder = placeholder + cell.unitLabel?.text = unitString + cell.textField.keyboardType = keyboardType + updateText(for: cell) + return cell + } + + override func textFieldTableViewCellDidEndEditing(_ cell: TextFieldTableViewCell) { + if let text = cell.textField.text { + number = formatter.number(from: text) + } else { + number = nil + } + + updateText(for: cell) + } + + private func updateText(for cell: TextFieldTableViewCell) { + if let number = number { + cell.textField.text = formatter.string(from: number) + } else { + cell.textField.text = nil + } + } +} + + +extension NumberEntry { + class func decimalEntry(value: Double?, unitString: String?) -> NumberEntry { + let number: NSNumber? + if let value = value { + number = NSNumber(value: value) + } else { + number = nil + } + + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + + return NumberEntry(number: number, formatter: formatter, placeholder: nil, unitString: unitString, keyboardType: .decimalPad) + } + + class func integerEntry(value: Int?, unitString: String?) -> NumberEntry { + let number: NSNumber? + if let value = value { + number = NSNumber(value: value) + } else { + number = nil + } + + + let formatter = NumberFormatter() + formatter.numberStyle = .none + + return NumberEntry(number: number, formatter: formatter, placeholder: nil, unitString: unitString, keyboardType: .decimalPad) + } +} diff --git a/Learn/Configuration/NumberRangeEntry.swift b/Learn/Configuration/NumberRangeEntry.swift new file mode 100644 index 0000000000..7382d99a5b --- /dev/null +++ b/Learn/Configuration/NumberRangeEntry.swift @@ -0,0 +1,66 @@ +// +// NumberRangeEntry.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import UIKit + + +class NumberRangeEntry: LessonSectionProviding { + let headerTitle: String? + + let footerTitle: String? = nil + + var cells: [LessonCellProviding] { + return numberCells + } + + var minValue: NSNumber? { + return numberCells.compactMap({ $0.number }).min() + } + + var maxValue: NSNumber? { + return numberCells.compactMap({ $0.number }).max() + } + + var range: Range? { + guard let minValue = minValue, let maxValue = maxValue else { + return nil + } + + return minValue..? { + guard let minValue = minValue, let maxValue = maxValue else { + return nil + } + + return minValue...maxValue + } + + private var numberCells: [NumberEntry] + + init(headerTitle: String?, minValue: NSNumber?, maxValue: NSNumber?, formatter: NumberFormatter, unitString: String?, keyboardType: UIKeyboardType) { + self.headerTitle = headerTitle + + self.numberCells = [ + NumberEntry( + number: minValue, + formatter: formatter, + placeholder: NSLocalizedString("Minimum", comment: "Placeholder for lower range entry"), + unitString: unitString, + keyboardType: keyboardType + ), + NumberEntry( + number: maxValue, + formatter: formatter, + placeholder: NSLocalizedString("Maximum", comment: "Placeholder for upper range entry"), + unitString: unitString, + keyboardType: keyboardType + ), + ] + } +} diff --git a/Learn/Configuration/QuantityRangeEntry.swift b/Learn/Configuration/QuantityRangeEntry.swift new file mode 100644 index 0000000000..fd151b8b05 --- /dev/null +++ b/Learn/Configuration/QuantityRangeEntry.swift @@ -0,0 +1,77 @@ +// +// QuantityRangeEntry.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import HealthKit +import LoopKit +import UIKit + + +class QuantityRangeEntry: LessonSectionProviding { + var headerTitle: String? { + return numberRange.headerTitle + } + + var footerTitle: String? { + return numberRange.footerTitle + } + + var cells: [LessonCellProviding] { + return numberRange.cells + } + + var minValue: HKQuantity? { + if let minValue = numberRange.minValue?.doubleValue { + return HKQuantity(unit: unit, doubleValue: minValue) + } else { + return nil + } + } + + var maxValue: HKQuantity? { + if let maxValue = numberRange.maxValue?.doubleValue { + return HKQuantity(unit: unit, doubleValue: maxValue) + } else { + return nil + } + } + + var range: Range? { + guard let minValue = minValue, let maxValue = maxValue else { + return nil + } + + return minValue..? { + guard let minValue = minValue, let maxValue = maxValue else { + return nil + } + + return minValue...maxValue + } + + private let numberRange: NumberRangeEntry + + let quantityFormatter: QuantityFormatter + + let unit: HKUnit + + init(headerTitle: String?, minValue: HKQuantity?, maxValue: HKQuantity?, quantityFormatter: QuantityFormatter, unit: HKUnit, keyboardType: UIKeyboardType) { + self.quantityFormatter = quantityFormatter + self.unit = unit + + numberRange = NumberRangeEntry( + headerTitle: headerTitle, + minValue: NSNumber(value: minValue?.doubleValue(for: unit)), + maxValue: NSNumber(value: maxValue?.doubleValue(for: unit)), + formatter: quantityFormatter.numberFormatter, + unitString: quantityFormatter.string(from: unit), + keyboardType: keyboardType + ) + } +} diff --git a/Learn/Display/DatesAndNumberCell.swift b/Learn/Display/DatesAndNumberCell.swift new file mode 100644 index 0000000000..be663f716a --- /dev/null +++ b/Learn/Display/DatesAndNumberCell.swift @@ -0,0 +1,43 @@ +// +// DatesAndNumberCell.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import UIKit + + +class DatesAndNumberCell: LessonCellProviding { + static let cellIdentifier = "DatesAndNumberCell" + + let date: DateInterval + let value: NSNumber + let dateFormatter: DateIntervalFormatter + let numberFormatter: NumberFormatter + + init(date: DateInterval, value: NSNumber, dateFormatter: DateIntervalFormatter, numberFormatter: NumberFormatter) { + self.date = date + self.value = value + self.dateFormatter = dateFormatter + self.numberFormatter = numberFormatter + } + + func registerCell(for tableView: UITableView) { + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell: UITableViewCell + + if let existingCell = tableView.dequeueReusableCell(withIdentifier: DatesAndNumberCell.cellIdentifier) { + cell = existingCell + } else { + cell = UITableViewCell(style: .value1, reuseIdentifier: DatesAndNumberCell.cellIdentifier) + } + + cell.textLabel?.text = dateFormatter.string(from: date) + cell.detailTextLabel?.text = numberFormatter.string(from: value) + + return cell + } +} diff --git a/Learn/Display/TextCell.swift b/Learn/Display/TextCell.swift new file mode 100644 index 0000000000..1b96291536 --- /dev/null +++ b/Learn/Display/TextCell.swift @@ -0,0 +1,51 @@ +// +// TextCell.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import UIKit + + +class TextCell: LessonCellProviding { + static let cellIdentifier = "TextCell" + + let text: String + let detailText: String? + let textColor: UIColor? + let detailTextColor: UIColor? + + init(text: String, detailText: String? = nil, textColor: UIColor? = nil, detailTextColor: UIColor? = nil) { + self.text = text + self.detailText = detailText + self.textColor = textColor + self.detailTextColor = detailTextColor + } + + func registerCell(for tableView: UITableView) { + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell: UITableViewCell + + if let existingCell = tableView.dequeueReusableCell(withIdentifier: TextCell.cellIdentifier) { + cell = existingCell + } else { + cell = UITableViewCell(style: .value1, reuseIdentifier: TextCell.cellIdentifier) + } + + cell.textLabel?.text = text + cell.detailTextLabel?.text = detailText + + if let color = textColor { + cell.textLabel?.textColor = color + } + + if let color = detailTextColor { + cell.detailTextLabel?.textColor = color + } + + return cell + } +} diff --git a/Learn/Extensions/Date.swift b/Learn/Extensions/Date.swift new file mode 100644 index 0000000000..bd2f73b890 --- /dev/null +++ b/Learn/Extensions/Date.swift @@ -0,0 +1,21 @@ +// +// Date.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import Foundation + + +extension Date: Strideable { + public typealias Stride = TimeInterval + + public func distance(to other: Date) -> TimeInterval { + return other.timeIntervalSince(self) + } + + public func advanced(by n: TimeInterval) -> Date { + return addingTimeInterval(n) + } +} diff --git a/Learn/Extensions/DateIntervalFormatter.swift b/Learn/Extensions/DateIntervalFormatter.swift new file mode 100644 index 0000000000..33a16e4c2a --- /dev/null +++ b/Learn/Extensions/DateIntervalFormatter.swift @@ -0,0 +1,17 @@ +// +// DateIntervalFormatter.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import Foundation + + +extension DateIntervalFormatter { + convenience init(dateStyle: DateIntervalFormatter.Style, timeStyle: DateIntervalFormatter.Style) { + self.init() + self.dateStyle = dateStyle + self.timeStyle = timeStyle + } +} diff --git a/Learn/Extensions/NSNumber.swift b/Learn/Extensions/NSNumber.swift new file mode 100644 index 0000000000..913f5f8bc4 --- /dev/null +++ b/Learn/Extensions/NSNumber.swift @@ -0,0 +1,26 @@ +// +// NSNumber.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import Foundation + + +extension NSNumber: Comparable { + public static func < (lhs: NSNumber, rhs: NSNumber) -> Bool { + return lhs.compare(rhs) == .orderedAscending + } +} + + +extension NSNumber { + convenience init?(value: Double?) { + if let value = value { + self.init(value: value) + } else { + return nil + } + } +} diff --git a/Learn/Extensions/UITableViewCell.swift b/Learn/Extensions/UITableViewCell.swift new file mode 100644 index 0000000000..369855d0f6 --- /dev/null +++ b/Learn/Extensions/UITableViewCell.swift @@ -0,0 +1,16 @@ +// +// UITableViewCell.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import LoopCore +import LoopKitUI +import LoopUI + + +extension DateAndDurationTableViewCell: NibLoadable { } + + +extension TextButtonTableViewCell: IdentifiableClass { } diff --git a/Learn/Info.plist b/Learn/Info.plist new file mode 100644 index 0000000000..48ea02de2d --- /dev/null +++ b/Learn/Info.plist @@ -0,0 +1,53 @@ + + + + + MainAppBundleIdentifier + $(MAIN_APP_BUNDLE_IDENTIFIER) + AppGroupIdentifier + $(APP_GROUP_IDENTIFIER) + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.9.4 dev + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSRequiresIPhoneOS + + NSHealthShareUsageDescription + Meal data from the Health database is used to determine glucose effects. Glucose data from the Health database is used for graphing and momentum calculation. + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + healthkit + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortraitUpsideDown + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Learn/Learn.entitlements b/Learn/Learn.entitlements new file mode 100644 index 0000000000..b09682c70b --- /dev/null +++ b/Learn/Learn.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.developer.healthkit + + com.apple.developer.healthkit.access + + com.apple.security.application-groups + + $(APP_GROUP_IDENTIFIER) + + + diff --git a/Learn/Lesson.swift b/Learn/Lesson.swift new file mode 100644 index 0000000000..f9f2a0bbf7 --- /dev/null +++ b/Learn/Lesson.swift @@ -0,0 +1,53 @@ +// +// Lesson.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import Foundation +import UIKit + + +protocol Lesson { + init(dataManager: DataManager) + + var title: String { get } + + var subtitle: String { get } + + var configurationSections: [LessonSectionProviding] { get } + + func execute(completion: @escaping (_ resultSections: [LessonSectionProviding]) -> Void) +} + + +protocol LessonSectionProviding { + var headerTitle: String? { get } + + var footerTitle: String? { get } + + var cells: [LessonCellProviding] { get } +} + + +protocol LessonCellProviding { + func registerCell(for tableView: UITableView) + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell +} + + +struct LessonSection: LessonSectionProviding { + let headerTitle: String? + + let footerTitle: String? + + let cells: [LessonCellProviding] + + init(headerTitle: String? = nil, footerTitle: String? = nil, cells: [LessonCellProviding]) { + self.headerTitle = headerTitle + self.footerTitle = footerTitle + self.cells = cells + } +} diff --git a/Learn/Lessons/TimeInRangeLesson.swift b/Learn/Lessons/TimeInRangeLesson.swift new file mode 100644 index 0000000000..34d43e86e6 --- /dev/null +++ b/Learn/Lessons/TimeInRangeLesson.swift @@ -0,0 +1,265 @@ +// +// LessonPlayground.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import Foundation +import LoopCore +import LoopKit +import LoopKitUI +import LoopUI +import HealthKit +import os.log + + +final class TimeInRangeLesson: Lesson { + let title = NSLocalizedString("Time in Range", comment: "Lesson title") + + let subtitle = NSLocalizedString("Computes the percentage of glucose measurements within a specified range", comment: "Lesson subtitle") + + let configurationSections: [LessonSectionProviding] + + private let dataManager: DataManager + + private let glucoseUnit: HKUnit + + private let glucoseFormatter = QuantityFormatter() + + private let dateEntry: DateEntry + + private let weeksEntry: NumberEntry + + private let rangeEntry: QuantityRangeEntry + + init(dataManager: DataManager) { + self.dataManager = dataManager + self.glucoseUnit = dataManager.glucoseStore.preferredUnit ?? .milligramsPerDeciliter + + let twoWeeksAgo = Calendar.current.date(byAdding: DateComponents(weekOfYear: -2), to: Date())! + + glucoseFormatter.setPreferredNumberFormatter(for: glucoseUnit) + + // TODO: Add a date components picker cell, and combine into a "DateIntervalEntry" section + dateEntry = DateEntry( + date: Calendar.current.startOfDay(for: twoWeeksAgo), + title: NSLocalizedString("Start Date", comment: "Title of config entry"), + mode: .date + ) + weeksEntry = NumberEntry.integerEntry( + value: 2, + unitString: NSLocalizedString("Weeks", comment: "Unit string for a count of calendar weeks") + ) + + rangeEntry = QuantityRangeEntry.glucoseRange(quantityFormatter: glucoseFormatter, unit: glucoseUnit) + + self.configurationSections = [ + LessonSection(headerTitle: nil, footerTitle: nil, cells: [dateEntry, weeksEntry]), + rangeEntry + ] + } + + func execute(completion: @escaping ([LessonSectionProviding]) -> Void) { + guard let weeks = weeksEntry.number?.intValue, let closedRange = rangeEntry.closedRange else { + // TODO: Cleaner error presentation + completion([LessonSection(headerTitle: "Error: Please fill out all fields", footerTitle: nil, cells: [])]) + return + } + + let start = dateEntry.date + let calculator = TimeInRangeCalculator(dataManager: dataManager, start: start, duration: DateComponents(weekOfYear: weeks), range: closedRange) + + calculator.perform { result in + switch result { + case .failure(let error): + completion([ + LessonSection(cells: [TextCell(text: String(describing: error))]) + ]) + case .success(let resultsByDay): + guard resultsByDay.count > 0 else { + completion([ + LessonSection(cells: [TextCell(text: NSLocalizedString("No data available", comment: "Lesson result text for no data"))]) + ]) + return + } + + let dateFormatter = DateIntervalFormatter(dateStyle: .short, timeStyle: .none) + let numberFormatter = NumberFormatter() + numberFormatter.numberStyle = .percent + + var aggregator = TimeInRangeAggregator() + resultsByDay.forEach({ (pair) in + aggregator.add(percentInRange: pair.value, for: pair.key) + }) + + completion([ + TimesInRangeSection( + ranges: aggregator.dictionary, + dateFormatter: dateFormatter, + numberFormatter: numberFormatter + ), + TimesInRangeSection( + ranges: resultsByDay, + dateFormatter: dateFormatter, + numberFormatter: numberFormatter + ) + ]) + } + } + } +} + + +private extension QuantityRangeEntry { + class func glucoseRange(quantityFormatter: QuantityFormatter, unit: HKUnit) -> QuantityRangeEntry { + return QuantityRangeEntry( + headerTitle: NSLocalizedString("Range", comment: "Section title for glucose range"), + minValue: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 80), + maxValue: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 160), + quantityFormatter: quantityFormatter, + unit: unit, + keyboardType: unit == .milligramsPerDeciliter ? .numberPad : .decimalPad + ) + } +} + + +class TimesInRangeSection: LessonSectionProviding { + let headerTitle: String? = nil + let footerTitle: String? = nil + + let cells: [LessonCellProviding] + + init(ranges: [DateInterval: Double], dateFormatter: DateIntervalFormatter, numberFormatter: NumberFormatter) { + cells = ranges.sorted(by: { $0.0 < $1.0 }).map { pair -> LessonCellProviding in + DatesAndNumberCell(date: pair.key, value: NSNumber(value: pair.value), dateFormatter: dateFormatter, numberFormatter: numberFormatter) + } + } +} + + +struct TimeInRangeAggregator { + private var count = 0 + private var sum: Double = 0 + var allDates: DateInterval? + + var averagePercentInRange: Double? { + guard count > 0 else { + return nil + } + + return sum / Double(count) + } + + var dictionary: [DateInterval: Double] { + guard let allDates = allDates, let averagePercentInRange = averagePercentInRange else { + return [:] + } + + return [allDates: averagePercentInRange] + } + + mutating func add(percentInRange: Double, for dates: DateInterval) { + sum += percentInRange + count += 1 + + if let allDates = self.allDates { + self.allDates = DateInterval(start: min(allDates.start, dates.start), end: max(allDates.end, dates.end)) + } else { + self.allDates = dates + } + } +} + + +/// Time-in-range, e.g. "2 weeks starting on March 5" +private class TimeInRangeCalculator { + let dataManager: DataManager + let start: Date + let duration: DateComponents + let range: ClosedRange + + init(dataManager: DataManager, start: Date, duration: DateComponents, range: ClosedRange) { + self.dataManager = dataManager + self.start = start + self.duration = duration + self.range = range + + log = OSLog(subsystem: "com.loopkit.Learn", category: String(describing: type(of: self))) + } + + private let log: OSLog + + private let unit = HKUnit.milligramsPerDeciliter + + func perform(completion: @escaping (_ result: Result<[DateInterval: Double]>) -> Void) { + // Compute the end date + guard let end = Calendar.current.date(byAdding: duration, to: start) else { + fatalError("Unable to resolve duration: \(duration)") + } + + os_log(.default, "Computing Time in range from %{public}@ for %{public}@ between %{public}@", String(describing: start), String(describing: end), String(describing: range)) + + // Paginate into 24-hour blocks + let lockedResults = Locked([DateInterval: Double]()) + var anyError: Error? + + let group = DispatchGroup() + + var segmentStart = start + + Calendar.current.enumerateDates(startingAfter: start, matching: DateComponents(hour: 0), matchingPolicy: .nextTime) { (date, _, stop) in + guard let date = date else { + stop = true + return + } + + let interval = DateInterval(start: segmentStart, end: min(end, date)) + + guard interval.duration > 0 else { + stop = true + return + } + + os_log(.default, "Fetching samples in %{public}@", String(describing: interval)) + + group.enter() + dataManager.glucoseStore.getGlucoseSamples(start: interval.start, end: interval.end) { (result) in + switch result { + case .failure(let error): + os_log(.error, log: self.log, "Failed to fetch samples: %{public}@", String(describing: error)) + anyError = error + case .success(let samples): + var inRangeCount = 0 + var totalCount = 0 + + for sample in samples { + if self.range.contains(sample.quantity) { + inRangeCount += 1 + } + totalCount += 1 + } + + if totalCount > 0 { + _ = lockedResults.mutate({ (results) in + results[interval] = Double(inRangeCount) / Double(totalCount) + }) + } + } + + group.leave() + } + + segmentStart = interval.end + } + + group.notify(queue: DispatchQueue.main) { + if let error = anyError { + completion(.failure(error)) + } else { + completion(.success(lockedResults.value)) + } + } + } +} diff --git a/Learn/Managers/DataManager.swift b/Learn/Managers/DataManager.swift new file mode 100644 index 0000000000..7082b5e6ff --- /dev/null +++ b/Learn/Managers/DataManager.swift @@ -0,0 +1,133 @@ +// +// DataManager.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import Foundation +import HealthKit +import LoopKit +import LoopCore + + +final class DataManager { + let carbStore: CarbStore + + let doseStore: DoseStore + + let glucoseStore: GlucoseStore + + let settings: LoopSettings + + init( + basalRateSchedule: BasalRateSchedule? = UserDefaults.appGroup?.basalRateSchedule, + carbRatioSchedule: CarbRatioSchedule? = UserDefaults.appGroup?.carbRatioSchedule, + insulinModelSettings: InsulinModelSettings? = UserDefaults.appGroup?.insulinModelSettings, + insulinSensitivitySchedule: InsulinSensitivitySchedule? = UserDefaults.appGroup?.insulinSensitivitySchedule, + settings: LoopSettings = UserDefaults.appGroup?.loopSettings ?? LoopSettings() + ) { + self.settings = settings + + let healthStore = HKHealthStore() + let cacheStore = PersistenceController.controllerInAppGroupDirectory(isReadOnly: true) + + carbStore = CarbStore( + healthStore: healthStore, + cacheStore: cacheStore, + observationEnabled: false, + carbRatioSchedule: carbRatioSchedule, + insulinSensitivitySchedule: insulinSensitivitySchedule + ) + + doseStore = DoseStore( + healthStore: healthStore, + cacheStore: cacheStore, + observationEnabled: false, + insulinModel: insulinModelSettings?.model, + basalProfile: basalRateSchedule, + insulinSensitivitySchedule: insulinSensitivitySchedule + ) + + glucoseStore = GlucoseStore( + healthStore: healthStore, + cacheStore: cacheStore, + observationEnabled: false + ) + } +} + + +// MARK: - Thread-safe Preferences +extension DataManager { + /// The daily schedule of basal insulin rates + var basalRateSchedule: BasalRateSchedule? { + return doseStore.basalProfile + } + + /// The daily schedule of carbs-to-insulin ratios + /// This is measured in grams/Unit + var carbRatioSchedule: CarbRatioSchedule? { + return carbStore.carbRatioSchedule + } + + /// The length of time insulin has an effect on blood glucose + var insulinModelSettings: InsulinModelSettings? { + guard let model = doseStore.insulinModel else { + return nil + } + + return InsulinModelSettings(model: model) + } + + /// The daily schedule of insulin sensitivity (also known as ISF) + /// This is measured in /Unit + var insulinSensitivitySchedule: InsulinSensitivitySchedule? { + return carbStore.insulinSensitivitySchedule + } +} + + +// MARK: - HealthKit Setup +extension DataManager { + var healthStore: HKHealthStore { + return carbStore.healthStore + } + + /// All the HealthKit types to be read and shared by stores + private var sampleTypes: Set { + return Set([ + glucoseStore.sampleType, + carbStore.sampleType, + doseStore.sampleType, + ].compactMap { $0 }) + } + + /// True if any stores require HealthKit authorization + var authorizationRequired: Bool { + return glucoseStore.authorizationRequired || + carbStore.authorizationRequired || + doseStore.authorizationRequired + } + + /// True if the user has explicitly denied access to any stores' HealthKit types + private var sharingDenied: Bool { + return glucoseStore.sharingDenied || + carbStore.sharingDenied || + doseStore.sharingDenied + } + + func authorize(_ completion: @escaping () -> Void) { + // Authorize all types at once for simplicity + carbStore.healthStore.requestAuthorization(toShare: [], read: sampleTypes) { (success, error) in + if success { + // Call the individual authorization methods to trigger query creation + self.carbStore.authorize(toShare: false, { _ in }) + self.doseStore.insulinDeliveryStore.authorize(toShare: false, { _ in }) + self.glucoseStore.authorize(toShare: false, { _ in }) + } + + completion() + } + } +} diff --git a/Learn/View Controllers/LessonConfigurationViewController.swift b/Learn/View Controllers/LessonConfigurationViewController.swift new file mode 100644 index 0000000000..c6476f2119 --- /dev/null +++ b/Learn/View Controllers/LessonConfigurationViewController.swift @@ -0,0 +1,161 @@ +// +// LessonConfigurationViewController.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import UIKit +import LoopKitUI + +class LessonConfigurationViewController: UITableViewController { + + var lesson: Lesson! + + private enum State { + case editing + case executing + } + + private var state = State.editing { + didSet { + guard state != oldValue else { + return + } + + if let cell = tableView.cellForRow(at: IndexPath(row: 0, section: lesson.configurationSections.count)) as? TextButtonTableViewCell { + cell.isLoading = state == .executing + cell.isEnabled = state == .editing + } + } + } + + override func viewDidLoad() { + super.viewDidLoad() + + title = lesson.title + tableView.estimatedRowHeight = 44 + tableView.rowHeight = UITableView.automaticDimension + + for section in lesson.configurationSections { + for cell in section.cells { + cell.registerCell(for: self.tableView) + } + } + + tableView.register(TextButtonTableViewCell.self, forCellReuseIdentifier: TextButtonTableViewCell.className) + } + + /// If a tap occurs in the table view, but not on any cells, dismiss any active edits + @IBAction private func dismissActiveEditing(gestureRecognizer: UITapGestureRecognizer) { + let tapPoint = gestureRecognizer.location(in: tableView) + guard tableView.indexPathForRow(at: tapPoint) == nil else { + return + } + + tableView.endEditing(false) + tableView.beginUpdates() + hideDatePickerCells() + tableView.endUpdates() + } + + // MARK: - Table view data source + + override func numberOfSections(in tableView: UITableView) -> Int { + return lesson.configurationSections.count + 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if section == lesson.configurationSections.count { + return 1 + } else { + return lesson.configurationSections[section].cells.count + } + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + if indexPath.section == lesson.configurationSections.count { + let cell = tableView.dequeueReusableCell(withIdentifier: TextButtonTableViewCell.className, for: indexPath) as! TextButtonTableViewCell + cell.textLabel?.text = NSLocalizedString("Continue", comment: "Title of the button to begin lesson execution") + + switch state { + case .editing: + cell.isEnabled = true + cell.isLoading = false + case .executing: + cell.isEnabled = false + cell.isLoading = true + } + + return cell + } else { + return lesson.configurationSections[indexPath.section].cells[indexPath.item].tableView(tableView, cellForRowAt: indexPath) + } + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + if section < lesson.configurationSections.count { + return lesson.configurationSections[section].headerTitle + } else { + return nil + } + } + + override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { + if section < lesson.configurationSections.count { + return lesson.configurationSections[section].footerTitle + } else { + return nil + } + } + + // MARK: - UITableViewDelegate + + override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { + return state == .editing + } + + override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { + guard case .editing = state else { + return nil + } + + tableView.endEditing(false) + tableView.beginUpdates() + hideDatePickerCells(excluding: indexPath) + return indexPath + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.endUpdates() + tableView.deselectRow(at: indexPath, animated: true) + + if indexPath.section == lesson.configurationSections.count { + state = .executing + + lesson.execute { resultSections in + dispatchPrecondition(condition: .onQueue(.main)) + + self.performSegue(withIdentifier: LessonResultsViewController.className, sender: resultSections) + + self.state = .editing + } + } + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + super.prepare(for: segue, sender: sender) + + if let results = sender as? [LessonSectionProviding], let destination = segue.destination as? LessonResultsViewController { + destination.lesson = lesson + destination.results = results + } + } +} + + +extension LessonConfigurationViewController: DatePickerTableViewCellDelegate { + func datePickerTableViewCellDidUpdateDate(_ cell: DatePickerTableViewCell) { + + } +} diff --git a/Learn/View Controllers/LessonResultsViewController.swift b/Learn/View Controllers/LessonResultsViewController.swift new file mode 100644 index 0000000000..e9c45b6b44 --- /dev/null +++ b/Learn/View Controllers/LessonResultsViewController.swift @@ -0,0 +1,58 @@ +// +// LessonResultsViewController.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import LoopCore +import UIKit + + +class LessonResultsViewController: UITableViewController, IdentifiableClass { + + var lesson: Lesson! + + var results: [LessonSectionProviding] = [] { + didSet { + if isViewLoaded { + tableView.reloadData() + } + } + } + + override func viewDidLoad() { + super.viewDidLoad() + + title = lesson.title + + for section in results { + for cell in section.cells { + cell.registerCell(for: self.tableView) + } + } + } + + // MARK: - Table view data source + + override func numberOfSections(in tableView: UITableView) -> Int { + return results.count + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return results[section].cells.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + return results[indexPath.section].cells[indexPath.row].tableView(tableView, cellForRowAt: indexPath) + } + + override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { + return nil + } + + override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { + return false + } + +} diff --git a/Learn/View Controllers/LessonsViewController.swift b/Learn/View Controllers/LessonsViewController.swift new file mode 100644 index 0000000000..a5c3b4ef7a --- /dev/null +++ b/Learn/View Controllers/LessonsViewController.swift @@ -0,0 +1,46 @@ +// +// LessonsViewController.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import UIKit + +class LessonsViewController: UITableViewController { + + var lessons: [Lesson] = [] { + didSet { + if isViewLoaded { + tableView.reloadData() + } + } + } + + // MARK: - Table view data source + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return lessons.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "Lesson", for: indexPath) + let lesson = lessons[indexPath.row] + + cell.textLabel?.text = lesson.title + cell.detailTextLabel?.text = lesson.subtitle + + return cell + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + super.prepare(for: segue, sender: sender) + + if let configVC = segue.destination as? LessonConfigurationViewController, + let cell = sender as? UITableViewCell, + let indexPath = tableView.indexPath(for: cell) + { + configVC.lesson = lessons[indexPath.row] + } + } +} diff --git a/Loop Status Extension/Base.lproj/Localizable.strings b/Loop Status Extension/Base.lproj/Localizable.strings index 4c9097d64c..d6bea75c74 100644 --- a/Loop Status Extension/Base.lproj/Localizable.strings +++ b/Loop Status Extension/Base.lproj/Localizable.strings @@ -1,24 +1,12 @@ /* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ "%1$@ v%2$@" = "%1$@ v%2$@"; -/* Subtitle of Fiasp preset */ -"A model based on the published absorption of Fiasp insulin." = "A model based on the published absorption of Fiasp insulin."; - -/* Subtitle of Rapid-Acting – Adult preset */ -"A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults." = "A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults."; - -/* Subtitle of Rapid-Acting – Children preset */ -"An adjustment to the adult model based on empirical effects in children." = "An adjustment to the adult model based on empirical effects in children."; - /* The short unit display string for decibles */ "dB" = "dB"; /* The subtitle format describing eventual glucose. (1: localized glucose value description) (2: localized glucose units description) */ "Eventually %1$@ %2$@" = "Eventually %1$@ %2$@"; -/* Title of insulin model preset */ -"Fiasp" = "Fiasp"; - /* The short unit display string for grams */ "g" = "g"; @@ -34,17 +22,5 @@ /* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ "QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; -/* Title of insulin model preset */ -"Rapid-Acting – Adults" = "Rapid-Acting – Adults"; - -/* Title of insulin model preset */ -"Rapid-Acting – Children" = "Rapid-Acting – Children"; - -/* Subtitle description of Walsh insulin model setting */ -"The legacy model used by Loop, allowing customization of action duration." = "The legacy model used by Loop, allowing customization of action duration."; - /* The short unit display string for international units of insulin */ "U" = "U"; - -/* Title of insulin model setting */ -"Walsh" = "Walsh"; diff --git a/Loop Status Extension/StatusViewController.swift b/Loop Status Extension/StatusViewController.swift index 29176c2a2a..b8657a6310 100644 --- a/Loop Status Extension/StatusViewController.swift +++ b/Loop Status Extension/StatusViewController.swift @@ -10,6 +10,7 @@ import CoreData import HealthKit import LoopKit import LoopKitUI +import LoopCore import LoopUI import NotificationCenter import UIKit @@ -60,7 +61,7 @@ class StatusViewController: UIViewController, NCWidgetProviding { var statusExtensionContext: StatusExtensionContext? - lazy var defaults = UserDefaults(suiteName: Bundle.main.appGroupSuiteName) + lazy var defaults = UserDefaults.appGroup private var observers: [Any] = [] diff --git a/Loop Status Extension/de.lproj/Localizable.strings b/Loop Status Extension/de.lproj/Localizable.strings index e18f2cf64d..9647565cac 100644 --- a/Loop Status Extension/de.lproj/Localizable.strings +++ b/Loop Status Extension/de.lproj/Localizable.strings @@ -1,24 +1,12 @@ /* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ "%1$@ v%2$@" = "%1$@ v%2$@"; -/* Subtitle of Fiasp preset */ -"A model based on the published absorption of Fiasp insulin." = "Ein Modell basierend auf der veröffentlichten Absorption von Fiasp-Insulin."; - -/* Subtitle of Rapid-Acting – Adult preset */ -"A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults." = "Ein Modell auf der Grundlage der veröffentlichten Absorption von Humalog, Novolog und Apidra Insulin bei Erwachsenen."; - -/* Subtitle of Rapid-Acting – Children preset */ -"An adjustment to the adult model based on empirical effects in children." = "Eine Anpassung an das Erwachsenenmodell basierend auf empirischen Effekten bei Kindern."; - /* The short unit display string for decibles */ "dB" = "dB"; /* The subtitle format describing eventual glucose. (1: localized glucose value description) (2: localized glucose units description) */ "Eventually %1$@ %2$@" = "Schließlich %1$@ %2$@"; -/* Title of insulin model preset */ -"Fiasp" = "Fiasp"; - /* The short unit display string for grams */ "g" = "g"; @@ -34,18 +22,5 @@ /* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ "QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; -/* Title of insulin model preset */ -"Rapid-Acting – Adults" = "Schnell handelnd - Erwachsene"; - -/* Title of insulin model preset */ -"Rapid-Acting – Children" = "Schnell handelnd - Kinder"; - -/* Subtitle description of Walsh insulin model setting */ -"The legacy model used by Loop, allowing customization of action duration." = "Das Legacy-Modell, das von Loop verwendet wird und die Anpassung der Aktionsdauer ermöglicht."; - /* The short unit display string for international units of insulin */ "U" = "E"; - -/* Title of insulin model setting */ -"Walsh" = "Walsh"; - diff --git a/Loop Status Extension/es.lproj/Localizable.strings b/Loop Status Extension/es.lproj/Localizable.strings index 689064591d..28d6aca859 100644 --- a/Loop Status Extension/es.lproj/Localizable.strings +++ b/Loop Status Extension/es.lproj/Localizable.strings @@ -1,24 +1,12 @@ /* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ "%1$@ v%2$@" = "%1$@ v%2$@"; -/* Subtitle of Fiasp preset */ -"A model based on the published absorption of Fiasp insulin." = "Un modelo basado en la publicación de la absorción de insulina Fiasp."; - -/* Subtitle of Rapid-Acting – Adult preset */ -"A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults." = "Un modelo basado en la publicación de la absorción de insulina Humalog, Novolog y Apidra en adultos."; - -/* Subtitle of Rapid-Acting – Children preset */ -"An adjustment to the adult model based on empirical effects in children." = "Un ajuste al modelo adulto basado en los efectos empíricos en niños."; - /* The short unit display string for decibles */ "dB" = "dB"; /* The subtitle format describing eventual glucose. (1: localized glucose value description) (2: localized glucose units description) */ "Eventually %1$@ %2$@" = "Eventualmente %1$@ %2$@"; -/* Title of insulin model preset */ -"Fiasp" = "Fiasp"; - /* The short unit display string for grams */ "g" = "g"; @@ -34,18 +22,5 @@ /* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ "QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; -/* Title of insulin model preset */ -"Rapid-Acting – Adults" = "Acción Rápida — Adultos"; - -/* Title of insulin model preset */ -"Rapid-Acting – Children" = "Acción Rápida — Niños"; - -/* Subtitle description of Walsh insulin model setting */ -"The legacy model used by Loop, allowing customization of action duration." = "El model utilizado por ediciones iniciales de Loop, permite ajustar duración de acción."; - /* The short unit display string for international units of insulin */ "U" = "U"; - -/* Title of insulin model setting */ -"Walsh" = "Walsh"; - diff --git a/Loop Status Extension/fr.lproj/Localizable.strings b/Loop Status Extension/fr.lproj/Localizable.strings index 1baaaf0570..3504d07581 100644 --- a/Loop Status Extension/fr.lproj/Localizable.strings +++ b/Loop Status Extension/fr.lproj/Localizable.strings @@ -1,24 +1,12 @@ /* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ "%1$@ v%2$@" = "%1$@ v%2$@"; -/* Subtitle of Fiasp preset */ -"A model based on the published absorption of Fiasp insulin." = "Un modèle basé sur l’absorption publiée de l’insuline FIASP."; - -/* Subtitle of Rapid-Acting – Adult preset */ -"A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults." = "Un modèle basé sur l’absorption publiée de l’Hunalog, Novolog, et Apidra chez l’adulte."; - -/* Subtitle of Rapid-Acting – Children preset */ -"An adjustment to the adult model based on empirical effects in children." = "Un ajustement au modèle adulte basé sur des effets empiriques chez les enfants."; - /* The short unit display string for decibles */ "dB" = "dB"; /* The subtitle format describing eventual glucose. (1: localized glucose value description) (2: localized glucose units description) */ "Eventually %1$@ %2$@" = "Éventuellement %1$@ %2$@"; -/* Title of insulin model preset */ -"Fiasp" = "Fiasp"; - /* The short unit display string for grams */ "g" = "g"; @@ -34,18 +22,5 @@ /* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ "QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; -/* Title of insulin model preset */ -"Rapid-Acting – Adults" = "Action rapide - Adulte"; - -/* Title of insulin model preset */ -"Rapid-Acting – Children" = "Action rapide - Enfant"; - -/* Subtitle description of Walsh insulin model setting */ -"The legacy model used by Loop, allowing customization of action duration." = "Le modèle hérité utilisé par Loop, permettant de personnaliser la durée de l’action."; - /* The short unit display string for international units of insulin */ "U" = "U"; - -/* Title of insulin model setting */ -"Walsh" = "Walsh"; - diff --git a/Loop Status Extension/it.lproj/Localizable.strings b/Loop Status Extension/it.lproj/Localizable.strings index 732d79d715..d50f79d31d 100644 --- a/Loop Status Extension/it.lproj/Localizable.strings +++ b/Loop Status Extension/it.lproj/Localizable.strings @@ -1,24 +1,12 @@ /* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ "%1$@ v%2$@" = "%1$@ v%2$@"; -/* Subtitle of Fiasp preset */ -"A model based on the published absorption of Fiasp insulin." = "Modello basato sull'assorbimento dell'insulina Fiasp."; - -/* Subtitle of Rapid-Acting – Adult preset */ -"A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults." = "Modello basato sull'assorbimento negli adulti dell'insulina Humalog, Novolog ed Apidra."; - -/* Subtitle of Rapid-Acting – Children preset */ -"An adjustment to the adult model based on empirical effects in children." = "Un adattamento al modello adulto basato su effetti empirici nei bambini."; - /* The short unit display string for decibles */ "dB" = "dB"; /* The subtitle format describing eventual glucose. (1: localized glucose value description) (2: localized glucose units description) */ "Eventually %1$@ %2$@" = "Probabile Glic. %1$@ %2$@"; -/* Title of insulin model preset */ -"Fiasp" = "Fiasp"; - /* The short unit display string for grams */ "g" = "g"; @@ -34,18 +22,5 @@ /* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ "QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; -/* Title of insulin model preset */ -"Rapid-Acting – Adults" = "Insulina ultrarapida – Adulti"; - -/* Title of insulin model preset */ -"Rapid-Acting – Children" = "Insulina ultrarapida – Bambini"; - -/* Subtitle description of Walsh insulin model setting */ -"The legacy model used by Loop, allowing customization of action duration." = "ll modello legacy utilizzato da Loop, che consente la personalizzazione della durata dell'azione."; - /* The short unit display string for international units of insulin */ "U" = "U"; - -/* Title of insulin model setting */ -"Walsh" = "Walsh"; - diff --git a/Loop Status Extension/nb.lproj/Localizable.strings b/Loop Status Extension/nb.lproj/Localizable.strings index 766f3ff5d7..8b9c8d3ca4 100644 --- a/Loop Status Extension/nb.lproj/Localizable.strings +++ b/Loop Status Extension/nb.lproj/Localizable.strings @@ -1,24 +1,12 @@ /* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ "%1$@ v%2$@" = "%1$@ v%2$@"; -/* Subtitle of Fiasp preset */ -"A model based on the published absorption of Fiasp insulin." = "En modell basert på publiserte data for absorpsjon av Fiasp insulin."; - -/* Subtitle of Rapid-Acting – Adult preset */ -"A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults." = "En modell basert på publiserte data for absorpsjon av Humalog, Novolog og Apidra insulin hos voksne."; - -/* Subtitle of Rapid-Acting – Children preset */ -"An adjustment to the adult model based on empirical effects in children." = "En justering til modellen tilpasset voksne, basert på empiriske effekter hos barn."; - /* The short unit display string for decibles */ "dB" = "dB"; /* The subtitle format describing eventual glucose. (1: localized glucose value description) (2: localized glucose units description) */ "Eventually %1$@ %2$@" = "Omsider %1$@ %2$@"; -/* Title of insulin model preset */ -"Fiasp" = "Fiasp"; - /* The short unit display string for grams */ "g" = "g"; @@ -34,18 +22,6 @@ /* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ "QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; -/* Title of insulin model preset */ -"Rapid-Acting – Adults" = "Hurtigvirkende – voksen"; - -/* Title of insulin model preset */ -"Rapid-Acting – Children" = "Hurtigvirkende – barn"; - -/* Subtitle description of Walsh insulin model setting */ -"The legacy model used by Loop, allowing customization of action duration." = "Den gamle modellen brukt av Loop, tillater endring av varighet for tiltak."; - /* The short unit display string for international units of insulin */ "U" = "E"; -/* Title of insulin model setting */ -"Walsh" = "Walsh"; - diff --git a/Loop Status Extension/nl.lproj/Localizable.strings b/Loop Status Extension/nl.lproj/Localizable.strings index d245c795f3..b461d99419 100644 --- a/Loop Status Extension/nl.lproj/Localizable.strings +++ b/Loop Status Extension/nl.lproj/Localizable.strings @@ -1,24 +1,12 @@ /* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ "%1$@ v%2$@" = "%1$@ v%2$@"; -/* Subtitle of Fiasp preset */ -"A model based on the published absorption of Fiasp insulin." = "Een model gebaseerd op de gepubliceerde opname van Fiasp insuline."; - -/* Subtitle of Rapid-Acting – Adult preset */ -"A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults." = "Een model gebaseerd op de gepubliceerde opname van Humalog, Novorapid, en Apidra insuline bij volwassenen."; - -/* Subtitle of Rapid-Acting – Children preset */ -"An adjustment to the adult model based on empirical effects in children." = "Een aanpassing aan het volwassen model gebaseerd om praktijk effecten bij kinderen."; - /* The short unit display string for decibles */ "dB" = "dB"; /* The subtitle format describing eventual glucose. (1: localized glucose value description) (2: localized glucose units description) */ "Eventually %1$@ %2$@" = "Uiteindelijk %1$@ %2$@"; -/* Title of insulin model preset */ -"Fiasp" = "Fiasp"; - /* The short unit display string for grams */ "g" = "gr"; @@ -34,18 +22,5 @@ /* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ "QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; -/* Title of insulin model preset */ -"Rapid-Acting – Adults" = "Snelwerkende - volwassenen"; - -/* Title of insulin model preset */ -"Rapid-Acting – Children" = "Snelwerkende - kinderen"; - -/* Subtitle description of Walsh insulin model setting */ -"The legacy model used by Loop, allowing customization of action duration." = "Het oude model dat door Loop wordt gebruikt, waardoor de actieduur kan worden aangepast."; - /* The short unit display string for international units of insulin */ "U" = "E"; - -/* Title of insulin model setting */ -"Walsh" = "Walsh"; - diff --git a/Loop Status Extension/pl.lproj/Localizable.strings b/Loop Status Extension/pl.lproj/Localizable.strings index faab632afe..63067cea30 100644 --- a/Loop Status Extension/pl.lproj/Localizable.strings +++ b/Loop Status Extension/pl.lproj/Localizable.strings @@ -1,24 +1,12 @@ /* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ "%1$@ v%2$@" = "%1$@ v%2$@"; -/* Subtitle of Fiasp preset */ -"A model based on the published absorption of Fiasp insulin." = "Model oparty na opublikowanym czasie absorpcji insuliny Fiasp."; - -/* Subtitle of Rapid-Acting – Adult preset */ -"A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults." = "Model oparty na opublikowanym czasie absorpcji insulin Humalog, Novolog/Novorapid i Apidra u dorosłych."; - -/* Subtitle of Rapid-Acting – Children preset */ -"An adjustment to the adult model based on empirical effects in children." = "Model absorpcji insuliny u dzieci oparty na empirycznym dostosowaniu czasu absorpcji u dorosłych."; - /* The short unit display string for decibles */ "dB" = "dB"; /* The subtitle format describing eventual glucose. (1: localized glucose value description) (2: localized glucose units description) */ "Eventually %1$@ %2$@" = "Docelowo %1$@ %2$@"; -/* Title of insulin model preset */ -"Fiasp" = "Fiasp"; - /* The short unit display string for grams */ "g" = "g"; @@ -34,17 +22,5 @@ /* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ "QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; -/* Title of insulin model preset */ -"Rapid-Acting – Adults" = "Szybko działająca – dorośli"; - -/* Title of insulin model preset */ -"Rapid-Acting – Children" = "Szybko działająca – dzieci"; - -/* Subtitle description of Walsh insulin model setting */ -"The legacy model used by Loop, allowing customization of action duration." = "Model umożliwiający dostosowanie czasu działania insuliny."; - /* The short unit display string for international units of insulin */ "U" = "J"; - -/* Title of insulin model setting */ -"Walsh" = "Walsh"; diff --git a/Loop Status Extension/ru.lproj/Localizable.strings b/Loop Status Extension/ru.lproj/Localizable.strings index eaad302889..fad6c822b1 100644 --- a/Loop Status Extension/ru.lproj/Localizable.strings +++ b/Loop Status Extension/ru.lproj/Localizable.strings @@ -1,24 +1,12 @@ /* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ "%1$@ v%2$@" = "%1$@ v%2$@"; -/* Subtitle of Fiasp preset */ -"A model based on the published absorption of Fiasp insulin." = "модель, основанная на опубликованных данных усвоения FIASP инсулина"; - -/* Subtitle of Rapid-Acting – Adult preset */ -"A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults." = "модель, основанная на опубликованных данных усвоения Humalog, Novolog и Apidra у взрослых"; - -/* Subtitle of Rapid-Acting – Children preset */ -"An adjustment to the adult model based on empirical effects in children." = "Поправка к модели для взрослых основанная на эмпирических данных для детей"; - /* The short unit display string for decibles */ "dB" = "dB"; /* The subtitle format describing eventual glucose. (1: localized glucose value description) (2: localized glucose units description) */ "Eventually %1$@ %2$@" = "В конечном итоге %1$@ %2$@"; -/* Title of insulin model preset */ -"Fiasp" = "Fiasp"; - /* The short unit display string for grams */ "g" = "г"; @@ -34,18 +22,5 @@ /* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ "QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; -/* Title of insulin model preset */ -"Rapid-Acting – Adults" = "Боыстродействующий - взрослые"; - -/* Title of insulin model preset */ -"Rapid-Acting – Children" = "Быстродействующий - дети"; - -/* Subtitle description of Walsh insulin model setting */ -"The legacy model used by Loop, allowing customization of action duration." = "Модель, используемая для цикла/контура, позволяет настройку длительности действия"; - /* The short unit display string for international units of insulin */ "U" = "ед"; - -/* Title of insulin model setting */ -"Walsh" = "Walsh"; - diff --git a/Loop Status Extension/zh-Hans.lproj/Localizable.strings b/Loop Status Extension/zh-Hans.lproj/Localizable.strings index 79541f11d5..3fd0ae4ddc 100644 --- a/Loop Status Extension/zh-Hans.lproj/Localizable.strings +++ b/Loop Status Extension/zh-Hans.lproj/Localizable.strings @@ -1,24 +1,12 @@ /* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ "%1$@ v%2$@" = "%1$@ v%2$@"; -/* Subtitle of Fiasp preset */ -"A model based on the published absorption of Fiasp insulin." = "基于公布的Fiasp胰岛素吸收的模型"; - -/* Subtitle of Rapid-Acting – Adult preset */ -"A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults." = "基于已公布的Humalog,Novolog和Apidra胰岛素在成人中吸收的模型。"; - -/* Subtitle of Rapid-Acting – Children preset */ -"An adjustment to the adult model based on empirical effects in children." = "在成人胰岛素模型基础上专为儿童修改的胰岛素代谢模型"; - /* The short unit display string for decibles */ "dB" = "dB"; /* The subtitle format describing eventual glucose. (1: localized glucose value description) (2: localized glucose units description) */ "Eventually %1$@ %2$@" = "最终 %1$@ %2$@"; -/* Title of insulin model preset */ -"Fiasp" = "Fiasp"; - /* The short unit display string for grams */ "g" = "克"; @@ -34,18 +22,5 @@ /* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ "QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; -/* Title of insulin model preset */ -"Rapid-Acting – Adults" = "速效胰岛素 - 成人模型"; - -/* Title of insulin model preset */ -"Rapid-Acting – Children" = "速效胰岛素 - 儿童模型"; - -/* Subtitle description of Walsh insulin model setting */ -"The legacy model used by Loop, allowing customization of action duration." = "Loop使用的默认模型参数,您可以自行修改胰岛素代谢时间。"; - /* The short unit display string for international units of insulin */ "U" = "单位"; - -/* Title of insulin model setting */ -"Walsh" = "Walsh"; - diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 4422013483..2a032d66c6 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -25,21 +25,19 @@ 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 */; }; - 430B298A2041F54A00BA9F93 /* NSUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430B29892041F54A00BA9F93 /* NSUserDefaults.swift */; }; - 430B298B2041F55700BA9F93 /* NSUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430B29892041F54A00BA9F93 /* NSUserDefaults.swift */; }; - 430B298E2041F56500BA9F93 /* LoopSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430B298C2041F56500BA9F93 /* LoopSettings.swift */; }; - 430B298F2041F56500BA9F93 /* GlucoseThreshold.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430B298D2041F56500BA9F93 /* GlucoseThreshold.swift */; }; - 430B29902041F57000BA9F93 /* GlucoseThreshold.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430B298D2041F56500BA9F93 /* GlucoseThreshold.swift */; }; - 430B29912041F57200BA9F93 /* LoopSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430B298C2041F56500BA9F93 /* LoopSettings.swift */; }; 430B29932041F5B300BA9F93 /* UserDefaults+Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430B29922041F5B200BA9F93 /* UserDefaults+Loop.swift */; }; 430B29952041F5CB00BA9F93 /* LoopSettings+Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430B29942041F5CB00BA9F93 /* LoopSettings+Loop.swift */; }; 430D85891F44037000AF2D4F /* HUDViewTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430D85881F44037000AF2D4F /* HUDViewTableViewCell.swift */; }; - 430DA58E1D4AEC230097D1CA /* NSBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430DA58D1D4AEC230097D1CA /* NSBundle.swift */; }; 4311FB9B1F37FE1B00D4C0A7 /* TitleSubtitleTextFieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4311FB9A1F37FE1B00D4C0A7 /* TitleSubtitleTextFieldTableViewCell.swift */; }; 4315D2871CA5CC3B00589052 /* CarbEntryEditTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4315D2861CA5CC3B00589052 /* CarbEntryEditTableViewController.swift */; }; 4315D28A1CA5F45E00589052 /* DiagnosticLogger+LoopKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4315D2891CA5F45E00589052 /* DiagnosticLogger+LoopKit.swift */; }; 431A8C401EC6E8AB00823B9C /* CircleMaskView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431A8C3F1EC6E8AB00823B9C /* CircleMaskView.swift */; }; - 431E73481FF95A900069B5F7 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431E73471FF95A900069B5F7 /* PersistenceController.swift */; }; + 431EA87021EB29120076EC1A /* ExponentialInsulinModelPreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435CB6241F37ABFC00C320C7 /* ExponentialInsulinModelPreset.swift */; }; + 431EA87121EB29120076EC1A /* ExponentialInsulinModelPreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435CB6241F37ABFC00C320C7 /* ExponentialInsulinModelPreset.swift */; }; + 431EA87221EB29150076EC1A /* InsulinModelSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435CB6281F37B01300C320C7 /* InsulinModelSettings.swift */; }; + 431EA87321EB29160076EC1A /* InsulinModelSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435CB6281F37B01300C320C7 /* InsulinModelSettings.swift */; }; + 431EA87421EB291A0076EC1A /* WalshInsulinModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435CB6261F37AE5600C320C7 /* WalshInsulinModel.swift */; }; + 431EA87521EB291B0076EC1A /* WalshInsulinModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435CB6261F37AE5600C320C7 /* WalshInsulinModel.swift */; }; 4326BA641F3A44D9007CCAD4 /* ChartLineModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4326BA631F3A44D9007CCAD4 /* ChartLineModel.swift */; }; 4328E01A1CFBE1DA00E199AA /* ActionHUDController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4328E0151CFBE1DA00E199AA /* ActionHUDController.swift */; }; 4328E01B1CFBE1DA00E199AA /* BolusInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4328E0161CFBE1DA00E199AA /* BolusInterfaceController.swift */; }; @@ -64,14 +62,19 @@ 4344628F20A7ADD500C4BE6F /* UserDefaults+CGM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4344628D20A7ADD100C4BE6F /* UserDefaults+CGM.swift */; }; 4344629220A7C19800C4BE6F /* ButtonGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4344629120A7C19800C4BE6F /* ButtonGroup.swift */; }; 4344629820A8B2D700C4BE6F /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4374B5EE209D84BE00D17AA8 /* OSLog.swift */; }; + 4345E3F221F0351E009E00E5 /* Locked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4374B5F1209D897600D17AA8 /* Locked.swift */; }; + 4345E3F321F0351E009E00E5 /* Locked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4374B5F1209D897600D17AA8 /* Locked.swift */; }; + 4345E3F421F036FC009E00E5 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D848AF1E7DCBE100DADCBC /* Result.swift */; }; + 4345E3F521F036FC009E00E5 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D848AF1E7DCBE100DADCBC /* Result.swift */; }; + 4345E3F821F03D2A009E00E5 /* DatesAndNumberCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4345E3F721F03D2A009E00E5 /* DatesAndNumberCell.swift */; }; + 4345E3FA21F0473B009E00E5 /* TextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4345E3F921F0473B009E00E5 /* TextCell.swift */; }; + 4345E3FB21F04911009E00E5 /* UIColor+HIG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0C31E4659E700FF19A9 /* UIColor+HIG.swift */; }; + 4345E3FC21F04911009E00E5 /* UIColor+HIG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0C31E4659E700FF19A9 /* UIColor+HIG.swift */; }; + 4345E3FE21F04A50009E00E5 /* DateIntervalFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4345E3FD21F04A50009E00E5 /* DateIntervalFormatter.swift */; }; + 4345E3FF21F051C6009E00E5 /* LoopCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43D9FFCF21EAE05D00AF44BF /* LoopCore.framework */; }; + 4345E40021F051DD009E00E5 /* LoopCore.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 43D9FFCF21EAE05D00AF44BF /* LoopCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 4346D1E71C77F5FE00ABAFE3 /* ChartTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4346D1E61C77F5FE00ABAFE3 /* ChartTableViewCell.swift */; }; - 434B2886206628B3000EE07B /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431E73471FF95A900069B5F7 /* PersistenceController.swift */; }; - 434B2887206B4F07000EE07B /* WalshInsulinModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435CB6261F37AE5600C320C7 /* WalshInsulinModel.swift */; }; - 434B2888206B4F0A000EE07B /* InsulinModelSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435CB6281F37B01300C320C7 /* InsulinModelSettings.swift */; }; - 434B2889206B4F0C000EE07B /* ExponentialInsulinModelPreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435CB6241F37ABFC00C320C7 /* ExponentialInsulinModelPreset.swift */; }; - 434F54571D287FDB002A9274 /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 434F54561D287FDB002A9274 /* NibLoadable.swift */; }; 434FB6461D68F1CD007B9C70 /* Amplitude.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 434FB6451D68F1CD007B9C70 /* Amplitude.framework */; }; - 434FF1EA1CF26C29000DB779 /* IdentifiableClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 434FF1E91CF26C29000DB779 /* IdentifiableClass.swift */; }; 434FF1EE1CF27EEF000DB779 /* UITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 434FF1ED1CF27EEF000DB779 /* UITableViewCell.swift */; }; 43523EDB1CC35083001850F1 /* RileyLinkKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43523EDA1CC35083001850F1 /* RileyLinkKit.framework */; }; 435400311C9F744E00D5819C /* BolusSuggestionUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435400301C9F744E00D5819C /* BolusSuggestionUserInfo.swift */; }; @@ -79,9 +82,6 @@ 435400341C9F878D00D5819C /* SetBolusUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435400331C9F878D00D5819C /* SetBolusUserInfo.swift */; }; 435400351C9F878D00D5819C /* SetBolusUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435400331C9F878D00D5819C /* SetBolusUserInfo.swift */; }; 435CB6231F37967800C320C7 /* InsulinModelSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435CB6221F37967800C320C7 /* InsulinModelSettingsViewController.swift */; }; - 435CB6251F37ABFC00C320C7 /* ExponentialInsulinModelPreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435CB6241F37ABFC00C320C7 /* ExponentialInsulinModelPreset.swift */; }; - 435CB6271F37AE5600C320C7 /* WalshInsulinModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435CB6261F37AE5600C320C7 /* WalshInsulinModel.swift */; }; - 435CB6291F37B01300C320C7 /* InsulinModelSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435CB6281F37B01300C320C7 /* InsulinModelSettings.swift */; }; 436961911F19D11E00447E89 /* ChartPointsContextFillLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4369618F1F19C86400447E89 /* ChartPointsContextFillLayer.swift */; }; 436A0DA51D236A2A00104B24 /* LoopError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 436A0DA41D236A2A00104B24 /* LoopError.swift */; }; 436D9BF81F6F4EA100CFA75F /* recommended_temp_start_low_end_just_above_range.json in Resources */ = {isa = PBXBuildFile; fileRef = 436D9BF71F6F4EA100CFA75F /* recommended_temp_start_low_end_just_above_range.json */; }; @@ -91,21 +91,12 @@ 4372E488213C862B0068E043 /* SampleValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4372E486213C86240068E043 /* SampleValue.swift */; }; 4372E48B213CB5F00068E043 /* Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4372E48A213CB5F00068E043 /* Double.swift */; }; 4372E48C213CB6750068E043 /* Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4372E48A213CB5F00068E043 /* Double.swift */; }; - 4372E48D213CF8A70068E043 /* GlucoseThreshold.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430B298D2041F56500BA9F93 /* GlucoseThreshold.swift */; }; - 4372E48E213CF8AD0068E043 /* LoopSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430B298C2041F56500BA9F93 /* LoopSettings.swift */; }; 4372E490213CFCE70068E043 /* LoopSettingsUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4372E48F213CFCE70068E043 /* LoopSettingsUserInfo.swift */; }; 4372E491213D05F90068E043 /* LoopSettingsUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4372E48F213CFCE70068E043 /* LoopSettingsUserInfo.swift */; }; 4372E492213D956C0068E043 /* GlucoseRangeSchedule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C513181E864C4E001547C7 /* GlucoseRangeSchedule.swift */; }; 4372E496213DCDD30068E043 /* GlucoseChartValueHashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4372E495213DCDD30068E043 /* GlucoseChartValueHashable.swift */; }; - 4372E497213F79F90068E043 /* NSUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430B29892041F54A00BA9F93 /* NSUserDefaults.swift */; }; - 4372E498213F7A550068E043 /* InsulinModelSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435CB6281F37B01300C320C7 /* InsulinModelSettings.swift */; }; - 4372E499213F7A6D0068E043 /* ExponentialInsulinModelPreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435CB6241F37ABFC00C320C7 /* ExponentialInsulinModelPreset.swift */; }; - 4372E49A213F7A830068E043 /* WalshInsulinModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435CB6261F37AE5600C320C7 /* WalshInsulinModel.swift */; }; - 4372E49B213F7B340068E043 /* NSBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430DA58D1D4AEC230097D1CA /* NSBundle.swift */; }; 4374B5EF209D84BF00D17AA8 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4374B5EE209D84BE00D17AA8 /* OSLog.swift */; }; 4374B5F0209D857E00D17AA8 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4374B5EE209D84BE00D17AA8 /* OSLog.swift */; }; - 4374B5F2209D897600D17AA8 /* Locked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4374B5F1209D897600D17AA8 /* Locked.swift */; }; - 4374B5F4209D89A900D17AA8 /* TextFieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4374B5F3209D89A900D17AA8 /* TextFieldTableViewCell.swift */; }; 43776F901B8022E90074EA36 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43776F8F1B8022E90074EA36 /* AppDelegate.swift */; }; 43776F971B8022E90074EA36 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 43776F951B8022E90074EA36 /* Main.storyboard */; }; 43776F991B8022E90074EA36 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 43776F981B8022E90074EA36 /* Assets.xcassets */; }; @@ -150,15 +141,35 @@ 43BFF0B71E45C20C00FF19A9 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0B31E45C1BE00FF19A9 /* NumberFormatter.swift */; }; 43BFF0BC1E45C80600FF19A9 /* UIColor+Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0BB1E45C80600FF19A9 /* UIColor+Loop.swift */; }; 43BFF0BF1E45C8EA00FF19A9 /* UIColor+Widget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0BE1E45C8EA00FF19A9 /* UIColor+Widget.swift */; }; - 43BFF0C51E465A2D00FF19A9 /* UIColor+HIG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0C31E4659E700FF19A9 /* UIColor+HIG.swift */; }; 43BFF0C61E465A4400FF19A9 /* UIColor+HIG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0C31E4659E700FF19A9 /* UIColor+HIG.swift */; }; - 43BFF0C71E465A4F00FF19A9 /* UIColor+HIG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0C31E4659E700FF19A9 /* UIColor+HIG.swift */; }; 43BFF0CD1E466C8400FF19A9 /* StateColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0CC1E466C8400FF19A9 /* StateColorPalette.swift */; }; + 43C05CA821EB2B26006FB252 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431E73471FF95A900069B5F7 /* PersistenceController.swift */; }; + 43C05CA921EB2B26006FB252 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431E73471FF95A900069B5F7 /* PersistenceController.swift */; }; + 43C05CAA21EB2B49006FB252 /* NSBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430DA58D1D4AEC230097D1CA /* NSBundle.swift */; }; + 43C05CAB21EB2B4A006FB252 /* NSBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430DA58D1D4AEC230097D1CA /* NSBundle.swift */; }; + 43C05CAC21EB2B8B006FB252 /* NSBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430DA58D1D4AEC230097D1CA /* NSBundle.swift */; }; + 43C05CAD21EB2BBF006FB252 /* NSUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430B29892041F54A00BA9F93 /* NSUserDefaults.swift */; }; + 43C05CAE21EB2BBF006FB252 /* NSUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430B29892041F54A00BA9F93 /* NSUserDefaults.swift */; }; + 43C05CAF21EB2C24006FB252 /* NSBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430DA58D1D4AEC230097D1CA /* NSBundle.swift */; }; + 43C05CB121EBBDB9006FB252 /* TimeInRangeLesson.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C05CB021EBBDB9006FB252 /* TimeInRangeLesson.swift */; }; + 43C05CB221EBD88A006FB252 /* LoopCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 43D9002A21EB209400AF44BF /* LoopCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 43C05CB521EBE274006FB252 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C05CB421EBE274006FB252 /* Date.swift */; }; + 43C05CB621EBE321006FB252 /* NSTimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439897341CD2F7DE00223065 /* NSTimeInterval.swift */; }; + 43C05CB821EBEA54006FB252 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C05CB721EBEA54006FB252 /* HKUnit.swift */; }; + 43C05CB921EBEA54006FB252 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C05CB721EBEA54006FB252 /* HKUnit.swift */; }; + 43C05CBA21EBEAD8006FB252 /* LoopCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43D9FFCF21EAE05D00AF44BF /* LoopCore.framework */; }; + 43C05CBD21EBF77D006FB252 /* LessonsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C05CBC21EBF77D006FB252 /* LessonsViewController.swift */; }; + 43C05CC021EBFFA4006FB252 /* Lesson.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C05CBF21EBFFA4006FB252 /* Lesson.swift */; }; + 43C05CC221EC06E4006FB252 /* LessonConfigurationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C05CC121EC06E4006FB252 /* LessonConfigurationViewController.swift */; }; + 43C05CC521EC29E3006FB252 /* TextFieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4374B5F3209D89A900D17AA8 /* TextFieldTableViewCell.swift */; }; + 43C05CC621EC29E7006FB252 /* TextFieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4374B5F3209D89A900D17AA8 /* TextFieldTableViewCell.swift */; }; + 43C05CC721EC2ABC006FB252 /* IdentifiableClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 434FF1E91CF26C29000DB779 /* IdentifiableClass.swift */; }; + 43C05CC821EC2ABC006FB252 /* IdentifiableClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 434FF1E91CF26C29000DB779 /* IdentifiableClass.swift */; }; + 43C05CCA21EC382B006FB252 /* NumberEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C05CC921EC382B006FB252 /* NumberEntry.swift */; }; 43C0944A1CACCC73001F6403 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C094491CACCC73001F6403 /* NotificationManager.swift */; }; 43C246A81D89990F0031F8D1 /* Crypto.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43C246A71D89990F0031F8D1 /* Crypto.framework */; }; 43C2FAE11EB656A500364AFF /* GlucoseEffectVelocity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C2FAE01EB656A500364AFF /* GlucoseEffectVelocity.swift */; }; 43C3B6EC20B650A80026CAFA /* SettingsImageTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C3B6EB20B650A80026CAFA /* SettingsImageTableViewCell.swift */; }; - 43C3B6ED20B884500026CAFA /* GlucoseThreshold.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430B298D2041F56500BA9F93 /* GlucoseThreshold.swift */; }; 43C513191E864C4E001547C7 /* GlucoseRangeSchedule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C513181E864C4E001547C7 /* GlucoseRangeSchedule.swift */; }; 43CA93371CB98079000026B5 /* MinimedKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43CA93361CB98079000026B5 /* MinimedKit.framework */; }; 43CB2B2B1D924D450079823D /* WCSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CB2B2A1D924D450079823D /* WCSession.swift */; }; @@ -166,7 +177,42 @@ 43CEE6E61E56AFD400CB9116 /* NightscoutUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CEE6E51E56AFD400CB9116 /* NightscoutUploader.swift */; }; 43D2E8231F00425400AE5CBF /* BolusViewController+LoopDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D2E8221F00425400AE5CBF /* BolusViewController+LoopDataManager.swift */; }; 43D381621EBD9759007F8C8F /* HeaderValuesTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D381611EBD9759007F8C8F /* HeaderValuesTableViewCell.swift */; }; - 43D848B01E7DCBE100DADCBC /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D848AF1E7DCBE100DADCBC /* Result.swift */; }; + 43D9000B21EB0BE000AF44BF /* LoopCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43D9FFCF21EAE05D00AF44BF /* LoopCore.framework */; }; + 43D9001E21EB209400AF44BF /* LoopCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 43D9FFD121EAE05D00AF44BF /* LoopCore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 43D9002021EB209400AF44BF /* NSTimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439897341CD2F7DE00223065 /* NSTimeInterval.swift */; }; + 43D9002121EB209400AF44BF /* GlucoseThreshold.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430B298D2041F56500BA9F93 /* GlucoseThreshold.swift */; }; + 43D9002221EB209400AF44BF /* LoopSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430B298C2041F56500BA9F93 /* LoopSettings.swift */; }; + 43D9002D21EB225D00AF44BF /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43D9002C21EB225D00AF44BF /* HealthKit.framework */; }; + 43D9002E21EB226F00AF44BF /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4344628320A7A3BE00C4BE6F /* LoopKit.framework */; }; + 43D9002F21EB234400AF44BF /* LoopCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43D9002A21EB209400AF44BF /* LoopCore.framework */; }; + 43D9003321EB258C00AF44BF /* InsulinModelSettings+Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D9003221EB258C00AF44BF /* InsulinModelSettings+Loop.swift */; }; + 43D9F81821EC51CC000578CD /* DateEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D9F81721EC51CC000578CD /* DateEntry.swift */; }; + 43D9F81A21EC593C000578CD /* UITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D9F81921EC593C000578CD /* UITableViewCell.swift */; }; + 43D9F81E21EF0609000578CD /* NumberRangeEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D9F81D21EF0609000578CD /* NumberRangeEntry.swift */; }; + 43D9F82021EF0906000578CD /* NSNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D9F81F21EF0906000578CD /* NSNumber.swift */; }; + 43D9F82221EF0A7A000578CD /* QuantityRangeEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D9F82121EF0A7A000578CD /* QuantityRangeEntry.swift */; }; + 43D9F82421EFF1AB000578CD /* LessonResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D9F82321EFF1AB000578CD /* LessonResultsViewController.swift */; }; + 43D9FFA521EA9A0C00AF44BF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D9FFA421EA9A0C00AF44BF /* AppDelegate.swift */; }; + 43D9FFAA21EA9A0C00AF44BF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 43D9FFA821EA9A0C00AF44BF /* Main.storyboard */; }; + 43D9FFAC21EA9A0F00AF44BF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 43D9FFAB21EA9A0F00AF44BF /* Assets.xcassets */; }; + 43D9FFAF21EA9A0F00AF44BF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 43D9FFAD21EA9A0F00AF44BF /* LaunchScreen.storyboard */; }; + 43D9FFB421EA9AD800AF44BF /* LoopUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F75288B1DFE1DC600C322D6 /* LoopUI.framework */; }; + 43D9FFB621EA9B2F00AF44BF /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43F5C2C81B929C09003EB13D /* HealthKit.framework */; }; + 43D9FFBB21EA9CC900AF44BF /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43F78D4B1C914197002152D1 /* LoopKit.framework */; }; + 43D9FFBC21EA9CCD00AF44BF /* LoopKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 437AFEE6203688CF008C4892 /* LoopKitUI.framework */; }; + 43D9FFBD21EA9CD700AF44BF /* SwiftCharts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4346D1EF1C781BEA00ABAFE3 /* SwiftCharts.framework */; }; + 43D9FFC021EAB22E00AF44BF /* DataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D9FFBF21EAB22E00AF44BF /* DataManager.swift */; }; + 43D9FFD321EAE05D00AF44BF /* LoopCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 43D9FFD121EAE05D00AF44BF /* LoopCore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 43D9FFD621EAE05D00AF44BF /* LoopCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43D9FFCF21EAE05D00AF44BF /* LoopCore.framework */; }; + 43D9FFD721EAE05D00AF44BF /* LoopCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 43D9FFCF21EAE05D00AF44BF /* LoopCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 43D9FFDE21EAE3AE00AF44BF /* LoopCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43D9FFCF21EAE05D00AF44BF /* LoopCore.framework */; }; + 43D9FFE021EAE3E500AF44BF /* LoopUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4F75288B1DFE1DC600C322D6 /* LoopUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 43D9FFE121EAE3E500AF44BF /* LoopCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 43D9FFCF21EAE05D00AF44BF /* LoopCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 43D9FFF521EAF27200AF44BF /* LoopSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430B298C2041F56500BA9F93 /* LoopSettings.swift */; }; + 43D9FFF821EAF2EF00AF44BF /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43F78D4B1C914197002152D1 /* LoopKit.framework */; }; + 43D9FFF921EAF34800AF44BF /* GlucoseThreshold.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430B298D2041F56500BA9F93 /* GlucoseThreshold.swift */; }; + 43D9FFFA21EAF35900AF44BF /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43F5C2C81B929C09003EB13D /* HealthKit.framework */; }; + 43D9FFFB21EAF3D300AF44BF /* NSTimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439897341CD2F7DE00223065 /* NSTimeInterval.swift */; }; 43DAD00020A2736F000F8529 /* PersistedPumpEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DACFFF20A2736F000F8529 /* PersistedPumpEvent.swift */; }; 43DBF04C1C93B8D700B3C386 /* BolusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DBF04B1C93B8D700B3C386 /* BolusViewController.swift */; }; 43DBF0531C93EC8200B3C386 /* DeviceDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DBF0521C93EC8200B3C386 /* DeviceDataManager.swift */; }; @@ -225,7 +271,6 @@ 4F2C15971E09E94E00E160D4 /* HUDAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4F2C15961E09E94E00E160D4 /* HUDAssets.xcassets */; }; 4F2C159A1E0C9E5600E160D4 /* LoopUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4F75288B1DFE1DC600C322D6 /* LoopUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 4F526D611DF8D9A900A04910 /* NetBasal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F526D601DF8D9A900A04910 /* NetBasal.swift */; }; - 4F526D621DF9D95200A04910 /* NSBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 430DA58D1D4AEC230097D1CA /* NSBundle.swift */; }; 4F6663941E905FD2009E74FC /* ChartColorPalette+Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6663931E905FD2009E74FC /* ChartColorPalette+Loop.swift */; }; 4F70C1E11DE8DCA7006380B7 /* StatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F70C1E01DE8DCA7006380B7 /* StatusViewController.swift */; }; 4F70C1E41DE8DCA7006380B7 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4F70C1E21DE8DCA7006380B7 /* MainInterface.storyboard */; }; @@ -264,9 +309,7 @@ 4FC8C8011DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FC8C8001DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift */; }; 4FC8C8021DEB943800A1452E /* NSUserDefaults+StatusExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FC8C8001DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift */; }; 4FDDD23720DC51DF00D04B16 /* LoopDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FDDD23620DC51DF00D04B16 /* LoopDataManager.swift */; }; - 4FF0F75E20E1E5D100FC6291 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431E73471FF95A900069B5F7 /* PersistenceController.swift */; }; 4FF4D0F81E1725B000846527 /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 434F54561D287FDB002A9274 /* NibLoadable.swift */; }; - 4FF4D0F91E17268800846527 /* IdentifiableClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 434FF1E91CF26C29000DB779 /* IdentifiableClass.swift */; }; 4FF4D1001E18374700846527 /* WatchContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF4D0FF1E18374700846527 /* WatchContext.swift */; }; 4FF4D1011E18375000846527 /* WatchContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF4D0FF1E18374700846527 /* WatchContext.swift */; }; 7D23667D21250C7E0028B67D /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D23667C21250C7E0028B67D /* LocalizedString.swift */; }; @@ -347,6 +390,62 @@ remoteGlobalIDString = 43A943711B926B7B0051FA24; remoteInfo = WatchApp; }; + 43D9000C21EB0BEA00AF44BF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 43776F841B8022E90074EA36 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 43D9FFCE21EAE05D00AF44BF; + remoteInfo = LoopCore; + }; + 43D9001221EB137A00AF44BF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 43776F841B8022E90074EA36 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 43D9FFCE21EAE05D00AF44BF; + remoteInfo = LoopCore; + }; + 43D9001821EB207300AF44BF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 43776F841B8022E90074EA36 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 432CF87720D8B8380066B889; + remoteInfo = Cartfile; + }; + 43D9001C21EB209400AF44BF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 43776F841B8022E90074EA36 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 432CF87720D8B8380066B889; + remoteInfo = Cartfile; + }; + 43D9003021EB236800AF44BF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 43776F841B8022E90074EA36 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 43D9001A21EB209400AF44BF; + remoteInfo = "LoopCore-watchOS"; + }; + 43D9FFB721EA9C9F00AF44BF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 43776F841B8022E90074EA36 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 432CF87720D8B8380066B889; + remoteInfo = Cartfile; + }; + 43D9FFB921EA9CA400AF44BF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 43776F841B8022E90074EA36 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4F75288A1DFE1DC600C322D6; + remoteInfo = LoopUI; + }; + 43D9FFD421EAE05D00AF44BF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 43776F841B8022E90074EA36 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 43D9FFCE21EAE05D00AF44BF; + remoteInfo = LoopCore; + }; 43E2D9101D20C581004DA55F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 43776F841B8022E90074EA36 /* Project object */; @@ -407,6 +506,7 @@ dstSubfolderSpec = 10; files = ( 4F2C159A1E0C9E5600E160D4 /* LoopUI.framework in Embed Frameworks */, + 43D9FFD721EAE05D00AF44BF /* LoopCore.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -417,6 +517,19 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( + 43C05CB221EBD88A006FB252 /* LoopCore.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 43D9FFDF21EAE3C600AF44BF /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 43D9FFE021EAE3E500AF44BF /* LoopUI.framework in Embed Frameworks */, + 43D9FFE121EAE3E500AF44BF /* LoopCore.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -428,6 +541,7 @@ dstSubfolderSpec = 10; files = ( 43E2D9171D2226BD004DA55F /* LoopKit.framework in CopyFiles */, + 4345E40021F051DD009E00E5 /* LoopCore.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -484,6 +598,9 @@ 4344628420A7A3BE00C4BE6F /* CGMBLEKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CGMBLEKit.framework; path = Carthage/Build/watchOS/CGMBLEKit.framework; sourceTree = ""; }; 4344628D20A7ADD100C4BE6F /* UserDefaults+CGM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+CGM.swift"; sourceTree = ""; }; 4344629120A7C19800C4BE6F /* ButtonGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroup.swift; sourceTree = ""; }; + 4345E3F721F03D2A009E00E5 /* DatesAndNumberCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatesAndNumberCell.swift; sourceTree = ""; }; + 4345E3F921F0473B009E00E5 /* TextCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextCell.swift; sourceTree = ""; }; + 4345E3FD21F04A50009E00E5 /* DateIntervalFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateIntervalFormatter.swift; sourceTree = ""; }; 4346D1E61C77F5FE00ABAFE3 /* ChartTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ChartTableViewCell.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 4346D1EF1C781BEA00ABAFE3 /* SwiftCharts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftCharts.framework; path = Carthage/Build/iOS/SwiftCharts.framework; sourceTree = SOURCE_ROOT; }; 434AB0B11CBB4C3300422F4A /* RileyLinkBLEKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RileyLinkBLEKit.framework; path = Carthage/Build/iOS/RileyLinkBLEKit.framework; sourceTree = SOURCE_ROOT; }; @@ -565,6 +682,13 @@ 43BFF0BE1E45C8EA00FF19A9 /* UIColor+Widget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Widget.swift"; sourceTree = ""; }; 43BFF0C31E4659E700FF19A9 /* UIColor+HIG.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+HIG.swift"; sourceTree = ""; }; 43BFF0CC1E466C8400FF19A9 /* StateColorPalette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateColorPalette.swift; sourceTree = ""; }; + 43C05CB021EBBDB9006FB252 /* TimeInRangeLesson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeInRangeLesson.swift; sourceTree = ""; }; + 43C05CB421EBE274006FB252 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; }; + 43C05CB721EBEA54006FB252 /* HKUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = ""; }; + 43C05CBC21EBF77D006FB252 /* LessonsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LessonsViewController.swift; sourceTree = ""; }; + 43C05CBF21EBFFA4006FB252 /* Lesson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Lesson.swift; sourceTree = ""; }; + 43C05CC121EC06E4006FB252 /* LessonConfigurationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LessonConfigurationViewController.swift; sourceTree = ""; }; + 43C05CC921EC382B006FB252 /* NumberEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberEntry.swift; sourceTree = ""; }; 43C094491CACCC73001F6403 /* NotificationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = ""; }; 43C246A71D89990F0031F8D1 /* Crypto.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Crypto.framework; path = Carthage/Build/iOS/Crypto.framework; sourceTree = SOURCE_ROOT; }; 43C2FAE01EB656A500364AFF /* GlucoseEffectVelocity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseEffectVelocity.swift; sourceTree = ""; }; @@ -580,6 +704,26 @@ 43D381611EBD9759007F8C8F /* HeaderValuesTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderValuesTableViewCell.swift; sourceTree = ""; }; 43D533BB1CFD1DD7009E3085 /* WatchApp Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "WatchApp Extension.entitlements"; sourceTree = ""; }; 43D848AF1E7DCBE100DADCBC /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; + 43D9002A21EB209400AF44BF /* LoopCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LoopCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 43D9002C21EB225D00AF44BF /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS5.1.sdk/System/Library/Frameworks/HealthKit.framework; sourceTree = DEVELOPER_DIR; }; + 43D9003221EB258C00AF44BF /* InsulinModelSettings+Loop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "InsulinModelSettings+Loop.swift"; sourceTree = ""; }; + 43D9F81721EC51CC000578CD /* DateEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateEntry.swift; sourceTree = ""; }; + 43D9F81921EC593C000578CD /* UITableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITableViewCell.swift; sourceTree = ""; }; + 43D9F81D21EF0609000578CD /* NumberRangeEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberRangeEntry.swift; sourceTree = ""; }; + 43D9F81F21EF0906000578CD /* NSNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSNumber.swift; sourceTree = ""; }; + 43D9F82121EF0A7A000578CD /* QuantityRangeEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuantityRangeEntry.swift; sourceTree = ""; }; + 43D9F82321EFF1AB000578CD /* LessonResultsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LessonResultsViewController.swift; sourceTree = ""; }; + 43D9FFA221EA9A0C00AF44BF /* Learn.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Learn.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 43D9FFA421EA9A0C00AF44BF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 43D9FFA921EA9A0C00AF44BF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 43D9FFAB21EA9A0F00AF44BF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 43D9FFAE21EA9A0F00AF44BF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 43D9FFB021EA9A0F00AF44BF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 43D9FFB521EA9B0100AF44BF /* Learn.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Learn.entitlements; sourceTree = ""; }; + 43D9FFBF21EAB22E00AF44BF /* DataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataManager.swift; sourceTree = ""; }; + 43D9FFCF21EAE05D00AF44BF /* LoopCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LoopCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 43D9FFD121EAE05D00AF44BF /* LoopCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LoopCore.h; sourceTree = ""; }; + 43D9FFD221EAE05D00AF44BF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43DACFFF20A2736F000F8529 /* PersistedPumpEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistedPumpEvent.swift; sourceTree = ""; }; 43DBF04B1C93B8D700B3C386 /* BolusViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = BolusViewController.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 43DBF0521C93EC8200B3C386 /* DeviceDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = DeviceDataManager.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; @@ -857,6 +1001,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 43D9FFD621EAE05D00AF44BF /* LoopCore.framework in Frameworks */, 4F7528941DFE1E9500C322D6 /* LoopUI.framework in Frameworks */, 434FB6461D68F1CD007B9C70 /* Amplitude.framework in Frameworks */, 43A8EC6F210E622700A81379 /* CGMBLEKitUI.framework in Frameworks */, @@ -886,6 +1031,38 @@ 4344628020A7A37400C4BE6F /* HealthKit.framework in Frameworks */, 4344628520A7A3BE00C4BE6F /* LoopKit.framework in Frameworks */, 4344628620A7A3BE00C4BE6F /* CGMBLEKit.framework in Frameworks */, + 43D9002F21EB234400AF44BF /* LoopCore.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 43D9002321EB209400AF44BF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 43D9002D21EB225D00AF44BF /* HealthKit.framework in Frameworks */, + 43D9002E21EB226F00AF44BF /* LoopKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 43D9FF9F21EA9A0C00AF44BF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 43D9FFDE21EAE3AE00AF44BF /* LoopCore.framework in Frameworks */, + 43D9FFBD21EA9CD700AF44BF /* SwiftCharts.framework in Frameworks */, + 43D9FFB421EA9AD800AF44BF /* LoopUI.framework in Frameworks */, + 43D9FFBB21EA9CC900AF44BF /* LoopKit.framework in Frameworks */, + 43D9FFB621EA9B2F00AF44BF /* HealthKit.framework in Frameworks */, + 43D9FFBC21EA9CCD00AF44BF /* LoopKitUI.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 43D9FFCC21EAE05D00AF44BF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 43D9FFFA21EAF35900AF44BF /* HealthKit.framework in Frameworks */, + 43D9FFF821EAF2EF00AF44BF /* LoopKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -893,6 +1070,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 4345E3FF21F051C6009E00E5 /* LoopCore.framework in Frameworks */, 43E2D9191D222759004DA55F /* LoopKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -910,6 +1088,7 @@ files = ( C168C40621B0D53E00ADE90E /* MinimedKit.framework in Frameworks */, C168C40821B0D53E00ADE90E /* MinimedKitUI.framework in Frameworks */, + 43D9000B21EB0BE000AF44BF /* LoopCore.framework in Frameworks */, 4F7528951DFE1E9B00C322D6 /* LoopUI.framework in Frameworks */, 437AFEE520352591008C4892 /* NotificationCenter.framework in Frameworks */, ); @@ -921,6 +1100,7 @@ files = ( 437AFEE8203689FE008C4892 /* LoopKit.framework in Frameworks */, 4FB76FB01E8C3E8000B39636 /* SwiftCharts.framework in Frameworks */, + 43C05CBA21EBEAD8006FB252 /* LoopCore.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -963,14 +1143,13 @@ path = Extensions; sourceTree = ""; }; - 43673E2E1F37BDA10058AC7C /* Insulin */ = { + 4345E3F621F03C2E009E00E5 /* Display */ = { isa = PBXGroup; children = ( - 435CB6241F37ABFC00C320C7 /* ExponentialInsulinModelPreset.swift */, - 435CB6281F37B01300C320C7 /* InsulinModelSettings.swift */, - 435CB6261F37AE5600C320C7 /* WalshInsulinModel.swift */, + 4345E3F721F03D2A009E00E5 /* DatesAndNumberCell.swift */, + 4345E3F921F0473B009E00E5 /* TextCell.swift */, ); - path = Insulin; + path = Display; sourceTree = ""; }; 43757D131C06F26C00910CB9 /* Models */ = { @@ -980,12 +1159,10 @@ 43DE92601C555C26001FFDE1 /* AbsorptionTimeType+CarbKit.swift */, C17824A41E1AD4D100D9D25C /* BolusRecommendation.swift */, 43C2FAE01EB656A500364AFF /* GlucoseEffectVelocity.swift */, - 4374B5F1209D897600D17AA8 /* Locked.swift */, 436A0DA41D236A2A00104B24 /* LoopError.swift */, 430B29942041F5CB00BA9F93 /* LoopSettings+Loop.swift */, 4F526D601DF8D9A900A04910 /* NetBasal.swift */, 438D42F81D7C88BC003244B0 /* PredictionInputEffect.swift */, - 43D848AF1E7DCBE100DADCBC /* Result.swift */, 43441A9B1EDB34810087958C /* StatusExtensionContext+LoopKit.swift */, 4328E0311CFC068900E199AA /* WatchContext+LoopKit.swift */, ); @@ -998,11 +1175,13 @@ 4FF4D0FA1E1834BD00846527 /* Common */, 43776F8E1B8022E90074EA36 /* Loop */, 4F70C1DF1DE8DCA7006380B7 /* Loop Status Extension */, + 43D9FFD021EAE05D00AF44BF /* LoopCore */, 4F75288C1DFE1DC600C322D6 /* LoopUI */, 43A943731B926B7B0051FA24 /* WatchApp */, 43A943821B926B7B0051FA24 /* WatchApp Extension */, 43F78D2C1C8FC58F002152D1 /* LoopTests */, 43E2D8D21D20BF42004DA55F /* DoseMathTests */, + 43D9FFA321EA9A0C00AF44BF /* Learn */, 968DCD53F724DE56FFE51920 /* Frameworks */, 43776F8D1B8022E90074EA36 /* Products */, 437D9BA11D7B5203007245E8 /* Loop.xcconfig */, @@ -1019,6 +1198,9 @@ 43E2D90B1D20C581004DA55F /* LoopTests.xctest */, 4F70C1DC1DE8DCA7006380B7 /* Loop Status Extension.appex */, 4F75288B1DFE1DC600C322D6 /* LoopUI.framework */, + 43D9FFA221EA9A0C00AF44BF /* Learn.app */, + 43D9FFCF21EAE05D00AF44BF /* LoopCore.framework */, + 43D9002A21EB209400AF44BF /* LoopCore.framework */, ); name = Products; sourceTree = ""; @@ -1079,8 +1261,8 @@ 4328E0121CFBE1B700E199AA /* Controllers */, 4328E01F1CFBE2B100E199AA /* Extensions */, 4FE3475F20D5D7FA00A86D03 /* Managers */, - 4F75F0052100146B00B5570E /* Scenes */, 898ECA5D218ABD17001E9D35 /* Models */, + 4F75F0052100146B00B5570E /* Scenes */, 43A943831B926B7B0051FA24 /* Supporting Files */, ); path = "WatchApp Extension"; @@ -1094,6 +1276,102 @@ name = "Supporting Files"; sourceTree = ""; }; + 43C05CB321EBE268006FB252 /* Extensions */ = { + isa = PBXGroup; + children = ( + 43C05CB421EBE274006FB252 /* Date.swift */, + 4345E3FD21F04A50009E00E5 /* DateIntervalFormatter.swift */, + 43D9F81F21EF0906000578CD /* NSNumber.swift */, + 43D9F81921EC593C000578CD /* UITableViewCell.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 43C05CBB21EBF743006FB252 /* View Controllers */ = { + isa = PBXGroup; + children = ( + 43C05CC121EC06E4006FB252 /* LessonConfigurationViewController.swift */, + 43D9F82321EFF1AB000578CD /* LessonResultsViewController.swift */, + 43C05CBC21EBF77D006FB252 /* LessonsViewController.swift */, + ); + path = "View Controllers"; + sourceTree = ""; + }; + 43C05CBE21EBFF66006FB252 /* Lessons */ = { + isa = PBXGroup; + children = ( + 43C05CB021EBBDB9006FB252 /* TimeInRangeLesson.swift */, + ); + path = Lessons; + sourceTree = ""; + }; + 43C05CC321EC0868006FB252 /* Configuration */ = { + isa = PBXGroup; + children = ( + 43D9F81721EC51CC000578CD /* DateEntry.swift */, + 43C05CC921EC382B006FB252 /* NumberEntry.swift */, + 43D9F81D21EF0609000578CD /* NumberRangeEntry.swift */, + 43D9F82121EF0A7A000578CD /* QuantityRangeEntry.swift */, + ); + path = Configuration; + sourceTree = ""; + }; + 43D9003A21EB281300AF44BF /* Insulin */ = { + isa = PBXGroup; + children = ( + 435CB6241F37ABFC00C320C7 /* ExponentialInsulinModelPreset.swift */, + 435CB6281F37B01300C320C7 /* InsulinModelSettings.swift */, + 435CB6261F37AE5600C320C7 /* WalshInsulinModel.swift */, + ); + path = Insulin; + sourceTree = ""; + }; + 43D9FFA321EA9A0C00AF44BF /* Learn */ = { + isa = PBXGroup; + children = ( + 43D9FFA421EA9A0C00AF44BF /* AppDelegate.swift */, + 43C05CBF21EBFFA4006FB252 /* Lesson.swift */, + 43C05CC321EC0868006FB252 /* Configuration */, + 4345E3F621F03C2E009E00E5 /* Display */, + 43C05CB321EBE268006FB252 /* Extensions */, + 43C05CBE21EBFF66006FB252 /* Lessons */, + 43D9FFBE21EAB20B00AF44BF /* Managers */, + 43C05CBB21EBF743006FB252 /* View Controllers */, + 43D9FFB521EA9B0100AF44BF /* Learn.entitlements */, + 43D9FFA821EA9A0C00AF44BF /* Main.storyboard */, + 43D9FFAB21EA9A0F00AF44BF /* Assets.xcassets */, + 43D9FFAD21EA9A0F00AF44BF /* LaunchScreen.storyboard */, + 43D9FFB021EA9A0F00AF44BF /* Info.plist */, + ); + path = Learn; + sourceTree = ""; + }; + 43D9FFBE21EAB20B00AF44BF /* Managers */ = { + isa = PBXGroup; + children = ( + 43D9FFBF21EAB22E00AF44BF /* DataManager.swift */, + ); + path = Managers; + sourceTree = ""; + }; + 43D9FFD021EAE05D00AF44BF /* LoopCore */ = { + isa = PBXGroup; + children = ( + 43D9003A21EB281300AF44BF /* Insulin */, + 430B298D2041F56500BA9F93 /* GlucoseThreshold.swift */, + 43C05CB721EBEA54006FB252 /* HKUnit.swift */, + 434FF1E91CF26C29000DB779 /* IdentifiableClass.swift */, + 4374B5F1209D897600D17AA8 /* Locked.swift */, + 430B298C2041F56500BA9F93 /* LoopSettings.swift */, + 430B29892041F54A00BA9F93 /* NSUserDefaults.swift */, + 431E73471FF95A900069B5F7 /* PersistenceController.swift */, + 43D848AF1E7DCBE100DADCBC /* Result.swift */, + 43D9FFD121EAE05D00AF44BF /* LoopCore.h */, + 43D9FFD221EAE05D00AF44BF /* Info.plist */, + ); + path = LoopCore; + sourceTree = ""; + }; 43E2D8D21D20BF42004DA55F /* DoseMathTests */ = { isa = PBXGroup; children = ( @@ -1137,6 +1415,7 @@ 4389916A1E91B689000EEF90 /* ChartSettings+Loop.swift */, 4F08DE8E1E7BB871006741EA /* CollectionType+Loop.swift */, 43CE7CDD1CA8B63E003CC1B0 /* Data.swift */, + 43D9003221EB258C00AF44BF /* InsulinModelSettings+Loop.swift */, C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */, 438172D81F4E9E37003C3328 /* NewPumpEvent.swift */, 43CEE6E51E56AFD400CB9116 /* NightscoutUploader.swift */, @@ -1183,7 +1462,6 @@ 430D85881F44037000AF2D4F /* HUDViewTableViewCell.swift */, 438D42FA1D7D11A4003244B0 /* PredictionInputEffectTableViewCell.swift */, 43C3B6EB20B650A80026CAFA /* SettingsImageTableViewCell.swift */, - 4374B5F3209D89A900D17AA8 /* TextFieldTableViewCell.swift */, 43F64DD81D9C92C900D24DC6 /* TitleSubtitleTableViewCell.swift */, 4311FB9A1F37FE1B00D4C0A7 /* TitleSubtitleTextFieldTableViewCell.swift */, ); @@ -1305,6 +1583,7 @@ 4F08DE801E7BB6F1006741EA /* CGPoint.swift */, 438991661E91B563000EEF90 /* ChartPoint.swift */, 43649A621C7A347F00523D7F /* CollectionType.swift */, + 434F54561D287FDB002A9274 /* NibLoadable.swift */, ); path = Extensions; sourceTree = ""; @@ -1331,12 +1610,9 @@ 4FF4D0FB1E1834C400846527 /* Models */ = { isa = PBXGroup; children = ( - 43673E2E1F37BDA10058AC7C /* Insulin */, 435400301C9F744E00D5819C /* BolusSuggestionUserInfo.swift */, 43DE92581C5479E4001FFDE1 /* CarbEntryUserInfo.swift */, 4F11D3BF20DCBEEC006E072C /* GlucoseBackfillRequestUserInfo.swift */, - 430B298D2041F56500BA9F93 /* GlucoseThreshold.swift */, - 430B298C2041F56500BA9F93 /* LoopSettings.swift */, 4372E48F213CFCE70068E043 /* LoopSettingsUserInfo.swift */, 435400331C9F878D00D5819C /* SetBolusUserInfo.swift */, 4F70C2111DE900EA006380B7 /* StatusExtensionContext.swift */, @@ -1355,18 +1631,15 @@ 4372E48A213CB5F00068E043 /* Double.swift */, 4F526D5E1DF2459000A04910 /* HKUnit.swift */, 43C513181E864C4E001547C7 /* GlucoseRangeSchedule.swift */, - 434FF1E91CF26C29000DB779 /* IdentifiableClass.swift */, 43785E922120A01B0057DED1 /* NewCarbEntryIntent+Loop.swift */, - 434F54561D287FDB002A9274 /* NibLoadable.swift */, 430DA58D1D4AEC230097D1CA /* NSBundle.swift */, 439897341CD2F7DE00223065 /* NSTimeInterval.swift */, 439A7943211FE22F0041B75F /* NSUserActivity.swift */, - 430B29892041F54A00BA9F93 /* NSUserDefaults.swift */, 4FC8C8001DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift */, 43BFF0B31E45C1BE00FF19A9 /* NumberFormatter.swift */, 4374B5EE209D84BE00D17AA8 /* OSLog.swift */, - 431E73471FF95A900069B5F7 /* PersistenceController.swift */, 4372E486213C86240068E043 /* SampleValue.swift */, + 4374B5F3209D89A900D17AA8 /* TextFieldTableViewCell.swift */, 43BFF0B11E45C18400FF19A9 /* UIColor.swift */, 43BFF0C31E4659E700FF19A9 /* UIColor+HIG.swift */, 4344628D20A7ADD100C4BE6F /* UserDefaults+CGM.swift */, @@ -1404,6 +1677,7 @@ 4344628120A7A37E00C4BE6F /* CoreBluetooth.framework */, 43C246A71D89990F0031F8D1 /* Crypto.framework */, 4D3B40021D4A9DFE00BC6334 /* G4ShareSpy.framework */, + 43D9002C21EB225D00AF44BF /* HealthKit.framework */, 4344627F20A7A37400C4BE6F /* HealthKit.framework */, 43F5C2C81B929C09003EB13D /* HealthKit.framework */, 4344628320A7A3BE00C4BE6F /* LoopKit.framework */, @@ -1426,6 +1700,22 @@ /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ + 43D9001D21EB209400AF44BF /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 43D9001E21EB209400AF44BF /* LoopCore.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 43D9FFCA21EAE05D00AF44BF /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 43D9FFD321EAE05D00AF44BF /* LoopCore.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 4F7528881DFE1DC600C322D6 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -1456,6 +1746,7 @@ 4F7528971DFE1ED400C322D6 /* PBXTargetDependency */, 43A943931B926B7B0051FA24 /* PBXTargetDependency */, 4F70C1E71DE8DCA7006380B7 /* PBXTargetDependency */, + 43D9FFD521EAE05D00AF44BF /* PBXTargetDependency */, ); name = Loop; productName = Loop; @@ -1494,12 +1785,72 @@ ); dependencies = ( 432CF88120D8BC460066B889 /* PBXTargetDependency */, + 43D9003121EB236800AF44BF /* PBXTargetDependency */, ); name = "WatchApp Extension"; productName = "WatchApp Extension"; productReference = 43A9437E1B926B7B0051FA24 /* WatchApp Extension.appex */; productType = "com.apple.product-type.watchkit2-extension"; }; + 43D9001A21EB209400AF44BF /* LoopCore-watchOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 43D9002721EB209400AF44BF /* Build configuration list for PBXNativeTarget "LoopCore-watchOS" */; + buildPhases = ( + 43D9001D21EB209400AF44BF /* Headers */, + 43D9001F21EB209400AF44BF /* Sources */, + 43D9002321EB209400AF44BF /* Frameworks */, + 43D9002621EB209400AF44BF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 43D9001B21EB209400AF44BF /* PBXTargetDependency */, + ); + name = "LoopCore-watchOS"; + productName = LoopCore; + productReference = 43D9002A21EB209400AF44BF /* LoopCore.framework */; + productType = "com.apple.product-type.framework"; + }; + 43D9FFA121EA9A0C00AF44BF /* Learn */ = { + isa = PBXNativeTarget; + buildConfigurationList = 43D9FFB321EA9A0F00AF44BF /* Build configuration list for PBXNativeTarget "Learn" */; + buildPhases = ( + 43D9FF9E21EA9A0C00AF44BF /* Sources */, + 43D9FF9F21EA9A0C00AF44BF /* Frameworks */, + 43D9FFA021EA9A0C00AF44BF /* Resources */, + 43D9FFDF21EAE3C600AF44BF /* Embed Frameworks */, + 43D9FFE221EAE40600AF44BF /* Copy Frameworks with Carthage */, + ); + buildRules = ( + ); + dependencies = ( + 43D9FFB821EA9C9F00AF44BF /* PBXTargetDependency */, + 43D9FFBA21EA9CA400AF44BF /* PBXTargetDependency */, + ); + name = Learn; + productName = Learn; + productReference = 43D9FFA221EA9A0C00AF44BF /* Learn.app */; + productType = "com.apple.product-type.application"; + }; + 43D9FFCE21EAE05D00AF44BF /* LoopCore */ = { + isa = PBXNativeTarget; + buildConfigurationList = 43D9FFD821EAE05D00AF44BF /* Build configuration list for PBXNativeTarget "LoopCore" */; + buildPhases = ( + 43D9FFCA21EAE05D00AF44BF /* Headers */, + 43D9FFCB21EAE05D00AF44BF /* Sources */, + 43D9FFCC21EAE05D00AF44BF /* Frameworks */, + 43D9FFCD21EAE05D00AF44BF /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 43D9001921EB207300AF44BF /* PBXTargetDependency */, + ); + name = LoopCore; + productName = LoopCore; + productReference = 43D9FFCF21EAE05D00AF44BF /* LoopCore.framework */; + productType = "com.apple.product-type.framework"; + }; 43E2D8D01D20BF42004DA55F /* DoseMathTests */ = { isa = PBXNativeTarget; buildConfigurationList = 43E2D8D61D20BF42004DA55F /* Build configuration list for PBXNativeTarget "DoseMathTests" */; @@ -1547,6 +1898,7 @@ buildRules = ( ); dependencies = ( + 43D9000D21EB0BEA00AF44BF /* PBXTargetDependency */, 4F7528991DFE1ED800C322D6 /* PBXTargetDependency */, ); name = "Loop Status Extension"; @@ -1567,6 +1919,7 @@ ); dependencies = ( 432CF87F20D8BC3B0066B889 /* PBXTargetDependency */, + 43D9001321EB137A00AF44BF /* PBXTargetDependency */, ); name = LoopUI; productName = LoopUI; @@ -1579,8 +1932,8 @@ 43776F841B8022E90074EA36 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0940; + LastSwiftUpdateCheck = 1010; + LastUpgradeCheck = 1010; ORGANIZATIONNAME = "LoopKit Authors"; TargetAttributes = { 432CF87720D8B8380066B889 = { @@ -1641,6 +1994,25 @@ }; }; }; + 43D9001A21EB209400AF44BF = { + ProvisioningStyle = Automatic; + }; + 43D9FFA121EA9A0C00AF44BF = { + CreatedOnToolsVersion = 10.1; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.ApplicationGroups.iOS = { + enabled = 1; + }; + com.apple.HealthKit = { + enabled = 1; + }; + }; + }; + 43D9FFCE21EAE05D00AF44BF = { + CreatedOnToolsVersion = 10.1; + ProvisioningStyle = Automatic; + }; 43E2D8D01D20BF42004DA55F = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 0800; @@ -1690,6 +2062,9 @@ 4F70C1DB1DE8DCA7006380B7 /* Loop Status Extension */, 43A943711B926B7B0051FA24 /* WatchApp */, 43A9437D1B926B7B0051FA24 /* WatchApp Extension */, + 43D9FFA121EA9A0C00AF44BF /* Learn */, + 43D9FFCE21EAE05D00AF44BF /* LoopCore */, + 43D9001A21EB209400AF44BF /* LoopCore-watchOS */, 4F75288A1DFE1DC600C322D6 /* LoopUI */, 43E2D8D01D20BF42004DA55F /* DoseMathTests */, 43E2D90A1D20C581004DA55F /* LoopTests */, @@ -1732,6 +2107,30 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 43D9002621EB209400AF44BF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 43D9FFA021EA9A0C00AF44BF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 43D9FFAF21EA9A0F00AF44BF /* LaunchScreen.storyboard in Resources */, + 43D9FFAC21EA9A0F00AF44BF /* Assets.xcassets in Resources */, + 43D9FFAA21EA9A0C00AF44BF /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 43D9FFCD21EAE05D00AF44BF /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 43E2D8CF1D20BF42004DA55F /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1817,6 +2216,27 @@ shellPath = /bin/sh; shellScript = "if ! [ -x \"$(command -v brew)\" ]; then\n # Install Homebrew\n ruby -e \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)\"\nfi\n\nif brew ls carthage > /dev/null; then\n brew upgrade carthage || echo \"Continuing…\"\nelse\n brew install carthage\nfi\n"; }; + 43D9FFE221EAE40600AF44BF /* Copy Frameworks with Carthage */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(SRCROOT)/Carthage/Build/iOS/LoopKit.framework", + "$(SRCROOT)/Carthage/Build/iOS/LoopKitUI.framework", + "$(SRCROOT)/Carthage/Build/iOS/SwiftCharts.framework", + ); + name = "Copy Frameworks with Carthage"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "carthage_frameworks_dir=${SRCROOT}/Carthage/Build/iOS\nif [ -f $PROJECT_DIR/.gitmodules && [ $ACTION != \"install\" ]; then\nfor varname in ${!SCRIPT_INPUT_FILE_*}\ndo\nexport ${varname}=${!varname/$carthage_frameworks_dir/$BUILT_PRODUCTS_DIR}\ndone\nfi\n/usr/local/bin/carthage copy-frameworks\n"; + }; 43EDDBEF1C361BCE007D89B5 /* Copy Frameworks with Carthage */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1865,7 +2285,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "carthage_frameworks_dir=${SRCROOT}/Carthage/Build/iOS\nif [ -f $PROJECT_DIR/.gitmodules ] && [ $ACTION != \"install\" ]; then\nfor varname in ${!SCRIPT_INPUT_FILE_*}\ndo\nexport ${varname}=${!varname/$carthage_frameworks_dir/$BUILT_PRODUCTS_DIR}\ndone\nfi\n/usr/local/bin/carthage copy-frameworks\n"; + shellScript = "carthage_frameworks_dir=${SRCROOT}/Carthage/Build/watchOS\nif [ -f $PROJECT_DIR/.gitmodules && [ $ACTION != \"install\" ]; then\nfor varname in ${!SCRIPT_INPUT_FILE_*}\ndo\nexport ${varname}=${!varname/$carthage_frameworks_dir/$BUILT_PRODUCTS_DIR}\ndone\nfi\n/usr/local/bin/carthage copy-frameworks\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -1876,14 +2296,12 @@ files = ( C17824A51E1AD4D100D9D25C /* BolusRecommendation.swift in Sources */, 4F70C2131DE90339006380B7 /* StatusExtensionContext.swift in Sources */, - 434F54571D287FDB002A9274 /* NibLoadable.swift in Sources */, 43441A9C1EDB34810087958C /* StatusExtensionContext+LoopKit.swift in Sources */, + 43C05CC521EC29E3006FB252 /* TextFieldTableViewCell.swift in Sources */, 4FF4D1001E18374700846527 /* WatchContext.swift in Sources */, - 430B298A2041F54A00BA9F93 /* NSUserDefaults.swift in Sources */, 4315D28A1CA5F45E00589052 /* DiagnosticLogger+LoopKit.swift in Sources */, 4F2C15821E074FC600E160D4 /* NSTimeInterval.swift in Sources */, 4311FB9B1F37FE1B00D4C0A7 /* TitleSubtitleTextFieldTableViewCell.swift in Sources */, - 430DA58E1D4AEC230097D1CA /* NSBundle.swift in Sources */, C1FB428F217921D600FAB378 /* PumpManagerUI.swift in Sources */, 43C513191E864C4E001547C7 /* GlucoseRangeSchedule.swift in Sources */, 43A51E1F1EB6D62A000736CC /* CarbAbsorptionViewController.swift in Sources */, @@ -1895,29 +2313,26 @@ 439A7942211F631C0041B75F /* RootNavigationController.swift in Sources */, 4F11D3C020DCBEEC006E072C /* GlucoseBackfillRequestUserInfo.swift in Sources */, 43F5C2DB1B92A5E1003EB13D /* SettingsTableViewController.swift in Sources */, - 434FF1EA1CF26C29000DB779 /* IdentifiableClass.swift in Sources */, 43A567691C94880B00334FAC /* LoopDataManager.swift in Sources */, - 43D848B01E7DCBE100DADCBC /* Result.swift in Sources */, 43B260491ED248FB008CAA77 /* CarbEntryTableViewCell.swift in Sources */, 4302F4E11D4E9C8900F0FCAF /* TextFieldTableViewController.swift in Sources */, - 435CB6271F37AE5600C320C7 /* WalshInsulinModel.swift in Sources */, 43F64DD91D9C92C900D24DC6 /* TitleSubtitleTableViewCell.swift in Sources */, C15713821DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift in Sources */, 435400321C9F745500D5819C /* BolusSuggestionUserInfo.swift in Sources */, 43E3449F1B9D68E900C85C07 /* StatusTableViewController.swift in Sources */, 43DBF0531C93EC8200B3C386 /* DeviceDataManager.swift in Sources */, 43E2D8C81D208D5B004DA55F /* KeychainManager+Loop.swift in Sources */, - 430B298F2041F56500BA9F93 /* GlucoseThreshold.swift in Sources */, C17824A01E19CF9800D9D25C /* GlucoseThresholdTableViewController.swift in Sources */, - 435CB6251F37ABFC00C320C7 /* ExponentialInsulinModelPreset.swift in Sources */, 4372E487213C86240068E043 /* SampleValue.swift in Sources */, 4346D1E71C77F5FE00ABAFE3 /* ChartTableViewCell.swift in Sources */, 437CEEE41CDE5C0A003C8C80 /* UIImage.swift in Sources */, 43DBF0591C93F73800B3C386 /* CarbEntryTableViewController.swift in Sources */, 43E93FB71E469A5100EAB8DB /* HKUnit.swift in Sources */, + 43C05CAF21EB2C24006FB252 /* NSBundle.swift in Sources */, 43BFF0BC1E45C80600FF19A9 /* UIColor+Loop.swift in Sources */, 43C0944A1CACCC73001F6403 /* NotificationManager.swift in Sources */, 4F08DE9D1E81D0E9006741EA /* StatusChartsManager+LoopKit.swift in Sources */, + 43D9003321EB258C00AF44BF /* InsulinModelSettings+Loop.swift in Sources */, 434FF1EE1CF27EEF000DB779 /* UITableViewCell.swift in Sources */, 439BED2A1E76093C00B0AED5 /* CGMManager.swift in Sources */, C18C8C511D5A351900E043FB /* NightscoutDataManager.swift in Sources */, @@ -1931,11 +2346,10 @@ 4374B5EF209D84BF00D17AA8 /* OSLog.swift in Sources */, 4F6663941E905FD2009E74FC /* ChartColorPalette+Loop.swift in Sources */, 4328E0351CFC0AE100E199AA /* WatchDataManager.swift in Sources */, + 4345E3FC21F04911009E00E5 /* UIColor+HIG.swift in Sources */, 43D381621EBD9759007F8C8F /* HeaderValuesTableViewCell.swift in Sources */, - 43BFF0C51E465A2D00FF19A9 /* UIColor+HIG.swift in Sources */, 43785E982120E7060057DED1 /* Intents.intentdefinition in Sources */, 4302F4E31D4EA54200F0FCAF /* InsulinDeliveryTableViewController.swift in Sources */, - 4374B5F4209D89A900D17AA8 /* TextFieldTableViewCell.swift in Sources */, 4FC8C8011DEB93E400A1452E /* NSUserDefaults+StatusExtension.swift in Sources */, 43DAD00020A2736F000F8529 /* PersistedPumpEvent.swift in Sources */, 438849EC1D29EC34003B3F23 /* AmplitudeService.swift in Sources */, @@ -1959,13 +2373,11 @@ 436A0DA51D236A2A00104B24 /* LoopError.swift in Sources */, 4F11D3C220DD80B3006E072C /* WatchHistoricalGlucose.swift in Sources */, 435CB6231F37967800C320C7 /* InsulinModelSettingsViewController.swift in Sources */, - 431E73481FF95A900069B5F7 /* PersistenceController.swift in Sources */, 4372E490213CFCE70068E043 /* LoopSettingsUserInfo.swift in Sources */, 43F78D261C8FC000002152D1 /* DoseMath.swift in Sources */, 438D42F91D7C88BC003244B0 /* PredictionInputEffect.swift in Sources */, 4F70C2101DE8FAC5006380B7 /* StatusExtensionDataManager.swift in Sources */, 43DFB62320D4CAE7008A7BAE /* PumpManager.swift in Sources */, - 435CB6291F37B01300C320C7 /* InsulinModelSettings.swift in Sources */, 431A8C401EC6E8AB00823B9C /* CircleMaskView.swift in Sources */, 439897371CD2F80600223065 /* AnalyticsManager.swift in Sources */, 430D85891F44037000AF2D4F /* HUDViewTableViewCell.swift in Sources */, @@ -1976,9 +2388,7 @@ 432E73CB1D24B3D6009AD15D /* RemoteDataManager.swift in Sources */, 43DE92591C5479E4001FFDE1 /* CarbEntryUserInfo.swift in Sources */, 43DE92611C555C26001FFDE1 /* AbsorptionTimeType+CarbKit.swift in Sources */, - 430B298E2041F56500BA9F93 /* LoopSettings.swift in Sources */, 43C2FAE11EB656A500364AFF /* GlucoseEffectVelocity.swift in Sources */, - 4374B5F2209D897600D17AA8 /* Locked.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1990,12 +2400,10 @@ 4F2C15741E0209F500E160D4 /* NSTimeInterval.swift in Sources */, 4FF4D1011E18375000846527 /* WatchContext.swift in Sources */, 435400311C9F744E00D5819C /* BolusSuggestionUserInfo.swift in Sources */, - 4372E49A213F7A830068E043 /* WalshInsulinModel.swift in Sources */, 898ECA63218ABD21001E9D35 /* ComplicationChartManager.swift in Sources */, 43A9438A1B926B7B0051FA24 /* NotificationController.swift in Sources */, 439A7945211FE23A0041B75F /* NSUserActivity.swift in Sources */, 43A943881B926B7B0051FA24 /* ExtensionDelegate.swift in Sources */, - 4372E498213F7A550068E043 /* InsulinModelSettings.swift in Sources */, 4F75F00220FCFE8C00B5570E /* GlucoseChartScene.swift in Sources */, 4328E02F1CFBF81800E199AA /* WKInterfaceImage.swift in Sources */, 4F2C15811E0495B200E160D4 /* WatchContext+WatchApp.swift in Sources */, @@ -2012,7 +2420,6 @@ 4328E02B1CFBE2C500E199AA /* WKAlertAction.swift in Sources */, 4F7E8AC720E2AC0300AEA65E /* WatchPredictedGlucose.swift in Sources */, 4344628E20A7ADD100C4BE6F /* UserDefaults+CGM.swift in Sources */, - 4372E499213F7A6D0068E043 /* ExponentialInsulinModelPreset.swift in Sources */, 4F7E8AC520E2AB9600AEA65E /* Date.swift in Sources */, 4F11D3C420DD881A006E072C /* WatchHistoricalGlucose.swift in Sources */, 4328E0281CFBE2C500E199AA /* CLKComplicationTemplate.swift in Sources */, @@ -2025,28 +2432,88 @@ 898ECA69218ABDA9001E9D35 /* CLKTextProvider+Compound.m in Sources */, 4372E48C213CB6750068E043 /* Double.swift in Sources */, 43785E972120E4500057DED1 /* INRelevantShortcutStore+Loop.swift in Sources */, - 4372E48E213CF8AD0068E043 /* LoopSettings.swift in Sources */, 898ECA65218ABD9B001E9D35 /* CGRect.swift in Sources */, - 4372E49B213F7B340068E043 /* NSBundle.swift in Sources */, 43CB2B2B1D924D450079823D /* WCSession.swift in Sources */, - 4372E497213F79F90068E043 /* NSUserDefaults.swift in Sources */, 4372E491213D05F90068E043 /* LoopSettingsUserInfo.swift in Sources */, 43DE925A1C5479E4001FFDE1 /* CarbEntryUserInfo.swift in Sources */, - 4FF0F75E20E1E5D100FC6291 /* PersistenceController.swift in Sources */, 43BFF0B51E45C1E700FF19A9 /* NumberFormatter.swift in Sources */, 43A9438E1B926B7B0051FA24 /* ComplicationController.swift in Sources */, 4328E01A1CFBE1DA00E199AA /* ActionHUDController.swift in Sources */, 4F11D3C320DD84DB006E072C /* GlucoseBackfillRequestUserInfo.swift in Sources */, - 4372E48D213CF8A70068E043 /* GlucoseThreshold.swift in Sources */, 435400351C9F878D00D5819C /* SetBolusUserInfo.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; + 43D9001F21EB209400AF44BF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 43C05CB821EBEA54006FB252 /* HKUnit.swift in Sources */, + 4345E3F421F036FC009E00E5 /* Result.swift in Sources */, + 43D9002021EB209400AF44BF /* NSTimeInterval.swift in Sources */, + 43C05CA921EB2B26006FB252 /* PersistenceController.swift in Sources */, + 431EA87221EB29150076EC1A /* InsulinModelSettings.swift in Sources */, + 43D9002121EB209400AF44BF /* GlucoseThreshold.swift in Sources */, + 4345E3F221F0351E009E00E5 /* Locked.swift in Sources */, + 43C05CAB21EB2B4A006FB252 /* NSBundle.swift in Sources */, + 43D9002221EB209400AF44BF /* LoopSettings.swift in Sources */, + 431EA87421EB291A0076EC1A /* WalshInsulinModel.swift in Sources */, + 431EA87021EB29120076EC1A /* ExponentialInsulinModelPreset.swift in Sources */, + 43C05CC721EC2ABC006FB252 /* IdentifiableClass.swift in Sources */, + 43C05CAE21EB2BBF006FB252 /* NSUserDefaults.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 43D9FF9E21EA9A0C00AF44BF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 43C05CBD21EBF77D006FB252 /* LessonsViewController.swift in Sources */, + 43C05CB621EBE321006FB252 /* NSTimeInterval.swift in Sources */, + 43C05CB521EBE274006FB252 /* Date.swift in Sources */, + 43D9F82421EFF1AB000578CD /* LessonResultsViewController.swift in Sources */, + 4345E3FA21F0473B009E00E5 /* TextCell.swift in Sources */, + 43D9F81821EC51CC000578CD /* DateEntry.swift in Sources */, + 43D9FFC021EAB22E00AF44BF /* DataManager.swift in Sources */, + 43C05CB121EBBDB9006FB252 /* TimeInRangeLesson.swift in Sources */, + 43D9F81E21EF0609000578CD /* NumberRangeEntry.swift in Sources */, + 43C05CCA21EC382B006FB252 /* NumberEntry.swift in Sources */, + 4345E3FE21F04A50009E00E5 /* DateIntervalFormatter.swift in Sources */, + 4345E3F821F03D2A009E00E5 /* DatesAndNumberCell.swift in Sources */, + 43D9F82221EF0A7A000578CD /* QuantityRangeEntry.swift in Sources */, + 43D9F81A21EC593C000578CD /* UITableViewCell.swift in Sources */, + 43D9F82021EF0906000578CD /* NSNumber.swift in Sources */, + 43C05CC221EC06E4006FB252 /* LessonConfigurationViewController.swift in Sources */, + 43C05CC621EC29E7006FB252 /* TextFieldTableViewCell.swift in Sources */, + 43C05CC021EBFFA4006FB252 /* Lesson.swift in Sources */, + 43D9FFA521EA9A0C00AF44BF /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 43D9FFCB21EAE05D00AF44BF /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 43C05CB921EBEA54006FB252 /* HKUnit.swift in Sources */, + 4345E3F521F036FC009E00E5 /* Result.swift in Sources */, + 43D9FFFB21EAF3D300AF44BF /* NSTimeInterval.swift in Sources */, + 43C05CA821EB2B26006FB252 /* PersistenceController.swift in Sources */, + 431EA87321EB29160076EC1A /* InsulinModelSettings.swift in Sources */, + 43D9FFF921EAF34800AF44BF /* GlucoseThreshold.swift in Sources */, + 4345E3F321F0351E009E00E5 /* Locked.swift in Sources */, + 43C05CAA21EB2B49006FB252 /* NSBundle.swift in Sources */, + 43D9FFF521EAF27200AF44BF /* LoopSettings.swift in Sources */, + 431EA87521EB291B0076EC1A /* WalshInsulinModel.swift in Sources */, + 431EA87121EB29120076EC1A /* ExponentialInsulinModelPreset.swift in Sources */, + 43C05CC821EC2ABC006FB252 /* IdentifiableClass.swift in Sources */, + 43C05CAD21EB2BBF006FB252 /* NSUserDefaults.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 43E2D8CD1D20BF42004DA55F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 43C3B6ED20B884500026CAFA /* GlucoseThreshold.swift in Sources */, 43947D731F529FAA00A07D31 /* GlucoseRangeSchedule.swift in Sources */, 43E2D8DC1D20C049004DA55F /* DoseMath.swift in Sources */, 43E2D8DB1D20C03B004DA55F /* NSTimeInterval.swift in Sources */, @@ -2069,26 +2536,19 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 43C05CAC21EB2B8B006FB252 /* NSBundle.swift in Sources */, 4FAC02541E22F6B20087A773 /* NSTimeInterval.swift in Sources */, - 434B2888206B4F0A000EE07B /* InsulinModelSettings.swift in Sources */, 4FB76FBA1E8C42CE00B39636 /* UIColor.swift in Sources */, 4F2C15831E0757E600E160D4 /* HKUnit.swift in Sources */, - 430B29902041F57000BA9F93 /* GlucoseThreshold.swift in Sources */, C1FB4290217922A100FAB378 /* PumpManagerUI.swift in Sources */, - 434B2887206B4F07000EE07B /* WalshInsulinModel.swift in Sources */, C1FB428D21791D2500FAB378 /* PumpManager.swift in Sources */, 43E93FB51E4675E800EAB8DB /* NumberFormatter.swift in Sources */, + 4345E3FB21F04911009E00E5 /* UIColor+HIG.swift in Sources */, 43BFF0CD1E466C8400FF19A9 /* StateColorPalette.swift in Sources */, - 430B29912041F57200BA9F93 /* LoopSettings.swift in Sources */, - 4F526D621DF9D95200A04910 /* NSBundle.swift in Sources */, 4FC8C8021DEB943800A1452E /* NSUserDefaults+StatusExtension.swift in Sources */, - 434B2889206B4F0C000EE07B /* ExponentialInsulinModelPreset.swift in Sources */, - 434B2886206628B3000EE07B /* PersistenceController.swift in Sources */, - 43BFF0C71E465A4F00FF19A9 /* UIColor+HIG.swift in Sources */, 43BFF0BF1E45C8EA00FF19A9 /* UIColor+Widget.swift in Sources */, 4F70C2121DE900EA006380B7 /* StatusExtensionContext.swift in Sources */, 4F70C1E11DE8DCA7006380B7 /* StatusViewController.swift in Sources */, - 430B298B2041F55700BA9F93 /* NSUserDefaults.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2098,7 +2558,6 @@ files = ( 4FB76FB91E8C42B000B39636 /* CollectionType.swift in Sources */, 7D23667D21250C7E0028B67D /* LocalizedString.swift in Sources */, - 4FF4D0F91E17268800846527 /* IdentifiableClass.swift in Sources */, 436961911F19D11E00447E89 /* ChartPointsContextFillLayer.swift in Sources */, 4FF4D0F81E1725B000846527 /* NibLoadable.swift in Sources */, 4326BA641F3A44D9007CCAD4 /* ChartLineModel.swift in Sources */, @@ -2154,6 +2613,46 @@ target = 43A943711B926B7B0051FA24 /* WatchApp */; targetProxy = 43A943921B926B7B0051FA24 /* PBXContainerItemProxy */; }; + 43D9000D21EB0BEA00AF44BF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 43D9FFCE21EAE05D00AF44BF /* LoopCore */; + targetProxy = 43D9000C21EB0BEA00AF44BF /* PBXContainerItemProxy */; + }; + 43D9001321EB137A00AF44BF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 43D9FFCE21EAE05D00AF44BF /* LoopCore */; + targetProxy = 43D9001221EB137A00AF44BF /* PBXContainerItemProxy */; + }; + 43D9001921EB207300AF44BF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 432CF87720D8B8380066B889 /* Cartfile */; + targetProxy = 43D9001821EB207300AF44BF /* PBXContainerItemProxy */; + }; + 43D9001B21EB209400AF44BF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 432CF87720D8B8380066B889 /* Cartfile */; + targetProxy = 43D9001C21EB209400AF44BF /* PBXContainerItemProxy */; + }; + 43D9003121EB236800AF44BF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 43D9001A21EB209400AF44BF /* LoopCore-watchOS */; + targetProxy = 43D9003021EB236800AF44BF /* PBXContainerItemProxy */; + }; + 43D9FFB821EA9C9F00AF44BF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 432CF87720D8B8380066B889 /* Cartfile */; + targetProxy = 43D9FFB721EA9C9F00AF44BF /* PBXContainerItemProxy */; + }; + 43D9FFBA21EA9CA400AF44BF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 4F75288A1DFE1DC600C322D6 /* LoopUI */; + targetProxy = 43D9FFB921EA9CA400AF44BF /* PBXContainerItemProxy */; + }; + 43D9FFD521EAE05D00AF44BF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 43D9FFCE21EAE05D00AF44BF /* LoopCore */; + targetProxy = 43D9FFD421EAE05D00AF44BF /* PBXContainerItemProxy */; + }; 43E2D9111D20C581004DA55F /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 43776F8B1B8022E90074EA36 /* Loop */; @@ -2240,6 +2739,22 @@ name = Interface.storyboard; sourceTree = ""; }; + 43D9FFA821EA9A0C00AF44BF /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 43D9FFA921EA9A0C00AF44BF /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 43D9FFAD21EA9A0F00AF44BF /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 43D9FFAE21EA9A0F00AF44BF /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; 4F70C1E21DE8DCA7006380B7 /* MainInterface.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -2756,6 +3271,202 @@ }; name = Release; }; + 43D9002821EB209400AF44BF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 54; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 54; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = ( + "$(PROJECT_DIR)/Carthage/Build/watchOS", + "$(inherited)", + ); + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = LoopCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.loopkit.LoopCore; + PRODUCT_NAME = LoopCore; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + TARGETED_DEVICE_FAMILY = 4; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 43D9002921EB209400AF44BF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 54; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 54; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = ( + "$(PROJECT_DIR)/Carthage/Build/watchOS", + "$(inherited)", + ); + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = LoopCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.loopkit.LoopCore; + PRODUCT_NAME = LoopCore; + SDKROOT = watchos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = 4; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 43D9FFB121EA9A0F00AF44BF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = Learn/Learn.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Learn/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER)Learn"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 43D9FFB221EA9A0F00AF44BF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = Learn/Learn.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Learn/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER)Learn"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.2; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 43D9FFD921EAE05D00AF44BF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 54; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 54; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = LoopCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.loopkit.LoopCore; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 43D9FFDA21EAE05D00AF44BF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 54; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 54; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = LoopCore/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.loopkit.LoopCore; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; 43E2D8D71D20BF42004DA55F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2950,6 +3661,33 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 43D9002721EB209400AF44BF /* Build configuration list for PBXNativeTarget "LoopCore-watchOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 43D9002821EB209400AF44BF /* Debug */, + 43D9002921EB209400AF44BF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 43D9FFB321EA9A0F00AF44BF /* Build configuration list for PBXNativeTarget "Learn" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 43D9FFB121EA9A0F00AF44BF /* Debug */, + 43D9FFB221EA9A0F00AF44BF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 43D9FFD821EAE05D00AF44BF /* Build configuration list for PBXNativeTarget "LoopCore" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 43D9FFD921EAE05D00AF44BF /* Debug */, + 43D9FFDA21EAE05D00AF44BF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 43E2D8D61D20BF42004DA55F /* Build configuration list for PBXNativeTarget "DoseMathTests" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Loop.xcodeproj/xcshareddata/xcschemes/Cartfile.xcscheme b/Loop.xcodeproj/xcshareddata/xcschemes/Cartfile.xcscheme new file mode 100644 index 0000000000..6b9c3c2cba --- /dev/null +++ b/Loop.xcodeproj/xcshareddata/xcschemes/Cartfile.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Loop.xcodeproj/xcshareddata/xcschemes/Complication - WatchApp.xcscheme b/Loop.xcodeproj/xcshareddata/xcschemes/Complication - WatchApp.xcscheme index 41e44bba86..e1cf09c605 100644 --- a/Loop.xcodeproj/xcshareddata/xcschemes/Complication - WatchApp.xcscheme +++ b/Loop.xcodeproj/xcshareddata/xcschemes/Complication - WatchApp.xcscheme @@ -1,6 +1,6 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Loop.xcodeproj/xcshareddata/xcschemes/Loop Status Extension.xcscheme b/Loop.xcodeproj/xcshareddata/xcschemes/Loop Status Extension.xcscheme index b9703af9df..7f086b637f 100644 --- a/Loop.xcodeproj/xcshareddata/xcschemes/Loop Status Extension.xcscheme +++ b/Loop.xcodeproj/xcshareddata/xcschemes/Loop Status Extension.xcscheme @@ -1,6 +1,6 @@ com.apple.developer.healthkit + com.apple.developer.healthkit.access + com.apple.developer.siri com.apple.security.application-groups diff --git a/Loop/Managers/AnalyticsManager.swift b/Loop/Managers/AnalyticsManager.swift index 431ed82921..dc62fde479 100644 --- a/Loop/Managers/AnalyticsManager.swift +++ b/Loop/Managers/AnalyticsManager.swift @@ -9,6 +9,7 @@ import Foundation import Amplitude import LoopKit +import LoopCore final class AnalyticsManager: IdentifiableClass { diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index 8fe8026f53..8603a0d156 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -9,6 +9,7 @@ import HealthKit import LoopKit import LoopKitUI +import LoopCore final class DeviceDataManager { @@ -29,7 +30,7 @@ final class DeviceDataManager { NotificationCenter.default.post(name: .PumpManagerChanged, object: self, userInfo: nil) - UserDefaults.appGroup.pumpManager = pumpManager + UserDefaults.appGroup?.pumpManager = pumpManager } } @@ -61,7 +62,7 @@ final class DeviceDataManager { didSet { setupCGM() - UserDefaults.appGroup.cgmManager = cgmManager + UserDefaults.appGroup?.cgmManager = cgmManager } } @@ -140,11 +141,11 @@ final class DeviceDataManager { private(set) var loopManager: LoopDataManager! init() { - pumpManager = UserDefaults.appGroup.pumpManager as? PumpManagerUI - - if let cgmManager = UserDefaults.appGroup.cgmManager { + pumpManager = UserDefaults.appGroup?.pumpManager as? PumpManagerUI + + if let cgmManager = UserDefaults.appGroup?.cgmManager { self.cgmManager = cgmManager - } else if UserDefaults.appGroup.isCGMManagerValidPumpManager { + } else if UserDefaults.appGroup?.isCGMManagerValidPumpManager == true { self.cgmManager = pumpManager as? CGMManager } @@ -228,7 +229,7 @@ extension DeviceDataManager: PumpManagerDelegate { func pumpManagerDidUpdateState(_ pumpManager: PumpManager) { log.default("PumpManager:\(type(of: pumpManager)) did update state") - UserDefaults.appGroup.pumpManager = pumpManager + UserDefaults.appGroup?.pumpManager = pumpManager } func pumpManagerBLEHeartbeatDidFire(_ pumpManager: PumpManager) { diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 6ae172a1f4..7b02413af5 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -9,6 +9,7 @@ import Foundation import HealthKit import LoopKit +import LoopCore final class LoopDataManager { @@ -44,11 +45,11 @@ final class LoopDataManager { init( lastLoopCompleted: Date?, lastTempBasal: DoseEntry?, - basalRateSchedule: BasalRateSchedule? = UserDefaults.appGroup.basalRateSchedule, - carbRatioSchedule: CarbRatioSchedule? = UserDefaults.appGroup.carbRatioSchedule, - insulinModelSettings: InsulinModelSettings? = UserDefaults.appGroup.insulinModelSettings, - insulinSensitivitySchedule: InsulinSensitivitySchedule? = UserDefaults.appGroup.insulinSensitivitySchedule, - settings: LoopSettings = UserDefaults.appGroup.loopSettings ?? LoopSettings() + basalRateSchedule: BasalRateSchedule? = UserDefaults.appGroup?.basalRateSchedule, + carbRatioSchedule: CarbRatioSchedule? = UserDefaults.appGroup?.carbRatioSchedule, + insulinModelSettings: InsulinModelSettings? = UserDefaults.appGroup?.insulinModelSettings, + insulinSensitivitySchedule: InsulinSensitivitySchedule? = UserDefaults.appGroup?.insulinSensitivitySchedule, + settings: LoopSettings = UserDefaults.appGroup?.loopSettings ?? LoopSettings() ) { self.logger = DiagnosticLogger.shared.forCategory("LoopDataManager") self.lockedLastLoopCompleted = Locked(lastLoopCompleted) @@ -118,7 +119,7 @@ final class LoopDataManager { /// These are not thread-safe. var settings: LoopSettings { didSet { - UserDefaults.appGroup.loopSettings = settings + UserDefaults.appGroup?.loopSettings = settings notify(forChange: .preferences) AnalyticsManager.shared.didChangeLoopSettings(from: oldValue, to: settings) } @@ -246,7 +247,7 @@ extension LoopDataManager { } set { doseStore.basalProfile = newValue - UserDefaults.appGroup.basalRateSchedule = newValue + UserDefaults.appGroup?.basalRateSchedule = newValue notify(forChange: .preferences) if let newValue = newValue, let oldValue = doseStore.basalProfile, newValue.items != oldValue.items { @@ -263,7 +264,7 @@ extension LoopDataManager { } set { carbStore.carbRatioSchedule = newValue - UserDefaults.appGroup.carbRatioSchedule = newValue + UserDefaults.appGroup?.carbRatioSchedule = newValue // Invalidate cached effects based on this schedule carbEffect = nil @@ -284,7 +285,7 @@ extension LoopDataManager { } set { doseStore.insulinModel = newValue?.model - UserDefaults.appGroup.insulinModelSettings = newValue + UserDefaults.appGroup?.insulinModelSettings = newValue self.dataAccessQueue.async { // Invalidate cached effects based on this schedule @@ -307,7 +308,7 @@ extension LoopDataManager { carbStore.insulinSensitivitySchedule = newValue doseStore.insulinSensitivitySchedule = newValue - UserDefaults.appGroup.insulinSensitivitySchedule = newValue + UserDefaults.appGroup?.insulinSensitivitySchedule = newValue dataAccessQueue.async { // Invalidate cached effects based on this schedule diff --git a/Loop/Managers/WatchDataManager.swift b/Loop/Managers/WatchDataManager.swift index 6f3fc32866..e8a9300b9a 100644 --- a/Loop/Managers/WatchDataManager.swift +++ b/Loop/Managers/WatchDataManager.swift @@ -10,6 +10,8 @@ import HealthKit import UIKit import WatchConnectivity import LoopKit +import LoopCore + final class WatchDataManager: NSObject { diff --git a/Loop/Models/LoopSettings+Loop.swift b/Loop/Models/LoopSettings+Loop.swift index 32ba973f0a..eaf569fdb0 100644 --- a/Loop/Models/LoopSettings+Loop.swift +++ b/Loop/Models/LoopSettings+Loop.swift @@ -5,6 +5,7 @@ // Copyright © 2018 LoopKit Authors. All rights reserved. // +import LoopCore // MARK: - Static configuration extension LoopSettings { diff --git a/Loop/View Controllers/BolusViewController.swift b/Loop/View Controllers/BolusViewController.swift index 3a58b7f3be..2e2cf97409 100644 --- a/Loop/View Controllers/BolusViewController.swift +++ b/Loop/View Controllers/BolusViewController.swift @@ -10,6 +10,7 @@ import UIKit import LocalAuthentication import LoopKit import HealthKit +import LoopCore final class BolusViewController: UITableViewController, IdentifiableClass, UITextFieldDelegate { diff --git a/Loop/View Controllers/CarbAbsorptionViewController.swift b/Loop/View Controllers/CarbAbsorptionViewController.swift index afab2f835f..8dfc1e32fd 100644 --- a/Loop/View Controllers/CarbAbsorptionViewController.swift +++ b/Loop/View Controllers/CarbAbsorptionViewController.swift @@ -12,6 +12,7 @@ import os.log import LoopKit import LoopKitUI +import LoopCore private extension RefreshContext { diff --git a/Loop/View Controllers/CarbEntryEditTableViewController.swift b/Loop/View Controllers/CarbEntryEditTableViewController.swift index 742ca766ba..99f73c6102 100644 --- a/Loop/View Controllers/CarbEntryEditTableViewController.swift +++ b/Loop/View Controllers/CarbEntryEditTableViewController.swift @@ -7,6 +7,7 @@ // import LoopKitUI +import LoopCore extension CarbEntryEditViewController: IdentifiableClass { diff --git a/Loop/View Controllers/CarbEntryTableViewController.swift b/Loop/View Controllers/CarbEntryTableViewController.swift index d2c6698bf9..1c66be5fb3 100644 --- a/Loop/View Controllers/CarbEntryTableViewController.swift +++ b/Loop/View Controllers/CarbEntryTableViewController.swift @@ -7,6 +7,7 @@ // import LoopKitUI +import LoopCore extension CarbEntryTableViewController: IdentifiableClass { diff --git a/Loop/View Controllers/InsulinDeliveryTableViewController.swift b/Loop/View Controllers/InsulinDeliveryTableViewController.swift index 4bd534e042..723ef3a8ff 100644 --- a/Loop/View Controllers/InsulinDeliveryTableViewController.swift +++ b/Loop/View Controllers/InsulinDeliveryTableViewController.swift @@ -7,6 +7,7 @@ // import LoopKitUI +import LoopCore extension InsulinDeliveryTableViewController: IdentifiableClass { diff --git a/Loop/View Controllers/InsulinModelSettingsViewController.swift b/Loop/View Controllers/InsulinModelSettingsViewController.swift index 855d0096f5..81878ba4fc 100644 --- a/Loop/View Controllers/InsulinModelSettingsViewController.swift +++ b/Loop/View Controllers/InsulinModelSettingsViewController.swift @@ -8,6 +8,7 @@ import UIKit import HealthKit import LoopKit +import LoopCore protocol InsulinModelSettingsViewControllerDelegate: class { diff --git a/Loop/View Controllers/PredictionTableViewController.swift b/Loop/View Controllers/PredictionTableViewController.swift index 14e8375b56..2b904cecaa 100644 --- a/Loop/View Controllers/PredictionTableViewController.swift +++ b/Loop/View Controllers/PredictionTableViewController.swift @@ -10,6 +10,7 @@ import UIKit import HealthKit import LoopKit import LoopKitUI +import LoopCore private extension RefreshContext { diff --git a/Loop/View Controllers/SettingsTableViewController.swift b/Loop/View Controllers/SettingsTableViewController.swift index 1ed62dfe69..d21e2d1d48 100644 --- a/Loop/View Controllers/SettingsTableViewController.swift +++ b/Loop/View Controllers/SettingsTableViewController.swift @@ -10,6 +10,7 @@ import UIKit import HealthKit import LoopKit import LoopKitUI +import LoopCore final class SettingsTableViewController: UITableViewController { diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index ed29d842c5..320fc740cc 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -9,6 +9,7 @@ import UIKit import HealthKit import Intents +import LoopCore import LoopKit import LoopKitUI import LoopUI diff --git a/Common/Models/GlucoseThreshold.swift b/LoopCore/GlucoseThreshold.swift similarity index 75% rename from Common/Models/GlucoseThreshold.swift rename to LoopCore/GlucoseThreshold.swift index 03bc479eab..8532905624 100644 --- a/Common/Models/GlucoseThreshold.swift +++ b/LoopCore/GlucoseThreshold.swift @@ -9,11 +9,11 @@ import Foundation import HealthKit -struct GlucoseThreshold: Equatable, RawRepresentable { - typealias RawValue = [String: Any] +public struct GlucoseThreshold: Equatable, RawRepresentable { + public typealias RawValue = [String: Any] - let value: Double - let unit: HKUnit + public let value: Double + public let unit: HKUnit public var quantity: HKQuantity { return HKQuantity(unit: unit, doubleValue: value) @@ -24,7 +24,7 @@ struct GlucoseThreshold: Equatable, RawRepresentable { self.unit = unit } - init?(rawValue: RawValue) { + public init?(rawValue: RawValue) { guard let unitsStr = rawValue["units"] as? String, let value = rawValue["value"] as? Double else { return nil } @@ -32,7 +32,7 @@ struct GlucoseThreshold: Equatable, RawRepresentable { self.value = value } - var rawValue: RawValue { + public var rawValue: RawValue { return [ "value": value, "units": unit.unitString diff --git a/LoopCore/HKUnit.swift b/LoopCore/HKUnit.swift new file mode 100644 index 0000000000..da6d179e84 --- /dev/null +++ b/LoopCore/HKUnit.swift @@ -0,0 +1,20 @@ +// +// HKUnit.swift +// Naterade +// +// Created by Nathan Racklyeft on 1/17/16. +// Copyright © 2016 Nathan Racklyeft. All rights reserved. +// + +import HealthKit + + +extension HKUnit { + public static let milligramsPerDeciliter: HKUnit = { + return HKUnit.gramUnit(with: .milli).unitDivided(by: .literUnit(with: .deci)) + }() + + public static let millimolesPerLiter: HKUnit = { + return HKUnit.moleUnit(with: .milli, molarMass: HKUnitMolarMassBloodGlucose).unitDivided(by: .liter()) + }() +} diff --git a/Common/Extensions/IdentifiableClass.swift b/LoopCore/IdentifiableClass.swift similarity index 79% rename from Common/Extensions/IdentifiableClass.swift rename to LoopCore/IdentifiableClass.swift index a350a1fde5..3e0035edd8 100644 --- a/Common/Extensions/IdentifiableClass.swift +++ b/LoopCore/IdentifiableClass.swift @@ -9,13 +9,13 @@ import Foundation -protocol IdentifiableClass: class { +public protocol IdentifiableClass: class { static var className: String { get } } extension IdentifiableClass { - static var className: String { + public static var className: String { return NSStringFromClass(self).components(separatedBy: ".").last! } } diff --git a/LoopCore/Info.plist b/LoopCore/Info.plist new file mode 100644 index 0000000000..e239aff029 --- /dev/null +++ b/LoopCore/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.9.4 dev + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/LoopCore/Insulin/ExponentialInsulinModelPreset.swift b/LoopCore/Insulin/ExponentialInsulinModelPreset.swift new file mode 100644 index 0000000000..e2f0877ed8 --- /dev/null +++ b/LoopCore/Insulin/ExponentialInsulinModelPreset.swift @@ -0,0 +1,63 @@ +// +// ExponentialInsulinModelPreset.swift +// Loop +// +// Copyright © 2017 LoopKit Authors. All rights reserved. +// + +import LoopKit + + +public enum ExponentialInsulinModelPreset: String { + case humalogNovologAdult + case humalogNovologChild + case fiasp +} + + +// MARK: - Model generation +extension ExponentialInsulinModelPreset { + var actionDuration: TimeInterval { + switch self { + case .humalogNovologAdult: + return .minutes(360) + case .humalogNovologChild: + return .minutes(360) + case .fiasp: + return .minutes(360) + } + } + + var peakActivity: TimeInterval { + switch self { + case .humalogNovologAdult: + return .minutes(75) + case .humalogNovologChild: + return .minutes(65) + case .fiasp: + return .minutes(55) + } + } + + var model: InsulinModel { + return ExponentialInsulinModel(actionDuration: actionDuration, peakActivityTime: peakActivity) + } +} + + +extension ExponentialInsulinModelPreset: InsulinModel { + public var effectDuration: TimeInterval { + return model.effectDuration + } + + public func percentEffectRemaining(at time: TimeInterval) -> Double { + return model.percentEffectRemaining(at: time) + } +} + + +extension ExponentialInsulinModelPreset: CustomDebugStringConvertible { + public var debugDescription: String { + return "\(self.rawValue)(\(String(reflecting: model))" + } +} diff --git a/Common/Models/Insulin/InsulinModelSettings.swift b/LoopCore/Insulin/InsulinModelSettings.swift similarity index 82% rename from Common/Models/Insulin/InsulinModelSettings.swift rename to LoopCore/Insulin/InsulinModelSettings.swift index 0d6eb295c4..84b3485555 100644 --- a/Common/Models/Insulin/InsulinModelSettings.swift +++ b/LoopCore/Insulin/InsulinModelSettings.swift @@ -8,11 +8,11 @@ import LoopKit -enum InsulinModelSettings { +public enum InsulinModelSettings { case exponentialPreset(ExponentialInsulinModelPreset) case walsh(WalshInsulinModel) - var model: InsulinModel { + public var model: InsulinModel { switch self { case .exponentialPreset(let model): return model @@ -21,7 +21,7 @@ enum InsulinModelSettings { } } - init?(model: InsulinModel) { + public init?(model: InsulinModel) { switch model { case let model as ExponentialInsulinModelPreset: self = .exponentialPreset(model) @@ -35,25 +35,16 @@ enum InsulinModelSettings { extension InsulinModelSettings: CustomDebugStringConvertible { - var title: String { - switch self { - case .exponentialPreset(let model): - return model.title - case .walsh(let model): - return model.title - } - } - - var debugDescription: String { + public var debugDescription: String { return String(reflecting: model) } } extension InsulinModelSettings: RawRepresentable { - typealias RawValue = [String: Any] + public typealias RawValue = [String: Any] - init?(rawValue: RawValue) { + public init?(rawValue: RawValue) { guard let typeName = rawValue["type"] as? InsulinModelType.RawValue, let type = InsulinModelType(rawValue: typeName) else { @@ -80,7 +71,7 @@ extension InsulinModelSettings: RawRepresentable { } } - var rawValue: [String : Any] { + public var rawValue: [String : Any] { switch self { case .exponentialPreset(let model): return [ diff --git a/Common/Models/Insulin/WalshInsulinModel.swift b/LoopCore/Insulin/WalshInsulinModel.swift similarity index 58% rename from Common/Models/Insulin/WalshInsulinModel.swift rename to LoopCore/Insulin/WalshInsulinModel.swift index 50d2f70ad5..eaa6aeae55 100644 --- a/Common/Models/Insulin/WalshInsulinModel.swift +++ b/LoopCore/Insulin/WalshInsulinModel.swift @@ -23,15 +23,3 @@ extension WalshInsulinModel: RawRepresentable { return ["actionDuration": self.actionDuration] } } - - -// MARK: - Localization -extension WalshInsulinModel { - var title: String { - return NSLocalizedString("Walsh", comment: "Title of insulin model setting") - } - - var subtitle: String { - return NSLocalizedString("The legacy model used by Loop, allowing customization of action duration.", comment: "Subtitle description of Walsh insulin model setting") - } -} diff --git a/Loop/Models/Locked.swift b/LoopCore/Locked.swift similarity index 82% rename from Loop/Models/Locked.swift rename to LoopCore/Locked.swift index fd6e35b104..27dd05d96b 100644 --- a/Loop/Models/Locked.swift +++ b/LoopCore/Locked.swift @@ -8,17 +8,17 @@ import os.lock -internal class Locked { +public class Locked { private var lock = os_unfair_lock() private var _value: T - init(_ value: T) { + public init(_ value: T) { os_unfair_lock_lock(&lock) defer { os_unfair_lock_unlock(&lock) } _value = value } - var value: T { + public var value: T { get { os_unfair_lock_lock(&lock) defer { os_unfair_lock_unlock(&lock) } @@ -31,7 +31,7 @@ internal class Locked { } } - func mutate(_ changes: (_ value: inout T) -> Void) -> T { + public func mutate(_ changes: (_ value: inout T) -> Void) -> T { os_unfair_lock_lock(&lock) defer { os_unfair_lock_unlock(&lock) } changes(&_value) diff --git a/LoopCore/LoopCore.h b/LoopCore/LoopCore.h new file mode 100644 index 0000000000..619115ff00 --- /dev/null +++ b/LoopCore/LoopCore.h @@ -0,0 +1,18 @@ +// +// LoopCore.h +// LoopCore +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +#import + +//! Project version number for LoopCore. +FOUNDATION_EXPORT double LoopCoreVersionNumber; + +//! Project version string for LoopCore. +FOUNDATION_EXPORT const unsigned char LoopCoreVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/Common/Models/LoopSettings.swift b/LoopCore/LoopSettings.swift similarity index 56% rename from Common/Models/LoopSettings.swift rename to LoopCore/LoopSettings.swift index ae1c5a33b1..4d0d4f084b 100644 --- a/Common/Models/LoopSettings.swift +++ b/LoopCore/LoopSettings.swift @@ -8,43 +8,59 @@ import LoopKit -struct LoopSettings: Equatable { - var dosingEnabled = false +public struct LoopSettings: Equatable { + public var dosingEnabled = false - let dynamicCarbAbsorptionEnabled = true + public let dynamicCarbAbsorptionEnabled = true - var glucoseTargetRangeSchedule: GlucoseRangeSchedule? + public var glucoseTargetRangeSchedule: GlucoseRangeSchedule? - var maximumBasalRatePerHour: Double? + public var maximumBasalRatePerHour: Double? - var maximumBolus: Double? + public var maximumBolus: Double? - var suspendThreshold: GlucoseThreshold? = nil + public var suspendThreshold: GlucoseThreshold? = nil - var retrospectiveCorrectionEnabled = true + public var retrospectiveCorrectionEnabled = true /// The interval over which to aggregate changes in glucose for retrospective correction - let retrospectiveCorrectionGroupingInterval = TimeInterval(minutes: 30) + public let retrospectiveCorrectionGroupingInterval = TimeInterval(minutes: 30) /// The maximum duration over which to integrate retrospective correction changes - let retrospectiveCorrectionIntegrationInterval = TimeInterval(minutes: 30) + public let retrospectiveCorrectionIntegrationInterval = TimeInterval(minutes: 30) /// The amount of time since a given date that data should be considered valid - let recencyInterval = TimeInterval(minutes: 15) + public let recencyInterval = TimeInterval(minutes: 15) // MARK - Display settings - let minimumChartWidthPerHour: CGFloat = 50 - - let statusChartMinimumHistoryDisplay: TimeInterval = .hours(1) + public let minimumChartWidthPerHour: CGFloat = 50 + + public let statusChartMinimumHistoryDisplay: TimeInterval = .hours(1) + + public init( + dosingEnabled: Bool = false, + glucoseTargetRangeSchedule: GlucoseRangeSchedule? = nil, + maximumBasalRatePerHour: Double? = nil, + maximumBolus: Double? = nil, + suspendThreshold: GlucoseThreshold? = nil, + retrospectiveCorrectionEnabled: Bool = true + ) { + self.dosingEnabled = dosingEnabled + self.glucoseTargetRangeSchedule = glucoseTargetRangeSchedule + self.maximumBasalRatePerHour = maximumBasalRatePerHour + self.maximumBolus = maximumBolus + self.suspendThreshold = suspendThreshold + self.retrospectiveCorrectionEnabled = retrospectiveCorrectionEnabled + } } extension LoopSettings: RawRepresentable { - typealias RawValue = [String: Any] + public typealias RawValue = [String: Any] private static let version = 1 - init?(rawValue: RawValue) { + public init?(rawValue: RawValue) { guard let version = rawValue["version"] as? Int, version == LoopSettings.version @@ -73,7 +89,7 @@ extension LoopSettings: RawRepresentable { } } - var rawValue: RawValue { + public var rawValue: RawValue { var raw: RawValue = [ "version": LoopSettings.version, "dosingEnabled": dosingEnabled, diff --git a/Common/Extensions/NSUserDefaults.swift b/LoopCore/NSUserDefaults.swift similarity index 92% rename from Common/Extensions/NSUserDefaults.swift rename to LoopCore/NSUserDefaults.swift index 7d5d73edde..062cb83ca6 100644 --- a/Common/Extensions/NSUserDefaults.swift +++ b/LoopCore/NSUserDefaults.swift @@ -21,7 +21,9 @@ extension UserDefaults { case insulinSensitivitySchedule = "com.loudnate.Naterade.InsulinSensitivitySchedule" } - var basalRateSchedule: BasalRateSchedule? { + public static let appGroup = UserDefaults(suiteName: Bundle.main.appGroupSuiteName) + + public var basalRateSchedule: BasalRateSchedule? { get { if let rawValue = dictionary(forKey: Key.basalRateSchedule.rawValue) { return BasalRateSchedule(rawValue: rawValue) @@ -34,7 +36,7 @@ extension UserDefaults { } } - var carbRatioSchedule: CarbRatioSchedule? { + public var carbRatioSchedule: CarbRatioSchedule? { get { if let rawValue = dictionary(forKey: Key.carbRatioSchedule.rawValue) { return CarbRatioSchedule(rawValue: rawValue) @@ -47,7 +49,7 @@ extension UserDefaults { } } - var insulinModelSettings: InsulinModelSettings? { + public var insulinModelSettings: InsulinModelSettings? { get { if let rawValue = dictionary(forKey: Key.insulinModelSettings.rawValue) { return InsulinModelSettings(rawValue: rawValue) @@ -67,7 +69,7 @@ extension UserDefaults { } } - var loopSettings: LoopSettings? { + public var loopSettings: LoopSettings? { get { if let rawValue = dictionary(forKey: Key.loopSettings.rawValue) { return LoopSettings(rawValue: rawValue) @@ -124,7 +126,7 @@ extension UserDefaults { } } - var insulinSensitivitySchedule: InsulinSensitivitySchedule? { + public var insulinSensitivitySchedule: InsulinSensitivitySchedule? { get { if let rawValue = dictionary(forKey: Key.insulinSensitivitySchedule.rawValue) { return InsulinSensitivitySchedule(rawValue: rawValue) diff --git a/Common/Extensions/PersistenceController.swift b/LoopCore/PersistenceController.swift similarity index 81% rename from Common/Extensions/PersistenceController.swift rename to LoopCore/PersistenceController.swift index a5c465bc42..4f88ffd24e 100644 --- a/Common/Extensions/PersistenceController.swift +++ b/LoopCore/PersistenceController.swift @@ -9,19 +9,19 @@ import LoopKit extension PersistenceController { - class func controllerInAppGroupDirectory() -> PersistenceController { + public class func controllerInAppGroupDirectory(isReadOnly: Bool = false) -> PersistenceController { let appGroup = Bundle.main.appGroupSuiteName guard let directoryURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else { assertionFailure("Could not get a container directory URL. Please ensure App Groups are set up correctly in entitlements.") return self.init(directoryURL: URL(fileURLWithPath: "/")) } - let isReadOnly = Bundle.main.bundleURL.pathExtension == "appex" + let isReadOnly = isReadOnly || Bundle.main.bundleURL.pathExtension == "appex" return self.init(directoryURL: directoryURL.appendingPathComponent("com.loopkit.LoopKit", isDirectory: true), isReadOnly: isReadOnly) } - class func controllerInLocalDirectory() -> PersistenceController { + public class func controllerInLocalDirectory() -> PersistenceController { guard let directoryURL = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) else { fatalError("Could not access the document directory of the current process") } diff --git a/Loop/Models/Result.swift b/LoopCore/Result.swift similarity index 85% rename from Loop/Models/Result.swift rename to LoopCore/Result.swift index b6763baf7c..580595159d 100644 --- a/Loop/Models/Result.swift +++ b/LoopCore/Result.swift @@ -6,7 +6,7 @@ // -enum Result { +public enum Result { case success(T) case failure(Error) } diff --git a/Common/Extensions/NibLoadable.swift b/LoopUI/Extensions/NibLoadable.swift similarity index 73% rename from Common/Extensions/NibLoadable.swift rename to LoopUI/Extensions/NibLoadable.swift index 4e762b2c69..00edfe2f6c 100644 --- a/Common/Extensions/NibLoadable.swift +++ b/LoopUI/Extensions/NibLoadable.swift @@ -7,15 +7,16 @@ // import UIKit +import LoopCore -protocol NibLoadable: IdentifiableClass { +public protocol NibLoadable: IdentifiableClass { static func nib() -> UINib } extension NibLoadable { - static func nib() -> UINib { + public static func nib() -> UINib { return UINib(nibName: className, bundle: Bundle(for: self)) } } diff --git a/LoopUI/Managers/StatusChartsManager.swift b/LoopUI/Managers/StatusChartsManager.swift index d605937b15..382115dfa6 100644 --- a/LoopUI/Managers/StatusChartsManager.swift +++ b/LoopUI/Managers/StatusChartsManager.swift @@ -8,6 +8,7 @@ import Foundation import HealthKit +import LoopCore import LoopKit import SwiftCharts import os.log diff --git a/WatchApp Extension/Controllers/ActionHUDController.swift b/WatchApp Extension/Controllers/ActionHUDController.swift index 71eef5a0f7..7729cb1fd0 100644 --- a/WatchApp Extension/Controllers/ActionHUDController.swift +++ b/WatchApp Extension/Controllers/ActionHUDController.swift @@ -9,6 +9,7 @@ import WatchKit import WatchConnectivity import LoopKit +import LoopCore final class ActionHUDController: HUDInterfaceController { diff --git a/WatchApp Extension/Managers/LoopDataManager.swift b/WatchApp Extension/Managers/LoopDataManager.swift index 4191ced26f..5195e950fc 100644 --- a/WatchApp Extension/Managers/LoopDataManager.swift +++ b/WatchApp Extension/Managers/LoopDataManager.swift @@ -9,6 +9,7 @@ import Foundation import HealthKit import LoopKit +import LoopCore import WatchConnectivity import os.log diff --git a/WatchApp Extension/Models/GlucoseChartData.swift b/WatchApp Extension/Models/GlucoseChartData.swift index 1be3f2d8d5..c45f24fa04 100644 --- a/WatchApp Extension/Models/GlucoseChartData.swift +++ b/WatchApp Extension/Models/GlucoseChartData.swift @@ -95,9 +95,9 @@ private extension HKUnit { var lowWatermark: Double { if self == .milligramsPerDeciliter { - return 50.0 + return 75 } else { - return 3.0 + return 3 } } } From cc1ae8173b1a0020bb3d6a256224a5b9be9b6d6e Mon Sep 17 00:00:00 2001 From: Michael Pangburn Date: Fri, 15 Mar 2019 11:09:19 -0700 Subject: [PATCH 009/132] Mock managers (#4) * Move mock health data deletion to Loop * Fix mock data deletion section logic --- Common/Models/PumpManager.swift | 4 +- Loop.xcodeproj/project.pbxproj | 21 +++ Loop/Extensions/Debug.swift | 15 ++ .../RangeReplaceableCollection.swift | 20 +++ Loop/Managers/CGMManager.swift | 2 + Loop/Managers/DeviceDataManager.swift | 43 ++++++ .../SettingsTableViewController.swift | 129 ++++++++++++++++-- 7 files changed, 223 insertions(+), 11 deletions(-) create mode 100644 Loop/Extensions/Debug.swift create mode 100644 Loop/Extensions/RangeReplaceableCollection.swift diff --git a/Common/Models/PumpManager.swift b/Common/Models/PumpManager.swift index 60e172b4a6..6bc58bb7ec 100644 --- a/Common/Models/PumpManager.swift +++ b/Common/Models/PumpManager.swift @@ -8,10 +8,12 @@ import Foundation import LoopKit import MinimedKit +import MockKit let allPumpManagers: [PumpManager.Type] = [ - MinimedPumpManager.self + MinimedPumpManager.self, + MockPumpManager.self, ] private let managersByIdentifier: [String: PumpManager.Type] = allPumpManagers.reduce(into: [:]) { (map, Type) in diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 4422013483..90cb38a90c 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -282,6 +282,11 @@ 7D70765E1FE06EE3004AC8EA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D7076601FE06EE3004AC8EA /* Localizable.strings */; }; 7D7076631FE06EE4004AC8EA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D7076651FE06EE4004AC8EA /* Localizable.strings */; }; 7D7076681FE0702F004AC8EA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D70766A1FE0702F004AC8EA /* InfoPlist.strings */; }; + 892A5D2A222EF60A008961AB /* MockKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 892A5D29222EF60A008961AB /* MockKit.framework */; }; + 892A5D2C222EF60A008961AB /* MockKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 892A5D2B222EF60A008961AB /* MockKitUI.framework */; }; + 892A5D59222F0A27008961AB /* Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892A5D58222F0A27008961AB /* Debug.swift */; }; + 892A5D5B222F0D7C008961AB /* LoopTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 892A5D5A222F0D7C008961AB /* LoopTestingKit.framework */; }; + 892A5D692230C41D008961AB /* RangeReplaceableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892A5D682230C41D008961AB /* RangeReplaceableCollection.swift */; }; 894F71E21FFEC4D8007D365C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 894F71E11FFEC4D8007D365C /* Assets.xcassets */; }; 898ECA60218ABD17001E9D35 /* GlucoseChartScaler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898ECA5E218ABD17001E9D35 /* GlucoseChartScaler.swift */; }; 898ECA61218ABD17001E9D35 /* GlucoseChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898ECA5F218ABD17001E9D35 /* GlucoseChartData.swift */; }; @@ -819,6 +824,11 @@ 7DD382771F8DBFC60071272B /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Main.strings; sourceTree = ""; }; 7DD382781F8DBFC60071272B /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/MainInterface.strings; sourceTree = ""; }; 7DD382791F8DBFC60071272B /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Interface.strings; sourceTree = ""; }; + 892A5D29222EF60A008961AB /* MockKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MockKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 892A5D2B222EF60A008961AB /* MockKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MockKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 892A5D58222F0A27008961AB /* Debug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debug.swift; sourceTree = ""; }; + 892A5D5A222F0D7C008961AB /* LoopTestingKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LoopTestingKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 892A5D682230C41D008961AB /* RangeReplaceableCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RangeReplaceableCollection.swift; sourceTree = ""; }; 894F71E11FFEC4D8007D365C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 898ECA5E218ABD17001E9D35 /* GlucoseChartScaler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseChartScaler.swift; sourceTree = ""; }; 898ECA5F218ABD17001E9D35 /* GlucoseChartData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseChartData.swift; sourceTree = ""; }; @@ -857,6 +867,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 892A5D5B222F0D7C008961AB /* LoopTestingKit.framework in Frameworks */, + 892A5D2A222EF60A008961AB /* MockKit.framework in Frameworks */, + 892A5D2C222EF60A008961AB /* MockKitUI.framework in Frameworks */, 4F7528941DFE1E9500C322D6 /* LoopUI.framework in Frameworks */, 434FB6461D68F1CD007B9C70 /* Amplitude.framework in Frameworks */, 43A8EC6F210E622700A81379 /* CGMBLEKitUI.framework in Frameworks */, @@ -1147,6 +1160,8 @@ 437CEEE31CDE5C0A003C8C80 /* UIImage.swift */, 434FF1ED1CF27EEF000DB779 /* UITableViewCell.swift */, 430B29922041F5B200BA9F93 /* UserDefaults+Loop.swift */, + 892A5D58222F0A27008961AB /* Debug.swift */, + 892A5D682230C41D008961AB /* RangeReplaceableCollection.swift */, ); path = Extensions; sourceTree = ""; @@ -1394,6 +1409,9 @@ 968DCD53F724DE56FFE51920 /* Frameworks */ = { isa = PBXGroup; children = ( + 892A5D5A222F0D7C008961AB /* LoopTestingKit.framework */, + 892A5D29222EF60A008961AB /* MockKit.framework */, + 892A5D2B222EF60A008961AB /* MockKitUI.framework */, C168C40521B0D53E00ADE90E /* MinimedKit.framework */, C168C40721B0D53E00ADE90E /* MinimedKitUI.framework */, C1C108C22152F46D00EA5165 /* CGMBLEKitUI.framework */, @@ -1963,8 +1981,10 @@ 4372E490213CFCE70068E043 /* LoopSettingsUserInfo.swift in Sources */, 43F78D261C8FC000002152D1 /* DoseMath.swift in Sources */, 438D42F91D7C88BC003244B0 /* PredictionInputEffect.swift in Sources */, + 892A5D692230C41D008961AB /* RangeReplaceableCollection.swift in Sources */, 4F70C2101DE8FAC5006380B7 /* StatusExtensionDataManager.swift in Sources */, 43DFB62320D4CAE7008A7BAE /* PumpManager.swift in Sources */, + 892A5D59222F0A27008961AB /* Debug.swift in Sources */, 435CB6291F37B01300C320C7 /* InsulinModelSettings.swift in Sources */, 431A8C401EC6E8AB00823B9C /* CircleMaskView.swift in Sources */, 439897371CD2F80600223065 /* AnalyticsManager.swift in Sources */, @@ -2644,6 +2664,7 @@ DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = Loop/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + "OTHER_SWIFT_FLAGS[arch=*]" = "-DDEBUG"; "OTHER_SWIFT_FLAGS[sdk=iphonesimulator*]" = "-D IOS_SIMULATOR"; PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/Loop/Extensions/Debug.swift b/Loop/Extensions/Debug.swift new file mode 100644 index 0000000000..7e4a0f39d8 --- /dev/null +++ b/Loop/Extensions/Debug.swift @@ -0,0 +1,15 @@ +// +// Debug.swift +// Loop +// +// Created by Michael Pangburn on 3/5/19. +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +func assertingDebugOnly(file: StaticString = #file, line: UInt = #line, _ doIt: () -> Void) { + #if DEBUG || IOS_SIMULATOR + doIt() + #else + fatalError("\(file):\(line) should never be invoked in release builds", file: file, line: line) + #endif +} diff --git a/Loop/Extensions/RangeReplaceableCollection.swift b/Loop/Extensions/RangeReplaceableCollection.swift new file mode 100644 index 0000000000..d4437a9555 --- /dev/null +++ b/Loop/Extensions/RangeReplaceableCollection.swift @@ -0,0 +1,20 @@ +// +// RangeReplaceableCollection.swift +// Loop +// +// Created by Michael Pangburn on 3/6/19. +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +extension RangeReplaceableCollection where Element: Equatable { + /// Returns `true` if the element was removed, or `false` if it is not present in the collection. + @discardableResult + mutating func remove(_ element: Element) -> Bool { + guard let index = self.index(of: element) else { + return false + } + + remove(at: index) + return true + } +} diff --git a/Loop/Managers/CGMManager.swift b/Loop/Managers/CGMManager.swift index 50bfb7bcae..7809c9f75c 100644 --- a/Loop/Managers/CGMManager.swift +++ b/Loop/Managers/CGMManager.swift @@ -9,6 +9,7 @@ import LoopKit import CGMBLEKit import G4ShareSpy import ShareClient +import MockKit let allCGMManagers: [CGMManager.Type] = [ @@ -16,6 +17,7 @@ let allCGMManagers: [CGMManager.Type] = [ G5CGMManager.self, G4CGMManager.self, ShareClientManager.self, + MockCGMManager.self, ] diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index 8fe8026f53..6c5daf545a 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -9,6 +9,8 @@ import HealthKit import LoopKit import LoopKitUI +import LoopTestingKit + final class DeviceDataManager { @@ -214,6 +216,10 @@ extension DeviceDataManager: CGMManagerDelegate { func startDateToFilterNewData(for manager: CGMManager) -> Date? { return loopManager.glucoseStore.latestGlucose?.startDate } + + func cgmManagerDidUpdateState(_ manager: CGMManager) { + UserDefaults.appGroup.cgmManager = manager + } } @@ -387,6 +393,43 @@ extension DeviceDataManager { } } +extension DeviceDataManager { + func deleteTestingPumpData() { + assertingDebugOnly { + guard let testingPumpManager = pumpManager as? TestingPumpManager else { + assertionFailure("\(#function) should be invoked only when a testing pump manager is in use") + return + } + let devicePredicate = HKQuery.predicateForObjects(from: [testingPumpManager.testingDevice]) + + // DoseStore.deleteAllPumpEvents first syncs the events to the health store, + // so HKHealthStore.deleteObjects catches any that were still in the cache. + let doseStore = loopManager.doseStore + let healthStore = doseStore.insulinDeliveryStore.healthStore + doseStore.deleteAllPumpEvents { doseStoreError in + if doseStoreError != nil { + healthStore.deleteObjects(of: doseStore.sampleType!, predicate: devicePredicate) { success, deletedObjectCount, error in + // errors are already logged through the store, so we'll ignore them here + } + } + } + } + } + + func deleteTestingCGMData() { + assertingDebugOnly { + guard let testingCGMManager = cgmManager as? TestingCGMManager else { + assertionFailure("\(#function) should be invoked only when a testing CGM manager is in use") + return + } + let predicate = HKQuery.predicateForObjects(from: [testingCGMManager.testingDevice]) + loopManager.glucoseStore.purgeGlucoseSamples(matchingCachePredicate: nil, healthKitPredicate: predicate) { success, count, error in + // result already logged through the store, so ignore the error here + } + } + } +} + extension DeviceDataManager: LoopDataManagerDelegate { func loopDataManager(_ manager: LoopDataManager, roundBasalRate unitsPerHour: Double) -> Double { guard let pumpManager = pumpManager else { diff --git a/Loop/View Controllers/SettingsTableViewController.swift b/Loop/View Controllers/SettingsTableViewController.swift index 1ed62dfe69..6903120f84 100644 --- a/Loop/View Controllers/SettingsTableViewController.swift +++ b/Loop/View Controllers/SettingsTableViewController.swift @@ -10,6 +10,7 @@ import UIKit import HealthKit import LoopKit import LoopKitUI +import LoopTestingKit final class SettingsTableViewController: UITableViewController { @@ -46,12 +47,17 @@ final class SettingsTableViewController: UITableViewController { var dataManager: DeviceDataManager! - fileprivate enum Section: Int, CaseCountable { + private lazy var isTestingPumpManager = dataManager.pumpManager is TestingPumpManager + private lazy var isTestingCGMManager = dataManager.cgmManager is TestingCGMManager + + fileprivate enum Section: Int, CaseIterable { case loop = 0 case pump case cgm case configuration case services + case testingPumpDataDeletion + case testingCGMDataDeletion } fileprivate enum LoopRow: Int, CaseCountable { @@ -121,12 +127,23 @@ final class SettingsTableViewController: UITableViewController { // MARK: - UITableViewDataSource + private var sections: [Section] { + var sections = Section.allCases + if !isTestingPumpManager { + sections.remove(.testingPumpDataDeletion) + } + if !isTestingCGMManager { + sections.remove(.testingCGMDataDeletion) + } + return sections + } + override func numberOfSections(in tableView: UITableView) -> Int { - return Section.count + return sections.count } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - switch Section(rawValue: section)! { + switch sections[section] { case .loop: return LoopRow.count case .pump: @@ -137,11 +154,13 @@ final class SettingsTableViewController: UITableViewController { return ConfigurationRow.count case .services: return ServiceRow.count + case .testingPumpDataDeletion, .testingCGMDataDeletion: + return 1 } } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - switch Section(rawValue: indexPath.section)! { + switch sections[indexPath.section] { case .loop: switch LoopRow(rawValue: indexPath.row)! { case .dosing: @@ -294,11 +313,25 @@ final class SettingsTableViewController: UITableViewController { configCell.accessoryType = .disclosureIndicator return configCell + case .testingPumpDataDeletion: + let cell = tableView.dequeueReusableCell(withIdentifier: TextButtonTableViewCell.className, for: indexPath) as! TextButtonTableViewCell + cell.textLabel?.text = "Delete Pump Data" + cell.textLabel?.textAlignment = .center + cell.tintColor = .delete + cell.isEnabled = true + return cell + case .testingCGMDataDeletion: + let cell = tableView.dequeueReusableCell(withIdentifier: TextButtonTableViewCell.className, for: indexPath) as! TextButtonTableViewCell + cell.textLabel?.text = "Delete CGM Data" + cell.textLabel?.textAlignment = .center + cell.tintColor = .delete + cell.isEnabled = true + return cell } } override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - switch Section(rawValue: section)! { + switch sections[section] { case .loop: return Bundle.main.localizedNameAndVersion case .pump: @@ -309,6 +342,8 @@ final class SettingsTableViewController: UITableViewController { return NSLocalizedString("Configuration", comment: "The title of the configuration section in settings") case .services: return NSLocalizedString("Services", comment: "The title of the services section in settings") + case .testingPumpDataDeletion, .testingCGMDataDeletion: + return nil } } @@ -321,7 +356,7 @@ final class SettingsTableViewController: UITableViewController { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let sender = tableView.cellForRow(at: indexPath) - switch Section(rawValue: indexPath.section)! { + switch sections[indexPath.section] { case .pump: switch PumpRow(rawValue: indexPath.row)! { case .pumpSettings: @@ -556,22 +591,62 @@ final class SettingsTableViewController: UITableViewController { show(vc, sender: sender) } + case .testingPumpDataDeletion: + let confirmVC = UIAlertController(pumpDataDeletionHandler: dataManager.deleteTestingPumpData) + present(confirmVC, animated: true) { + tableView.deselectRow(at: indexPath, animated: true) + } + case .testingCGMDataDeletion: + let confirmVC = UIAlertController(cgmDataDeletionHandler: dataManager.deleteTestingCGMData) + present(confirmVC, animated: true) { + tableView.deselectRow(at: indexPath, animated: true) + } } } override func tableView(_ tableView: UITableView, willDeselectRowAt indexPath: IndexPath) -> IndexPath? { - switch Section(rawValue: indexPath.section)! { + switch sections[indexPath.section] { case .loop: break case .pump: + let previousTestingPumpDataDeletionSection = sections.index(of: .testingPumpDataDeletion) + let wasTestingPumpManager = isTestingPumpManager + isTestingPumpManager = dataManager.pumpManager is TestingPumpManager + if !wasTestingPumpManager, isTestingPumpManager { + guard let testingPumpDataDeletionSection = sections.index(of: .testingPumpDataDeletion) else { + fatalError("Expected to find testing pump data deletion section with testing pump in use") + } + tableView.insertSections([testingPumpDataDeletionSection], with: .automatic) + } else if wasTestingPumpManager, !isTestingPumpManager { + guard let previousTestingPumpDataDeletionSection = previousTestingPumpDataDeletionSection else { + fatalError("Expected to have had testing pump data deletion section when testing pump was in use") + } + tableView.deleteSections([previousTestingPumpDataDeletionSection], with: .automatic) + } tableView.reloadSections([Section.pump.rawValue], with: .fade) tableView.reloadRows(at: [[Section.cgm.rawValue, CGMRow.cgmSettings.rawValue]], with: .fade) case .cgm: + let previousTestingCGMDataDeletionSection = sections.index(of: .testingCGMDataDeletion) + let wasTestingCGMManager = isTestingCGMManager + isTestingCGMManager = dataManager.cgmManager is TestingCGMManager + if !wasTestingCGMManager, isTestingCGMManager { + guard let testingCGMDataDeletionSection = sections.index(of: .testingCGMDataDeletion) else { + fatalError("Expected to find testing CGM data deletion section with testing CGM in use") + } + tableView.insertSections([testingCGMDataDeletionSection], with: .automatic) + } else if wasTestingCGMManager, !isTestingCGMManager { + guard let previousTestingCGMDataDeletionSection = previousTestingCGMDataDeletionSection else { + fatalError("Expected to have had testing CGM data deletion section when testing CGM was in use") + } + tableView.deleteSections([previousTestingCGMDataDeletionSection], with: .automatic) + } tableView.reloadRows(at: [indexPath], with: .fade) case .configuration: break case .services: break + case .testingPumpDataDeletion, .testingCGMDataDeletion: + break } return indexPath @@ -645,7 +720,7 @@ extension SettingsTableViewController: DailyValueScheduleTableViewControllerDele return } - switch Section(rawValue: indexPath.section)! { + switch sections[indexPath.section] { case .configuration: switch ConfigurationRow(rawValue: indexPath.row)! { case .glucoseTargetRange: @@ -685,7 +760,7 @@ extension SettingsTableViewController: InsulinModelSettingsViewControllerDelegat return } - switch Section(rawValue: indexPath.section)! { + switch sections[indexPath.section] { case .configuration: switch ConfigurationRow(rawValue: indexPath.row)! { case .insulinModel: @@ -707,7 +782,7 @@ extension SettingsTableViewController: InsulinModelSettingsViewControllerDelegat extension SettingsTableViewController: LoopKitUI.TextFieldTableViewControllerDelegate { func textFieldTableViewControllerDidEndEditing(_ controller: LoopKitUI.TextFieldTableViewController) { if let indexPath = controller.indexPath { - switch Section(rawValue: indexPath.section)! { + switch sections[indexPath.section] { case .configuration: switch ConfigurationRow(rawValue: indexPath.row)! { case .suspendThreshold: @@ -747,3 +822,37 @@ extension SettingsTableViewController: DeliveryLimitSettingsTableViewControllerD tableView.reloadRows(at: [[Section.configuration.rawValue, ConfigurationRow.deliveryLimits.rawValue]], with: .none) } } + +private extension UIAlertController { + convenience init(pumpDataDeletionHandler handler: @escaping () -> Void) { + self.init( + title: nil, + message: "Are you sure you want to delete testing pump health data?", + preferredStyle: .actionSheet + ) + + addAction(UIAlertAction( + title: "Delete Pump Data", + style: .destructive, + handler: { _ in handler() } + )) + + addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + } + + convenience init(cgmDataDeletionHandler handler: @escaping () -> Void) { + self.init( + title: nil, + message: "Are you sure you want to delete testing CGM health data?", + preferredStyle: .actionSheet + ) + + addAction(UIAlertAction( + title: "Delete CGM Data", + style: .destructive, + handler: { _ in handler() } + )) + + addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + } +} From 81b8a34f9b8595a94e8e0f0a033ab3cf390a8d14 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 19 Mar 2019 16:44:06 -0500 Subject: [PATCH 010/132] Link WatchApp extension with ClockKit for Series 0 watches (#899) --- Loop.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 2a032d66c6..1b56e07031 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -348,6 +348,7 @@ C18C8C511D5A351900E043FB /* NightscoutDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */; }; C1C6591C1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json in Resources */ = {isa = PBXBuildFile; fileRef = C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */; }; C1C73F0D1DE3D0270022FC89 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1C73F0F1DE3D0270022FC89 /* InfoPlist.strings */; }; + C1E2773E224177C000354103 /* ClockKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1E2773D224177C000354103 /* ClockKit.framework */; }; C1FB428C217806A400FAB378 /* StateColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428B217806A300FAB378 /* StateColorPalette.swift */; }; C1FB428D21791D2500FAB378 /* PumpManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C3B6F620BBCAA30026CAFA /* PumpManager.swift */; }; C1FB428F217921D600FAB378 /* PumpManagerUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428E217921D600FAB378 /* PumpManagerUI.swift */; }; @@ -985,6 +986,7 @@ C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutDataManager.swift; sourceTree = ""; }; C1C108C22152F46D00EA5165 /* CGMBLEKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CGMBLEKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_temp_basal_dropping_then_rising.json; sourceTree = ""; }; + C1E2773D224177C000354103 /* ClockKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ClockKit.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS5.1.sdk/System/Library/Frameworks/ClockKit.framework; sourceTree = DEVELOPER_DIR; }; C1FB428B217806A300FAB378 /* StateColorPalette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateColorPalette.swift; sourceTree = ""; }; C1FB428E217921D600FAB378 /* PumpManagerUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpManagerUI.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1027,6 +1029,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C1E2773E224177C000354103 /* ClockKit.framework in Frameworks */, 4344628220A7A37F00C4BE6F /* CoreBluetooth.framework in Frameworks */, 4344628020A7A37400C4BE6F /* HealthKit.framework in Frameworks */, 4344628520A7A3BE00C4BE6F /* LoopKit.framework in Frameworks */, @@ -1667,6 +1670,7 @@ 968DCD53F724DE56FFE51920 /* Frameworks */ = { isa = PBXGroup; children = ( + C1E2773D224177C000354103 /* ClockKit.framework */, C168C40521B0D53E00ADE90E /* MinimedKit.framework */, C168C40721B0D53E00ADE90E /* MinimedKitUI.framework */, C1C108C22152F46D00EA5165 /* CGMBLEKitUI.framework */, From 79b5451fdd270498a52d8ccebb520705b63b951a Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 19 Mar 2019 17:35:52 -0500 Subject: [PATCH 011/132] Fix target range charting unit mismatch (#903) * Fix target range charting unit mismatch This fixes a bug where users whose graphs are broken when their target range unit doesn't match the HealthKit preferred unit * Bump LoopKit rev --- Cartfile.resolved | 2 +- Common/Extensions/GlucoseRangeSchedule.swift | 20 +++---------- Loop/Managers/DoseMath.swift | 29 +++++++++---------- LoopUI/Extensions/ChartPoint.swift | 24 +++++++++------ LoopUI/Managers/StatusChartsManager.swift | 13 ++------- .../Models/GlucoseChartData.swift | 2 +- .../Scenes/GlucoseChartScene.swift | 2 +- .../Scenes/GlucoseChartValueHashable.swift | 4 +-- 8 files changed, 40 insertions(+), 56 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index ddbfd0d55e..66b850c60b 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,7 +1,7 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "ea1267791c66e884f1013fffd36faf4555cc6eaf" github "LoopKit/G4ShareSpy" "fed5a389e3e47e3a1953878dd21852aa5f44b360" -github "LoopKit/LoopKit" "c6e9f9200deec9a401ab1725e888451b17dee98a" +github "LoopKit/LoopKit" "643026a2adbb1011f195c8d9bb5a16cd9fb576e4" github "LoopKit/dexcom-share-client-swift" "b0419edf55c7f389b36cb47dd5c376bbd3d03d69" github "i-schuetz/SwiftCharts" "0.6.2" github "ps2/rileylink_ios" "8418b57c1983bdefaec7ccb456c86d5efacf40e7" diff --git a/Common/Extensions/GlucoseRangeSchedule.swift b/Common/Extensions/GlucoseRangeSchedule.swift index 4cd1712a52..51ed6b48b3 100644 --- a/Common/Extensions/GlucoseRangeSchedule.swift +++ b/Common/Extensions/GlucoseRangeSchedule.swift @@ -35,16 +35,6 @@ extension GlucoseRangeSchedule { return activeOverride?.context } - var activeOverrideQuantityRange: Range? { - guard let activeOverride = activeOverride else { - return nil - } - - let lowerBound = HKQuantity(unit: unit, doubleValue: activeOverride.value.minValue) - let upperBound = HKQuantity(unit: unit, doubleValue: activeOverride.value.maxValue) - return lowerBound.. HKQuantity { - return HKQuantity(unit: unit, doubleValue: value(at: date).minValue) - } } -extension DoubleRange { - var averageValue: Double { +extension Range where Bound == HKQuantity { + func averageValue(for unit: HKUnit) -> Double { + let minValue = lowerBound.doubleValue(for: unit) + let maxValue = upperBound.doubleValue(for: unit) return (maxValue + minValue) / 2 } } diff --git a/Loop/Managers/DoseMath.swift b/Loop/Managers/DoseMath.swift index 61ba33ecf6..aa561e5108 100644 --- a/Loop/Managers/DoseMath.swift +++ b/Loop/Managers/DoseMath.swift @@ -257,7 +257,7 @@ extension Collection where Element == GlucoseValue { let targetValue = targetGlucoseValue( percentEffectDuration: time / model.effectDuration, minValue: suspendThresholdValue, - maxValue: correctionRange.value(at: prediction.startDate).averageValue + maxValue: correctionRange.quantityRange(at: prediction.startDate).averageValue(for: unit) ) // Compute the dose required to bring this prediction to target: @@ -287,23 +287,20 @@ extension Collection where Element == GlucoseValue { } // Choose either the minimum glucose or eventual glocse as the correction delta - let minGlucoseTargets = correctionRange.value(at: min.startDate) - let eventualGlucoseTargets = correctionRange.value(at: eventual.startDate) - - let minGlucoseValue = min.quantity.doubleValue(for: unit) - let eventualGlucoseValue = eventual.quantity.doubleValue(for: unit) + let minGlucoseTargets = correctionRange.quantityRange(at: min.startDate) + let eventualGlucoseTargets = correctionRange.quantityRange(at: eventual.startDate) // Treat the mininum glucose when both are below range - if minGlucoseValue < minGlucoseTargets.minValue && - eventual.quantity.doubleValue(for: unit) < eventualGlucoseTargets.minValue + if min.quantity < minGlucoseTargets.lowerBound && + eventual.quantity < eventualGlucoseTargets.lowerBound { let time = min.startDate.timeIntervalSince(date) // For time = 0, assume a small amount effected. This will result in large (negative) unit recommendation rather than no recommendation at all. let percentEffected = Swift.max(.ulpOfOne, 1 - model.percentEffectRemaining(at: time)) guard let units = insulinCorrectionUnits( - fromValue: minGlucoseValue, - toValue: minGlucoseTargets.averageValue, + fromValue: min.quantity.doubleValue(for: unit), + toValue: minGlucoseTargets.averageValue(for: unit), effectedSensitivity: sensitivityValue * percentEffected ) else { return nil @@ -311,16 +308,16 @@ extension Collection where Element == GlucoseValue { return .entirelyBelowRange( correcting: min, - minTarget: HKQuantity(unit: unit, doubleValue: minGlucoseTargets.minValue), + minTarget: minGlucoseTargets.lowerBound, units: units ) - } else if eventualGlucoseValue > eventualGlucoseTargets.maxValue, + } else if eventual.quantity > eventualGlucoseTargets.upperBound, let minCorrectionUnits = minCorrectionUnits, let correctingGlucose = correctingGlucose { return .aboveRange( min: min, correcting: correctingGlucose, - minTarget: HKQuantity(unit: unit, doubleValue: eventualGlucoseTargets.minValue), + minTarget: eventualGlucoseTargets.lowerBound, units: minCorrectionUnits ) } else { @@ -361,7 +358,7 @@ extension Collection where Element == GlucoseValue { let correction = self.insulinCorrection( to: correctionRange, at: date, - suspendThreshold: suspendThreshold ?? correctionRange.minQuantity(at: date), + suspendThreshold: suspendThreshold ?? correctionRange.quantityRange(at: date).lowerBound, sensitivity: sensitivity.quantity(at: date), model: model ) @@ -416,7 +413,7 @@ extension Collection where Element == GlucoseValue { guard let correction = self.insulinCorrection( to: correctionRange, at: date, - suspendThreshold: suspendThreshold ?? correctionRange.minQuantity(at: date), + suspendThreshold: suspendThreshold ?? correctionRange.quantityRange(at: date).lowerBound, sensitivity: sensitivity.quantity(at: date), model: model ) else { @@ -432,7 +429,7 @@ extension Collection where Element == GlucoseValue { // Handle the "current BG below target" notice here // TODO: Don't assume in the future that the first item in the array is current BG if case .predictedGlucoseBelowTarget? = bolus.notice, - let first = first, first.quantity < correctionRange.minQuantity(at: first.startDate) + let first = first, first.quantity < correctionRange.quantityRange(at: first.startDate).lowerBound { bolus.notice = .currentGlucoseBelowTarget(glucose: first) } diff --git a/LoopUI/Extensions/ChartPoint.swift b/LoopUI/Extensions/ChartPoint.swift index 032f934726..c9fce8efc9 100644 --- a/LoopUI/Extensions/ChartPoint.swift +++ b/LoopUI/Extensions/ChartPoint.swift @@ -13,8 +13,8 @@ import SwiftCharts extension ChartPoint { - static func pointsForGlucoseRangeSchedule(_ glucoseRangeSchedule: GlucoseRangeSchedule, xAxisValues: [ChartAxisValue]) -> [ChartPoint] { - let targetRanges = glucoseRangeSchedule.between( + static func pointsForGlucoseRangeSchedule(_ glucoseRangeSchedule: GlucoseRangeSchedule, unit: HKUnit, xAxisValues: [ChartAxisValue]) -> [ChartPoint] { + let targetRanges = glucoseRangeSchedule.quantityBetween( start: ChartAxisValueDate.dateFromScalar(xAxisValues.first!.scalar), end: ChartAxisValueDate.dateFromScalar(xAxisValues.last!.scalar) ) @@ -38,7 +38,7 @@ extension ChartPoint { endDate = ChartAxisValueDate(date: targetRanges[index + 1].startDate, formatter: dateFormatter) } - let value = range.value.rangeWithMinimumIncremement(glucoseRangeSchedule.unit.chartableIncrement) + let value = range.value.doubleRangeWithMinimumIncrement(in: unit) let minValue = ChartAxisValueDouble(value.minValue) let maxValue = ChartAxisValueDouble(value.maxValue) @@ -56,8 +56,12 @@ extension ChartPoint { return maxPoints + minPoints.reversed() } - static func pointsForGlucoseRangeScheduleOverride(_ override: GlucoseRangeSchedule.Override, unit: HKUnit, xAxisValues: [ChartAxisValue], extendEndDateToChart: Bool = false) -> [ChartPoint] { - let range = override.value.rangeWithMinimumIncremement(unit.chartableIncrement) + static func pointsForGlucoseRangeScheduleOverride(_ glucoseRangeSchedule: GlucoseRangeSchedule, unit: HKUnit, xAxisValues: [ChartAxisValue], extendEndDateToChart: Bool = false) -> [ChartPoint] { + guard let override = glucoseRangeSchedule.override else { + return [] + } + + let range = override.quantityRange.doubleRangeWithMinimumIncrement(in: unit) let startDate = Date() let endDate = override.end @@ -96,10 +100,12 @@ extension ChartPoint: TimelineValue { } -private extension DoubleRange { - func rangeWithMinimumIncremement(_ increment: Double) -> DoubleRange { - var minValue = self.minValue - var maxValue = self.maxValue +private extension Range where Bound == HKQuantity { + func doubleRangeWithMinimumIncrement(in unit: HKUnit) -> DoubleRange { + let increment = unit.chartableIncrement + + var minValue = self.lowerBound.doubleValue(for: unit) + var maxValue = self.upperBound.doubleValue(for: unit) if (maxValue - minValue) < .ulpOfOne { minValue -= increment diff --git a/LoopUI/Managers/StatusChartsManager.swift b/LoopUI/Managers/StatusChartsManager.swift index 382115dfa6..5c3942e240 100644 --- a/LoopUI/Managers/StatusChartsManager.swift +++ b/LoopUI/Managers/StatusChartsManager.swift @@ -952,16 +952,9 @@ public final class StatusChartsManager { let xAxisValues = xAxisValues, xAxisValues.count > 1, let schedule = targetGlucoseSchedule { - targetGlucosePoints = ChartPoint.pointsForGlucoseRangeSchedule(schedule, xAxisValues: xAxisValues) - - if let override = schedule.override { - targetOverridePoints = ChartPoint.pointsForGlucoseRangeScheduleOverride(override, unit: schedule.unit, xAxisValues: xAxisValues, extendEndDateToChart: true) - - targetOverrideDurationPoints = ChartPoint.pointsForGlucoseRangeScheduleOverride(override, unit: schedule.unit, xAxisValues: xAxisValues) - } else { - targetOverridePoints = [] - targetOverrideDurationPoints = [] - } + targetGlucosePoints = ChartPoint.pointsForGlucoseRangeSchedule(schedule, unit: glucoseUnit, xAxisValues: xAxisValues) + targetOverridePoints = ChartPoint.pointsForGlucoseRangeScheduleOverride(schedule, unit: glucoseUnit, xAxisValues: xAxisValues, extendEndDateToChart: true) + targetOverrideDurationPoints = ChartPoint.pointsForGlucoseRangeScheduleOverride(schedule, unit: glucoseUnit, xAxisValues: xAxisValues) } } } diff --git a/WatchApp Extension/Models/GlucoseChartData.swift b/WatchApp Extension/Models/GlucoseChartData.swift index c45f24fa04..9b5e5485f8 100644 --- a/WatchApp Extension/Models/GlucoseChartData.swift +++ b/WatchApp Extension/Models/GlucoseChartData.swift @@ -53,7 +53,7 @@ struct GlucoseChartData { max = Swift.max(max, correction.value.upperBound.doubleValue(for: unit)) } - if let override = correctionRange?.activeOverrideQuantityRange { + if let override = correctionRange?.activeOverride?.quantityRange { min = Swift.min(min, override.lowerBound.doubleValue(for: unit)) max = Swift.max(max, override.upperBound.doubleValue(for: unit)) } diff --git a/WatchApp Extension/Scenes/GlucoseChartScene.swift b/WatchApp Extension/Scenes/GlucoseChartScene.swift index b233401623..363fc364dc 100644 --- a/WatchApp Extension/Scenes/GlucoseChartScene.swift +++ b/WatchApp Extension/Scenes/GlucoseChartScene.swift @@ -258,7 +258,7 @@ class GlucoseChartScene: SKScene { inactiveNodes.removeValue(forKey: range.chartHashValue) if range.end < spannedInterval.end { - let extendedRange = GlucoseRangeSchedule.Override(context: range.context, start: range.start, end: spannedInterval.end, value: range.value) + let extendedRange = GlucoseRangeSchedule.Override(context: range.context, start: range.start, end: spannedInterval.end, unit: range.unit, value: range.value) let (sprite2, created) = getSprite(forHash: extendedRange.chartHashValue) sprite2.color = UIColor.glucose.withAlphaComponent(0.25) sprite2.zPosition = NodePlane.overrideRanges.zPosition diff --git a/WatchApp Extension/Scenes/GlucoseChartValueHashable.swift b/WatchApp Extension/Scenes/GlucoseChartValueHashable.swift index 869a1a3d93..8b43c48987 100644 --- a/WatchApp Extension/Scenes/GlucoseChartValueHashable.swift +++ b/WatchApp Extension/Scenes/GlucoseChartValueHashable.swift @@ -78,10 +78,10 @@ extension AbsoluteScheduleValue: GlucoseChartValueHashable where T == Range Date: Wed, 20 Mar 2019 07:56:01 -0500 Subject: [PATCH 012/132] Update SwiftCharts to 0.6.3 (#879) --- Cartfile | 2 +- Cartfile.resolved | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cartfile b/Cartfile index caac804a02..ecf2b3d1b7 100644 --- a/Cartfile +++ b/Cartfile @@ -1,6 +1,6 @@ github "LoopKit/LoopKit" "dev" github "LoopKit/CGMBLEKit" "dev" -github "i-schuetz/SwiftCharts" == 0.6.2 +github "i-schuetz/SwiftCharts" == 0.6.3 github "LoopKit/dexcom-share-client-swift" "dev" github "LoopKit/G4ShareSpy" "dev" github "ps2/rileylink_ios" "dev" diff --git a/Cartfile.resolved b/Cartfile.resolved index 66b850c60b..0c217d1064 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -3,5 +3,5 @@ github "LoopKit/CGMBLEKit" "ea1267791c66e884f1013fffd36faf4555cc6eaf" github "LoopKit/G4ShareSpy" "fed5a389e3e47e3a1953878dd21852aa5f44b360" github "LoopKit/LoopKit" "643026a2adbb1011f195c8d9bb5a16cd9fb576e4" github "LoopKit/dexcom-share-client-swift" "b0419edf55c7f389b36cb47dd5c376bbd3d03d69" -github "i-schuetz/SwiftCharts" "0.6.2" +github "i-schuetz/SwiftCharts" "0.6.3" github "ps2/rileylink_ios" "8418b57c1983bdefaec7ccb456c86d5efacf40e7" From 83edb4b3de1e0173f56312147a7f6ddd7a426cc1 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Wed, 20 Mar 2019 10:24:14 -0500 Subject: [PATCH 013/132] Bump LoopKit revision --- Cartfile.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 0c217d1064..57bd892ac3 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,7 +1,7 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "ea1267791c66e884f1013fffd36faf4555cc6eaf" github "LoopKit/G4ShareSpy" "fed5a389e3e47e3a1953878dd21852aa5f44b360" -github "LoopKit/LoopKit" "643026a2adbb1011f195c8d9bb5a16cd9fb576e4" +github "LoopKit/LoopKit" "3c31297a8c3ee1ac5552f4edc6b4bf454267a549" github "LoopKit/dexcom-share-client-swift" "b0419edf55c7f389b36cb47dd5c376bbd3d03d69" github "i-schuetz/SwiftCharts" "0.6.3" github "ps2/rileylink_ios" "8418b57c1983bdefaec7ccb456c86d5efacf40e7" From 25f0457f7a97a9a43168ef169f739cab88b087a4 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Wed, 20 Mar 2019 11:49:02 -0500 Subject: [PATCH 014/132] Copy LoopTestingKit, MockKit, and MockKitUI frameworks to Loop app bundle --- Loop.xcodeproj/project.pbxproj | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index eb024d3a73..658e8e2252 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -2281,6 +2281,9 @@ "$(SRCROOT)/Carthage/Build/iOS/MinimedKitUI.framework", "$(SRCROOT)/Carthage/Build/iOS/CGMBLEKitUI.framework", "$(SRCROOT)/Carthage/Build/iOS/ShareClientUI.framework", + "$(SRCROOT)/Carthage/Build/iOS/LoopTestingKit.framework", + "$(SRCROOT)/Carthage/Build/iOS/MockKit.framework", + "$(SRCROOT)/Carthage/Build/iOS/MockKitUI.framework", ); name = "Copy Frameworks with Carthage"; outputPaths = ( @@ -2402,7 +2405,6 @@ 4F70C2101DE8FAC5006380B7 /* StatusExtensionDataManager.swift in Sources */, 43DFB62320D4CAE7008A7BAE /* PumpManager.swift in Sources */, 892A5D59222F0A27008961AB /* Debug.swift in Sources */, - 435CB6291F37B01300C320C7 /* InsulinModelSettings.swift in Sources */, 431A8C401EC6E8AB00823B9C /* CircleMaskView.swift in Sources */, 439897371CD2F80600223065 /* AnalyticsManager.swift in Sources */, 430D85891F44037000AF2D4F /* HUDViewTableViewCell.swift in Sources */, From afc3585b3f73f46d0e2d09e820e859fc07e5838c Mon Sep 17 00:00:00 2001 From: UlricusR Date: Wed, 20 Mar 2019 19:56:46 +0100 Subject: [PATCH 015/132] Corrected German strings - rebased to head of dev --- Loop/de.lproj/Localizable.strings | 58 +++++++++++++++---------------- Loop/de.lproj/Main.strings | 26 +++++++------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/Loop/de.lproj/Localizable.strings b/Loop/de.lproj/Localizable.strings index 73db5816ed..602ffad4c8 100644 --- a/Loop/de.lproj/Localizable.strings +++ b/Loop/de.lproj/Localizable.strings @@ -53,7 +53,7 @@ "A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults." = "Ein Modell auf der Grundlage der veröffentlichten Absorption von Humalog, Novolog und Apidra Insulin bei Erwachsenen."; /* Action to copy the recommended Bolus value to the actual Bolus Field */ -"AcceptRecommendedBolus" = "AkzeptiereEmpfohlenenBolus"; +"AcceptRecommendedBolus" = "Akzeptiere empfohlenen Bolus"; /* The title of the Carbs On-Board graph */ "Active Carbohydrates" = "Aktive Kohlenhydrate"; @@ -74,7 +74,7 @@ "Add Pump" = "Pumpe hinzufügen"; /* The title of the section containing algorithm settings */ -"Algorithm Settings" = "Algorithmus einstellungen"; +"Algorithm Settings" = "Algorithmus-Einstellungen"; /* The title of the Amplitude service */ "Amplitude" = "Amplitude"; @@ -109,17 +109,17 @@ "Cancel" = "Abbrechen"; /* Details for missing data error when carb effects are missing */ -"Carb effects" = "Carbwirkungen"; +"Carb effects" = "Kohlenhydrat-Wirkungen"; /* The title of the carb ratios schedule screen The title text for the carb ratio schedule */ -"Carb Ratios" = "Kohlenhydrate Verhältnisse"; +"Carb Ratios" = "Kohlenhydrat-Verhältnisse"; /* Title of the prediction input effect for carbohydrates */ "Carbohydrates" = "Kohlenhydrate"; /* Description of the prediction input effect for carbohydrates. (1: The glucose unit string) */ -"Carbs Absorbed (g) ÷ Carb Ratio (g/U) × Insulin Sensitivity (%1$@/U)" = "Kohlenhydrate aufgenommen (G) ÷ Carb Ratio (G/E) × Insulinempfindlichkeit (%1$@/E)"; +"Carbs Absorbed (g) ÷ Carb Ratio (g/U) × Insulin Sensitivity (%1$@/U)" = "Aufgenommene Kohlenhydrate (G) ÷ Kohlenhydrat-Verhältnis (G/E) × Insulinempfindlichkeit (%1$@/E)"; /* The notification alert describing a low pump battery */ "Change the pump battery immediately" = "Wechseln Sie sofort die Pumpenbatterie"; @@ -137,7 +137,7 @@ "Check your CGM data source" = "Überprüfen Sie Ihre CGM-Datenquelle"; /* The title text for the looping enabled switch cell */ -"Closed Loop" = "Geschlossene Loop"; +"Closed Loop" = "Geschlossener Loop"; /* The title of the configuration section in settings */ "Configuration" = "Konfiguration"; @@ -171,19 +171,19 @@ "Disables" = "Deaktiviert"; /* Title of the switch which toggles retrospective correction effects */ -"Enable Retrospective Correction" = "Aktivieren retrospektive Korrektur"; +"Enable Retrospective Correction" = "Retrospektive Korrektur aktivieren"; /* The action hint of the workout mode toggle button when disabled */ -"Enables" = "Ermöglicht"; +"Enables" = "Aktivieren"; /* The placeholder text instructing users to enter a suspend treshold */ -"Enter suspend threshold" = "Schwellwert für Aussetzung eingeben"; +"Enter suspend threshold" = "Schwellwert für Suspendierung eingeben"; /* The placeholder text instructing users how to enter a transmitter ID */ "Enter the 6-digit transmitter ID" = "Geben Sie die 6-stellige Sender-ID ein"; /* The subtitle format describing eventual glucose. (1: localized glucose value description) */ -"Eventually %@" = "Schließlich %@"; +"Eventually %@" = "Voraussichtlich %@"; /* The title of the alert describing a maximum bolus validation error */ "Exceeds Maximum Bolus" = "Überschreitet den maximalen Bolus"; @@ -204,16 +204,16 @@ "G5/G6 Transmitter" = "G5/G6 Sender"; /* The title of the glucose and prediction graph */ -"Glucose" = "Glukose"; +"Glucose" = "Blutzucker"; /* The error message when glucose data is too old to be used. (1: glucose data age in minutes) */ -"Glucose data is %1$@ old" = "Glukosedaten sind %1$@ alt"; +"Glucose data is %1$@ old" = "Blutzuckerdaten sind %1$@ alt"; /* Description of error when glucose data is missing */ -"Glucose data not available" = "Glukosedaten sind nicht verfügbar"; +"Glucose data not available" = "Blutzuckerdaten sind nicht verfügbar"; /* Title of the prediction input effect for glucose momentum */ -"Glucose Momentum" = "Glukose-Momentum"; +"Glucose Momentum" = "Blutzucker-Momentum"; /* The placeholder text for the nightscout site URL credential */ "https://mysite.azurewebsites.net" = "https://mysite.azurewebsites.net"; @@ -257,10 +257,10 @@ "Loggly" = "Loggly"; /* The notification title for a loop failure */ -"Loop Failure" = "Loop Fehler"; +"Loop Failure" = "Loop-Fehler"; /* The notification alert describing a long-lasting loop failure. The substitution parameter is the time interval since the last loop */ -"Loop has not completed successfully in %@" = "Loop wurde nicht erfolgreich abgeschlossen um %@"; +"Loop has not completed successfully in %@" = "Loop wurde nicht erfolgreich abgeschlossen seit %@"; /* The short unit display string for milligrams of glucose per decilter */ "mg/dL" = "mg/dL"; @@ -269,13 +269,13 @@ "Missing data: %1$@" = "Fehlende Daten: %1$@"; /* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; +"mmol/L" = "mmol/l"; /* Details for missing data error when momentum effects are missing */ "Momentum effects" = "Momentumwirkungen"; /* Sensor state description for the non-valid state */ -"Needs Attention" = "Erfordert Aufmerksamkeit"; +"Needs Attention" = "Erfordert Prüfung"; /* The title of the Nightscout service */ "Nightscout" = "Nightscout"; @@ -287,10 +287,10 @@ "Pre-Meal Targets" = "Ziele vor dem Essen"; /* Message when offering bolus recommendation even though bg is below range and minBG is in future. (1: glucose time)(2: glucose number) */ -"Predicted glucose at %1$@ is %2$@." = "Vorhergesagte Glukose um %1$@ ist %2$@."; +"Predicted glucose at %1$@ is %2$@." = "Vorhergesagter Blutzucker um %1$@ ist %2$@."; /* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ -"Predicted glucose of %1$@ is below your suspend threshold setting." = "Die vorhergesagte Glukose von %1$@ liegt unter der eingestellten Schwelle."; +"Predicted glucose of %1$@ is below your suspend threshold setting." = "Der vorhergesagte Blutzucker von %1$@ liegt unter der eingestellten Schwelle."; /* The title of the pump section in settings */ "Pump" = "Pumpe"; @@ -311,16 +311,16 @@ "Pump Reservoir Low" = "Pumpenreservoir niedrig"; /* Title of insulin model preset */ -"Rapid-Acting – Adults" = "Schnell handelnd - Erwachsene"; +"Rapid-Acting – Adults" = "Schnell wirkend - Erwachsene"; /* Title of insulin model preset */ -"Rapid-Acting – Children" = "Schnell handelnd - Kinder"; +"Rapid-Acting – Children" = "Schnell wirkend - Kinder"; /* The error message when a recommendation has expired. (1: age of recommendation in minutes) */ "Recommendation expired: %1$@ old" = "Empfehlung abgelaufen: %1$@ alt"; /* The title of the cell displaying a recommended temp basal value */ -"Recommended Basal" = "Empfohlen Basal"; +"Recommended Basal" = "Empfohlene Basalrate"; /* Accessibility hint describing recommended bolus units */ "Recommended Bolus: %@ Units" = "Empfohlener Bolus: %@ Einheiten"; @@ -353,7 +353,7 @@ "Sof-Sensor / Enlite" = "Sof-Sensor / Enlite"; /* The title of the cell indicating a bolus is being sent */ -"Starting Bolus" = "Start Bolus"; +"Starting Bolus" = "Starte Bolus"; /* The title text in settings */ "Suspend Threshold" = "Suspend-Schwellenwert"; @@ -365,23 +365,23 @@ "The maximum bolus amount is %@ Units" = "Die maximale Bolusmenge beträgt %@ Einheiten"; /* Instructions on where to find the transmitter ID */ -"The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app." = "Die Sender-ID finden Sie auf der Rückseite des Geräts, auf der Seite der Box, in der es geliefert wurde, und in den Einstellungsmenüs des G5/G6-Empfängers und der mobilen App."; +"The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app." = "Die Sender-ID finden Sie auf der Rückseite des Geräts, auf der Seite der Verpackung, in der es geliefert wurde, und in den Einstellungsmenüs des G5/G6-Empfängers und der mobilen App."; /* The description of the switch which toggles retrospective correction effects */ -"This will more aggresively increase or decrease basal delivery when glucose movement doesn't match the carbohydrate and insulin-based model." = "Dies wird die basale Abgabe aggressiver erhöhen oder verringern, wenn die Glukosebewegung nicht mit dem Kohlenhydrat- und Insulin-basierten Modell übereinstimmt."; +"This will more aggresively increase or decrease basal delivery when glucose movement doesn't match the carbohydrate and insulin-based model." = "Dies wird die basale Abgabe stärker erhöhen oder verringern, wenn der Blutzuckerverlauf nicht mit dem Kohlenhydrat- und Insulin-basierten Modell übereinstimmt."; /* The short unit display string for international units of insulin */ "U" = "E"; /* The title of the alert controller used to select a duration for workout targets */ -"Use Workout Glucose Targets" = "Verwenden Training Glucose Targets"; +"Use Workout Glucose Targets" = "Verwende Trainings-Blutzuckerziele"; /* Title of insulin model setting */ "Walsh" = "Walsh"; /* Explanation of suspend threshold */ -"When current or forecasted glucose is below the suspend threshold, Loop will not recommend a bolus, and will always recommend a temporary basal rate of 0 units per hour." = "Wenn die aktuelle oder prognostizierte Glukose unter dem Schwellenwert für die Aufhebung liegt, empfiehlt Loop keinen Bolus und empfiehlt immer eine temporäre Basalrate von 0 Einheiten pro Stunde."; +"When current or forecasted glucose is below the suspend threshold, Loop will not recommend a bolus, and will always recommend a temporary basal rate of 0 units per hour." = "Wenn der aktuelle oder prognostizierte Blutzucker unter dem Schwellenwert für die Suspendierung liegt, empfiehlt Loop keinen Bolus und immer eine temporäre Basalrate von 0 Einheiten pro Stunde."; /* The label of the workout mode toggle button */ -"Workout Targets" = "Training Glucose Targets"; +"Workout Targets" = "Trainings-Blutzuckerziele"; diff --git a/Loop/de.lproj/Main.strings b/Loop/de.lproj/Main.strings index d26b9714d1..53b652232a 100644 --- a/Loop/de.lproj/Main.strings +++ b/Loop/de.lproj/Main.strings @@ -44,10 +44,10 @@ "cpo-Po-gFM.text" = "Beschriftung"; /* Class = "UILabel"; text = "g Total"; ObjectID = "d3X-AN-tA5"; */ -"d3X-AN-tA5.text" = "g Insgesamt"; +"d3X-AN-tA5.text" = "g insgesamt"; /* Class = "UILabel"; text = "Future glucose is predicted by combining the effects of multiple inputs. Use this tool to toggle various inputs to see how they compare to the final prediction."; ObjectID = "D4C-I2-dhA"; */ -"D4C-I2-dhA.text" = "Zukünftige Glukose wird vorhergesagt, indem die Effekte mehrerer Eingaben kombiniert werden. Verwenden Sie dieses Tool, um verschiedene Eingaben umzuschalten, um zu sehen, wie sie mit der endgültigen Vorhersage verglichen werden."; +"D4C-I2-dhA.text" = "Der Blutzuckerverlauf wird vorhergesagt, indem die Effekte mehrerer Eingaben kombiniert werden. Verwenden Sie dieses Tool, um verschiedene Eingaben ein- und auszuschalten, um zu sehen, wie sie die Vorhersage beeinflussen."; /* Class = "UILabel"; text = "Label"; ObjectID = "d6m-qV-wWi"; */ "d6m-qV-wWi.text" = "Beschriftung"; @@ -59,10 +59,10 @@ "DyC-Sv-qP8.text" = "Geräte"; /* Class = "UILabel"; text = "eventually 92 mg/dL"; ObjectID = "E41-FN-nkk"; */ -"E41-FN-nkk.text" = "schließlich 92 mg/dL"; +"E41-FN-nkk.text" = "voraussichtlich 92 mg/dL"; /* Class = "UILabel"; text = "Observed"; ObjectID = "EAn-Ja-S1d"; */ -"EAn-Ja-S1d.text" = "Beobachtete"; +"EAn-Ja-S1d.text" = "Beobachtet"; /* Class = "UILabel"; text = "Active Carbohydrates: 40g"; ObjectID = "hHZ-uY-aKw"; */ "hHZ-uY-aKw.text" = "Aktive Kohlenhydrate: 40g"; @@ -71,16 +71,16 @@ "hZZ-2S-lrd.title" = "Kohlenhydrat-Effekte"; /* Class = "UILabel"; text = "Observed changes in glucose, subtracting changes modeled from insulin delivery, can be used to estimate carbohydrate absorption."; ObjectID = "IxU-As-glo"; */ -"IxU-As-glo.text" = "Beobachtete Änderungen in Glukose, subtrahierende Veränderungen, die aus der Insulinabgabe modelliert wurden, können verwendet werden, um die Kohlenhydratabsorption zu schätzen."; +"IxU-As-glo.text" = "Die beobachtete Blutzuckerveränderung abzüglich der modellbasierten Veränderung kann verwendet werden, um die Kohlenhydratabsorption abzuschätzen."; /* Class = "UILabel"; text = "Detail"; ObjectID = "J7x-W5-gwo"; */ "J7x-W5-gwo.text" = "Detail"; /* Class = "UILabel"; text = "⚠ Glucose Predicted Below Range"; ObjectID = "k0g-P7-OVN"; */ -"k0g-P7-OVN.text" = "⚠ Glukose Vorhergesagt Bereich"; +"k0g-P7-OVN.text" = "⚠ Blutzucker unterhalb Zielbereich vorausgesagt"; /* Class = "UILabel"; text = "Recommended Basal"; ObjectID = "k3F-Na-7mn"; */ -"k3F-Na-7mn.text" = "Empfohlen Basal"; +"k3F-Na-7mn.text" = "Empfohlene Basalrate"; /* Class = "UILabel"; text = "Label"; ObjectID = "Krd-Aa-ret"; */ "Krd-Aa-ret.text" = "Beschriftung"; @@ -89,7 +89,7 @@ "l7l-mw-Oc9.text" = "Beschriftung"; /* Class = "UILabel"; text = "Tap to set"; ObjectID = "m9c-SQ-djE"; */ -"m9c-SQ-djE.text" = "Tippen, um festzulegen"; +"m9c-SQ-djE.text" = "Tippen zum Setzen"; /* Class = "UILabel"; accessibilityLabel = "Units"; ObjectID = "mVz-dr-xLU"; */ "mVz-dr-xLU.accessibilityLabel" = "Einheiten"; @@ -101,22 +101,22 @@ "OFA-qT-ZAg.text" = "Beschriftung"; /* Class = "UITableViewController"; title = "Predicted Glucose"; ObjectID = "PA3-sP-cWY"; */ -"PA3-sP-cWY.title" = "Die Vorhergesagte Glukose"; +"PA3-sP-cWY.title" = "Vorhergesagter Blutzucker"; /* Class = "UITableViewController"; title = "Insulin Model"; ObjectID = "pi6-Dh-72V"; */ "pi6-Dh-72V.title" = "Insulin-Modell"; /* Class = "UILabel"; text = "An insulin activity model is used to estimate effects of insulin on glucose levels. An accurate model can help prevent insulin stacking and safely recommend corrective treatments."; ObjectID = "PJv-p9-cFe"; */ -"PJv-p9-cFe.text" = "Ein Insulinaktivitätsmodell wird verwendet, um die Wirkungen von Insulin auf den Glucosespiegel abzuschätzen. Ein genaues Modell kann dazu beitragen, Insulinstapelung zu verhindern und korrigierende Behandlungen sicher zu empfehlen."; +"PJv-p9-cFe.text" = "Ein Insulinaktivitätsmodell wird verwendet, um die Wirkung von Insulin auf den Blutzuckerspiegel abzuschätzen. Ein genaues Modell kann dazu beitragen, Insulinstapelung zu verhindern und Korrekturen sicher zu empfehlen."; /* Class = "UILabel"; text = "eventually 92 mg/dL"; ObjectID = "Rse-x8-amW"; */ -"Rse-x8-amW.text" = "schließlich 92 mg/dL"; +"Rse-x8-amW.text" = "voraussichtlich 92 mg/dL"; /* Class = "UILabel"; text = "g COB"; ObjectID = "SQx-au-ZcM"; */ "SQx-au-ZcM.text" = "g KaB"; /* Class = "UILabel"; text = "Glucose"; ObjectID = "tuw-av-A3x"; */ -"tuw-av-A3x.text" = "Glukose"; +"tuw-av-A3x.text" = "Blutzucker"; /* Class = "UILabel"; text = "Label"; ObjectID = "ufi-Kj-33k"; */ "ufi-Kj-33k.text" = "Beschriftung"; @@ -131,7 +131,7 @@ "Wk3-xv-IM5.text" = "4 Stunden"; /* Class = "UIButton"; normalTitle = "Deliver"; ObjectID = "Ya0-9b-ZAS"; */ -"Ya0-9b-ZAS.normalTitle" = "Verwalten"; +"Ya0-9b-ZAS.normalTitle" = "Abgeben"; /* Class = "UILabel"; text = "0"; ObjectID = "yn7-2M-jZz"; */ "yn7-2M-jZz.text" = "0"; From 0d397c48acc64ced6915f6815831f05130d8e158 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Wed, 27 Mar 2019 07:09:38 -0500 Subject: [PATCH 016/132] Bolus progress and quick cancellation (#6) * Bolus progress and quick cancellation * Add MKRingProgressView files directly * Update LICENCE file to include license for MKRingProgressView * Use MKRingProgressView via carthage * Use pumpmanager's bolusProgressEstimator * Updates from code review: - Delay removal of bolus progress so user sees progress finish - Reset ring progress during cell re-use - Tweak cornerRadius - Code clarity updates * PumpManager Protocol updates * Protocol naming update * Create bolus progress reporter * Fix progress animating from 1 to 0 on reuse. --- Cartfile | 1 + Cartfile.resolved | 1 + LICENSE.md | 5 + Loop.xcodeproj/project.pbxproj | 16 ++ Loop/Managers/DeviceDataManager.swift | 9 +- .../StatusTableViewController.swift | 215 +++++++++++++----- Loop/Views/BolusProgressTableViewCell.swift | 112 +++++++++ Loop/Views/BolusProgressTableViewCell.xib | 92 ++++++++ 8 files changed, 386 insertions(+), 65 deletions(-) create mode 100644 Loop/Views/BolusProgressTableViewCell.swift create mode 100644 Loop/Views/BolusProgressTableViewCell.xib diff --git a/Cartfile b/Cartfile index caac804a02..ffbf99e939 100644 --- a/Cartfile +++ b/Cartfile @@ -5,3 +5,4 @@ github "LoopKit/dexcom-share-client-swift" "dev" github "LoopKit/G4ShareSpy" "dev" github "ps2/rileylink_ios" "dev" github "LoopKit/Amplitude-iOS" "decreepify" +github "maxkonovalov/MKRingProgressView" ~> 2.2 diff --git a/Cartfile.resolved b/Cartfile.resolved index 1ded1a366c..dc3c5e247e 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -4,4 +4,5 @@ github "LoopKit/G4ShareSpy" "fed5a389e3e47e3a1953878dd21852aa5f44b360" github "LoopKit/LoopKit" "bf4166bc77f89e22971f2030ad006967881ff082" github "LoopKit/dexcom-share-client-swift" "b0419edf55c7f389b36cb47dd5c376bbd3d03d69" github "i-schuetz/SwiftCharts" "0.6.2" +github "maxkonovalov/MKRingProgressView" "2.2.1" github "ps2/rileylink_ios" "46ed2c845e7c4b88c721994d8e8432e86be4c699" diff --git a/LICENSE.md b/LICENSE.md index 333c3ab974..9a737abb2c 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -58,6 +58,11 @@ Copyright (c) 2016 Mark Wilson Copyright (c) 2015 Nathan Racklyeft Copyright (c) 2016 LoopKit Authors +## MKRingProgressView.framework + +Copyright (c) 2015 Max Konovalov + + > Permission is hereby granted, free of charge, to any person obtaining a copy > of this software and associated documentation files (the "Software"), to deal > in the Software without restriction, including without limitation the rights diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 90cb38a90c..3c970c91f3 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -297,6 +297,7 @@ C10B28461EA9BA5E006EA1FC /* far_future_high_bg_forecast.json in Resources */ = {isa = PBXBuildFile; fileRef = C10B28451EA9BA5E006EA1FC /* far_future_high_bg_forecast.json */; }; C11C87DE1E21EAAD00BB71D3 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F526D5E1DF2459000A04910 /* HKUnit.swift */; }; C12F21A71DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json in Resources */ = {isa = PBXBuildFile; fileRef = C12F21A61DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json */; }; + C13255D6223E7BE2008AF50C /* BolusProgressTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C1F8B1DB223862D500DD66CF /* BolusProgressTableViewCell.xib */; }; C13BAD941E8009B000050CB5 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0B31E45C1BE00FF19A9 /* NumberFormatter.swift */; }; C15713821DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */; }; C168C40621B0D53E00ADE90E /* MinimedKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C168C40521B0D53E00ADE90E /* MinimedKit.framework */; }; @@ -310,6 +311,8 @@ C18C8C511D5A351900E043FB /* NightscoutDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */; }; C1C6591C1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json in Resources */ = {isa = PBXBuildFile; fileRef = C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */; }; C1C73F0D1DE3D0270022FC89 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1C73F0F1DE3D0270022FC89 /* InfoPlist.strings */; }; + C1E2774822433D7A00354103 /* MKRingProgressView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1E2774722433D7A00354103 /* MKRingProgressView.framework */; }; + C1F8B243223E73FD00DD66CF /* BolusProgressTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F8B1D122375E4200DD66CF /* BolusProgressTableViewCell.swift */; }; C1FB428C217806A400FAB378 /* StateColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428B217806A300FAB378 /* StateColorPalette.swift */; }; C1FB428D21791D2500FAB378 /* PumpManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C3B6F620BBCAA30026CAFA /* PumpManager.swift */; }; C1FB428F217921D600FAB378 /* PumpManagerUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428E217921D600FAB378 /* PumpManagerUI.swift */; }; @@ -851,6 +854,9 @@ C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutDataManager.swift; sourceTree = ""; }; C1C108C22152F46D00EA5165 /* CGMBLEKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CGMBLEKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_temp_basal_dropping_then_rising.json; sourceTree = ""; }; + C1E2774722433D7A00354103 /* MKRingProgressView.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MKRingProgressView.framework; path = Carthage/Build/iOS/MKRingProgressView.framework; sourceTree = ""; }; + C1F8B1D122375E4200DD66CF /* BolusProgressTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusProgressTableViewCell.swift; sourceTree = ""; }; + C1F8B1DB223862D500DD66CF /* BolusProgressTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BolusProgressTableViewCell.xib; sourceTree = ""; }; C1FB428B217806A300FAB378 /* StateColorPalette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateColorPalette.swift; sourceTree = ""; }; C1FB428E217921D600FAB378 /* PumpManagerUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpManagerUI.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -867,6 +873,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C1E2774822433D7A00354103 /* MKRingProgressView.framework in Frameworks */, 892A5D5B222F0D7C008961AB /* LoopTestingKit.framework in Frameworks */, 892A5D2A222EF60A008961AB /* MockKit.framework in Frameworks */, 892A5D2C222EF60A008961AB /* MockKitUI.framework in Frameworks */, @@ -1201,6 +1208,8 @@ 4374B5F3209D89A900D17AA8 /* TextFieldTableViewCell.swift */, 43F64DD81D9C92C900D24DC6 /* TitleSubtitleTableViewCell.swift */, 4311FB9A1F37FE1B00D4C0A7 /* TitleSubtitleTextFieldTableViewCell.swift */, + C1F8B1D122375E4200DD66CF /* BolusProgressTableViewCell.swift */, + C1F8B1DB223862D500DD66CF /* BolusProgressTableViewCell.xib */, ); path = Views; sourceTree = ""; @@ -1409,6 +1418,7 @@ 968DCD53F724DE56FFE51920 /* Frameworks */ = { isa = PBXGroup; children = ( + C1E2774722433D7A00354103 /* MKRingProgressView.framework */, 892A5D5A222F0D7C008961AB /* LoopTestingKit.framework */, 892A5D29222EF60A008961AB /* MockKit.framework */, 892A5D2B222EF60A008961AB /* MockKitUI.framework */, @@ -1721,6 +1731,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + C13255D6223E7BE2008AF50C /* BolusProgressTableViewCell.xib in Resources */, 43FCBBC21E51710B00343C1B /* LaunchScreen.storyboard in Resources */, 7D70764F1FE06EE1004AC8EA /* InfoPlist.strings in Resources */, 43776F991B8022E90074EA36 /* Assets.xcassets in Resources */, @@ -1857,6 +1868,10 @@ "$(SRCROOT)/Carthage/Build/iOS/MinimedKitUI.framework", "$(SRCROOT)/Carthage/Build/iOS/CGMBLEKitUI.framework", "$(SRCROOT)/Carthage/Build/iOS/ShareClientUI.framework", + "$(SRCROOT)/Carthage/Build/iOS/MockKit.framework", + "$(SRCROOT)/Carthage/Build/iOS/MockKitUI.framework", + "$(SRCROOT)/Carthage/Build/iOS/LoopTestingKit.framework", + "$(SRCROOT)/Carthage/Build/iOS/MKRingProgressView.framework", ); name = "Copy Frameworks with Carthage"; outputPaths = ( @@ -1910,6 +1925,7 @@ 430B29932041F5B300BA9F93 /* UserDefaults+Loop.swift in Sources */, 4341F4EB1EDB92AC001C936B /* LogglyService.swift in Sources */, 43CE7CDE1CA8B63E003CC1B0 /* Data.swift in Sources */, + C1F8B243223E73FD00DD66CF /* BolusProgressTableViewCell.swift in Sources */, 439A7942211F631C0041B75F /* RootNavigationController.swift in Sources */, 4F11D3C020DCBEEC006E072C /* GlucoseBackfillRequestUserInfo.swift in Sources */, 43F5C2DB1B92A5E1003EB13D /* SettingsTableViewController.swift in Sources */, diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index 6c5daf545a..194822181b 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -168,7 +168,7 @@ final class DeviceDataManager { } } - +// MARK: - RemoteDataManagerDelegate extension DeviceDataManager: RemoteDataManagerDelegate { func remoteDataManagerDidUpdateServices(_ dataManager: RemoteDataManager) { loopManager.carbStore.syncDelegate = dataManager.nightscoutService.uploader @@ -176,6 +176,7 @@ extension DeviceDataManager: RemoteDataManagerDelegate { } +// MARK: - CGMManagerDelegate extension DeviceDataManager: CGMManagerDelegate { func cgmManagerWantsDeletion(_ manager: CGMManager) { self.cgmManager = nil @@ -223,6 +224,7 @@ extension DeviceDataManager: CGMManagerDelegate { } +// MARK: - PumpManagerDelegate extension DeviceDataManager: PumpManagerDelegate { func pumpManager(_ pumpManager: PumpManager, didAdjustPumpClockBy adjustment: TimeInterval) { @@ -257,6 +259,7 @@ extension DeviceDataManager: PumpManagerDelegate { } func pumpManager(_ pumpManager: PumpManager, didUpdate status: PumpManagerStatus) { + log.default("PumpManager:\(type(of: pumpManager)) did update status: \(status)") self.pumpManagerStatus = status } @@ -344,7 +347,7 @@ extension DeviceDataManager: PumpManagerDelegate { } - +// MARK: - DoseStoreDelegate extension DeviceDataManager: DoseStoreDelegate { func doseStore(_ doseStore: DoseStore, hasEventsNeedingUpload pumpEvents: [PersistedPumpEvent], @@ -430,6 +433,7 @@ extension DeviceDataManager { } } +// MARK: - LoopDataManagerDelegate extension DeviceDataManager: LoopDataManagerDelegate { func loopDataManager(_ manager: LoopDataManager, roundBasalRate unitsPerHour: Double) -> Double { guard let pumpManager = pumpManager else { @@ -477,6 +481,7 @@ extension DeviceDataManager: LoopDataManagerDelegate { } +// MARK: - CustomDebugStringConvertible extension DeviceDataManager: CustomDebugStringConvertible { var debugDescription: String { return [ diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index ed29d842c5..bb82437b69 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -16,24 +16,16 @@ import SwiftCharts import os.log -/// Describes the state within the bolus setting flow -/// -/// - recommended: A bolus recommendation was discovered and the bolus view controller is presenting/presented -/// - enacting: A bolus was requested by the user and is pending with the device manager -private enum BolusState { - case recommended - case enacting -} - private extension RefreshContext { static let all: Set = [.status, .glucose, .insulin, .carbs, .targets] } - final class StatusTableViewController: ChartsTableViewController { private let log = OSLog(category: "StatusTableViewController") + lazy var quantityFormatter: QuantityFormatter = QuantityFormatter() + override func viewDidLoad() { super.viewDidLoad() @@ -101,6 +93,8 @@ final class StatusTableViewController: ChartsTableViewController { toolbarItems![4].tintColor = UIColor.doseTintColor toolbarItems![8].accessibilityLabel = NSLocalizedString("Settings", comment: "The label of the settings button") toolbarItems![8].tintColor = UIColor.secondaryLabelColor + + tableView.register(BolusProgressTableViewCell.nib(), forCellReuseIdentifier: BolusProgressTableViewCell.className) } override func didReceiveMemoryWarning() { @@ -117,6 +111,8 @@ final class StatusTableViewController: ChartsTableViewController { super.viewWillAppear(animated) navigationController?.setNavigationBarHidden(true, animated: animated) + + updateBolusProgress() } override func viewDidAppear(_ animated: Bool) { @@ -173,16 +169,24 @@ final class StatusTableViewController: ChartsTableViewController { } } - private var bolusState: BolusState? { + private var bolusState = PumpManagerStatus.BolusState.none { didSet { - switch bolusState { - case .enacting?: - updateHUDandStatusRows(statusRowMode: .enactingBolus, newSize: nil, animated: true) - default: - updateHUDandStatusRows(statusRowMode: .hidden, newSize: nil, animated: true) + if oldValue != bolusState { + // Bolus starting + if case .inProgress = bolusState { + self.bolusProgressReporter = self.deviceManager.pumpManager?.createBolusProgressReporter(reportingOn: DispatchQueue.main) + } + refreshContext.update(with: .status) + self.reloadData(animated: true) } + } + } - refreshContext.update(with: .status) + private var bolusProgressReporter: DoseProgressReporter? + + private func updateBolusProgress() { + if let cell = tableView.cellForRow(at: IndexPath(row: StatusRow.status.rawValue, section: Section.status.rawValue)) as? BolusProgressTableViewCell { + cell.deliveredUnits = bolusProgressReporter?.progress.deliveredUnits } } @@ -192,7 +196,9 @@ final class StatusTableViewController: ChartsTableViewController { public var basalDeliveryState: PumpManagerStatus.BasalDeliveryState = .active { didSet { - refreshContext.update(with: .status) + if oldValue != basalDeliveryState { + refreshContext.update(with: .status) + } } } @@ -248,6 +254,12 @@ final class StatusTableViewController: ChartsTableViewController { updateChartDateRange() redrawCharts() + if visible && active { + bolusProgressReporter?.addObserver(self) + } else { + bolusProgressReporter?.removeObserver(self) + } + guard active && visible && !refreshContext.isEmpty else { return } @@ -267,7 +279,6 @@ final class StatusTableViewController: ChartsTableViewController { var doseEntries: [DoseEntry]? var totalDelivery: Double? var cobValues: [CarbValue]? - let bolusState = self.bolusState let startDate = charts.startDate // TODO: Don't always assume currentContext.contains(.status) @@ -437,26 +448,10 @@ final class StatusTableViewController: ChartsTableViewController { } // Show/hide the table view rows - let statusRowMode: StatusRowMode? - - if self.basalDeliveryState == .suspended { - statusRowMode = .pumpSuspended(resuming: false) - } else if self.basalDeliveryState == .resuming { - statusRowMode = .pumpSuspended(resuming: true) - } else { - switch bolusState { - case .recommended?, .enacting?: - statusRowMode = nil - case .none: - if let (recommendation: tempBasal, date: date) = newRecommendedTempBasal { - statusRowMode = .recommendedTempBasal(tempBasal: tempBasal, at: date, enacting: false) - } else { - statusRowMode = .hidden - } - } - } + let statusRowMode = self.determineStatusRowMode(recommendedTempBasal: newRecommendedTempBasal) self.updateHUDandStatusRows(statusRowMode: statusRowMode, newSize: currentContext.newSize, animated: animated) + self.redrawCharts() self.tableView.endUpdates() @@ -520,6 +515,8 @@ final class StatusTableViewController: ChartsTableViewController { case hidden case recommendedTempBasal(tempBasal: TempBasalRecommendation, at: Date, enacting: Bool) case enactingBolus + case bolusing(dose: DoseEntry) + case cancelingBolus case pumpSuspended(resuming: Bool) var hasRow: Bool { @@ -534,14 +531,37 @@ final class StatusTableViewController: ChartsTableViewController { private var statusRowMode = StatusRowMode.hidden - private func updateHUDandStatusRows(statusRowMode: StatusRowMode?, newSize: CGSize?, animated: Bool) { + private func determineStatusRowMode(recommendedTempBasal: (recommendation: TempBasalRecommendation, date: Date)? = nil) -> StatusRowMode { + let statusRowMode: StatusRowMode + + if case .initiating = bolusState { + statusRowMode = .enactingBolus + } else if case .canceling = bolusState { + statusRowMode = .cancelingBolus + } else if self.basalDeliveryState == .suspended { + statusRowMode = .pumpSuspended(resuming: false) + } else if self.basalDeliveryState == .resuming { + statusRowMode = .pumpSuspended(resuming: true) + } else { + if case .inProgress(let dose) = bolusState, dose.endDate.timeIntervalSinceNow > 0 { + statusRowMode = .bolusing(dose: dose) + } else if let (recommendation: tempBasal, date: date) = recommendedTempBasal { + statusRowMode = .recommendedTempBasal(tempBasal: tempBasal, at: date, enacting: false) + } else { + statusRowMode = .hidden + } + } + + return statusRowMode + } + + private func updateHUDandStatusRows(statusRowMode: StatusRowMode, newSize: CGSize?, animated: Bool) { let hudWasVisible = self.shouldShowHUD let statusWasVisible = self.shouldShowStatus let oldStatusRowMode = self.statusRowMode - if let statusRowMode = statusRowMode { - self.statusRowMode = statusRowMode - } + + self.statusRowMode = statusRowMode if let newSize = newSize { self.landscapeMode = newSize.width > newSize.height @@ -588,14 +608,14 @@ final class StatusTableViewController: ChartsTableViewController { } case (.enactingBolus, .enactingBolus): break + case (.bolusing(let oldDose), .bolusing(let newDose)): + if oldDose != newDose { + self.tableView.reloadRows(at: [statusIndexPath], with: animated ? .fade : .none) + } case (.pumpSuspended(resuming: let wasResuming), .pumpSuspended(resuming: let isResuming)): - if isResuming, !wasResuming, let cell = tableView.cellForRow(at: statusIndexPath) as? TitleSubtitleTableViewCell { - let indicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray) - indicatorView.startAnimating() - cell.accessoryView = indicatorView - cell.subtitleLabel.text = nil + if isResuming != wasResuming { + self.tableView.reloadRows(at: [statusIndexPath], with: animated ? .fade : .none) } - break default: self.tableView.reloadRows(at: [statusIndexPath], with: animated ? .fade : .none) } @@ -713,17 +733,24 @@ final class StatusTableViewController: ChartsTableViewController { return cell case .status: - let cell = tableView.dequeueReusableCell(withIdentifier: TitleSubtitleTableViewCell.className, for: indexPath) as! TitleSubtitleTableViewCell - cell.selectionStyle = .none + + func getTitleSubtitleCell() -> TitleSubtitleTableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: TitleSubtitleTableViewCell.className, for: indexPath) as! TitleSubtitleTableViewCell + cell.selectionStyle = .none + return cell + } switch StatusRow(rawValue: indexPath.row)! { case .status: switch statusRowMode { case .hidden: + let cell = getTitleSubtitleCell() cell.titleLabel.text = nil cell.subtitleLabel?.text = nil cell.accessoryView = nil + return cell case .recommendedTempBasal(tempBasal: let tempBasal, at: let date, enacting: let enacting): + let cell = getTitleSubtitleCell() let timeFormatter = DateFormatter() timeFormatter.dateStyle = .none timeFormatter.timeStyle = .short @@ -739,21 +766,49 @@ final class StatusTableViewController: ChartsTableViewController { } else { cell.accessoryView = nil } + return cell case .enactingBolus: + let cell = getTitleSubtitleCell() cell.titleLabel.text = NSLocalizedString("Starting Bolus", comment: "The title of the cell indicating a bolus is being sent") cell.subtitleLabel.text = nil let indicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray) indicatorView.startAnimating() cell.accessoryView = indicatorView - case .pumpSuspended: + return cell + case .bolusing(let dose): + let progressCell = tableView.dequeueReusableCell(withIdentifier: BolusProgressTableViewCell.className, for: indexPath) as! BolusProgressTableViewCell + progressCell.selectionStyle = .none + progressCell.totalUnits = dose.units + progressCell.tintColor = .doseTintColor + progressCell.unit = HKUnit.internationalUnit() + progressCell.deliveredUnits = bolusProgressReporter?.progress.deliveredUnits + return progressCell + case .cancelingBolus: + let cell = getTitleSubtitleCell() + cell.titleLabel.text = NSLocalizedString("Canceling Bolus", comment: "The title of the cell indicating a bolus is being canceled") + cell.subtitleLabel.text = nil + + let indicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray) + indicatorView.startAnimating() + cell.accessoryView = indicatorView + return cell + case .pumpSuspended(let resuming): + let cell = getTitleSubtitleCell() cell.titleLabel.text = NSLocalizedString("Pump Suspended", comment: "The title of the cell indicating the pump is suspended") - cell.subtitleLabel.text = NSLocalizedString("Tap to Resume", comment: "The subtitle of the cell displaying an action to resume insulin delivery") + + if resuming { + let indicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray) + indicatorView.startAnimating() + cell.accessoryView = indicatorView + cell.subtitleLabel.text = "" + } else { + cell.subtitleLabel.text = NSLocalizedString("Tap to Resume", comment: "The subtitle of the cell displaying an action to resume insulin delivery") + } cell.selectionStyle = .default + return cell } } - - return cell } } @@ -864,9 +919,34 @@ final class StatusTableViewController: ChartsTableViewController { if let error = error { let alert = UIAlertController(with: error, title: NSLocalizedString("Error Resuming", comment: "The alert title for a resume error")) self.present(alert, animated: true, completion: nil) + if case .suspended = self.basalDeliveryState { + self.updateHUDandStatusRows(statusRowMode: .pumpSuspended(resuming: false), newSize: nil, animated: true) + } + } else { + self.updateHUDandStatusRows(statusRowMode: .hidden, newSize: nil, animated: true) } } } + case .bolusing: + self.updateHUDandStatusRows(statusRowMode: .cancelingBolus, newSize: nil, animated: true) + self.deviceManager.pumpManager?.cancelBolus() { (result) in + DispatchQueue.main.async { + switch result { + case .success: + // show user confirmation and actual delivery amount? + break + case .failure(let error): + let alert = UIAlertController(with: error, title: NSLocalizedString("Error Canceling Bolus", comment: "The alert title for an error while canceling a bolus")) + self.present(alert, animated: true, completion: nil) + if case .inProgress(let dose) = self.bolusState { + self.updateHUDandStatusRows(statusRowMode: .bolusing(dose: dose), newSize: nil, animated: true) + } else { + self.updateHUDandStatusRows(statusRowMode: .hidden, newSize: nil, animated: true) + } + } + } + } + default: break } @@ -948,7 +1028,6 @@ final class StatusTableViewController: ChartsTableViewController { switch result { case .success(let recommendation): if self.active && self.visible, let bolus = recommendation?.amount, bolus > 0 { - self.bolusState = .recommended self.performSegue(withIdentifier: BolusViewController.className, sender: recommendation) } case .failure(let error): @@ -966,14 +1045,7 @@ final class StatusTableViewController: ChartsTableViewController { @IBAction func unwindFromBolusViewController(_ segue: UIStoryboardSegue) { if let bolusViewController = segue.source as? BolusViewController { if let bolus = bolusViewController.bolus, bolus > 0 { - self.bolusState = .enacting - deviceManager.enactBolus(units: bolus) { (_) in - DispatchQueue.main.async { - self.bolusState = nil - } - } - } else { - self.bolusState = nil + deviceManager.enactBolus(units: bolus) { (_) in } } } } @@ -1140,7 +1212,24 @@ extension StatusTableViewController: PumpManagerStatusObserver { func pumpManager(_ pumpManager: PumpManager, didUpdate status: PumpManagerStatus) { DispatchQueue.main.async { self.basalDeliveryState = status.basalDeliveryState - self.reloadData(animated: true) + self.bolusState = status.bolusState } } } + +extension StatusTableViewController: DoseProgressObserver { + func doseProgressReporterDidUpdate(_ doseProgressReporter: DoseProgressReporter) { + + updateBolusProgress() + + if doseProgressReporter.progress.isComplete { + // Bolus ended + self.bolusProgressReporter = nil + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: { + self.bolusState = .none + self.reloadData(animated: true) + }) + } + } +} + diff --git a/Loop/Views/BolusProgressTableViewCell.swift b/Loop/Views/BolusProgressTableViewCell.swift new file mode 100644 index 0000000000..ca461921d5 --- /dev/null +++ b/Loop/Views/BolusProgressTableViewCell.swift @@ -0,0 +1,112 @@ +// +// BolusProgressTableViewCell.swift +// LoopUI +// +// Created by Pete Schwamb on 3/11/19. +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import Foundation +import LoopKit +import HealthKit +import MKRingProgressView + + +public class BolusProgressTableViewCell: UITableViewCell { + @IBOutlet weak var progressLabel: UILabel! + + @IBOutlet weak var stopSquare: UIView! { + didSet { + stopSquare.layer.cornerRadius = 2 + } + } + + @IBOutlet weak var progressIndicator: RingProgressView! + + public var totalUnits: Double? { + didSet { + updateProgress() + } + } + + public var deliveredUnits: Double? { + didSet { + updateProgress() + } + } + + public var unit: HKUnit? + + private lazy var gradient = CAGradientLayer() + + private var doseTotalUnits: Double? + + private var disableUpdates: Bool = false + + lazy var quantityFormatter: QuantityFormatter = { + let formatter = QuantityFormatter() + formatter.numberFormatter.minimumFractionDigits = 2 + return formatter + }() + + override public func awakeFromNib() { + super.awakeFromNib() + + gradient.frame = bounds + gradient.colors = [UIColor.white.cgColor, UIColor.cellBackgroundColor.cgColor] + backgroundView?.layer.insertSublayer(gradient, at: 0) + updateProgressColor() + } + + override public func layoutSubviews() { + super.layoutSubviews() + + gradient.frame = bounds + } + + public override func tintColorDidChange() { + super.tintColorDidChange() + updateProgressColor() + } + + private func updateProgressColor() { + progressIndicator.startColor = tintColor + progressIndicator.endColor = tintColor + stopSquare.backgroundColor = tintColor + } + + private func updateProgress() { + guard !disableUpdates, let totalUnits = totalUnits, let unit = unit else { + return + } + + let totalUnitsQuantity = HKQuantity(unit: unit, doubleValue: totalUnits) + let totalUnitsString = quantityFormatter.string(from: totalUnitsQuantity, for: unit) ?? "" + + if let deliveredUnits = deliveredUnits { + let deliveredUnitsQuantity = HKQuantity(unit: unit, doubleValue: deliveredUnits) + let deliveredUnitsString = quantityFormatter.string(from: deliveredUnitsQuantity, for: unit) ?? "" + + progressLabel.text = String(format: NSLocalizedString("Bolused %1$@ of %2$@", comment: "The format string for bolus progress. (1: delivered volume)(2: total volume)"), deliveredUnitsString, totalUnitsString) + + let progress = deliveredUnits / totalUnits + UIView.animate(withDuration: 0.3) { + self.progressIndicator.progress = progress + } + } else { + progressLabel.text = String(format: NSLocalizedString("Bolusing %1$@", comment: "The format string for bolus in progress showing total volume. (1: total volume)"), totalUnitsString) + } + } + + override public func prepareForReuse() { + super.prepareForReuse() + disableUpdates = true + deliveredUnits = 0 + disableUpdates = false + progressIndicator.progress = 0 + CATransaction.flush() + progressLabel.text = "" + } +} + +extension BolusProgressTableViewCell: NibLoadable { } diff --git a/Loop/Views/BolusProgressTableViewCell.xib b/Loop/Views/BolusProgressTableViewCell.xib new file mode 100644 index 0000000000..2d689900d4 --- /dev/null +++ b/Loop/Views/BolusProgressTableViewCell.xib @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 78260ca2f2e54227f98ed484c2ed0972ed20ed2d Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Wed, 27 Mar 2019 15:31:54 -0400 Subject: [PATCH 017/132] Use Locked implementation from LoopKit --- Cartfile.resolved | 4 +-- Loop.xcodeproj/project.pbxproj | 8 ++--- Loop/Views/BolusProgressTableViewCell.swift | 1 + LoopCore/Locked.swift | 40 --------------------- 4 files changed, 5 insertions(+), 48 deletions(-) delete mode 100644 LoopCore/Locked.swift diff --git a/Cartfile.resolved b/Cartfile.resolved index e48902ca01..ea1517afd8 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,8 +1,8 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "ea1267791c66e884f1013fffd36faf4555cc6eaf" github "LoopKit/G4ShareSpy" "fed5a389e3e47e3a1953878dd21852aa5f44b360" -github "LoopKit/LoopKit" "3c31297a8c3ee1ac5552f4edc6b4bf454267a549" +github "LoopKit/LoopKit" "e386a24577244ee6b2add52e8b2aff0385c2200b" github "LoopKit/dexcom-share-client-swift" "b0419edf55c7f389b36cb47dd5c376bbd3d03d69" github "i-schuetz/SwiftCharts" "0.6.3" -github "ps2/rileylink_ios" "8418b57c1983bdefaec7ccb456c86d5efacf40e7" github "maxkonovalov/MKRingProgressView" "2.2.1" +github "ps2/rileylink_ios" "809eca7e7272339cd60e24dea9d29ad165bb0edd" diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index acc237d7d7..e05644cc6a 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -62,8 +62,6 @@ 4344628F20A7ADD500C4BE6F /* UserDefaults+CGM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4344628D20A7ADD100C4BE6F /* UserDefaults+CGM.swift */; }; 4344629220A7C19800C4BE6F /* ButtonGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4344629120A7C19800C4BE6F /* ButtonGroup.swift */; }; 4344629820A8B2D700C4BE6F /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4374B5EE209D84BE00D17AA8 /* OSLog.swift */; }; - 4345E3F221F0351E009E00E5 /* Locked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4374B5F1209D897600D17AA8 /* Locked.swift */; }; - 4345E3F321F0351E009E00E5 /* Locked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4374B5F1209D897600D17AA8 /* Locked.swift */; }; 4345E3F421F036FC009E00E5 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D848AF1E7DCBE100DADCBC /* Result.swift */; }; 4345E3F521F036FC009E00E5 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D848AF1E7DCBE100DADCBC /* Result.swift */; }; 4345E3F821F03D2A009E00E5 /* DatesAndNumberCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4345E3F721F03D2A009E00E5 /* DatesAndNumberCell.swift */; }; @@ -634,7 +632,6 @@ 4372E48F213CFCE70068E043 /* LoopSettingsUserInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopSettingsUserInfo.swift; sourceTree = ""; }; 4372E495213DCDD30068E043 /* GlucoseChartValueHashable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseChartValueHashable.swift; sourceTree = ""; }; 4374B5EE209D84BE00D17AA8 /* OSLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = ""; }; - 4374B5F1209D897600D17AA8 /* Locked.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Locked.swift; sourceTree = ""; }; 4374B5F3209D89A900D17AA8 /* TextFieldTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldTableViewCell.swift; sourceTree = ""; }; 43776F8C1B8022E90074EA36 /* Loop.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Loop.app; sourceTree = BUILT_PRODUCTS_DIR; }; 43776F8F1B8022E90074EA36 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AppDelegate.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; @@ -1384,7 +1381,6 @@ 430B298D2041F56500BA9F93 /* GlucoseThreshold.swift */, 43C05CB721EBEA54006FB252 /* HKUnit.swift */, 434FF1E91CF26C29000DB779 /* IdentifiableClass.swift */, - 4374B5F1209D897600D17AA8 /* Locked.swift */, 430B298C2041F56500BA9F93 /* LoopSettings.swift */, 430B29892041F54A00BA9F93 /* NSUserDefaults.swift */, 431E73471FF95A900069B5F7 /* PersistenceController.swift */, @@ -2074,6 +2070,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, fr, @@ -2084,6 +2081,7 @@ nb, es, pl, + ru, ); mainGroup = 43776F831B8022E90074EA36; productRefGroup = 43776F8D1B8022E90074EA36 /* Products */; @@ -2497,7 +2495,6 @@ 43C05CA921EB2B26006FB252 /* PersistenceController.swift in Sources */, 431EA87221EB29150076EC1A /* InsulinModelSettings.swift in Sources */, 43D9002121EB209400AF44BF /* GlucoseThreshold.swift in Sources */, - 4345E3F221F0351E009E00E5 /* Locked.swift in Sources */, 43C05CAB21EB2B4A006FB252 /* NSBundle.swift in Sources */, 43D9002221EB209400AF44BF /* LoopSettings.swift in Sources */, 431EA87421EB291A0076EC1A /* WalshInsulinModel.swift in Sources */, @@ -2543,7 +2540,6 @@ 43C05CA821EB2B26006FB252 /* PersistenceController.swift in Sources */, 431EA87321EB29160076EC1A /* InsulinModelSettings.swift in Sources */, 43D9FFF921EAF34800AF44BF /* GlucoseThreshold.swift in Sources */, - 4345E3F321F0351E009E00E5 /* Locked.swift in Sources */, 43C05CAA21EB2B49006FB252 /* NSBundle.swift in Sources */, 43D9FFF521EAF27200AF44BF /* LoopSettings.swift in Sources */, 431EA87521EB291B0076EC1A /* WalshInsulinModel.swift in Sources */, diff --git a/Loop/Views/BolusProgressTableViewCell.swift b/Loop/Views/BolusProgressTableViewCell.swift index ca461921d5..2121fd7987 100644 --- a/Loop/Views/BolusProgressTableViewCell.swift +++ b/Loop/Views/BolusProgressTableViewCell.swift @@ -8,6 +8,7 @@ import Foundation import LoopKit +import LoopUI import HealthKit import MKRingProgressView diff --git a/LoopCore/Locked.swift b/LoopCore/Locked.swift deleted file mode 100644 index 27dd05d96b..0000000000 --- a/LoopCore/Locked.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// Locked.swift -// LoopKit -// -// Copyright © 2018 LoopKit Authors. All rights reserved. -// - -import os.lock - - -public class Locked { - private var lock = os_unfair_lock() - private var _value: T - - public init(_ value: T) { - os_unfair_lock_lock(&lock) - defer { os_unfair_lock_unlock(&lock) } - _value = value - } - - public var value: T { - get { - os_unfair_lock_lock(&lock) - defer { os_unfair_lock_unlock(&lock) } - return _value - } - set { - os_unfair_lock_lock(&lock) - defer { os_unfair_lock_unlock(&lock) } - _value = newValue - } - } - - public func mutate(_ changes: (_ value: inout T) -> Void) -> T { - os_unfair_lock_lock(&lock) - defer { os_unfair_lock_unlock(&lock) } - changes(&_value) - return _value - } -} From ebfe28415da1ee7aaf6b9587e96d8b71d4e32df3 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 30 Mar 2019 22:02:21 -0500 Subject: [PATCH 018/132] SwiftCharts 0.6.5 --- Cartfile | 2 +- Cartfile.resolved | 4 ++-- Loop.xcodeproj/project.pbxproj | 3 --- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Cartfile b/Cartfile index f4d24c3bdb..67bf550a95 100644 --- a/Cartfile +++ b/Cartfile @@ -3,6 +3,6 @@ github "LoopKit/CGMBLEKit" "dev" github "LoopKit/dexcom-share-client-swift" "dev" github "LoopKit/G4ShareSpy" "dev" github "ps2/rileylink_ios" "dev" -github "ps2/SwiftCharts" "hotfix-xcode10.2" +github "i-schuetz/SwiftCharts" == 0.6.5 github "LoopKit/Amplitude-iOS" "decreepify" github "maxkonovalov/MKRingProgressView" ~> 2.2 diff --git a/Cartfile.resolved b/Cartfile.resolved index b403022a47..e95507cdcd 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -3,6 +3,6 @@ github "LoopKit/CGMBLEKit" "ea1267791c66e884f1013fffd36faf4555cc6eaf" github "LoopKit/G4ShareSpy" "fed5a389e3e47e3a1953878dd21852aa5f44b360" github "LoopKit/LoopKit" "e386a24577244ee6b2add52e8b2aff0385c2200b" github "LoopKit/dexcom-share-client-swift" "b0419edf55c7f389b36cb47dd5c376bbd3d03d69" +github "i-schuetz/SwiftCharts" "0.6.5" github "maxkonovalov/MKRingProgressView" "2.2.1" -github "ps2/SwiftCharts" "cc8c401171d5ccb638ec6c87f6c410ee31fa774d" -github "ps2/rileylink_ios" "809eca7e7272339cd60e24dea9d29ad165bb0edd" +github "ps2/rileylink_ios" "e0dc72050510fb8bf7fc85f655b8d24dc4de0097" diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index e267979b4b..02b83346a7 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -1107,13 +1107,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( -<<<<<<< HEAD C168C40621B0D53E00ADE90E /* MinimedKit.framework in Frameworks */, C168C40821B0D53E00ADE90E /* MinimedKitUI.framework in Frameworks */, 43D9000B21EB0BE000AF44BF /* LoopCore.framework in Frameworks */, -======= C1C0BE2A224C0FA000C03B4D /* SwiftCharts.framework in Frameworks */, ->>>>>>> master 4F7528951DFE1E9B00C322D6 /* LoopUI.framework in Frameworks */, 437AFEE520352591008C4892 /* NotificationCenter.framework in Frameworks */, ); From 282bc52bb9473e1a6e3e952e3e5c66d4ced80cd9 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 30 Mar 2019 23:09:59 -0500 Subject: [PATCH 019/132] Use Xcode 10.2 for travis builds --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bd1ad9d3b4..1c5c34d5f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -osx_image: xcode10 +osx_image: xcode10.2 # xcode_sdk: iphonesimulator11 # xcode_project: Loop.xcodeproj # xcode_scheme: Loop From 1939632b05a5e995f8213408838a787eb4a8a2e1 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 31 Mar 2019 08:29:24 -0500 Subject: [PATCH 020/132] Turn code signing off for travis --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1c5c34d5f3..dd01c3fc33 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,8 +8,8 @@ before_script: - set -o pipefail && xcodebuild -project Loop.xcodeproj -target Cartfile script: # Build the app target - - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme Loop build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO | xcpretty - - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme Learn build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO | xcpretty + - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme Loop build CODE_SIGN_IDENTITY="" CODE_SIGNING_ALLOWED=NO | xcpretty + - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme Learn build CODE_SIGN_IDENTITY="" CODE_SIGNING_ALLOWED=NO | xcpretty # Run the test target - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme LoopTests -destination 'name=iPhone SE' test | xcpretty - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme DoseMathTests -destination 'name=iPhone SE' test | xcpretty From 5377f3c37b22a5d27d65fcec34582c2a6c5ef079 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 2 Apr 2019 09:02:09 -0500 Subject: [PATCH 021/132] Bump bolus ring width down a point --- Loop/Views/BolusProgressTableViewCell.xib | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Loop/Views/BolusProgressTableViewCell.xib b/Loop/Views/BolusProgressTableViewCell.xib index 2d689900d4..3e47fbdadc 100644 --- a/Loop/Views/BolusProgressTableViewCell.xib +++ b/Loop/Views/BolusProgressTableViewCell.xib @@ -1,11 +1,11 @@ - + - + @@ -42,7 +42,7 @@ - + From f0b9429340d45765a65bfa820e9c35da5efc7cb9 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 4 Apr 2019 09:49:33 -0500 Subject: [PATCH 022/132] Display recently-entered carbs in Watch app (#908) * Display recently-entered carbs in Watch app - Refactors the watch interface controller to use a WKInterfaceTable - Present a list of recently logged carbs from HealthKit on tap of the COB cell - Proactively save new carb entries to HealthKit after successfully logging from the watch app - Add a new cell to display remaining reservoir volume * Have travis update brew before building --- .travis.yml | 6 + Common/Models/CarbEntryUserInfo.swift | 90 ------- Loop.xcodeproj/project.pbxproj | 30 ++- Loop/Managers/AnalyticsManager.swift | 2 +- Loop/Managers/LoopDataManager.swift | 6 +- Loop/Managers/WatchDataManager.swift | 13 +- Loop/Models/AbsorptionTimeType+CarbKit.swift | 23 -- LoopCore/CarbEntryUserInfo.swift | 46 ++++ LoopCore/LoopSettings.swift | 2 + .../carbs-dark.colorset/Contents.json | 20 ++ .../carbs.colorset/Contents.json | 20 ++ .../glucose-dark.colorset/Contents.json | 20 ++ .../glucose.colorset/Contents.json | 20 ++ .../insulin-dark.colorset/Contents.json | 20 ++ .../Contents.json | 20 ++ .../insulin.colorset/Contents.json | 20 ++ .../separator.colorset/Contents.json | 20 ++ .../tint.colorset/Contents.json | 20 ++ .../workout-dark.colorset/Contents.json | 20 ++ .../workout.colorset/Contents.json | 20 ++ .../AddCarbsInterfaceController.swift | 45 +++- .../BolusInterfaceController.swift | 1 + .../Controllers/CarbEntryListController.swift | 120 ++++++++++ .../Controllers/ChartHUDController.swift | 128 ++++++---- .../Controllers/HUDRowController.swift | 124 ++++++++++ .../Controllers/TextRowController.swift | 14 ++ .../Extensions/IdentifiableClass.swift | 21 -- WatchApp Extension/Extensions/UIColor.swift | 14 +- WatchApp Extension/Extensions/WCSession.swift | 1 + .../Extensions/WatchContext+WatchApp.swift | 8 + .../Managers/LoopDataManager.swift | 30 ++- WatchApp/Base.lproj/Interface.storyboard | 222 ++++++++++++------ 32 files changed, 852 insertions(+), 314 deletions(-) delete mode 100644 Common/Models/CarbEntryUserInfo.swift delete mode 100644 Loop/Models/AbsorptionTimeType+CarbKit.swift create mode 100644 LoopCore/CarbEntryUserInfo.swift create mode 100644 WatchApp Extension/Assets.xcassets/carbs-dark.colorset/Contents.json create mode 100644 WatchApp Extension/Assets.xcassets/carbs.colorset/Contents.json create mode 100644 WatchApp Extension/Assets.xcassets/glucose-dark.colorset/Contents.json create mode 100644 WatchApp Extension/Assets.xcassets/glucose.colorset/Contents.json create mode 100644 WatchApp Extension/Assets.xcassets/insulin-dark.colorset/Contents.json create mode 100644 WatchApp Extension/Assets.xcassets/insulin-notification-background.colorset/Contents.json create mode 100644 WatchApp Extension/Assets.xcassets/insulin.colorset/Contents.json create mode 100644 WatchApp Extension/Assets.xcassets/separator.colorset/Contents.json create mode 100644 WatchApp Extension/Assets.xcassets/tint.colorset/Contents.json create mode 100644 WatchApp Extension/Assets.xcassets/workout-dark.colorset/Contents.json create mode 100644 WatchApp Extension/Assets.xcassets/workout.colorset/Contents.json create mode 100644 WatchApp Extension/Controllers/CarbEntryListController.swift create mode 100644 WatchApp Extension/Controllers/HUDRowController.swift create mode 100644 WatchApp Extension/Controllers/TextRowController.swift delete mode 100644 WatchApp Extension/Extensions/IdentifiableClass.swift diff --git a/.travis.yml b/.travis.yml index dd01c3fc33..7dcfe36544 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,12 @@ osx_image: xcode10.2 # xcode_sdk: iphonesimulator11 # xcode_project: Loop.xcodeproj # xcode_scheme: Loop + +addons: + homebrew: + packages: + - carthage + update: true before_script: - git config --global protocol.version 1 - set -o pipefail && xcodebuild -project Loop.xcodeproj -target Cartfile diff --git a/Common/Models/CarbEntryUserInfo.swift b/Common/Models/CarbEntryUserInfo.swift deleted file mode 100644 index 4b1a6285a4..0000000000 --- a/Common/Models/CarbEntryUserInfo.swift +++ /dev/null @@ -1,90 +0,0 @@ -// -// CarbEntryUserInfo.swift -// Naterade -// -// Created by Nathan Racklyeft on 1/23/16. -// Copyright © 2016 Nathan Racklyeft. All rights reserved. -// - -import Foundation - -enum AbsorptionTimeType { - case fast - case medium - case slow -} - - -struct CarbEntryUserInfo { - let value: Double - let absorptionTimeType: AbsorptionTimeType - let startDate: Date - - init(value: Double, absorptionTimeType: AbsorptionTimeType, startDate: Date) { - self.value = value - self.absorptionTimeType = absorptionTimeType - self.startDate = startDate - } -} - - -extension AbsorptionTimeType: RawRepresentable { - typealias RawValue = Int - - init?(rawValue: RawValue) { - switch rawValue { - case 0: - self = .fast - case 1: - self = .medium - case 2: - self = .slow - default: - return nil - } - } - - var rawValue: RawValue { - switch self { - case .fast: - return 0 - case .medium: - return 1 - case .slow: - return 2 - } - } -} - - -extension CarbEntryUserInfo: RawRepresentable { - typealias RawValue = [String: Any] - - static let version = 1 - static let name = "CarbEntryUserInfo" - - init?(rawValue: RawValue) { - guard rawValue["v"] as? Int == type(of: self).version && rawValue["name"] as? String == CarbEntryUserInfo.name, - let value = rawValue["cv"] as? Double, - let absorptionTimeRaw = rawValue["ca"] as? Int, - let absorptionTime = AbsorptionTimeType(rawValue: absorptionTimeRaw), - let startDate = rawValue["sd"] as? Date else - { - return nil - } - - self.value = value - self.startDate = startDate - self.absorptionTimeType = absorptionTime - } - - var rawValue: RawValue { - return [ - "v": type(of: self).version, - "name": CarbEntryUserInfo.name, - "cv": value, - "ca": absorptionTimeType.rawValue, - "sd": startDate - ] - } -} diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 02b83346a7..0111de9521 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -42,7 +42,6 @@ 4328E01A1CFBE1DA00E199AA /* ActionHUDController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4328E0151CFBE1DA00E199AA /* ActionHUDController.swift */; }; 4328E01B1CFBE1DA00E199AA /* BolusInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4328E0161CFBE1DA00E199AA /* BolusInterfaceController.swift */; }; 4328E01E1CFBE25F00E199AA /* AddCarbsInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4328E01D1CFBE25F00E199AA /* AddCarbsInterfaceController.swift */; }; - 4328E0261CFBE2C500E199AA /* IdentifiableClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4328E0201CFBE2C500E199AA /* IdentifiableClass.swift */; }; 4328E0281CFBE2C500E199AA /* CLKComplicationTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4328E0221CFBE2C500E199AA /* CLKComplicationTemplate.swift */; }; 4328E02A1CFBE2C500E199AA /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4328E0241CFBE2C500E199AA /* UIColor.swift */; }; 4328E02B1CFBE2C500E199AA /* WKAlertAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4328E0251CFBE2C500E199AA /* WKAlertAction.swift */; }; @@ -71,9 +70,14 @@ 4345E3FE21F04A50009E00E5 /* DateIntervalFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4345E3FD21F04A50009E00E5 /* DateIntervalFormatter.swift */; }; 4345E3FF21F051C6009E00E5 /* LoopCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43D9FFCF21EAE05D00AF44BF /* LoopCore.framework */; }; 4345E40021F051DD009E00E5 /* LoopCore.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 43D9FFCF21EAE05D00AF44BF /* LoopCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 4345E40121F67300009E00E5 /* CarbEntryUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DE92581C5479E4001FFDE1 /* CarbEntryUserInfo.swift */; }; + 4345E40221F67300009E00E5 /* CarbEntryUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DE92581C5479E4001FFDE1 /* CarbEntryUserInfo.swift */; }; + 4345E40421F68AD9009E00E5 /* TextRowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4345E40321F68AD9009E00E5 /* TextRowController.swift */; }; + 4345E40621F68E18009E00E5 /* CarbEntryListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4345E40521F68E18009E00E5 /* CarbEntryListController.swift */; }; 4346D1E71C77F5FE00ABAFE3 /* ChartTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4346D1E61C77F5FE00ABAFE3 /* ChartTableViewCell.swift */; }; 434FB6461D68F1CD007B9C70 /* Amplitude.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 434FB6451D68F1CD007B9C70 /* Amplitude.framework */; }; 434FF1EE1CF27EEF000DB779 /* UITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 434FF1ED1CF27EEF000DB779 /* UITableViewCell.swift */; }; + 43511CEE220FC61700566C63 /* HUDRowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43511CED220FC61700566C63 /* HUDRowController.swift */; }; 43523EDB1CC35083001850F1 /* RileyLinkKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43523EDA1CC35083001850F1 /* RileyLinkKit.framework */; }; 435400311C9F744E00D5819C /* BolusSuggestionUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435400301C9F744E00D5819C /* BolusSuggestionUserInfo.swift */; }; 435400321C9F745500D5819C /* BolusSuggestionUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435400301C9F744E00D5819C /* BolusSuggestionUserInfo.swift */; }; @@ -215,9 +219,6 @@ 43DBF04C1C93B8D700B3C386 /* BolusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DBF04B1C93B8D700B3C386 /* BolusViewController.swift */; }; 43DBF0531C93EC8200B3C386 /* DeviceDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DBF0521C93EC8200B3C386 /* DeviceDataManager.swift */; }; 43DBF0591C93F73800B3C386 /* CarbEntryTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DBF0581C93F73800B3C386 /* CarbEntryTableViewController.swift */; }; - 43DE92591C5479E4001FFDE1 /* CarbEntryUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DE92581C5479E4001FFDE1 /* CarbEntryUserInfo.swift */; }; - 43DE925A1C5479E4001FFDE1 /* CarbEntryUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DE92581C5479E4001FFDE1 /* CarbEntryUserInfo.swift */; }; - 43DE92611C555C26001FFDE1 /* AbsorptionTimeType+CarbKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DE92601C555C26001FFDE1 /* AbsorptionTimeType+CarbKit.swift */; }; 43DFB62320D4CAE7008A7BAE /* PumpManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C3B6F620BBCAA30026CAFA /* PumpManager.swift */; }; 43E2D8C81D208D5B004DA55F /* KeychainManager+Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43E2D8C71D208D5B004DA55F /* KeychainManager+Loop.swift */; }; 43E2D8D41D20BF42004DA55F /* DoseMathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43E2D8D31D20BF42004DA55F /* DoseMathTests.swift */; }; @@ -587,7 +588,6 @@ 4328E0151CFBE1DA00E199AA /* ActionHUDController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionHUDController.swift; sourceTree = ""; }; 4328E0161CFBE1DA00E199AA /* BolusInterfaceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BolusInterfaceController.swift; sourceTree = ""; }; 4328E01D1CFBE25F00E199AA /* AddCarbsInterfaceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddCarbsInterfaceController.swift; sourceTree = ""; }; - 4328E0201CFBE2C500E199AA /* IdentifiableClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentifiableClass.swift; sourceTree = ""; }; 4328E0221CFBE2C500E199AA /* CLKComplicationTemplate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CLKComplicationTemplate.swift; sourceTree = ""; }; 4328E0231CFBE2C500E199AA /* NSUserDefaults+WatchApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSUserDefaults+WatchApp.swift"; sourceTree = ""; }; 4328E0241CFBE2C500E199AA /* UIColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; @@ -609,6 +609,8 @@ 4345E3F721F03D2A009E00E5 /* DatesAndNumberCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatesAndNumberCell.swift; sourceTree = ""; }; 4345E3F921F0473B009E00E5 /* TextCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextCell.swift; sourceTree = ""; }; 4345E3FD21F04A50009E00E5 /* DateIntervalFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateIntervalFormatter.swift; sourceTree = ""; }; + 4345E40321F68AD9009E00E5 /* TextRowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRowController.swift; sourceTree = ""; }; + 4345E40521F68E18009E00E5 /* CarbEntryListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbEntryListController.swift; sourceTree = ""; }; 4346D1E61C77F5FE00ABAFE3 /* ChartTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ChartTableViewCell.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 4346D1EF1C781BEA00ABAFE3 /* SwiftCharts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftCharts.framework; path = Carthage/Build/iOS/SwiftCharts.framework; sourceTree = SOURCE_ROOT; }; 434AB0B11CBB4C3300422F4A /* RileyLinkBLEKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RileyLinkBLEKit.framework; path = Carthage/Build/iOS/RileyLinkBLEKit.framework; sourceTree = SOURCE_ROOT; }; @@ -616,6 +618,7 @@ 434FB6451D68F1CD007B9C70 /* Amplitude.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Amplitude.framework; path = Carthage/Build/iOS/Amplitude.framework; sourceTree = SOURCE_ROOT; }; 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 = ""; }; + 43511CED220FC61700566C63 /* HUDRowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUDRowController.swift; sourceTree = ""; }; 43523EDA1CC35083001850F1 /* RileyLinkKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RileyLinkKit.framework; path = Carthage/Build/iOS/RileyLinkKit.framework; sourceTree = SOURCE_ROOT; }; 435400301C9F744E00D5819C /* BolusSuggestionUserInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BolusSuggestionUserInfo.swift; sourceTree = ""; }; 435400331C9F878D00D5819C /* SetBolusUserInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetBolusUserInfo.swift; sourceTree = ""; }; @@ -736,7 +739,6 @@ 43DBF0521C93EC8200B3C386 /* DeviceDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = DeviceDataManager.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 43DBF0581C93F73800B3C386 /* CarbEntryTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CarbEntryTableViewController.swift; sourceTree = ""; }; 43DE92581C5479E4001FFDE1 /* CarbEntryUserInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CarbEntryUserInfo.swift; sourceTree = ""; }; - 43DE92601C555C26001FFDE1 /* AbsorptionTimeType+CarbKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AbsorptionTimeType+CarbKit.swift"; sourceTree = ""; }; 43E2D8C71D208D5B004DA55F /* KeychainManager+Loop.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "KeychainManager+Loop.swift"; sourceTree = ""; }; 43E2D8C91D20B9E7004DA55F /* KeychainManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainManagerTests.swift; sourceTree = ""; }; 43E2D8D11D20BF42004DA55F /* DoseMathTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DoseMathTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1135,9 +1137,12 @@ 4328E0151CFBE1DA00E199AA /* ActionHUDController.swift */, 4328E01D1CFBE25F00E199AA /* AddCarbsInterfaceController.swift */, 4328E0161CFBE1DA00E199AA /* BolusInterfaceController.swift */, + 4345E40521F68E18009E00E5 /* CarbEntryListController.swift */, 4FFEDFBE20E5CF22000BFC58 /* ChartHUDController.swift */, 4F82654F20E69F9A0031A8F5 /* HUDInterfaceController.swift */, + 43511CED220FC61700566C63 /* HUDRowController.swift */, 43A943891B926B7B0051FA24 /* NotificationController.swift */, + 4345E40321F68AD9009E00E5 /* TextRowController.swift */, ); path = Controllers; sourceTree = ""; @@ -1152,7 +1157,6 @@ 898ECA67218ABDA8001E9D35 /* CLKTextProvider+Compound.m */, 4F7E8AC420E2AB9600AEA65E /* Date.swift */, 4F73F5FB20E2E7FA00E8D82C /* GlucoseStore.swift */, - 4328E0201CFBE2C500E199AA /* IdentifiableClass.swift */, 43785E952120E4010057DED1 /* INRelevantShortcutStore+Loop.swift */, 4328E0231CFBE2C500E199AA /* NSUserDefaults+WatchApp.swift */, 4328E0241CFBE2C500E199AA /* UIColor.swift */, @@ -1178,7 +1182,6 @@ isa = PBXGroup; children = ( 43880F961D9D8052009061A8 /* ServiceAuthentication */, - 43DE92601C555C26001FFDE1 /* AbsorptionTimeType+CarbKit.swift */, C17824A41E1AD4D100D9D25C /* BolusRecommendation.swift */, 43C2FAE01EB656A500364AFF /* GlucoseEffectVelocity.swift */, 436A0DA41D236A2A00104B24 /* LoopError.swift */, @@ -1380,6 +1383,7 @@ isa = PBXGroup; children = ( 43D9003A21EB281300AF44BF /* Insulin */, + 43DE92581C5479E4001FFDE1 /* CarbEntryUserInfo.swift */, 430B298D2041F56500BA9F93 /* GlucoseThreshold.swift */, 43C05CB721EBEA54006FB252 /* HKUnit.swift */, 434FF1E91CF26C29000DB779 /* IdentifiableClass.swift */, @@ -1636,7 +1640,6 @@ isa = PBXGroup; children = ( 435400301C9F744E00D5819C /* BolusSuggestionUserInfo.swift */, - 43DE92581C5479E4001FFDE1 /* CarbEntryUserInfo.swift */, 4F11D3BF20DCBEEC006E072C /* GlucoseBackfillRequestUserInfo.swift */, 4372E48F213CFCE70068E043 /* LoopSettingsUserInfo.swift */, 435400331C9F878D00D5819C /* SetBolusUserInfo.swift */, @@ -2429,8 +2432,6 @@ 438D42FB1D7D11A4003244B0 /* PredictionInputEffectTableViewCell.swift in Sources */, 43F4EF1D1BA2A57600526CE1 /* DiagnosticLogger.swift in Sources */, 432E73CB1D24B3D6009AD15D /* RemoteDataManager.swift in Sources */, - 43DE92591C5479E4001FFDE1 /* CarbEntryUserInfo.swift in Sources */, - 43DE92611C555C26001FFDE1 /* AbsorptionTimeType+CarbKit.swift in Sources */, 43C2FAE11EB656A500364AFF /* GlucoseEffectVelocity.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2447,6 +2448,7 @@ 43A9438A1B926B7B0051FA24 /* NotificationController.swift in Sources */, 439A7945211FE23A0041B75F /* NSUserActivity.swift in Sources */, 43A943881B926B7B0051FA24 /* ExtensionDelegate.swift in Sources */, + 43511CEE220FC61700566C63 /* HUDRowController.swift in Sources */, 4F75F00220FCFE8C00B5570E /* GlucoseChartScene.swift in Sources */, 4328E02F1CFBF81800E199AA /* WKInterfaceImage.swift in Sources */, 4F2C15811E0495B200E160D4 /* WatchContext+WatchApp.swift in Sources */, @@ -2455,6 +2457,7 @@ 4344629820A8B2D700C4BE6F /* OSLog.swift in Sources */, 4328E02A1CFBE2C500E199AA /* UIColor.swift in Sources */, 4372E484213A63FB0068E043 /* ChartHUDController.swift in Sources */, + 4345E40621F68E18009E00E5 /* CarbEntryListController.swift in Sources */, 4FDDD23720DC51DF00D04B16 /* LoopDataManager.swift in Sources */, 898ECA60218ABD17001E9D35 /* GlucoseChartScaler.swift in Sources */, 4328E01B1CFBE1DA00E199AA /* BolusInterfaceController.swift in Sources */, @@ -2467,7 +2470,6 @@ 4F11D3C420DD881A006E072C /* WatchHistoricalGlucose.swift in Sources */, 4328E0281CFBE2C500E199AA /* CLKComplicationTemplate.swift in Sources */, 4328E01E1CFBE25F00E199AA /* AddCarbsInterfaceController.swift in Sources */, - 4328E0261CFBE2C500E199AA /* IdentifiableClass.swift in Sources */, 4F73F5FC20E2E7FA00E8D82C /* GlucoseStore.swift in Sources */, 432CF87520D8AC950066B889 /* NSUserDefaults+WatchApp.swift in Sources */, 43027F0F1DFE0EC900C51989 /* HKUnit.swift in Sources */, @@ -2478,7 +2480,7 @@ 898ECA65218ABD9B001E9D35 /* CGRect.swift in Sources */, 43CB2B2B1D924D450079823D /* WCSession.swift in Sources */, 4372E491213D05F90068E043 /* LoopSettingsUserInfo.swift in Sources */, - 43DE925A1C5479E4001FFDE1 /* CarbEntryUserInfo.swift in Sources */, + 4345E40421F68AD9009E00E5 /* TextRowController.swift in Sources */, 43BFF0B51E45C1E700FF19A9 /* NumberFormatter.swift in Sources */, 43A9438E1B926B7B0051FA24 /* ComplicationController.swift in Sources */, 4328E01A1CFBE1DA00E199AA /* ActionHUDController.swift in Sources */, @@ -2503,6 +2505,7 @@ 431EA87021EB29120076EC1A /* ExponentialInsulinModelPreset.swift in Sources */, 43C05CC721EC2ABC006FB252 /* IdentifiableClass.swift in Sources */, 43C05CAE21EB2BBF006FB252 /* NSUserDefaults.swift in Sources */, + 4345E40221F67300009E00E5 /* CarbEntryUserInfo.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2548,6 +2551,7 @@ 431EA87121EB29120076EC1A /* ExponentialInsulinModelPreset.swift in Sources */, 43C05CC821EC2ABC006FB252 /* IdentifiableClass.swift in Sources */, 43C05CAD21EB2BBF006FB252 /* NSUserDefaults.swift in Sources */, + 4345E40121F67300009E00E5 /* CarbEntryUserInfo.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Loop/Managers/AnalyticsManager.swift b/Loop/Managers/AnalyticsManager.swift index dc62fde479..1df38803d5 100644 --- a/Loop/Managers/AnalyticsManager.swift +++ b/Loop/Managers/AnalyticsManager.swift @@ -134,7 +134,7 @@ final class AnalyticsManager: IdentifiableClass { // MARK: - Loop Events - func didAddCarbsFromWatch(_ carbs: Double) { + func didAddCarbsFromWatch() { logEvent("Carb entry created", withProperties: ["source" : "Watch"], outOfSession: true) } diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 7b02413af5..3db08b5bcc 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -62,11 +62,7 @@ final class LoopDataManager { carbStore = CarbStore( healthStore: healthStore, cacheStore: cacheStore, - defaultAbsorptionTimes: ( - fast: TimeInterval(hours: 2), - medium: TimeInterval(hours: 3), - slow: TimeInterval(hours: 4) - ), + defaultAbsorptionTimes: LoopSettings.defaultCarbAbsorptionTimes, carbRatioSchedule: carbRatioSchedule, insulinSensitivitySchedule: insulinSensitivitySchedule ) diff --git a/Loop/Managers/WatchDataManager.swift b/Loop/Managers/WatchDataManager.swift index e8a9300b9a..b734796f0c 100644 --- a/Loop/Managers/WatchDataManager.swift +++ b/Loop/Managers/WatchDataManager.swift @@ -193,18 +193,11 @@ final class WatchDataManager: NSObject { } private func addCarbEntryFromWatchMessage(_ message: [String: Any], completionHandler: ((_ units: Double?) -> Void)? = nil) { - if let carbEntry = CarbEntryUserInfo(rawValue: message) { - let newEntry = NewCarbEntry( - quantity: HKQuantity(unit: deviceManager.loopManager.carbStore.preferredUnit, doubleValue: carbEntry.value), - startDate: carbEntry.startDate, - foodType: nil, - absorptionTime: carbEntry.absorptionTimeType.absorptionTimeFromDefaults(deviceManager.loopManager.carbStore.defaultAbsorptionTimes) - ) - - deviceManager.loopManager.addCarbEntryAndRecommendBolus(newEntry) { (result) in + if let carbEntry = CarbEntryUserInfo(rawValue: message)?.carbEntry { + deviceManager.loopManager.addCarbEntryAndRecommendBolus(carbEntry) { (result) in switch result { case .success(let recommendation): - AnalyticsManager.shared.didAddCarbsFromWatch(carbEntry.value) + AnalyticsManager.shared.didAddCarbsFromWatch() completionHandler?(recommendation?.amount) case .failure(let error): self.log.error(error) diff --git a/Loop/Models/AbsorptionTimeType+CarbKit.swift b/Loop/Models/AbsorptionTimeType+CarbKit.swift deleted file mode 100644 index 92d7f17b2a..0000000000 --- a/Loop/Models/AbsorptionTimeType+CarbKit.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// AbsorptionTimeType+CarbKit.swift -// Naterade -// -// Created by Nathan Racklyeft on 1/24/16. -// Copyright © 2016 Nathan Racklyeft. All rights reserved. -// - -import LoopKit - - -extension AbsorptionTimeType { - func absorptionTimeFromDefaults(_ defaults: CarbStore.DefaultAbsorptionTimes) -> TimeInterval { - switch self { - case .fast: - return defaults.fast - case .medium: - return defaults.medium - case .slow: - return defaults.slow - } - } -} diff --git a/LoopCore/CarbEntryUserInfo.swift b/LoopCore/CarbEntryUserInfo.swift new file mode 100644 index 0000000000..f8fa01a288 --- /dev/null +++ b/LoopCore/CarbEntryUserInfo.swift @@ -0,0 +1,46 @@ +// +// CarbEntryUserInfo.swift +// Naterade +// +// Created by Nathan Racklyeft on 1/23/16. +// Copyright © 2016 Nathan Racklyeft. All rights reserved. +// + +import Foundation +import LoopKit + + +public struct CarbEntryUserInfo { + public let carbEntry: NewCarbEntry + + public init(carbEntry: NewCarbEntry) { + self.carbEntry = carbEntry + } +} + + +extension CarbEntryUserInfo: RawRepresentable { + public typealias RawValue = [String: Any] + + static let version = 2 + public static let name = "CarbEntryUserInfo" + + public init?(rawValue: RawValue) { + guard rawValue["v"] as? Int == type(of: self).version && rawValue["name"] as? String == CarbEntryUserInfo.name, + let value = rawValue["ce"] as? NewCarbEntry.RawValue, + let carbEntry = NewCarbEntry(rawValue: value) + else { + return nil + } + + self.carbEntry = carbEntry + } + + public var rawValue: RawValue { + return [ + "v": type(of: self).version, + "name": CarbEntryUserInfo.name, + "ce": carbEntry.rawValue, + ] + } +} diff --git a/LoopCore/LoopSettings.swift b/LoopCore/LoopSettings.swift index 4d0d4f084b..66366eec6d 100644 --- a/LoopCore/LoopSettings.swift +++ b/LoopCore/LoopSettings.swift @@ -13,6 +13,8 @@ public struct LoopSettings: Equatable { public let dynamicCarbAbsorptionEnabled = true + public static let defaultCarbAbsorptionTimes: CarbStore.DefaultAbsorptionTimes = (fast: .hours(2), medium: .hours(3), slow: .hours(4)) + public var glucoseTargetRangeSchedule: GlucoseRangeSchedule? public var maximumBasalRatePerHour: Double? diff --git a/WatchApp Extension/Assets.xcassets/carbs-dark.colorset/Contents.json b/WatchApp Extension/Assets.xcassets/carbs-dark.colorset/Contents.json new file mode 100644 index 0000000000..b84dc0d758 --- /dev/null +++ b/WatchApp Extension/Assets.xcassets/carbs-dark.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.070", + "alpha" : "1.000", + "blue" : "0.040", + "green" : "0.120" + } + } + } + ] +} \ No newline at end of file diff --git a/WatchApp Extension/Assets.xcassets/carbs.colorset/Contents.json b/WatchApp Extension/Assets.xcassets/carbs.colorset/Contents.json new file mode 100644 index 0000000000..466a4ff5e6 --- /dev/null +++ b/WatchApp Extension/Assets.xcassets/carbs.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.388", + "alpha" : "1.000", + "blue" : "0.220", + "green" : "0.855" + } + } + } + ] +} \ No newline at end of file diff --git a/WatchApp Extension/Assets.xcassets/glucose-dark.colorset/Contents.json b/WatchApp Extension/Assets.xcassets/glucose-dark.colorset/Contents.json new file mode 100644 index 0000000000..224d3a60db --- /dev/null +++ b/WatchApp Extension/Assets.xcassets/glucose-dark.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.020", + "alpha" : "1.000", + "blue" : "0.140", + "green" : "0.100" + } + } + } + ] +} \ No newline at end of file diff --git a/WatchApp Extension/Assets.xcassets/glucose.colorset/Contents.json b/WatchApp Extension/Assets.xcassets/glucose.colorset/Contents.json new file mode 100644 index 0000000000..b517cc730a --- /dev/null +++ b/WatchApp Extension/Assets.xcassets/glucose.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.310", + "alpha" : "1.000", + "blue" : "0.973", + "green" : "0.678" + } + } + } + ] +} \ No newline at end of file diff --git a/WatchApp Extension/Assets.xcassets/insulin-dark.colorset/Contents.json b/WatchApp Extension/Assets.xcassets/insulin-dark.colorset/Contents.json new file mode 100644 index 0000000000..0dc7535153 --- /dev/null +++ b/WatchApp Extension/Assets.xcassets/insulin-dark.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "1.000", + "alpha" : "0.150", + "blue" : "0.000", + "green" : "0.584" + } + } + } + ] +} \ No newline at end of file diff --git a/WatchApp Extension/Assets.xcassets/insulin-notification-background.colorset/Contents.json b/WatchApp Extension/Assets.xcassets/insulin-notification-background.colorset/Contents.json new file mode 100644 index 0000000000..31cf33c954 --- /dev/null +++ b/WatchApp Extension/Assets.xcassets/insulin-notification-background.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "1.000", + "alpha" : "0.500", + "blue" : "0.000", + "green" : "0.584" + } + } + } + ] +} \ No newline at end of file diff --git a/WatchApp Extension/Assets.xcassets/insulin.colorset/Contents.json b/WatchApp Extension/Assets.xcassets/insulin.colorset/Contents.json new file mode 100644 index 0000000000..c80adbee2b --- /dev/null +++ b/WatchApp Extension/Assets.xcassets/insulin.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "1.000", + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.584" + } + } + } + ] +} \ No newline at end of file diff --git a/WatchApp Extension/Assets.xcassets/separator.colorset/Contents.json b/WatchApp Extension/Assets.xcassets/separator.colorset/Contents.json new file mode 100644 index 0000000000..5fa0de2011 --- /dev/null +++ b/WatchApp Extension/Assets.xcassets/separator.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.722", + "alpha" : "0.500", + "blue" : "0.722", + "green" : "0.722" + } + } + } + ] +} \ No newline at end of file diff --git a/WatchApp Extension/Assets.xcassets/tint.colorset/Contents.json b/WatchApp Extension/Assets.xcassets/tint.colorset/Contents.json new file mode 100644 index 0000000000..ff9e13530c --- /dev/null +++ b/WatchApp Extension/Assets.xcassets/tint.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.298", + "alpha" : "1.000", + "blue" : "0.392", + "green" : "0.851" + } + } + } + ] +} \ No newline at end of file diff --git a/WatchApp Extension/Assets.xcassets/workout-dark.colorset/Contents.json b/WatchApp Extension/Assets.xcassets/workout-dark.colorset/Contents.json new file mode 100644 index 0000000000..224d3a60db --- /dev/null +++ b/WatchApp Extension/Assets.xcassets/workout-dark.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.020", + "alpha" : "1.000", + "blue" : "0.140", + "green" : "0.100" + } + } + } + ] +} \ No newline at end of file diff --git a/WatchApp Extension/Assets.xcassets/workout.colorset/Contents.json b/WatchApp Extension/Assets.xcassets/workout.colorset/Contents.json new file mode 100644 index 0000000000..b517cc730a --- /dev/null +++ b/WatchApp Extension/Assets.xcassets/workout.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.310", + "alpha" : "1.000", + "blue" : "0.973", + "green" : "0.678" + } + } + } + ] +} \ No newline at end of file diff --git a/WatchApp Extension/Controllers/AddCarbsInterfaceController.swift b/WatchApp Extension/Controllers/AddCarbsInterfaceController.swift index 51be151f37..56e39878bb 100644 --- a/WatchApp Extension/Controllers/AddCarbsInterfaceController.swift +++ b/WatchApp Extension/Controllers/AddCarbsInterfaceController.swift @@ -9,12 +9,19 @@ import WatchKit import WatchConnectivity import HealthKit +import LoopCore import LoopKit import os.log final class AddCarbsInterfaceController: WKInterfaceController, IdentifiableClass { + private enum AbsorptionTimeType { + case fast + case medium + case slow + } + private var carbValue: Int = 15 { didSet { if carbValue < minimumCarbValue { @@ -56,13 +63,13 @@ final class AddCarbsInterfaceController: WKInterfaceController, IdentifiableClas return formatter }() - private var absorptionTime: AbsorptionTimeType = .medium { + private var absorptionTimeType: AbsorptionTimeType = .medium { didSet { absorptionButtonA.setBackgroundColor(UIColor.darkCarbsColor) absorptionButtonB.setBackgroundColor(UIColor.darkCarbsColor) absorptionButtonC.setBackgroundColor(UIColor.darkCarbsColor) - switch absorptionTime { + switch absorptionTimeType { case .fast: absorptionButtonA.setBackgroundColor(UIColor.carbsColor) case .medium: @@ -73,6 +80,8 @@ final class AddCarbsInterfaceController: WKInterfaceController, IdentifiableClas } } + var defaultAbsorptionTimes: CarbStore.DefaultAbsorptionTimes? + @IBOutlet var valueLabel: WKInterfaceLabel! @IBOutlet var dateLabel: WKInterfaceLabel! @@ -108,7 +117,8 @@ final class AddCarbsInterfaceController: WKInterfaceController, IdentifiableClas crownSequencer.delegate = self date = Date() - absorptionTime = .medium + absorptionTimeType = .medium + defaultAbsorptionTimes = ExtensionDelegate.shared().loopManager.carbStore.defaultAbsorptionTimes } override func willActivate() { @@ -161,20 +171,20 @@ final class AddCarbsInterfaceController: WKInterfaceController, IdentifiableClas } @IBAction func setAbsorptionTimeFast() { - absorptionTime = .fast + absorptionTimeType = .fast } @IBAction func setAbsorptionTimeMedium() { - absorptionTime = .medium + absorptionTimeType = .medium } @IBAction func setAbsorptionTimeSlow() { - absorptionTime = .slow + absorptionTimeType = .slow } @IBAction func save() { if carbValue > 0 { - let entry = CarbEntryUserInfo(value: Double(carbValue), absorptionTimeType: absorptionTime, startDate: date) + let entry = CarbEntryUserInfo(carbEntry: self.entry) do { try WCSession.default.sendCarbEntryMessage(entry, @@ -182,7 +192,7 @@ final class AddCarbsInterfaceController: WKInterfaceController, IdentifiableClas DispatchQueue.main.async { WKInterfaceDevice.current().play(.success) - ExtensionDelegate.shared().loopManager.addConfirmedCarbEntry(entry) + ExtensionDelegate.shared().loopManager.addConfirmedCarbEntry(entry.carbEntry) if let recommendedBolus = suggestion.recommendedBolus?.rawValue, recommendedBolus > 0.0 { WKExtension.shared().rootInterfaceController?.presentController(withName: BolusInterfaceController.className, context: suggestion) @@ -244,8 +254,25 @@ extension AddCarbsInterfaceController: NSUserActivityDelegate { updateUserActivity(userActivity.activityType, userInfo: userActivity.userInfo, webpageURL: nil) } } +} + +extension AddCarbsInterfaceController { + private var absorptionTime: TimeInterval? { + guard let defaultTimes = defaultAbsorptionTimes else { + return nil + } + + switch absorptionTimeType { + case .fast: + return defaultTimes.fast + case .medium: + return defaultTimes.medium + case .slow: + return defaultTimes.slow + } + } private var entry: NewCarbEntry { - return NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: Double(carbValue)), startDate: date, foodType: nil, absorptionTime: nil) + return NewCarbEntry(quantity: HKQuantity(unit: .gram(), doubleValue: Double(carbValue)), startDate: date, foodType: nil, absorptionTime: absorptionTime, syncIdentifier: UUID().uuidString) } } diff --git a/WatchApp Extension/Controllers/BolusInterfaceController.swift b/WatchApp Extension/Controllers/BolusInterfaceController.swift index 6d83a3da29..7154ff7c88 100644 --- a/WatchApp Extension/Controllers/BolusInterfaceController.swift +++ b/WatchApp Extension/Controllers/BolusInterfaceController.swift @@ -6,6 +6,7 @@ // Copyright © 2016 Nathan Racklyeft. All rights reserved. // +import LoopCore import WatchKit import Foundation import WatchConnectivity diff --git a/WatchApp Extension/Controllers/CarbEntryListController.swift b/WatchApp Extension/Controllers/CarbEntryListController.swift new file mode 100644 index 0000000000..a2405654c4 --- /dev/null +++ b/WatchApp Extension/Controllers/CarbEntryListController.swift @@ -0,0 +1,120 @@ +// +// CarbEntryListController.swift +// WatchApp Extension +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import HealthKit +import LoopCore +import LoopKit +import os.log +import WatchKit + +class CarbEntryListController: WKInterfaceController, IdentifiableClass { + @IBOutlet private var table: WKInterfaceTable! + + @IBOutlet private var cobLabel: WKInterfaceLabel! + + @IBOutlet var totalLabel: WKInterfaceLabel! + + @IBOutlet var headerGroup: WKInterfaceGroup! + + private let log = OSLog(category: "CarbEntryListController") + + private lazy var loopManager = ExtensionDelegate.shared().loopManager + + private lazy var carbFormatter: QuantityFormatter = { + let formatter = QuantityFormatter() + formatter.numberFormatter.numberStyle = .none + return formatter + }() + + private var observers: [Any] = [] { + didSet { + for observer in oldValue { + NotificationCenter.default.removeObserver(observer) + } + } + } + + override func awake(withContext context: Any?) { + table.setNumberOfRows(0, withRowType: TextRowController.className) + reloadCarbEntries() + updateActiveCarbs() + } + + override func willActivate() { + observers = [ + NotificationCenter.default.addObserver(forName: .CarbEntriesDidUpdate, object: loopManager.carbStore, queue: nil) { [weak self] (note) in + self?.log.default("Received CarbEntriesDidUpdate notification: %{public}@. Updating list", String(describing: note.userInfo ?? [:])) + + DispatchQueue.main.async { + self?.reloadCarbEntries() + } + }, + NotificationCenter.default.addObserver(forName: LoopDataManager.didUpdateContextNotification, object: loopManager, queue: nil) { [weak self] (note) in + DispatchQueue.main.async { + self?.updateActiveCarbs() + } + } + ] + } + + override func didDeactivate() { + observers = [] + } +} + + +extension CarbEntryListController { + private func updateActiveCarbs() { + guard let activeCarbohydrates = loopManager.activeContext?.activeCarbohydrates else { + return + } + + cobLabel.setText(carbFormatter.string(from: activeCarbohydrates, for: .gram())) + } + + private func reloadCarbEntries() { + let start = min(Calendar.current.startOfDay(for: Date()), Date(timeIntervalSinceNow: -2 * loopManager.carbStore.defaultAbsorptionTimes.slow)) + + loopManager.carbStore.getCarbEntries(start: start) { (result) in + switch result { + case .success(let entries): + DispatchQueue.main.async { + self.setCarbEntries(entries) + } + case .failure(let error): + self.log.error("Failed to fetch carb entries: %{public}@", String(describing: error)) + } + } + } + + private func setCarbEntries(_ entries: [StoredCarbEntry]) { + dispatchPrecondition(condition: .onQueue(.main)) + + table.setNumberOfRows(entries.count, withRowType: TextRowController.className) + + var total = 0.0 + + let timeFormatter = DateFormatter() + timeFormatter.dateStyle = .none + timeFormatter.timeStyle = .short + + let unit = loopManager.carbStore.preferredUnit ?? .gram() + + for (index, entry) in entries.reversed().enumerated() { + guard let row = table.rowController(at: index) as? TextRowController else { + continue + } + + total += entry.quantity.doubleValue(for: unit) + + row.textLabel.setText(timeFormatter.string(from: entry.startDate)) + row.detailTextLabel.setText(carbFormatter.string(from: entry.quantity, for: unit)) + } + + totalLabel.setText(carbFormatter.string(from: HKQuantity(unit: unit, doubleValue: total), for: unit)) + } +} diff --git a/WatchApp Extension/Controllers/ChartHUDController.swift b/WatchApp Extension/Controllers/ChartHUDController.swift index b182073ed5..1369c440a0 100644 --- a/WatchApp Extension/Controllers/ChartHUDController.swift +++ b/WatchApp Extension/Controllers/ChartHUDController.swift @@ -15,10 +15,32 @@ import SpriteKit import os.log final class ChartHUDController: HUDInterfaceController, WKCrownDelegate { - @IBOutlet private weak var tableGroup: WKInterfaceGroup! - @IBOutlet private weak var basalLabel: WKInterfaceLabel! - @IBOutlet private weak var iobLabel: WKInterfaceLabel! - @IBOutlet private weak var cobLabel: WKInterfaceLabel! + private enum TableRow: Int, CaseIterable { + case iob + case cob + case netBasal + case reservoirVolume + + var title: String { + switch self { + case .iob: + return NSLocalizedString("Active Insulin", comment: "HUD row title for IOB") + case .cob: + return NSLocalizedString("Active Carbs", comment: "HUD row title for COB") + case .netBasal: + return NSLocalizedString("Net Basal Rate", comment: "HUD row title") + case .reservoirVolume: + return NSLocalizedString("Reservoir Volume", comment: "HUD row title for remaining reservoir volume") + } + } + + var isLast: Bool { + return self == TableRow.allCases.last + } + } + + @IBOutlet private weak var table: WKInterfaceTable! + @IBOutlet private weak var glucoseScene: WKInterfaceSKScene! @IBAction private func setChartWindow1Hour() { scene.visibleDuration = .hours(2) @@ -38,19 +60,24 @@ final class ChartHUDController: HUDInterfaceController, WKCrownDelegate { private let log = OSLog(category: "ChartHUDController") private var hasInitialActivation = false + private var observers: [Any] = [] { + didSet { + for observer in oldValue { + NotificationCenter.default.removeObserver(observer) + } + } + } + override init() { super.init() - loopManager = ExtensionDelegate.shared().loopManager - NotificationCenter.default.addObserver(forName: .GlucoseSamplesDidChange, object: loopManager.glucoseStore, queue: nil) { [weak self] (note) in - self?.log.default("Received GlucoseSamplesDidChange notification: %{public}@. Updating chart", String(describing: note.userInfo ?? [:])) + glucoseScene.presentScene(scene) + } - DispatchQueue.main.async { - self?.updateGlucoseChart() - } - } + override func awake(withContext context: Any?) { + super.awake(withContext: context) - glucoseScene.presentScene(scene) + table.setNumberOfRows(TableRow.allCases.count, withRowType: HUDRowController.className) } override func didAppear() { @@ -76,7 +103,6 @@ final class ChartHUDController: HUDInterfaceController, WKCrownDelegate { if #available(watchOSApplicationExtension 5.0, *) { scene.textInsets.left = max(scene.textInsets.left, systemMinimumLayoutMargins.leading) scene.textInsets.right = max(scene.textInsets.right, systemMinimumLayoutMargins.trailing) - tableGroup.setContentInset(UIEdgeInsets(top: 0, left: systemMinimumLayoutMargins.leading, bottom: 0, right: systemMinimumLayoutMargins.trailing)) } } @@ -91,6 +117,16 @@ final class ChartHUDController: HUDInterfaceController, WKCrownDelegate { override func willActivate() { super.willActivate() + observers = [ + NotificationCenter.default.addObserver(forName: .GlucoseSamplesDidChange, object: loopManager.glucoseStore, queue: nil) { [weak self] (note) in + self?.log.default("Received GlucoseSamplesDidChange notification: %{public}@. Updating chart", String(describing: note.userInfo ?? [:])) + + DispatchQueue.main.async { + self?.updateGlucoseChart() + } + } + ] + if glucoseScene.isPaused { log.default("willActivate() unpausing") glucoseScene.isPaused = false @@ -111,6 +147,8 @@ final class ChartHUDController: HUDInterfaceController, WKCrownDelegate { override func didDeactivate() { super.didDeactivate() + observers = [] + log.default("didDeactivate() pausing") glucoseScene.isPaused = true } @@ -122,44 +160,24 @@ final class ChartHUDController: HUDInterfaceController, WKCrownDelegate { return } - if let activeInsulin = activeContext.activeInsulin { - let insulinFormatter: QuantityFormatter = { - let insulinFormatter = QuantityFormatter() - insulinFormatter.numberFormatter.minimumFractionDigits = 1 - insulinFormatter.numberFormatter.maximumFractionDigits = 1 - - return insulinFormatter - }() - - iobLabel.setText(insulinFormatter.string(from: activeInsulin, for: .internationalUnit())) - } else { - iobLabel.setText("—") - } - - if let carbsOnBoard = activeContext.activeCarbohydrates { - let carbFormatter = QuantityFormatter() - carbFormatter.numberFormatter.maximumFractionDigits = 0 - - cobLabel.setText(carbFormatter.string(from: carbsOnBoard, for: .gram())) - } else { - cobLabel.setText("—") - } - - if let tempBasal = activeContext.lastNetTempBasalDose { - let basalFormatter = NumberFormatter() - basalFormatter.numberStyle = .decimal - basalFormatter.minimumFractionDigits = 1 - basalFormatter.maximumFractionDigits = 3 - basalFormatter.positivePrefix = basalFormatter.plusSign - - let unit = NSLocalizedString( - "U/hr", - comment: "The short unit display string for international units of insulin delivery per hour" - ) + for row in TableRow.allCases { + let cell = table.rowController(at: row.rawValue) as! HUDRowController + cell.setTitle(row.title) + cell.setIsLastRow(row.isLast) + if #available(watchOSApplicationExtension 5.0, *) { + cell.setContentInset(systemMinimumLayoutMargins) + } - basalLabel.setText(basalFormatter.string(from: tempBasal, unit: unit)) - } else { - basalLabel.setText("—") + switch row { + case .iob: + cell.setActiveInsulin(activeContext.activeInsulin) + case .cob: + cell.setActiveCarbohydrates(activeContext.activeCarbohydrates) + case .netBasal: + cell.setNetTempBasalDose(activeContext.lastNetTempBasalDose) + case .reservoirVolume: + cell.setReservoirVolume(activeContext.reservoirVolume) + } } if glucoseScene.isPaused { @@ -170,7 +188,7 @@ final class ChartHUDController: HUDInterfaceController, WKCrownDelegate { updateGlucoseChart() } - func updateGlucoseChart() { + private func updateGlucoseChart() { loopManager.generateChartData { chartData in DispatchQueue.main.async { self.scene.data = chartData @@ -178,4 +196,12 @@ final class ChartHUDController: HUDInterfaceController, WKCrownDelegate { } } } + + override func table(_ table: WKInterfaceTable, didSelectRowAt rowIndex: Int) { + guard table == self.table, case .cob? = TableRow(rawValue: rowIndex) else { + return + } + + presentController(withName: CarbEntryListController.className, context: nil) + } } diff --git a/WatchApp Extension/Controllers/HUDRowController.swift b/WatchApp Extension/Controllers/HUDRowController.swift new file mode 100644 index 0000000000..d09f14a827 --- /dev/null +++ b/WatchApp Extension/Controllers/HUDRowController.swift @@ -0,0 +1,124 @@ +// +// HUDRowController.swift +// WatchApp Extension +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import HealthKit +import LoopCore +import LoopKit +import WatchKit + +class HUDRowController: NSObject, IdentifiableClass { + @IBOutlet private var textLabel: WKInterfaceLabel! + @IBOutlet private var detailTextLabel: WKInterfaceLabel! + @IBOutlet private var outerGroup: WKInterfaceGroup! + @IBOutlet private var bottomSeparator: WKInterfaceSeparator! +} + +extension HUDRowController { + func setTitle(_ title: String) { + textLabel.setText(title.localizedUppercase) + } + + func setContentInset(_ inset: NSDirectionalEdgeInsets) { + outerGroup.setContentInset(inset.deviceInsets) + } + + func setDetail(_ detail: String?) { + detailTextLabel.setText(detail ?? "—") + } + + func setIsLastRow(_ isLastRow: Bool) { + bottomSeparator.setHidden(isLastRow) + } +} + +extension HUDRowController { + func setActiveInsulin(_ activeInsulin: HKQuantity?) { + guard let activeInsulin = activeInsulin else { + setDetail(nil) + return + } + + let insulinFormatter: QuantityFormatter = { + let insulinFormatter = QuantityFormatter() + insulinFormatter.numberFormatter.minimumFractionDigits = 1 + insulinFormatter.numberFormatter.maximumFractionDigits = 1 + + return insulinFormatter + }() + + setDetail(insulinFormatter.string(from: activeInsulin, for: .internationalUnit())) + } + + func setActiveCarbohydrates(_ activeCarbohydrates: HKQuantity?) { + guard let activeCarbohydrates = activeCarbohydrates else { + setDetail(nil) + return + } + + let carbFormatter = QuantityFormatter() + carbFormatter.numberFormatter.maximumFractionDigits = 0 + + setDetail(carbFormatter.string(from: activeCarbohydrates, for: .gram())) + } + + func setNetTempBasalDose(_ tempBasal: Double?) { + guard let tempBasal = tempBasal else { + setDetail(nil) + return + } + + let basalFormatter = NumberFormatter() + basalFormatter.numberStyle = .decimal + basalFormatter.minimumFractionDigits = 1 + basalFormatter.maximumFractionDigits = 3 + basalFormatter.positivePrefix = basalFormatter.plusSign + + let unit = NSLocalizedString( + "U/hr", + comment: "The short unit display string for international units of insulin delivery per hour" + ) + + setDetail(basalFormatter.string(from: tempBasal, unit: unit)) + } + + func setReservoirVolume(_ reservoirVolume: HKQuantity?) { + guard let reservoirVolume = reservoirVolume else { + setDetail(nil) + return + } + + let insulinFormatter: QuantityFormatter = { + let insulinFormatter = QuantityFormatter() + insulinFormatter.unitStyle = .long + insulinFormatter.numberFormatter.minimumFractionDigits = 0 + insulinFormatter.numberFormatter.maximumFractionDigits = 0 + + return insulinFormatter + }() + + setDetail(insulinFormatter.string(from: reservoirVolume, for: .internationalUnit())) + } +} + + +fileprivate extension NSDirectionalEdgeInsets { + var deviceInsets: UIEdgeInsets { + let left: CGFloat + let right: CGFloat + + switch WKInterfaceDevice.current().layoutDirection { + case .leftToRight: + left = leading + right = trailing + case .rightToLeft: + right = leading + left = trailing + } + + return UIEdgeInsets(top: top, left: left, bottom: bottom, right: right) + } +} diff --git a/WatchApp Extension/Controllers/TextRowController.swift b/WatchApp Extension/Controllers/TextRowController.swift new file mode 100644 index 0000000000..db48a009bb --- /dev/null +++ b/WatchApp Extension/Controllers/TextRowController.swift @@ -0,0 +1,14 @@ +// +// TextRowController.swift +// WatchApp Extension +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import LoopCore +import WatchKit + +class TextRowController: NSObject, IdentifiableClass { + @IBOutlet private(set) var textLabel: WKInterfaceLabel! + @IBOutlet private(set) var detailTextLabel: WKInterfaceLabel! +} diff --git a/WatchApp Extension/Extensions/IdentifiableClass.swift b/WatchApp Extension/Extensions/IdentifiableClass.swift deleted file mode 100644 index 91ce24e219..0000000000 --- a/WatchApp Extension/Extensions/IdentifiableClass.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// IdentifiableClass.swift -// Naterade -// -// Created by Nathan Racklyeft on 2/9/16. -// Copyright © 2016 Nathan Racklyeft. All rights reserved. -// - -import Foundation - - -protocol IdentifiableClass: class { - static var className: String { get } -} - - -extension IdentifiableClass { - static var className: String { - return NSStringFromClass(self).components(separatedBy: ".").last! - } -} diff --git a/WatchApp Extension/Extensions/UIColor.swift b/WatchApp Extension/Extensions/UIColor.swift index 02328e78ec..a53aa31cc4 100644 --- a/WatchApp Extension/Extensions/UIColor.swift +++ b/WatchApp Extension/Extensions/UIColor.swift @@ -10,22 +10,22 @@ import UIKit extension UIColor { - static let tintColor = UIColor(red: 76 / 255, green: 217 / 255, blue: 100 / 255, alpha: 1) + static let tintColor = UIColor(named: "tint")! - static let carbsColor = UIColor(red: 99 / 255, green: 218 / 255, blue: 56 / 255, alpha: 1) + static let carbsColor = UIColor(named: "carbs")! // Equivalent to carbsColor with alpha 0.14 on a black background - static let darkCarbsColor = UIColor(red: 0.07, green: 0.12, blue: 0.04, alpha: 1) + static let darkCarbsColor = UIColor(named: "carbs-dark")! - static let glucose = UIColor(red: 79 / 255, green: 173 / 255, blue: 248 / 255, alpha: 1) + static let glucose = UIColor(named: "glucose")! // Equivalent to glucoseColor with alpha 0.14 on a black background - static let darkGlucose = UIColor(red: 0.02, green: 0.10, blue: 0.14, alpha: 1) + static let darkGlucose = UIColor(named: "glucose-dark")! - static let workoutColor = glucose + static let workoutColor = UIColor(named: "workout")! // Equivalent to workoutColor with alpha 0.14 on a black background - static let darkWorkoutColor = darkGlucose + static let darkWorkoutColor = UIColor(named: "workout-dark")! static let disabledButtonColor = UIColor.gray diff --git a/WatchApp Extension/Extensions/WCSession.swift b/WatchApp Extension/Extensions/WCSession.swift index 72055d4245..af549614f6 100644 --- a/WatchApp Extension/Extensions/WCSession.swift +++ b/WatchApp Extension/Extensions/WCSession.swift @@ -6,6 +6,7 @@ // Copyright © 2016 Nathan Racklyeft. All rights reserved. // +import LoopCore import WatchConnectivity import os.log diff --git a/WatchApp Extension/Extensions/WatchContext+WatchApp.swift b/WatchApp Extension/Extensions/WatchContext+WatchApp.swift index bc38ac52c7..9a9f26e270 100644 --- a/WatchApp Extension/Extensions/WatchContext+WatchApp.swift +++ b/WatchApp Extension/Extensions/WatchContext+WatchApp.swift @@ -34,4 +34,12 @@ extension WatchContext { return HKQuantity(unit: .gram(), doubleValue: value) } + + var reservoirVolume: HKQuantity? { + guard let value = reservoir else { + return nil + } + + return HKQuantity(unit: .internationalUnit(), doubleValue: value) + } } diff --git a/WatchApp Extension/Managers/LoopDataManager.swift b/WatchApp Extension/Managers/LoopDataManager.swift index 5195e950fc..7540c28a39 100644 --- a/WatchApp Extension/Managers/LoopDataManager.swift +++ b/WatchApp Extension/Managers/LoopDataManager.swift @@ -14,6 +14,8 @@ import WatchConnectivity import os.log class LoopDataManager { + let carbStore: CarbStore + let glucoseStore: GlucoseStore var healthStore: HKHealthStore { @@ -49,9 +51,18 @@ class LoopDataManager { init(settings: LoopSettings = UserDefaults.standard.loopSettings ?? LoopSettings()) { self.settings = settings + let healthStore = HKHealthStore() + let cacheStore = PersistenceController.controllerInLocalDirectory() + + carbStore = CarbStore( + healthStore: healthStore, + cacheStore: cacheStore, + defaultAbsorptionTimes: LoopSettings.defaultCarbAbsorptionTimes, + syncVersion: 0 + ) glucoseStore = GlucoseStore( - healthStore: HKHealthStore(), - cacheStore: PersistenceController.controllerInLocalDirectory(), + healthStore: healthStore, + cacheStore: cacheStore, cacheLength: .hours(4) ) } @@ -76,10 +87,17 @@ extension LoopDataManager { activeContext?.iob = (activeContext?.iob ?? 0) + bolus.value } - func addConfirmedCarbEntry(_ entry: CarbEntryUserInfo) { - dispatchPrecondition(condition: .onQueue(.main)) - - activeContext?.cob = (activeContext?.cob ?? 0) + entry.value + func addConfirmedCarbEntry(_ entry: NewCarbEntry) { + carbStore.addCarbEntry(entry) { (result) in + switch result { + case .success(let entry): + DispatchQueue.main.async { + self.activeContext?.cob = (self.activeContext?.cob ?? 0) + entry.quantity.doubleValue(for: .gram()) + } + case .failure(let error): + self.log.error("Error adding entry to carbStore: %{public}@", String(describing: error)) + } + } } func sendDidUpdateContextNotificationIfNecessary() { diff --git a/WatchApp/Base.lproj/Interface.storyboard b/WatchApp/Base.lproj/Interface.storyboard index c5473c1cde..59b2c7ca62 100644 --- a/WatchApp/Base.lproj/Interface.storyboard +++ b/WatchApp/Base.lproj/Interface.storyboard @@ -1,12 +1,13 @@ - + + @@ -23,8 +24,8 @@ - @@ -267,12 +267,13 @@ + + + + - - - @@ -542,6 +543,41 @@ + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+ +
From 01653f4d4f45efb1564677fb8c2eed843a00e058 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 24 Jun 2019 11:01:20 -0500 Subject: [PATCH 042/132] Use build config to specify feature flags (#19) * Use build config to specify feature flags * Remove merge-feature-flag script * Use normal icon for Learn * Put EXCLUDED_SOURCE_FILE_NAMES before config override --- Common/FeatureFlags.swift | 17 +++---- Loop.xcconfig | 6 +++ Loop.xcodeproj/project.pbxproj | 89 ++++++---------------------------- Loop/FeatureFlags.plist | 8 --- Scripts/merge-feature-flags.sh | 29 ----------- WatchApp/Info.plist | 2 +- 6 files changed, 27 insertions(+), 124 deletions(-) delete mode 100644 Loop/FeatureFlags.plist delete mode 100755 Scripts/merge-feature-flags.sh diff --git a/Common/FeatureFlags.swift b/Common/FeatureFlags.swift index a2c4eb5d7d..bc27a15439 100644 --- a/Common/FeatureFlags.swift +++ b/Common/FeatureFlags.swift @@ -9,22 +9,17 @@ import Foundation -let FeatureFlags: FeatureFlagConfiguration = { - guard - let path = Bundle.main.path(forResource: "FeatureFlags", ofType: "plist"), - let data = FileManager.default.contents(atPath: path), - let configuration = try? PropertyListDecoder().decode(FeatureFlagConfiguration.self, from: data) - else { - return FeatureFlagConfiguration() - } - - return configuration -}() +let FeatureFlags = FeatureFlagConfiguration() struct FeatureFlagConfiguration: Decodable { let sensitivityOverridesEnabled: Bool fileprivate init() { + // Swift compiler config is inverse, since the default state is enabled. + #if FEATURE_OVERRIDES_DISABLED self.sensitivityOverridesEnabled = false + #else + self.sensitivityOverridesEnabled = true + #endif } } diff --git a/Loop.xcconfig b/Loop.xcconfig index 781da033a4..f2c1e9b70c 100644 --- a/Loop.xcconfig +++ b/Loop.xcconfig @@ -11,5 +11,11 @@ MAIN_APP_BUNDLE_IDENTIFIER = com.${DEVELOPMENT_TEAM}.loopkit MAIN_APP_DISPLAY_NAME = Loop +APPICON_NAME = AppIcon + +// Exclude additional assets by default, and let other configurations that provide them add the file back in +EXCLUDED_SOURCE_FILE_NAMES = AdditionalAssets.xcassets + // Optional workspace configuration overrides #include? "../LoopConfigOverride.xcconfig" + diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index ae165d93eb..e4ab8c489e 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -19,17 +19,6 @@ name = Cartfile; productName = Cartfile; }; - C189E6AF22B410CA005904DA /* Merge Feature Flags */ = { - isa = PBXAggregateTarget; - buildConfigurationList = C189E6B222B410CA005904DA /* Build configuration list for PBXAggregateTarget "Merge Feature Flags" */; - buildPhases = ( - C189E6B522B410E8005904DA /* Merge Feature Flags */, - ); - dependencies = ( - ); - name = "Merge Feature Flags"; - productName = Cartfile; - }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ @@ -352,8 +341,6 @@ 892FB4CF220402C0005293EC /* OverrideSelectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892FB4CE220402C0005293EC /* OverrideSelectionController.swift */; }; 894F71E21FFEC4D8007D365C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 894F71E11FFEC4D8007D365C /* Assets.xcassets */; }; 895FE0952201234000FCF18A /* OverrideSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 895FE0942201234000FCF18A /* OverrideSelectionViewController.swift */; }; - 89686F872297786B007443C9 /* FeatureFlags.plist in Resources */ = {isa = PBXBuildFile; fileRef = 89686F862297786B007443C9 /* FeatureFlags.plist */; }; - 89686F882297786B007443C9 /* FeatureFlags.plist in Resources */ = {isa = PBXBuildFile; fileRef = 89686F862297786B007443C9 /* FeatureFlags.plist */; }; 898ECA60218ABD17001E9D35 /* GlucoseChartScaler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898ECA5E218ABD17001E9D35 /* GlucoseChartScaler.swift */; }; 898ECA61218ABD17001E9D35 /* GlucoseChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898ECA5F218ABD17001E9D35 /* GlucoseChartData.swift */; }; 898ECA63218ABD21001E9D35 /* ComplicationChartManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898ECA62218ABD21001E9D35 /* ComplicationChartManager.swift */; }; @@ -381,7 +368,10 @@ C17824A61E1AF91F00D9D25C /* BolusRecommendation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17824A41E1AD4D100D9D25C /* BolusRecommendation.swift */; }; C1814B86225E507C008D2D8E /* Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1814B85225E507C008D2D8E /* Sequence.swift */; }; C18852E22082AB1B00BECC8C /* RileyLinkKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C18852E12082AB1A00BECC8C /* RileyLinkKitUI.framework */; }; - C189E6B922B41A62005904DA /* FeatureFlagsMerged.plist in Resources */ = {isa = PBXBuildFile; fileRef = C189E6B822B41A62005904DA /* FeatureFlagsMerged.plist */; }; + C189E6BC22B82908005904DA /* AdditionalAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C189E6BA22B81E7A005904DA /* AdditionalAssets.xcassets */; }; + C189E6BD22B8291F005904DA /* AdditionalAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C189E6BA22B81E7A005904DA /* AdditionalAssets.xcassets */; }; + C189E6C022B829C6005904DA /* AdditionalAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C189E6BA22B81E7A005904DA /* AdditionalAssets.xcassets */; }; + C189E6C122B82BE4005904DA /* AdditionalAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C189E6BA22B81E7A005904DA /* AdditionalAssets.xcassets */; }; C18C8C511D5A351900E043FB /* NightscoutDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */; }; C1C0BE2A224C0FA000C03B4D /* SwiftCharts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4346D1EF1C781BEA00ABAFE3 /* SwiftCharts.framework */; }; C1C6591C1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json in Resources */ = {isa = PBXBuildFile; fileRef = C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */; }; @@ -993,7 +983,6 @@ 892FB4CE220402C0005293EC /* OverrideSelectionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideSelectionController.swift; sourceTree = ""; }; 894F71E11FFEC4D8007D365C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 895FE0942201234000FCF18A /* OverrideSelectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverrideSelectionViewController.swift; sourceTree = ""; }; - 89686F862297786B007443C9 /* FeatureFlags.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = FeatureFlags.plist; sourceTree = ""; }; 898ECA5E218ABD17001E9D35 /* GlucoseChartScaler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseChartScaler.swift; sourceTree = ""; }; 898ECA5F218ABD17001E9D35 /* GlucoseChartData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseChartData.swift; sourceTree = ""; }; 898ECA62218ABD21001E9D35 /* ComplicationChartManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComplicationChartManager.swift; sourceTree = ""; }; @@ -1017,7 +1006,7 @@ C17824A41E1AD4D100D9D25C /* BolusRecommendation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BolusRecommendation.swift; sourceTree = ""; }; C1814B85225E507C008D2D8E /* Sequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sequence.swift; sourceTree = ""; }; C18852E12082AB1A00BECC8C /* RileyLinkKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RileyLinkKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - C189E6B822B41A62005904DA /* FeatureFlagsMerged.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = FeatureFlagsMerged.plist; sourceTree = BUILT_PRODUCTS_DIR; }; + C189E6BA22B81E7A005904DA /* AdditionalAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = AdditionalAssets.xcassets; path = ../../AdditionalAssets.xcassets; sourceTree = ""; }; C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutDataManager.swift; sourceTree = ""; }; C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_temp_basal_dropping_then_rising.json; sourceTree = ""; }; C1E2773D224177C000354103 /* ClockKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ClockKit.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk/System/Library/Frameworks/ClockKit.framework; sourceTree = DEVELOPER_DIR; }; @@ -1228,7 +1217,6 @@ 43D9FFA321EA9A0C00AF44BF /* Learn */, 968DCD53F724DE56FFE51920 /* Frameworks */, 43776F8D1B8022E90074EA36 /* Products */, - C189E6B822B41A62005904DA /* FeatureFlagsMerged.plist */, 437D9BA11D7B5203007245E8 /* Loop.xcconfig */, ); sourceTree = ""; @@ -1257,9 +1245,9 @@ 7D7076511FE06EE1004AC8EA /* InfoPlist.strings */, 43EDEE6B1CF2E12A00393BE3 /* Loop.entitlements */, 43F5C2D41B92A4A6003EB13D /* Info.plist */, - 89686F862297786B007443C9 /* FeatureFlags.plist */, 43776F8F1B8022E90074EA36 /* AppDelegate.swift */, 43776F981B8022E90074EA36 /* Assets.xcassets */, + C189E6BA22B81E7A005904DA /* AdditionalAssets.xcassets */, 43776F9A1B8022E90074EA36 /* LaunchScreen.storyboard */, 43776F951B8022E90074EA36 /* Main.storyboard */, 43E344A01B9E144300C85C07 /* Extensions */, @@ -2101,9 +2089,6 @@ CreatedOnToolsVersion = 8.1; ProvisioningStyle = Automatic; }; - C189E6AF22B410CA005904DA = { - ProvisioningStyle = Automatic; - }; }; }; buildConfigurationList = 43776F871B8022E90074EA36 /* Build configuration list for PBXProject "Loop" */; @@ -2139,7 +2124,6 @@ 43E2D8D01D20BF42004DA55F /* DoseMathTests */, 43E2D90A1D20C581004DA55F /* LoopTests */, 432CF87720D8B8380066B889 /* Cartfile */, - C189E6AF22B410CA005904DA /* Merge Feature Flags */, ); }; /* End PBXProject section */ @@ -2153,10 +2137,9 @@ 43FCBBC21E51710B00343C1B /* LaunchScreen.storyboard in Resources */, 7D70764F1FE06EE1004AC8EA /* InfoPlist.strings in Resources */, 43776F991B8022E90074EA36 /* Assets.xcassets in Resources */, + C189E6BD22B8291F005904DA /* AdditionalAssets.xcassets in Resources */, 7D7076631FE06EE4004AC8EA /* Localizable.strings in Resources */, 43776F971B8022E90074EA36 /* Main.storyboard in Resources */, - 89686F872297786B007443C9 /* FeatureFlags.plist in Resources */, - C189E6B922B41A62005904DA /* FeatureFlagsMerged.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2165,6 +2148,7 @@ buildActionMask = 2147483647; files = ( C1C73F0D1DE3D0270022FC89 /* InfoPlist.strings in Resources */, + C189E6C122B82BE4005904DA /* AdditionalAssets.xcassets in Resources */, 894F71E21FFEC4D8007D365C /* Assets.xcassets in Resources */, 43A943761B926B7B0051FA24 /* Interface.storyboard in Resources */, ); @@ -2177,8 +2161,8 @@ 7D70765E1FE06EE3004AC8EA /* Localizable.strings in Resources */, 7D70763A1FE06EDF004AC8EA /* InfoPlist.strings in Resources */, 7D70763F1FE06EDF004AC8EA /* ckcomplication.strings in Resources */, + C189E6C022B829C6005904DA /* AdditionalAssets.xcassets in Resources */, 43A943901B926B7B0051FA24 /* Assets.xcassets in Resources */, - 89686F882297786B007443C9 /* FeatureFlags.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2256,6 +2240,7 @@ 4F2C15971E09E94E00E160D4 /* HUDAssets.xcassets in Resources */, 7D70764A1FE06EE1004AC8EA /* Localizable.strings in Resources */, 7D7076451FE06EE0004AC8EA /* InfoPlist.strings in Resources */, + C189E6BC22B82908005904DA /* AdditionalAssets.xcassets in Resources */, 4F2C15951E09BF3C00E160D4 /* HUDView.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2409,27 +2394,6 @@ shellPath = /bin/sh; shellScript = "CARTHAGE_BUILD_DIR=\"${SRCROOT}/Carthage/Build\"\nif [ -n \"${IPHONEOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/iOS\"\nelif [ -n \"${WATCHOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/watchOS\"\nelse\n echo \"ERROR: Unexpected deployment target type\"\n exit 1\nfi\n\nfor SCRIPT_INPUT_FILE in ${!SCRIPT_INPUT_FILE_*}; do\n CARTHAGE_BUILD_FILE=\"${!SCRIPT_INPUT_FILE/${BUILT_PRODUCTS_DIR}/${CARTHAGE_BUILD_DIR}}\"\n if [ -e \"${CARTHAGE_BUILD_FILE}\" ]; then\n if [ -e \"${SCRIPT_INPUT_FILE}\" ]; then\n echo \"ERROR: Duplicate frameworks found at:\"\n echo \" ${SCRIPT_INPUT_FILE}\"\n echo \" ${CARTHAGE_BUILD_FILE}\"\n exit 1\n fi\n echo \"Substituting \\\"${CARTHAGE_BUILD_FILE}\\\" for \\\"${!SCRIPT_INPUT_FILE}\\\"\"\n export ${SCRIPT_INPUT_FILE}=\"${CARTHAGE_BUILD_FILE}\"\n fi\ndone\n\necho \"Copy Frameworks with Carthage\"\ncarthage copy-frameworks\n"; }; - C189E6B522B410E8005904DA /* Merge Feature Flags */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "$(SRCROOT)/../LoopFeaturesOverride.plist", - "$(SRCROOT)/Loop/FeatureFlags.plist", - ); - name = "Merge Feature Flags"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(BUILT_PRODUCTS_DIR)/FeatureFlagsMerged.plist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "${SRCROOT}/Scripts/merge-feature-flags.sh\n"; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -3300,7 +3264,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_ENTITLEMENTS = Loop/Loop.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; DEVELOPMENT_TEAM = ""; @@ -3318,7 +3282,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_ENTITLEMENTS = Loop/Loop.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; DEVELOPMENT_TEAM = ""; @@ -3381,7 +3345,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = ""; @@ -3402,7 +3366,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = ""; @@ -3755,22 +3719,6 @@ }; name = Release; }; - C189E6B322B410CA005904DA /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - C189E6B422B410CA005904DA /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -3882,15 +3830,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - C189E6B222B410CA005904DA /* Build configuration list for PBXAggregateTarget "Merge Feature Flags" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - C189E6B322B410CA005904DA /* Debug */, - C189E6B422B410CA005904DA /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ }; rootObject = 43776F841B8022E90074EA36 /* Project object */; diff --git a/Loop/FeatureFlags.plist b/Loop/FeatureFlags.plist deleted file mode 100644 index 6ca8fce59d..0000000000 --- a/Loop/FeatureFlags.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - sensitivityOverridesEnabled - - - diff --git a/Scripts/merge-feature-flags.sh b/Scripts/merge-feature-flags.sh deleted file mode 100755 index 38dabd40b4..0000000000 --- a/Scripts/merge-feature-flags.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/sh - -# merge-feature-flags.sh -# Loop -# -# Copyright © 2019 LoopKit Authors. All rights reserved. - -echo "Merging feature flags" - -MERGED_FILE=${SCRIPT_OUTPUT_FILE_0} - -if [ -e "${MERGED_FILE}" ]; then - echo "Removing old merged file" - rm ${MERGED_FILE} -fi - -for COUNTER in $(seq 0 $(($SCRIPT_INPUT_FILE_COUNT - 1))); do - SCRIPT_INPUT_FILE="SCRIPT_INPUT_FILE_${COUNTER}" - FEATURE_PLIST=${!SCRIPT_INPUT_FILE} - if [ -e "${FEATURE_PLIST}" ]; then - echo "Adding features from \"${FEATURE_PLIST}\" to \"${MERGED_FILE}\"" - echo /usr/libexec/PlistBuddy -c \"Merge ${FEATURE_PLIST}\" \"${MERGED_FILE}\" - /usr/libexec/PlistBuddy -c "Merge ${FEATURE_PLIST}" "${MERGED_FILE}" - else - echo "Skipping missing input file \"${FEATURE_PLIST}\"" - fi -done - -echo "Merged feature flags in ${MERGED_FILE}" diff --git a/WatchApp/Info.plist b/WatchApp/Info.plist index 7c85ee1001..b881f84c30 100644 --- a/WatchApp/Info.plist +++ b/WatchApp/Info.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion en CFBundleDisplayName - Loop + $(MAIN_APP_DISPLAY_NAME) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier From 10f21e3e23f537ce3e679909e9675c457b3dfe34 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 29 Jun 2019 18:03:48 -0500 Subject: [PATCH 043/132] Cleanup and add Modal Day lesson --- Learn/AppDelegate.swift | 3 +- Learn/Configuration/DateIntervalEntry.swift | 52 +++++ Learn/Extensions/DateIntervalFormatter.swift | 2 +- Learn/Extensions/OSLog.swift | 15 ++ Learn/Lessons/ModalDayLesson.swift | 212 +++++++++++++++++++ Learn/Lessons/TimeInRangeLesson.swift | 100 +++------ Learn/Managers/DayCalculator.swift | 64 ++++++ Learn/Models/TimeComponents.swift | 73 +++++++ Loop.xcodeproj/project.pbxproj | 38 +++- 9 files changed, 479 insertions(+), 80 deletions(-) create mode 100644 Learn/Configuration/DateIntervalEntry.swift create mode 100644 Learn/Extensions/OSLog.swift create mode 100644 Learn/Lessons/ModalDayLesson.swift create mode 100644 Learn/Managers/DayCalculator.swift create mode 100644 Learn/Models/TimeComponents.swift diff --git a/Learn/AppDelegate.swift b/Learn/AppDelegate.swift index 7b1a7fe712..53abb62dbb 100644 --- a/Learn/AppDelegate.swift +++ b/Learn/AppDelegate.swift @@ -26,7 +26,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { dataManager.authorize({ DispatchQueue.main.async { lessonsVC.lessons = [ - TimeInRangeLesson(dataManager: dataManager) + TimeInRangeLesson(dataManager: dataManager), + ModalDayLesson(dataManager: dataManager), ] } }) diff --git a/Learn/Configuration/DateIntervalEntry.swift b/Learn/Configuration/DateIntervalEntry.swift new file mode 100644 index 0000000000..3763b3bb9e --- /dev/null +++ b/Learn/Configuration/DateIntervalEntry.swift @@ -0,0 +1,52 @@ +// +// DateIntervalEntry.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import UIKit + + +class DateIntervalEntry: LessonSectionProviding { + let headerTitle: String? + + let footerTitle: String? + + let dateEntry: DateEntry + let numberEntry: NumberEntry + + let cells: [LessonCellProviding] + + init(headerTitle: String? = nil, footerTitle: String? = nil, start: Date, weeks: Int) { + self.headerTitle = headerTitle + self.footerTitle = footerTitle + + self.dateEntry = DateEntry(date: start, title: NSLocalizedString("Start Date", comment: "Title of config entry"), mode: .date) + self.numberEntry = NumberEntry.integerEntry(value: weeks, unitString: NSLocalizedString("Weeks", comment: "Unit string for a count of calendar weeks")) + + self.cells = [ + self.dateEntry, + self.numberEntry + ] + } +} + +extension DateIntervalEntry { + convenience init(headerTitle: String? = nil, footerTitle: String? = nil, end: Date, weeks: Int, calendar: Calendar = .current) { + let start = calendar.date(byAdding: DateComponents(weekOfYear: -weeks), to: end)! + self.init(headerTitle: headerTitle, footerTitle: footerTitle, start: calendar.startOfDay(for: start), weeks: weeks) + } + + var dateInterval: DateInterval? { + let start = dateEntry.date + + guard let weeks = numberEntry.number?.intValue, + let end = Calendar.current.date(byAdding: DateComponents(weekOfYear: weeks), to: start) + else { + return nil + } + + return DateInterval(start: start, end: end) + } +} diff --git a/Learn/Extensions/DateIntervalFormatter.swift b/Learn/Extensions/DateIntervalFormatter.swift index 33a16e4c2a..fe2ed19009 100644 --- a/Learn/Extensions/DateIntervalFormatter.swift +++ b/Learn/Extensions/DateIntervalFormatter.swift @@ -9,7 +9,7 @@ import Foundation extension DateIntervalFormatter { - convenience init(dateStyle: DateIntervalFormatter.Style, timeStyle: DateIntervalFormatter.Style) { + convenience init(dateStyle: DateIntervalFormatter.Style = .none, timeStyle: DateIntervalFormatter.Style = .none) { self.init() self.dateStyle = dateStyle self.timeStyle = timeStyle diff --git a/Learn/Extensions/OSLog.swift b/Learn/Extensions/OSLog.swift new file mode 100644 index 0000000000..0e2001d042 --- /dev/null +++ b/Learn/Extensions/OSLog.swift @@ -0,0 +1,15 @@ +// +// OSLog.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import os.log + + +extension OSLog { + convenience init(category: String) { + self.init(subsystem: "com.loopkit.Learn", category: category) + } +} diff --git a/Learn/Lessons/ModalDayLesson.swift b/Learn/Lessons/ModalDayLesson.swift new file mode 100644 index 0000000000..751ea3a471 --- /dev/null +++ b/Learn/Lessons/ModalDayLesson.swift @@ -0,0 +1,212 @@ +// +// ModalDayLesson.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import Foundation +import HealthKit +import LoopCore +import LoopKit +import os.log + +final class ModalDayLesson: Lesson { + let title = NSLocalizedString("Modal Day", comment: "Lesson title") + + let subtitle = NSLocalizedString("Visualizes the most frequent glucose values by time of day", comment: "Lesson subtitle") + + let configurationSections: [LessonSectionProviding] + + private let dataManager: DataManager + + private let dateIntervalEntry: DateIntervalEntry + + private let glucoseUnit: HKUnit + + init(dataManager: DataManager) { + self.dataManager = dataManager + self.glucoseUnit = dataManager.glucoseStore.preferredUnit ?? .milligramsPerDeciliter + + dateIntervalEntry = DateIntervalEntry( + end: Date(), + weeks: 2 + ) + + self.configurationSections = [ + dateIntervalEntry + ] + } + + func execute(completion: @escaping ([LessonSectionProviding]) -> Void) { + guard let dates = dateIntervalEntry.dateInterval else { + // TODO: Cleaner error presentation + completion([LessonSection(headerTitle: "Error: Please fill out all fields", footerTitle: nil, cells: [])]) + return + } + + let calendar = Calendar.current + + let calculator = ModalDayCalculator(dataManager: dataManager, dates: dates, bucketSize: .minutes(60), unit: glucoseUnit, calendar: calendar) + calculator.execute { (result) in + switch result { + case .failure(let error): + completion([ + LessonSection(cells: [TextCell(text: String(describing: error))]) + ]) + case .success(let buckets): + guard buckets.count > 0 else { + completion([ + LessonSection(cells: [TextCell(text: NSLocalizedString("No data available", comment: "Lesson result text for no data"))]) + ]) + return + } + + let dateFormatter = DateIntervalFormatter(timeStyle: .short) + let glucoseFormatter = QuantityFormatter() + glucoseFormatter.setPreferredNumberFormatter(for: self.glucoseUnit) + + completion([ + LessonSection(cells: buckets.compactMap({ (bucket) -> TextCell? in + guard let start = calendar.date(from: bucket.time.lowerBound.dateComponents), + let end = calendar.date(from: bucket.time.upperBound.dateComponents), + let time = dateFormatter.string(from: DateInterval(start: start, end: end)), + let median = bucket.median, + let medianString = glucoseFormatter.string(from: median, for: bucket.unit) + else { + return nil + } + + return TextCell(text: time, detailText: medianString) + })) + ]) + } + } + } +} + + +fileprivate extension TextCell { + +} + + +fileprivate struct ModalDayBucket { + let time: Range + let orderedValues: [Double] + let unit: HKUnit + + init(time: Range, unorderedValues: [Double], unit: HKUnit) { + self.time = time + self.orderedValues = unorderedValues.sorted() + self.unit = unit + } + + var median: HKQuantity? { + let count = orderedValues.count + guard count > 0 else { + return nil + } + + if count % 2 == 1 { + return HKQuantity(unit: unit, doubleValue: orderedValues[count / 2]) + } else { + let mid = count / 2 + let lower = orderedValues[mid - 1] + let upper = orderedValues[mid] + return HKQuantity(unit: unit, doubleValue: (lower + upper) / 2) + } + } +} + + +fileprivate struct ModalDayBuilder { + let calendar: Calendar + let bucketSize: TimeInterval + let unit: HKUnit + private(set) var unorderedValuesByBucket: [Range: [Double]] + + init(calendar: Calendar, bucketSize: TimeInterval, unit: HKUnit) { + self.calendar = calendar + self.bucketSize = bucketSize + self.unit = unit + self.unorderedValuesByBucket = [:] + } + + mutating func add(_ value: Double, at time: TimeComponents) { + let bucket = time.bucket(withBucketSize: bucketSize) + var values = unorderedValuesByBucket[bucket] ?? [] + values.append(value) + unorderedValuesByBucket[bucket] = values + } + + mutating func add(_ value: Double, at date: DateComponents) { + guard let time = TimeComponents(dateComponents: date) else { + return + } + add(value, at: time) + } + + mutating func add(_ value: Double, at date: Date) { + add(value, at: calendar.dateComponents([.hour, .minute], from: date)) + } + + mutating func add(_ quantity: HKQuantity, at date: Date) { + add(quantity.doubleValue(for: unit), at: date) + } + + var allBuckets: [ModalDayBucket] { + return unorderedValuesByBucket.sorted(by: { $0.0.lowerBound < $1.0.lowerBound }).map { pair -> ModalDayBucket in + return ModalDayBucket(time: pair.key, unorderedValues: pair.value, unit: unit) + } + } +} + + +fileprivate class ModalDayCalculator { + typealias ResultType = ModalDayBuilder + let calculator: DayCalculator + let bucketSize: TimeInterval + let calendar: Calendar + private let log: OSLog + + init(dataManager: DataManager, dates: DateInterval, bucketSize: TimeInterval, unit: HKUnit, calendar: Calendar) { + self.calculator = DayCalculator(dataManager: dataManager, dates: dates, initial: ModalDayBuilder(calendar: calendar, bucketSize: bucketSize, unit: unit)) + self.bucketSize = bucketSize + self.calendar = calendar + + log = OSLog(category: String(describing: type(of: self))) + } + + func execute(completion: @escaping (_ result: Result<[ModalDayBucket]>) -> Void) { + os_log(.default, log: log, "Computing Modal day in %{public}@", String(describing: calculator.dates)) + + calculator.execute(calculator: { (dataManager, day, mutableResult, completion) in + os_log(.default, log: self.log, "Fetching samples in %{public}@", String(describing: day)) + + dataManager.glucoseStore.getGlucoseSamples(start: day.start, end: day.end, completion: { (result) in + switch result { + case .failure(let error): + os_log(.error, log: self.log, "Failed to fetch samples: %{public}@", String(describing: error)) + completion(error) + case .success(let samples): + os_log(.error, log: self.log, "Found %d samples", samples.count) + + for sample in samples { + _ = mutableResult.mutate({ (result) in + result.add(sample.quantity, at: sample.startDate) + }) + } + completion(nil) + } + }) + }, completion: { (result) in + switch result { + case .failure(let error): + completion(.failure(error)) + case .success(let builder): + completion(.success(builder.allBuckets)) + } + }) + } +} diff --git a/Learn/Lessons/TimeInRangeLesson.swift b/Learn/Lessons/TimeInRangeLesson.swift index a3e3057827..add895d865 100644 --- a/Learn/Lessons/TimeInRangeLesson.swift +++ b/Learn/Lessons/TimeInRangeLesson.swift @@ -27,9 +27,7 @@ final class TimeInRangeLesson: Lesson { private let glucoseFormatter = QuantityFormatter() - private let dateEntry: DateEntry - - private let weeksEntry: NumberEntry + private let dateIntervalEntry: DateIntervalEntry private let rangeEntry: QuantityRangeEntry @@ -37,19 +35,11 @@ final class TimeInRangeLesson: Lesson { self.dataManager = dataManager self.glucoseUnit = dataManager.glucoseStore.preferredUnit ?? .milligramsPerDeciliter - let twoWeeksAgo = Calendar.current.date(byAdding: DateComponents(weekOfYear: -2), to: Date())! - glucoseFormatter.setPreferredNumberFormatter(for: glucoseUnit) - // TODO: Add a date components picker cell, and combine into a "DateIntervalEntry" section - dateEntry = DateEntry( - date: Calendar.current.startOfDay(for: twoWeeksAgo), - title: NSLocalizedString("Start Date", comment: "Title of config entry"), - mode: .date - ) - weeksEntry = NumberEntry.integerEntry( - value: 2, - unitString: NSLocalizedString("Weeks", comment: "Unit string for a count of calendar weeks") + dateIntervalEntry = DateIntervalEntry( + end: Date(), + weeks: 2 ) rangeEntry = QuantityRangeEntry.glucoseRange( @@ -59,22 +49,21 @@ final class TimeInRangeLesson: Lesson { unit: glucoseUnit) self.configurationSections = [ - LessonSection(headerTitle: nil, footerTitle: nil, cells: [dateEntry, weeksEntry]), + dateIntervalEntry, rangeEntry ] } func execute(completion: @escaping ([LessonSectionProviding]) -> Void) { - guard let weeks = weeksEntry.number?.intValue, let closedRange = rangeEntry.closedRange else { + guard let dates = dateIntervalEntry.dateInterval, let closedRange = rangeEntry.closedRange else { // TODO: Cleaner error presentation completion([LessonSection(headerTitle: "Error: Please fill out all fields", footerTitle: nil, cells: [])]) return } - let start = dateEntry.date - let calculator = TimeInRangeCalculator(dataManager: dataManager, start: start, duration: DateComponents(weekOfYear: weeks), range: closedRange) + let calculator = TimeInRangeCalculator(dataManager: dataManager, dates: dates, range: closedRange) - calculator.perform { result in + calculator.execute { result in switch result { case .failure(let error): completion([ @@ -162,82 +151,41 @@ struct TimeInRangeAggregator { /// Time-in-range, e.g. "2 weeks starting on March 5" private class TimeInRangeCalculator { - let dataManager: DataManager - let start: Date - let duration: DateComponents + let calculator: DayCalculator<[DateInterval: Double]> let range: ClosedRange - init(dataManager: DataManager, start: Date, duration: DateComponents, range: ClosedRange) { - self.dataManager = dataManager - self.start = start - self.duration = duration - self.range = range - - log = OSLog(subsystem: "com.loopkit.Learn", category: String(describing: type(of: self))) - } - private let log: OSLog private let unit = HKUnit.milligramsPerDeciliter - func perform(completion: @escaping (_ result: Result<[DateInterval: Double]>) -> Void) { - // Compute the end date - guard let end = Calendar.current.date(byAdding: duration, to: start) else { - fatalError("Unable to resolve duration: \(duration)") - } - - os_log(.default, "Computing Time in range from %{public}@ for %{public}@ between %{public}@", String(describing: start), String(describing: end), String(describing: range)) - - // Paginate into 24-hour blocks - let lockedResults = Locked([DateInterval: Double]()) - var anyError: Error? - - let group = DispatchGroup() - - var segmentStart = start - - Calendar.current.enumerateDates(startingAfter: start, matching: DateComponents(hour: 0), matchingPolicy: .nextTime) { (date, _, stop) in - guard let date = date else { - stop = true - return - } + init(dataManager: DataManager, dates: DateInterval, range: ClosedRange) { + self.calculator = DayCalculator(dataManager: dataManager, dates: dates, initial: [:]) + self.range = range - let interval = DateInterval(start: segmentStart, end: min(end, date)) + log = OSLog(category: String(describing: type(of: self))) + } - guard interval.duration > 0 else { - stop = true - return - } + func execute(completion: @escaping (_ result: Result<[DateInterval: Double]>) -> Void) { + os_log(.default, log: log, "Computing Time in range from %{public}@ between %{public}@", String(describing: calculator.dates), String(describing: range)) - os_log(.default, "Fetching samples in %{public}@", String(describing: interval)) + calculator.execute(calculator: { (dataManager, day, results, completion) in + os_log(.default, log: self.log, "Fetching samples in %{public}@", String(describing: day)) - group.enter() - dataManager.glucoseStore.getGlucoseSamples(start: interval.start, end: interval.end) { (result) in + dataManager.glucoseStore.getGlucoseSamples(start: day.start, end: day.end) { (result) in switch result { case .failure(let error): os_log(.error, log: self.log, "Failed to fetch samples: %{public}@", String(describing: error)) - anyError = error + completion(error) case .success(let samples): if let timeInRange = samples.proportion(where: { self.range.contains($0.quantity) }) { - _ = lockedResults.mutate({ (results) in - results[interval] = timeInRange + _ = results.mutate({ (results) in + results[day] = timeInRange }) } + completion(nil) } - - group.leave() - } - - segmentStart = interval.end - } - - group.notify(queue: DispatchQueue.main) { - if let error = anyError { - completion(.failure(error)) - } else { - completion(.success(lockedResults.value)) } - } + }, completion: completion) } } diff --git a/Learn/Managers/DayCalculator.swift b/Learn/Managers/DayCalculator.swift new file mode 100644 index 0000000000..f46ffc9747 --- /dev/null +++ b/Learn/Managers/DayCalculator.swift @@ -0,0 +1,64 @@ +// +// DayCalculator.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import Foundation +import LoopCore +import LoopKit + +class DayCalculator { + typealias Calculator = (_ dataManager: DataManager, _ day: DateInterval, _ results: Locked, _ completion: @escaping (_ error: Error?) -> Void) -> Void + + let dataManager: DataManager + let dates: DateInterval + private var lockedResults: Locked + + init(dataManager: DataManager, dates: DateInterval, initial: ResultType) { + self.dataManager = dataManager + self.dates = dates + self.lockedResults = Locked(initial) + } + + func execute(calculator: @escaping Calculator, completion: @escaping (_ result: Result) -> Void) { + var anyError: Error? + + let group = DispatchGroup() + + var segmentStart = dates.start + + Calendar.current.enumerateDates(startingAfter: dates.start, matching: DateComponents(hour: 0), matchingPolicy: .nextTime) { (date, exactMatch, stop) in + guard let date = date else { + stop = true + return + } + + let interval = DateInterval(start: segmentStart, end: min(dates.end, date)) + + guard interval.duration > 0 else { + stop = true + return + } + + group.enter() + calculator(dataManager, interval, lockedResults) { error in + if let error = error { + anyError = error + } + + group.leave() + } + segmentStart = interval.end + } + + group.notify(queue: .main) { + if let error = anyError { + completion(.failure(error)) + } else { + completion(.success(self.lockedResults.value)) + } + } + } +} diff --git a/Learn/Models/TimeComponents.swift b/Learn/Models/TimeComponents.swift new file mode 100644 index 0000000000..cf93a09635 --- /dev/null +++ b/Learn/Models/TimeComponents.swift @@ -0,0 +1,73 @@ +// +// TimeComponents.swift +// Learn +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import Foundation + + +struct TimeComponents: Equatable, Hashable { + let hour: Int + let minute: Int + + init(hour: Int, minute: Int) { + if hour >= 24, minute >= 60 { + assertionFailure("Invalid time components: \(hour):\(minute)") + } + + self.hour = hour + self.minute = minute + } + + init?(dateComponents: DateComponents) { + guard let hour = dateComponents.hour, let minute = dateComponents.minute else { + return nil + } + + self.init(hour: hour, minute: minute) + } + + init(timeIntervalSinceMidnight timeInterval: TimeInterval) { + self.init(hour: Int(timeInterval.hours), minute: Int(timeInterval.minutes) % 60) + } + + var dateComponents: DateComponents { + return DateComponents(hour: hour, minute: minute, second: 0) + } + + var timeIntervalSinceMidnight: TimeInterval { + return TimeInterval(hours: Double(hour)) + TimeInterval(minutes: Double(minute)) + } + + func floored(to timeInterval: TimeInterval) -> TimeComponents { + let floored = floor(timeIntervalSinceMidnight / timeInterval) * timeInterval + return TimeComponents(timeIntervalSinceMidnight: floored) + } + + func bucket(withBucketSize bucketSize: TimeInterval) -> Range { + let lowerBound = floored(to: bucketSize) + return lowerBound..<(lowerBound + bucketSize) + } + + private func adding(_ timeInterval: TimeInterval) -> TimeComponents { + return TimeComponents(timeIntervalSinceMidnight: timeIntervalSinceMidnight + timeInterval) + } +} + +extension TimeComponents: Comparable { + static func < (lhs: TimeComponents, rhs: TimeComponents) -> Bool { + if lhs.hour == rhs.hour { + return lhs.minute < rhs.minute + } else { + return lhs.hour < rhs.hour + } + } +} + +extension TimeComponents { + static func + (lhs: TimeComponents, rhs: TimeInterval) -> TimeComponents { + return lhs.adding(rhs) + } +} diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 14faea5845..aa5dd96bdc 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -175,6 +175,12 @@ 43C2FAE11EB656A500364AFF /* GlucoseEffectVelocity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C2FAE01EB656A500364AFF /* GlucoseEffectVelocity.swift */; }; 43C3B6EC20B650A80026CAFA /* SettingsImageTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C3B6EB20B650A80026CAFA /* SettingsImageTableViewCell.swift */; }; 43C513191E864C4E001547C7 /* GlucoseRangeSchedule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C513181E864C4E001547C7 /* GlucoseRangeSchedule.swift */; }; + 43C5F257222C7B7200905D10 /* TimeComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C5F256222C7B7200905D10 /* TimeComponents.swift */; }; + 43C5F258222C7BD400905D10 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D9FFA421EA9A0C00AF44BF /* AppDelegate.swift */; }; + 43C5F25A222C921B00905D10 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C5F259222C921B00905D10 /* OSLog.swift */; }; + 43C728F5222266F000C62969 /* ModalDayLesson.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C728F4222266F000C62969 /* ModalDayLesson.swift */; }; + 43C728F72222700000C62969 /* DateIntervalEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C728F62222700000C62969 /* DateIntervalEntry.swift */; }; + 43C728F9222A448700C62969 /* DayCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C728F8222A448700C62969 /* DayCalculator.swift */; }; 43CA93371CB98079000026B5 /* MinimedKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43CA93361CB98079000026B5 /* MinimedKit.framework */; }; 43CB2B2B1D924D450079823D /* WCSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CB2B2A1D924D450079823D /* WCSession.swift */; }; 43CE7CDE1CA8B63E003CC1B0 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CE7CDD1CA8B63E003CC1B0 /* Data.swift */; }; @@ -196,7 +202,6 @@ 43D9F82021EF0906000578CD /* NSNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D9F81F21EF0906000578CD /* NSNumber.swift */; }; 43D9F82221EF0A7A000578CD /* QuantityRangeEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D9F82121EF0A7A000578CD /* QuantityRangeEntry.swift */; }; 43D9F82421EFF1AB000578CD /* LessonResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D9F82321EFF1AB000578CD /* LessonResultsViewController.swift */; }; - 43D9FFA521EA9A0C00AF44BF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D9FFA421EA9A0C00AF44BF /* AppDelegate.swift */; }; 43D9FFAA21EA9A0C00AF44BF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 43D9FFA821EA9A0C00AF44BF /* Main.storyboard */; }; 43D9FFAC21EA9A0F00AF44BF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 43D9FFAB21EA9A0F00AF44BF /* Assets.xcassets */; }; 43D9FFAF21EA9A0F00AF44BF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 43D9FFAD21EA9A0F00AF44BF /* LaunchScreen.storyboard */; }; @@ -263,6 +268,9 @@ 43FCEEB9221BCF790013DD30 /* GlucoseChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43FCEEB8221BCF790013DD30 /* GlucoseChart.swift */; }; 43FCEEBB22211C860013DD30 /* CarbEffectChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43FCEEBA22211C860013DD30 /* CarbEffectChart.swift */; }; 43FCEEBD22212DD50013DD30 /* PredictedGlucoseChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43FCEEBC22212DD50013DD30 /* PredictedGlucoseChart.swift */; }; + 43FCEEBE22220CE70013DD30 /* LoopKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 437AFEE6203688CF008C4892 /* LoopKitUI.framework */; }; + 43FCEEBF22220CF30013DD30 /* LoopKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 437AFEE6203688CF008C4892 /* LoopKitUI.framework */; }; + 43FCEEC022220D1F0013DD30 /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43F78D4B1C914197002152D1 /* LoopKit.framework */; }; 4D3B40041D4A9E1A00BC6334 /* G4ShareSpy.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D3B40021D4A9DFE00BC6334 /* G4ShareSpy.framework */; }; 4F08DE8F1E7BB871006741EA /* CollectionType+Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F08DE8E1E7BB871006741EA /* CollectionType+Loop.swift */; }; 4F08DE9B1E7BC4ED006741EA /* SwiftCharts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4346D1EF1C781BEA00ABAFE3 /* SwiftCharts.framework */; }; @@ -685,6 +693,11 @@ 43C3B6EB20B650A80026CAFA /* SettingsImageTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsImageTableViewCell.swift; sourceTree = ""; }; 43C3B6F620BBCAA30026CAFA /* PumpManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpManager.swift; sourceTree = ""; }; 43C513181E864C4E001547C7 /* GlucoseRangeSchedule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseRangeSchedule.swift; sourceTree = ""; }; + 43C5F256222C7B7200905D10 /* TimeComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeComponents.swift; sourceTree = ""; }; + 43C5F259222C921B00905D10 /* OSLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = ""; }; + 43C728F4222266F000C62969 /* ModalDayLesson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalDayLesson.swift; sourceTree = ""; }; + 43C728F62222700000C62969 /* DateIntervalEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateIntervalEntry.swift; sourceTree = ""; }; + 43C728F8222A448700C62969 /* DayCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayCalculator.swift; sourceTree = ""; }; 43C98058212A799E003B5D17 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Intents.strings; sourceTree = ""; }; 43CA93361CB98079000026B5 /* MinimedKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MinimedKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 43CB2B2A1D924D450079823D /* WCSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WCSession.swift; sourceTree = ""; }; @@ -1104,9 +1117,11 @@ C168C40621B0D53E00ADE90E /* MinimedKit.framework in Frameworks */, C168C40821B0D53E00ADE90E /* MinimedKitUI.framework in Frameworks */, 43D9000B21EB0BE000AF44BF /* LoopCore.framework in Frameworks */, + 43FCEEBF22220CF30013DD30 /* LoopKitUI.framework in Frameworks */, C1C0BE2A224C0FA000C03B4D /* SwiftCharts.framework in Frameworks */, 4F7528951DFE1E9B00C322D6 /* LoopUI.framework in Frameworks */, 437AFEE520352591008C4892 /* NotificationCenter.framework in Frameworks */, + 43FCEEC022220D1F0013DD30 /* LoopKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1117,6 +1132,7 @@ 437AFEE8203689FE008C4892 /* LoopKit.framework in Frameworks */, 4FB76FB01E8C3E8000B39636 /* SwiftCharts.framework in Frameworks */, 43C05CBA21EBEAD8006FB252 /* LoopCore.framework in Frameworks */, + 43FCEEBE22220CE70013DD30 /* LoopKitUI.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1299,6 +1315,7 @@ 43C05CB421EBE274006FB252 /* Date.swift */, 4345E3FD21F04A50009E00E5 /* DateIntervalFormatter.swift */, 43D9F81F21EF0906000578CD /* NSNumber.swift */, + 43C5F259222C921B00905D10 /* OSLog.swift */, 43D9F81921EC593C000578CD /* UITableViewCell.swift */, C1814B85225E507C008D2D8E /* Sequence.swift */, ); @@ -1318,6 +1335,7 @@ 43C05CBE21EBFF66006FB252 /* Lessons */ = { isa = PBXGroup; children = ( + 43C728F4222266F000C62969 /* ModalDayLesson.swift */, 43C05CB021EBBDB9006FB252 /* TimeInRangeLesson.swift */, ); path = Lessons; @@ -1330,10 +1348,19 @@ 43C05CC921EC382B006FB252 /* NumberEntry.swift */, 43D9F81D21EF0609000578CD /* NumberRangeEntry.swift */, 43D9F82121EF0A7A000578CD /* QuantityRangeEntry.swift */, + 43C728F62222700000C62969 /* DateIntervalEntry.swift */, ); path = Configuration; sourceTree = ""; }; + 43C5F255222C7B6300905D10 /* Models */ = { + isa = PBXGroup; + children = ( + 43C5F256222C7B7200905D10 /* TimeComponents.swift */, + ); + path = Models; + sourceTree = ""; + }; 43D9003A21EB281300AF44BF /* Insulin */ = { isa = PBXGroup; children = ( @@ -1354,6 +1381,7 @@ 43C05CB321EBE268006FB252 /* Extensions */, 43C05CBE21EBFF66006FB252 /* Lessons */, 43D9FFBE21EAB20B00AF44BF /* Managers */, + 43C5F255222C7B6300905D10 /* Models */, 43C05CBB21EBF743006FB252 /* View Controllers */, 43D9FFB521EA9B0100AF44BF /* Learn.entitlements */, 43D9FFA821EA9A0C00AF44BF /* Main.storyboard */, @@ -1368,6 +1396,7 @@ isa = PBXGroup; children = ( 43D9FFBF21EAB22E00AF44BF /* DataManager.swift */, + 43C728F8222A448700C62969 /* DayCalculator.swift */, ); path = Managers; sourceTree = ""; @@ -2550,15 +2579,20 @@ files = ( 43C05CBD21EBF77D006FB252 /* LessonsViewController.swift in Sources */, 43C05CB621EBE321006FB252 /* NSTimeInterval.swift in Sources */, + 43C5F25A222C921B00905D10 /* OSLog.swift in Sources */, 43C05CB521EBE274006FB252 /* Date.swift in Sources */, 43D9F82421EFF1AB000578CD /* LessonResultsViewController.swift in Sources */, + 43C728F9222A448700C62969 /* DayCalculator.swift in Sources */, 4345E3FA21F0473B009E00E5 /* TextCell.swift in Sources */, + 43C728F5222266F000C62969 /* ModalDayLesson.swift in Sources */, 43D9F81821EC51CC000578CD /* DateEntry.swift in Sources */, 43D9FFC021EAB22E00AF44BF /* DataManager.swift in Sources */, 43C05CB121EBBDB9006FB252 /* TimeInRangeLesson.swift in Sources */, + 43C728F72222700000C62969 /* DateIntervalEntry.swift in Sources */, 43D9F81E21EF0609000578CD /* NumberRangeEntry.swift in Sources */, 43C05CCA21EC382B006FB252 /* NumberEntry.swift in Sources */, 4345E3FE21F04A50009E00E5 /* DateIntervalFormatter.swift in Sources */, + 43C5F257222C7B7200905D10 /* TimeComponents.swift in Sources */, 4345E3F821F03D2A009E00E5 /* DatesAndNumberCell.swift in Sources */, 43D9F82221EF0A7A000578CD /* QuantityRangeEntry.swift in Sources */, 43D9F81A21EC593C000578CD /* UITableViewCell.swift in Sources */, @@ -2567,7 +2601,7 @@ 43C05CC621EC29E7006FB252 /* TextFieldTableViewCell.swift in Sources */, 43C05CC021EBFFA4006FB252 /* Lesson.swift in Sources */, C1814B86225E507C008D2D8E /* Sequence.swift in Sources */, - 43D9FFA521EA9A0C00AF44BF /* AppDelegate.swift in Sources */, + 43C5F258222C7BD400905D10 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From e2c6bd571ce7d21bae840ae59ff450e5e0a4ee6a Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 29 Jun 2019 20:46:58 -0500 Subject: [PATCH 044/132] Update dependency revisions --- Cartfile.resolved | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index dc9bc6387f..2b758e8595 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,8 +1,8 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" -github "LoopKit/CGMBLEKit" "4194dbdabd4a2df390f41ada25bb99941fd165ca" -github "LoopKit/G4ShareSpy" "4e4ea119d34d65cee35ccab9df0259e1dee52460" -github "LoopKit/LoopKit" "346f5e85940fb56211035ae2e9f522512f17d0c0" -github "LoopKit/dexcom-share-client-swift" "2179b253800dc527391ac582bdc9a0a7622e17c1" +github "LoopKit/CGMBLEKit" "c4d73c4646591e43362f012b6a1b229a76d082ba" +github "LoopKit/G4ShareSpy" "f6ae0cf1753131135f26046ddf07e096da884b84" +github "LoopKit/LoopKit" "4eca69b9f2e3f1fc74e5b62484db00777c002cb7" +github "LoopKit/dexcom-share-client-swift" "cdbb1cd19cf54b37add6d6c25300445a6abe24da" github "i-schuetz/SwiftCharts" "0.6.5" github "maxkonovalov/MKRingProgressView" "2.2.2" github "ps2/rileylink_ios" "f616a671bac254b25c5e298f01824200944c2267" From a80978582f16e847f196cb405260a20aecc5013a Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 30 Jun 2019 00:35:51 -0500 Subject: [PATCH 045/132] Do not create testingScenariosManager in release --- Loop/Managers/DeviceDataManager.swift | 7 +++++-- .../View Controllers/StatusTableViewController.swift | 12 ++++++++---- .../TestingScenariosTableViewController.swift | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index d3f9547b18..c9cee04829 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -27,7 +27,7 @@ final class DeviceDataManager { private var nightscoutDataManager: NightscoutDataManager! - private(set) var testingScenariosManager: TestingScenariosManager! + private(set) var testingScenariosManager: TestingScenariosManager? /// The last error recorded by a device manager /// Should be accessed only on the main queue @@ -93,7 +93,10 @@ final class DeviceDataManager { ) watchManager = WatchDataManager(deviceManager: self) nightscoutDataManager = NightscoutDataManager(deviceDataManager: self) - testingScenariosManager = LocalTestingScenariosManager(deviceManager: self) + + if debugEnabled { + testingScenariosManager = LocalTestingScenariosManager(deviceManager: self) + } loopManager.delegate = self loopManager.carbStore.syncDelegate = remoteDataManager.nightscoutService.uploader diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index 230f7994f5..926f240454 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -1272,7 +1272,7 @@ final class StatusTableViewController: ChartsTableViewController { // MARK: - Testing scenarios override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) { - if debugEnabled, !deviceManager.testingScenariosManager.scenarioURLs.isEmpty { + if let testingScenariosManager = deviceManager.testingScenariosManager, testingScenariosManager.scenarioURLs.isEmpty { if motion == .motionShake { presentScenarioSelector() } @@ -1280,7 +1280,11 @@ final class StatusTableViewController: ChartsTableViewController { } private func presentScenarioSelector() { - let vc = TestingScenariosTableViewController(scenariosManager: deviceManager.testingScenariosManager) + guard let testingScenariosManager = deviceManager.testingScenariosManager else { + return + } + + let vc = TestingScenariosTableViewController(scenariosManager: testingScenariosManager) present(UINavigationController(rootViewController: vc), animated: true) } @@ -1298,11 +1302,11 @@ final class StatusTableViewController: ChartsTableViewController { } @objc private func stepActiveScenarioForward() { - deviceManager.testingScenariosManager.stepActiveScenarioForward { _ in } + deviceManager.testingScenariosManager?.stepActiveScenarioForward { _ in } } @objc private func stepActiveScenarioBackward() { - deviceManager.testingScenariosManager.stepActiveScenarioBackward { _ in } + deviceManager.testingScenariosManager?.stepActiveScenarioBackward { _ in } } } diff --git a/Loop/View Controllers/TestingScenariosTableViewController.swift b/Loop/View Controllers/TestingScenariosTableViewController.swift index f9bd437740..62af0632e0 100644 --- a/Loop/View Controllers/TestingScenariosTableViewController.swift +++ b/Loop/View Controllers/TestingScenariosTableViewController.swift @@ -56,7 +56,7 @@ final class TestingScenariosTableViewController: RadioSelectionTableViewControll contextHelp = "The scenarios directory location is available in the debug output of the Xcode console." if let activeScenarioURL = scenariosManager.activeScenarioURL { - selectedIndex = scenarioURLs.index(of: activeScenarioURL) + selectedIndex = scenarioURLs.firstIndex(of: activeScenarioURL) } updateLoadButtonEnabled() From 76e0bac970ee5e2421dd820f8751e0c15dd5f30c Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 30 Jun 2019 20:11:40 -0500 Subject: [PATCH 046/132] Refactor Retrospective Correction to allow for future enhancements --- Loop.xcodeproj/project.pbxproj | 16 +++++ Loop/Managers/LoopDataManager.swift | 54 ++++++++------ Loop/Models/LoopSettings+Loop.swift | 8 +++ .../RetrospectiveCorrection.swift | 36 ++++++++++ .../StandardRetrospectiveCorrection.swift | 72 +++++++++++++++++++ .../PredictionTableViewController.swift | 55 ++++---------- LoopCore/LoopSettings.swift | 14 +--- LoopCore/NSUserDefaults.swift | 3 +- 8 files changed, 182 insertions(+), 76 deletions(-) create mode 100644 Loop/Models/RetrospectiveCorrection/RetrospectiveCorrection.swift create mode 100644 Loop/Models/RetrospectiveCorrection/StandardRetrospectiveCorrection.swift diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 12548e6dad..b0288e5650 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -76,6 +76,8 @@ 4346D1E71C77F5FE00ABAFE3 /* ChartTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4346D1E61C77F5FE00ABAFE3 /* ChartTableViewCell.swift */; }; 434FB6461D68F1CD007B9C70 /* Amplitude.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 434FB6451D68F1CD007B9C70 /* Amplitude.framework */; }; 434FF1EE1CF27EEF000DB779 /* UITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 434FF1ED1CF27EEF000DB779 /* UITableViewCell.swift */; }; + 43511CE221FD80E400566C63 /* RetrospectiveCorrection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43511CDF21FD80E400566C63 /* RetrospectiveCorrection.swift */; }; + 43511CE321FD80E400566C63 /* StandardRetrospectiveCorrection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43511CE021FD80E400566C63 /* StandardRetrospectiveCorrection.swift */; }; 43511CEE220FC61700566C63 /* HUDRowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43511CED220FC61700566C63 /* HUDRowController.swift */; }; 43523EDB1CC35083001850F1 /* RileyLinkKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43523EDA1CC35083001850F1 /* RileyLinkKit.framework */; }; 435400311C9F744E00D5819C /* BolusSuggestionUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435400301C9F744E00D5819C /* BolusSuggestionUserInfo.swift */; }; @@ -617,6 +619,8 @@ 434FB6451D68F1CD007B9C70 /* Amplitude.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Amplitude.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; + 43511CDF21FD80E400566C63 /* RetrospectiveCorrection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RetrospectiveCorrection.swift; sourceTree = ""; }; + 43511CE021FD80E400566C63 /* StandardRetrospectiveCorrection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StandardRetrospectiveCorrection.swift; sourceTree = ""; }; 43511CED220FC61700566C63 /* HUDRowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUDRowController.swift; sourceTree = ""; }; 43523EDA1CC35083001850F1 /* RileyLinkKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RileyLinkKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 435400301C9F744E00D5819C /* BolusSuggestionUserInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BolusSuggestionUserInfo.swift; sourceTree = ""; }; @@ -1202,9 +1206,19 @@ path = Display; sourceTree = ""; }; + 43511CDD21FD80AD00566C63 /* RetrospectiveCorrection */ = { + isa = PBXGroup; + children = ( + 43511CDF21FD80E400566C63 /* RetrospectiveCorrection.swift */, + 43511CE021FD80E400566C63 /* StandardRetrospectiveCorrection.swift */, + ); + path = RetrospectiveCorrection; + sourceTree = ""; + }; 43757D131C06F26C00910CB9 /* Models */ = { isa = PBXGroup; children = ( + 43511CDD21FD80AD00566C63 /* RetrospectiveCorrection */, 43880F961D9D8052009061A8 /* ServiceAuthentication */, C17824A41E1AD4D100D9D25C /* BolusRecommendation.swift */, 43C2FAE01EB656A500364AFF /* GlucoseEffectVelocity.swift */, @@ -2458,6 +2472,7 @@ 43F64DD91D9C92C900D24DC6 /* TitleSubtitleTableViewCell.swift in Sources */, C15713821DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift in Sources */, 43FCEEA9221A615B0013DD30 /* StatusChartsManager.swift in Sources */, + 43511CE321FD80E400566C63 /* StandardRetrospectiveCorrection.swift in Sources */, 435400321C9F745500D5819C /* BolusSuggestionUserInfo.swift in Sources */, 43E3449F1B9D68E900C85C07 /* StatusTableViewController.swift in Sources */, 43DBF0531C93EC8200B3C386 /* DeviceDataManager.swift in Sources */, @@ -2518,6 +2533,7 @@ 4372E490213CFCE70068E043 /* LoopSettingsUserInfo.swift in Sources */, 89CA2B3D226E6B13004D9350 /* LocalTestingScenariosManager.swift in Sources */, 43F78D261C8FC000002152D1 /* DoseMath.swift in Sources */, + 43511CE221FD80E400566C63 /* RetrospectiveCorrection.swift in Sources */, 438D42F91D7C88BC003244B0 /* PredictionInputEffect.swift in Sources */, 892A5D692230C41D008961AB /* RangeReplaceableCollection.swift in Sources */, 4F70C2101DE8FAC5006380B7 /* StatusExtensionDataManager.swift in Sources */, diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 4c3931e5b8..348f9be214 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -81,6 +81,8 @@ final class LoopDataManager { glucoseStore = GlucoseStore(healthStore: healthStore, cacheStore: cacheStore, cacheLength: .hours(24)) + retrospectiveCorrection = settings.enabledRetrospectiveCorrectionAlgorithm + overrideHistory.delegate = self cacheStore.delegate = self @@ -218,6 +220,9 @@ final class LoopDataManager { } } + // Confined to dataAccessQueue + private var retrospectiveCorrection: RetrospectiveCorrection + // MARK: - Background task management private var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid @@ -647,7 +652,7 @@ extension LoopDataManager { throw LoopError.missingDataError(.glucose) } - let retrospectiveStart = lastGlucoseDate.addingTimeInterval(-settings.retrospectiveCorrectionIntegrationInterval) + let retrospectiveStart = lastGlucoseDate.addingTimeInterval(-retrospectiveCorrection.retrospectionInterval) let earliestEffectDate = Date(timeIntervalSinceNow: .hours(-24)) let nextEffectDate = insulinCounteractionEffects.last?.endDate ?? earliestEffectDate @@ -827,39 +832,38 @@ extension LoopDataManager { return prediction } - /// Generates an effect based on how large the discrepancy is between the current glucose and its predicted value. + /// Generates a correction effect based on how large the discrepancy is between the current glucose and its model predicted value. /// - /// - Parameter effectDuration: The length of time to extend the effect /// - Throws: LoopError.missingDataError - private func updateRetrospectiveGlucoseEffect(effectDuration: TimeInterval = TimeInterval(minutes: 60)) throws { + private func updateRetrospectiveGlucoseEffect() throws { dispatchPrecondition(condition: .onQueue(dataAccessQueue)) + // Get carb effects, otherwise clear effect and throw error guard let carbEffects = self.carbEffect else { retrospectiveGlucoseDiscrepancies = nil retrospectiveGlucoseEffect = [] throw LoopError.missingDataError(.carbEffect) } - retrospectiveGlucoseDiscrepancies = insulinCounteractionEffects.subtracting(carbEffects, withUniformInterval: carbStore.delta) - - // Our last change should be recent, otherwise clear the effects - guard let discrepancy = retrospectiveGlucoseDiscrepanciesSummed?.last, - Date().timeIntervalSince(discrepancy.endDate) <= settings.recencyInterval - else { - retrospectiveGlucoseEffect = [] - return - } - + // Get most recent glucose, otherwise clear effect and throw error guard let glucose = self.glucoseStore.latestGlucose else { retrospectiveGlucoseEffect = [] throw LoopError.missingDataError(.glucose) } - let unit = HKUnit.milligramsPerDeciliter - let discrepancyTime = max(discrepancy.endDate.timeIntervalSince(discrepancy.startDate), settings.retrospectiveCorrectionGroupingInterval) - let velocity = HKQuantity(unit: unit.unitDivided(by: .second()), doubleValue: discrepancy.quantity.doubleValue(for: unit) / discrepancyTime) + // Get timeline of glucose discrepancies + retrospectiveGlucoseDiscrepancies = insulinCounteractionEffects.subtracting(carbEffects, withUniformInterval: carbStore.delta) - retrospectiveGlucoseEffect = glucose.decayEffect(atRate: velocity, for: effectDuration) + // Calculate retrospective correction + retrospectiveGlucoseEffect = retrospectiveCorrection.computeEffect( + startingAt: glucose, + retrospectiveGlucoseDiscrepanciesSummed: retrospectiveGlucoseDiscrepanciesSummed, + recencyInterval: settings.recencyInterval, + insulinSensitivitySchedule: insulinSensitivitySchedule, + basalRateSchedule: basalRateSchedule, + glucoseCorrectionRangeSchedule: settings.glucoseTargetRangeSchedule, + retrospectiveCorrectionGroupingInterval: settings.retrospectiveCorrectionGroupingInterval + ) } /// Runs the glucose prediction on the latest effect data. @@ -1026,6 +1030,9 @@ protocol LoopState { /// The difference in predicted vs actual glucose over a recent period var retrospectiveGlucoseDiscrepancies: [GlucoseChange]? { get } + /// The total corrective glucose effect from retrospective correction + var totalRetrospectiveCorrection: HKQuantity? { get } + /// Calculates a new prediction from the current data using the specified effect inputs /// /// This method is intended for visualization purposes only, not dosing calculation. No validation of input data is done. @@ -1087,6 +1094,11 @@ extension LoopDataManager { return loopDataManager.retrospectiveGlucoseDiscrepanciesSummed } + var totalRetrospectiveCorrection: HKQuantity? { + dispatchPrecondition(condition: .onQueue(loopDataManager.dataAccessQueue)) + return loopDataManager.retrospectiveCorrection.totalGlucoseCorrectionEffect + } + func predictGlucose(using inputs: PredictionInputEffect) throws -> [GlucoseValue] { return try loopDataManager.predictGlucose(using: inputs) } @@ -1158,7 +1170,7 @@ extension LoopDataManager { "retrospectiveGlucoseDiscrepancies: [", "* GlucoseEffect(start, mg/dL)", - (manager.retrospectiveGlucoseDiscrepancies ?? []).reduce(into: "", { (entries, entry) in + (state.retrospectiveGlucoseDiscrepancies ?? []).reduce(into: "", { (entries, entry) in entries.append("* \(entry.startDate), \(entry.quantity.doubleValue(for: .milligramsPerDeciliter))\n") }), "]", @@ -1182,6 +1194,8 @@ extension LoopDataManager { "", "cacheStore: \(String(reflecting: self.glucoseStore.cacheStore))", "", + String(reflecting: self.retrospectiveCorrection), + "", ] self.glucoseStore.generateDiagnosticReport { (report) in @@ -1206,7 +1220,7 @@ extension LoopDataManager { extension Notification.Name { - static let LoopDataUpdated = Notification.Name(rawValue: "com.loudnate.Naterade.notification.LoopDataUpdated") + static let LoopDataUpdated = Notification.Name(rawValue: "com.loudnate.Naterade.notification.LoopDataUpdated") static let LoopRunning = Notification.Name(rawValue: "com.loudnate.Naterade.notification.LoopRunning") } diff --git a/Loop/Models/LoopSettings+Loop.swift b/Loop/Models/LoopSettings+Loop.swift index eaf569fdb0..d13509b051 100644 --- a/Loop/Models/LoopSettings+Loop.swift +++ b/Loop/Models/LoopSettings+Loop.swift @@ -5,6 +5,7 @@ // Copyright © 2018 LoopKit Authors. All rights reserved. // +import Foundation import LoopCore // MARK: - Static configuration @@ -16,4 +17,11 @@ extension LoopSettings { } return inputs } + + static let retrospectiveCorrectionEffectDuration = TimeInterval(hours: 1) + + /// Creates an instance of the enabled retrospective correction implementation + var enabledRetrospectiveCorrectionAlgorithm: RetrospectiveCorrection { + return StandardRetrospectiveCorrection(effectDuration: LoopSettings.retrospectiveCorrectionEffectDuration) + } } diff --git a/Loop/Models/RetrospectiveCorrection/RetrospectiveCorrection.swift b/Loop/Models/RetrospectiveCorrection/RetrospectiveCorrection.swift new file mode 100644 index 0000000000..c232721151 --- /dev/null +++ b/Loop/Models/RetrospectiveCorrection/RetrospectiveCorrection.swift @@ -0,0 +1,36 @@ +// +// RetrospectiveCorrection.swift +// Loop +// +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import Foundation +import HealthKit +import LoopKit + + +/// Derives a continued glucose effect from recent prediction discrepancies +protocol RetrospectiveCorrection: CustomDebugStringConvertible { + /// The maximum interval of historical glucose discrepancies that should be provided to the computation + var retrospectionInterval: TimeInterval { get } + + /// Overall retrospective correction effect + var totalGlucoseCorrectionEffect: HKQuantity? { get } + + /// Calculates overall correction effect based on timeline of discrepancies, and updates glucoseCorrectionEffect + /// + /// - Parameters: + /// - glucose: Most recent glucose + /// - retrospectiveGlucoseDiscrepanciesSummed: Timeline of past discepancies + /// - Returns: Glucose correction effects + func computeEffect( + startingAt startingGlucose: GlucoseValue, + retrospectiveGlucoseDiscrepanciesSummed: [GlucoseChange]?, + recencyInterval: TimeInterval, + insulinSensitivitySchedule: InsulinSensitivitySchedule?, + basalRateSchedule: BasalRateSchedule?, + glucoseCorrectionRangeSchedule: GlucoseRangeSchedule?, + retrospectiveCorrectionGroupingInterval: TimeInterval + ) -> [GlucoseEffect] +} diff --git a/Loop/Models/RetrospectiveCorrection/StandardRetrospectiveCorrection.swift b/Loop/Models/RetrospectiveCorrection/StandardRetrospectiveCorrection.swift new file mode 100644 index 0000000000..64619d7a07 --- /dev/null +++ b/Loop/Models/RetrospectiveCorrection/StandardRetrospectiveCorrection.swift @@ -0,0 +1,72 @@ +// +// StandardRetrospectiveCorrection.swift +// Loop +// +// Created by Dragan Maksimovic on 10/27/18. +// Copyright © 2018 LoopKit Authors. All rights reserved. +// + +import Foundation +import HealthKit +import LoopKit + +/** + Standard Retrospective Correction (RC) calculates a correction effect in glucose prediction based on the most recent discrepancy between observed glucose movement and movement expected based on insulin and carb models. Standard retrospective correction acts as a proportional (P) controller aimed at reducing modeling errors in glucose prediction. + + In the above summary, "discrepancy" is a difference between the actual glucose and the model predicted glucose over retrospective correction grouping interval (set to 30 min in LoopSettings) + */ +class StandardRetrospectiveCorrection: RetrospectiveCorrection { + let retrospectionInterval = TimeInterval(minutes: 30) + + /// RetrospectiveCorrection protocol variables + /// Standard effect duration + let effectDuration: TimeInterval + /// Overall retrospective correction effect + var totalGlucoseCorrectionEffect: HKQuantity? + + /// All math is performed with glucose expressed in mg/dL + private let unit = HKUnit.milligramsPerDeciliter + + init(effectDuration: TimeInterval) { + self.effectDuration = effectDuration + } + + func computeEffect( + startingAt startingGlucose: GlucoseValue, + retrospectiveGlucoseDiscrepanciesSummed: [GlucoseChange]?, + recencyInterval: TimeInterval, + insulinSensitivitySchedule: InsulinSensitivitySchedule?, + basalRateSchedule: BasalRateSchedule?, + glucoseCorrectionRangeSchedule: GlucoseRangeSchedule?, + retrospectiveCorrectionGroupingInterval: TimeInterval + ) -> [GlucoseEffect] { + // Last discrepancy should be recent, otherwise clear the effect and return + let glucoseDate = startingGlucose.startDate + guard let currentDiscrepancy = retrospectiveGlucoseDiscrepanciesSummed?.last, + glucoseDate.timeIntervalSince(currentDiscrepancy.endDate) <= recencyInterval + else { + totalGlucoseCorrectionEffect = nil + return [] + } + + // Standard retrospective correction math + let currentDiscrepancyValue = currentDiscrepancy.quantity.doubleValue(for: unit) + totalGlucoseCorrectionEffect = HKQuantity(unit: unit, doubleValue: currentDiscrepancyValue) + + let retrospectionTimeInterval = currentDiscrepancy.endDate.timeIntervalSince(currentDiscrepancy.startDate) + let discrepancyTime = max(retrospectionTimeInterval, retrospectiveCorrectionGroupingInterval) + let velocity = HKQuantity(unit: unit.unitDivided(by: .second()), doubleValue: currentDiscrepancyValue / discrepancyTime) + + // Update array of glucose correction effects + return startingGlucose.decayEffect(atRate: velocity, for: effectDuration) + } + + var debugDescription: String { + let report: [String] = [ + "## StandardRetrospectiveCorrection", + "" + ] + + return report.joined(separator: "\n") + } +} diff --git a/Loop/View Controllers/PredictionTableViewController.swift b/Loop/View Controllers/PredictionTableViewController.swift index 01996aa6d9..b1801d5c83 100644 --- a/Loop/View Controllers/PredictionTableViewController.swift +++ b/Loop/View Controllers/PredictionTableViewController.swift @@ -68,6 +68,8 @@ class PredictionTableViewController: ChartsTableViewController, IdentifiableClas private var retrospectiveGlucoseDiscrepancies: [GlucoseChange]? + private var totalRetrospectiveCorrection: HKQuantity? + private var refreshContext = RefreshContext.all private var chartStartDate: Date { @@ -105,6 +107,7 @@ class PredictionTableViewController: ChartsTableViewController, IdentifiableClas let reloadGroup = DispatchGroup() var glucoseValues: [StoredGlucoseSample]? + var totalRetrospectiveCorrection: HKQuantity? if self.refreshContext.remove(.glucose) != nil { reloadGroup.enter() @@ -119,6 +122,7 @@ class PredictionTableViewController: ChartsTableViewController, IdentifiableClas reloadGroup.enter() self.deviceManager.loopManager.getLoopState { (manager, state) in self.retrospectiveGlucoseDiscrepancies = state.retrospectiveGlucoseDiscrepancies + totalRetrospectiveCorrection = state.totalRetrospectiveCorrection self.glucoseChart.setPredictedGlucoseValues(state.predictedGlucose ?? []) do { @@ -148,6 +152,10 @@ class PredictionTableViewController: ChartsTableViewController, IdentifiableClas } self.charts.invalidateChart(atIndex: 0) + if let totalRetrospectiveCorrection = totalRetrospectiveCorrection { + self.totalRetrospectiveCorrection = totalRetrospectiveCorrection + } + self.charts.prerender() self.tableView.beginUpdates() @@ -173,12 +181,9 @@ class PredictionTableViewController: ChartsTableViewController, IdentifiableClas // MARK: - UITableViewDataSource - private enum Section: Int { + private enum Section: Int, CaseIterable { case charts case inputs - case settings - - static let count = 3 } private var eventualGlucoseDescription: String? @@ -188,7 +193,7 @@ class PredictionTableViewController: ChartsTableViewController, IdentifiableClas private var selectedInputs = PredictionInputEffect.all override func numberOfSections(in tableView: UITableView) -> Int { - return Section.count + return Section.allCases.count } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { @@ -197,8 +202,6 @@ class PredictionTableViewController: ChartsTableViewController, IdentifiableClas return 1 case .inputs: return availableInputs.count - case .settings: - return 1 } } @@ -221,17 +224,6 @@ class PredictionTableViewController: ChartsTableViewController, IdentifiableClas case .inputs: let cell = tableView.dequeueReusableCell(withIdentifier: PredictionInputEffectTableViewCell.className, for: indexPath) as! PredictionInputEffectTableViewCell self.tableView(tableView, updateTextFor: cell, at: indexPath) - return cell - case .settings: - let cell = tableView.dequeueReusableCell(withIdentifier: SwitchTableViewCell.className, for: indexPath) as! SwitchTableViewCell - - cell.titleLabel?.text = NSLocalizedString("Enable Retrospective Correction", comment: "Title of the switch which toggles retrospective correction effects") - cell.subtitleLabel?.text = NSLocalizedString("This will more aggresively increase or decrease basal delivery when glucose movement doesn't match the carbohydrate and insulin-based model.", comment: "The description of the switch which toggles retrospective correction effects") - cell.`switch`?.isOn = deviceManager.loopManager.settings.retrospectiveCorrectionEnabled - cell.`switch`?.addTarget(self, action: #selector(retrospectiveCorrectionSwitchChanged(_:)), for: .valueChanged) - - cell.contentView.layoutMargins.left = tableView.separatorInset.left - return cell } } @@ -257,7 +249,6 @@ class PredictionTableViewController: ChartsTableViewController, IdentifiableClas cell.titleLabel?.text = input.localizedTitle cell.accessoryType = selectedInputs.contains(input) ? .checkmark : .none - cell.enabled = input != .retrospection || deviceManager.loopManager.settings.retrospectiveCorrectionEnabled var subtitleText = input.localizedDescription(forGlucoseUnit: glucoseChart.glucoseUnit) ?? "" @@ -270,35 +261,27 @@ class PredictionTableViewController: ChartsTableViewController, IdentifiableClas let predicted = HKQuantity(unit: glucoseChart.glucoseUnit, doubleValue: currentGlucose.quantity.doubleValue(for: glucoseChart.glucoseUnit) - lastDiscrepancy.quantity.doubleValue(for: glucoseChart.glucoseUnit)) var values = [predicted, currentGlucose.quantity].map { formatter.string(from: $0, for: glucoseChart.glucoseUnit) ?? "?" } formatter.numberFormatter.positivePrefix = formatter.numberFormatter.plusSign - values.append(formatter.string(from: lastDiscrepancy.quantity, for: glucoseChart.glucoseUnit) ?? "?" ) + values.append(formatter.string(from: lastDiscrepancy.quantity, for: glucoseChart.glucoseUnit) ?? "?") let retro = String( format: NSLocalizedString("prediction-description-retrospective-correction", comment: "Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference)"), values[0], values[1], values[2] ) + // Standard retrospective correction subtitleText = String(format: "%@\n%@", subtitleText, retro) } cell.subtitleLabel?.text = subtitleText } - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - switch Section(rawValue: section)! { - case .settings: - return NSLocalizedString("Algorithm Settings", comment: "The title of the section containing algorithm settings") - default: - return nil - } - } - // MARK: - UITableViewDelegate override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { switch Section(rawValue: indexPath.section)! { case .charts: return 275 - case .inputs, .settings: + case .inputs: return 60 } } @@ -320,16 +303,4 @@ class PredictionTableViewController: ChartsTableViewController, IdentifiableClas refreshContext.update(with: .status) reloadData() } - - // MARK: - Actions - - @objc private func retrospectiveCorrectionSwitchChanged(_ sender: UISwitch) { - deviceManager.loopManager.settings.retrospectiveCorrectionEnabled = sender.isOn - - if let row = availableInputs.index(where: { $0 == .retrospection }), - let cell = tableView.cellForRow(at: IndexPath(row: row, section: Section.inputs.rawValue)) as? PredictionInputEffectTableViewCell - { - cell.enabled = self.deviceManager.loopManager.settings.retrospectiveCorrectionEnabled - } - } } diff --git a/LoopCore/LoopSettings.swift b/LoopCore/LoopSettings.swift index 31cb152c70..11d39d9de8 100644 --- a/LoopCore/LoopSettings.swift +++ b/LoopCore/LoopSettings.swift @@ -31,14 +31,11 @@ public struct LoopSettings: Equatable { public var suspendThreshold: GlucoseThreshold? = nil - public var retrospectiveCorrectionEnabled = true + public let retrospectiveCorrectionEnabled = true /// The interval over which to aggregate changes in glucose for retrospective correction public let retrospectiveCorrectionGroupingInterval = TimeInterval(minutes: 30) - /// The maximum duration over which to integrate retrospective correction changes - public let retrospectiveCorrectionIntegrationInterval = TimeInterval(minutes: 30) - /// The amount of time since a given date that data should be considered valid public let recencyInterval = TimeInterval(minutes: 15) @@ -55,15 +52,13 @@ public struct LoopSettings: Equatable { glucoseTargetRangeSchedule: GlucoseRangeSchedule? = nil, maximumBasalRatePerHour: Double? = nil, maximumBolus: Double? = nil, - suspendThreshold: GlucoseThreshold? = nil, - retrospectiveCorrectionEnabled: Bool = true + suspendThreshold: GlucoseThreshold? = nil ) { self.dosingEnabled = dosingEnabled self.glucoseTargetRangeSchedule = glucoseTargetRangeSchedule self.maximumBasalRatePerHour = maximumBasalRatePerHour self.maximumBolus = maximumBolus self.suspendThreshold = suspendThreshold - self.retrospectiveCorrectionEnabled = retrospectiveCorrectionEnabled } } @@ -193,17 +188,12 @@ extension LoopSettings: RawRepresentable { if let rawThreshold = rawValue["minimumBGGuard"] as? GlucoseThreshold.RawValue { self.suspendThreshold = GlucoseThreshold(rawValue: rawThreshold) } - - if let retrospectiveCorrectionEnabled = rawValue["retrospectiveCorrectionEnabled"] as? Bool { - self.retrospectiveCorrectionEnabled = retrospectiveCorrectionEnabled - } } public var rawValue: RawValue { var raw: RawValue = [ "version": LoopSettings.version, "dosingEnabled": dosingEnabled, - "retrospectiveCorrectionEnabled": retrospectiveCorrectionEnabled, "overridePresets": overridePresets.map { $0.rawValue } ] diff --git a/LoopCore/NSUserDefaults.swift b/LoopCore/NSUserDefaults.swift index d83a2423ff..54db2d46c5 100644 --- a/LoopCore/NSUserDefaults.swift +++ b/LoopCore/NSUserDefaults.swift @@ -114,8 +114,7 @@ extension UserDefaults { glucoseTargetRangeSchedule: glucoseTargetRangeSchedule, maximumBasalRatePerHour: maximumBasalRatePerHour, maximumBolus: maximumBolus, - suspendThreshold: suspendThreshold, - retrospectiveCorrectionEnabled: bool(forKey: "com.loudnate.Loop.RetrospectiveCorrectionEnabled") + suspendThreshold: suspendThreshold ) self.loopSettings = settings From b6b27d66765a1f0246eddc7ccbe526004bf558e5 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 18 Jul 2019 16:41:05 -0500 Subject: [PATCH 047/132] Bump CGMBLEKit and LoopKit revs --- Cartfile.resolved | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 2b758e8595..506474a660 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,7 +1,7 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" -github "LoopKit/CGMBLEKit" "c4d73c4646591e43362f012b6a1b229a76d082ba" +github "LoopKit/CGMBLEKit" "fe92d93c24d18ff9755ff027e9b036d3769a8a0a" github "LoopKit/G4ShareSpy" "f6ae0cf1753131135f26046ddf07e096da884b84" -github "LoopKit/LoopKit" "4eca69b9f2e3f1fc74e5b62484db00777c002cb7" +github "LoopKit/LoopKit" "f417b72562eb966ae13a29f440f3b147326b385d" github "LoopKit/dexcom-share-client-swift" "cdbb1cd19cf54b37add6d6c25300445a6abe24da" github "i-schuetz/SwiftCharts" "0.6.5" github "maxkonovalov/MKRingProgressView" "2.2.2" From 324ef2f6f601f7199d94b8a35b4971ae03fc8c2a Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 18 Jul 2019 19:43:53 -0500 Subject: [PATCH 048/132] Use new ISF and Correction Range interfaces with guardrails --- .../SettingsTableViewController.swift | 87 +++++++++---------- LoopCore/LoopSettings.swift | 27 +++++- 2 files changed, 67 insertions(+), 47 deletions(-) diff --git a/Loop/View Controllers/SettingsTableViewController.swift b/Loop/View Controllers/SettingsTableViewController.swift index 9f103843e0..06825eb8a8 100644 --- a/Loop/View Controllers/SettingsTableViewController.swift +++ b/Loop/View Controllers/SettingsTableViewController.swift @@ -239,10 +239,13 @@ final class SettingsTableViewController: UITableViewController { if let insulinSensitivitySchedule = dataManager.loopManager.insulinSensitivitySchedule { let unit = insulinSensitivitySchedule.unit - let value = valueNumberFormatter.string(from: insulinSensitivitySchedule.averageQuantity().doubleValue(for: unit)) ?? SettingsTableViewCell.NoValueString - - configCell.detailTextLabel?.text = String(format: NSLocalizedString("%1$@ %2$@/U", comment: "Format string for insulin sensitivity average (1: value)(2: glucose unit)"), value, unit.localizedShortUnitString - ) + // Schedule is in mg/dL or mmol/L, but we display as mg/dL/U or mmol/L/U + let average = insulinSensitivitySchedule.averageQuantity().doubleValue(for: unit) + let unitPerU = unit.unitDivided(by: .internationalUnit()) + let averageQuantity = HKQuantity(unit: unitPerU, doubleValue: average) + let formatter = QuantityFormatter() + formatter.setPreferredNumberFormatter(for: unit) + configCell.detailTextLabel?.text = formatter.string(from: averageQuantity, for: unitPerU) } else { configCell.detailTextLabel?.text = SettingsTableViewCell.TapToSetString } @@ -470,29 +473,22 @@ final class SettingsTableViewController: UITableViewController { show(scheduleVC, sender: sender) case .insulinSensitivity: - let scheduleVC = DailyQuantityScheduleTableViewController() + let unit = dataManager.loopManager.insulinSensitivitySchedule?.unit ?? dataManager.loopManager.glucoseStore.preferredUnit ?? HKUnit.milligramsPerDeciliter + let allowedSensitivityValues = dataManager.loopManager.settings.allowedSensitivityValues(for: unit) + let scheduleVC = InsulinSensitivityScheduleViewController(allowedValues: allowedSensitivityValues, unit: unit) scheduleVC.delegate = self + scheduleVC.insulinSensitivityScheduleStorageDelegate = self scheduleVC.title = NSLocalizedString("Insulin Sensitivities", comment: "The title of the insulin sensitivities schedule screen") - if let schedule = dataManager.loopManager.insulinSensitivitySchedule { - scheduleVC.timeZone = schedule.timeZone - scheduleVC.scheduleItems = schedule.items - scheduleVC.unit = schedule.unit - - show(scheduleVC, sender: sender) - } else { - if let timeZone = dataManager.pumpManager?.status.timeZone { - scheduleVC.timeZone = timeZone - } + scheduleVC.schedule = dataManager.loopManager.insulinSensitivitySchedule - if let unit = dataManager.loopManager.glucoseStore.preferredUnit { - scheduleVC.unit = unit - self.show(scheduleVC, sender: sender) - } - } + show(scheduleVC, sender: sender) case .glucoseTargetRange: - let scheduleVC = GlucoseRangeScheduleTableViewController() + let unit = dataManager.loopManager.settings.glucoseTargetRangeSchedule?.unit ?? dataManager.loopManager.glucoseStore.preferredUnit ?? HKUnit.milligramsPerDeciliter + let allowedCorrectionRangeValues = dataManager.loopManager.settings.allowedCorrectionRangeValues(for: unit) + let scheduleVC = GlucoseRangeScheduleTableViewController(allowedValues: allowedCorrectionRangeValues, unit: unit) + if FeatureFlags.sensitivityOverridesEnabled { scheduleVC.overrideContexts.remove(.legacyWorkout) } @@ -501,23 +497,13 @@ final class SettingsTableViewController: UITableViewController { scheduleVC.title = NSLocalizedString("Correction Range", comment: "The title of the glucose target range schedule screen") if let schedule = dataManager.loopManager.settings.glucoseTargetRangeSchedule { - scheduleVC.timeZone = schedule.timeZone - scheduleVC.scheduleItems = schedule.items - scheduleVC.unit = schedule.unit - scheduleVC.overrideRanges[.preMeal] = dataManager.loopManager.settings.preMealTargetRange.filter { !$0.isZero } - scheduleVC.overrideRanges[.legacyWorkout] = dataManager.loopManager.settings.legacyWorkoutTargetRange.filter { !$0.isZero } - - show(scheduleVC, sender: sender) - } else { - if let timeZone = dataManager.pumpManager?.status.timeZone { - scheduleVC.timeZone = timeZone - } - - if let unit = dataManager.loopManager.glucoseStore.preferredUnit { - scheduleVC.unit = unit - self.show(scheduleVC, sender: sender) - } + var overrides: [TemporaryScheduleOverride.Context: DoubleRange] = [:] + overrides[.preMeal] = dataManager.loopManager.settings.preMealTargetRange.filter { !$0.isZero } + overrides[.legacyWorkout] = dataManager.loopManager.settings.legacyWorkoutTargetRange.filter { !$0.isZero } + scheduleVC.setSchedule(schedule, withOverrideRanges: overrides) } + + show(scheduleVC, sender: sender) case .suspendThreshold: if let minBGGuard = dataManager.loopManager.settings.suspendThreshold { let vc = GlucoseThresholdTableViewController(threshold: minBGGuard.value, glucoseUnit: minBGGuard.unit) @@ -750,12 +736,6 @@ extension SettingsTableViewController: DailyValueScheduleTableViewControllerDele switch sections[indexPath.section] { case .configuration: switch ConfigurationRow(rawValue: indexPath.row)! { - case .glucoseTargetRange: - if let controller = controller as? GlucoseRangeScheduleTableViewController { - dataManager.loopManager.settings.preMealTargetRange = controller.overrideRanges[.preMeal].filter { !$0.isZero } - dataManager.loopManager.settings.legacyWorkoutTargetRange = controller.overrideRanges[.legacyWorkout].filter { !$0.isZero } - dataManager.loopManager.settings.glucoseTargetRangeSchedule = GlucoseRangeSchedule(unit: controller.unit, dailyItems: controller.scheduleItems, timeZone: controller.timeZone) - } case .basalRate: if let controller = controller as? BasalScheduleTableViewController { dataManager.loopManager.basalRateSchedule = BasalRateSchedule(dailyItems: controller.scheduleItems, timeZone: controller.timeZone) @@ -766,9 +746,6 @@ extension SettingsTableViewController: DailyValueScheduleTableViewControllerDele case .carbRatio: dataManager.loopManager.carbRatioSchedule = CarbRatioSchedule(unit: controller.unit, dailyItems: controller.scheduleItems, timeZone: controller.timeZone) AnalyticsManager.shared.didChangeCarbRatioSchedule() - case .insulinSensitivity: - dataManager.loopManager.insulinSensitivitySchedule = InsulinSensitivitySchedule(unit: controller.unit, dailyItems: controller.scheduleItems, timeZone: controller.timeZone) - AnalyticsManager.shared.didChangeInsulinSensitivitySchedule() default: break } @@ -782,6 +759,24 @@ extension SettingsTableViewController: DailyValueScheduleTableViewControllerDele } } +extension SettingsTableViewController: GlucoseRangeScheduleStorageDelegate { + func saveSchedule(for viewController: GlucoseRangeScheduleTableViewController, completion: @escaping (SaveGlucoseRangeScheduleResult) -> Void) { + dataManager.loopManager.settings.preMealTargetRange = viewController.overrideRanges[.preMeal].filter { !$0.isZero } + dataManager.loopManager.settings.legacyWorkoutTargetRange = viewController.overrideRanges[.legacyWorkout].filter { !$0.isZero } + dataManager.loopManager.settings.glucoseTargetRangeSchedule = viewController.schedule + completion(.success) + tableView.reloadRows(at: [IndexPath(row: ConfigurationRow.glucoseTargetRange.rawValue, section: Section.configuration.rawValue)], with: .none) + } +} + +extension SettingsTableViewController: InsulinSensitivityScheduleStorageDelegate { + func saveSchedule(_ schedule: InsulinSensitivitySchedule, for viewController: InsulinSensitivityScheduleViewController, completion: @escaping (SaveInsulinSensitivityScheduleResult) -> Void) { + dataManager.loopManager.insulinSensitivitySchedule = schedule + AnalyticsManager.shared.didChangeInsulinSensitivitySchedule() + completion(.success) + tableView.reloadRows(at: [IndexPath(row: ConfigurationRow.insulinSensitivity.rawValue, section: Section.configuration.rawValue)], with: .none) + } +} extension SettingsTableViewController: InsulinModelSettingsViewControllerDelegate { func insulinModelSettingsViewControllerDidChangeValue(_ controller: InsulinModelSettingsViewController) { diff --git a/LoopCore/LoopSettings.swift b/LoopCore/LoopSettings.swift index 31cb152c70..daefa83cee 100644 --- a/LoopCore/LoopSettings.swift +++ b/LoopCore/LoopSettings.swift @@ -6,7 +6,7 @@ // import LoopKit - +import HealthKit public struct LoopSettings: Equatable { public var dosingEnabled = false @@ -50,6 +50,31 @@ public struct LoopSettings: Equatable { public let statusChartMinimumHistoryDisplay: TimeInterval = .hours(1) + // MARK - Guardrails + + public func allowedSensitivityValues(for unit: HKUnit) -> [Double] { + switch unit { + case HKUnit.milligramsPerDeciliter: + return (10...500).map { Double($0) } + case HKUnit.millimolesPerLiter: + return (6...270).map { Double($0) / 10.0 } + default: + return [] + } + } + + public func allowedCorrectionRangeValues(for unit: HKUnit) -> [Double] { + switch unit { + case HKUnit.milligramsPerDeciliter: + return (60...180).map { Double($0) } + case HKUnit.millimolesPerLiter: + return (33...100).map { Double($0) / 10.0 } + default: + return [] + } + } + + public init( dosingEnabled: Bool = false, glucoseTargetRangeSchedule: GlucoseRangeSchedule? = nil, From 21ecb97506c988a91aaa67cfcb310555757ff760 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 19 Jul 2019 17:13:03 -0500 Subject: [PATCH 049/132] Bump LoopKit rev --- Cartfile.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 506474a660..68fd97ae8f 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,7 +1,7 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "fe92d93c24d18ff9755ff027e9b036d3769a8a0a" github "LoopKit/G4ShareSpy" "f6ae0cf1753131135f26046ddf07e096da884b84" -github "LoopKit/LoopKit" "f417b72562eb966ae13a29f440f3b147326b385d" +github "LoopKit/LoopKit" "422112acfe850df28ff826519646d4c2be73f5d0" github "LoopKit/dexcom-share-client-swift" "cdbb1cd19cf54b37add6d6c25300445a6abe24da" github "i-schuetz/SwiftCharts" "0.6.5" github "maxkonovalov/MKRingProgressView" "2.2.2" From 7ed319460363b709b2f5db71e02ee54dbecdeb4c Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 20 Jul 2019 08:05:52 -0500 Subject: [PATCH 050/132] Omnipod testing integrate (#997) * Omnikit * Add OmniKit frameworks * fix cartfile * Track lastAddedPumpData, including lastAddedPumpEvents * Fix carthage copy frameworks script * rebase * update deps * Initialize setupViewController with existing pump settings * Suspend/Resume * Use normal separator behavior * Reload pump section after adding pumpmanager * LoopKit PumpManagerStatus updates * PumpManagerUI vended HUD views * Add missing file * Update to LoopKit naming changes * Push instead of present pumpmanager vended vc in response to HUD view tap * Add prep_workspace.sh script * HUDProvider updates * Fix hud clearing after removing pump manager. Show boluses that take > 5 minutes * PumpManager returns DoseEntry for requested bolus, including duration * Example jumpstart * Suspend/Resume with new PumpManager status updates * Fix issue with DeviceDataManager missing from issue report * Handle LoopKit and rileylink_ios updates * Cleanup testing code * Update Loop today widget when HUD views change * Renaming updates from LoopKit * Updates for recent LoopKit pumpmanager-changes branch * update prep script for CGMBLEKit branch change * Updates for hud provider delegate change * Pumpmanager updates * Update prep script * Fix permissions on prep workspace script * Pumpmanager changes * Update to pumpmanager-updates * Back out DEVELOPMENT_TEAM settings * Context object does not need HUDProvider reference * Include dose in enactBolus completion * Add omnikit back to Loop * Bump dependency revisions * Update HUD set of views only on PumpManager change * Remove unused file * synchronize deviceDataManager.pumpManagerStatus access * Bump dep revisions * Remove unused files * Remove unneeded reloadSections calls * Add script for workspace prep * Bump rileylink_ios rev * Link in OmniKit frameworks * Fix build issue when carthage is run from Xcode * Only allow BLE to trigger looping every 5 minutes * Use bolus end date to clear lastRequestedBolus * present instead of show settings vc * small refactorings * Improvements from @mpangburn's review * Update active flag on HUDProvider when hud is visible and app is active * Remove signing team changes * HUDProvider active -> visible * Update to dev LoopKit * Update to dev LoopKit * Limit recommended temp basals to supported rates. * Unset DEVELOPMENT_TEAM * Remove lockedSupportedTempBasalRates * Update to new GlucoseRangeSchedule.Override initializer signature * Copy LoopTestingKit framework to Loop app bundle * update swift charts for Xcode 10.2 * Updated cartfile.resolved for swift charts 0.6.5 * Handle notifications scheduled by DeviceManagers * Checkout notification-identifiers branch of LoopKit * Perform UNUserNotificationCenter calls on main queue * Use omnipod-testing branches * Bump rileylink_ios/omnipod-testing rev * include pump manufacturer and model in the pump status * only upload the reservoir units if they're current * Bump rileylink_ios rev * Fix initial checkout branch for workspace builds * Fix project file * Update dependency revisions, remove conflict markers * Add dismiss button for max bolus error --- Cartfile | 5 +-- Cartfile.resolved | 2 +- Common/Models/PumpManager.swift | 2 + Loop.xcodeproj/project.pbxproj | 41 +++++++++++++++++++ Loop/Managers/DeviceDataManager.swift | 17 +++++++- Loop/Managers/NightscoutDataManager.swift | 11 ++++- .../BolusViewController.swift | 5 +++ prep_workspace.sh | 28 +++++++++++++ 8 files changed, 104 insertions(+), 7 deletions(-) create mode 100755 prep_workspace.sh diff --git a/Cartfile b/Cartfile index 67bf550a95..d6b4d7647b 100644 --- a/Cartfile +++ b/Cartfile @@ -1,8 +1,7 @@ github "LoopKit/LoopKit" "dev" github "LoopKit/CGMBLEKit" "dev" +github "i-schuetz/SwiftCharts" == 0.6.5 github "LoopKit/dexcom-share-client-swift" "dev" github "LoopKit/G4ShareSpy" "dev" -github "ps2/rileylink_ios" "dev" -github "i-schuetz/SwiftCharts" == 0.6.5 +github "ps2/rileylink_ios" "omnipod-testing-integrate" github "LoopKit/Amplitude-iOS" "decreepify" -github "maxkonovalov/MKRingProgressView" ~> 2.2 diff --git a/Cartfile.resolved b/Cartfile.resolved index 68fd97ae8f..51eac3d356 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -5,4 +5,4 @@ github "LoopKit/LoopKit" "422112acfe850df28ff826519646d4c2be73f5d0" github "LoopKit/dexcom-share-client-swift" "cdbb1cd19cf54b37add6d6c25300445a6abe24da" github "i-schuetz/SwiftCharts" "0.6.5" github "maxkonovalov/MKRingProgressView" "2.2.2" -github "ps2/rileylink_ios" "f616a671bac254b25c5e298f01824200944c2267" +github "ps2/rileylink_ios" "b7b1c44e52166244a0583a781f1b42e5e75ad838" diff --git a/Common/Models/PumpManager.swift b/Common/Models/PumpManager.swift index 6bc58bb7ec..c1bccf2ab2 100644 --- a/Common/Models/PumpManager.swift +++ b/Common/Models/PumpManager.swift @@ -8,10 +8,12 @@ import Foundation import LoopKit import MinimedKit +import OmniKit import MockKit let allPumpManagers: [PumpManager.Type] = [ + OmnipodPumpManager.self, MinimedPumpManager.self, MockPumpManager.self, ] diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index b0288e5650..f15f99c1a9 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -366,6 +366,8 @@ 89E26800229267DF00A3F2AF /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89E267FE229267DF00A3F2AF /* Optional.swift */; }; C10428971D17BAD400DD539A /* NightscoutUploadKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C10428961D17BAD400DD539A /* NightscoutUploadKit.framework */; }; C10B28461EA9BA5E006EA1FC /* far_future_high_bg_forecast.json in Resources */ = {isa = PBXBuildFile; fileRef = C10B28451EA9BA5E006EA1FC /* far_future_high_bg_forecast.json */; }; + C110A0E7221BC2400016560B /* OmniKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C7F1BF220D675700689850 /* OmniKit.framework */; }; + C110A0E8221BC2430016560B /* OmniKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C7F1C0220D675700689850 /* OmniKitUI.framework */; }; C11C87DE1E21EAAD00BB71D3 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F526D5E1DF2459000A04910 /* HKUnit.swift */; }; C12F21A71DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json in Resources */ = {isa = PBXBuildFile; fileRef = C12F21A61DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json */; }; C13255D6223E7BE2008AF50C /* BolusProgressTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C1F8B1DB223862D500DD66CF /* BolusProgressTableViewCell.xib */; }; @@ -386,6 +388,8 @@ C1C0BE2A224C0FA000C03B4D /* SwiftCharts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4346D1EF1C781BEA00ABAFE3 /* SwiftCharts.framework */; }; C1C6591C1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json in Resources */ = {isa = PBXBuildFile; fileRef = C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */; }; C1C73F0D1DE3D0270022FC89 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1C73F0F1DE3D0270022FC89 /* InfoPlist.strings */; }; + C1C7F1C1220D675800689850 /* OmniKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C7F1BF220D675700689850 /* OmniKit.framework */; }; + C1C7F1C2220D675800689850 /* OmniKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C7F1C0220D675700689850 /* OmniKitUI.framework */; }; C1E2773E224177C000354103 /* ClockKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1E2773D224177C000354103 /* ClockKit.framework */; }; C1E2774822433D7A00354103 /* MKRingProgressView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1E2774722433D7A00354103 /* MKRingProgressView.framework */; }; C1F8B243223E73FD00DD66CF /* BolusProgressTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F8B1D122375E4200DD66CF /* BolusProgressTableViewCell.swift */; }; @@ -487,6 +491,13 @@ remoteGlobalIDString = 43D9FFCE21EAE05D00AF44BF; remoteInfo = LoopCore; }; + C110A0E9221BC29B0016560B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 43776F841B8022E90074EA36 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 432CF87720D8B8380066B889; + remoteInfo = Cartfile; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -1026,6 +1037,8 @@ C189E6BA22B81E7A005904DA /* AdditionalAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = AdditionalAssets.xcassets; path = ../../AdditionalAssets.xcassets; sourceTree = ""; }; C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutDataManager.swift; sourceTree = ""; }; C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_temp_basal_dropping_then_rising.json; sourceTree = ""; }; + C1C7F1BF220D675700689850 /* OmniKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OmniKit.framework; path = Carthage/Build/iOS/OmniKit.framework; sourceTree = ""; }; + C1C7F1C0220D675700689850 /* OmniKitUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OmniKitUI.framework; path = Carthage/Build/iOS/OmniKitUI.framework; sourceTree = ""; }; C1E2773D224177C000354103 /* ClockKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ClockKit.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk/System/Library/Frameworks/ClockKit.framework; sourceTree = DEVELOPER_DIR; }; C1E2774722433D7A00354103 /* MKRingProgressView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MKRingProgressView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C1F8B1D122375E4200DD66CF /* BolusProgressTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusProgressTableViewCell.swift; sourceTree = ""; }; @@ -1053,6 +1066,7 @@ 892A5D2C222EF60A008961AB /* MockKitUI.framework in Frameworks */, 4F7528941DFE1E9500C322D6 /* LoopUI.framework in Frameworks */, 434FB6461D68F1CD007B9C70 /* Amplitude.framework in Frameworks */, + C1C7F1C1220D675800689850 /* OmniKit.framework in Frameworks */, 43A8EC6F210E622700A81379 /* CGMBLEKitUI.framework in Frameworks */, 438A95A81D8B9B24009D12E1 /* CGMBLEKit.framework in Frameworks */, 43C246A81D89990F0031F8D1 /* Crypto.framework in Frameworks */, @@ -1061,6 +1075,7 @@ 43F78D4F1C914197002152D1 /* LoopKit.framework in Frameworks */, 43CA93371CB98079000026B5 /* MinimedKit.framework in Frameworks */, 43709AEA20DF3F8200F941B3 /* MinimedKitUI.framework in Frameworks */, + C1C7F1C2220D675800689850 /* OmniKitUI.framework in Frameworks */, C10428971D17BAD400DD539A /* NightscoutUploadKit.framework in Frameworks */, 437AFEE42035252A008C4892 /* RileyLinkBLEKit.framework in Frameworks */, 43523EDB1CC35083001850F1 /* RileyLinkKit.framework in Frameworks */, @@ -1140,6 +1155,8 @@ C1C0BE2A224C0FA000C03B4D /* SwiftCharts.framework in Frameworks */, 4F7528951DFE1E9B00C322D6 /* LoopUI.framework in Frameworks */, 437AFEE520352591008C4892 /* NotificationCenter.framework in Frameworks */, + C110A0E7221BC2400016560B /* OmniKit.framework in Frameworks */, + C110A0E8221BC2430016560B /* OmniKitUI.framework in Frameworks */, 43FCEEC022220D1F0013DD30 /* LoopKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1766,6 +1783,8 @@ 968DCD53F724DE56FFE51920 /* Frameworks */ = { isa = PBXGroup; children = ( + C1C7F1BF220D675700689850 /* OmniKit.framework */, + C1C7F1C0220D675700689850 /* OmniKitUI.framework */, 434FB6451D68F1CD007B9C70 /* Amplitude.framework */, 4344628420A7A3BE00C4BE6F /* CGMBLEKit.framework */, 438A95A71D8B9B24009D12E1 /* CGMBLEKit.framework */, @@ -1996,6 +2015,7 @@ buildRules = ( ); dependencies = ( + C110A0EA221BC29B0016560B /* PBXTargetDependency */, 43D9000D21EB0BEA00AF44BF /* PBXTargetDependency */, 4F7528991DFE1ED800C322D6 /* PBXTargetDependency */, ); @@ -2113,10 +2133,12 @@ 43E2D8D01D20BF42004DA55F = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 0800; + ProvisioningStyle = Automatic; }; 43E2D90A1D20C581004DA55F = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 0800; + ProvisioningStyle = Automatic; TestTargetID = 43776F8B1B8022E90074EA36; }; 4F70C1DB1DE8DCA7006380B7 = { @@ -2361,6 +2383,8 @@ "$(BUILT_PRODUCTS_DIR)/Crypto.framework", "$(BUILT_PRODUCTS_DIR)/G4ShareSpy.framework", "$(BUILT_PRODUCTS_DIR)/RileyLinkKitUI.framework", + "$(BUILT_PRODUCTS_DIR)/OmniKit.framework", + "$(BUILT_PRODUCTS_DIR)/OmniKitUI.framework", "$(BUILT_PRODUCTS_DIR)/LoopKitUI.framework", "$(BUILT_PRODUCTS_DIR)/MinimedKitUI.framework", "$(BUILT_PRODUCTS_DIR)/CGMBLEKitUI.framework", @@ -2834,6 +2858,11 @@ target = 43D9FFCE21EAE05D00AF44BF /* LoopCore */; targetProxy = A942E446225FD9A300DD4980 /* PBXContainerItemProxy */; }; + C110A0EA221BC29B0016560B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 432CF87720D8B8380066B889 /* Cartfile */; + targetProxy = C110A0E9221BC29B0016560B /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -3627,12 +3656,15 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = DoseMathTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.loudnate.DoseMathTests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; }; name = Debug; }; @@ -3640,12 +3672,15 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = DoseMathTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.loudnate.DoseMathTests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; }; name = Release; }; @@ -3654,12 +3689,15 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = LoopTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.loudnate.LoopTests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Loop.app/Loop"; }; name = Debug; @@ -3669,12 +3707,15 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = LoopTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.loudnate.LoopTests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Loop.app/Loop"; }; name = Release; diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index c9cee04829..5aa360ef1f 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -43,6 +43,8 @@ final class DeviceDataManager { } } + private var lastBLEDrivenUpdate = Date.distantPast + // MARK: - Pump var pumpManager: PumpManagerUI? { @@ -196,11 +198,15 @@ extension DeviceDataManager: DeviceManagerDelegate { trigger: trigger ) - UNUserNotificationCenter.current().add(request) + DispatchQueue.main.async { + UNUserNotificationCenter.current().add(request) + } } func clearNotification(for manager: DeviceManager, identifier: String) { - UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [identifier]) + DispatchQueue.main.async { + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [identifier]) + } } } @@ -277,6 +283,13 @@ extension DeviceDataManager: PumpManagerDelegate { dispatchPrecondition(condition: .onQueue(queue)) log.default("PumpManager:\(type(of: pumpManager)) did fire BLE heartbeat") + let bleHeartbeatUpdateInterval = TimeInterval(minutes: 4.5) + guard lastBLEDrivenUpdate.timeIntervalSinceNow < -bleHeartbeatUpdateInterval else { + log.default("Skipping ble heartbeat") + return + } + lastBLEDrivenUpdate = Date() + cgmManager?.fetchNewDataIfNeeded { (result) in if case .newData = result { AnalyticsManager.shared.didFetchNewCGMData() diff --git a/Loop/Managers/NightscoutDataManager.swift b/Loop/Managers/NightscoutDataManager.swift index 9e74600a4f..876ddd8b0d 100644 --- a/Loop/Managers/NightscoutDataManager.swift +++ b/Loop/Managers/NightscoutDataManager.swift @@ -152,14 +152,23 @@ final class NightscoutDataManager { bolusing = false } + let currentReservoirUnits: Double? + if let lastReservoirValue = deviceManager.loopManager.doseStore.lastReservoirValue, lastReservoirValue.startDate > Date().addingTimeInterval(.minutes(-15)) { + currentReservoirUnits = lastReservoirValue.unitVolume + } else { + currentReservoirUnits = nil + } + pumpStatus = NightscoutUploadKit.PumpStatus( clock: Date(), pumpID: pumpManagerStatus.device.localIdentifier ?? "Unknown", + manufacturer: pumpManagerStatus.device.manufacturer, + model: pumpManagerStatus.device.model, iob: nil, battery: battery, suspended: pumpManagerStatus.basalDeliveryState == .suspended, bolusing: bolusing, - reservoir: deviceManager.loopManager.doseStore.lastReservoirValue?.unitVolume, + reservoir: currentReservoirUnits, secondsFromGMT: pumpManagerStatus.timeZone.secondsFromGMT()) } else { pumpStatus = nil diff --git a/Loop/View Controllers/BolusViewController.swift b/Loop/View Controllers/BolusViewController.swift index 2e2cf97409..c2648778b9 100644 --- a/Loop/View Controllers/BolusViewController.swift +++ b/Loop/View Controllers/BolusViewController.swift @@ -187,6 +187,11 @@ final class BolusViewController: UITableViewController, IdentifiableClass, UITex title: NSLocalizedString("Exceeds Maximum Bolus", comment: "The title of the alert describing a maximum bolus validation error"), message: String(format: NSLocalizedString("The maximum bolus amount is %@ Units", comment: "Body of the alert describing a maximum bolus validation error. (1: The localized max bolus value)"), bolusUnitsFormatter.string(from: maxBolus) ?? ""), preferredStyle: .alert) + + let action = UIAlertAction(title: NSLocalizedString("com.loudnate.LoopKit.errorAlertActionTitle", value: "OK", comment: "The title of the action used to dismiss an error alert"), style: .default) + alert.addAction(action) + alert.preferredAction = action + present(alert, animated: true) return } diff --git a/prep_workspace.sh b/prep_workspace.sh new file mode 100755 index 0000000000..aa660c3b0f --- /dev/null +++ b/prep_workspace.sh @@ -0,0 +1,28 @@ +set -x +set -u +set -e + +# Uncomment if you have not already cloned the repo: +# git clone https://github.com/ps2/loop-priv.git +# cd loop-priv + +git checkout omnipod-testing + +carthage bootstrap --use-submodules --no-build + +git reset HEAD + +cd Carthage/Checkouts +cd LoopKit; git fetch; git checkout omnipod-testing; git pull; cd .. +cd rileylink_ios; git fetch; git checkout omnipod-testing; git pull; cd .. +cd CGMBLEKit; git fetch; git checkout omnipod-testing; git pull; cd .. +cd dexcom-share-client-swift; git fetch; git checkout omnipod-testing; git pull; cd .. +cd G4ShareSpy; git fetch; git checkout omnipod-testing; git pull; cd .. +cd ../.. +carthage build + +echo "Open Xcode, and select File -> New -> Workspace and create "Loop.xcworkspace" in the loop-priv directory just created. +Select File -> Add Files to "Loop"... to add the Loop.xcodeproj project. +Repeatedly select File -> Add Files to "Loop"... to add the .xcodeproj file for each project within the Carthage/Checkouts directory. +run 'carthage build' at the top level Loop directory +Build Loop in Xcode!" From f2c56fb00bc7a1e35592401581939eafbad9f8e9 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 20 Jul 2019 08:22:40 -0500 Subject: [PATCH 051/132] Bump rileylink_ios rev --- Cartfile.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 51eac3d356..81e1c8d709 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -5,4 +5,4 @@ github "LoopKit/LoopKit" "422112acfe850df28ff826519646d4c2be73f5d0" github "LoopKit/dexcom-share-client-swift" "cdbb1cd19cf54b37add6d6c25300445a6abe24da" github "i-schuetz/SwiftCharts" "0.6.5" github "maxkonovalov/MKRingProgressView" "2.2.2" -github "ps2/rileylink_ios" "b7b1c44e52166244a0583a781f1b42e5e75ad838" +github "ps2/rileylink_ios" "25a15e0dda0ccd708da1e4ba09da783bbd7d6a1d" From 5c631359983d94c0966100694105b4fa38096197 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 20 Jul 2019 10:11:54 -0500 Subject: [PATCH 052/132] Point to rileylink_ios dev branch --- Cartfile | 2 +- Cartfile.resolved | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cartfile b/Cartfile index d6b4d7647b..ca66f13586 100644 --- a/Cartfile +++ b/Cartfile @@ -3,5 +3,5 @@ github "LoopKit/CGMBLEKit" "dev" github "i-schuetz/SwiftCharts" == 0.6.5 github "LoopKit/dexcom-share-client-swift" "dev" github "LoopKit/G4ShareSpy" "dev" -github "ps2/rileylink_ios" "omnipod-testing-integrate" +github "ps2/rileylink_ios" "dev" github "LoopKit/Amplitude-iOS" "decreepify" diff --git a/Cartfile.resolved b/Cartfile.resolved index 81e1c8d709..2dbfa94528 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -5,4 +5,4 @@ github "LoopKit/LoopKit" "422112acfe850df28ff826519646d4c2be73f5d0" github "LoopKit/dexcom-share-client-swift" "cdbb1cd19cf54b37add6d6c25300445a6abe24da" github "i-schuetz/SwiftCharts" "0.6.5" github "maxkonovalov/MKRingProgressView" "2.2.2" -github "ps2/rileylink_ios" "25a15e0dda0ccd708da1e4ba09da783bbd7d6a1d" +github "ps2/rileylink_ios" "7297016d4fc063c75c40f9e8402e303df78816aa" From 11c71feefd0d5e6fe416f39403e774cbbf7bd337 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 20 Jul 2019 13:22:40 -0500 Subject: [PATCH 053/132] Remove Cartfile dependency --- Loop.xcodeproj/project.pbxproj | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index f15f99c1a9..e1061dfd50 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -491,13 +491,6 @@ remoteGlobalIDString = 43D9FFCE21EAE05D00AF44BF; remoteInfo = LoopCore; }; - C110A0E9221BC29B0016560B /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 43776F841B8022E90074EA36 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 432CF87720D8B8380066B889; - remoteInfo = Cartfile; - }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -2015,7 +2008,6 @@ buildRules = ( ); dependencies = ( - C110A0EA221BC29B0016560B /* PBXTargetDependency */, 43D9000D21EB0BEA00AF44BF /* PBXTargetDependency */, 4F7528991DFE1ED800C322D6 /* PBXTargetDependency */, ); @@ -2858,11 +2850,6 @@ target = 43D9FFCE21EAE05D00AF44BF /* LoopCore */; targetProxy = A942E446225FD9A300DD4980 /* PBXContainerItemProxy */; }; - C110A0EA221BC29B0016560B /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 432CF87720D8B8380066B889 /* Cartfile */; - targetProxy = C110A0E9221BC29B0016560B /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ From 7f4402a933d4d061a91039be4f118201c37b5350 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 20 Jul 2019 18:29:20 -0500 Subject: [PATCH 054/132] Update watch data in response to adding carbs, bolus (#1019) * Update watch data in response to adding carbs, bolus * Updates types in tests to concrete types. --- Common/Models/BolusSuggestionUserInfo.swift | 49 ------------------- Common/Models/WatchContext.swift | 13 +---- Common/Models/WatchPredictedGlucose.swift | 6 +-- DoseMathTests/DoseMathTests.swift | 8 +-- Loop.xcodeproj/project.pbxproj | 6 --- Loop/Managers/DeviceDataManager.swift | 6 ++- Loop/Managers/DoseMath.swift | 2 +- Loop/Managers/LoopDataManager.swift | 8 +-- Loop/Managers/WatchDataManager.swift | 35 ++++++++----- .../Controllers/ActionHUDController.swift | 16 +++--- .../AddCarbsInterfaceController.swift | 11 +++-- .../BolusInterfaceController.swift | 14 ++---- .../Controllers/HUDInterfaceController.swift | 8 +-- WatchApp Extension/Extensions/WCSession.swift | 16 +++--- 14 files changed, 71 insertions(+), 127 deletions(-) delete mode 100644 Common/Models/BolusSuggestionUserInfo.swift diff --git a/Common/Models/BolusSuggestionUserInfo.swift b/Common/Models/BolusSuggestionUserInfo.swift deleted file mode 100644 index 2a7023b6fe..0000000000 --- a/Common/Models/BolusSuggestionUserInfo.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// BolusSuggestionUserInfo.swift -// Naterade -// -// Created by Nathan Racklyeft on 3/20/16. -// Copyright © 2016 Nathan Racklyeft. All rights reserved. -// - -import Foundation - - -struct BolusSuggestionUserInfo: RawRepresentable { - let recommendedBolus: Double? - var maxBolus: Double? - - init(recommendedBolus: Double?, maxBolus: Double? = nil) { - self.recommendedBolus = recommendedBolus - self.maxBolus = maxBolus - } - - // MARK: - RawRepresentable - typealias RawValue = [String: Any] - - static let version = 1 - static let name = "BolusSuggestionUserInfo" - - init?(rawValue: RawValue) { - guard rawValue["v"] as? Int == type(of: self).version && rawValue["name"] as? String == BolusSuggestionUserInfo.name, - let recommendedBolus = rawValue["br"] as? Double else - { - return nil - } - - self.recommendedBolus = recommendedBolus - self.maxBolus = rawValue["mb"] as? Double - } - - var rawValue: RawValue { - var raw: RawValue = [ - "v": type(of: self).version, - "name": BolusSuggestionUserInfo.name, - ] - - raw["br"] = recommendedBolus - raw["mb"] = maxBolus - - return raw - } -} diff --git a/Common/Models/WatchContext.swift b/Common/Models/WatchContext.swift index d25e5c7e55..67f6c603d4 100644 --- a/Common/Models/WatchContext.swift +++ b/Common/Models/WatchContext.swift @@ -11,7 +11,7 @@ import HealthKit import LoopKit -final class WatchContext: NSObject, RawRepresentable { +final class WatchContext: RawRepresentable { typealias RawValue = [String: Any] private let version = 4 @@ -32,12 +32,6 @@ final class WatchContext: NSObject, RawRepresentable { var lastNetTempBasalDate: Date? var recommendedBolusDose: Double? - var bolusSuggestion: BolusSuggestionUserInfo? { - guard let recommended = recommendedBolusDose else { return nil } - - return BolusSuggestionUserInfo(recommendedBolus: recommended) - } - var cob: Double? var iob: Double? var reservoir: Double? @@ -46,13 +40,10 @@ final class WatchContext: NSObject, RawRepresentable { var cgmManagerState: CGMManager.RawStateValue? - override init() { - super.init() + init() { } required init?(rawValue: RawValue) { - super.init() - guard rawValue["v"] as? Int == version else { return nil } diff --git a/Common/Models/WatchPredictedGlucose.swift b/Common/Models/WatchPredictedGlucose.swift index d801febbfa..080a824074 100644 --- a/Common/Models/WatchPredictedGlucose.swift +++ b/Common/Models/WatchPredictedGlucose.swift @@ -11,10 +11,10 @@ import LoopKit import HealthKit -struct WatchPredictedGlucose { - let values: [GlucoseValue] +struct WatchPredictedGlucose: Equatable { + let values: [PredictedGlucoseValue] - init?(values: [GlucoseValue]) { + init?(values: [PredictedGlucoseValue]) { guard values.count > 1 else { return nil } diff --git a/DoseMathTests/DoseMathTests.swift b/DoseMathTests/DoseMathTests.swift index a380784164..f2c1b495cf 100644 --- a/DoseMathTests/DoseMathTests.swift +++ b/DoseMathTests/DoseMathTests.swift @@ -57,7 +57,7 @@ class RecommendTempBasalTests: XCTestCase { fileprivate let fortyIncrementsPerUnitRounder = { round($0 * 40) / 40 } - func loadGlucoseValueFixture(_ resourceName: String) -> [GlucoseValue] { + func loadGlucoseValueFixture(_ resourceName: String) -> [GlucoseFixtureValue] { let fixture: [JSONDictionary] = loadFixture(resourceName) let dateFormatter = ISO8601DateFormatter.localTimeDateFormatter() @@ -441,7 +441,7 @@ class RecommendTempBasalTests: XCTestCase { func testNoInputGlucose() { - let glucose: [GlucoseValue] = [] + let glucose: [GlucoseFixtureValue] = [] let dose = glucose.recommendedTempBasal( to: glucoseTargetRange, @@ -464,7 +464,7 @@ class RecommendBolusTests: XCTestCase { fileprivate let fortyIncrementsPerUnitRounder = { round($0 * 40) / 40 } - func loadGlucoseValueFixture(_ resourceName: String) -> [GlucoseValue] { + func loadGlucoseValueFixture(_ resourceName: String) -> [GlucoseFixtureValue] { let fixture: [JSONDictionary] = loadFixture(resourceName) let dateFormatter = ISO8601DateFormatter.localTimeDateFormatter() @@ -823,7 +823,7 @@ class RecommendBolusTests: XCTestCase { func testNoInputGlucose() { - let glucose: [GlucoseValue] = [] + let glucose: [GlucoseFixtureValue] = [] let dose = glucose.recommendedBolus( to: glucoseTargetRange, diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index e1061dfd50..739fd44fc1 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -80,8 +80,6 @@ 43511CE321FD80E400566C63 /* StandardRetrospectiveCorrection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43511CE021FD80E400566C63 /* StandardRetrospectiveCorrection.swift */; }; 43511CEE220FC61700566C63 /* HUDRowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43511CED220FC61700566C63 /* HUDRowController.swift */; }; 43523EDB1CC35083001850F1 /* RileyLinkKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43523EDA1CC35083001850F1 /* RileyLinkKit.framework */; }; - 435400311C9F744E00D5819C /* BolusSuggestionUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435400301C9F744E00D5819C /* BolusSuggestionUserInfo.swift */; }; - 435400321C9F745500D5819C /* BolusSuggestionUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435400301C9F744E00D5819C /* BolusSuggestionUserInfo.swift */; }; 435400341C9F878D00D5819C /* SetBolusUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435400331C9F878D00D5819C /* SetBolusUserInfo.swift */; }; 435400351C9F878D00D5819C /* SetBolusUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435400331C9F878D00D5819C /* SetBolusUserInfo.swift */; }; 435CB6231F37967800C320C7 /* InsulinModelSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435CB6221F37967800C320C7 /* InsulinModelSettingsViewController.swift */; }; @@ -627,7 +625,6 @@ 43511CE021FD80E400566C63 /* StandardRetrospectiveCorrection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StandardRetrospectiveCorrection.swift; sourceTree = ""; }; 43511CED220FC61700566C63 /* HUDRowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUDRowController.swift; sourceTree = ""; }; 43523EDA1CC35083001850F1 /* RileyLinkKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RileyLinkKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 435400301C9F744E00D5819C /* BolusSuggestionUserInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BolusSuggestionUserInfo.swift; sourceTree = ""; }; 435400331C9F878D00D5819C /* SetBolusUserInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetBolusUserInfo.swift; sourceTree = ""; }; 435CB6221F37967800C320C7 /* InsulinModelSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InsulinModelSettingsViewController.swift; sourceTree = ""; }; 435CB6241F37ABFC00C320C7 /* ExponentialInsulinModelPreset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExponentialInsulinModelPreset.swift; sourceTree = ""; }; @@ -1719,7 +1716,6 @@ 4FF4D0FB1E1834C400846527 /* Models */ = { isa = PBXGroup; children = ( - 435400301C9F744E00D5819C /* BolusSuggestionUserInfo.swift */, 4F11D3BF20DCBEEC006E072C /* GlucoseBackfillRequestUserInfo.swift */, 4372E48F213CFCE70068E043 /* LoopSettingsUserInfo.swift */, 435400331C9F878D00D5819C /* SetBolusUserInfo.swift */, @@ -2489,7 +2485,6 @@ C15713821DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift in Sources */, 43FCEEA9221A615B0013DD30 /* StatusChartsManager.swift in Sources */, 43511CE321FD80E400566C63 /* StandardRetrospectiveCorrection.swift in Sources */, - 435400321C9F745500D5819C /* BolusSuggestionUserInfo.swift in Sources */, 43E3449F1B9D68E900C85C07 /* StatusTableViewController.swift in Sources */, 43DBF0531C93EC8200B3C386 /* DeviceDataManager.swift in Sources */, 43E2D8C81D208D5B004DA55F /* KeychainManager+Loop.swift in Sources */, @@ -2575,7 +2570,6 @@ 4372E488213C862B0068E043 /* SampleValue.swift in Sources */, 4F2C15741E0209F500E160D4 /* NSTimeInterval.swift in Sources */, 4FF4D1011E18375000846527 /* WatchContext.swift in Sources */, - 435400311C9F744E00D5819C /* BolusSuggestionUserInfo.swift in Sources */, 898ECA63218ABD21001E9D35 /* ComplicationChartManager.swift in Sources */, 43A9438A1B926B7B0051FA24 /* NotificationController.swift in Sources */, 439A7945211FE23A0041B75F /* NSUserActivity.swift in Sources */, diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index 5aa360ef1f..dec0ea93b1 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -144,14 +144,16 @@ private extension DeviceDataManager { // MARK: - Client API extension DeviceDataManager { - func enactBolus(units: Double, at startDate: Date = Date(), completion: @escaping (_ error: Error?) -> Void) { + func enactBolus(units: Double, at startDate: Date = Date(), willRequest: ((DoseEntry) -> Void)? = nil, completion: @escaping (_ error: Error?) -> Void) { guard let pumpManager = pumpManager else { completion(LoopError.configurationError(.pumpManager)) return } pumpManager.enactBolus(units: units, at: startDate, willRequest: { (dose) in - self.loopManager.addRequestedBolus(dose, completion: nil) + self.loopManager.addRequestedBolus(dose, completion: { + willRequest?(dose) + }) }) { (result) in switch result { case .failure(let error): diff --git a/Loop/Managers/DoseMath.swift b/Loop/Managers/DoseMath.swift index 4be6116a37..5502084671 100644 --- a/Loop/Managers/DoseMath.swift +++ b/Loop/Managers/DoseMath.swift @@ -206,7 +206,7 @@ private func targetGlucoseValue(percentEffectDuration: Double, minValue: Double, } -extension Collection where Element == GlucoseValue { +extension Collection where Element: GlucoseValue { /// For a collection of glucose prediction, determine the least amount of insulin delivered at /// `date` to correct the predicted glucose to the middle of `correctionRange` at the time of prediction. diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 348f9be214..15fa94e6c3 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -173,7 +173,7 @@ final class LoopDataManager { } private var retrospectiveGlucoseDiscrepanciesSummed: [GlucoseChange]? - fileprivate var predictedGlucose: [GlucoseValue]? { + fileprivate var predictedGlucose: [PredictedGlucoseValue]? { didSet { recommendedTempBasal = nil recommendedBolus = nil @@ -790,7 +790,7 @@ extension LoopDataManager { } /// - Throws: LoopError.missingDataError - fileprivate func predictGlucose(using inputs: PredictionInputEffect) throws -> [GlucoseValue] { + fileprivate func predictGlucose(using inputs: PredictionInputEffect) throws -> [PredictedGlucoseValue] { dispatchPrecondition(condition: .onQueue(dataAccessQueue)) guard let model = insulinModelSettings?.model else { @@ -1020,7 +1020,7 @@ protocol LoopState { var lastTempBasal: DoseEntry? { get } /// The calculated timeline of predicted glucose values - var predictedGlucose: [GlucoseValue]? { get } + var predictedGlucose: [PredictedGlucoseValue]? { get } /// The recommended temp basal based on predicted glucose var recommendedTempBasal: (recommendation: TempBasalRecommendation, date: Date)? { get } @@ -1074,7 +1074,7 @@ extension LoopDataManager { return loopDataManager.lastTempBasal } - var predictedGlucose: [GlucoseValue]? { + var predictedGlucose: [PredictedGlucoseValue]? { dispatchPrecondition(condition: .onQueue(loopDataManager.dataAccessQueue)) return loopDataManager.predictedGlucose } diff --git a/Loop/Managers/WatchDataManager.swift b/Loop/Managers/WatchDataManager.swift index 2cb458edd4..85b7868116 100644 --- a/Loop/Managers/WatchDataManager.swift +++ b/Loop/Managers/WatchDataManager.swift @@ -98,9 +98,7 @@ final class WatchDataManager: NSObject { } createWatchContext { (context) in - if let context = context { - self.sendWatchContext(context) - } + self.sendWatchContext(context) } } @@ -142,7 +140,7 @@ final class WatchDataManager: NSObject { } } - private func createWatchContext(_ completion: @escaping (_ context: WatchContext?) -> Void) { + private func createWatchContext(_ completion: @escaping (_ context: WatchContext) -> Void) { let loopManager = deviceManager.loopManager! let glucose = loopManager.glucoseStore.latestGlucose @@ -179,7 +177,7 @@ final class WatchDataManager: NSObject { if let scheduledBasal = manager.basalRateScheduleApplyingOverrideHistory?.between(start: date, end: date).first, let lastTempBasal = state.lastTempBasal, lastTempBasal.endDate > Date() { - context.lastNetTempBasalDose = lastTempBasal.unitsPerHour - scheduledBasal.value + context.lastNetTempBasalDose = lastTempBasal.unitsPerHour - scheduledBasal.value } // Drop the first element in predictedGlucose because it is the current glucose @@ -192,19 +190,20 @@ final class WatchDataManager: NSObject { } } - private func addCarbEntryFromWatchMessage(_ message: [String: Any], completionHandler: ((_ units: Double?) -> Void)? = nil) { + private func addCarbEntryFromWatchMessage(_ message: [String: Any], completionHandler: ((_ error: Error?) -> Void)? = nil) { if let carbEntry = CarbEntryUserInfo(rawValue: message)?.carbEntry { deviceManager.loopManager.addCarbEntryAndRecommendBolus(carbEntry) { (result) in switch result { - case .success(let recommendation): + case .success: AnalyticsManager.shared.didAddCarbsFromWatch() - completionHandler?(recommendation?.amount) + completionHandler?(nil) case .failure(let error): self.log.error(error) - completionHandler?(nil) + completionHandler?(error) } } } else { + log.error("Could not add carb entry from unknown message: \(message)") completionHandler?(nil) } } @@ -215,18 +214,26 @@ extension WatchDataManager: WCSessionDelegate { func session(_ session: WCSession, didReceiveMessage message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void) { switch message["name"] as? String { case CarbEntryUserInfo.name?: - addCarbEntryFromWatchMessage(message) { (units) in - replyHandler(BolusSuggestionUserInfo(recommendedBolus: units ?? 0, maxBolus: self.deviceManager.loopManager.settings.maximumBolus).rawValue) + addCarbEntryFromWatchMessage(message) { (_) in + self.createWatchContext { (context) in + // Send back the updated prediction and recommended bolus + replyHandler(context.rawValue) + } } case SetBolusUserInfo.name?: + // Start the bolus and reply when it's successfully requested if let bolus = SetBolusUserInfo(rawValue: message as SetBolusUserInfo.RawValue) { self.deviceManager.enactBolus(units: bolus.value, at: bolus.startDate) { (error) in if error == nil { AnalyticsManager.shared.didSetBolusFromWatch(bolus.value) } + + // When we've successfully started the bolus, send a new context with our new prediction + self.sendWatchContextIfNeeded() } } + // Reply immediately replyHandler([:]) case LoopSettingsUserInfo.name?: if let watchSettings = LoopSettingsUserInfo(rawValue: message)?.settings { @@ -238,7 +245,11 @@ extension WatchDataManager: WCSessionDelegate { lastSentSettings = settings deviceManager.loopManager.settings = settings } - replyHandler([:]) + + // Since target range affects recommended bolus, send back a new one + createWatchContext { (context) in + replyHandler(context.rawValue) + } case GlucoseBackfillRequestUserInfo.name?: if let userInfo = GlucoseBackfillRequestUserInfo(rawValue: message), let manager = deviceManager.loopManager { diff --git a/WatchApp Extension/Controllers/ActionHUDController.swift b/WatchApp Extension/Controllers/ActionHUDController.swift index 9f3403dd8c..5dc6e3b496 100644 --- a/WatchApp Extension/Controllers/ActionHUDController.swift +++ b/WatchApp Extension/Controllers/ActionHUDController.swift @@ -128,17 +128,21 @@ final class ActionHUDController: HUDInterfaceController { settings.scheduleOverride = override let userInfo = LoopSettingsUserInfo(settings: settings) do { - try WCSession.default.sendSettingsUpdateMessage(userInfo, completionHandler: { error in + try WCSession.default.sendSettingsUpdateMessage(userInfo, completionHandler: { (result) in DispatchQueue.main.async { self.pendingMessageResponses -= 1 - if let error = error { + + switch result { + case .success(let context): if self.pendingMessageResponses == 0 { - ExtensionDelegate.shared().present(error) - self.updateForOverrideContext(self.loopManager.settings.scheduleOverride?.context) + self.loopManager.settings.scheduleOverride = override } - } else { + + ExtensionDelegate.shared().loopManager.updateContext(context) + case .failure(let error): if self.pendingMessageResponses == 0 { - self.loopManager.settings.scheduleOverride = override + ExtensionDelegate.shared().present(error) + self.updateForOverrideContext(self.loopManager.settings.scheduleOverride?.context) } } } diff --git a/WatchApp Extension/Controllers/AddCarbsInterfaceController.swift b/WatchApp Extension/Controllers/AddCarbsInterfaceController.swift index 56e39878bb..7b4a550135 100644 --- a/WatchApp Extension/Controllers/AddCarbsInterfaceController.swift +++ b/WatchApp Extension/Controllers/AddCarbsInterfaceController.swift @@ -188,14 +188,15 @@ final class AddCarbsInterfaceController: WKInterfaceController, IdentifiableClas do { try WCSession.default.sendCarbEntryMessage(entry, - replyHandler: { (suggestion) in + replyHandler: { (context) in DispatchQueue.main.async { WKInterfaceDevice.current().play(.success) + let loopManager = ExtensionDelegate.shared().loopManager + loopManager.addConfirmedCarbEntry(entry.carbEntry) + loopManager.updateContext(context) - ExtensionDelegate.shared().loopManager.addConfirmedCarbEntry(entry.carbEntry) - - if let recommendedBolus = suggestion.recommendedBolus?.rawValue, recommendedBolus > 0.0 { - WKExtension.shared().rootInterfaceController?.presentController(withName: BolusInterfaceController.className, context: suggestion) + if let units = context.recommendedBolusDose, units > 0.0 { + WKExtension.shared().rootInterfaceController?.presentController(withName: BolusInterfaceController.className, context: context) } } }, diff --git a/WatchApp Extension/Controllers/BolusInterfaceController.swift b/WatchApp Extension/Controllers/BolusInterfaceController.swift index 7154ff7c88..b32561db9a 100644 --- a/WatchApp Extension/Controllers/BolusInterfaceController.swift +++ b/WatchApp Extension/Controllers/BolusInterfaceController.swift @@ -82,21 +82,13 @@ final class BolusInterfaceController: WKInterfaceController, IdentifiableClass { override func awake(withContext context: Any?) { super.awake(withContext: context) - var maxBolusValue: Double = 15 + let maxBolusValue: Double = ExtensionDelegate.shared().loopManager.settings.maximumBolus ?? 10 var pickerValue = 0 - if let context = context as? BolusSuggestionUserInfo { - let recommendedBolus = context.recommendedBolus ?? 0 - - if let maxBolus = context.maxBolus { - maxBolusValue = maxBolus - } else if recommendedBolus > 0 { - maxBolusValue = recommendedBolus - } - + if let context = context as? WatchContext, let recommendedBolus = context.recommendedBolusDose { pickerValue = pickerValueFromBolusValue(recommendedBolus * 0.75) - if let recommendedBolus = context.recommendedBolus, let valueString = formatter.string(from: recommendedBolus) { + if let valueString = formatter.string(from: recommendedBolus) { recommendedValueLabel.setText(String(format: NSLocalizedString("Rec: %@ U", comment: "The label and value showing the recommended bolus"), valueString).localizedUppercase) } } diff --git a/WatchApp Extension/Controllers/HUDInterfaceController.swift b/WatchApp Extension/Controllers/HUDInterfaceController.swift index ab1b62a381..3b3eddcf63 100644 --- a/WatchApp Extension/Controllers/HUDInterfaceController.swift +++ b/WatchApp Extension/Controllers/HUDInterfaceController.swift @@ -83,13 +83,7 @@ class HUDInterfaceController: WKInterfaceController { } @IBAction func setBolus() { - var context = loopManager.activeContext?.bolusSuggestion ?? BolusSuggestionUserInfo(recommendedBolus: nil) - - if context.maxBolus == nil { - context.maxBolus = loopManager.settings.maximumBolus - } - - presentController(withName: BolusInterfaceController.className, context: context) + presentController(withName: BolusInterfaceController.className, context: loopManager.activeContext) } } diff --git a/WatchApp Extension/Extensions/WCSession.swift b/WatchApp Extension/Extensions/WCSession.swift index 909d027468..ea281a16c0 100644 --- a/WatchApp Extension/Extensions/WCSession.swift +++ b/WatchApp Extension/Extensions/WCSession.swift @@ -26,7 +26,7 @@ enum WCSessionMessageResult { private let log = OSLog(category: "WCSession Extension") extension WCSession { - func sendCarbEntryMessage(_ carbEntry: CarbEntryUserInfo, replyHandler: @escaping (BolusSuggestionUserInfo) -> Void, errorHandler: @escaping (Error) -> Void) throws { + func sendCarbEntryMessage(_ carbEntry: CarbEntryUserInfo, replyHandler: @escaping (WatchContext) -> Void, errorHandler: @escaping (Error) -> Void) throws { guard activationState == .activated else { throw MessageError.activation } @@ -39,13 +39,13 @@ extension WCSession { sendMessage(carbEntry.rawValue, replyHandler: { reply in - guard let suggestion = BolusSuggestionUserInfo(rawValue: reply as BolusSuggestionUserInfo.RawValue) else { + guard let context = WatchContext(rawValue: reply as WatchContext.RawValue) else { log.error("sendCarbEntryMessage: could not decode reply: %{public}@", reply) errorHandler(MessageError.decoding) return } - replyHandler(suggestion) + replyHandler(context) }, errorHandler: { error in log.error("sendCarbEntryMessage: message send failed with error: %{public}@", String(describing: error)) @@ -73,7 +73,7 @@ extension WCSession { ) } - func sendSettingsUpdateMessage(_ userInfo: LoopSettingsUserInfo, completionHandler: @escaping (Error?) -> Void) throws { + func sendSettingsUpdateMessage(_ userInfo: LoopSettingsUserInfo, completionHandler: @escaping (Result) -> Void) throws { guard activationState == .activated else { throw MessageError.activation } @@ -83,9 +83,13 @@ extension WCSession { } sendMessage(userInfo.rawValue, replyHandler: { (reply) in - completionHandler(nil) + if let context = WatchContext(rawValue: reply) { + completionHandler(.success(context)) + } else { + completionHandler(.failure(MessageError.decoding)) + } }, errorHandler: { (error) in - completionHandler(error) + completionHandler(.failure(error)) }) } From 97cff3e23a35706ee2e664c783a292d5106169d1 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 20 Jul 2019 22:46:41 -0500 Subject: [PATCH 055/132] Bump LoopKit rev --- Cartfile.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 2dbfa94528..dcf9db9ccd 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,7 +1,7 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "fe92d93c24d18ff9755ff027e9b036d3769a8a0a" github "LoopKit/G4ShareSpy" "f6ae0cf1753131135f26046ddf07e096da884b84" -github "LoopKit/LoopKit" "422112acfe850df28ff826519646d4c2be73f5d0" +github "LoopKit/LoopKit" "fc68a7dcb33b2fa675534a8e47119ca1c29b0a0a" github "LoopKit/dexcom-share-client-swift" "cdbb1cd19cf54b37add6d6c25300445a6abe24da" github "i-schuetz/SwiftCharts" "0.6.5" github "maxkonovalov/MKRingProgressView" "2.2.2" From a6af00c592a5457851eaf8963bc10f55499b074b Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 20 Jul 2019 23:05:17 -0500 Subject: [PATCH 056/132] Fix display of correction range override --- LoopUI/Charts/PredictedGlucoseChart.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LoopUI/Charts/PredictedGlucoseChart.swift b/LoopUI/Charts/PredictedGlucoseChart.swift index 2028dfde04..fcc431ad72 100644 --- a/LoopUI/Charts/PredictedGlucoseChart.swift +++ b/LoopUI/Charts/PredictedGlucoseChart.swift @@ -79,8 +79,8 @@ extension PredictedGlucoseChart { targetGlucosePoints = ChartPoint.pointsForGlucoseRangeSchedule(schedule, unit: glucoseUnit, xAxisValues: xAxisValues) if let override = scheduleOverride, override.isActive() || override.startDate > Date() { - targetOverridePoints = ChartPoint.pointsForGlucoseRangeScheduleOverride(override, unit: schedule.unit, xAxisValues: xAxisValues, extendEndDateToChart: true) - targetOverrideDurationPoints = ChartPoint.pointsForGlucoseRangeScheduleOverride(override, unit: schedule.unit, xAxisValues: xAxisValues) + targetOverridePoints = ChartPoint.pointsForGlucoseRangeScheduleOverride(override, unit: glucoseUnit, xAxisValues: xAxisValues, extendEndDateToChart: true) + targetOverrideDurationPoints = ChartPoint.pointsForGlucoseRangeScheduleOverride(override, unit: glucoseUnit, xAxisValues: xAxisValues) } else { targetOverridePoints = [] targetOverrideDurationPoints = [] From bbe1587ad979277d96ddd9b756afa788e809f7f1 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 21 Jul 2019 23:28:53 -0500 Subject: [PATCH 057/132] Bump LoopKit rev to bring in significant digits for basal schedule editor --- Cartfile.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index dcf9db9ccd..c9f7613905 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,7 +1,7 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "fe92d93c24d18ff9755ff027e9b036d3769a8a0a" github "LoopKit/G4ShareSpy" "f6ae0cf1753131135f26046ddf07e096da884b84" -github "LoopKit/LoopKit" "fc68a7dcb33b2fa675534a8e47119ca1c29b0a0a" +github "LoopKit/LoopKit" "501d7ce0e616322a50b0cac2a33e1a4278362933" github "LoopKit/dexcom-share-client-swift" "cdbb1cd19cf54b37add6d6c25300445a6abe24da" github "i-schuetz/SwiftCharts" "0.6.5" github "maxkonovalov/MKRingProgressView" "2.2.2" From 21572234df3c2f93fed06212b4ca59ee8efb8509 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 22 Jul 2019 09:24:31 -0500 Subject: [PATCH 058/132] Update to Swift 5 --- Learn/Lessons/TimeInRangeLesson.swift | 1 - .../StatusViewController.swift | 18 ++-- Loop.xcodeproj/project.pbxproj | 98 ++++++++----------- .../xcshareddata/xcschemes/Loop.xcscheme | 14 --- Loop/AppDelegate.swift | 4 +- .../RangeReplaceableCollection.swift | 2 +- Loop/Managers/LoopDataManager.swift | 6 +- Loop/Managers/NotificationManager.swift | 10 +- Loop/Managers/WatchDataManager.swift | 2 + .../CarbAbsorptionViewController.swift | 4 +- .../ChartsTableViewController.swift | 10 +- .../InsulinModelSettingsViewController.swift | 2 +- .../PredictionTableViewController.swift | 2 +- .../SettingsTableViewController.swift | 10 +- .../StatusTableViewController.swift | 20 ++-- Loop/Views/SettingsImageTableViewCell.swift | 4 +- LoopUI/Charts/ChartsManager.swift | 2 +- LoopUI/Views/BasalStateView.swift | 2 +- LoopUI/Views/HUDView.swift | 12 ++- LoopUI/Views/LoopCompletionHUDView.swift | 2 +- LoopUI/Views/LoopStateView.swift | 2 +- Scripts/copy-frameworks.sh | 41 ++++++++ .../ComplicationController.swift | 2 + .../Controllers/HUDRowController.swift | 8 +- .../Extensions/CLKComplicationTemplate.swift | 2 + 25 files changed, 156 insertions(+), 124 deletions(-) create mode 100755 Scripts/copy-frameworks.sh diff --git a/Learn/Lessons/TimeInRangeLesson.swift b/Learn/Lessons/TimeInRangeLesson.swift index add895d865..0d3ac7cf8e 100644 --- a/Learn/Lessons/TimeInRangeLesson.swift +++ b/Learn/Lessons/TimeInRangeLesson.swift @@ -177,7 +177,6 @@ private class TimeInRangeCalculator { os_log(.error, log: self.log, "Failed to fetch samples: %{public}@", String(describing: error)) completion(error) case .success(let samples): - if let timeInRange = samples.proportion(where: { self.range.contains($0.quantity) }) { _ = results.mutate({ (results) in results[day] = timeInRange diff --git a/Loop Status Extension/StatusViewController.swift b/Loop Status Extension/StatusViewController.swift index a2cd9d91af..08e1e9e465 100644 --- a/Loop Status Extension/StatusViewController.swift +++ b/Loop Status Extension/StatusViewController.swift @@ -100,10 +100,12 @@ class StatusViewController: UIViewController, NCWidgetProviding { extensionContext?.widgetLargestAvailableDisplayMode = .expanded switch extensionContext?.widgetActiveDisplayMode ?? .compact { - case .compact: - glucoseChartContentView.isHidden = true case .expanded: glucoseChartContentView.isHidden = false + case .compact: + fallthrough + @unknown default: + glucoseChartContentView.isHidden = true } observers = [ @@ -121,10 +123,12 @@ class StatusViewController: UIViewController, NCWidgetProviding { let compactHeight = hudView.systemLayoutSizeFitting(maxSize).height + subtitleLabel.systemLayoutSizeFitting(maxSize).height switch activeDisplayMode { - case .compact: - preferredContentSize = CGSize(width: maxSize.width, height: compactHeight) case .expanded: preferredContentSize = CGSize(width: maxSize.width, height: compactHeight + 100) + case .compact: + fallthrough + @unknown default: + preferredContentSize = CGSize(width: maxSize.width, height: compactHeight) } } @@ -275,10 +279,12 @@ class StatusViewController: UIViewController, NCWidgetProviding { } switch extensionContext?.widgetActiveDisplayMode ?? .compact { - case .compact: - glucoseChartContentView.isHidden = true case .expanded: glucoseChartContentView.isHidden = false + case .compact: + fallthrough + @unknown default: + glucoseChartContentView.isHidden = true } // Right now we always act as if there's new data. diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 739fd44fc1..acdc2f7aeb 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -659,6 +659,7 @@ 437CEEE31CDE5C0A003C8C80 /* UIImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = ""; }; 437D9BA11D7B5203007245E8 /* Loop.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Loop.xcconfig; sourceTree = ""; }; 437D9BA21D7BC977007245E8 /* PredictionTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PredictionTableViewController.swift; sourceTree = ""; }; + 437DE503229C8375003B1074 /* copy-frameworks.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "copy-frameworks.sh"; sourceTree = ""; }; 438172D81F4E9E37003C3328 /* NewPumpEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewPumpEvent.swift; sourceTree = ""; }; 438849E91D297CB6003B3F23 /* NightscoutService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutService.swift; sourceTree = ""; }; 438849EB1D29EC34003B3F23 /* AmplitudeService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AmplitudeService.swift; sourceTree = ""; }; @@ -1049,24 +1050,24 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 43D9FFD621EAE05D00AF44BF /* LoopCore.framework in Frameworks */, - C1E2774822433D7A00354103 /* MKRingProgressView.framework in Frameworks */, - 892A5D5B222F0D7C008961AB /* LoopTestingKit.framework in Frameworks */, - 892A5D2A222EF60A008961AB /* MockKit.framework in Frameworks */, - 892A5D2C222EF60A008961AB /* MockKitUI.framework in Frameworks */, - 4F7528941DFE1E9500C322D6 /* LoopUI.framework in Frameworks */, 434FB6461D68F1CD007B9C70 /* Amplitude.framework in Frameworks */, - C1C7F1C1220D675800689850 /* OmniKit.framework in Frameworks */, 43A8EC6F210E622700A81379 /* CGMBLEKitUI.framework in Frameworks */, 438A95A81D8B9B24009D12E1 /* CGMBLEKit.framework in Frameworks */, 43C246A81D89990F0031F8D1 /* Crypto.framework in Frameworks */, 4D3B40041D4A9E1A00BC6334 /* G4ShareSpy.framework in Frameworks */, 43F5C2C91B929C09003EB13D /* HealthKit.framework in Frameworks */, + 43D9FFD621EAE05D00AF44BF /* LoopCore.framework in Frameworks */, 43F78D4F1C914197002152D1 /* LoopKit.framework in Frameworks */, + 892A5D5B222F0D7C008961AB /* LoopTestingKit.framework in Frameworks */, + 4F7528941DFE1E9500C322D6 /* LoopUI.framework in Frameworks */, 43CA93371CB98079000026B5 /* MinimedKit.framework in Frameworks */, 43709AEA20DF3F8200F941B3 /* MinimedKitUI.framework in Frameworks */, - C1C7F1C2220D675800689850 /* OmniKitUI.framework in Frameworks */, + C1E2774822433D7A00354103 /* MKRingProgressView.framework in Frameworks */, + 892A5D2A222EF60A008961AB /* MockKit.framework in Frameworks */, + 892A5D2C222EF60A008961AB /* MockKitUI.framework in Frameworks */, C10428971D17BAD400DD539A /* NightscoutUploadKit.framework in Frameworks */, + C1C7F1C1220D675800689850 /* OmniKit.framework in Frameworks */, + C1C7F1C2220D675800689850 /* OmniKitUI.framework in Frameworks */, 437AFEE42035252A008C4892 /* RileyLinkBLEKit.framework in Frameworks */, 43523EDB1CC35083001850F1 /* RileyLinkKit.framework in Frameworks */, C18852E22082AB1B00BECC8C /* RileyLinkKitUI.framework in Frameworks */, @@ -1141,13 +1142,13 @@ buildActionMask = 2147483647; files = ( 43D9000B21EB0BE000AF44BF /* LoopCore.framework in Frameworks */, + 43FCEEC022220D1F0013DD30 /* LoopKit.framework in Frameworks */, 43FCEEBF22220CF30013DD30 /* LoopKitUI.framework in Frameworks */, - C1C0BE2A224C0FA000C03B4D /* SwiftCharts.framework in Frameworks */, 4F7528951DFE1E9B00C322D6 /* LoopUI.framework in Frameworks */, 437AFEE520352591008C4892 /* NotificationCenter.framework in Frameworks */, C110A0E7221BC2400016560B /* OmniKit.framework in Frameworks */, C110A0E8221BC2430016560B /* OmniKitUI.framework in Frameworks */, - 43FCEEC022220D1F0013DD30 /* LoopKit.framework in Frameworks */, + C1C0BE2A224C0FA000C03B4D /* SwiftCharts.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1254,6 +1255,7 @@ 43D9FFA321EA9A0C00AF44BF /* Learn */, 968DCD53F724DE56FFE51920 /* Frameworks */, 43776F8D1B8022E90074EA36 /* Products */, + 437DE502229C834D003B1074 /* Scripts */, 437D9BA11D7B5203007245E8 /* Loop.xcconfig */, ); sourceTree = ""; @@ -1296,6 +1298,14 @@ path = Loop; sourceTree = ""; }; + 437DE502229C834D003B1074 /* Scripts */ = { + isa = PBXGroup; + children = ( + 437DE503229C8375003B1074 /* copy-frameworks.sh */, + ); + path = Scripts; + sourceTree = ""; + }; 43880F961D9D8052009061A8 /* ServiceAuthentication */ = { isa = PBXGroup; children = ( @@ -1772,11 +1782,8 @@ 968DCD53F724DE56FFE51920 /* Frameworks */ = { isa = PBXGroup; children = ( - C1C7F1BF220D675700689850 /* OmniKit.framework */, - C1C7F1C0220D675700689850 /* OmniKitUI.framework */, 434FB6451D68F1CD007B9C70 /* Amplitude.framework */, 4344628420A7A3BE00C4BE6F /* CGMBLEKit.framework */, - 438A95A71D8B9B24009D12E1 /* CGMBLEKit.framework */, 43A8EC6E210E622600A81379 /* CGMBLEKitUI.framework */, C1E2773D224177C000354103 /* ClockKit.framework */, 4344628120A7A37E00C4BE6F /* CoreBluetooth.framework */, @@ -1785,7 +1792,6 @@ 43D9002C21EB225D00AF44BF /* HealthKit.framework */, 43F5C2C81B929C09003EB13D /* HealthKit.framework */, 4344628320A7A3BE00C4BE6F /* LoopKit.framework */, - 43F78D4B1C914197002152D1 /* LoopKit.framework */, 437AFEE6203688CF008C4892 /* LoopKitUI.framework */, 892A5D5A222F0D7C008961AB /* LoopTestingKit.framework */, 43CA93361CB98079000026B5 /* MinimedKit.framework */, @@ -1795,12 +1801,16 @@ 892A5D2B222EF60A008961AB /* MockKitUI.framework */, C10428961D17BAD400DD539A /* NightscoutUploadKit.framework */, 4F70C1DD1DE8DCA7006380B7 /* NotificationCenter.framework */, + C1C7F1BF220D675700689850 /* OmniKit.framework */, + C1C7F1C0220D675700689850 /* OmniKitUI.framework */, 434AB0B11CBB4C3300422F4A /* RileyLinkBLEKit.framework */, 43523EDA1CC35083001850F1 /* RileyLinkKit.framework */, C18852E12082AB1A00BECC8C /* RileyLinkKitUI.framework */, 43B371871CE597D10013C5A6 /* ShareClient.framework */, 4379CFEF21112CF700AADC79 /* ShareClientUI.framework */, 4346D1EF1C781BEA00ABAFE3 /* SwiftCharts.framework */, + 438A95A71D8B9B24009D12E1 /* CGMBLEKit.framework */, + 43F78D4B1C914197002152D1 /* LoopKit.framework */, ); name = Frameworks; sourceTree = ""; @@ -2047,7 +2057,7 @@ }; 43776F8B1B8022E90074EA36 = { CreatedOnToolsVersion = 7.0; - LastSwiftMigration = 0800; + LastSwiftMigration = 1020; SystemCapabilities = { com.apple.ApplicationGroups.iOS = { enabled = 1; @@ -2080,7 +2090,7 @@ }; 43A9437D1B926B7B0051FA24 = { CreatedOnToolsVersion = 7.0; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; SystemCapabilities = { com.apple.ApplicationGroups.iOS = { enabled = 0; @@ -2100,10 +2110,12 @@ }; }; 43D9001A21EB209400AF44BF = { + LastSwiftMigration = 1020; ProvisioningStyle = Automatic; }; 43D9FFA121EA9A0C00AF44BF = { CreatedOnToolsVersion = 10.1; + LastSwiftMigration = 1020; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.ApplicationGroups.iOS = { @@ -2116,6 +2128,7 @@ }; 43D9FFCE21EAE05D00AF44BF = { CreatedOnToolsVersion = 10.1; + LastSwiftMigration = 1020; ProvisioningStyle = Automatic; }; 43E2D8D01D20BF42004DA55F = { @@ -2131,6 +2144,7 @@ }; 4F70C1DB1DE8DCA7006380B7 = { CreatedOnToolsVersion = 8.1; + LastSwiftMigration = 1020; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.ApplicationGroups.iOS = { @@ -2140,6 +2154,7 @@ }; 4F75288A1DFE1DC600C322D6 = { CreatedOnToolsVersion = 8.1; + LastSwiftMigration = 1020; ProvisioningStyle = Automatic; }; }; @@ -2384,30 +2399,10 @@ ); name = "Copy Frameworks with Carthage"; outputPaths = ( - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/CGMBLEKit.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/LoopKit.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/SwiftCharts.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/MinimedKit.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/RileyLinkBLEKit.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/RileyLinkKit.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/Amplitude.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/ShareClient.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/NightscoutUploadKit.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/Crypto.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/G4ShareSpy.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/RileyLinkKitUI.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/LoopKitUI.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/MinimedKitUI.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/CGMBLEKitUI.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/ShareClientUI.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/LoopTestingKit.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/MockKit.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/MockKitUI.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/MKRingProgressView.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "CARTHAGE_BUILD_DIR=\"${SRCROOT}/Carthage/Build\"\nif [ -n \"${IPHONEOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/iOS\"\nelif [ -n \"${WATCHOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/watchOS\"\nelse\n echo \"ERROR: Unexpected deployment target type\"\n exit 1\nfi\n\nfor SCRIPT_INPUT_FILE in ${!SCRIPT_INPUT_FILE_*}; do\n CARTHAGE_BUILD_FILE=\"${!SCRIPT_INPUT_FILE/${BUILT_PRODUCTS_DIR}/${CARTHAGE_BUILD_DIR}}\"\n if [ -e \"${CARTHAGE_BUILD_FILE}\" ]; then\n if [ -e \"${SCRIPT_INPUT_FILE}\" ]; then\n echo \"ERROR: Duplicate frameworks found at:\"\n echo \" ${SCRIPT_INPUT_FILE}\"\n echo \" ${CARTHAGE_BUILD_FILE}\"\n exit 1\n fi\n echo \"Substituting \\\"${CARTHAGE_BUILD_FILE}\\\" for \\\"${!SCRIPT_INPUT_FILE}\\\"\"\n export ${SCRIPT_INPUT_FILE}=\"${CARTHAGE_BUILD_FILE}\"\n fi\ndone\n\necho \"Copy Frameworks with Carthage\"\ncarthage copy-frameworks\n"; + shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n"; }; 43FF3DF620A8EFE800F8E62C /* Copy Frameworks with Carthage */ = { isa = PBXShellScriptBuildPhase; @@ -2421,13 +2416,10 @@ ); name = "Copy Frameworks with Carthage"; outputPaths = ( - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/CGMBLEKit.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/LoopKit.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/ShareClient.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "CARTHAGE_BUILD_DIR=\"${SRCROOT}/Carthage/Build\"\nif [ -n \"${IPHONEOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/iOS\"\nelif [ -n \"${WATCHOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/watchOS\"\nelse\n echo \"ERROR: Unexpected deployment target type\"\n exit 1\nfi\n\nfor SCRIPT_INPUT_FILE in ${!SCRIPT_INPUT_FILE_*}; do\n CARTHAGE_BUILD_FILE=\"${!SCRIPT_INPUT_FILE/${BUILT_PRODUCTS_DIR}/${CARTHAGE_BUILD_DIR}}\"\n if [ -e \"${CARTHAGE_BUILD_FILE}\" ]; then\n if [ -e \"${SCRIPT_INPUT_FILE}\" ]; then\n echo \"ERROR: Duplicate frameworks found at:\"\n echo \" ${SCRIPT_INPUT_FILE}\"\n echo \" ${CARTHAGE_BUILD_FILE}\"\n exit 1\n fi\n echo \"Substituting \\\"${CARTHAGE_BUILD_FILE}\\\" for \\\"${!SCRIPT_INPUT_FILE}\\\"\"\n export ${SCRIPT_INPUT_FILE}=\"${CARTHAGE_BUILD_FILE}\"\n fi\ndone\n\necho \"Copy Frameworks with Carthage\"\ncarthage copy-frameworks\n"; + shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n"; }; A942E448225FD9D500DD4980 /* Copy Frameworks with Carthage */ = { isa = PBXShellScriptBuildPhase; @@ -3247,7 +3239,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; WARNING_CFLAGS = "-Wall"; WATCHOS_DEPLOYMENT_TARGET = 4.1; @@ -3312,7 +3304,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; WARNING_CFLAGS = "-Wall"; @@ -3327,7 +3319,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_ENTITLEMENTS = Loop/Loop.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = UY678SP37Q; INFOPLIST_FILE = Loop/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; "OTHER_SWIFT_FLAGS[arch=*]" = "-DDEBUG"; @@ -3345,7 +3337,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_ENTITLEMENTS = Loop/Loop.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = UY678SP37Q; INFOPLIST_FILE = Loop/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER)"; @@ -3362,7 +3354,7 @@ CODE_SIGN_ENTITLEMENTS = "WatchApp Extension/WatchApp Extension.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = UY678SP37Q; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; @@ -3373,7 +3365,6 @@ SKIP_INSTALL = YES; SWIFT_OBJC_BRIDGING_HEADER = "WatchApp Extension/Extensions/WatchApp Extension-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = 4; }; name = Debug; @@ -3386,7 +3377,7 @@ CODE_SIGN_ENTITLEMENTS = "WatchApp Extension/WatchApp Extension.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = UY678SP37Q; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; @@ -3396,7 +3387,6 @@ SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_OBJC_BRIDGING_HEADER = "WatchApp Extension/Extensions/WatchApp Extension-Bridging-Header.h"; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = 4; }; name = Release; @@ -3408,7 +3398,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = UY678SP37Q; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; IBSC_MODULE = WatchApp_Extension; INFOPLIST_FILE = WatchApp/Info.plist; @@ -3429,7 +3419,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = UY678SP37Q; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; IBSC_MODULE = WatchApp_Extension; INFOPLIST_FILE = WatchApp/Info.plist; @@ -3537,7 +3527,6 @@ PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER)Learn"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -3564,7 +3553,6 @@ MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER)Learn"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -3710,7 +3698,7 @@ CODE_SIGN_ENTITLEMENTS = "Loop Status Extension/Loop Status Extension.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = UY678SP37Q; INFOPLIST_FILE = "Loop Status Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER).statuswidget"; @@ -3729,7 +3717,7 @@ CODE_SIGN_ENTITLEMENTS = "Loop Status Extension/Loop Status Extension.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = UY678SP37Q; INFOPLIST_FILE = "Loop Status Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER).statuswidget"; diff --git a/Loop.xcodeproj/xcshareddata/xcschemes/Loop.xcscheme b/Loop.xcodeproj/xcshareddata/xcschemes/Loop.xcscheme index e61d35fb8c..e542777bd7 100644 --- a/Loop.xcodeproj/xcshareddata/xcschemes/Loop.xcscheme +++ b/Loop.xcodeproj/xcshareddata/xcschemes/Loop.xcscheme @@ -20,20 +20,6 @@ ReferencedContainer = "container:Loop.xcodeproj"> - - - - Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window?.tintColor = UIColor.tintColor NotificationManager.authorize(delegate: self) @@ -56,7 +56,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { // MARK: - Continuity - func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool { + func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { if #available(iOS 12.0, *) { if userActivity.activityType == NewCarbEntryIntent.className { diff --git a/Loop/Extensions/RangeReplaceableCollection.swift b/Loop/Extensions/RangeReplaceableCollection.swift index d4437a9555..b27cf69f7a 100644 --- a/Loop/Extensions/RangeReplaceableCollection.swift +++ b/Loop/Extensions/RangeReplaceableCollection.swift @@ -10,7 +10,7 @@ extension RangeReplaceableCollection where Element: Equatable { /// Returns `true` if the element was removed, or `false` if it is not present in the collection. @discardableResult mutating func remove(_ element: Element) -> Bool { - guard let index = self.index(of: element) else { + guard let index = self.firstIndex(of: element) else { return false } diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 15fa94e6c3..f4da064454 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -225,7 +225,7 @@ final class LoopDataManager { // MARK: - Background task management - private var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid + private var backgroundTask: UIBackgroundTaskIdentifier = .invalid private func startBackgroundTask() { endBackgroundTask() @@ -235,9 +235,9 @@ final class LoopDataManager { } private func endBackgroundTask() { - if backgroundTask != UIBackgroundTaskInvalid { + if backgroundTask != .invalid { UIApplication.shared.endBackgroundTask(backgroundTask) - backgroundTask = UIBackgroundTaskInvalid + backgroundTask = .invalid } } } diff --git a/Loop/Managers/NotificationManager.swift b/Loop/Managers/NotificationManager.swift index fe63747f37..8f93f322ec 100644 --- a/Loop/Managers/NotificationManager.swift +++ b/Loop/Managers/NotificationManager.swift @@ -78,7 +78,7 @@ struct NotificationManager { notification.body = error.localizedDescription } - notification.sound = UNNotificationSound.default() + notification.sound = .default if startDate.timeIntervalSinceNow >= TimeInterval(minutes: -5) { notification.categoryIdentifier = LoopNotificationCategory.bolusFailure.rawValue @@ -122,7 +122,7 @@ struct NotificationManager { } notification.title = NSLocalizedString("Loop Failure", comment: "The notification title for a loop failure") - notification.sound = UNNotificationSound.default() + notification.sound = .default notification.categoryIdentifier = LoopNotificationCategory.loopNotRunning.rawValue notification.threadIdentifier = LoopNotificationCategory.loopNotRunning.rawValue @@ -157,7 +157,7 @@ struct NotificationManager { notification.title = NSLocalizedString("Pump Battery Low", comment: "The notification title for a low pump battery") notification.body = NSLocalizedString("Change the pump battery immediately", comment: "The notification alert describing a low pump battery") - notification.sound = UNNotificationSound.default() + notification.sound = .default notification.categoryIdentifier = LoopNotificationCategory.pumpBatteryLow.rawValue let request = UNNotificationRequest( @@ -178,7 +178,7 @@ struct NotificationManager { notification.title = NSLocalizedString("Pump Reservoir Empty", comment: "The notification title for an empty pump reservoir") notification.body = NSLocalizedString("Change the pump reservoir now", comment: "The notification alert describing an empty pump reservoir") - notification.sound = UNNotificationSound.default() + notification.sound = .default notification.categoryIdentifier = LoopNotificationCategory.pumpReservoirEmpty.rawValue let request = UNNotificationRequest( @@ -211,7 +211,7 @@ struct NotificationManager { notification.body = String(format: NSLocalizedString("%1$@ U left", comment: "Low reservoir alert format string. (1: Number of units remaining)"), unitsString) } - notification.sound = UNNotificationSound.default() + notification.sound = .default notification.categoryIdentifier = LoopNotificationCategory.pumpReservoirLow.rawValue let request = UNNotificationRequest( diff --git a/Loop/Managers/WatchDataManager.swift b/Loop/Managers/WatchDataManager.swift index 85b7868116..527f4d8ea3 100644 --- a/Loop/Managers/WatchDataManager.swift +++ b/Loop/Managers/WatchDataManager.swift @@ -279,6 +279,8 @@ extension WatchDataManager: WCSessionDelegate { } case .inactive, .notActivated: break + @unknown default: + break } } diff --git a/Loop/View Controllers/CarbAbsorptionViewController.swift b/Loop/View Controllers/CarbAbsorptionViewController.swift index 1ea685f4b5..5f2a8fefd9 100644 --- a/Loop/View Controllers/CarbAbsorptionViewController.swift +++ b/Loop/View Controllers/CarbAbsorptionViewController.swift @@ -56,7 +56,7 @@ final class CarbAbsorptionViewController: ChartsTableViewController, Identifiabl navigationItem.rightBarButtonItems?.append(editButtonItem) - tableView.rowHeight = UITableViewAutomaticDimension + tableView.rowHeight = UITableView.automaticDimension reloadData(animated: false) } @@ -432,7 +432,7 @@ final class CarbAbsorptionViewController: ChartsTableViewController, Identifiabl } } - public override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { + public override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { let status = carbStatuses[indexPath.row] deviceManager.loopManager.carbStore.deleteCarbEntry(status.entry) { (result) -> Void in diff --git a/Loop/View Controllers/ChartsTableViewController.swift b/Loop/View Controllers/ChartsTableViewController.swift index 44f6f31c78..6bcb64e037 100644 --- a/Loop/View Controllers/ChartsTableViewController.swift +++ b/Loop/View Controllers/ChartsTableViewController.swift @@ -54,7 +54,7 @@ extension RefreshContext: Hashable { extension Set where Element == RefreshContext { /// Returns the size value in the set if one exists var newSize: CGSize? { - guard let index = index(of: .size(.zero)), + guard let index = firstIndex(of: .size(.zero)), case .size(let size) = self[index] else { return nil @@ -90,10 +90,10 @@ class ChartsTableViewController: UITableViewController, UIGestureRecognizerDeleg let notificationCenter = NotificationCenter.default notificationObservers += [ - notificationCenter.addObserver(forName: .UIApplicationDidEnterBackground, object: UIApplication.shared, queue: .main) { [weak self] _ in + notificationCenter.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: UIApplication.shared, queue: .main) { [weak self] _ in self?.active = false }, - notificationCenter.addObserver(forName: .UIApplicationDidBecomeActive, object: UIApplication.shared, queue: .main) { [weak self] _ in + notificationCenter.addObserver(forName: UIApplication.didBecomeActiveNotification, object: UIApplication.shared, queue: .main) { [weak self] _ in self?.active = true } ] @@ -176,7 +176,7 @@ class ChartsTableViewController: UITableViewController, UIGestureRecognizerDeleg return UIApplication.shared.applicationState == .active } set { - log.debug("[reloadData] for app change to active: %d", active) + log.debug("[reloadData] for app change to active: %d, applicationState: %d", newValue, active) reloadData() } } @@ -230,6 +230,8 @@ class ChartsTableViewController: UITableViewController, UIGestureRecognizerDeleg row.subtitleLabel?.alpha = alpha }) } + @unknown default: + break } } } diff --git a/Loop/View Controllers/InsulinModelSettingsViewController.swift b/Loop/View Controllers/InsulinModelSettingsViewController.swift index 499d72e652..4b60fcf66c 100644 --- a/Loop/View Controllers/InsulinModelSettingsViewController.swift +++ b/Loop/View Controllers/InsulinModelSettingsViewController.swift @@ -105,7 +105,7 @@ class InsulinModelSettingsViewController: ChartsTableViewController, Identifiabl override func viewDidLoad() { super.viewDidLoad() - tableView.rowHeight = UITableViewAutomaticDimension + tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 91 } diff --git a/Loop/View Controllers/PredictionTableViewController.swift b/Loop/View Controllers/PredictionTableViewController.swift index b1801d5c83..34238fc7c5 100644 --- a/Loop/View Controllers/PredictionTableViewController.swift +++ b/Loop/View Controllers/PredictionTableViewController.swift @@ -24,7 +24,7 @@ class PredictionTableViewController: ChartsTableViewController, IdentifiableClas override func viewDidLoad() { super.viewDidLoad() - tableView.rowHeight = UITableViewAutomaticDimension + tableView.rowHeight = UITableView.automaticDimension tableView.cellLayoutMarginsFollowReadableWidth = true glucoseChart.glucoseDisplayRange = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 60)...HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 200) diff --git a/Loop/View Controllers/SettingsTableViewController.swift b/Loop/View Controllers/SettingsTableViewController.swift index 06825eb8a8..36e1107b3f 100644 --- a/Loop/View Controllers/SettingsTableViewController.swift +++ b/Loop/View Controllers/SettingsTableViewController.swift @@ -21,7 +21,7 @@ final class SettingsTableViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() - tableView.rowHeight = UITableViewAutomaticDimension + tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 44 tableView.register(SettingsTableViewCell.self, forCellReuseIdentifier: SettingsTableViewCell.className) @@ -622,11 +622,11 @@ final class SettingsTableViewController: UITableViewController { case .loop: break case .pump: - let previousTestingPumpDataDeletionSection = sections.index(of: .testingPumpDataDeletion) + let previousTestingPumpDataDeletionSection = sections.firstIndex(of: .testingPumpDataDeletion) let wasTestingPumpManager = isTestingPumpManager isTestingPumpManager = dataManager.pumpManager is TestingPumpManager if !wasTestingPumpManager, isTestingPumpManager { - guard let testingPumpDataDeletionSection = sections.index(of: .testingPumpDataDeletion) else { + guard let testingPumpDataDeletionSection = sections.firstIndex(of: .testingPumpDataDeletion) else { fatalError("Expected to find testing pump data deletion section with testing pump in use") } tableView.insertSections([testingPumpDataDeletionSection], with: .automatic) @@ -639,11 +639,11 @@ final class SettingsTableViewController: UITableViewController { tableView.reloadSections([Section.pump.rawValue], with: .fade) tableView.reloadSections([Section.cgm.rawValue], with: .fade) case .cgm: - let previousTestingCGMDataDeletionSection = sections.index(of: .testingCGMDataDeletion) + let previousTestingCGMDataDeletionSection = sections.firstIndex(of: .testingCGMDataDeletion) let wasTestingCGMManager = isTestingCGMManager isTestingCGMManager = dataManager.cgmManager is TestingCGMManager if !wasTestingCGMManager, isTestingCGMManager { - guard let testingCGMDataDeletionSection = sections.index(of: .testingCGMDataDeletion) else { + guard let testingCGMDataDeletionSection = sections.firstIndex(of: .testingCGMDataDeletion) else { fatalError("Expected to find testing CGM data deletion section with testing CGM in use") } tableView.insertSections([testingCGMDataDeletionSection], with: .automatic) diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index 926f240454..dbcd4d0673 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -629,7 +629,7 @@ final class StatusTableViewController: ChartsTableViewController { } else if let cell = tableView.cellForRow(at: statusIndexPath) { // If only the enacting state changed, update the activity indicator if isEnacting { - let indicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray) + let indicatorView = UIActivityIndicatorView(style: .gray) indicatorView.startAnimating() cell.accessoryView = indicatorView } else { @@ -790,7 +790,7 @@ final class StatusTableViewController: ChartsTableViewController { cell.selectionStyle = .default if enacting { - let indicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray) + let indicatorView = UIActivityIndicatorView(style: .gray) indicatorView.startAnimating() cell.accessoryView = indicatorView } else { @@ -826,7 +826,7 @@ final class StatusTableViewController: ChartsTableViewController { cell.titleLabel.text = NSLocalizedString("Starting Bolus", comment: "The title of the cell indicating a bolus is being sent") cell.subtitleLabel.text = nil - let indicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray) + let indicatorView = UIActivityIndicatorView(style: .gray) indicatorView.startAnimating() cell.accessoryView = indicatorView return cell @@ -843,7 +843,7 @@ final class StatusTableViewController: ChartsTableViewController { cell.titleLabel.text = NSLocalizedString("Canceling Bolus", comment: "The title of the cell indicating a bolus is being canceled") cell.subtitleLabel.text = nil - let indicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray) + let indicatorView = UIActivityIndicatorView(style: .gray) indicatorView.startAnimating() cell.accessoryView = indicatorView return cell @@ -852,7 +852,7 @@ final class StatusTableViewController: ChartsTableViewController { cell.titleLabel.text = NSLocalizedString("Pump Suspended", comment: "The title of the cell indicating the pump is suspended") if resuming { - let indicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray) + let indicatorView = UIActivityIndicatorView(style: .gray) indicatorView.startAnimating() cell.accessoryView = indicatorView cell.subtitleLabel.text = "" @@ -910,7 +910,7 @@ final class StatusTableViewController: ChartsTableViewController { switch Section(rawValue: indexPath.section)! { case .charts: // Compute the height of the HUD, defaulting to 70 - let hudHeight = ceil(hudView?.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height ?? 70) + let hudHeight = ceil(hudView?.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height ?? 70) var availableSize = max(tableView.bounds.width, tableView.bounds.height) if #available(iOS 11.0, *) { @@ -928,7 +928,7 @@ final class StatusTableViewController: ChartsTableViewController { return max(106, 0.21 * availableSize) } case .hud, .status: - return UITableViewAutomaticDimension + return UITableView.automaticDimension } } @@ -1124,7 +1124,7 @@ final class StatusTableViewController: ChartsTableViewController { item.accessibilityLabel = NSLocalizedString("Pre-Meal Targets", comment: "The label of the pre-meal mode toggle button") if selected { - item.accessibilityTraits = item.accessibilityTraits | UIAccessibilityTraitSelected + item.accessibilityTraits.insert(.selected) item.accessibilityHint = NSLocalizedString("Disables", comment: "The action hint of the workout mode toggle button when enabled") } else { item.accessibilityHint = NSLocalizedString("Enables", comment: "The action hint of the workout mode toggle button when disabled") @@ -1140,7 +1140,7 @@ final class StatusTableViewController: ChartsTableViewController { item.accessibilityLabel = NSLocalizedString("Workout Targets", comment: "The label of the workout mode toggle button") if selected { - item.accessibilityTraits = item.accessibilityTraits | UIAccessibilityTraitSelected + item.accessibilityTraits.insert(.selected) item.accessibilityHint = NSLocalizedString("Disables", comment: "The action hint of the workout mode toggle button when enabled") } else { item.accessibilityHint = NSLocalizedString("Enables", comment: "The action hint of the workout mode toggle button when disabled") @@ -1271,7 +1271,7 @@ final class StatusTableViewController: ChartsTableViewController { // MARK: - Testing scenarios - override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) { + override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) { if let testingScenariosManager = deviceManager.testingScenariosManager, testingScenariosManager.scenarioURLs.isEmpty { if motion == .motionShake { presentScenarioSelector() diff --git a/Loop/Views/SettingsImageTableViewCell.swift b/Loop/Views/SettingsImageTableViewCell.swift index 1b997ae9fb..d62777f1c5 100644 --- a/Loop/Views/SettingsImageTableViewCell.swift +++ b/Loop/Views/SettingsImageTableViewCell.swift @@ -9,7 +9,7 @@ import UIKit class SettingsImageTableViewCell: UITableViewCell { - override init(style: UITableViewCellStyle, reuseIdentifier: String?) { + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: .default, reuseIdentifier: reuseIdentifier) setup() @@ -37,7 +37,7 @@ class SettingsImageTableViewCell: UITableViewCell { imageView.topAnchor.constraint(greaterThanOrEqualTo: parent.topAnchor), parent.bottomAnchor.constraint(greaterThanOrEqualTo: imageView.bottomAnchor), imageView.centerYAnchor.constraint(equalTo: parent.centerYAnchor), - textLabel.leadingAnchor.constraintEqualToSystemSpacingAfter(imageView.trailingAnchor, multiplier: 2), + textLabel.leadingAnchor.constraint(equalToSystemSpacingAfter: imageView.trailingAnchor, multiplier: 2), textLabel.topAnchor.constraint(greaterThanOrEqualTo: parent.topAnchor), parent.bottomAnchor.constraint(greaterThanOrEqualTo: textLabel.bottomAnchor), parent.trailingAnchor.constraint(equalTo: textLabel.trailingAnchor), diff --git a/LoopUI/Charts/ChartsManager.swift b/LoopUI/Charts/ChartsManager.swift index b7546c14bd..0c7897e8d5 100644 --- a/LoopUI/Charts/ChartsManager.swift +++ b/LoopUI/Charts/ChartsManager.swift @@ -20,7 +20,7 @@ open class ChartsManager { private lazy var timeFormatter: DateFormatter = { let formatter = DateFormatter() let dateFormat = DateFormatter.dateFormat(fromTemplate: "j", options: 0, locale: Locale.current)! - let isAmPmTimeFormat = dateFormat.index( of: "a") != nil + let isAmPmTimeFormat = dateFormat.firstIndex(of: "a") != nil formatter.dateFormat = isAmPmTimeFormat ? "h a" : "H:mm" diff --git a/LoopUI/Views/BasalStateView.swift b/LoopUI/Views/BasalStateView.swift index 411eb518dd..15d51c7bf5 100644 --- a/LoopUI/Views/BasalStateView.swift +++ b/LoopUI/Views/BasalStateView.swift @@ -83,7 +83,7 @@ public final class BasalStateView: UIView { animation.fromValue = shapeLayer.path ?? drawPath() animation.toValue = path animation.duration = 1 - animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) shapeLayer.add(animation, forKey: type(of: self).AnimationKey) } diff --git a/LoopUI/Views/HUDView.swift b/LoopUI/Views/HUDView.swift index 3dc832c4a8..eaa8c88301 100644 --- a/LoopUI/Views/HUDView.swift +++ b/LoopUI/Views/HUDView.swift @@ -18,14 +18,16 @@ public class HUDView: UIView, NibLoadable { func setup() { stackView = (HUDView.nib().instantiate(withOwner: self, options: nil)[0] as! UIStackView) + stackView.translatesAutoresizingMaskIntoConstraints = false self.addSubview(stackView) // Use AutoLayout to have the stack view fill its entire container. - let horizontalConstraint = NSLayoutConstraint(item: stackView, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0) - let verticalConstraint = NSLayoutConstraint(item: stackView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0) - let widthConstraint = NSLayoutConstraint(item: stackView, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1, constant: 0) - let heightConstraint = NSLayoutConstraint(item: stackView, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 1, constant: 0) - self.addConstraints([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint]) + NSLayoutConstraint.activate([ + stackView.centerXAnchor.constraint(equalTo: centerXAnchor), + stackView.centerYAnchor.constraint(equalTo: centerYAnchor), + stackView.widthAnchor.constraint(equalTo: widthAnchor), + stackView.heightAnchor.constraint(equalTo: heightAnchor), + ]) } public func removePumpManagerProvidedViews() { diff --git a/LoopUI/Views/LoopCompletionHUDView.swift b/LoopUI/Views/LoopCompletionHUDView.swift index 3fbdd1aff4..1f62d14dde 100644 --- a/LoopUI/Views/LoopCompletionHUDView.swift +++ b/LoopUI/Views/LoopCompletionHUDView.swift @@ -104,7 +104,7 @@ public final class LoopCompletionHUDView: BaseHUDView { ) updateTimer = timer - RunLoop.main.add(timer, forMode: .defaultRunLoopMode) + RunLoop.main.add(timer, forMode: .default) } private var updateTimer: Timer? { diff --git a/LoopUI/Views/LoopStateView.swift b/LoopUI/Views/LoopStateView.swift index beadc42a93..eedc483de4 100644 --- a/LoopUI/Views/LoopStateView.swift +++ b/LoopUI/Views/LoopStateView.swift @@ -101,7 +101,7 @@ final class LoopStateView: UIView { group.duration = firstDataUpdate ? 0 : 1 group.repeatCount = HUGE group.autoreverses = true - group.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + group.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) shapeLayer.add(group, forKey: type(of: self).AnimationKey) } else { diff --git a/Scripts/copy-frameworks.sh b/Scripts/copy-frameworks.sh new file mode 100755 index 0000000000..a8c503f8ee --- /dev/null +++ b/Scripts/copy-frameworks.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +# copy-frameworks.sh +# Loop +# +# Copyright © 2019 LoopKit Authors. All rights reserved. + +date + +CARTHAGE_BUILD_DIR="${SRCROOT}/Carthage/Build" +if [ -n "${IPHONEOS_DEPLOYMENT_TARGET}" ]; then + CARTHAGE_BUILD_DIR="${CARTHAGE_BUILD_DIR}/iOS" +elif [ -n "${WATCHOS_DEPLOYMENT_TARGET}" ]; then + CARTHAGE_BUILD_DIR="${CARTHAGE_BUILD_DIR}/watchOS" +else + echo "ERROR: Unexpected deployment target type" + exit 1 +fi + +for COUNTER in $(seq 0 $(($SCRIPT_INPUT_FILE_COUNT - 1))); do + SCRIPT_INPUT_FILE="SCRIPT_INPUT_FILE_${COUNTER}" + CARTHAGE_BUILD_FILE="${!SCRIPT_INPUT_FILE/${BUILT_PRODUCTS_DIR}/${CARTHAGE_BUILD_DIR}}" + if [ -e "${CARTHAGE_BUILD_FILE}" ]; then + if [ -e "${SCRIPT_INPUT_FILE}" ]; then + echo "ERROR: Duplicate frameworks found at:" + echo " ${SCRIPT_INPUT_FILE}" + echo " ${CARTHAGE_BUILD_FILE}" + exit 1 + fi + echo "Substituting \"${CARTHAGE_BUILD_FILE}\" for \"${!SCRIPT_INPUT_FILE}\"" + export ${SCRIPT_INPUT_FILE}="${CARTHAGE_BUILD_FILE}" + elif [ -e "${!SCRIPT_INPUT_FILE}" ]; then + echo "Using original path: \"${!SCRIPT_INPUT_FILE}\"" + else + echo "ERROR: Input file not found at \"${!SCRIPT_INPUT_FILE}\"" + exit 1 + fi +done + +echo "Copy Frameworks with Carthage" +carthage copy-frameworks diff --git a/WatchApp Extension/ComplicationController.swift b/WatchApp Extension/ComplicationController.swift index 12c30e9d98..cab92ddae8 100644 --- a/WatchApp Extension/ComplicationController.swift +++ b/WatchApp Extension/ComplicationController.swift @@ -204,6 +204,8 @@ final class ComplicationController: NSObject, CLKComplicationDataSource { } else { return nil } + @unknown default: + return nil } } } diff --git a/WatchApp Extension/Controllers/HUDRowController.swift b/WatchApp Extension/Controllers/HUDRowController.swift index 2a8db754fb..ba7752ec3b 100644 --- a/WatchApp Extension/Controllers/HUDRowController.swift +++ b/WatchApp Extension/Controllers/HUDRowController.swift @@ -111,12 +111,14 @@ fileprivate extension NSDirectionalEdgeInsets { let right: CGFloat switch WKInterfaceDevice.current().layoutDirection { - case .leftToRight: - left = leading - right = trailing case .rightToLeft: right = leading left = trailing + case .leftToRight: + fallthrough + @unknown default: + left = leading + right = trailing } return UIEdgeInsets(top: top, left: left, bottom: bottom, right: right) diff --git a/WatchApp Extension/Extensions/CLKComplicationTemplate.swift b/WatchApp Extension/Extensions/CLKComplicationTemplate.swift index debd12c2c4..57c293029a 100644 --- a/WatchApp Extension/Extensions/CLKComplicationTemplate.swift +++ b/WatchApp Extension/Extensions/CLKComplicationTemplate.swift @@ -141,6 +141,8 @@ extension CLKComplicationTemplate { } else { return nil } + @unknown default: + return nil } } } From 406c6f9fe35b3e3701f478681e596b6b82283196 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 22 Jul 2019 12:07:35 -0500 Subject: [PATCH 059/132] Use new ISF and Correction Range interfaces with guardrails (#21) --- .../SettingsTableViewController.swift | 87 +++++++++---------- LoopCore/LoopSettings.swift | 27 +++++- 2 files changed, 67 insertions(+), 47 deletions(-) diff --git a/Loop/View Controllers/SettingsTableViewController.swift b/Loop/View Controllers/SettingsTableViewController.swift index 9f103843e0..06825eb8a8 100644 --- a/Loop/View Controllers/SettingsTableViewController.swift +++ b/Loop/View Controllers/SettingsTableViewController.swift @@ -239,10 +239,13 @@ final class SettingsTableViewController: UITableViewController { if let insulinSensitivitySchedule = dataManager.loopManager.insulinSensitivitySchedule { let unit = insulinSensitivitySchedule.unit - let value = valueNumberFormatter.string(from: insulinSensitivitySchedule.averageQuantity().doubleValue(for: unit)) ?? SettingsTableViewCell.NoValueString - - configCell.detailTextLabel?.text = String(format: NSLocalizedString("%1$@ %2$@/U", comment: "Format string for insulin sensitivity average (1: value)(2: glucose unit)"), value, unit.localizedShortUnitString - ) + // Schedule is in mg/dL or mmol/L, but we display as mg/dL/U or mmol/L/U + let average = insulinSensitivitySchedule.averageQuantity().doubleValue(for: unit) + let unitPerU = unit.unitDivided(by: .internationalUnit()) + let averageQuantity = HKQuantity(unit: unitPerU, doubleValue: average) + let formatter = QuantityFormatter() + formatter.setPreferredNumberFormatter(for: unit) + configCell.detailTextLabel?.text = formatter.string(from: averageQuantity, for: unitPerU) } else { configCell.detailTextLabel?.text = SettingsTableViewCell.TapToSetString } @@ -470,29 +473,22 @@ final class SettingsTableViewController: UITableViewController { show(scheduleVC, sender: sender) case .insulinSensitivity: - let scheduleVC = DailyQuantityScheduleTableViewController() + let unit = dataManager.loopManager.insulinSensitivitySchedule?.unit ?? dataManager.loopManager.glucoseStore.preferredUnit ?? HKUnit.milligramsPerDeciliter + let allowedSensitivityValues = dataManager.loopManager.settings.allowedSensitivityValues(for: unit) + let scheduleVC = InsulinSensitivityScheduleViewController(allowedValues: allowedSensitivityValues, unit: unit) scheduleVC.delegate = self + scheduleVC.insulinSensitivityScheduleStorageDelegate = self scheduleVC.title = NSLocalizedString("Insulin Sensitivities", comment: "The title of the insulin sensitivities schedule screen") - if let schedule = dataManager.loopManager.insulinSensitivitySchedule { - scheduleVC.timeZone = schedule.timeZone - scheduleVC.scheduleItems = schedule.items - scheduleVC.unit = schedule.unit - - show(scheduleVC, sender: sender) - } else { - if let timeZone = dataManager.pumpManager?.status.timeZone { - scheduleVC.timeZone = timeZone - } + scheduleVC.schedule = dataManager.loopManager.insulinSensitivitySchedule - if let unit = dataManager.loopManager.glucoseStore.preferredUnit { - scheduleVC.unit = unit - self.show(scheduleVC, sender: sender) - } - } + show(scheduleVC, sender: sender) case .glucoseTargetRange: - let scheduleVC = GlucoseRangeScheduleTableViewController() + let unit = dataManager.loopManager.settings.glucoseTargetRangeSchedule?.unit ?? dataManager.loopManager.glucoseStore.preferredUnit ?? HKUnit.milligramsPerDeciliter + let allowedCorrectionRangeValues = dataManager.loopManager.settings.allowedCorrectionRangeValues(for: unit) + let scheduleVC = GlucoseRangeScheduleTableViewController(allowedValues: allowedCorrectionRangeValues, unit: unit) + if FeatureFlags.sensitivityOverridesEnabled { scheduleVC.overrideContexts.remove(.legacyWorkout) } @@ -501,23 +497,13 @@ final class SettingsTableViewController: UITableViewController { scheduleVC.title = NSLocalizedString("Correction Range", comment: "The title of the glucose target range schedule screen") if let schedule = dataManager.loopManager.settings.glucoseTargetRangeSchedule { - scheduleVC.timeZone = schedule.timeZone - scheduleVC.scheduleItems = schedule.items - scheduleVC.unit = schedule.unit - scheduleVC.overrideRanges[.preMeal] = dataManager.loopManager.settings.preMealTargetRange.filter { !$0.isZero } - scheduleVC.overrideRanges[.legacyWorkout] = dataManager.loopManager.settings.legacyWorkoutTargetRange.filter { !$0.isZero } - - show(scheduleVC, sender: sender) - } else { - if let timeZone = dataManager.pumpManager?.status.timeZone { - scheduleVC.timeZone = timeZone - } - - if let unit = dataManager.loopManager.glucoseStore.preferredUnit { - scheduleVC.unit = unit - self.show(scheduleVC, sender: sender) - } + var overrides: [TemporaryScheduleOverride.Context: DoubleRange] = [:] + overrides[.preMeal] = dataManager.loopManager.settings.preMealTargetRange.filter { !$0.isZero } + overrides[.legacyWorkout] = dataManager.loopManager.settings.legacyWorkoutTargetRange.filter { !$0.isZero } + scheduleVC.setSchedule(schedule, withOverrideRanges: overrides) } + + show(scheduleVC, sender: sender) case .suspendThreshold: if let minBGGuard = dataManager.loopManager.settings.suspendThreshold { let vc = GlucoseThresholdTableViewController(threshold: minBGGuard.value, glucoseUnit: minBGGuard.unit) @@ -750,12 +736,6 @@ extension SettingsTableViewController: DailyValueScheduleTableViewControllerDele switch sections[indexPath.section] { case .configuration: switch ConfigurationRow(rawValue: indexPath.row)! { - case .glucoseTargetRange: - if let controller = controller as? GlucoseRangeScheduleTableViewController { - dataManager.loopManager.settings.preMealTargetRange = controller.overrideRanges[.preMeal].filter { !$0.isZero } - dataManager.loopManager.settings.legacyWorkoutTargetRange = controller.overrideRanges[.legacyWorkout].filter { !$0.isZero } - dataManager.loopManager.settings.glucoseTargetRangeSchedule = GlucoseRangeSchedule(unit: controller.unit, dailyItems: controller.scheduleItems, timeZone: controller.timeZone) - } case .basalRate: if let controller = controller as? BasalScheduleTableViewController { dataManager.loopManager.basalRateSchedule = BasalRateSchedule(dailyItems: controller.scheduleItems, timeZone: controller.timeZone) @@ -766,9 +746,6 @@ extension SettingsTableViewController: DailyValueScheduleTableViewControllerDele case .carbRatio: dataManager.loopManager.carbRatioSchedule = CarbRatioSchedule(unit: controller.unit, dailyItems: controller.scheduleItems, timeZone: controller.timeZone) AnalyticsManager.shared.didChangeCarbRatioSchedule() - case .insulinSensitivity: - dataManager.loopManager.insulinSensitivitySchedule = InsulinSensitivitySchedule(unit: controller.unit, dailyItems: controller.scheduleItems, timeZone: controller.timeZone) - AnalyticsManager.shared.didChangeInsulinSensitivitySchedule() default: break } @@ -782,6 +759,24 @@ extension SettingsTableViewController: DailyValueScheduleTableViewControllerDele } } +extension SettingsTableViewController: GlucoseRangeScheduleStorageDelegate { + func saveSchedule(for viewController: GlucoseRangeScheduleTableViewController, completion: @escaping (SaveGlucoseRangeScheduleResult) -> Void) { + dataManager.loopManager.settings.preMealTargetRange = viewController.overrideRanges[.preMeal].filter { !$0.isZero } + dataManager.loopManager.settings.legacyWorkoutTargetRange = viewController.overrideRanges[.legacyWorkout].filter { !$0.isZero } + dataManager.loopManager.settings.glucoseTargetRangeSchedule = viewController.schedule + completion(.success) + tableView.reloadRows(at: [IndexPath(row: ConfigurationRow.glucoseTargetRange.rawValue, section: Section.configuration.rawValue)], with: .none) + } +} + +extension SettingsTableViewController: InsulinSensitivityScheduleStorageDelegate { + func saveSchedule(_ schedule: InsulinSensitivitySchedule, for viewController: InsulinSensitivityScheduleViewController, completion: @escaping (SaveInsulinSensitivityScheduleResult) -> Void) { + dataManager.loopManager.insulinSensitivitySchedule = schedule + AnalyticsManager.shared.didChangeInsulinSensitivitySchedule() + completion(.success) + tableView.reloadRows(at: [IndexPath(row: ConfigurationRow.insulinSensitivity.rawValue, section: Section.configuration.rawValue)], with: .none) + } +} extension SettingsTableViewController: InsulinModelSettingsViewControllerDelegate { func insulinModelSettingsViewControllerDidChangeValue(_ controller: InsulinModelSettingsViewController) { diff --git a/LoopCore/LoopSettings.swift b/LoopCore/LoopSettings.swift index 31cb152c70..daefa83cee 100644 --- a/LoopCore/LoopSettings.swift +++ b/LoopCore/LoopSettings.swift @@ -6,7 +6,7 @@ // import LoopKit - +import HealthKit public struct LoopSettings: Equatable { public var dosingEnabled = false @@ -50,6 +50,31 @@ public struct LoopSettings: Equatable { public let statusChartMinimumHistoryDisplay: TimeInterval = .hours(1) + // MARK - Guardrails + + public func allowedSensitivityValues(for unit: HKUnit) -> [Double] { + switch unit { + case HKUnit.milligramsPerDeciliter: + return (10...500).map { Double($0) } + case HKUnit.millimolesPerLiter: + return (6...270).map { Double($0) / 10.0 } + default: + return [] + } + } + + public func allowedCorrectionRangeValues(for unit: HKUnit) -> [Double] { + switch unit { + case HKUnit.milligramsPerDeciliter: + return (60...180).map { Double($0) } + case HKUnit.millimolesPerLiter: + return (33...100).map { Double($0) / 10.0 } + default: + return [] + } + } + + public init( dosingEnabled: Bool = false, glucoseTargetRangeSchedule: GlucoseRangeSchedule? = nil, From 5b616071ce0a4792a4e67049565c59972d54bf2f Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 25 Jul 2019 09:24:13 -0500 Subject: [PATCH 060/132] Tweaks to device manager --- Common/Models/PumpManagerUI.swift | 13 +++---- Loop/Managers/DeviceDataManager.swift | 36 +++++++++++++------ .../StatusTableViewController.swift | 25 +++++++++---- 3 files changed, 48 insertions(+), 26 deletions(-) diff --git a/Common/Models/PumpManagerUI.swift b/Common/Models/PumpManagerUI.swift index a56a92d2a2..422ea4c0a8 100644 --- a/Common/Models/PumpManagerUI.swift +++ b/Common/Models/PumpManagerUI.swift @@ -11,19 +11,16 @@ import LoopKit import LoopKitUI import MinimedKit import MinimedKitUI - -private let managersByIdentifier: [String: PumpManagerUI.Type] = allPumpManagers.compactMap{ $0 as? PumpManagerUI.Type}.reduce(into: [:]) { (map, Type) in - map[Type.managerIdentifier] = Type -} +import OmniKit +import OmniKitUI typealias PumpManagerHUDViewsRawValue = [String: Any] func PumpManagerHUDViewsFromRawValue(_ rawValue: PumpManagerHUDViewsRawValue) -> [BaseHUDView]? { guard let rawState = rawValue["hudProviderViews"] as? HUDProvider.HUDViewsRawState, - let managerIdentifier = rawValue["managerIdentifier"] as? String, - let manager = managersByIdentifier[managerIdentifier] - else { - return nil + let manager = PumpManagerTypeFromRawValue(rawValue) as? PumpManagerUI.Type + else { + return nil } return manager.createHUDViews(rawValue: rawState) diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index dec0ea93b1..bca3787d14 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -33,6 +33,9 @@ final class DeviceDataManager { /// Should be accessed only on the main queue private(set) var lastError: (date: Date, error: Error)? + /// The last time a BLE heartbeat was received by the pump manager + private var lastBLEDrivenUpdate = Date.distantPast + // MARK: - CGM var cgmManager: CGMManager? { @@ -43,8 +46,6 @@ final class DeviceDataManager { } } - private var lastBLEDrivenUpdate = Date.distantPast - // MARK: - Pump var pumpManager: PumpManagerUI? { @@ -200,15 +201,11 @@ extension DeviceDataManager: DeviceManagerDelegate { trigger: trigger ) - DispatchQueue.main.async { - UNUserNotificationCenter.current().add(request) - } + UNUserNotificationCenter.current().add(request) } func clearNotification(for manager: DeviceManager, identifier: String) { - DispatchQueue.main.async { - UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [identifier]) - } + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [identifier]) } } @@ -285,9 +282,25 @@ extension DeviceDataManager: PumpManagerDelegate { dispatchPrecondition(condition: .onQueue(queue)) log.default("PumpManager:\(type(of: pumpManager)) did fire BLE heartbeat") - let bleHeartbeatUpdateInterval = TimeInterval(minutes: 4.5) - guard lastBLEDrivenUpdate.timeIntervalSinceNow < -bleHeartbeatUpdateInterval else { - log.default("Skipping ble heartbeat") + let bleHeartbeatUpdateInterval: TimeInterval + switch loopManager.lastLoopCompleted?.timeIntervalSinceNow { + case .none: + // If we haven't looped successfully, retry only every 5 minutes + bleHeartbeatUpdateInterval = .minutes(5) + case let interval? where interval < .minutes(-10): + // If we haven't looped successfully in more than 10 minutes, retry only every 5 minutes + bleHeartbeatUpdateInterval = .minutes(5) + case let interval? where interval <= .minutes(-5): + // If we haven't looped successfully in more than 5 minutes, retry every minute + bleHeartbeatUpdateInterval = .minutes(1) + case let interval?: + // If we looped successfully less than 5 minutes ago, ignore the heartbeat. + log.default("PumpManager:\(type(of: pumpManager)) ignoring heartbeat. Last loop completed \(interval.minutes) ago") + return + } + + guard lastBLEDrivenUpdate.timeIntervalSinceNow <= -bleHeartbeatUpdateInterval else { + log.default("PumpManager:\(type(of: pumpManager)) ignoring heartbeat. Last update \(lastBLEDrivenUpdate)") return } lastBLEDrivenUpdate = Date() @@ -552,6 +565,7 @@ extension DeviceDataManager: CustomDebugStringConvertible { "## DeviceDataManager", "* launchDate: \(launchDate)", "* lastError: \(String(describing: lastError))", + "* lastBLEDrivenUpdate: \(lastBLEDrivenUpdate)", "", cgmManager != nil ? String(reflecting: cgmManager!) : "cgmManager: nil", "", diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index dbcd4d0673..9aa7a6c892 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -31,11 +31,8 @@ final class StatusTableViewController: ChartsTableViewController { super.viewDidLoad() statusCharts.glucose.glucoseDisplayRange = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 100)...HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 175) - - if let pumpManager = deviceManager.pumpManager { - self.basalDeliveryState = pumpManager.status.basalDeliveryState - pumpManager.addStatusObserver(self, queue: .main) - } + + registerPumpManager() let notificationCenter = NotificationCenter.default @@ -69,6 +66,7 @@ final class StatusTableViewController: ChartsTableViewController { }, notificationCenter.addObserver(forName: .PumpManagerChanged, object: deviceManager, queue: nil) { [weak self] (notification: Notification) in DispatchQueue.main.async { + self?.registerPumpManager() self?.configurePumpManagerHUDViews() } } @@ -223,6 +221,14 @@ final class StatusTableViewController: ChartsTableViewController { refreshContext = RefreshContext.all } + private func registerPumpManager() { + if let pumpManager = deviceManager.pumpManager { + self.basalDeliveryState = pumpManager.status.basalDeliveryState + pumpManager.removeStatusObserver(self) + pumpManager.addStatusObserver(self, queue: .main) + } + } + private lazy var statusCharts = StatusChartsManager(colors: .default, settings: .default) override func createChartsManager() -> ChartsManager { @@ -436,8 +442,12 @@ final class StatusTableViewController: ChartsTableViewController { if let iobValues = iobValues { charts.setIOBValues(iobValues) } - if let index = charts.iob.iobPoints.closestIndex(priorTo: Date()) { - self.currentIOBDescription = String(describing: charts.iob.iobPoints[index].y) + + // Show the larger of the value either before or after the current date + if let maxValue = charts.iob.iobPoints.allElementsAdjacent(to: Date()).max(by: { + return $0.y.scalar < $1.y.scalar + }) { + self.currentIOBDescription = String(describing: maxValue.y) } else { self.currentIOBDescription = nil } @@ -1321,6 +1331,7 @@ extension StatusTableViewController: CompletionDelegate { extension StatusTableViewController: PumpManagerStatusObserver { func pumpManager(_ pumpManager: PumpManager, didUpdate status: PumpManagerStatus, oldStatus: PumpManagerStatus) { dispatchPrecondition(condition: .onQueue(.main)) + log.default("PumpManager:%{public}@ did update status", String(describing: type(of: pumpManager))) self.basalDeliveryState = status.basalDeliveryState self.bolusState = status.bolusState From 50fba8c39e9861e14cb8d3b3816f329364b8e1df Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 27 Jul 2019 23:25:06 -0500 Subject: [PATCH 061/132] Remove legacy workspace setup script --- prep_workspace.sh | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100755 prep_workspace.sh diff --git a/prep_workspace.sh b/prep_workspace.sh deleted file mode 100755 index aa660c3b0f..0000000000 --- a/prep_workspace.sh +++ /dev/null @@ -1,28 +0,0 @@ -set -x -set -u -set -e - -# Uncomment if you have not already cloned the repo: -# git clone https://github.com/ps2/loop-priv.git -# cd loop-priv - -git checkout omnipod-testing - -carthage bootstrap --use-submodules --no-build - -git reset HEAD - -cd Carthage/Checkouts -cd LoopKit; git fetch; git checkout omnipod-testing; git pull; cd .. -cd rileylink_ios; git fetch; git checkout omnipod-testing; git pull; cd .. -cd CGMBLEKit; git fetch; git checkout omnipod-testing; git pull; cd .. -cd dexcom-share-client-swift; git fetch; git checkout omnipod-testing; git pull; cd .. -cd G4ShareSpy; git fetch; git checkout omnipod-testing; git pull; cd .. -cd ../.. -carthage build - -echo "Open Xcode, and select File -> New -> Workspace and create "Loop.xcworkspace" in the loop-priv directory just created. -Select File -> Add Files to "Loop"... to add the Loop.xcodeproj project. -Repeatedly select File -> Add Files to "Loop"... to add the .xcodeproj file for each project within the Carthage/Checkouts directory. -run 'carthage build' at the top level Loop directory -Build Loop in Xcode!" From 00c3edc586af1319fbe4a8f5be49d4725fd8af4c Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Wed, 31 Jul 2019 11:06:21 -0500 Subject: [PATCH 062/132] Bump LoopKit carthage rev --- Cartfile.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index c9f7613905..7bb6555847 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,7 +1,7 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "fe92d93c24d18ff9755ff027e9b036d3769a8a0a" github "LoopKit/G4ShareSpy" "f6ae0cf1753131135f26046ddf07e096da884b84" -github "LoopKit/LoopKit" "501d7ce0e616322a50b0cac2a33e1a4278362933" +github "LoopKit/LoopKit" "dfc5268c1f1b92c76e949d48d9a1fd9c51f87a6c" github "LoopKit/dexcom-share-client-swift" "cdbb1cd19cf54b37add6d6c25300445a6abe24da" github "i-schuetz/SwiftCharts" "0.6.5" github "maxkonovalov/MKRingProgressView" "2.2.2" From c6c0a6f224cead5f782742f971afa28ab5b246a5 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 4 Aug 2019 20:30:12 -0500 Subject: [PATCH 063/132] Track lastReconciliation, show last temp basal on start --- Loop.xcodeproj/project.pbxproj | 16 +++++------ Loop/Managers/DeviceDataManager.swift | 28 +++++++++++++------ Loop/Managers/LoopDataManager.swift | 40 ++++++++++----------------- 3 files changed, 41 insertions(+), 43 deletions(-) diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index acdc2f7aeb..d376222d82 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -3319,7 +3319,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_ENTITLEMENTS = Loop/Loop.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; - DEVELOPMENT_TEAM = UY678SP37Q; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = Loop/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; "OTHER_SWIFT_FLAGS[arch=*]" = "-DDEBUG"; @@ -3337,7 +3337,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_ENTITLEMENTS = Loop/Loop.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; - DEVELOPMENT_TEAM = UY678SP37Q; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = Loop/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER)"; @@ -3354,7 +3354,7 @@ CODE_SIGN_ENTITLEMENTS = "WatchApp Extension/WatchApp Extension.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = UY678SP37Q; + DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; @@ -3377,7 +3377,7 @@ CODE_SIGN_ENTITLEMENTS = "WatchApp Extension/WatchApp Extension.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = UY678SP37Q; + DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; @@ -3398,7 +3398,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = UY678SP37Q; + DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; IBSC_MODULE = WatchApp_Extension; INFOPLIST_FILE = WatchApp/Info.plist; @@ -3419,7 +3419,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = UY678SP37Q; + DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; IBSC_MODULE = WatchApp_Extension; INFOPLIST_FILE = WatchApp/Info.plist; @@ -3698,7 +3698,7 @@ CODE_SIGN_ENTITLEMENTS = "Loop Status Extension/Loop Status Extension.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = UY678SP37Q; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "Loop Status Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER).statuswidget"; @@ -3717,7 +3717,7 @@ CODE_SIGN_ENTITLEMENTS = "Loop Status Extension/Loop Status Extension.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = UY678SP37Q; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "Loop Status Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER).statuswidget"; diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index bca3787d14..7c358cec63 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -90,9 +90,19 @@ final class DeviceDataManager { remoteDataManager.delegate = self statusExtensionManager = StatusExtensionDataManager(deviceDataManager: self) + + let lastTempBasal: DoseEntry? + + if case .some(.tempBasal(let dose)) = pumpManager?.status.basalDeliveryState { + lastTempBasal = dose + } else { + lastTempBasal = nil + } + loopManager = LoopDataManager( lastLoopCompleted: statusExtensionManager.context?.lastLoopCompleted, - lastTempBasal: statusExtensionManager.context?.netBasal?.tempBasal + lastTempBasal: lastTempBasal, + lastPumpEventsReconciliation: pumpManager?.lastReconciliation ) watchManager = WatchDataManager(deviceManager: self) nightscoutDataManager = NightscoutDataManager(deviceDataManager: self) @@ -145,16 +155,14 @@ private extension DeviceDataManager { // MARK: - Client API extension DeviceDataManager { - func enactBolus(units: Double, at startDate: Date = Date(), willRequest: ((DoseEntry) -> Void)? = nil, completion: @escaping (_ error: Error?) -> Void) { + func enactBolus(units: Double, at startDate: Date = Date(), completion: @escaping (_ error: Error?) -> Void) { guard let pumpManager = pumpManager else { completion(LoopError.configurationError(.pumpManager)) return } pumpManager.enactBolus(units: units, at: startDate, willRequest: { (dose) in - self.loopManager.addRequestedBolus(dose, completion: { - willRequest?(dose) - }) + self.loopManager.addRequestedBolus(dose, completion: nil) }) { (result) in switch result { case .failure(let error): @@ -162,7 +170,7 @@ extension DeviceDataManager { NotificationManager.sendBolusFailureNotification(for: error, units: units, at: startDate) completion(error) case .success(let dose): - self.loopManager.addConfirmedBolus(dose) { + self.loopManager.bolusConfirmed(dose) { completion(nil) } } @@ -234,6 +242,7 @@ extension DeviceDataManager: CGMManagerDelegate { } } + self.log.default("Asserting current pump data") self.pumpManager?.assertCurrentPumpData() } case .noData: @@ -244,6 +253,7 @@ extension DeviceDataManager: CGMManagerDelegate { log.default("CGMManager:\(type(of: manager)) did update with error: \(error)") self.setLastError(error: error) + log.default("Asserting current pump data") pumpManager?.assertCurrentPumpData() } @@ -295,7 +305,7 @@ extension DeviceDataManager: PumpManagerDelegate { bleHeartbeatUpdateInterval = .minutes(1) case let interval?: // If we looped successfully less than 5 minutes ago, ignore the heartbeat. - log.default("PumpManager:\(type(of: pumpManager)) ignoring heartbeat. Last loop completed \(interval.minutes) ago") + log.default("PumpManager:\(type(of: pumpManager)) ignoring heartbeat. Last loop completed \(interval.minutes) minutes ago") return } @@ -379,11 +389,11 @@ extension DeviceDataManager: PumpManagerDelegate { nightscoutDataManager.uploadLoopStatus(loopError: error) } - func pumpManager(_ pumpManager: PumpManager, didReadPumpEvents events: [NewPumpEvent], completion: @escaping (_ error: Error?) -> Void) { + func pumpManager(_ pumpManager: PumpManager, hasNewPumpEvents events: [NewPumpEvent], lastReconciliation: Date?, completion: @escaping (_ error: Error?) -> Void) { dispatchPrecondition(condition: .onQueue(queue)) log.default("PumpManager:\(type(of: pumpManager)) did read pump events") - loopManager.addPumpEvents(events) { (error) in + loopManager.addPumpEvents(events, lastReconciliation: lastReconciliation) { (error) in if let error = error { self.log.error("Failed to addPumpEvents to DoseStore: \(error)") } diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index f4da064454..0270a36c46 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -50,7 +50,8 @@ final class LoopDataManager { insulinModelSettings: InsulinModelSettings? = UserDefaults.appGroup?.insulinModelSettings, insulinSensitivitySchedule: InsulinSensitivitySchedule? = UserDefaults.appGroup?.insulinSensitivitySchedule, settings: LoopSettings = UserDefaults.appGroup?.loopSettings ?? LoopSettings(), - overrideHistory: TemporaryScheduleOverrideHistory = UserDefaults.appGroup?.overrideHistory ?? .init() + overrideHistory: TemporaryScheduleOverrideHistory = UserDefaults.appGroup?.overrideHistory ?? .init(), + lastPumpEventsReconciliation: Date? ) { self.logger = DiagnosticLogger.shared.forCategory("LoopDataManager") self.lockedLastLoopCompleted = Locked(lastLoopCompleted) @@ -76,7 +77,8 @@ final class LoopDataManager { insulinModel: insulinModelSettings?.model, basalProfile: basalRateSchedule, insulinSensitivitySchedule: insulinSensitivitySchedule, - overrideHistory: overrideHistory + overrideHistory: overrideHistory, + lastPumpEventsReconciliation: lastPumpEventsReconciliation ) glucoseStore = GlucoseStore(healthStore: healthStore, cacheStore: cacheStore, cacheLength: .hours(24)) @@ -502,19 +504,17 @@ extension LoopDataManager { } } - /// Adds a bolus enacted by the pump, but not fully delivered. + /// Notifies the manager that the bolus is confirmed, but not fully delivered. /// /// - Parameters: /// - dose: The DoseEntry representing the confirmed bolus - func addConfirmedBolus(_ dose: DoseEntry, completion: (() -> Void)?) { - self.doseStore.addPendingPumpEvent(.enactedBolus(dose: dose)) { - self.dataAccessQueue.async { - self.lastRequestedBolus = nil - self.insulinEffect = nil - self.notify(forChange: .bolus) + func bolusConfirmed(_ dose: DoseEntry, completion: (() -> Void)?) { + self.dataAccessQueue.async { + self.lastRequestedBolus = nil + self.insulinEffect = nil + self.notify(forChange: .bolus) - completion?() - } + completion?() } } @@ -524,18 +524,13 @@ extension LoopDataManager { /// - events: The pump events to add /// - completion: A closure called once upon completion /// - error: An error explaining why the events could not be saved. - func addPumpEvents(_ events: [NewPumpEvent], completion: @escaping (_ error: DoseStore.DoseStoreError?) -> Void) { - doseStore.addPumpEvents(events) { (error) in + func addPumpEvents(_ events: [NewPumpEvent], lastReconciliation: Date?, completion: @escaping (_ error: DoseStore.DoseStoreError?) -> Void) { + doseStore.addPumpEvents(events, lastReconciliation: lastReconciliation) { (error) in self.dataAccessQueue.async { if error == nil { self.insulinEffect = nil - // Expire any bolus values now represented in the insulin data - // TODO: Ask pumpManager if dose represented in data - if let bolusEndDate = self.lastRequestedBolus?.endDate, bolusEndDate < Date() { - self.lastRequestedBolus = nil - } + self.lastRequestedBolus = nil } - completion(error) } } @@ -1252,13 +1247,6 @@ protocol LoopDataManagerDelegate: class { func loopDataManager(_ manager: LoopDataManager, roundBolusVolume units: Double) -> Double } -extension DoseStore { - var lastAddedPumpData: Date { - return max(lastReservoirValue?.startDate ?? .distantPast, lastAddedPumpEvents) - } -} - - private extension TemporaryScheduleOverride { func isBasalRateScheduleOverriden(at date: Date) -> Bool { guard isActive(at: date), let basalRateMultiplier = settings.basalRateMultiplier else { From b345dfcb84c05ce5b32a69680a096d745ec1ce04 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 5 Aug 2019 21:03:29 -0500 Subject: [PATCH 064/132] Update basal rate hud for suspends and resumes --- Common/Models/StatusExtensionContext.swift | 14 +++--- Loop.xcodeproj/project.pbxproj | 4 ++ Loop/Extensions/BasalDeliveryState.swift | 50 +++++++++++++++++++ Loop/Managers/DeviceDataManager.swift | 14 ++---- Loop/Managers/LoopDataManager.swift | 42 ++++++++++------ Loop/Managers/NightscoutDataManager.swift | 16 ++++-- .../Managers/StatusExtensionDataManager.swift | 18 +++---- Loop/Managers/WatchDataManager.swift | 12 ++--- Loop/Models/NetBasal.swift | 28 +++++++++-- .../StatusExtensionContext+LoopKit.swift | 26 +++++----- .../StatusTableViewController.swift | 22 ++++---- 11 files changed, 167 insertions(+), 79 deletions(-) create mode 100644 Loop/Extensions/BasalDeliveryState.swift diff --git a/Common/Models/StatusExtensionContext.swift b/Common/Models/StatusExtensionContext.swift index cdfa2dd7cf..6a8cbabd23 100644 --- a/Common/Models/StatusExtensionContext.swift +++ b/Common/Models/StatusExtensionContext.swift @@ -17,7 +17,7 @@ struct NetBasalContext { let rate: Double let percentage: Double let start: Date - let end: Date + let end: Date? } struct SensorDisplayableContext: SensorDisplayable { @@ -56,20 +56,20 @@ extension NetBasalContext: RawRepresentable { typealias RawValue = [String: Any] var rawValue: RawValue { - return [ + var value: RawValue = [ "rate": rate, "percentage": percentage, - "start": start, - "end": end + "start": start ] + value["end"] = end + return value } init?(rawValue: RawValue) { guard let rate = rawValue["rate"] as? Double, let percentage = rawValue["percentage"] as? Double, - let start = rawValue["start"] as? Date, - let end = rawValue["end"] as? Date + let start = rawValue["start"] as? Date else { return nil } @@ -77,7 +77,7 @@ extension NetBasalContext: RawRepresentable { self.rate = rate self.percentage = percentage self.start = start - self.end = end + self.end = rawValue["end"] as? Date } } diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index d376222d82..3afd81d15d 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -388,6 +388,7 @@ C1C73F0D1DE3D0270022FC89 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1C73F0F1DE3D0270022FC89 /* InfoPlist.strings */; }; C1C7F1C1220D675800689850 /* OmniKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C7F1BF220D675700689850 /* OmniKit.framework */; }; C1C7F1C2220D675800689850 /* OmniKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C7F1C0220D675700689850 /* OmniKitUI.framework */; }; + C1D289B522F90A52003FFBD9 /* BasalDeliveryState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D289B422F90A52003FFBD9 /* BasalDeliveryState.swift */; }; C1E2773E224177C000354103 /* ClockKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1E2773D224177C000354103 /* ClockKit.framework */; }; C1E2774822433D7A00354103 /* MKRingProgressView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1E2774722433D7A00354103 /* MKRingProgressView.framework */; }; C1F8B243223E73FD00DD66CF /* BolusProgressTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F8B1D122375E4200DD66CF /* BolusProgressTableViewCell.swift */; }; @@ -1030,6 +1031,7 @@ C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_temp_basal_dropping_then_rising.json; sourceTree = ""; }; C1C7F1BF220D675700689850 /* OmniKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OmniKit.framework; path = Carthage/Build/iOS/OmniKit.framework; sourceTree = ""; }; C1C7F1C0220D675700689850 /* OmniKitUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OmniKitUI.framework; path = Carthage/Build/iOS/OmniKitUI.framework; sourceTree = ""; }; + C1D289B422F90A52003FFBD9 /* BasalDeliveryState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasalDeliveryState.swift; sourceTree = ""; }; C1E2773D224177C000354103 /* ClockKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ClockKit.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk/System/Library/Frameworks/ClockKit.framework; sourceTree = DEVELOPER_DIR; }; C1E2774722433D7A00354103 /* MKRingProgressView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MKRingProgressView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C1F8B1D122375E4200DD66CF /* BolusProgressTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusProgressTableViewCell.swift; sourceTree = ""; }; @@ -1526,6 +1528,7 @@ 437CEEE31CDE5C0A003C8C80 /* UIImage.swift */, 434FF1ED1CF27EEF000DB779 /* UITableViewCell.swift */, 430B29922041F5B200BA9F93 /* UserDefaults+Loop.swift */, + C1D289B422F90A52003FFBD9 /* BasalDeliveryState.swift */, ); path = Extensions; sourceTree = ""; @@ -2454,6 +2457,7 @@ 43C05CC521EC29E3006FB252 /* TextFieldTableViewCell.swift in Sources */, 4FF4D1001E18374700846527 /* WatchContext.swift in Sources */, 4315D28A1CA5F45E00589052 /* DiagnosticLogger+LoopKit.swift in Sources */, + C1D289B522F90A52003FFBD9 /* BasalDeliveryState.swift in Sources */, 4F2C15821E074FC600E160D4 /* NSTimeInterval.swift in Sources */, 4311FB9B1F37FE1B00D4C0A7 /* TitleSubtitleTextFieldTableViewCell.swift in Sources */, C1FB428F217921D600FAB378 /* PumpManagerUI.swift in Sources */, diff --git a/Loop/Extensions/BasalDeliveryState.swift b/Loop/Extensions/BasalDeliveryState.swift new file mode 100644 index 0000000000..39ddddf91c --- /dev/null +++ b/Loop/Extensions/BasalDeliveryState.swift @@ -0,0 +1,50 @@ +// +// BasalDeliveryState.swift +// Loop +// +// Created by Pete Schwamb on 8/5/19. +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import LoopKit +import LoopCore + +extension PumpManagerStatus.BasalDeliveryState { + func getNetBasal(basalSchedule: BasalRateSchedule, settings: LoopSettings) -> NetBasal? { + func scheduledBasal(for date: Date) -> AbsoluteScheduleValue? { + return basalSchedule.between(start: date, end: date).first + } + + switch self { + case .tempBasal(let dose): + if let scheduledBasal = scheduledBasal(for: dose.startDate) { + return NetBasal( + lastTempBasal: dose, + maxBasal: settings.maximumBasalRatePerHour, + scheduledBasal: scheduledBasal + ) + } else { + return nil + } + case .suspended(let date): + if let scheduledBasal = scheduledBasal(for: date) { + return NetBasal( + suspendedAt: date, + maxBasal: settings.maximumBasalRatePerHour, + scheduledBasal: scheduledBasal + ) + } else { + return nil + } + case .active(let date): + if let scheduledBasal = scheduledBasal(for: date) { + return NetBasal(scheduledRateStartedAt: date, scheduledBasal: scheduledBasal) + } else { + return nil + } + default: + return nil + } + } +} + diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index 7c358cec63..6ccc2f40b4 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -91,17 +91,9 @@ final class DeviceDataManager { remoteDataManager.delegate = self statusExtensionManager = StatusExtensionDataManager(deviceDataManager: self) - let lastTempBasal: DoseEntry? - - if case .some(.tempBasal(let dose)) = pumpManager?.status.basalDeliveryState { - lastTempBasal = dose - } else { - lastTempBasal = nil - } - loopManager = LoopDataManager( lastLoopCompleted: statusExtensionManager.context?.lastLoopCompleted, - lastTempBasal: lastTempBasal, + basalDeliveryState: pumpManager?.status.basalDeliveryState, lastPumpEventsReconciliation: pumpManager?.lastReconciliation ) watchManager = WatchDataManager(deviceManager: self) @@ -359,6 +351,10 @@ extension DeviceDataManager: PumpManagerDelegate { } } + if status.basalDeliveryState != oldStatus.basalDeliveryState { + loopManager.basalDeliveryState = status.basalDeliveryState + } + // Update the pump-schedule based settings loopManager.setScheduleTimeZone(status.timeZone) } diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 0270a36c46..7ca8d7f34e 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -44,7 +44,7 @@ final class LoopDataManager { init( lastLoopCompleted: Date?, - lastTempBasal: DoseEntry?, + basalDeliveryState: PumpManagerStatus.BasalDeliveryState?, basalRateSchedule: BasalRateSchedule? = UserDefaults.appGroup?.basalRateSchedule, carbRatioSchedule: CarbRatioSchedule? = UserDefaults.appGroup?.carbRatioSchedule, insulinModelSettings: InsulinModelSettings? = UserDefaults.appGroup?.insulinModelSettings, @@ -55,7 +55,7 @@ final class LoopDataManager { ) { self.logger = DiagnosticLogger.shared.forCategory("LoopDataManager") self.lockedLastLoopCompleted = Locked(lastLoopCompleted) - self.lastTempBasal = lastTempBasal + self.lockedBasalDeliveryState = Locked(basalDeliveryState) self.settings = settings self.overrideHistory = overrideHistory @@ -188,7 +188,17 @@ final class LoopDataManager { fileprivate var carbsOnBoard: CarbValue? - fileprivate var lastTempBasal: DoseEntry? + var basalDeliveryState: PumpManagerStatus.BasalDeliveryState? { + get { + return lockedBasalDeliveryState.value + } + set { + self.logger.debug("Updating basalDeliveryState to \(String(describing: newValue))") + lockedBasalDeliveryState.value = newValue + } + } + private let lockedBasalDeliveryState: Locked + fileprivate var lastRequestedBolus: DoseEntry? /// The last date at which a loop completed, from prediction to dose (if dosing is enabled) @@ -768,7 +778,7 @@ extension LoopDataManager { let pendingTempBasalInsulin: Double let date = Date() - if let lastTempBasal = lastTempBasal, lastTempBasal.endDate > date { + if let basalDeliveryState = basalDeliveryState, case .tempBasal(let lastTempBasal) = basalDeliveryState, lastTempBasal.endDate > date { let normalBasalRate = basalRates.value(at: date) let remainingTime = lastTempBasal.endDate.timeIntervalSince(date) let remainingUnits = (lastTempBasal.unitsPerHour - normalBasalRate) * remainingTime.hours @@ -932,6 +942,14 @@ extension LoopDataManager { let rateRounder = { (_ rate: Double) in return self.delegate?.loopDataManager(self, roundBasalRate: rate) ?? rate } + + let lastTempBasal: DoseEntry? + + if case .some(.tempBasal(let dose)) = basalDeliveryState { + lastTempBasal = dose + } else { + lastTempBasal = nil + } let tempBasal = predictedGlucose.recommendedTempBasal( to: glucoseTargetRange, @@ -946,6 +964,8 @@ extension LoopDataManager { ) if let temp = tempBasal { + self.logger.default("Current basal state: \(String(describing: basalDeliveryState))") + self.logger.default("Recommending temp basal: \(temp) at \(startDate)") recommendedTempBasal = (recommendation: temp, date: startDate) } else { recommendedTempBasal = nil @@ -986,10 +1006,8 @@ extension LoopDataManager { delegate?.loopDataManager(self, didRecommendBasalChange: recommendedTempBasal) { (result) in self.dataAccessQueue.async { switch result { - case .success(let basal): - self.lastTempBasal = basal + case .success: self.recommendedTempBasal = nil - completion(nil) case .failure(let error): completion(error) @@ -1011,9 +1029,6 @@ protocol LoopState { /// A timeline of average velocity of glucose change counteracting predicted insulin effects var insulinCounteractionEffects: [GlucoseEffectVelocity] { get } - /// The last set temp basal - var lastTempBasal: DoseEntry? { get } - /// The calculated timeline of predicted glucose values var predictedGlucose: [PredictedGlucoseValue]? { get } @@ -1064,11 +1079,6 @@ extension LoopDataManager { return loopDataManager.insulinCounteractionEffects } - var lastTempBasal: DoseEntry? { - dispatchPrecondition(condition: .onQueue(loopDataManager.dataAccessQueue)) - return loopDataManager.lastTempBasal - } - var predictedGlucose: [PredictedGlucoseValue]? { dispatchPrecondition(condition: .onQueue(loopDataManager.dataAccessQueue)) return loopDataManager.predictedGlucose @@ -1183,7 +1193,7 @@ extension LoopDataManager { "recommendedBolus: \(String(describing: state.recommendedBolus))", "lastBolus: \(String(describing: manager.lastRequestedBolus))", "lastLoopCompleted: \(String(describing: manager.lastLoopCompleted))", - "lastTempBasal: \(String(describing: state.lastTempBasal))", + "basalDeliveryState: \(String(describing: manager.basalDeliveryState))", "carbsOnBoard: \(String(describing: state.carbsOnBoard))", "error: \(String(describing: state.error))", "", diff --git a/Loop/Managers/NightscoutDataManager.swift b/Loop/Managers/NightscoutDataManager.swift index 876ddd8b0d..0d1aab340f 100644 --- a/Loop/Managers/NightscoutDataManager.swift +++ b/Loop/Managers/NightscoutDataManager.swift @@ -44,7 +44,6 @@ final class NightscoutDataManager { let carbsOnBoard = state.carbsOnBoard let predictedGlucose = state.predictedGlucose let recommendedTempBasal = state.recommendedTempBasal - let lastTempBasal = state.lastTempBasal manager.doseStore.insulinOnBoard(at: Date()) { (result) in let insulinOnBoard: InsulinValue? @@ -66,7 +65,6 @@ final class NightscoutDataManager { predictedGlucose: predictedGlucose, recommendedTempBasal: recommendedTempBasal, recommendedBolus: recommendedBolus, - lastTempBasal: lastTempBasal, loopError: loopError ) } @@ -75,7 +73,15 @@ final class NightscoutDataManager { private var lastTempBasalUploaded: DoseEntry? - func uploadLoopStatus(insulinOnBoard: InsulinValue? = nil, carbsOnBoard: CarbValue? = nil, predictedGlucose: [GlucoseValue]? = nil, recommendedTempBasal: (recommendation: TempBasalRecommendation, date: Date)? = nil, recommendedBolus: Double? = nil, lastTempBasal: DoseEntry? = nil, loopError: Error? = nil) { + func uploadLoopStatus( + insulinOnBoard: InsulinValue? = nil, + carbsOnBoard: CarbValue? = nil, + predictedGlucose: [GlucoseValue]? = nil, + recommendedTempBasal: (recommendation: TempBasalRecommendation, + date: Date)? = nil, + recommendedBolus: Double? = nil, + loopError: Error? = nil) + { guard deviceManager.remoteDataManager.nightscoutService.uploader != nil else { return @@ -116,7 +122,7 @@ final class NightscoutDataManager { } let loopEnacted: LoopEnacted? - if let tempBasal = lastTempBasal, lastTempBasalUploaded?.startDate != tempBasal.startDate { + if case .some(.tempBasal(let tempBasal)) = deviceManager.pumpManagerStatus?.basalDeliveryState, lastTempBasalUploaded?.startDate != tempBasal.startDate { let duration = tempBasal.endDate.timeIntervalSince(tempBasal.startDate) loopEnacted = LoopEnacted(rate: tempBasal.unitsPerHour, duration: duration, timestamp: tempBasal.startDate, received: true) @@ -166,7 +172,7 @@ final class NightscoutDataManager { model: pumpManagerStatus.device.model, iob: nil, battery: battery, - suspended: pumpManagerStatus.basalDeliveryState == .suspended, + suspended: pumpManagerStatus.basalDeliveryState.suspended, bolusing: bolusing, reservoir: currentReservoirUnits, secondsFromGMT: pumpManagerStatus.timeZone.secondsFromGMT()) diff --git a/Loop/Managers/StatusExtensionDataManager.swift b/Loop/Managers/StatusExtensionDataManager.swift index c4bebb5e8d..0f57b89892 100644 --- a/Loop/Managers/StatusExtensionDataManager.swift +++ b/Loop/Managers/StatusExtensionDataManager.swift @@ -42,6 +42,9 @@ final class StatusExtensionDataManager { } private func createContext(glucoseUnit: HKUnit, _ completionHandler: @escaping (_ context: StatusExtensionContext?) -> Void) { + + let basalDeliveryState = deviceManager.pumpManager?.status.basalDeliveryState + deviceManager.loopManager.getLoopState { (manager, state) in let dataManager = self.deviceManager var context = StatusExtensionContext() @@ -89,18 +92,13 @@ final class StatusExtensionDataManager { interval: second.startDate.timeIntervalSince(first.startDate)) } - let date = state.lastTempBasal?.startDate ?? Date() - if let scheduledBasal = manager.basalRateScheduleApplyingOverrideHistory?.between(start: date, end: date).first { - let netBasal = NetBasal( - lastTempBasal: state.lastTempBasal, - maxBasal: manager.settings.maximumBasalRatePerHour, - scheduledBasal: scheduledBasal - ) - + if let basalDeliveryState = basalDeliveryState, + let basalSchedule = manager.basalRateScheduleApplyingOverrideHistory, + let netBasal = basalDeliveryState.getNetBasal(basalSchedule: basalSchedule, settings: manager.settings) + { context.netBasal = NetBasalContext(rate: netBasal.rate, percentage: netBasal.percent, start: netBasal.start, end: netBasal.end) } - - + context.batteryPercentage = dataManager.pumpManager?.status.pumpBatteryChargeRemaining context.reservoirCapacity = dataManager.pumpManager?.pumpReservoirCapacity diff --git a/Loop/Managers/WatchDataManager.swift b/Loop/Managers/WatchDataManager.swift index 527f4d8ea3..34f563fa57 100644 --- a/Loop/Managers/WatchDataManager.swift +++ b/Loop/Managers/WatchDataManager.swift @@ -145,6 +145,7 @@ final class WatchDataManager: NSObject { let glucose = loopManager.glucoseStore.latestGlucose let reservoir = loopManager.doseStore.lastReservoirValue + let basalDeliveryState = deviceManager.pumpManager?.status.basalDeliveryState loopManager.getLoopState { (manager, state) in let updateGroup = DispatchGroup() @@ -172,12 +173,11 @@ final class WatchDataManager: NSObject { updateGroup.leave() } - // Only set this value in the Watch context if there is a temp basal running that hasn't ended yet - let date = state.lastTempBasal?.startDate ?? Date() - if let scheduledBasal = manager.basalRateScheduleApplyingOverrideHistory?.between(start: date, end: date).first, - let lastTempBasal = state.lastTempBasal, - lastTempBasal.endDate > Date() { - context.lastNetTempBasalDose = lastTempBasal.unitsPerHour - scheduledBasal.value + if let basalDeliveryState = basalDeliveryState, + let basalSchedule = manager.basalRateScheduleApplyingOverrideHistory, + let netBasal = basalDeliveryState.getNetBasal(basalSchedule: basalSchedule, settings: manager.settings) + { + context.lastNetTempBasalDose = netBasal.rate } // Drop the first element in predictedGlucose because it is the current glucose diff --git a/Loop/Models/NetBasal.swift b/Loop/Models/NetBasal.swift index 5070ee8325..1ca446ef75 100644 --- a/Loop/Models/NetBasal.swift +++ b/Loop/Models/NetBasal.swift @@ -9,15 +9,37 @@ import Foundation import LoopKit +/// Max basal should generally be set, but in those cases where it isn't just use 3.0U/hr as a default top of scale, so we can show *something*. +fileprivate let defaultMaxBasalForScale = 3.0 struct NetBasal { let rate: Double let percent: Double let start: Date - let end: Date - + let end: Date? + + init(suspendedAt: Date, maxBasal: Double?, scheduledBasal: AbsoluteScheduleValue) { + rate = -scheduledBasal.value + start = suspendedAt + end = nil + + if rate < 0 { + percent = rate / scheduledBasal.value + } else { + percent = rate / ((maxBasal ?? defaultMaxBasalForScale) - scheduledBasal.value) + } + } + + init(scheduledRateStartedAt: Date, scheduledBasal: AbsoluteScheduleValue) { + rate = 0 + start = scheduledRateStartedAt + end = nil + percent = 0 + } + init(lastTempBasal: DoseEntry?, maxBasal: Double?, scheduledBasal: AbsoluteScheduleValue) { - if let lastTempBasal = lastTempBasal, lastTempBasal.endDate > Date(), let maxBasal = maxBasal { + if let lastTempBasal = lastTempBasal, lastTempBasal.endDate > Date() { + let maxBasal = maxBasal ?? defaultMaxBasalForScale rate = lastTempBasal.unitsPerHour - scheduledBasal.value start = lastTempBasal.startDate end = lastTempBasal.endDate diff --git a/Loop/Models/StatusExtensionContext+LoopKit.swift b/Loop/Models/StatusExtensionContext+LoopKit.swift index da4ea68887..736a1f1269 100644 --- a/Loop/Models/StatusExtensionContext+LoopKit.swift +++ b/Loop/Models/StatusExtensionContext+LoopKit.swift @@ -9,17 +9,17 @@ import LoopKit extension NetBasalContext { - var tempBasal: DoseEntry? { - guard rate != 0 else { - return nil - } - - return DoseEntry( - type: .tempBasal, - startDate: start, - endDate: end, - value: rate, - unit: .unitsPerHour - ) - } +// var tempBasal: DoseEntry? { +// guard rate != 0 else { +// return nil +// } +// +// return DoseEntry( +// type: .tempBasal, +// startDate: start, +// endDate: end, +// value: rate, +// unit: .unitsPerHour +// ) +// } } diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index 9aa7a6c892..0be9f60141 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -192,10 +192,12 @@ final class StatusTableViewController: ChartsTableViewController { deviceManager.pumpManagerHUDProvider?.visible = active && onscreen } - public var basalDeliveryState: PumpManagerStatus.BasalDeliveryState = .active { + public var basalDeliveryState: PumpManagerStatus.BasalDeliveryState = .active(Date()) { didSet { if oldValue != basalDeliveryState { + log.debug("New basalDeliveryState: %@", String(describing: basalDeliveryState)) refreshContext.update(with: .status) + self.reloadData(animated: true) } } } @@ -296,6 +298,7 @@ final class StatusTableViewController: ChartsTableViewController { var totalDelivery: Double? var cobValues: [CarbValue]? let startDate = charts.startDate + let basalDeliveryState = self.basalDeliveryState // TODO: Don't always assume currentContext.contains(.status) reloadGroup.enter() @@ -308,21 +311,17 @@ final class StatusTableViewController: ChartsTableViewController { } /// Update the status HUDs immediately - let netBasal: NetBasal? let lastLoopCompleted = manager.lastLoopCompleted let lastLoopError = state.error // Net basal rate HUD - let date = state.lastTempBasal?.startDate ?? Date() - if let scheduledBasal = manager.basalRateScheduleApplyingOverrideHistory?.between(start: date, end: date).first { - netBasal = NetBasal( - lastTempBasal: state.lastTempBasal, - maxBasal: manager.settings.maximumBasalRatePerHour, - scheduledBasal: scheduledBasal - ) + let netBasal: NetBasal? + if let basalSchedule = manager.basalRateScheduleApplyingOverrideHistory { + netBasal = basalDeliveryState.getNetBasal(basalSchedule: basalSchedule, settings: manager.settings) } else { netBasal = nil } + self.log.debug("Update net basal to %{public}@", String(describing: netBasal)) DispatchQueue.main.async { self.hudView?.loopCompletionHUD.dosingEnabled = manager.settings.dosingEnabled @@ -575,7 +574,7 @@ final class StatusTableViewController: ChartsTableViewController { statusRowMode = .enactingBolus } else if case .canceling = bolusState { statusRowMode = .cancelingBolus - } else if self.basalDeliveryState == .suspended { + } else if case .suspended = self.basalDeliveryState { statusRowMode = .pumpSuspended(resuming: false) } else if self.basalDeliveryState == .resuming { statusRowMode = .pumpSuspended(resuming: true) @@ -988,6 +987,9 @@ final class StatusTableViewController: ChartsTableViewController { } } else { self.updateHUDandStatusRows(statusRowMode: self.determineStatusRowMode(), newSize: nil, animated: true) + self.refreshContext.update(with: .insulin) + self.log.debug("[reloadData] after manually resuming suspend") + self.reloadData() } } } From 65e048780c77d81b66554c5944504889683aaa7e Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 6 Aug 2019 11:09:18 -0500 Subject: [PATCH 065/132] Generate notification on new doses available --- Loop/Managers/DeviceDataManager.swift | 5 +++++ Loop/View Controllers/StatusTableViewController.swift | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index 6ccc2f40b4..b85ec6dd0f 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -395,6 +395,10 @@ extension DeviceDataManager: PumpManagerDelegate { } completion(error) + + if error == nil { + NotificationCenter.default.post(name: .PumpEventsAdded, object: self, userInfo: nil) + } } } @@ -586,5 +590,6 @@ extension DeviceDataManager: CustomDebugStringConvertible { extension Notification.Name { static let PumpManagerChanged = Notification.Name(rawValue: "com.loopKit.notification.PumpManagerChanged") + static let PumpEventsAdded = Notification.Name(rawValue: "com.loopKit.notification.PumpEventsAdded") } diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index 0be9f60141..e32cc5cbad 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -69,6 +69,12 @@ final class StatusTableViewController: ChartsTableViewController { self?.registerPumpManager() self?.configurePumpManagerHUDViews() } + }, + notificationCenter.addObserver(forName: .PumpEventsAdded, object: deviceManager, queue: nil) { [weak self] (notification: Notification) in + DispatchQueue.main.async { + self?.refreshContext.update(with: .insulin) + self?.reloadData(animated: true) + } } ] From e174b3f75d047e352747c65e4c8bf5126f45a4ed Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 6 Aug 2019 12:43:21 -0500 Subject: [PATCH 066/132] Show even long delivery time boluses as boluses in dose chart --- LoopUI/Charts/DoseChart.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LoopUI/Charts/DoseChart.swift b/LoopUI/Charts/DoseChart.swift index 31e025b822..99f87c30c7 100644 --- a/LoopUI/Charts/DoseChart.swift +++ b/LoopUI/Charts/DoseChart.swift @@ -133,7 +133,7 @@ public extension DoseChart { for entry in doseEntries { let time = entry.endDate.timeIntervalSince(entry.startDate) - if entry.type == .bolus && entry.netBasalUnits > 0 && time < .minutes(10) { + if entry.type == .bolus && entry.netBasalUnits > 0 { let x = ChartAxisValueDate(date: entry.startDate, formatter: dateFormatter) let y = ChartAxisValueDoubleLog(actualDouble: entry.units, unitString: "U", formatter: doseFormatter) From dc17179fa5338b014b7565d17e5960a77279a39c Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 11 Aug 2019 09:09:22 -0500 Subject: [PATCH 067/132] Update to renamed isSupended var --- Loop/Managers/NightscoutDataManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Loop/Managers/NightscoutDataManager.swift b/Loop/Managers/NightscoutDataManager.swift index 0d1aab340f..89a01a6236 100644 --- a/Loop/Managers/NightscoutDataManager.swift +++ b/Loop/Managers/NightscoutDataManager.swift @@ -172,7 +172,7 @@ final class NightscoutDataManager { model: pumpManagerStatus.device.model, iob: nil, battery: battery, - suspended: pumpManagerStatus.basalDeliveryState.suspended, + suspended: pumpManagerStatus.basalDeliveryState.isSuspended, bolusing: bolusing, reservoir: currentReservoirUnits, secondsFromGMT: pumpManagerStatus.timeZone.secondsFromGMT()) From cba00952a4c5983a8ee5422a7b29d931a77d7e9d Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 13 Aug 2019 18:12:39 -0500 Subject: [PATCH 068/132] Prevent showing user same bolus recommendation after bolusing. --- Loop/Extensions/PersistedPumpEvent.swift | 4 +- Loop/Managers/DeviceDataManager.swift | 7 ++- Loop/Managers/LoopDataManager.swift | 57 +++++++++++++++---- .../BolusViewController+LoopDataManager.swift | 2 + .../StatusTableViewController.swift | 2 +- LoopUI/Charts/DoseChart.swift | 2 +- 6 files changed, 58 insertions(+), 16 deletions(-) diff --git a/Loop/Extensions/PersistedPumpEvent.swift b/Loop/Extensions/PersistedPumpEvent.swift index 9851db0587..3a60554c92 100644 --- a/Loop/Extensions/PersistedPumpEvent.swift +++ b/Loop/Extensions/PersistedPumpEvent.swift @@ -27,8 +27,8 @@ extension PersistedPumpEvent { timestamp: dose.startDate, enteredBy: source, bolusType: duration > 0 ? .Square : .Normal, - amount: dose.units, - programmed: dose.units, // Persisted pump events are always completed + amount: dose.deliveredUnits ?? dose.programmedUnits, + programmed: dose.programmedUnits, // Persisted pump events are always completed unabsorbed: 0, // The pump's reported IOB isn't relevant, nor stored duration: duration, carbs: 0, diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index b85ec6dd0f..b2d0a22fd4 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -153,14 +153,17 @@ extension DeviceDataManager { return } + self.loopManager.addRequestedBolus(DoseEntry(type: .bolus, startDate: Date(), value: units, unit: .units), completion: nil) pumpManager.enactBolus(units: units, at: startDate, willRequest: { (dose) in - self.loopManager.addRequestedBolus(dose, completion: nil) + // No longer used... }) { (result) in switch result { case .failure(let error): self.log.error(error) NotificationManager.sendBolusFailureNotification(for: error, units: units, at: startDate) - completion(error) + self.loopManager.bolusRequestFailed(error) { + completion(error) + } case .success(let dose): self.loopManager.bolusConfirmed(dose) { completion(nil) diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 7ca8d7f34e..643b8fbd3c 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -115,6 +115,19 @@ final class LoopDataManager { self.notify(forChange: .glucose) } + }, + NotificationCenter.default.addObserver( + forName: nil, + object: doseStore, + queue: OperationQueue.main + ) { (note) in + self.dataAccessQueue.async { + self.logger.default("Received notification of dosing changing") + + self.insulinEffect = nil + + self.notify(forChange: .bolus) + } } ] } @@ -507,6 +520,7 @@ extension LoopDataManager { /// - dose: The DoseEntry representing the requested bolus func addRequestedBolus(_ dose: DoseEntry, completion: (() -> Void)?) { dataAccessQueue.async { + self.logger.debug("addRequestedBolus") self.lastRequestedBolus = dose self.notify(forChange: .bolus) @@ -517,9 +531,27 @@ extension LoopDataManager { /// Notifies the manager that the bolus is confirmed, but not fully delivered. /// /// - Parameters: - /// - dose: The DoseEntry representing the confirmed bolus + /// - dose: The DoseEntry representing the confirmed bolus. func bolusConfirmed(_ dose: DoseEntry, completion: (() -> Void)?) { self.dataAccessQueue.async { + self.logger.debug("bolusConfirmed") + self.lastRequestedBolus = nil + self.recommendedBolus = nil + self.recommendedTempBasal = nil + self.insulinEffect = nil + self.notify(forChange: .bolus) + + completion?() + } + } + + /// Notifies the manager that the bolus failed. + /// + /// - Parameters: + /// - dose: The DoseEntry representing the confirmed bolus. + func bolusRequestFailed(_ error: Error, completion: (() -> Void)?) { + self.dataAccessQueue.async { + self.logger.debug("bolusRequestFailed") self.lastRequestedBolus = nil self.insulinEffect = nil self.notify(forChange: .bolus) @@ -528,6 +560,7 @@ extension LoopDataManager { } } + /// Adds and stores new pump events /// /// - Parameters: @@ -539,7 +572,6 @@ extension LoopDataManager { self.dataAccessQueue.async { if error == nil { self.insulinEffect = nil - self.lastRequestedBolus = nil } completion(error) } @@ -563,11 +595,6 @@ extension LoopDataManager { } else if let newValue = newValue { self.dataAccessQueue.async { self.insulinEffect = nil - // Expire any bolus values now represented in the insulin data - // TODO: Ask pumpManager if dose represented in data - if areStoredValuesContinuous, let bolusEndDate = self.lastRequestedBolus?.endDate, bolusEndDate < Date() { - self.lastRequestedBolus = nil - } if let newDoseStartDate = previousValue?.startDate { // Prune back any counteraction effects for recomputation, after the effect delay @@ -671,6 +698,7 @@ extension LoopDataManager { } if insulinEffect == nil { + self.logger.debug("Recomputing insulin effects") updateGroup.enter() doseStore.getGlucoseEffects(start: nextEffectDate) { (result) -> Void in switch result { @@ -788,7 +816,7 @@ extension LoopDataManager { pendingTempBasalInsulin = 0 } - let pendingBolusAmount: Double = lastRequestedBolus?.units ?? 0 + let pendingBolusAmount: Double = lastRequestedBolus?.programmedUnits ?? 0 // All outstanding potential insulin delivery return pendingTempBasalInsulin + pendingBolusAmount @@ -881,6 +909,8 @@ extension LoopDataManager { private func updatePredictedGlucoseAndRecommendedBasalAndBolus() throws { dispatchPrecondition(condition: .onQueue(dataAccessQueue)) + self.logger.debug("Recomputing prediction and recommendations.") + guard let glucose = glucoseStore.latestGlucose else { self.predictedGlucose = nil throw LoopError.missingDataError(.glucose) @@ -934,8 +964,7 @@ extension LoopDataManager { // Don't recommend changes if a bolus was just requested. // Sending additional pump commands is not going to be // successful in any case. - recommendedBolus = nil - recommendedTempBasal = nil + self.logger.debug("Not generating recommendations because bolus request is in progress.") return } @@ -987,6 +1016,7 @@ extension LoopDataManager { volumeRounder: volumeRounder ) recommendedBolus = (recommendation: recommendation, date: startDate) + self.logger.debug("Recommending bolus: \(String(describing: recommendedBolus))") } /// *This method should only be called from the `dataAccessQueue`* @@ -1086,11 +1116,17 @@ extension LoopDataManager { var recommendedTempBasal: (recommendation: TempBasalRecommendation, date: Date)? { dispatchPrecondition(condition: .onQueue(loopDataManager.dataAccessQueue)) + guard loopDataManager.lastRequestedBolus == nil else { + return nil + } return loopDataManager.recommendedTempBasal } var recommendedBolus: (recommendation: BolusRecommendation, date: Date)? { dispatchPrecondition(condition: .onQueue(loopDataManager.dataAccessQueue)) + guard loopDataManager.lastRequestedBolus == nil else { + return nil + } return loopDataManager.recommendedBolus } @@ -1121,6 +1157,7 @@ extension LoopDataManager { var updateError: Error? do { + self.logger.debug("getLoopState: update()") try self.update() } catch let error { updateError = error diff --git a/Loop/View Controllers/BolusViewController+LoopDataManager.swift b/Loop/View Controllers/BolusViewController+LoopDataManager.swift index 84246ef882..a032bc439c 100644 --- a/Loop/View Controllers/BolusViewController+LoopDataManager.swift +++ b/Loop/View Controllers/BolusViewController+LoopDataManager.swift @@ -23,6 +23,8 @@ extension BolusViewController { bolusRecommendation = state.recommendedBolus?.recommendation } + print("BolusViewController: recommendation = \(String(describing: bolusRecommendation))") + manager.doseStore.insulinOnBoard(at: Date()) { (result) in let activeInsulin: Double? diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index e32cc5cbad..0d09494b03 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -848,7 +848,7 @@ final class StatusTableViewController: ChartsTableViewController { case .bolusing(let dose): let progressCell = tableView.dequeueReusableCell(withIdentifier: BolusProgressTableViewCell.className, for: indexPath) as! BolusProgressTableViewCell progressCell.selectionStyle = .none - progressCell.totalUnits = dose.units + progressCell.totalUnits = dose.programmedUnits progressCell.tintColor = .doseTintColor progressCell.unit = HKUnit.internationalUnit() progressCell.deliveredUnits = bolusProgressReporter?.progress.deliveredUnits diff --git a/LoopUI/Charts/DoseChart.swift b/LoopUI/Charts/DoseChart.swift index 99f87c30c7..d6ab6ead3a 100644 --- a/LoopUI/Charts/DoseChart.swift +++ b/LoopUI/Charts/DoseChart.swift @@ -135,7 +135,7 @@ public extension DoseChart { if entry.type == .bolus && entry.netBasalUnits > 0 { let x = ChartAxisValueDate(date: entry.startDate, formatter: dateFormatter) - let y = ChartAxisValueDoubleLog(actualDouble: entry.units, unitString: "U", formatter: doseFormatter) + let y = ChartAxisValueDoubleLog(actualDouble: entry.unitsInDeliverableIncrements, unitString: "U", formatter: doseFormatter) let point = ChartPoint(x: x, y: y) bolusDosePoints.append(point) From 0be3b48f2bf81ec02f7a9211e3677a084170b7fa Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 15 Aug 2019 11:38:29 -0500 Subject: [PATCH 069/132] Add transition for engaging manual temp basals in status bar --- .../StatusTableViewController.swift | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index 0d09494b03..238faf3aed 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -556,6 +556,7 @@ final class StatusTableViewController: ChartsTableViewController { case hidden case recommendedTempBasal(tempBasal: TempBasalRecommendation, at: Date, enacting: Bool) case scheduleOverrideEnabled(TemporaryScheduleOverride) + case enactingTempbasal case enactingBolus case bolusing(dose: DoseEntry) case cancelingBolus @@ -580,7 +581,9 @@ final class StatusTableViewController: ChartsTableViewController { statusRowMode = .enactingBolus } else if case .canceling = bolusState { statusRowMode = .cancelingBolus - } else if case .suspended = self.basalDeliveryState { + } else if case .initiatingTempBasal = basalDeliveryState { + statusRowMode = .enactingTempbasal + } else if case .suspended = basalDeliveryState { statusRowMode = .pumpSuspended(resuming: false) } else if self.basalDeliveryState == .resuming { statusRowMode = .pumpSuspended(resuming: true) @@ -841,6 +844,15 @@ final class StatusTableViewController: ChartsTableViewController { cell.titleLabel.text = NSLocalizedString("Starting Bolus", comment: "The title of the cell indicating a bolus is being sent") cell.subtitleLabel.text = nil + let indicatorView = UIActivityIndicatorView(style: .gray) + indicatorView.startAnimating() + cell.accessoryView = indicatorView + return cell + case .enactingTempbasal: + let cell = getTitleSubtitleCell() + cell.titleLabel.text = NSLocalizedString("Enacting Temp Basal", comment: "The title of the cell indicating a temp basal is being enacted") + cell.subtitleLabel.text = nil + let indicatorView = UIActivityIndicatorView(style: .gray) indicatorView.startAnimating() cell.accessoryView = indicatorView From e13ef5619a296a5cdac1ea3b886f20b5037da4ff Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 16 Aug 2019 08:12:44 -0500 Subject: [PATCH 070/132] Track units for overrides target ranges --- LoopCore/LoopSettings.swift | 12 ++++++++---- LoopUI/Extensions/ChartPoint.swift | 4 ++-- .../Managers/ComplicationChartManager.swift | 2 +- WatchApp Extension/Models/GlucoseChartData.swift | 13 +------------ WatchApp Extension/Scenes/GlucoseChartScene.swift | 4 ++-- .../Scenes/GlucoseChartValueHashable.swift | 8 +++++--- 6 files changed, 19 insertions(+), 24 deletions(-) diff --git a/LoopCore/LoopSettings.swift b/LoopCore/LoopSettings.swift index 657b99f99d..48f3458248 100644 --- a/LoopCore/LoopSettings.swift +++ b/LoopCore/LoopSettings.swift @@ -47,6 +47,10 @@ public struct LoopSettings: Equatable { public let statusChartMinimumHistoryDisplay: TimeInterval = .hours(1) + public var glucoseUnit: HKUnit? { + return glucoseTargetRangeSchedule?.unit + } + // MARK - Guardrails public func allowedSensitivityValues(for unit: HKUnit) -> [Double] { @@ -121,12 +125,12 @@ extension LoopSettings { } public func preMealOverride(beginningAt date: Date = Date(), for duration: TimeInterval) -> TemporaryScheduleOverride? { - guard let premealTargetRange = preMealTargetRange else { + guard let premealTargetRange = preMealTargetRange, let unit = glucoseUnit else { return nil } return TemporaryScheduleOverride( context: .preMeal, - settings: TemporaryScheduleOverrideSettings(targetRange: premealTargetRange), + settings: TemporaryScheduleOverrideSettings(unit: unit, targetRange: premealTargetRange), startDate: date, duration: .finite(duration) ) @@ -137,12 +141,12 @@ extension LoopSettings { } public func legacyWorkoutOverride(beginningAt date: Date = Date(), for duration: TimeInterval) -> TemporaryScheduleOverride? { - guard let legacyWorkoutTargetRange = legacyWorkoutTargetRange else { + guard let legacyWorkoutTargetRange = legacyWorkoutTargetRange, let unit = glucoseUnit else { return nil } return TemporaryScheduleOverride( context: .legacyWorkout, - settings: TemporaryScheduleOverrideSettings(targetRange: legacyWorkoutTargetRange), + settings: TemporaryScheduleOverrideSettings(unit: unit, targetRange: legacyWorkoutTargetRange), startDate: date, duration: duration.isInfinite ? .indefinite : .finite(duration) ) diff --git a/LoopUI/Extensions/ChartPoint.swift b/LoopUI/Extensions/ChartPoint.swift index 20db14a606..ff98ed9ad9 100644 --- a/LoopUI/Extensions/ChartPoint.swift +++ b/LoopUI/Extensions/ChartPoint.swift @@ -60,9 +60,9 @@ extension ChartPoint { guard let targetRange = override.settings.targetRange else { return [] } - let quantityRange = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: targetRange.minValue)...HKQuantity(unit: .milligramsPerDeciliter, doubleValue: targetRange.maxValue) + return pointsForGlucoseRangeScheduleOverride( - range: quantityRange.doubleRangeWithMinimumIncrement(in: unit), + range: targetRange.doubleRangeWithMinimumIncrement(in: unit), activeInterval: override.activeInterval, unit: unit, xAxisValues: xAxisValues, diff --git a/WatchApp Extension/Managers/ComplicationChartManager.swift b/WatchApp Extension/Managers/ComplicationChartManager.swift index 3ee5e54a4e..c6a28f8a67 100644 --- a/WatchApp Extension/Managers/ComplicationChartManager.swift +++ b/WatchApp Extension/Managers/ComplicationChartManager.swift @@ -119,7 +119,7 @@ final class ComplicationChartManager { private func drawOverridesIfNeeded(in context: CGContext, using scaler: GlucoseChartScaler) { guard let override = data?.activeScheduleOverride, - let overrideHashable = TemporaryScheduleOverrideHashable(override) + let overrideHashable = TemporaryScheduleOverrideHashable(override, unit: unit) else { return } diff --git a/WatchApp Extension/Models/GlucoseChartData.swift b/WatchApp Extension/Models/GlucoseChartData.swift index 7c68efb7ec..923f57ecbc 100644 --- a/WatchApp Extension/Models/GlucoseChartData.swift +++ b/WatchApp Extension/Models/GlucoseChartData.swift @@ -56,7 +56,7 @@ struct GlucoseChartData { max = Swift.max(max, correction.value.upperBound.doubleValue(for: unit)) } - if let override = activeOverrideQuantityRange { + if let override = activeScheduleOverride?.settings.targetRange { min = Swift.min(min, override.lowerBound.doubleValue(for: unit)) max = Swift.max(max, override.upperBound.doubleValue(for: unit)) } @@ -88,17 +88,6 @@ struct GlucoseChartData { } return override } - - private var activeOverrideQuantityRange: Range? { - guard let targetRange = activeScheduleOverride?.settings.targetRange else { - return nil - } - - let unit = self.unit ?? .milligramsPerDeciliter - let lowerBound = HKQuantity(unit: unit, doubleValue: targetRange.minValue) - let upperBound = HKQuantity(unit: unit, doubleValue: targetRange.maxValue) - return lowerBound.. Date: Fri, 16 Aug 2019 09:38:15 -0500 Subject: [PATCH 071/132] Update resolved dependencies --- Cartfile.resolved | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index c9f7613905..5a98b45028 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,8 +1,8 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "fe92d93c24d18ff9755ff027e9b036d3769a8a0a" github "LoopKit/G4ShareSpy" "f6ae0cf1753131135f26046ddf07e096da884b84" -github "LoopKit/LoopKit" "501d7ce0e616322a50b0cac2a33e1a4278362933" +github "LoopKit/LoopKit" "eaed65166138c5ea1b8d8dddc7b14f1032f3da06" github "LoopKit/dexcom-share-client-swift" "cdbb1cd19cf54b37add6d6c25300445a6abe24da" github "i-schuetz/SwiftCharts" "0.6.5" github "maxkonovalov/MKRingProgressView" "2.2.2" -github "ps2/rileylink_ios" "7297016d4fc063c75c40f9e8402e303df78816aa" +github "ps2/rileylink_ios" "3a5a92c9a69565dc32c916294826b4e7ae02297e" From 15d48bbf96d7c7d92963be25d1a3d6c62bc7ed2c Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 16 Aug 2019 13:14:42 -0500 Subject: [PATCH 072/132] Track units for overrides target ranges (#1058) * Track units for overrides target ranges * Update LoopKit rev --- Cartfile.resolved | 2 +- LoopCore/LoopSettings.swift | 12 ++++++++---- LoopUI/Extensions/ChartPoint.swift | 4 ++-- .../Managers/ComplicationChartManager.swift | 2 +- WatchApp Extension/Models/GlucoseChartData.swift | 13 +------------ WatchApp Extension/Scenes/GlucoseChartScene.swift | 4 ++-- .../Scenes/GlucoseChartValueHashable.swift | 8 +++++--- 7 files changed, 20 insertions(+), 25 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 5a98b45028..879a840028 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,7 +1,7 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "fe92d93c24d18ff9755ff027e9b036d3769a8a0a" github "LoopKit/G4ShareSpy" "f6ae0cf1753131135f26046ddf07e096da884b84" -github "LoopKit/LoopKit" "eaed65166138c5ea1b8d8dddc7b14f1032f3da06" +github "LoopKit/LoopKit" "166f889e7c9e4369c8ba7168b4e1cf5c417354a8" github "LoopKit/dexcom-share-client-swift" "cdbb1cd19cf54b37add6d6c25300445a6abe24da" github "i-schuetz/SwiftCharts" "0.6.5" github "maxkonovalov/MKRingProgressView" "2.2.2" diff --git a/LoopCore/LoopSettings.swift b/LoopCore/LoopSettings.swift index 657b99f99d..48f3458248 100644 --- a/LoopCore/LoopSettings.swift +++ b/LoopCore/LoopSettings.swift @@ -47,6 +47,10 @@ public struct LoopSettings: Equatable { public let statusChartMinimumHistoryDisplay: TimeInterval = .hours(1) + public var glucoseUnit: HKUnit? { + return glucoseTargetRangeSchedule?.unit + } + // MARK - Guardrails public func allowedSensitivityValues(for unit: HKUnit) -> [Double] { @@ -121,12 +125,12 @@ extension LoopSettings { } public func preMealOverride(beginningAt date: Date = Date(), for duration: TimeInterval) -> TemporaryScheduleOverride? { - guard let premealTargetRange = preMealTargetRange else { + guard let premealTargetRange = preMealTargetRange, let unit = glucoseUnit else { return nil } return TemporaryScheduleOverride( context: .preMeal, - settings: TemporaryScheduleOverrideSettings(targetRange: premealTargetRange), + settings: TemporaryScheduleOverrideSettings(unit: unit, targetRange: premealTargetRange), startDate: date, duration: .finite(duration) ) @@ -137,12 +141,12 @@ extension LoopSettings { } public func legacyWorkoutOverride(beginningAt date: Date = Date(), for duration: TimeInterval) -> TemporaryScheduleOverride? { - guard let legacyWorkoutTargetRange = legacyWorkoutTargetRange else { + guard let legacyWorkoutTargetRange = legacyWorkoutTargetRange, let unit = glucoseUnit else { return nil } return TemporaryScheduleOverride( context: .legacyWorkout, - settings: TemporaryScheduleOverrideSettings(targetRange: legacyWorkoutTargetRange), + settings: TemporaryScheduleOverrideSettings(unit: unit, targetRange: legacyWorkoutTargetRange), startDate: date, duration: duration.isInfinite ? .indefinite : .finite(duration) ) diff --git a/LoopUI/Extensions/ChartPoint.swift b/LoopUI/Extensions/ChartPoint.swift index 20db14a606..ff98ed9ad9 100644 --- a/LoopUI/Extensions/ChartPoint.swift +++ b/LoopUI/Extensions/ChartPoint.swift @@ -60,9 +60,9 @@ extension ChartPoint { guard let targetRange = override.settings.targetRange else { return [] } - let quantityRange = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: targetRange.minValue)...HKQuantity(unit: .milligramsPerDeciliter, doubleValue: targetRange.maxValue) + return pointsForGlucoseRangeScheduleOverride( - range: quantityRange.doubleRangeWithMinimumIncrement(in: unit), + range: targetRange.doubleRangeWithMinimumIncrement(in: unit), activeInterval: override.activeInterval, unit: unit, xAxisValues: xAxisValues, diff --git a/WatchApp Extension/Managers/ComplicationChartManager.swift b/WatchApp Extension/Managers/ComplicationChartManager.swift index 3ee5e54a4e..c6a28f8a67 100644 --- a/WatchApp Extension/Managers/ComplicationChartManager.swift +++ b/WatchApp Extension/Managers/ComplicationChartManager.swift @@ -119,7 +119,7 @@ final class ComplicationChartManager { private func drawOverridesIfNeeded(in context: CGContext, using scaler: GlucoseChartScaler) { guard let override = data?.activeScheduleOverride, - let overrideHashable = TemporaryScheduleOverrideHashable(override) + let overrideHashable = TemporaryScheduleOverrideHashable(override, unit: unit) else { return } diff --git a/WatchApp Extension/Models/GlucoseChartData.swift b/WatchApp Extension/Models/GlucoseChartData.swift index 7c68efb7ec..923f57ecbc 100644 --- a/WatchApp Extension/Models/GlucoseChartData.swift +++ b/WatchApp Extension/Models/GlucoseChartData.swift @@ -56,7 +56,7 @@ struct GlucoseChartData { max = Swift.max(max, correction.value.upperBound.doubleValue(for: unit)) } - if let override = activeOverrideQuantityRange { + if let override = activeScheduleOverride?.settings.targetRange { min = Swift.min(min, override.lowerBound.doubleValue(for: unit)) max = Swift.max(max, override.upperBound.doubleValue(for: unit)) } @@ -88,17 +88,6 @@ struct GlucoseChartData { } return override } - - private var activeOverrideQuantityRange: Range? { - guard let targetRange = activeScheduleOverride?.settings.targetRange else { - return nil - } - - let unit = self.unit ?? .milligramsPerDeciliter - let lowerBound = HKQuantity(unit: unit, doubleValue: targetRange.minValue) - let upperBound = HKQuantity(unit: unit, doubleValue: targetRange.maxValue) - return lowerBound.. Date: Sat, 17 Aug 2019 13:00:04 -0500 Subject: [PATCH 073/132] Bump LoopKit rev in Cartfile.resolved --- Cartfile.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 879a840028..653b17d60c 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,7 +1,7 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "fe92d93c24d18ff9755ff027e9b036d3769a8a0a" github "LoopKit/G4ShareSpy" "f6ae0cf1753131135f26046ddf07e096da884b84" -github "LoopKit/LoopKit" "166f889e7c9e4369c8ba7168b4e1cf5c417354a8" +github "LoopKit/LoopKit" "a0dc68e428e2fe6cba89df4809703e9d2087ebfe" github "LoopKit/dexcom-share-client-swift" "cdbb1cd19cf54b37add6d6c25300445a6abe24da" github "i-schuetz/SwiftCharts" "0.6.5" github "maxkonovalov/MKRingProgressView" "2.2.2" From a9fdd4da70af63ecc10e649315e4f682d0b589a6 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 17 Aug 2019 18:53:31 -0500 Subject: [PATCH 074/132] Upload Loop settings to NS when they change (#1064) * Upload Loop settings to NS when they change * Bump revisions in Cartfile.resolved --- Cartfile.resolved | 2 +- Loop/Managers/LoopDataManager.swift | 11 +- Loop/Managers/NightscoutDataManager.swift | 200 ++++++++++++++++++++-- 3 files changed, 198 insertions(+), 15 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 653b17d60c..371f5166b5 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -5,4 +5,4 @@ github "LoopKit/LoopKit" "a0dc68e428e2fe6cba89df4809703e9d2087ebfe" github "LoopKit/dexcom-share-client-swift" "cdbb1cd19cf54b37add6d6c25300445a6abe24da" github "i-schuetz/SwiftCharts" "0.6.5" github "maxkonovalov/MKRingProgressView" "2.2.2" -github "ps2/rileylink_ios" "3a5a92c9a69565dc32c916294826b4e7ae02297e" +github "ps2/rileylink_ios" "475ffef52ce22bba82f6ebaf99da5ab7c8fcba4a" diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 643b8fbd3c..a7f02eaae9 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -137,6 +137,9 @@ final class LoopDataManager { /// These are not thread-safe. var settings: LoopSettings { didSet { + guard settings != oldValue else { + return + } if settings.scheduleOverride != oldValue.scheduleOverride { overrideHistory.recordOverride(settings.scheduleOverride) @@ -225,6 +228,7 @@ final class LoopDataManager { NotificationManager.clearLoopNotRunningNotifications() NotificationManager.scheduleLoopNotRunningNotifications() AnalyticsManager.shared.loopDidSucceed() + NotificationCenter.default.post(name: .LoopCompleted, object: self) } } private let lockedLastLoopCompleted: Locked @@ -1262,12 +1266,11 @@ extension LoopDataManager { extension Notification.Name { - static let LoopDataUpdated = Notification.Name(rawValue: "com.loudnate.Naterade.notification.LoopDataUpdated") - - static let LoopRunning = Notification.Name(rawValue: "com.loudnate.Naterade.notification.LoopRunning") + static let LoopDataUpdated = Notification.Name(rawValue: "com.loopkit.Loop.LoopDataUpdated") + static let LoopRunning = Notification.Name(rawValue: "com.loopkit.Loop.LoopRunning") + static let LoopCompleted = Notification.Name(rawValue: "com.loopkit.Loop.LoopCompleted") } - protocol LoopDataManagerDelegate: class { /// Informs the delegate that an immediate basal change is recommended diff --git a/Loop/Managers/NightscoutDataManager.swift b/Loop/Managers/NightscoutDataManager.swift index 89a01a6236..0557954541 100644 --- a/Loop/Managers/NightscoutDataManager.swift +++ b/Loop/Managers/NightscoutDataManager.swift @@ -10,28 +10,126 @@ import Foundation import NightscoutUploadKit import HealthKit import LoopKit +import os.log final class NightscoutDataManager { unowned let deviceManager: DeviceDataManager + + private let log = OSLog(category: "NightscoutDataManager") // Last time we uploaded device status var lastDeviceStatusUpload: Date? + // Last time we uploaded settings + var lastSettingsUpload: Date = .distantPast + + // Last time settings were updated + var lastSettingsUpdate: Date = .distantPast + init(deviceDataManager: DeviceDataManager) { self.deviceManager = deviceDataManager + NotificationCenter.default.addObserver(self, selector: #selector(loopCompleted(_:)), name: .LoopCompleted, object: deviceDataManager.loopManager) NotificationCenter.default.addObserver(self, selector: #selector(loopDataUpdated(_:)), name: .LoopDataUpdated, object: deviceDataManager.loopManager) } - + + @objc func loopDataUpdated(_ note: Notification) { guard - deviceManager.remoteDataManager.nightscoutService.uploader != nil, let rawContext = note.userInfo?[LoopDataManager.LoopUpdateContextKey] as? LoopDataManager.LoopUpdateContext.RawValue, let context = LoopDataManager.LoopUpdateContext(rawValue: rawContext), - case .tempBasal = context - else { + case .preferences = context + else { + return + } + + lastSettingsUpdate = Date() + uploadSettings() + } + + private func uploadSettings() { + guard + let uploader = deviceManager.remoteDataManager.nightscoutService.uploader, + let settings = UserDefaults.appGroup?.loopSettings, + let basalRateSchedule = UserDefaults.appGroup?.basalRateSchedule, + let insulinModelSettings = UserDefaults.appGroup?.insulinModelSettings, + let carbRatioSchedule = UserDefaults.appGroup?.carbRatioSchedule, + let insulinSensitivitySchedule = UserDefaults.appGroup?.insulinSensitivitySchedule, + let preferredUnit = settings.glucoseUnit, + let correctionSchedule = settings.glucoseTargetRangeSchedule else + { + log.default("Not uploading due to incomplete configuration") + return + } + + let targetLowItems = correctionSchedule.items.map { (item) -> ProfileSet.ScheduleItem in + return ProfileSet.ScheduleItem(offset: item.startTime, value: item.value.minValue) + } + + let targetHighItems = correctionSchedule.items.map { (item) -> ProfileSet.ScheduleItem in + return ProfileSet.ScheduleItem(offset: item.startTime, value: item.value.maxValue) + } + + let nsScheduledOverride = settings.scheduleOverride?.nsScheduleOverride(for: preferredUnit) + + let nsPreMealTargetRange: ClosedRange? + if let preMealTargetRange = settings.preMealTargetRange { + nsPreMealTargetRange = ClosedRange(uncheckedBounds: ( + lower: preMealTargetRange.minValue, + upper: preMealTargetRange.maxValue)) + } else { + nsPreMealTargetRange = nil + } + + let nsLoopSettings = NightscoutUploadKit.LoopSettings( + dosingEnabled: settings.dosingEnabled, + overridePresets: settings.overridePresets.map { $0.nsScheduleOverride(for: preferredUnit) }, + scheduleOverride: nsScheduledOverride, + minimumBGGuard: settings.suspendThreshold?.quantity.doubleValue(for: preferredUnit), + preMealTargetRange: nsPreMealTargetRange, + maximumBasalRatePerHour: settings.maximumBasalRatePerHour, + maximumBolus: settings.maximumBolus) + + let profile = ProfileSet.Profile( + timezone: basalRateSchedule.timeZone, + dia: insulinModelSettings.model.effectDuration, + sensitivity: insulinSensitivitySchedule.items.scheduleItems(), + carbratio: carbRatioSchedule.items.scheduleItems(), + basal: basalRateSchedule.items.scheduleItems(), + targetLow: targetLowItems, + targetHigh: targetHighItems, + units: correctionSchedule.unit.shortLocalizedUnitString()) + + let store: [String: ProfileSet.Profile] = [ + "Default": profile + ] + + let profileSet = ProfileSet( + startDate: Date(), + units: preferredUnit.shortLocalizedUnitString(), + enteredBy: "Loop", + defaultProfile: "Default", + store: store, + settings: nsLoopSettings) + + log.default("Uploading profile") + + uploader.uploadProfile(profileSet: profileSet) { (result) in + switch(result) { + case .failure(let error): + self.log.error("Settings upload failed: %{public}@", String(describing: error)) + case .success: + DispatchQueue.main.async { + self.lastSettingsUpload = Date() + } + } + } + } + + @objc func loopCompleted(_ note: Notification) { + guard deviceManager.remoteDataManager.nightscoutService.uploader != nil else { return } @@ -67,6 +165,10 @@ final class NightscoutDataManager { recommendedBolus: recommendedBolus, loopError: loopError ) + + if self.lastSettingsUpdate > self.lastSettingsUpload { + self.uploadSettings() + } } } } @@ -179,9 +281,9 @@ final class NightscoutDataManager { } else { pumpStatus = nil } - - upload(pumpStatus: pumpStatus, loopStatus: loopStatus, deviceName: nil, firmwareVersion: nil, uploaderStatus: getUploaderStatus()) + log.default("Uploading loop status") + upload(pumpStatus: pumpStatus, loopStatus: loopStatus, deviceName: nil, firmwareVersion: nil, uploaderStatus: getUploaderStatus()) } private func getUploaderStatus() -> UploaderStatus { @@ -197,10 +299,6 @@ final class NightscoutDataManager { return UploaderStatus(name: uploaderDevice.name, timestamp: Date(), battery: battery) } - func upload(pumpStatus: NightscoutUploadKit.PumpStatus?, deviceName: String?, firmwareVersion: String?) { - upload(pumpStatus: pumpStatus, loopStatus: nil, deviceName: deviceName, firmwareVersion: firmwareVersion, uploaderStatus: nil) - } - private func upload(pumpStatus: NightscoutUploadKit.PumpStatus?, loopStatus: LoopStatus?, deviceName: String?, firmwareVersion: String?, uploaderStatus: UploaderStatus?) { guard let uploader = deviceManager.remoteDataManager.nightscoutService.uploader else { @@ -257,3 +355,85 @@ final class NightscoutDataManager { } } +private extension Array where Element == RepeatingScheduleValue { + func scheduleItems() -> [ProfileSet.ScheduleItem] { + return map { (item) -> ProfileSet.ScheduleItem in + return ProfileSet.ScheduleItem(offset: item.startTime, value: item.value) + } + } +} + +private extension LoopKit.TemporaryScheduleOverride { + func nsScheduleOverride(for unit: HKUnit) -> NightscoutUploadKit.TemporaryScheduleOverride { + let nsTargetRange: ClosedRange? + if let targetRange = settings.targetRange { + nsTargetRange = ClosedRange(uncheckedBounds: ( + lower: targetRange.lowerBound.doubleValue(for: unit), + upper: targetRange.upperBound.doubleValue(for: unit))) + } else { + nsTargetRange = nil + } + + let nsDuration: TimeInterval + switch duration { + case .finite(let interval): + nsDuration = interval + case .indefinite: + nsDuration = 0 + } + + let name: String? + let symbol: String? + + switch context { + case .custom: + name = nil + symbol = nil + case .legacyWorkout: + name = "Workout" + symbol = nil + case .preMeal: + name = "PreMeal" + symbol = nil + case .preset(let preset): + name = preset.name + symbol = preset.symbol + } + + return NightscoutUploadKit.TemporaryScheduleOverride( + targetRange: nsTargetRange, + insulinNeedsScaleFactor: settings.insulinNeedsScaleFactor, + symbol: symbol, + duration: nsDuration, + name: name) + } +} + +private extension LoopKit.TemporaryScheduleOverridePreset { + func nsScheduleOverride(for unit: HKUnit) -> NightscoutUploadKit.TemporaryScheduleOverride { + let nsTargetRange: ClosedRange? + if let targetRange = settings.targetRange { + nsTargetRange = ClosedRange(uncheckedBounds: ( + lower: targetRange.lowerBound.doubleValue(for: unit), + upper: targetRange.upperBound.doubleValue(for: unit))) + } else { + nsTargetRange = nil + } + + let nsDuration: TimeInterval + switch duration { + case .finite(let interval): + nsDuration = interval + case .indefinite: + nsDuration = 0 + } + + return NightscoutUploadKit.TemporaryScheduleOverride( + targetRange: nsTargetRange, + insulinNeedsScaleFactor: settings.insulinNeedsScaleFactor, + symbol: self.symbol, + duration: nsDuration, + name: self.name) + } +} + From 7677e5421ee5f83f6bf96e877aef0e02f8b87a66 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 18 Aug 2019 09:47:46 -0500 Subject: [PATCH 075/132] Update dependency revisions --- Cartfile.resolved | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 371f5166b5..6db11b0e9b 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,8 +1,8 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "fe92d93c24d18ff9755ff027e9b036d3769a8a0a" github "LoopKit/G4ShareSpy" "f6ae0cf1753131135f26046ddf07e096da884b84" -github "LoopKit/LoopKit" "a0dc68e428e2fe6cba89df4809703e9d2087ebfe" +github "LoopKit/LoopKit" "0df7678ccf06c972a082663eacf19218ffc98869" github "LoopKit/dexcom-share-client-swift" "cdbb1cd19cf54b37add6d6c25300445a6abe24da" github "i-schuetz/SwiftCharts" "0.6.5" github "maxkonovalov/MKRingProgressView" "2.2.2" -github "ps2/rileylink_ios" "475ffef52ce22bba82f6ebaf99da5ab7c8fcba4a" +github "ps2/rileylink_ios" "7fc5e851c15eb5166ec9b7012bb12f4d78702491" From fd8377e7d0bf1f02d835b99ace34ff4ab6dd8498 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 18 Aug 2019 11:16:35 -0500 Subject: [PATCH 076/132] Remove dev string for testflight distribution, bump version --- DoseMathTests/Info.plist | 2 +- Learn/Info.plist | 2 +- Loop Status Extension/Info.plist | 2 +- Loop.xcodeproj/project.pbxproj | 28 ++++++++++++++-------------- Loop/Info.plist | 2 +- LoopCore/Info.plist | 2 +- LoopTests/Info.plist | 2 +- LoopUI/Info.plist | 2 +- WatchApp Extension/Info.plist | 2 +- WatchApp/Info.plist | 2 +- 10 files changed, 23 insertions(+), 23 deletions(-) diff --git a/DoseMathTests/Info.plist b/DoseMathTests/Info.plist index b90781aa4f..cf0ee080ee 100644 --- a/DoseMathTests/Info.plist +++ b/DoseMathTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.10.0dev + 1.10.1 CFBundleSignature ???? CFBundleVersion diff --git a/Learn/Info.plist b/Learn/Info.plist index 07b79e155e..f870dba92f 100644 --- a/Learn/Info.plist +++ b/Learn/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.10.0dev + 1.10.1 CFBundleVersion $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS diff --git a/Loop Status Extension/Info.plist b/Loop Status Extension/Info.plist index 6cb91fe668..93631d1736 100644 --- a/Loop Status Extension/Info.plist +++ b/Loop Status Extension/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.10.0dev + 1.10.1 CFBundleVersion $(CURRENT_PROJECT_VERSION) AppGroupIdentifier diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 3afd81d15d..d8c606e08a 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -3210,7 +3210,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer: loudnate@gmail.com (XZN842LDLT)"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 56; + CURRENT_PROJECT_VERSION = 57; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -3282,7 +3282,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer: loudnate@gmail.com (XZN842LDLT)"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 56; + CURRENT_PROJECT_VERSION = 57; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3449,11 +3449,11 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 56; + CURRENT_PROJECT_VERSION = 57; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 56; + DYLIB_CURRENT_VERSION = 57; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3485,11 +3485,11 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 56; + CURRENT_PROJECT_VERSION = 57; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 56; + DYLIB_CURRENT_VERSION = 57; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3573,11 +3573,11 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 56; + CURRENT_PROJECT_VERSION = 57; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 56; + DYLIB_CURRENT_VERSION = 57; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = LoopCore/Info.plist; @@ -3606,11 +3606,11 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 56; + CURRENT_PROJECT_VERSION = 57; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 56; + DYLIB_CURRENT_VERSION = 57; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = LoopCore/Info.plist; @@ -3739,11 +3739,11 @@ CLANG_WARN_SUSPICIOUS_MOVES = YES; CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - CURRENT_PROJECT_VERSION = 56; + CURRENT_PROJECT_VERSION = 57; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 56; + DYLIB_CURRENT_VERSION = 57; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = LoopUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -3766,11 +3766,11 @@ CLANG_WARN_SUSPICIOUS_MOVES = YES; CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - CURRENT_PROJECT_VERSION = 56; + CURRENT_PROJECT_VERSION = 57; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 56; + DYLIB_CURRENT_VERSION = 57; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = LoopUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/Loop/Info.plist b/Loop/Info.plist index 80083c8794..ee97c8379f 100644 --- a/Loop/Info.plist +++ b/Loop/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.10.0dev + 1.10.1 CFBundleSignature ???? CFBundleURLTypes diff --git a/LoopCore/Info.plist b/LoopCore/Info.plist index e3942fbb31..0a0c2136d4 100644 --- a/LoopCore/Info.plist +++ b/LoopCore/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.10.0dev + 1.10.1 CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/LoopTests/Info.plist b/LoopTests/Info.plist index b90781aa4f..cf0ee080ee 100644 --- a/LoopTests/Info.plist +++ b/LoopTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.10.0dev + 1.10.1 CFBundleSignature ???? CFBundleVersion diff --git a/LoopUI/Info.plist b/LoopUI/Info.plist index f383ba1b06..1a002eb8b6 100644 --- a/LoopUI/Info.plist +++ b/LoopUI/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.10.0dev + 1.10.1 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/WatchApp Extension/Info.plist b/WatchApp Extension/Info.plist index 536c502c12..ef50f76c43 100644 --- a/WatchApp Extension/Info.plist +++ b/WatchApp Extension/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.10.0dev + 1.10.1 CFBundleSignature ???? CFBundleVersion diff --git a/WatchApp/Info.plist b/WatchApp/Info.plist index b881f84c30..331cfb86e9 100644 --- a/WatchApp/Info.plist +++ b/WatchApp/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.10.0dev + 1.10.1 CFBundleSignature ???? CFBundleVersion From a1f9fa0f54c11534056af7866440786276989b33 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 19 Aug 2019 07:05:11 -0500 Subject: [PATCH 077/132] Upload enabled override as part of status (#1070) * Upload enabled override as part of status * Update rileylink_ios rev --- Cartfile.resolved | 2 +- Loop/Managers/NightscoutDataManager.swift | 68 +++++++++++++++++++---- 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 6db11b0e9b..e1a24a7f2f 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -5,4 +5,4 @@ github "LoopKit/LoopKit" "0df7678ccf06c972a082663eacf19218ffc98869" github "LoopKit/dexcom-share-client-swift" "cdbb1cd19cf54b37add6d6c25300445a6abe24da" github "i-schuetz/SwiftCharts" "0.6.5" github "maxkonovalov/MKRingProgressView" "2.2.2" -github "ps2/rileylink_ios" "7fc5e851c15eb5166ec9b7012bb12f4d78702491" +github "ps2/rileylink_ios" "64513224c34b9ce796c2a88f1b94e1c2fe107a59" diff --git a/Loop/Managers/NightscoutDataManager.swift b/Loop/Managers/NightscoutDataManager.swift index 0557954541..320f1a174a 100644 --- a/Loop/Managers/NightscoutDataManager.swift +++ b/Loop/Managers/NightscoutDataManager.swift @@ -18,7 +18,7 @@ final class NightscoutDataManager { unowned let deviceManager: DeviceDataManager private let log = OSLog(category: "NightscoutDataManager") - + // Last time we uploaded device status var lastDeviceStatusUpload: Date? @@ -136,6 +136,7 @@ final class NightscoutDataManager { deviceManager.loopManager.getLoopState { (manager, state) in var loopError = state.error let recommendedBolus: Double? + recommendedBolus = state.recommendedBolus?.recommendation.amount @@ -179,8 +180,7 @@ final class NightscoutDataManager { insulinOnBoard: InsulinValue? = nil, carbsOnBoard: CarbValue? = nil, predictedGlucose: [GlucoseValue]? = nil, - recommendedTempBasal: (recommendation: TempBasalRecommendation, - date: Date)? = nil, + recommendedTempBasal: (recommendation: TempBasalRecommendation, date: Date)? = nil, recommendedBolus: Double? = nil, loopError: Error? = nil) { @@ -236,10 +236,10 @@ final class NightscoutDataManager { let loopName = Bundle.main.bundleDisplayName let loopVersion = Bundle.main.shortVersionString + //this is the only pill that has the option to modify the text + //to do that pass a different name value instead of loopName let loopStatus = LoopStatus(name: loopName, version: loopVersion, timestamp: statusTime, iob: iob, cob: cob, predicted: predicted, recommendedTempBasal: recommended, recommendedBolus: recommendedBolus, enacted: loopEnacted, failureReason: loopError) - - - + let pumpStatus: NightscoutUploadKit.PumpStatus? if let pumpManagerStatus = deviceManager.pumpManagerStatus @@ -281,9 +281,52 @@ final class NightscoutDataManager { } else { pumpStatus = nil } - + //add overrideStatus + + let overrideStatus: NightscoutUploadKit.OverrideStatus? + let settings = deviceManager.loopManager.settings + let unit: HKUnit = settings.glucoseTargetRangeSchedule?.unit ?? HKUnit.milligramsPerDeciliter + if let override = settings.scheduleOverride, override.isActive(), + let range = settings.glucoseTargetRangeScheduleApplyingOverrideIfActive?.value(at: Date()) { + let lowerTarget : HKQuantity = HKQuantity(unit : unit, doubleValue: range.minValue) + let upperTarget : HKQuantity = HKQuantity(unit : unit, doubleValue: range.maxValue) + let correctionRange = CorrectionRange(minValue: lowerTarget, maxValue: upperTarget) + let endDate = override.endDate + let duration : TimeInterval? + if override.duration == .indefinite { + duration = nil + } + else + { + duration = round(endDate.timeIntervalSince(Date())) + + } + let name : String? + + switch override.context { + case .preMeal: + name = "preMeal" + case .custom: + name = "Custom" + case .preset(let preset): + name = preset.name + case .legacyWorkout: + name = "Workout" + } + + + overrideStatus = NightscoutUploadKit.OverrideStatus(name: name, timestamp: Date(), active: true, currentCorrectionRange: correctionRange, duration: duration, multiplier: override.settings.insulinNeedsScaleFactor) + + } + + else + + { + overrideStatus = NightscoutUploadKit.OverrideStatus(timestamp: Date(), active: false) + } log.default("Uploading loop status") - upload(pumpStatus: pumpStatus, loopStatus: loopStatus, deviceName: nil, firmwareVersion: nil, uploaderStatus: getUploaderStatus()) + upload(pumpStatus: pumpStatus, loopStatus: loopStatus, deviceName: nil, firmwareVersion: nil, uploaderStatus: getUploaderStatus(), overrideStatus: overrideStatus) + } private func getUploaderStatus() -> UploaderStatus { @@ -299,7 +342,11 @@ final class NightscoutDataManager { return UploaderStatus(name: uploaderDevice.name, timestamp: Date(), battery: battery) } - private func upload(pumpStatus: NightscoutUploadKit.PumpStatus?, loopStatus: LoopStatus?, deviceName: String?, firmwareVersion: String?, uploaderStatus: UploaderStatus?) { + func upload(pumpStatus: NightscoutUploadKit.PumpStatus?, deviceName: String?, firmwareVersion: String?) { + upload(pumpStatus: pumpStatus, loopStatus: nil, deviceName: deviceName, firmwareVersion: firmwareVersion, uploaderStatus: nil, overrideStatus: nil) + } + + private func upload(pumpStatus: NightscoutUploadKit.PumpStatus?, loopStatus: LoopStatus?, deviceName: String?, firmwareVersion: String?, uploaderStatus: UploaderStatus?, overrideStatus: OverrideStatus?) { guard let uploader = deviceManager.remoteDataManager.nightscoutService.uploader else { return @@ -315,7 +362,7 @@ final class NightscoutDataManager { let uploaderDevice = UIDevice.current // Build DeviceStatus - let deviceStatus = DeviceStatus(device: "loop://\(uploaderDevice.name)", timestamp: Date(), pumpStatus: pumpStatus, uploaderStatus: uploaderStatus, loopStatus: loopStatus, radioAdapter: nil) + let deviceStatus = DeviceStatus(device: "loop://\(uploaderDevice.name)", timestamp: Date(), pumpStatus: pumpStatus, uploaderStatus: uploaderStatus, loopStatus: loopStatus, radioAdapter: nil, overrideStatus: overrideStatus) self.lastDeviceStatusUpload = Date() uploader.uploadDeviceStatus(deviceStatus) @@ -436,4 +483,3 @@ private extension LoopKit.TemporaryScheduleOverridePreset { name: self.name) } } - From cdb2c13990f5fc4125d797e88e1b51efdcca7554 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 19 Aug 2019 22:56:19 -0500 Subject: [PATCH 078/132] Avoid extra conversions of temp target ranges (#1072) * Avoid extra conversions of temp target ranges * Remove out of date comment --- .../Managers/ComplicationChartManager.swift | 2 +- .../Models/GlucoseChartScaler.swift | 11 ++------- .../Scenes/GlucoseChartScene.swift | 4 ++-- .../Scenes/GlucoseChartValueHashable.swift | 24 +++++++++---------- 4 files changed, 16 insertions(+), 25 deletions(-) diff --git a/WatchApp Extension/Managers/ComplicationChartManager.swift b/WatchApp Extension/Managers/ComplicationChartManager.swift index c6a28f8a67..3ee5e54a4e 100644 --- a/WatchApp Extension/Managers/ComplicationChartManager.swift +++ b/WatchApp Extension/Managers/ComplicationChartManager.swift @@ -119,7 +119,7 @@ final class ComplicationChartManager { private func drawOverridesIfNeeded(in context: CGContext, using scaler: GlucoseChartScaler) { guard let override = data?.activeScheduleOverride, - let overrideHashable = TemporaryScheduleOverrideHashable(override, unit: unit) + let overrideHashable = TemporaryScheduleOverrideHashable(override) else { return } diff --git a/WatchApp Extension/Models/GlucoseChartScaler.swift b/WatchApp Extension/Models/GlucoseChartScaler.swift index a9293e0974..cb03f8380b 100644 --- a/WatchApp Extension/Models/GlucoseChartScaler.swift +++ b/WatchApp Extension/Models/GlucoseChartScaler.swift @@ -59,16 +59,9 @@ struct GlucoseChartScaler { minHeight: CGFloat = 2, alignedToScreenScale screenScale: CGFloat = WKInterfaceDevice.current().screenScale ) -> CGRect { - let minY: Double - let maxY: Double - if unit != .milligramsPerDeciliter { - minY = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: range.min).doubleValue(for: unit) - maxY = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: range.max).doubleValue(for: unit) - } else { - minY = range.min - maxY = range.max - } + let minY = range.min.doubleValue(for: unit) + let maxY = range.max.doubleValue(for: unit) switch coordinateSystem { case .standard: diff --git a/WatchApp Extension/Scenes/GlucoseChartScene.swift b/WatchApp Extension/Scenes/GlucoseChartScene.swift index 2010570945..0188ab6d75 100644 --- a/WatchApp Extension/Scenes/GlucoseChartScene.swift +++ b/WatchApp Extension/Scenes/GlucoseChartScene.swift @@ -250,7 +250,7 @@ class GlucoseChartScene: SKScene { // Make temporary overrides visually match what we do in the Loop app. This means that we have // one darker box which represents the duration of the override, but we have a second lighter box which // extends to the end of the visible window. - if let range = activeOverride, let rangeHashable = TemporaryScheduleOverrideHashable(range, unit: unit) { + if let range = activeOverride, let rangeHashable = TemporaryScheduleOverrideHashable(range) { let (sprite1, created) = getSprite(forHash: rangeHashable.chartHashValue) sprite1.color = UIColor.glucose.withAlphaComponent(0.4) sprite1.zPosition = NodePlane.overrideRanges.zPosition @@ -260,7 +260,7 @@ class GlucoseChartScene: SKScene { if rangeHashable.end < spannedInterval.end { let extendedDuration = spannedInterval.end.timeIntervalSince(rangeHashable.start) let extendedRange = TemporaryScheduleOverride(context: rangeHashable.override.context, settings: rangeHashable.override.settings, startDate: rangeHashable.start, duration: .finite(extendedDuration)) - let extendedRangeHashable = TemporaryScheduleOverrideHashable(extendedRange, unit: unit)! // Target range already known to be non-nil + let extendedRangeHashable = TemporaryScheduleOverrideHashable(extendedRange)! // Target range already known to be non-nil let (sprite2, created) = getSprite(forHash: extendedRangeHashable.chartHashValue) sprite2.color = UIColor.glucose.withAlphaComponent(0.25) sprite2.zPosition = NodePlane.overrideRanges.zPosition diff --git a/WatchApp Extension/Scenes/GlucoseChartValueHashable.swift b/WatchApp Extension/Scenes/GlucoseChartValueHashable.swift index 4be69fcc88..808ba82d47 100644 --- a/WatchApp Extension/Scenes/GlucoseChartValueHashable.swift +++ b/WatchApp Extension/Scenes/GlucoseChartValueHashable.swift @@ -12,8 +12,8 @@ import HealthKit protocol GlucoseChartValueHashable { var start: Date { get } var end: Date { get } - var min: Double { get } // milligramsPerDeciliter - var max: Double { get } // milligramsPerDeciliter + var min: HKQuantity { get } + var max: HKQuantity { get } var chartHashValue: Int { get } } @@ -66,25 +66,23 @@ extension AbsoluteScheduleValue: GlucoseChartValueHashable where T == ClosedRang return endDate } - var min: Double { - return value.lowerBound.doubleValue(for: .milligramsPerDeciliter) + var min: HKQuantity { + return value.lowerBound } - var max: Double { - return value.upperBound.doubleValue(for: .milligramsPerDeciliter) + var max: HKQuantity { + return value.upperBound } } struct TemporaryScheduleOverrideHashable: GlucoseChartValueHashable { let override: TemporaryScheduleOverride - let unit: HKUnit - init?(_ override: TemporaryScheduleOverride, unit: HKUnit) { + init?(_ override: TemporaryScheduleOverride) { guard override.settings.targetRange != nil else { return nil } self.override = override - self.unit = unit } var start: Date { @@ -95,11 +93,11 @@ struct TemporaryScheduleOverrideHashable: GlucoseChartValueHashable { return override.activeInterval.end } - var min: Double { - return override.settings.targetRange!.lowerBound.doubleValue(for: unit) + var min: HKQuantity { + return override.settings.targetRange!.lowerBound } - var max: Double { - return override.settings.targetRange!.upperBound.doubleValue(for: unit) + var max: HKQuantity { + return override.settings.targetRange!.upperBound } } From 800c9212381b7835d6bc234daf766f12f6d618bd Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 20 Aug 2019 23:47:27 -0500 Subject: [PATCH 079/132] Update Cartfile.resolved revisions --- Cartfile.resolved | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index e1a24a7f2f..e96e802f1e 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,8 +1,8 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "fe92d93c24d18ff9755ff027e9b036d3769a8a0a" github "LoopKit/G4ShareSpy" "f6ae0cf1753131135f26046ddf07e096da884b84" -github "LoopKit/LoopKit" "0df7678ccf06c972a082663eacf19218ffc98869" +github "LoopKit/LoopKit" "72ef9c6067b7f19b926471f6d5ecd7a7b2d8118e" github "LoopKit/dexcom-share-client-swift" "cdbb1cd19cf54b37add6d6c25300445a6abe24da" github "i-schuetz/SwiftCharts" "0.6.5" github "maxkonovalov/MKRingProgressView" "2.2.2" -github "ps2/rileylink_ios" "64513224c34b9ce796c2a88f1b94e1c2fe107a59" +github "ps2/rileylink_ios" "557f74a16bf31189a275e2610e0b8e922c0cdf92" From 615f06a5e56707d31082c5987cbac8182673df4d Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 23 Aug 2019 16:42:04 -0500 Subject: [PATCH 080/132] Plugins (#22) * Add DashKit * Remove output from copy-files phase to force update each time * Sign plugins * Remove quotes to handle empty list * Handle spaces, deref symlink, and re-enable bitcode * Comment out testing vars * Fix version for archive builds * Move plugin's frameworks to main app frameworks directory * Fix search for plugin's frameworks * Set marketing version via config * Update copy frameworks script to dereference framework symlinks * Use copy-frameworks script for extensions and learn app * Bumping build version * Add missing script * Back out development team id changes * Scripts fixes from review * Fix capitalization * Small tweaks --- Common/Models/PumpManager.swift | 28 +-- Common/Models/PumpManagerUI.swift | 2 +- DoseMathTests/Info.plist | 2 +- Learn/Info.plist | 2 +- .../Base.lproj/InfoPlist.strings | 3 - Loop Status Extension/Info.plist | 10 +- Loop.xcconfig | 5 +- Loop.xcodeproj/project.pbxproj | 187 ++++++++++++------ Loop/Base.lproj/InfoPlist.strings | 2 - .../AppStore-1024pt@1x.png} | Bin .../AppIcon.appiconset/Contents.json | 2 +- .../AppIcon.appiconset/Icon-60@2x.png | Bin .../AppIcon.appiconset/Icon-60@3x.png | Bin .../AppIcon.appiconset/Icon-76.png | Bin .../AppIcon.appiconset/Icon-76@2x.png | Bin .../AppIcon.appiconset/Icon-83.5@2x.png | Bin .../Icon-Notification-20.png | Bin .../Icon-Notification-40.png | Bin .../Icon-Notification-41.png | Bin .../Icon-Notification-42.png | Bin .../AppIcon.appiconset/Icon-Small-40@2x-1.png | Bin .../AppIcon.appiconset/Icon-Small-40@2x.png | Bin .../AppIcon.appiconset/Icon-Small-60.png | Bin .../AppIcon.appiconset/Icon-Small-60@2x.png | Bin .../AppIcon.appiconset/Icon-Small.png | Bin .../AppIcon.appiconset/Icon-Small@2x-1.png | Bin .../AppIcon.appiconset/Icon-Small@2x.png | Bin .../AppIcon.appiconset/Icon-Small@3x.png | Bin .../Contents.json | 0 .../Pre-Meal Selected.imageset/Contents.json | 0 .../Pre-Meal Selected.pdf | Bin .../Pre-Meal.imageset/Contents.json | 0 .../Pre-Meal.imageset/Pre-Meal.pdf | Bin .../Uploading.imageset/Contents.json | 0 .../Uploading.imageset/Uploading.pdf | Bin .../bolus.imageset/Contents.json | 0 .../bolus.imageset/bolus.pdf | Bin .../carbs.imageset/Contents.json | 0 .../carbs.imageset/Meal.pdf | Bin .../settings.imageset/Contents.json | 0 .../settings.imageset/settings.png | Bin .../settings.imageset/settings@2x.png | Bin .../settings.imageset/settings@3x.png | Bin .../settings.imageset/settings_compact@2x.png | Bin .../settings.imageset/settings_compact@3x.png | Bin .../workout-selected.imageset/Contents.json | 0 .../workout-selected.pdf | Bin .../workout.imageset/Contents.json | 0 .../workout.imageset/workout.pdf | Bin Loop/Extensions/UIAlertController.swift | 8 +- Loop/Extensions/UserDefaults+Loop.swift | 18 +- Loop/Info.plist | 2 +- Loop/Managers/DeviceDataManager.swift | 54 ++++- Loop/Plugins/LoopPlugins.swift | 79 ++++++++ .../SettingsTableViewController.swift | 9 +- LoopCore/Info.plist | 2 +- LoopTests/Info.plist | 2 +- LoopUI/Info.plist | 2 +- Scripts/build-derived-assets.sh | 20 ++ Scripts/build-derived-watch-assets.sh | 19 ++ Scripts/copy-frameworks.sh | 44 +++++ Scripts/copy-plugins.sh | 38 ++++ WatchApp Extension/Info.plist | 2 +- .../AppIcon.appiconset/Contents.json | 0 .../Icon-App-Store-1024.png | Bin .../Icon-AppleWatch-100x100@2x.png | Bin .../Icon-AppleWatch-216x216@2x.png | Bin .../Icon-AppleWatch-24x24@2x.png | Bin .../Icon-AppleWatch-27.5x27.5@2x.png | Bin .../Icon-AppleWatch-40x40@2x.png | Bin .../Icon-AppleWatch-44x44@2x.png | Bin .../Icon-AppleWatch-86x86@2x.png | Bin .../Icon-AppleWatch-98x98@2x.png | Bin .../Icon-AppleWatch-Companion-29x29@2x.png | Bin .../Icon-AppleWatch-Companion-29x29@3x.png | Bin .../Contents.json | 0 .../Graph menu icons/1-hour-graph-38mm.png | Bin .../Graph menu icons/1-hour-graph-42mm.png | Bin .../1-hour-graph-38mm.png | Bin .../1-hour-graph-42mm.png | Bin .../1-hour-graph.imageset/Contents.json | 0 .../Graph menu icons/2-hour-graph-38mm.png | Bin .../Graph menu icons/2-hour-graph-42mm.png | Bin .../2-hour-graph-38mm.png | Bin .../2-hour-graph-42mm.png | Bin .../2-hour-graph.imageset/Contents.json | 0 .../Graph menu icons/3-hour-graph-38mm.png | Bin .../Graph menu icons/3-hour-graph-42mm.png | Bin .../3-hour-graph-38mm.png | Bin .../3-hour-graph-42mm.png | Bin .../3-hour-graph.imageset/Contents.json | 0 .../Graph menu icons/Contents.json | 0 .../bolus.imageset/Bolus Icon 42mm.pdf | Bin .../bolus.imageset/Bolus icon 38mm.png | Bin .../bolus.imageset/Contents.json | 0 .../carbs.imageset/Carb Icon 42mm.pdf | Bin .../carbs.imageset/Carbs icon 38mm.png | Bin .../carbs.imageset/Contents.json | 0 .../loop/Contents.json | 0 .../loop/loop_aging.imageset/Contents.json | 0 .../loop_aging.imageset/loop-aging@38mm.png | Bin .../loop_aging.imageset/loop-aging@42mm.png | Bin .../loop/loop_fresh.imageset/Contents.json | 0 .../loop_fresh.imageset/loop-fresh@38mm.png | Bin .../loop_fresh.imageset/loop-fresh@42mm.png | Bin .../loop/loop_stale.imageset/Contents.json | 0 .../loop_stale.imageset/loop-stale@38mm.png | Bin .../loop_stale.imageset/loop-stale@42mm.png | Bin .../loop/loop_unknown.imageset/Contents.json | 0 .../loop-unknown@38mm.png | Bin .../loop-unknown@42mm.png | Bin .../pre-meal.imageset/Contents.json | 0 .../pre-meal.imageset/Pre-Meal 38mm.png | Bin .../pre-meal.imageset/Pre-Meal 42mm.pdf | Bin .../workout.imageset/Contents.json | 0 .../workout.imageset/Workout 38mm.png | Bin .../workout.imageset/Workout 42mm.pdf | Bin WatchApp/Info.plist | 10 +- 118 files changed, 417 insertions(+), 135 deletions(-) rename Loop/{Assets.xcassets/AppIcon.appiconset/App Store Copy@2x.png => DefaultAssets.xcassets/AppIcon.appiconset/AppStore-1024pt@1x.png} (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Contents.json (98%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-60@2x.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-60@3x.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-76.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-76@2x.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-83.5@2x.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-Notification-20.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-Notification-40.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-Notification-41.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-Notification-42.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-Small-40@2x-1.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-Small-40@2x.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-Small-60.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-Small-60@2x.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-Small.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-Small@2x-1.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-Small@2x.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-Small@3x.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/Contents.json (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/Pre-Meal Selected.imageset/Contents.json (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/Pre-Meal Selected.imageset/Pre-Meal Selected.pdf (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/Pre-Meal.imageset/Contents.json (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/Pre-Meal.imageset/Pre-Meal.pdf (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/Uploading.imageset/Contents.json (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/Uploading.imageset/Uploading.pdf (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/bolus.imageset/Contents.json (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/bolus.imageset/bolus.pdf (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/carbs.imageset/Contents.json (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/carbs.imageset/Meal.pdf (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/settings.imageset/Contents.json (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/settings.imageset/settings.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/settings.imageset/settings@2x.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/settings.imageset/settings@3x.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/settings.imageset/settings_compact@2x.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/settings.imageset/settings_compact@3x.png (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/workout-selected.imageset/Contents.json (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/workout-selected.imageset/workout-selected.pdf (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/workout.imageset/Contents.json (100%) rename Loop/{Assets.xcassets => DefaultAssets.xcassets}/workout.imageset/workout.pdf (100%) create mode 100644 Loop/Plugins/LoopPlugins.swift create mode 100755 Scripts/build-derived-assets.sh create mode 100755 Scripts/build-derived-watch-assets.sh create mode 100755 Scripts/copy-frameworks.sh create mode 100755 Scripts/copy-plugins.sh rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Contents.json (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-App-Store-1024.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-AppleWatch-100x100@2x.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-AppleWatch-216x216@2x.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-AppleWatch-24x24@2x.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-AppleWatch-27.5x27.5@2x.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-AppleWatch-40x40@2x.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-AppleWatch-44x44@2x.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-AppleWatch-86x86@2x.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-AppleWatch-98x98@2x.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-AppleWatch-Companion-29x29@2x.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/AppIcon.appiconset/Icon-AppleWatch-Companion-29x29@3x.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/Contents.json (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/Graph menu icons/1-hour-graph-38mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/Graph menu icons/1-hour-graph-42mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/Graph menu icons/1-hour-graph.imageset/1-hour-graph-38mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/Graph menu icons/1-hour-graph.imageset/1-hour-graph-42mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/Graph menu icons/1-hour-graph.imageset/Contents.json (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/Graph menu icons/2-hour-graph-38mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/Graph menu icons/2-hour-graph-42mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/Graph menu icons/2-hour-graph.imageset/2-hour-graph-38mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/Graph menu icons/2-hour-graph.imageset/2-hour-graph-42mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/Graph menu icons/2-hour-graph.imageset/Contents.json (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/Graph menu icons/3-hour-graph-38mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/Graph menu icons/3-hour-graph-42mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/Graph menu icons/3-hour-graph.imageset/3-hour-graph-38mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/Graph menu icons/3-hour-graph.imageset/3-hour-graph-42mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/Graph menu icons/3-hour-graph.imageset/Contents.json (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/Graph menu icons/Contents.json (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/bolus.imageset/Bolus Icon 42mm.pdf (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/bolus.imageset/Bolus icon 38mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/bolus.imageset/Contents.json (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/carbs.imageset/Carb Icon 42mm.pdf (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/carbs.imageset/Carbs icon 38mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/carbs.imageset/Contents.json (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/loop/Contents.json (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/loop/loop_aging.imageset/Contents.json (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/loop/loop_aging.imageset/loop-aging@38mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/loop/loop_aging.imageset/loop-aging@42mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/loop/loop_fresh.imageset/Contents.json (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/loop/loop_fresh.imageset/loop-fresh@38mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/loop/loop_fresh.imageset/loop-fresh@42mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/loop/loop_stale.imageset/Contents.json (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/loop/loop_stale.imageset/loop-stale@38mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/loop/loop_stale.imageset/loop-stale@42mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/loop/loop_unknown.imageset/Contents.json (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/loop/loop_unknown.imageset/loop-unknown@38mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/loop/loop_unknown.imageset/loop-unknown@42mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/pre-meal.imageset/Contents.json (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/pre-meal.imageset/Pre-Meal 38mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/pre-meal.imageset/Pre-Meal 42mm.pdf (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/workout.imageset/Contents.json (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/workout.imageset/Workout 38mm.png (100%) rename WatchApp/{Assets.xcassets => DefaultAssets.xcassets}/workout.imageset/Workout 42mm.pdf (100%) diff --git a/Common/Models/PumpManager.swift b/Common/Models/PumpManager.swift index 6bc58bb7ec..0aa371f53d 100644 --- a/Common/Models/PumpManager.swift +++ b/Common/Models/PumpManager.swift @@ -11,31 +11,23 @@ import MinimedKit import MockKit -let allPumpManagers: [PumpManager.Type] = [ +public struct AvailableDevice { + let identifier: String + let localizedTitle: String +} + + +let staticPumpManagers: [PumpManager.Type] = [ MinimedPumpManager.self, MockPumpManager.self, ] -private let managersByIdentifier: [String: PumpManager.Type] = allPumpManagers.reduce(into: [:]) { (map, Type) in +let staticPumpManagersByIdentifier: [String: PumpManager.Type] = staticPumpManagers.reduce(into: [:]) { (map, Type) in map[Type.managerIdentifier] = Type } -func PumpManagerTypeFromRawValue(_ rawValue: [String: Any]) -> PumpManager.Type? { - guard let managerIdentifier = rawValue["managerIdentifier"] as? String else { - return nil - } - - return managersByIdentifier[managerIdentifier] -} - -func PumpManagerFromRawValue(_ rawValue: [String: Any]) -> PumpManager? { - guard let rawState = rawValue["state"] as? PumpManager.RawStateValue, - let Manager = PumpManagerTypeFromRawValue(rawValue) - else { - return nil - } - - return Manager.init(rawState: rawState) +let availableStaticPumpManagers = staticPumpManagers.map { (Type) -> AvailableDevice in + return AvailableDevice(identifier: Type.managerIdentifier, localizedTitle: Type.localizedTitle) } extension PumpManager { diff --git a/Common/Models/PumpManagerUI.swift b/Common/Models/PumpManagerUI.swift index a56a92d2a2..1c899c6a10 100644 --- a/Common/Models/PumpManagerUI.swift +++ b/Common/Models/PumpManagerUI.swift @@ -12,7 +12,7 @@ import LoopKitUI import MinimedKit import MinimedKitUI -private let managersByIdentifier: [String: PumpManagerUI.Type] = allPumpManagers.compactMap{ $0 as? PumpManagerUI.Type}.reduce(into: [:]) { (map, Type) in +private let managersByIdentifier: [String: PumpManagerUI.Type] = staticPumpManagers.compactMap{ $0 as? PumpManagerUI.Type}.reduce(into: [:]) { (map, Type) in map[Type.managerIdentifier] = Type } diff --git a/DoseMathTests/Info.plist b/DoseMathTests/Info.plist index b90781aa4f..fe0e63e179 100644 --- a/DoseMathTests/Info.plist +++ b/DoseMathTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.10.0dev + 1.10.0 CFBundleSignature ???? CFBundleVersion diff --git a/Learn/Info.plist b/Learn/Info.plist index 07b79e155e..8c046ceecf 100644 --- a/Learn/Info.plist +++ b/Learn/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.10.0dev + 1.10.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS diff --git a/Loop Status Extension/Base.lproj/InfoPlist.strings b/Loop Status Extension/Base.lproj/InfoPlist.strings index 9250064a26..f8e9a2b43f 100644 --- a/Loop Status Extension/Base.lproj/InfoPlist.strings +++ b/Loop Status Extension/Base.lproj/InfoPlist.strings @@ -1,6 +1,3 @@ -/* (No Comment) */ -"CFBundleDisplayName" = "Loop"; - /* (No Comment) */ "CFBundleName" = "$(PRODUCT_NAME)"; diff --git a/Loop Status Extension/Info.plist b/Loop Status Extension/Info.plist index 6cb91fe668..1c13816bf6 100644 --- a/Loop Status Extension/Info.plist +++ b/Loop Status Extension/Info.plist @@ -2,10 +2,10 @@ + AppGroupIdentifier + $(APP_GROUP_IDENTIFIER) CFBundleDevelopmentRegion en - MainAppBundleIdentifier - $(MAIN_APP_BUNDLE_IDENTIFIER) CFBundleDisplayName Loop CFBundleExecutable @@ -19,11 +19,11 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.10.0dev + $(LOOP_MARKETING_VERSION) CFBundleVersion $(CURRENT_PROJECT_VERSION) - AppGroupIdentifier - $(APP_GROUP_IDENTIFIER) + MainAppBundleIdentifier + $(MAIN_APP_BUNDLE_IDENTIFIER) NSExtension NSExtensionMainStoryboard diff --git a/Loop.xcconfig b/Loop.xcconfig index f2c1e9b70c..492e0a3ca0 100644 --- a/Loop.xcconfig +++ b/Loop.xcconfig @@ -11,10 +11,9 @@ MAIN_APP_BUNDLE_IDENTIFIER = com.${DEVELOPMENT_TEAM}.loopkit MAIN_APP_DISPLAY_NAME = Loop -APPICON_NAME = AppIcon +LOOP_MARKETING_VERSION = 1.10.0 -// Exclude additional assets by default, and let other configurations that provide them add the file back in -EXCLUDED_SOURCE_FILE_NAMES = AdditionalAssets.xcassets +APPICON_NAME = AppIcon // Optional workspace configuration overrides #include? "../LoopConfigOverride.xcconfig" diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index e4ab8c489e..73c98f5b94 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -100,7 +100,6 @@ 4374B5F0209D857E00D17AA8 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4374B5EE209D84BE00D17AA8 /* OSLog.swift */; }; 43776F901B8022E90074EA36 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43776F8F1B8022E90074EA36 /* AppDelegate.swift */; }; 43776F971B8022E90074EA36 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 43776F951B8022E90074EA36 /* Main.storyboard */; }; - 43776F991B8022E90074EA36 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 43776F981B8022E90074EA36 /* Assets.xcassets */; }; 43785E932120A01B0057DED1 /* NewCarbEntryIntent+Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43785E922120A01B0057DED1 /* NewCarbEntryIntent+Loop.swift */; }; 43785E972120E4500057DED1 /* INRelevantShortcutStore+Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43785E952120E4010057DED1 /* INRelevantShortcutStore+Loop.swift */; }; 43785E982120E7060057DED1 /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 43785E9B2120E7060057DED1 /* Intents.intentdefinition */; }; @@ -339,7 +338,6 @@ 892A5D692230C41D008961AB /* RangeReplaceableCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892A5D682230C41D008961AB /* RangeReplaceableCollection.swift */; }; 892FB4CD22040104005293EC /* OverridePresetRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892FB4CC22040104005293EC /* OverridePresetRow.swift */; }; 892FB4CF220402C0005293EC /* OverrideSelectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892FB4CE220402C0005293EC /* OverrideSelectionController.swift */; }; - 894F71E21FFEC4D8007D365C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 894F71E11FFEC4D8007D365C /* Assets.xcassets */; }; 895FE0952201234000FCF18A /* OverrideSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 895FE0942201234000FCF18A /* OverrideSelectionViewController.swift */; }; 898ECA60218ABD17001E9D35 /* GlucoseChartScaler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898ECA5E218ABD17001E9D35 /* GlucoseChartScaler.swift */; }; 898ECA61218ABD17001E9D35 /* GlucoseChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898ECA5F218ABD17001E9D35 /* GlucoseChartData.swift */; }; @@ -357,10 +355,12 @@ C10428971D17BAD400DD539A /* NightscoutUploadKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C10428961D17BAD400DD539A /* NightscoutUploadKit.framework */; }; C10B28461EA9BA5E006EA1FC /* far_future_high_bg_forecast.json in Resources */ = {isa = PBXBuildFile; fileRef = C10B28451EA9BA5E006EA1FC /* far_future_high_bg_forecast.json */; }; C11C87DE1E21EAAD00BB71D3 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F526D5E1DF2459000A04910 /* HKUnit.swift */; }; + C125F31B22FE7CE200FD0545 /* copy-frameworks.sh in Resources */ = {isa = PBXBuildFile; fileRef = C125F31A22FE7CE200FD0545 /* copy-frameworks.sh */; }; C12F21A71DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json in Resources */ = {isa = PBXBuildFile; fileRef = C12F21A61DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json */; }; C13255D6223E7BE2008AF50C /* BolusProgressTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C1F8B1DB223862D500DD66CF /* BolusProgressTableViewCell.xib */; }; C13BAD941E8009B000050CB5 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0B31E45C1BE00FF19A9 /* NumberFormatter.swift */; }; C15713821DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */; }; + C16DA84222E8E112008624C2 /* LoopPlugins.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DA84122E8E112008624C2 /* LoopPlugins.swift */; }; C178249A1E1999FA00D9D25C /* CaseCountable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17824991E1999FA00D9D25C /* CaseCountable.swift */; }; C17824A01E19CF9800D9D25C /* GlucoseThresholdTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C178249F1E19CF9800D9D25C /* GlucoseThresholdTableViewController.swift */; }; C17824A31E19EAB600D9D25C /* recommend_temp_basal_start_very_low_end_high.json in Resources */ = {isa = PBXBuildFile; fileRef = C17824A21E19EAB600D9D25C /* recommend_temp_basal_start_very_low_end_high.json */; }; @@ -368,14 +368,12 @@ C17824A61E1AF91F00D9D25C /* BolusRecommendation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17824A41E1AD4D100D9D25C /* BolusRecommendation.swift */; }; C1814B86225E507C008D2D8E /* Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1814B85225E507C008D2D8E /* Sequence.swift */; }; C18852E22082AB1B00BECC8C /* RileyLinkKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C18852E12082AB1A00BECC8C /* RileyLinkKitUI.framework */; }; - C189E6BC22B82908005904DA /* AdditionalAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C189E6BA22B81E7A005904DA /* AdditionalAssets.xcassets */; }; - C189E6BD22B8291F005904DA /* AdditionalAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C189E6BA22B81E7A005904DA /* AdditionalAssets.xcassets */; }; - C189E6C022B829C6005904DA /* AdditionalAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C189E6BA22B81E7A005904DA /* AdditionalAssets.xcassets */; }; - C189E6C122B82BE4005904DA /* AdditionalAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C189E6BA22B81E7A005904DA /* AdditionalAssets.xcassets */; }; C18C8C511D5A351900E043FB /* NightscoutDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */; }; C1C0BE2A224C0FA000C03B4D /* SwiftCharts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4346D1EF1C781BEA00ABAFE3 /* SwiftCharts.framework */; }; C1C6591C1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json in Resources */ = {isa = PBXBuildFile; fileRef = C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */; }; C1C73F0D1DE3D0270022FC89 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1C73F0F1DE3D0270022FC89 /* InfoPlist.strings */; }; + C1D1405922FB69CC00DA6242 /* DerivedAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1D1405822FB69CC00DA6242 /* DerivedAssets.xcassets */; }; + C1D1405F22FB7ADF00DA6242 /* DerivedWatchAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1D1405E22FB7ADF00DA6242 /* DerivedWatchAssets.xcassets */; }; C1E2773E224177C000354103 /* ClockKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1E2773D224177C000354103 /* ClockKit.framework */; }; C1E2774822433D7A00354103 /* MKRingProgressView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1E2774722433D7A00354103 /* MKRingProgressView.framework */; }; C1F8B243223E73FD00DD66CF /* BolusProgressTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F8B1D122375E4200DD66CF /* BolusProgressTableViewCell.swift */; }; @@ -631,7 +629,7 @@ 43776F8C1B8022E90074EA36 /* Loop.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Loop.app; sourceTree = BUILT_PRODUCTS_DIR; }; 43776F8F1B8022E90074EA36 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AppDelegate.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 43776F961B8022E90074EA36 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 43776F981B8022E90074EA36 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 43776F981B8022E90074EA36 /* DefaultAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DefaultAssets.xcassets; sourceTree = ""; }; 43776F9B1B8022E90074EA36 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 43785E922120A01B0057DED1 /* NewCarbEntryIntent+Loop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NewCarbEntryIntent+Loop.swift"; sourceTree = ""; }; 43785E952120E4010057DED1 /* INRelevantShortcutStore+Loop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "INRelevantShortcutStore+Loop.swift"; sourceTree = ""; }; @@ -981,7 +979,7 @@ 892A5D682230C41D008961AB /* RangeReplaceableCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RangeReplaceableCollection.swift; sourceTree = ""; }; 892FB4CC22040104005293EC /* OverridePresetRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverridePresetRow.swift; sourceTree = ""; }; 892FB4CE220402C0005293EC /* OverrideSelectionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideSelectionController.swift; sourceTree = ""; }; - 894F71E11FFEC4D8007D365C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 894F71E11FFEC4D8007D365C /* DefaultAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DefaultAssets.xcassets; sourceTree = ""; }; 895FE0942201234000FCF18A /* OverrideSelectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverrideSelectionViewController.swift; sourceTree = ""; }; 898ECA5E218ABD17001E9D35 /* GlucoseChartScaler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseChartScaler.swift; sourceTree = ""; }; 898ECA5F218ABD17001E9D35 /* GlucoseChartData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseChartData.swift; sourceTree = ""; }; @@ -998,17 +996,24 @@ 89E267FE229267DF00A3F2AF /* Optional.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Optional.swift; sourceTree = ""; }; C10428961D17BAD400DD539A /* NightscoutUploadKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = NightscoutUploadKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C10B28451EA9BA5E006EA1FC /* far_future_high_bg_forecast.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = far_future_high_bg_forecast.json; sourceTree = ""; }; + C125F31A22FE7CE200FD0545 /* copy-frameworks.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "copy-frameworks.sh"; sourceTree = ""; }; C12F21A61DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_temp_basal_very_low_end_in_range.json; sourceTree = ""; }; C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MealBolusNightscoutTreatment.swift; sourceTree = ""; }; + C16DA84122E8E112008624C2 /* LoopPlugins.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopPlugins.swift; sourceTree = ""; }; C17824991E1999FA00D9D25C /* CaseCountable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaseCountable.swift; sourceTree = ""; }; C178249F1E19CF9800D9D25C /* GlucoseThresholdTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseThresholdTableViewController.swift; sourceTree = ""; }; C17824A21E19EAB600D9D25C /* recommend_temp_basal_start_very_low_end_high.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_temp_basal_start_very_low_end_high.json; sourceTree = ""; }; C17824A41E1AD4D100D9D25C /* BolusRecommendation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BolusRecommendation.swift; sourceTree = ""; }; C1814B85225E507C008D2D8E /* Sequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sequence.swift; sourceTree = ""; }; C18852E12082AB1A00BECC8C /* RileyLinkKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RileyLinkKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - C189E6BA22B81E7A005904DA /* AdditionalAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = AdditionalAssets.xcassets; path = ../../AdditionalAssets.xcassets; sourceTree = ""; }; + C18A491222FCC22800FDA733 /* build-derived-assets.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "build-derived-assets.sh"; sourceTree = ""; }; + C18A491322FCC22900FDA733 /* make_scenario.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = make_scenario.py; sourceTree = ""; }; + C18A491422FCC22900FDA733 /* build-derived-watch-assets.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "build-derived-watch-assets.sh"; sourceTree = ""; }; + C18A491522FCC22900FDA733 /* copy-plugins.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "copy-plugins.sh"; sourceTree = ""; }; C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutDataManager.swift; sourceTree = ""; }; C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_temp_basal_dropping_then_rising.json; sourceTree = ""; }; + C1D1405822FB69CC00DA6242 /* DerivedAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DerivedAssets.xcassets; sourceTree = BUILT_PRODUCTS_DIR; }; + C1D1405E22FB7ADF00DA6242 /* DerivedWatchAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DerivedWatchAssets.xcassets; sourceTree = BUILT_PRODUCTS_DIR; }; C1E2773D224177C000354103 /* ClockKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ClockKit.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk/System/Library/Frameworks/ClockKit.framework; sourceTree = DEVELOPER_DIR; }; C1E2774722433D7A00354103 /* MKRingProgressView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MKRingProgressView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C1F8B1D122375E4200DD66CF /* BolusProgressTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusProgressTableViewCell.swift; sourceTree = ""; }; @@ -1205,6 +1210,7 @@ 43776F831B8022E90074EA36 = { isa = PBXGroup; children = ( + C18A491122FCC20B00FDA733 /* Scripts */, 4FF4D0FA1E1834BD00846527 /* Common */, 43776F8E1B8022E90074EA36 /* Loop */, 4F70C1DF1DE8DCA7006380B7 /* Loop Status Extension */, @@ -1241,13 +1247,14 @@ 43776F8E1B8022E90074EA36 /* Loop */ = { isa = PBXGroup; children = ( + C16DA84022E8E104008624C2 /* Plugins */, 7D7076651FE06EE4004AC8EA /* Localizable.strings */, 7D7076511FE06EE1004AC8EA /* InfoPlist.strings */, 43EDEE6B1CF2E12A00393BE3 /* Loop.entitlements */, 43F5C2D41B92A4A6003EB13D /* Info.plist */, 43776F8F1B8022E90074EA36 /* AppDelegate.swift */, - 43776F981B8022E90074EA36 /* Assets.xcassets */, - C189E6BA22B81E7A005904DA /* AdditionalAssets.xcassets */, + 43776F981B8022E90074EA36 /* DefaultAssets.xcassets */, + C1D1405822FB69CC00DA6242 /* DerivedAssets.xcassets */, 43776F9A1B8022E90074EA36 /* LaunchScreen.storyboard */, 43776F951B8022E90074EA36 /* Main.storyboard */, 43E344A01B9E144300C85C07 /* Extensions */, @@ -1273,7 +1280,8 @@ 43A943731B926B7B0051FA24 /* WatchApp */ = { isa = PBXGroup; children = ( - 894F71E11FFEC4D8007D365C /* Assets.xcassets */, + 894F71E11FFEC4D8007D365C /* DefaultAssets.xcassets */, + C1D1405E22FB7ADF00DA6242 /* DerivedWatchAssets.xcassets */, C1C73F0F1DE3D0270022FC89 /* InfoPlist.strings */, 43F5C2D61B92A4DC003EB13D /* Info.plist */, 43A943741B926B7B0051FA24 /* Interface.storyboard */, @@ -1754,6 +1762,26 @@ name = Frameworks; sourceTree = ""; }; + C16DA84022E8E104008624C2 /* Plugins */ = { + isa = PBXGroup; + children = ( + C16DA84122E8E112008624C2 /* LoopPlugins.swift */, + ); + path = Plugins; + sourceTree = ""; + }; + C18A491122FCC20B00FDA733 /* Scripts */ = { + isa = PBXGroup; + children = ( + C125F31A22FE7CE200FD0545 /* copy-frameworks.sh */, + C18A491222FCC22800FDA733 /* build-derived-assets.sh */, + C18A491422FCC22900FDA733 /* build-derived-watch-assets.sh */, + C18A491522FCC22900FDA733 /* copy-plugins.sh */, + C18A491322FCC22900FDA733 /* make_scenario.py */, + ); + path = Scripts; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -1788,12 +1816,14 @@ isa = PBXNativeTarget; buildConfigurationList = 43776FB61B8022E90074EA36 /* Build configuration list for PBXNativeTarget "Loop" */; buildPhases = ( + C1D1405722FB66DF00DA6242 /* Build Derived Assets */, 43776F881B8022E90074EA36 /* Sources */, 43776F891B8022E90074EA36 /* Frameworks */, 43776F8A1B8022E90074EA36 /* Resources */, 43A9439C1B926B7B0051FA24 /* Embed Watch Content */, 43A943AE1B928D400051FA24 /* Embed Frameworks */, 43EDDBEF1C361BCE007D89B5 /* Copy Frameworks with Carthage */, + C16DA84322E8E5FF008624C2 /* Copy Plugins */, 4F70C1EC1DE8DCA8006380B7 /* Embed App Extensions */, ); buildRules = ( @@ -1813,6 +1843,7 @@ isa = PBXNativeTarget; buildConfigurationList = 43A943991B926B7B0051FA24 /* Build configuration list for PBXNativeTarget "WatchApp" */; buildPhases = ( + C1D1406222FB7ED200DA6242 /* Build Derived Watch Assets */, 43A943701B926B7B0051FA24 /* Resources */, 43A943981B926B7B0051FA24 /* Embed App Extensions */, 43105EF81BADC8F9009CD81E /* Frameworks */, @@ -2133,11 +2164,11 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + C1D1405922FB69CC00DA6242 /* DerivedAssets.xcassets in Resources */, C13255D6223E7BE2008AF50C /* BolusProgressTableViewCell.xib in Resources */, 43FCBBC21E51710B00343C1B /* LaunchScreen.storyboard in Resources */, 7D70764F1FE06EE1004AC8EA /* InfoPlist.strings in Resources */, - 43776F991B8022E90074EA36 /* Assets.xcassets in Resources */, - C189E6BD22B8291F005904DA /* AdditionalAssets.xcassets in Resources */, + C125F31B22FE7CE200FD0545 /* copy-frameworks.sh in Resources */, 7D7076631FE06EE4004AC8EA /* Localizable.strings in Resources */, 43776F971B8022E90074EA36 /* Main.storyboard in Resources */, ); @@ -2147,9 +2178,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + C1D1405F22FB7ADF00DA6242 /* DerivedWatchAssets.xcassets in Resources */, C1C73F0D1DE3D0270022FC89 /* InfoPlist.strings in Resources */, - C189E6C122B82BE4005904DA /* AdditionalAssets.xcassets in Resources */, - 894F71E21FFEC4D8007D365C /* Assets.xcassets in Resources */, 43A943761B926B7B0051FA24 /* Interface.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2161,7 +2191,6 @@ 7D70765E1FE06EE3004AC8EA /* Localizable.strings in Resources */, 7D70763A1FE06EDF004AC8EA /* InfoPlist.strings in Resources */, 7D70763F1FE06EDF004AC8EA /* ckcomplication.strings in Resources */, - C189E6C022B829C6005904DA /* AdditionalAssets.xcassets in Resources */, 43A943901B926B7B0051FA24 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2240,7 +2269,6 @@ 4F2C15971E09E94E00E160D4 /* HUDAssets.xcassets in Resources */, 7D70764A1FE06EE1004AC8EA /* Localizable.strings in Resources */, 7D7076451FE06EE0004AC8EA /* InfoPlist.strings in Resources */, - C189E6BC22B82908005904DA /* AdditionalAssets.xcassets in Resources */, 4F2C15951E09BF3C00E160D4 /* HUDView.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2292,13 +2320,10 @@ outputFileListPaths = ( ); outputPaths = ( - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/LoopKit.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/LoopKitUI.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/SwiftCharts.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "CARTHAGE_BUILD_DIR=\"${SRCROOT}/Carthage/Build\"\nif [ -n \"${IPHONEOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/iOS\"\nelif [ -n \"${WATCHOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/watchOS\"\nelse\n echo \"ERROR: Unexpected deployment target type\"\n exit 1\nfi\n\nfor SCRIPT_INPUT_FILE in ${!SCRIPT_INPUT_FILE_*}; do\n CARTHAGE_BUILD_FILE=\"${!SCRIPT_INPUT_FILE/${BUILT_PRODUCTS_DIR}/${CARTHAGE_BUILD_DIR}}\"\n if [ -e \"${CARTHAGE_BUILD_FILE}\" ]; then\n if [ -e \"${SCRIPT_INPUT_FILE}\" ]; then\n echo \"ERROR: Duplicate frameworks found at:\"\n echo \" ${SCRIPT_INPUT_FILE}\"\n echo \" ${CARTHAGE_BUILD_FILE}\"\n exit 1\n fi\n echo \"Substituting \\\"${CARTHAGE_BUILD_FILE}\\\" for \\\"${!SCRIPT_INPUT_FILE}\\\"\"\n export ${SCRIPT_INPUT_FILE}=\"${CARTHAGE_BUILD_FILE}\"\n fi\ndone\n\necho \"Copy Frameworks with Carthage\"\ncarthage copy-frameworks\n"; + shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n\n"; }; 43EDDBEF1C361BCE007D89B5 /* Copy Frameworks with Carthage */ = { isa = PBXShellScriptBuildPhase; @@ -2329,30 +2354,10 @@ ); name = "Copy Frameworks with Carthage"; outputPaths = ( - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/CGMBLEKit.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/LoopKit.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/SwiftCharts.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/MinimedKit.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/RileyLinkBLEKit.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/RileyLinkKit.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/Amplitude.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/ShareClient.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/NightscoutUploadKit.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/Crypto.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/G4ShareSpy.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/RileyLinkKitUI.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/LoopKitUI.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/MinimedKitUI.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/CGMBLEKitUI.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/ShareClientUI.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/LoopTestingKit.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/MockKit.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/MockKitUI.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/MKRingProgressView.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "CARTHAGE_BUILD_DIR=\"${SRCROOT}/Carthage/Build\"\nif [ -n \"${IPHONEOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/iOS\"\nelif [ -n \"${WATCHOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/watchOS\"\nelse\n echo \"ERROR: Unexpected deployment target type\"\n exit 1\nfi\n\nfor SCRIPT_INPUT_FILE in ${!SCRIPT_INPUT_FILE_*}; do\n CARTHAGE_BUILD_FILE=\"${!SCRIPT_INPUT_FILE/${BUILT_PRODUCTS_DIR}/${CARTHAGE_BUILD_DIR}}\"\n if [ -e \"${CARTHAGE_BUILD_FILE}\" ]; then\n if [ -e \"${SCRIPT_INPUT_FILE}\" ]; then\n echo \"ERROR: Duplicate frameworks found at:\"\n echo \" ${SCRIPT_INPUT_FILE}\"\n echo \" ${CARTHAGE_BUILD_FILE}\"\n exit 1\n fi\n echo \"Substituting \\\"${CARTHAGE_BUILD_FILE}\\\" for \\\"${!SCRIPT_INPUT_FILE}\\\"\"\n export ${SCRIPT_INPUT_FILE}=\"${CARTHAGE_BUILD_FILE}\"\n fi\ndone\n\necho \"Copy Frameworks with Carthage\"\ncarthage copy-frameworks\n"; + shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n\n"; }; 43FF3DF620A8EFE800F8E62C /* Copy Frameworks with Carthage */ = { isa = PBXShellScriptBuildPhase; @@ -2366,13 +2371,10 @@ ); name = "Copy Frameworks with Carthage"; outputPaths = ( - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/CGMBLEKit.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/LoopKit.framework", - "$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/ShareClient.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "CARTHAGE_BUILD_DIR=\"${SRCROOT}/Carthage/Build\"\nif [ -n \"${IPHONEOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/iOS\"\nelif [ -n \"${WATCHOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/watchOS\"\nelse\n echo \"ERROR: Unexpected deployment target type\"\n exit 1\nfi\n\nfor SCRIPT_INPUT_FILE in ${!SCRIPT_INPUT_FILE_*}; do\n CARTHAGE_BUILD_FILE=\"${!SCRIPT_INPUT_FILE/${BUILT_PRODUCTS_DIR}/${CARTHAGE_BUILD_DIR}}\"\n if [ -e \"${CARTHAGE_BUILD_FILE}\" ]; then\n if [ -e \"${SCRIPT_INPUT_FILE}\" ]; then\n echo \"ERROR: Duplicate frameworks found at:\"\n echo \" ${SCRIPT_INPUT_FILE}\"\n echo \" ${CARTHAGE_BUILD_FILE}\"\n exit 1\n fi\n echo \"Substituting \\\"${CARTHAGE_BUILD_FILE}\\\" for \\\"${!SCRIPT_INPUT_FILE}\\\"\"\n export ${SCRIPT_INPUT_FILE}=\"${CARTHAGE_BUILD_FILE}\"\n fi\ndone\n\necho \"Copy Frameworks with Carthage\"\ncarthage copy-frameworks\n"; + shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n\n"; }; A942E448225FD9D500DD4980 /* Copy Frameworks with Carthage */ = { isa = PBXShellScriptBuildPhase; @@ -2394,6 +2396,60 @@ shellPath = /bin/sh; shellScript = "CARTHAGE_BUILD_DIR=\"${SRCROOT}/Carthage/Build\"\nif [ -n \"${IPHONEOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/iOS\"\nelif [ -n \"${WATCHOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/watchOS\"\nelse\n echo \"ERROR: Unexpected deployment target type\"\n exit 1\nfi\n\nfor SCRIPT_INPUT_FILE in ${!SCRIPT_INPUT_FILE_*}; do\n CARTHAGE_BUILD_FILE=\"${!SCRIPT_INPUT_FILE/${BUILT_PRODUCTS_DIR}/${CARTHAGE_BUILD_DIR}}\"\n if [ -e \"${CARTHAGE_BUILD_FILE}\" ]; then\n if [ -e \"${SCRIPT_INPUT_FILE}\" ]; then\n echo \"ERROR: Duplicate frameworks found at:\"\n echo \" ${SCRIPT_INPUT_FILE}\"\n echo \" ${CARTHAGE_BUILD_FILE}\"\n exit 1\n fi\n echo \"Substituting \\\"${CARTHAGE_BUILD_FILE}\\\" for \\\"${!SCRIPT_INPUT_FILE}\\\"\"\n export ${SCRIPT_INPUT_FILE}=\"${CARTHAGE_BUILD_FILE}\"\n fi\ndone\n\necho \"Copy Frameworks with Carthage\"\ncarthage copy-frameworks\n"; }; + C16DA84322E8E5FF008624C2 /* Copy Plugins */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Copy Plugins"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "${SRCROOT}/Scripts/copy-plugins.sh\n"; + }; + C1D1405722FB66DF00DA6242 /* Build Derived Assets */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Build Derived Assets"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "${SRCROOT}/Scripts/build-derived-assets.sh\n"; + }; + C1D1406222FB7ED200DA6242 /* Build Derived Watch Assets */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Build Derived Watch Assets"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "${SRCROOT}/Scripts/build-derived-watch-assets.sh\n"; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -2473,6 +2529,7 @@ 4344628F20A7ADD500C4BE6F /* UserDefaults+CGM.swift in Sources */, 43F41C371D3BF32400C11ED6 /* UIAlertController.swift in Sources */, 433EA4C41D9F71C800CD78FB /* CommandResponseViewController.swift in Sources */, + C16DA84222E8E112008624C2 /* LoopPlugins.swift in Sources */, 43D2E8231F00425400AE5CBF /* BolusViewController+LoopDataManager.swift in Sources */, 430B29952041F5CB00BA9F93 /* LoopSettings+Loop.swift in Sources */, 43785E932120A01B0057DED1 /* NewCarbEntryIntent+Loop.swift in Sources */, @@ -3154,7 +3211,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer: loudnate@gmail.com (XZN842LDLT)"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 56; + CURRENT_PROJECT_VERSION = 61; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -3176,7 +3233,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.1; + IPHONEOS_DEPLOYMENT_TARGET = 11.4; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, CFLocalizedString, @@ -3226,7 +3283,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Developer: loudnate@gmail.com (XZN842LDLT)"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 56; + CURRENT_PROJECT_VERSION = 61; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -3242,7 +3299,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.1; + IPHONEOS_DEPLOYMENT_TARGET = 11.4; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, CFLocalizedString, @@ -3268,6 +3325,7 @@ CODE_SIGN_ENTITLEMENTS = Loop/Loop.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; DEVELOPMENT_TEAM = ""; + ENABLE_BITCODE = YES; INFOPLIST_FILE = Loop/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; "OTHER_SWIFT_FLAGS[arch=*]" = "-DDEBUG"; @@ -3286,6 +3344,7 @@ CODE_SIGN_ENTITLEMENTS = Loop/Loop.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; DEVELOPMENT_TEAM = ""; + ENABLE_BITCODE = YES; INFOPLIST_FILE = Loop/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER)"; @@ -3395,11 +3454,11 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 56; + CURRENT_PROJECT_VERSION = 61; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 56; + DYLIB_CURRENT_VERSION = 61; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3431,11 +3490,11 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 56; + CURRENT_PROJECT_VERSION = 61; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 56; + DYLIB_CURRENT_VERSION = 61; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3521,11 +3580,11 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 56; + CURRENT_PROJECT_VERSION = 61; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 56; + DYLIB_CURRENT_VERSION = 61; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = LoopCore/Info.plist; @@ -3554,11 +3613,11 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 56; + CURRENT_PROJECT_VERSION = 61; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 56; + DYLIB_CURRENT_VERSION = 61; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = LoopCore/Info.plist; @@ -3639,6 +3698,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = ""; + ENABLE_BITCODE = NO; INFOPLIST_FILE = "Loop Status Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER).statuswidget"; @@ -3658,6 +3718,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = ""; + ENABLE_BITCODE = NO; INFOPLIST_FILE = "Loop Status Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER).statuswidget"; @@ -3675,11 +3736,11 @@ CLANG_WARN_SUSPICIOUS_MOVES = YES; CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - CURRENT_PROJECT_VERSION = 56; + CURRENT_PROJECT_VERSION = 61; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 56; + DYLIB_CURRENT_VERSION = 61; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = LoopUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -3702,11 +3763,11 @@ CLANG_WARN_SUSPICIOUS_MOVES = YES; CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - CURRENT_PROJECT_VERSION = 56; + CURRENT_PROJECT_VERSION = 61; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 56; + DYLIB_CURRENT_VERSION = 61; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = LoopUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/Loop/Base.lproj/InfoPlist.strings b/Loop/Base.lproj/InfoPlist.strings index 63a0c976de..14e4d82681 100644 --- a/Loop/Base.lproj/InfoPlist.strings +++ b/Loop/Base.lproj/InfoPlist.strings @@ -1,5 +1,3 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "Loop"; /* Bundle name */ "CFBundleName" = "$(PRODUCT_NAME)"; diff --git a/Loop/Assets.xcassets/AppIcon.appiconset/App Store Copy@2x.png b/Loop/DefaultAssets.xcassets/AppIcon.appiconset/AppStore-1024pt@1x.png similarity index 100% rename from Loop/Assets.xcassets/AppIcon.appiconset/App Store Copy@2x.png rename to Loop/DefaultAssets.xcassets/AppIcon.appiconset/AppStore-1024pt@1x.png diff --git a/Loop/Assets.xcassets/AppIcon.appiconset/Contents.json b/Loop/DefaultAssets.xcassets/AppIcon.appiconset/Contents.json similarity index 98% rename from Loop/Assets.xcassets/AppIcon.appiconset/Contents.json rename to Loop/DefaultAssets.xcassets/AppIcon.appiconset/Contents.json index f9383e1e01..8bf119b6a8 100644 --- a/Loop/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Loop/DefaultAssets.xcassets/AppIcon.appiconset/Contents.json @@ -105,7 +105,7 @@ { "size" : "1024x1024", "idiom" : "ios-marketing", - "filename" : "App Store Copy@2x.png", + "filename" : "AppStore-1024pt@1x.png", "scale" : "1x" } ], diff --git a/Loop/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png b/Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-60@2x.png similarity index 100% rename from Loop/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png rename to Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-60@2x.png diff --git a/Loop/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png b/Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-60@3x.png similarity index 100% rename from Loop/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png rename to Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-60@3x.png diff --git a/Loop/Assets.xcassets/AppIcon.appiconset/Icon-76.png b/Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-76.png similarity index 100% rename from Loop/Assets.xcassets/AppIcon.appiconset/Icon-76.png rename to Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-76.png diff --git a/Loop/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png b/Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-76@2x.png similarity index 100% rename from Loop/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png rename to Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-76@2x.png diff --git a/Loop/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png b/Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png similarity index 100% rename from Loop/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png rename to Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png diff --git a/Loop/Assets.xcassets/AppIcon.appiconset/Icon-Notification-20.png b/Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Notification-20.png similarity index 100% rename from Loop/Assets.xcassets/AppIcon.appiconset/Icon-Notification-20.png rename to Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Notification-20.png diff --git a/Loop/Assets.xcassets/AppIcon.appiconset/Icon-Notification-40.png b/Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Notification-40.png similarity index 100% rename from Loop/Assets.xcassets/AppIcon.appiconset/Icon-Notification-40.png rename to Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Notification-40.png diff --git a/Loop/Assets.xcassets/AppIcon.appiconset/Icon-Notification-41.png b/Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Notification-41.png similarity index 100% rename from Loop/Assets.xcassets/AppIcon.appiconset/Icon-Notification-41.png rename to Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Notification-41.png diff --git a/Loop/Assets.xcassets/AppIcon.appiconset/Icon-Notification-42.png b/Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Notification-42.png similarity index 100% rename from Loop/Assets.xcassets/AppIcon.appiconset/Icon-Notification-42.png rename to Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Notification-42.png diff --git a/Loop/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x-1.png b/Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Small-40@2x-1.png similarity index 100% rename from Loop/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x-1.png rename to Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Small-40@2x-1.png diff --git a/Loop/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png b/Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png similarity index 100% rename from Loop/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png rename to Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png diff --git a/Loop/Assets.xcassets/AppIcon.appiconset/Icon-Small-60.png b/Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Small-60.png similarity index 100% rename from Loop/Assets.xcassets/AppIcon.appiconset/Icon-Small-60.png rename to Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Small-60.png diff --git a/Loop/Assets.xcassets/AppIcon.appiconset/Icon-Small-60@2x.png b/Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Small-60@2x.png similarity index 100% rename from Loop/Assets.xcassets/AppIcon.appiconset/Icon-Small-60@2x.png rename to Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Small-60@2x.png diff --git a/Loop/Assets.xcassets/AppIcon.appiconset/Icon-Small.png b/Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Small.png similarity index 100% rename from Loop/Assets.xcassets/AppIcon.appiconset/Icon-Small.png rename to Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Small.png diff --git a/Loop/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png b/Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png similarity index 100% rename from Loop/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png rename to Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png diff --git a/Loop/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png b/Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Small@2x.png similarity index 100% rename from Loop/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png rename to Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Small@2x.png diff --git a/Loop/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png b/Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Small@3x.png similarity index 100% rename from Loop/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png rename to Loop/DefaultAssets.xcassets/AppIcon.appiconset/Icon-Small@3x.png diff --git a/Loop/Assets.xcassets/Contents.json b/Loop/DefaultAssets.xcassets/Contents.json similarity index 100% rename from Loop/Assets.xcassets/Contents.json rename to Loop/DefaultAssets.xcassets/Contents.json diff --git a/Loop/Assets.xcassets/Pre-Meal Selected.imageset/Contents.json b/Loop/DefaultAssets.xcassets/Pre-Meal Selected.imageset/Contents.json similarity index 100% rename from Loop/Assets.xcassets/Pre-Meal Selected.imageset/Contents.json rename to Loop/DefaultAssets.xcassets/Pre-Meal Selected.imageset/Contents.json diff --git a/Loop/Assets.xcassets/Pre-Meal Selected.imageset/Pre-Meal Selected.pdf b/Loop/DefaultAssets.xcassets/Pre-Meal Selected.imageset/Pre-Meal Selected.pdf similarity index 100% rename from Loop/Assets.xcassets/Pre-Meal Selected.imageset/Pre-Meal Selected.pdf rename to Loop/DefaultAssets.xcassets/Pre-Meal Selected.imageset/Pre-Meal Selected.pdf diff --git a/Loop/Assets.xcassets/Pre-Meal.imageset/Contents.json b/Loop/DefaultAssets.xcassets/Pre-Meal.imageset/Contents.json similarity index 100% rename from Loop/Assets.xcassets/Pre-Meal.imageset/Contents.json rename to Loop/DefaultAssets.xcassets/Pre-Meal.imageset/Contents.json diff --git a/Loop/Assets.xcassets/Pre-Meal.imageset/Pre-Meal.pdf b/Loop/DefaultAssets.xcassets/Pre-Meal.imageset/Pre-Meal.pdf similarity index 100% rename from Loop/Assets.xcassets/Pre-Meal.imageset/Pre-Meal.pdf rename to Loop/DefaultAssets.xcassets/Pre-Meal.imageset/Pre-Meal.pdf diff --git a/Loop/Assets.xcassets/Uploading.imageset/Contents.json b/Loop/DefaultAssets.xcassets/Uploading.imageset/Contents.json similarity index 100% rename from Loop/Assets.xcassets/Uploading.imageset/Contents.json rename to Loop/DefaultAssets.xcassets/Uploading.imageset/Contents.json diff --git a/Loop/Assets.xcassets/Uploading.imageset/Uploading.pdf b/Loop/DefaultAssets.xcassets/Uploading.imageset/Uploading.pdf similarity index 100% rename from Loop/Assets.xcassets/Uploading.imageset/Uploading.pdf rename to Loop/DefaultAssets.xcassets/Uploading.imageset/Uploading.pdf diff --git a/Loop/Assets.xcassets/bolus.imageset/Contents.json b/Loop/DefaultAssets.xcassets/bolus.imageset/Contents.json similarity index 100% rename from Loop/Assets.xcassets/bolus.imageset/Contents.json rename to Loop/DefaultAssets.xcassets/bolus.imageset/Contents.json diff --git a/Loop/Assets.xcassets/bolus.imageset/bolus.pdf b/Loop/DefaultAssets.xcassets/bolus.imageset/bolus.pdf similarity index 100% rename from Loop/Assets.xcassets/bolus.imageset/bolus.pdf rename to Loop/DefaultAssets.xcassets/bolus.imageset/bolus.pdf diff --git a/Loop/Assets.xcassets/carbs.imageset/Contents.json b/Loop/DefaultAssets.xcassets/carbs.imageset/Contents.json similarity index 100% rename from Loop/Assets.xcassets/carbs.imageset/Contents.json rename to Loop/DefaultAssets.xcassets/carbs.imageset/Contents.json diff --git a/Loop/Assets.xcassets/carbs.imageset/Meal.pdf b/Loop/DefaultAssets.xcassets/carbs.imageset/Meal.pdf similarity index 100% rename from Loop/Assets.xcassets/carbs.imageset/Meal.pdf rename to Loop/DefaultAssets.xcassets/carbs.imageset/Meal.pdf diff --git a/Loop/Assets.xcassets/settings.imageset/Contents.json b/Loop/DefaultAssets.xcassets/settings.imageset/Contents.json similarity index 100% rename from Loop/Assets.xcassets/settings.imageset/Contents.json rename to Loop/DefaultAssets.xcassets/settings.imageset/Contents.json diff --git a/Loop/Assets.xcassets/settings.imageset/settings.png b/Loop/DefaultAssets.xcassets/settings.imageset/settings.png similarity index 100% rename from Loop/Assets.xcassets/settings.imageset/settings.png rename to Loop/DefaultAssets.xcassets/settings.imageset/settings.png diff --git a/Loop/Assets.xcassets/settings.imageset/settings@2x.png b/Loop/DefaultAssets.xcassets/settings.imageset/settings@2x.png similarity index 100% rename from Loop/Assets.xcassets/settings.imageset/settings@2x.png rename to Loop/DefaultAssets.xcassets/settings.imageset/settings@2x.png diff --git a/Loop/Assets.xcassets/settings.imageset/settings@3x.png b/Loop/DefaultAssets.xcassets/settings.imageset/settings@3x.png similarity index 100% rename from Loop/Assets.xcassets/settings.imageset/settings@3x.png rename to Loop/DefaultAssets.xcassets/settings.imageset/settings@3x.png diff --git a/Loop/Assets.xcassets/settings.imageset/settings_compact@2x.png b/Loop/DefaultAssets.xcassets/settings.imageset/settings_compact@2x.png similarity index 100% rename from Loop/Assets.xcassets/settings.imageset/settings_compact@2x.png rename to Loop/DefaultAssets.xcassets/settings.imageset/settings_compact@2x.png diff --git a/Loop/Assets.xcassets/settings.imageset/settings_compact@3x.png b/Loop/DefaultAssets.xcassets/settings.imageset/settings_compact@3x.png similarity index 100% rename from Loop/Assets.xcassets/settings.imageset/settings_compact@3x.png rename to Loop/DefaultAssets.xcassets/settings.imageset/settings_compact@3x.png diff --git a/Loop/Assets.xcassets/workout-selected.imageset/Contents.json b/Loop/DefaultAssets.xcassets/workout-selected.imageset/Contents.json similarity index 100% rename from Loop/Assets.xcassets/workout-selected.imageset/Contents.json rename to Loop/DefaultAssets.xcassets/workout-selected.imageset/Contents.json diff --git a/Loop/Assets.xcassets/workout-selected.imageset/workout-selected.pdf b/Loop/DefaultAssets.xcassets/workout-selected.imageset/workout-selected.pdf similarity index 100% rename from Loop/Assets.xcassets/workout-selected.imageset/workout-selected.pdf rename to Loop/DefaultAssets.xcassets/workout-selected.imageset/workout-selected.pdf diff --git a/Loop/Assets.xcassets/workout.imageset/Contents.json b/Loop/DefaultAssets.xcassets/workout.imageset/Contents.json similarity index 100% rename from Loop/Assets.xcassets/workout.imageset/Contents.json rename to Loop/DefaultAssets.xcassets/workout.imageset/Contents.json diff --git a/Loop/Assets.xcassets/workout.imageset/workout.pdf b/Loop/DefaultAssets.xcassets/workout.imageset/workout.pdf similarity index 100% rename from Loop/Assets.xcassets/workout.imageset/workout.pdf rename to Loop/DefaultAssets.xcassets/workout.imageset/workout.pdf diff --git a/Loop/Extensions/UIAlertController.swift b/Loop/Extensions/UIAlertController.swift index 920a772415..c5589df29b 100644 --- a/Loop/Extensions/UIAlertController.swift +++ b/Loop/Extensions/UIAlertController.swift @@ -51,19 +51,19 @@ extension UIAlertController { /// - cgmManagers: An array of PumpManagers /// - selectionHandler: A closure to execute when a manager is selected /// - manager: The selected manager - internal convenience init(pumpManagers: [PumpManagerUI.Type], selectionHandler: @escaping (_ manager: PumpManagerUI.Type) -> Void) { + internal convenience init(pumpManagers: [AvailableDevice], selectionHandler: @escaping (_ identifier: String) -> Void) { self.init( title: NSLocalizedString("Add Pump", comment: "Action sheet title selecting Pump"), message: nil, preferredStyle: .actionSheet ) - for manager in pumpManagers { + for device in pumpManagers { addAction(UIAlertAction( - title: manager.localizedTitle, + title: device.localizedTitle, style: .default, handler: { (_) in - selectionHandler(manager) + selectionHandler(device.identifier) } )) } diff --git a/Loop/Extensions/UserDefaults+Loop.swift b/Loop/Extensions/UserDefaults+Loop.swift index e7d47f57ef..9a9abf58fe 100644 --- a/Loop/Extensions/UserDefaults+Loop.swift +++ b/Loop/Extensions/UserDefaults+Loop.swift @@ -14,27 +14,15 @@ extension UserDefaults { case pumpManagerState = "com.loopkit.Loop.PumpManagerState" } - var pumpManager: PumpManager? { + var pumpManagerRawValue: [String: Any]? { get { - guard let rawValue = dictionary(forKey: Key.pumpManagerState.rawValue) else { - return nil - } - - return PumpManagerFromRawValue(rawValue) + return dictionary(forKey: Key.pumpManagerState.rawValue) } set { - set(newValue?.rawValue, forKey: Key.pumpManagerState.rawValue) + set(newValue, forKey: Key.pumpManagerState.rawValue) } } - var isCGMManagerValidPumpManager: Bool { - guard let rawValue = cgmManagerState else { - return false - } - - return PumpManagerTypeFromRawValue(rawValue) != nil - } - var cgmManager: CGMManager? { get { guard let rawValue = cgmManagerState else { diff --git a/Loop/Info.plist b/Loop/Info.plist index 80083c8794..9cde5bd59d 100644 --- a/Loop/Info.plist +++ b/Loop/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.10.0dev + $(LOOP_MARKETING_VERSION) CFBundleSignature ???? CFBundleURLTypes diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index d3f9547b18..f4e083999e 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -58,7 +58,7 @@ final class DeviceDataManager { NotificationCenter.default.post(name: .PumpManagerChanged, object: self, userInfo: nil) - UserDefaults.appGroup?.pumpManager = pumpManager + UserDefaults.appGroup?.pumpManagerRawValue = pumpManager?.rawValue } } @@ -72,16 +72,27 @@ final class DeviceDataManager { private var statusExtensionManager: StatusExtensionDataManager! + // MARK: - Plugins + + private var pluginManager: PluginManager + // MARK: - Initialization + private(set) var loopManager: LoopDataManager! init() { - pumpManager = UserDefaults.appGroup?.pumpManager as? PumpManagerUI + pluginManager = PluginManager() + + if let pumpManagerRawValue = UserDefaults.appGroup?.pumpManagerRawValue { + pumpManager = pumpManagerFromRawValue(pumpManagerRawValue) + } else { + pumpManager = nil + } if let cgmManager = UserDefaults.appGroup?.cgmManager { self.cgmManager = cgmManager - } else if UserDefaults.appGroup?.isCGMManagerValidPumpManager == true { + } else if isCGMManagerValidPumpManager { self.cgmManager = pumpManager as? CGMManager } @@ -102,6 +113,41 @@ final class DeviceDataManager { setupPump() setupCGM() } + + var isCGMManagerValidPumpManager: Bool { + guard let rawValue = UserDefaults.appGroup?.cgmManagerState else { + return false + } + + return pumpManagerTypeFromRawValue(rawValue) != nil + } + + var availablePumpManagers: [AvailableDevice] { + return pluginManager.availablePumpManagers + availableStaticPumpManagers + } + + public func pumpManagerTypeByIdentifier(_ identifier: String) -> PumpManagerUI.Type? { + return pluginManager.getPumpManagerTypeByIdentifier(identifier) ?? staticPumpManagersByIdentifier[identifier] as? PumpManagerUI.Type + } + + private func pumpManagerTypeFromRawValue(_ rawValue: [String: Any]) -> PumpManager.Type? { + guard let managerIdentifier = rawValue["managerIdentifier"] as? String else { + return nil + } + + return pumpManagerTypeByIdentifier(managerIdentifier) + } + + func pumpManagerFromRawValue(_ rawValue: [String: Any]) -> PumpManagerUI? { + guard let rawState = rawValue["state"] as? PumpManager.RawStateValue, + let Manager = pumpManagerTypeFromRawValue(rawValue) + else { + return nil + } + + return Manager.init(rawState: rawState) as? PumpManagerUI + } + } private extension DeviceDataManager { @@ -267,7 +313,7 @@ extension DeviceDataManager: PumpManagerDelegate { dispatchPrecondition(condition: .onQueue(queue)) log.default("PumpManager:\(type(of: pumpManager)) did update state") - UserDefaults.appGroup?.pumpManager = pumpManager + UserDefaults.appGroup?.pumpManagerRawValue = pumpManager.rawValue } func pumpManagerBLEHeartbeatDidFire(_ pumpManager: PumpManager) { diff --git a/Loop/Plugins/LoopPlugins.swift b/Loop/Plugins/LoopPlugins.swift new file mode 100644 index 0000000000..033ae6ae6f --- /dev/null +++ b/Loop/Plugins/LoopPlugins.swift @@ -0,0 +1,79 @@ +// +// LoopPlugins.swift +// Loop +// +// Created by Pete Schwamb on 7/24/19. +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import Foundation +import LoopKit +import LoopKitUI + +class PluginManager { + private let pluginBundles: [Bundle] + + public init() { + var bundles = [Bundle]() + + if let pluginsURL = Bundle.main.privateFrameworksURL { + do { + for pluginURL in try FileManager.default.contentsOfDirectory(at: pluginsURL, includingPropertiesForKeys: nil).filter{$0.path.hasSuffix(".framework")} { + if let bundle = Bundle(url: pluginURL), bundle.isLoopPlugin { + print("Found loop plugin at \(pluginURL)") + bundles.append(bundle) + } + } + } catch let error { + print("Error loading plugins: \(String(describing: error))") + } + } + self.pluginBundles = bundles + } + + func getPumpManagerTypeByIdentifier(_ identifier: String) -> PumpManagerUI.Type? { + for bundle in pluginBundles { + if let name = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.pumpManagerIdentifier.rawValue) as? String, name == identifier { + do { + try bundle.loadAndReturnError() + + if let principalClass = bundle.principalClass as? NSObject.Type { + + if let plugin = principalClass.init() as? LoopUIPlugin { + return plugin.pumpManagerType + } else { + fatalError("PrincipalClass does not conform to LoopUIPlugin") + } + + } else { + fatalError("PrincipalClass not found") + } + } catch let error { + print(error) + } + } + } + return nil + } + + var availablePumpManagers: [AvailableDevice] { + return pluginBundles.compactMap({ (bundle) -> AvailableDevice? in + guard let title = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.pumpManagerDisplayName.rawValue) as? String, + let identifier = bundle.object(forInfoDictionaryKey: LoopPluginBundleKey.pumpManagerIdentifier.rawValue) as? String else { + return nil + } + + return AvailableDevice(identifier: identifier, localizedTitle: title) + + }) + } +} + + +extension Bundle { + var isLoopPlugin: Bool { + return + object(forInfoDictionaryKey: LoopPluginBundleKey.pumpManagerIdentifier.rawValue) as? String != nil || + object(forInfoDictionaryKey: LoopPluginBundleKey.cgmManagerIdentifier.rawValue) as? String != nil + } +} diff --git a/Loop/View Controllers/SettingsTableViewController.swift b/Loop/View Controllers/SettingsTableViewController.swift index 06825eb8a8..8483b74948 100644 --- a/Loop/View Controllers/SettingsTableViewController.swift +++ b/Loop/View Controllers/SettingsTableViewController.swift @@ -382,17 +382,18 @@ final class SettingsTableViewController: UITableViewController { present(settings, animated: true) } else { // Add new pump - let pumpManagers = allPumpManagers.compactMap({ $0 as? PumpManagerUI.Type }) + let pumpManagers = dataManager.availablePumpManagers switch pumpManagers.count { case 1: - if let PumpManagerType = pumpManagers.first { + if let pumpManager = pumpManagers.first, let PumpManagerType = dataManager.pumpManagerTypeByIdentifier(pumpManager.identifier) { + let setupViewController = configuredSetupViewController(for: PumpManagerType) present(setupViewController, animated: true, completion: nil) } case let x where x > 1: - let alert = UIAlertController(pumpManagers: pumpManagers) { [weak self] (manager) in - if let self = self { + let alert = UIAlertController(pumpManagers: pumpManagers) { [weak self] (identifier) in + if let self = self, let manager = self.dataManager.pumpManagerTypeByIdentifier(identifier) { let setupViewController = self.configuredSetupViewController(for: manager) self.present(setupViewController, animated: true, completion: nil) } diff --git a/LoopCore/Info.plist b/LoopCore/Info.plist index e3942fbb31..6db01d4593 100644 --- a/LoopCore/Info.plist +++ b/LoopCore/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.10.0dev + $(LOOP_MARKETING_VERSION) CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/LoopTests/Info.plist b/LoopTests/Info.plist index b90781aa4f..fe0e63e179 100644 --- a/LoopTests/Info.plist +++ b/LoopTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.10.0dev + 1.10.0 CFBundleSignature ???? CFBundleVersion diff --git a/LoopUI/Info.plist b/LoopUI/Info.plist index f383ba1b06..ff26182ff1 100644 --- a/LoopUI/Info.plist +++ b/LoopUI/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.10.0dev + $(LOOP_MARKETING_VERSION) CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/Scripts/build-derived-assets.sh b/Scripts/build-derived-assets.sh new file mode 100755 index 0000000000..cd0f985003 --- /dev/null +++ b/Scripts/build-derived-assets.sh @@ -0,0 +1,20 @@ +#!/bin/sh -eu + +# build-derived-assets.sh +# Loop +# +# Copyright © 2019 LoopKit Authors. All rights reserved. + + +echo "Building DerivedAssets.xcassets" + +output="${BUILT_PRODUCTS_DIR}/DerivedAssets.xcassets" +cp -a "${PROJECT_DIR}/Loop/DefaultAssets.xcassets/." "$output" + +override="${PROJECT_DIR}/../AssetOverrides.xcassets/." + +if [ -d $override ]; then + echo "Adding asset overrides to DerivedAssets.xcassets" + cp -a "$override" "$output" +fi + diff --git a/Scripts/build-derived-watch-assets.sh b/Scripts/build-derived-watch-assets.sh new file mode 100755 index 0000000000..217b2fc252 --- /dev/null +++ b/Scripts/build-derived-watch-assets.sh @@ -0,0 +1,19 @@ +#!/bin/sh -eu + +# build-derived-watch-assets.sh +# Loop +# +# Copyright © 2019 LoopKit Authors. All rights reserved. + +echo "Building WatchDerivedAssets.xcassets" + +watch_output="${BUILT_PRODUCTS_DIR}/DerivedWatchAssets.xcassets" +cp -a "${PROJECT_DIR}/WatchApp/DefaultAssets.xcassets/." "$watch_output" + + +watch_override="${PROJECT_DIR}/../WatchAssetOverrides.xcassets/." + +if [ -d $watch_override ]; then + echo "Adding asset overrides to DerivedWatchAssets.xcassets" + cp -a "$watch_override" "$watch_output" +fi diff --git a/Scripts/copy-frameworks.sh b/Scripts/copy-frameworks.sh new file mode 100755 index 0000000000..a5082d1ff2 --- /dev/null +++ b/Scripts/copy-frameworks.sh @@ -0,0 +1,44 @@ +#!/bin/sh -e + +# copy-frameworks.sh +# Loop +# +# Copyright © 2019 LoopKit Authors. All rights reserved. + +date + +CARTHAGE_BUILD_DIR="${SRCROOT}/Carthage/Build" +if [ -n "${IPHONEOS_DEPLOYMENT_TARGET}" ]; then + CARTHAGE_BUILD_DIR="${CARTHAGE_BUILD_DIR}/iOS" +elif [ -n "${WATCHOS_DEPLOYMENT_TARGET}" ]; then + CARTHAGE_BUILD_DIR="${CARTHAGE_BUILD_DIR}/watchOS" +else + echo "ERROR: Unexpected deployment target type" + exit 1 +fi + +for COUNTER in $(seq 0 $(($SCRIPT_INPUT_FILE_COUNT - 1))); do + SCRIPT_INPUT_VAR="SCRIPT_INPUT_FILE_${COUNTER}" + CARTHAGE_BUILD_FILE="${!SCRIPT_INPUT_VAR/${BUILT_PRODUCTS_DIR}/${CARTHAGE_BUILD_DIR}}" + if [ -e "${CARTHAGE_BUILD_FILE}" ]; then + if [ -e "${!SCRIPT_INPUT_VAR}" ]; then + echo "ERROR: Duplicate frameworks found at:" + echo " ${!SCRIPT_INPUT_VAR}" + echo " ${CARTHAGE_BUILD_FILE}" + exit 1 + fi + echo "Substituting \"${CARTHAGE_BUILD_FILE}\" for \"${!SCRIPT_INPUT_VAR}\"" + export ${SCRIPT_INPUT_VAR}="${CARTHAGE_BUILD_FILE}" + elif [ -e "${!SCRIPT_INPUT_VAR}" ]; then + echo "Using original path: \"${!SCRIPT_INPUT_VAR}\"" + else + echo "ERROR: Input file not found at \"${!SCRIPT_INPUT_FILE}\"" + exit 1 + fi + # Resolve any symlinks + export ${SCRIPT_INPUT_VAR}="$(readlink "${!SCRIPT_INPUT_VAR}" || echo "${!SCRIPT_INPUT_VAR}")" + echo "copy-frameworks resolved path: ${!SCRIPT_INPUT_VAR}" +done + +echo "Copy Frameworks with Carthage" +carthage copy-frameworks diff --git a/Scripts/copy-plugins.sh b/Scripts/copy-plugins.sh new file mode 100755 index 0000000000..c6e950df99 --- /dev/null +++ b/Scripts/copy-plugins.sh @@ -0,0 +1,38 @@ +#!/bin/sh -eu + +# copy-plugins.sh +# Loop +# +# Copyright © 2019 LoopKit Authors. All rights reserved. + +echo "Looking for plugins in $BUILT_PRODUCTS_DIR" + +shopt -s nullglob + +# Copy device plugins +for f in "${BUILT_PRODUCTS_DIR}"/*.loopplugin; do + plugin=$(basename "$f") + echo Copying device plugin: $plugin to frameworks directory in app + plugin_path="$(readlink "$f" || echo "$f")" + plugin_as_framework_path="${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/${plugin%.*}.framework" + rsync -va --exclude=Frameworks "$plugin_path/." "${plugin_as_framework_path}" + # Rename .plugin to .framework + if [ "$EXPANDED_CODE_SIGN_IDENTITY" != "-" ]; then + export CODESIGN_ALLOCATE=${DT_TOOLCHAIN_DIR}/usr/bin/codesign_allocate + echo "Signing ${plugin} with ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" + /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} --timestamp=none --preserve-metadata=identifier,entitlements,flags "$plugin_as_framework_path" + else + echo "Skipping signing, no identity set" + fi + for framework_path in "${f}"/Frameworks/*.framework; do + framework=$(basename "$framework_path") + echo Copying "$framework_path/." to "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/${framework}" + cp -a "$framework_path/." "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/${framework}" + plugin_path="$(readlink "$f" || echo "$f")" + if [ "$EXPANDED_CODE_SIGN_IDENTITY" != "-" ]; then + echo "Signing $framework for $plugin with $EXPANDED_CODE_SIGN_IDENTITY_NAME" + /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} --timestamp=none --preserve-metadata=identifier,entitlements,flags "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/${framework}" + fi + done +done + diff --git a/WatchApp Extension/Info.plist b/WatchApp Extension/Info.plist index 536c502c12..4ea1e4ca82 100644 --- a/WatchApp Extension/Info.plist +++ b/WatchApp Extension/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.10.0dev + $(LOOP_MARKETING_VERSION) CFBundleSignature ???? CFBundleVersion diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from WatchApp/Assets.xcassets/AppIcon.appiconset/Contents.json rename to WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Contents.json diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-App-Store-1024.png b/WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-App-Store-1024.png similarity index 100% rename from WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-App-Store-1024.png rename to WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-App-Store-1024.png diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-AppleWatch-100x100@2x.png b/WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-AppleWatch-100x100@2x.png similarity index 100% rename from WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-AppleWatch-100x100@2x.png rename to WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-AppleWatch-100x100@2x.png diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-AppleWatch-216x216@2x.png b/WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-AppleWatch-216x216@2x.png similarity index 100% rename from WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-AppleWatch-216x216@2x.png rename to WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-AppleWatch-216x216@2x.png diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-AppleWatch-24x24@2x.png b/WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-AppleWatch-24x24@2x.png similarity index 100% rename from WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-AppleWatch-24x24@2x.png rename to WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-AppleWatch-24x24@2x.png diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-AppleWatch-27.5x27.5@2x.png b/WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-AppleWatch-27.5x27.5@2x.png similarity index 100% rename from WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-AppleWatch-27.5x27.5@2x.png rename to WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-AppleWatch-27.5x27.5@2x.png diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-AppleWatch-40x40@2x.png b/WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-AppleWatch-40x40@2x.png similarity index 100% rename from WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-AppleWatch-40x40@2x.png rename to WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-AppleWatch-40x40@2x.png diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-AppleWatch-44x44@2x.png b/WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-AppleWatch-44x44@2x.png similarity index 100% rename from WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-AppleWatch-44x44@2x.png rename to WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-AppleWatch-44x44@2x.png diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-AppleWatch-86x86@2x.png b/WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-AppleWatch-86x86@2x.png similarity index 100% rename from WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-AppleWatch-86x86@2x.png rename to WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-AppleWatch-86x86@2x.png diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-AppleWatch-98x98@2x.png b/WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-AppleWatch-98x98@2x.png similarity index 100% rename from WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-AppleWatch-98x98@2x.png rename to WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-AppleWatch-98x98@2x.png diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-AppleWatch-Companion-29x29@2x.png b/WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-AppleWatch-Companion-29x29@2x.png similarity index 100% rename from WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-AppleWatch-Companion-29x29@2x.png rename to WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-AppleWatch-Companion-29x29@2x.png diff --git a/WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-AppleWatch-Companion-29x29@3x.png b/WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-AppleWatch-Companion-29x29@3x.png similarity index 100% rename from WatchApp/Assets.xcassets/AppIcon.appiconset/Icon-AppleWatch-Companion-29x29@3x.png rename to WatchApp/DefaultAssets.xcassets/AppIcon.appiconset/Icon-AppleWatch-Companion-29x29@3x.png diff --git a/WatchApp/Assets.xcassets/Contents.json b/WatchApp/DefaultAssets.xcassets/Contents.json similarity index 100% rename from WatchApp/Assets.xcassets/Contents.json rename to WatchApp/DefaultAssets.xcassets/Contents.json diff --git a/WatchApp/Assets.xcassets/Graph menu icons/1-hour-graph-38mm.png b/WatchApp/DefaultAssets.xcassets/Graph menu icons/1-hour-graph-38mm.png similarity index 100% rename from WatchApp/Assets.xcassets/Graph menu icons/1-hour-graph-38mm.png rename to WatchApp/DefaultAssets.xcassets/Graph menu icons/1-hour-graph-38mm.png diff --git a/WatchApp/Assets.xcassets/Graph menu icons/1-hour-graph-42mm.png b/WatchApp/DefaultAssets.xcassets/Graph menu icons/1-hour-graph-42mm.png similarity index 100% rename from WatchApp/Assets.xcassets/Graph menu icons/1-hour-graph-42mm.png rename to WatchApp/DefaultAssets.xcassets/Graph menu icons/1-hour-graph-42mm.png diff --git a/WatchApp/Assets.xcassets/Graph menu icons/1-hour-graph.imageset/1-hour-graph-38mm.png b/WatchApp/DefaultAssets.xcassets/Graph menu icons/1-hour-graph.imageset/1-hour-graph-38mm.png similarity index 100% rename from WatchApp/Assets.xcassets/Graph menu icons/1-hour-graph.imageset/1-hour-graph-38mm.png rename to WatchApp/DefaultAssets.xcassets/Graph menu icons/1-hour-graph.imageset/1-hour-graph-38mm.png diff --git a/WatchApp/Assets.xcassets/Graph menu icons/1-hour-graph.imageset/1-hour-graph-42mm.png b/WatchApp/DefaultAssets.xcassets/Graph menu icons/1-hour-graph.imageset/1-hour-graph-42mm.png similarity index 100% rename from WatchApp/Assets.xcassets/Graph menu icons/1-hour-graph.imageset/1-hour-graph-42mm.png rename to WatchApp/DefaultAssets.xcassets/Graph menu icons/1-hour-graph.imageset/1-hour-graph-42mm.png diff --git a/WatchApp/Assets.xcassets/Graph menu icons/1-hour-graph.imageset/Contents.json b/WatchApp/DefaultAssets.xcassets/Graph menu icons/1-hour-graph.imageset/Contents.json similarity index 100% rename from WatchApp/Assets.xcassets/Graph menu icons/1-hour-graph.imageset/Contents.json rename to WatchApp/DefaultAssets.xcassets/Graph menu icons/1-hour-graph.imageset/Contents.json diff --git a/WatchApp/Assets.xcassets/Graph menu icons/2-hour-graph-38mm.png b/WatchApp/DefaultAssets.xcassets/Graph menu icons/2-hour-graph-38mm.png similarity index 100% rename from WatchApp/Assets.xcassets/Graph menu icons/2-hour-graph-38mm.png rename to WatchApp/DefaultAssets.xcassets/Graph menu icons/2-hour-graph-38mm.png diff --git a/WatchApp/Assets.xcassets/Graph menu icons/2-hour-graph-42mm.png b/WatchApp/DefaultAssets.xcassets/Graph menu icons/2-hour-graph-42mm.png similarity index 100% rename from WatchApp/Assets.xcassets/Graph menu icons/2-hour-graph-42mm.png rename to WatchApp/DefaultAssets.xcassets/Graph menu icons/2-hour-graph-42mm.png diff --git a/WatchApp/Assets.xcassets/Graph menu icons/2-hour-graph.imageset/2-hour-graph-38mm.png b/WatchApp/DefaultAssets.xcassets/Graph menu icons/2-hour-graph.imageset/2-hour-graph-38mm.png similarity index 100% rename from WatchApp/Assets.xcassets/Graph menu icons/2-hour-graph.imageset/2-hour-graph-38mm.png rename to WatchApp/DefaultAssets.xcassets/Graph menu icons/2-hour-graph.imageset/2-hour-graph-38mm.png diff --git a/WatchApp/Assets.xcassets/Graph menu icons/2-hour-graph.imageset/2-hour-graph-42mm.png b/WatchApp/DefaultAssets.xcassets/Graph menu icons/2-hour-graph.imageset/2-hour-graph-42mm.png similarity index 100% rename from WatchApp/Assets.xcassets/Graph menu icons/2-hour-graph.imageset/2-hour-graph-42mm.png rename to WatchApp/DefaultAssets.xcassets/Graph menu icons/2-hour-graph.imageset/2-hour-graph-42mm.png diff --git a/WatchApp/Assets.xcassets/Graph menu icons/2-hour-graph.imageset/Contents.json b/WatchApp/DefaultAssets.xcassets/Graph menu icons/2-hour-graph.imageset/Contents.json similarity index 100% rename from WatchApp/Assets.xcassets/Graph menu icons/2-hour-graph.imageset/Contents.json rename to WatchApp/DefaultAssets.xcassets/Graph menu icons/2-hour-graph.imageset/Contents.json diff --git a/WatchApp/Assets.xcassets/Graph menu icons/3-hour-graph-38mm.png b/WatchApp/DefaultAssets.xcassets/Graph menu icons/3-hour-graph-38mm.png similarity index 100% rename from WatchApp/Assets.xcassets/Graph menu icons/3-hour-graph-38mm.png rename to WatchApp/DefaultAssets.xcassets/Graph menu icons/3-hour-graph-38mm.png diff --git a/WatchApp/Assets.xcassets/Graph menu icons/3-hour-graph-42mm.png b/WatchApp/DefaultAssets.xcassets/Graph menu icons/3-hour-graph-42mm.png similarity index 100% rename from WatchApp/Assets.xcassets/Graph menu icons/3-hour-graph-42mm.png rename to WatchApp/DefaultAssets.xcassets/Graph menu icons/3-hour-graph-42mm.png diff --git a/WatchApp/Assets.xcassets/Graph menu icons/3-hour-graph.imageset/3-hour-graph-38mm.png b/WatchApp/DefaultAssets.xcassets/Graph menu icons/3-hour-graph.imageset/3-hour-graph-38mm.png similarity index 100% rename from WatchApp/Assets.xcassets/Graph menu icons/3-hour-graph.imageset/3-hour-graph-38mm.png rename to WatchApp/DefaultAssets.xcassets/Graph menu icons/3-hour-graph.imageset/3-hour-graph-38mm.png diff --git a/WatchApp/Assets.xcassets/Graph menu icons/3-hour-graph.imageset/3-hour-graph-42mm.png b/WatchApp/DefaultAssets.xcassets/Graph menu icons/3-hour-graph.imageset/3-hour-graph-42mm.png similarity index 100% rename from WatchApp/Assets.xcassets/Graph menu icons/3-hour-graph.imageset/3-hour-graph-42mm.png rename to WatchApp/DefaultAssets.xcassets/Graph menu icons/3-hour-graph.imageset/3-hour-graph-42mm.png diff --git a/WatchApp/Assets.xcassets/Graph menu icons/3-hour-graph.imageset/Contents.json b/WatchApp/DefaultAssets.xcassets/Graph menu icons/3-hour-graph.imageset/Contents.json similarity index 100% rename from WatchApp/Assets.xcassets/Graph menu icons/3-hour-graph.imageset/Contents.json rename to WatchApp/DefaultAssets.xcassets/Graph menu icons/3-hour-graph.imageset/Contents.json diff --git a/WatchApp/Assets.xcassets/Graph menu icons/Contents.json b/WatchApp/DefaultAssets.xcassets/Graph menu icons/Contents.json similarity index 100% rename from WatchApp/Assets.xcassets/Graph menu icons/Contents.json rename to WatchApp/DefaultAssets.xcassets/Graph menu icons/Contents.json diff --git a/WatchApp/Assets.xcassets/bolus.imageset/Bolus Icon 42mm.pdf b/WatchApp/DefaultAssets.xcassets/bolus.imageset/Bolus Icon 42mm.pdf similarity index 100% rename from WatchApp/Assets.xcassets/bolus.imageset/Bolus Icon 42mm.pdf rename to WatchApp/DefaultAssets.xcassets/bolus.imageset/Bolus Icon 42mm.pdf diff --git a/WatchApp/Assets.xcassets/bolus.imageset/Bolus icon 38mm.png b/WatchApp/DefaultAssets.xcassets/bolus.imageset/Bolus icon 38mm.png similarity index 100% rename from WatchApp/Assets.xcassets/bolus.imageset/Bolus icon 38mm.png rename to WatchApp/DefaultAssets.xcassets/bolus.imageset/Bolus icon 38mm.png diff --git a/WatchApp/Assets.xcassets/bolus.imageset/Contents.json b/WatchApp/DefaultAssets.xcassets/bolus.imageset/Contents.json similarity index 100% rename from WatchApp/Assets.xcassets/bolus.imageset/Contents.json rename to WatchApp/DefaultAssets.xcassets/bolus.imageset/Contents.json diff --git a/WatchApp/Assets.xcassets/carbs.imageset/Carb Icon 42mm.pdf b/WatchApp/DefaultAssets.xcassets/carbs.imageset/Carb Icon 42mm.pdf similarity index 100% rename from WatchApp/Assets.xcassets/carbs.imageset/Carb Icon 42mm.pdf rename to WatchApp/DefaultAssets.xcassets/carbs.imageset/Carb Icon 42mm.pdf diff --git a/WatchApp/Assets.xcassets/carbs.imageset/Carbs icon 38mm.png b/WatchApp/DefaultAssets.xcassets/carbs.imageset/Carbs icon 38mm.png similarity index 100% rename from WatchApp/Assets.xcassets/carbs.imageset/Carbs icon 38mm.png rename to WatchApp/DefaultAssets.xcassets/carbs.imageset/Carbs icon 38mm.png diff --git a/WatchApp/Assets.xcassets/carbs.imageset/Contents.json b/WatchApp/DefaultAssets.xcassets/carbs.imageset/Contents.json similarity index 100% rename from WatchApp/Assets.xcassets/carbs.imageset/Contents.json rename to WatchApp/DefaultAssets.xcassets/carbs.imageset/Contents.json diff --git a/WatchApp/Assets.xcassets/loop/Contents.json b/WatchApp/DefaultAssets.xcassets/loop/Contents.json similarity index 100% rename from WatchApp/Assets.xcassets/loop/Contents.json rename to WatchApp/DefaultAssets.xcassets/loop/Contents.json diff --git a/WatchApp/Assets.xcassets/loop/loop_aging.imageset/Contents.json b/WatchApp/DefaultAssets.xcassets/loop/loop_aging.imageset/Contents.json similarity index 100% rename from WatchApp/Assets.xcassets/loop/loop_aging.imageset/Contents.json rename to WatchApp/DefaultAssets.xcassets/loop/loop_aging.imageset/Contents.json diff --git a/WatchApp/Assets.xcassets/loop/loop_aging.imageset/loop-aging@38mm.png b/WatchApp/DefaultAssets.xcassets/loop/loop_aging.imageset/loop-aging@38mm.png similarity index 100% rename from WatchApp/Assets.xcassets/loop/loop_aging.imageset/loop-aging@38mm.png rename to WatchApp/DefaultAssets.xcassets/loop/loop_aging.imageset/loop-aging@38mm.png diff --git a/WatchApp/Assets.xcassets/loop/loop_aging.imageset/loop-aging@42mm.png b/WatchApp/DefaultAssets.xcassets/loop/loop_aging.imageset/loop-aging@42mm.png similarity index 100% rename from WatchApp/Assets.xcassets/loop/loop_aging.imageset/loop-aging@42mm.png rename to WatchApp/DefaultAssets.xcassets/loop/loop_aging.imageset/loop-aging@42mm.png diff --git a/WatchApp/Assets.xcassets/loop/loop_fresh.imageset/Contents.json b/WatchApp/DefaultAssets.xcassets/loop/loop_fresh.imageset/Contents.json similarity index 100% rename from WatchApp/Assets.xcassets/loop/loop_fresh.imageset/Contents.json rename to WatchApp/DefaultAssets.xcassets/loop/loop_fresh.imageset/Contents.json diff --git a/WatchApp/Assets.xcassets/loop/loop_fresh.imageset/loop-fresh@38mm.png b/WatchApp/DefaultAssets.xcassets/loop/loop_fresh.imageset/loop-fresh@38mm.png similarity index 100% rename from WatchApp/Assets.xcassets/loop/loop_fresh.imageset/loop-fresh@38mm.png rename to WatchApp/DefaultAssets.xcassets/loop/loop_fresh.imageset/loop-fresh@38mm.png diff --git a/WatchApp/Assets.xcassets/loop/loop_fresh.imageset/loop-fresh@42mm.png b/WatchApp/DefaultAssets.xcassets/loop/loop_fresh.imageset/loop-fresh@42mm.png similarity index 100% rename from WatchApp/Assets.xcassets/loop/loop_fresh.imageset/loop-fresh@42mm.png rename to WatchApp/DefaultAssets.xcassets/loop/loop_fresh.imageset/loop-fresh@42mm.png diff --git a/WatchApp/Assets.xcassets/loop/loop_stale.imageset/Contents.json b/WatchApp/DefaultAssets.xcassets/loop/loop_stale.imageset/Contents.json similarity index 100% rename from WatchApp/Assets.xcassets/loop/loop_stale.imageset/Contents.json rename to WatchApp/DefaultAssets.xcassets/loop/loop_stale.imageset/Contents.json diff --git a/WatchApp/Assets.xcassets/loop/loop_stale.imageset/loop-stale@38mm.png b/WatchApp/DefaultAssets.xcassets/loop/loop_stale.imageset/loop-stale@38mm.png similarity index 100% rename from WatchApp/Assets.xcassets/loop/loop_stale.imageset/loop-stale@38mm.png rename to WatchApp/DefaultAssets.xcassets/loop/loop_stale.imageset/loop-stale@38mm.png diff --git a/WatchApp/Assets.xcassets/loop/loop_stale.imageset/loop-stale@42mm.png b/WatchApp/DefaultAssets.xcassets/loop/loop_stale.imageset/loop-stale@42mm.png similarity index 100% rename from WatchApp/Assets.xcassets/loop/loop_stale.imageset/loop-stale@42mm.png rename to WatchApp/DefaultAssets.xcassets/loop/loop_stale.imageset/loop-stale@42mm.png diff --git a/WatchApp/Assets.xcassets/loop/loop_unknown.imageset/Contents.json b/WatchApp/DefaultAssets.xcassets/loop/loop_unknown.imageset/Contents.json similarity index 100% rename from WatchApp/Assets.xcassets/loop/loop_unknown.imageset/Contents.json rename to WatchApp/DefaultAssets.xcassets/loop/loop_unknown.imageset/Contents.json diff --git a/WatchApp/Assets.xcassets/loop/loop_unknown.imageset/loop-unknown@38mm.png b/WatchApp/DefaultAssets.xcassets/loop/loop_unknown.imageset/loop-unknown@38mm.png similarity index 100% rename from WatchApp/Assets.xcassets/loop/loop_unknown.imageset/loop-unknown@38mm.png rename to WatchApp/DefaultAssets.xcassets/loop/loop_unknown.imageset/loop-unknown@38mm.png diff --git a/WatchApp/Assets.xcassets/loop/loop_unknown.imageset/loop-unknown@42mm.png b/WatchApp/DefaultAssets.xcassets/loop/loop_unknown.imageset/loop-unknown@42mm.png similarity index 100% rename from WatchApp/Assets.xcassets/loop/loop_unknown.imageset/loop-unknown@42mm.png rename to WatchApp/DefaultAssets.xcassets/loop/loop_unknown.imageset/loop-unknown@42mm.png diff --git a/WatchApp/Assets.xcassets/pre-meal.imageset/Contents.json b/WatchApp/DefaultAssets.xcassets/pre-meal.imageset/Contents.json similarity index 100% rename from WatchApp/Assets.xcassets/pre-meal.imageset/Contents.json rename to WatchApp/DefaultAssets.xcassets/pre-meal.imageset/Contents.json diff --git a/WatchApp/Assets.xcassets/pre-meal.imageset/Pre-Meal 38mm.png b/WatchApp/DefaultAssets.xcassets/pre-meal.imageset/Pre-Meal 38mm.png similarity index 100% rename from WatchApp/Assets.xcassets/pre-meal.imageset/Pre-Meal 38mm.png rename to WatchApp/DefaultAssets.xcassets/pre-meal.imageset/Pre-Meal 38mm.png diff --git a/WatchApp/Assets.xcassets/pre-meal.imageset/Pre-Meal 42mm.pdf b/WatchApp/DefaultAssets.xcassets/pre-meal.imageset/Pre-Meal 42mm.pdf similarity index 100% rename from WatchApp/Assets.xcassets/pre-meal.imageset/Pre-Meal 42mm.pdf rename to WatchApp/DefaultAssets.xcassets/pre-meal.imageset/Pre-Meal 42mm.pdf diff --git a/WatchApp/Assets.xcassets/workout.imageset/Contents.json b/WatchApp/DefaultAssets.xcassets/workout.imageset/Contents.json similarity index 100% rename from WatchApp/Assets.xcassets/workout.imageset/Contents.json rename to WatchApp/DefaultAssets.xcassets/workout.imageset/Contents.json diff --git a/WatchApp/Assets.xcassets/workout.imageset/Workout 38mm.png b/WatchApp/DefaultAssets.xcassets/workout.imageset/Workout 38mm.png similarity index 100% rename from WatchApp/Assets.xcassets/workout.imageset/Workout 38mm.png rename to WatchApp/DefaultAssets.xcassets/workout.imageset/Workout 38mm.png diff --git a/WatchApp/Assets.xcassets/workout.imageset/Workout 42mm.pdf b/WatchApp/DefaultAssets.xcassets/workout.imageset/Workout 42mm.pdf similarity index 100% rename from WatchApp/Assets.xcassets/workout.imageset/Workout 42mm.pdf rename to WatchApp/DefaultAssets.xcassets/workout.imageset/Workout 42mm.pdf diff --git a/WatchApp/Info.plist b/WatchApp/Info.plist index b881f84c30..70d55802d0 100644 --- a/WatchApp/Info.plist +++ b/WatchApp/Info.plist @@ -17,11 +17,15 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.10.0dev + $(LOOP_MARKETING_VERSION) CFBundleSignature ???? CFBundleVersion $(CURRENT_PROJECT_VERSION) + NSUserActivityTypes + + com.loopkit.Loop.AddCarbEntryOnWatch + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait @@ -31,9 +35,5 @@ $(MAIN_APP_BUNDLE_IDENTIFIER) WKWatchKitApp - NSUserActivityTypes - - com.loopkit.Loop.AddCarbEntryOnWatch - From 64ce5397932000bfe501382ec705eff0fdf8c21a Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 23 Aug 2019 16:55:26 -0500 Subject: [PATCH 081/132] Resolve conflict lines --- Learn/Info.plist | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Learn/Info.plist b/Learn/Info.plist index f76bd4435f..f870dba92f 100644 --- a/Learn/Info.plist +++ b/Learn/Info.plist @@ -19,11 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString -<<<<<<< HEAD 1.10.1 -======= - 1.10.0 ->>>>>>> tidepool/dev CFBundleVersion $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS From 184582d607aa76636b9b3d803dd261d6dde07827 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 23 Aug 2019 22:52:49 -0500 Subject: [PATCH 082/132] Update copy-frameworks to test presence correctly, and follow symlinks for release builds (#1076) --- Scripts/copy-frameworks.sh | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Scripts/copy-frameworks.sh b/Scripts/copy-frameworks.sh index a8c503f8ee..8208e12785 100755 --- a/Scripts/copy-frameworks.sh +++ b/Scripts/copy-frameworks.sh @@ -18,23 +18,26 @@ else fi for COUNTER in $(seq 0 $(($SCRIPT_INPUT_FILE_COUNT - 1))); do - SCRIPT_INPUT_FILE="SCRIPT_INPUT_FILE_${COUNTER}" - CARTHAGE_BUILD_FILE="${!SCRIPT_INPUT_FILE/${BUILT_PRODUCTS_DIR}/${CARTHAGE_BUILD_DIR}}" + SCRIPT_INPUT_VAR="SCRIPT_INPUT_FILE_${COUNTER}" + CARTHAGE_BUILD_FILE="${!SCRIPT_INPUT_VAR/${BUILT_PRODUCTS_DIR}/${CARTHAGE_BUILD_DIR}}" if [ -e "${CARTHAGE_BUILD_FILE}" ]; then - if [ -e "${SCRIPT_INPUT_FILE}" ]; then + if [ -e "${!SCRIPT_INPUT_VAR}" ]; then echo "ERROR: Duplicate frameworks found at:" - echo " ${SCRIPT_INPUT_FILE}" + echo " ${!SCRIPT_INPUT_VAR}" echo " ${CARTHAGE_BUILD_FILE}" exit 1 fi - echo "Substituting \"${CARTHAGE_BUILD_FILE}\" for \"${!SCRIPT_INPUT_FILE}\"" - export ${SCRIPT_INPUT_FILE}="${CARTHAGE_BUILD_FILE}" - elif [ -e "${!SCRIPT_INPUT_FILE}" ]; then - echo "Using original path: \"${!SCRIPT_INPUT_FILE}\"" + echo "Substituting \"${CARTHAGE_BUILD_FILE}\" for \"${!SCRIPT_INPUT_VAR}\"" + export ${SCRIPT_INPUT_VAR}="${CARTHAGE_BUILD_FILE}" + elif [ -e "${!SCRIPT_INPUT_VAR}" ]; then + echo "Using original path: \"${!SCRIPT_INPUT_VAR}\"" else echo "ERROR: Input file not found at \"${!SCRIPT_INPUT_FILE}\"" exit 1 fi + # Resolve any symlinks + export ${SCRIPT_INPUT_VAR}="$(readlink "${!SCRIPT_INPUT_VAR}" || echo "${!SCRIPT_INPUT_VAR}")" + echo "copy-frameworks resolved path: ${!SCRIPT_INPUT_VAR}" done echo "Copy Frameworks with Carthage" From c5899b065563d6004880c48fab1aa21409c03011 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 23 Aug 2019 22:54:35 -0500 Subject: [PATCH 083/132] Fix chartHashValue for GlucoseChartValueHashable (#1073) --- WatchApp Extension/Scenes/GlucoseChartValueHashable.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/WatchApp Extension/Scenes/GlucoseChartValueHashable.swift b/WatchApp Extension/Scenes/GlucoseChartValueHashable.swift index 808ba82d47..4737a2336f 100644 --- a/WatchApp Extension/Scenes/GlucoseChartValueHashable.swift +++ b/WatchApp Extension/Scenes/GlucoseChartValueHashable.swift @@ -22,9 +22,10 @@ extension GlucoseChartValueHashable { var chartHashValue: Int { var hashValue = start.timeIntervalSinceReferenceDate.hashValue hashValue ^= end.timeIntervalSince(start).hashValue - hashValue ^= min.hashValue + // HKQuantity.hashValue returns 0, so we need to convert + hashValue ^= min.doubleValue(for: .milligramsPerDeciliter).hashValue if min != max { - hashValue ^= max.hashValue + hashValue ^= max.doubleValue(for: .milligramsPerDeciliter).hashValue } return hashValue } From 1286353bd09c65fdad36cb7a7526452f6e527290 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 23 Aug 2019 22:59:57 -0500 Subject: [PATCH 084/132] Add translations for New Carb Entry intent, fix plugins for app extension --- Common/Base.lproj/Intents.intentdefinition | 8 ++++--- Common/Models/PumpManagerUI.swift | 11 +++++----- Common/Models/StatusExtensionContext.swift | 4 ---- Common/de.lproj/Intents.strings | 6 +++++ Common/en.lproj/Intents.strings | 2 +- Common/es.lproj/Intents.strings | 2 +- Common/fr.lproj/Intents.strings | 6 +++++ Common/it.lproj/Intents.strings | 6 ++--- Common/nb.lproj/Intents.strings | 6 +++++ Common/nl.lproj/Intents.strings | 6 +++++ Common/pl.lproj/Intents.strings | 6 +++++ Common/ru.lproj/Intents.strings | 2 +- Common/zh-Hans.lproj/Intents.strings | 6 +++++ .../StatusViewController.swift | 4 +++- Loop.xcodeproj/project.pbxproj | 22 +++++++++++++++---- LoopCore/LoopSettings.swift | 4 ++++ 16 files changed, 77 insertions(+), 24 deletions(-) create mode 100644 Common/de.lproj/Intents.strings create mode 100644 Common/fr.lproj/Intents.strings create mode 100644 Common/nb.lproj/Intents.strings create mode 100644 Common/nl.lproj/Intents.strings create mode 100644 Common/pl.lproj/Intents.strings create mode 100644 Common/zh-Hans.lproj/Intents.strings diff --git a/Common/Base.lproj/Intents.intentdefinition b/Common/Base.lproj/Intents.intentdefinition index 5a963140de..7dccff8c62 100644 --- a/Common/Base.lproj/Intents.intentdefinition +++ b/Common/Base.lproj/Intents.intentdefinition @@ -7,16 +7,18 @@ INIntentDefinitionModelVersion 1.0 INIntentDefinitionSystemVersion - 17G65 + 18G87 INIntentDefinitionToolsBuildVersion - 10L232m + 10G8 INIntentDefinitionToolsVersion - 10.0 + 10.3 INIntents INIntentCategory create + INIntentDescription + Add a carb entry to Loop INIntentDescriptionID yc02Yq INIntentLastParameterTag diff --git a/Common/Models/PumpManagerUI.swift b/Common/Models/PumpManagerUI.swift index 7eae5d8b7e..6a3d896e58 100644 --- a/Common/Models/PumpManagerUI.swift +++ b/Common/Models/PumpManagerUI.swift @@ -20,13 +20,14 @@ private let managersByIdentifier: [String: PumpManagerUI.Type] = staticPumpManag typealias PumpManagerHUDViewsRawValue = [String: Any] -func PumpManagerHUDViewsFromRawValue(_ rawValue: PumpManagerHUDViewsRawValue) -> [BaseHUDView]? { - guard let rawState = rawValue["hudProviderViews"] as? HUDProvider.HUDViewsRawState, - let manager = PumpManagerTypeFromRawValue(rawValue) as? PumpManagerUI.Type - else { +func PumpManagerHUDViewsFromRawValue(_ rawValue: PumpManagerHUDViewsRawValue, pluginManager: PluginManager) -> [BaseHUDView]? { + guard + let identifier = rawValue["managerIdentifier"] as? String, + let rawState = rawValue["hudProviderViews"] as? HUDProvider.HUDViewsRawState, + let manager = pluginManager.getPumpManagerTypeByIdentifier(identifier) ?? staticPumpManagersByIdentifier[identifier] as? PumpManagerUI.Type else + { return nil } - return manager.createHUDViews(rawValue: rawState) } diff --git a/Common/Models/StatusExtensionContext.swift b/Common/Models/StatusExtensionContext.swift index 6a8cbabd23..d9ac4aa837 100644 --- a/Common/Models/StatusExtensionContext.swift +++ b/Common/Models/StatusExtensionContext.swift @@ -157,10 +157,6 @@ struct PumpManagerHUDViewsContext: RawRepresentable { let pumpManagerHUDViewsRawValue: PumpManagerHUDViewsRawValue - var hudViews: [BaseHUDView]? { - return PumpManagerHUDViewsFromRawValue(pumpManagerHUDViewsRawValue) - } - init(pumpManagerHUDViewsRawValue: PumpManagerHUDViewsRawValue) { self.pumpManagerHUDViewsRawValue = pumpManagerHUDViewsRawValue } diff --git a/Common/de.lproj/Intents.strings b/Common/de.lproj/Intents.strings new file mode 100644 index 0000000000..46410e152e --- /dev/null +++ b/Common/de.lproj/Intents.strings @@ -0,0 +1,6 @@ +"80eo5o" = "Add Carb Entry"; + +"OcNxIj" = "Add Carb Entry"; + +"yc02Yq" = "Add a carb entry to Loop"; + diff --git a/Common/en.lproj/Intents.strings b/Common/en.lproj/Intents.strings index 504d81b65f..5553d89cde 100644 --- a/Common/en.lproj/Intents.strings +++ b/Common/en.lproj/Intents.strings @@ -5,4 +5,4 @@ "OcNxIj" = "Add Carb Entry"; /* INIntentDescription */ -"yc02Yq" = ""; +"yc02Yq" = "Add a carb entry to Loop"; diff --git a/Common/es.lproj/Intents.strings b/Common/es.lproj/Intents.strings index 9d5febcec5..34a25016e7 100644 --- a/Common/es.lproj/Intents.strings +++ b/Common/es.lproj/Intents.strings @@ -5,4 +5,4 @@ "OcNxIj" = "Agregar entrada de carbohidratos"; /* INIntentDescription */ -"yc02Yq" = ""; +"yc02Yq" = "Add a carb entry to Loop"; diff --git a/Common/fr.lproj/Intents.strings b/Common/fr.lproj/Intents.strings new file mode 100644 index 0000000000..46410e152e --- /dev/null +++ b/Common/fr.lproj/Intents.strings @@ -0,0 +1,6 @@ +"80eo5o" = "Add Carb Entry"; + +"OcNxIj" = "Add Carb Entry"; + +"yc02Yq" = "Add a carb entry to Loop"; + diff --git a/Common/it.lproj/Intents.strings b/Common/it.lproj/Intents.strings index 504d81b65f..46410e152e 100644 --- a/Common/it.lproj/Intents.strings +++ b/Common/it.lproj/Intents.strings @@ -1,8 +1,6 @@ -/* INIntentTitle */ "80eo5o" = "Add Carb Entry"; -/* INIntentParameterCombinationTitle */ "OcNxIj" = "Add Carb Entry"; -/* INIntentDescription */ -"yc02Yq" = ""; +"yc02Yq" = "Add a carb entry to Loop"; + diff --git a/Common/nb.lproj/Intents.strings b/Common/nb.lproj/Intents.strings new file mode 100644 index 0000000000..46410e152e --- /dev/null +++ b/Common/nb.lproj/Intents.strings @@ -0,0 +1,6 @@ +"80eo5o" = "Add Carb Entry"; + +"OcNxIj" = "Add Carb Entry"; + +"yc02Yq" = "Add a carb entry to Loop"; + diff --git a/Common/nl.lproj/Intents.strings b/Common/nl.lproj/Intents.strings new file mode 100644 index 0000000000..46410e152e --- /dev/null +++ b/Common/nl.lproj/Intents.strings @@ -0,0 +1,6 @@ +"80eo5o" = "Add Carb Entry"; + +"OcNxIj" = "Add Carb Entry"; + +"yc02Yq" = "Add a carb entry to Loop"; + diff --git a/Common/pl.lproj/Intents.strings b/Common/pl.lproj/Intents.strings new file mode 100644 index 0000000000..46410e152e --- /dev/null +++ b/Common/pl.lproj/Intents.strings @@ -0,0 +1,6 @@ +"80eo5o" = "Add Carb Entry"; + +"OcNxIj" = "Add Carb Entry"; + +"yc02Yq" = "Add a carb entry to Loop"; + diff --git a/Common/ru.lproj/Intents.strings b/Common/ru.lproj/Intents.strings index 1043009cb4..e3311d49c4 100644 --- a/Common/ru.lproj/Intents.strings +++ b/Common/ru.lproj/Intents.strings @@ -5,4 +5,4 @@ "OcNxIj" = "Добавить запись углеводов"; /* INIntentDescription */ -"yc02Yq" = ""; +"yc02Yq" = "Add a carb entry to Loop"; diff --git a/Common/zh-Hans.lproj/Intents.strings b/Common/zh-Hans.lproj/Intents.strings new file mode 100644 index 0000000000..46410e152e --- /dev/null +++ b/Common/zh-Hans.lproj/Intents.strings @@ -0,0 +1,6 @@ +"80eo5o" = "Add Carb Entry"; + +"OcNxIj" = "Add Carb Entry"; + +"yc02Yq" = "Add a carb entry to Loop"; + diff --git a/Loop Status Extension/StatusViewController.swift b/Loop Status Extension/StatusViewController.swift index 08e1e9e465..52af6408dc 100644 --- a/Loop Status Extension/StatusViewController.swift +++ b/Loop Status Extension/StatusViewController.swift @@ -80,6 +80,8 @@ class StatusViewController: UIViewController, NCWidgetProviding { basalProfile: defaults?.basalRateSchedule, insulinSensitivitySchedule: defaults?.insulinSensitivitySchedule ) + + private var pluginManager = PluginManager() override func viewDidLoad() { super.viewDidLoad() @@ -193,7 +195,7 @@ class StatusViewController: UIViewController, NCWidgetProviding { let hudViews: [BaseHUDView] if let hudViewsContext = context.pumpManagerHUDViewsContext, - let contextHUDViews = hudViewsContext.hudViews + let contextHUDViews = PumpManagerHUDViewsFromRawValue(hudViewsContext.pumpManagerHUDViewsRawValue, pluginManager: self.pluginManager) { hudViews = contextHUDViews } else { diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index ee20af07ed..bafe172155 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -368,6 +368,7 @@ C125F31B22FE7CE200FD0545 /* copy-frameworks.sh in Resources */ = {isa = PBXBuildFile; fileRef = C125F31A22FE7CE200FD0545 /* copy-frameworks.sh */; }; C12F21A71DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json in Resources */ = {isa = PBXBuildFile; fileRef = C12F21A61DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json */; }; C13255D6223E7BE2008AF50C /* BolusProgressTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C1F8B1DB223862D500DD66CF /* BolusProgressTableViewCell.xib */; }; + C136AA2423109CC6008A320D /* LoopPlugins.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DA84122E8E112008624C2 /* LoopPlugins.swift */; }; C13BAD941E8009B000050CB5 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0B31E45C1BE00FF19A9 /* NumberFormatter.swift */; }; C15713821DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */; }; C16DA84222E8E112008624C2 /* LoopPlugins.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DA84122E8E112008624C2 /* LoopPlugins.swift */; }; @@ -384,9 +385,9 @@ C1C73F0D1DE3D0270022FC89 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1C73F0F1DE3D0270022FC89 /* InfoPlist.strings */; }; C1C7F1C1220D675800689850 /* OmniKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C7F1BF220D675700689850 /* OmniKit.framework */; }; C1C7F1C2220D675800689850 /* OmniKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C7F1C0220D675700689850 /* OmniKitUI.framework */; }; - C1D289B522F90A52003FFBD9 /* BasalDeliveryState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D289B422F90A52003FFBD9 /* BasalDeliveryState.swift */; }; C1D1405922FB69CC00DA6242 /* DerivedAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1D1405822FB69CC00DA6242 /* DerivedAssets.xcassets */; }; C1D1405F22FB7ADF00DA6242 /* DerivedWatchAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1D1405E22FB7ADF00DA6242 /* DerivedWatchAssets.xcassets */; }; + C1D289B522F90A52003FFBD9 /* BasalDeliveryState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D289B422F90A52003FFBD9 /* BasalDeliveryState.swift */; }; C1E2773E224177C000354103 /* ClockKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1E2773D224177C000354103 /* ClockKit.framework */; }; C1E2774822433D7A00354103 /* MKRingProgressView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1E2774722433D7A00354103 /* MKRingProgressView.framework */; }; C1F8B243223E73FD00DD66CF /* BolusProgressTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F8B1D122375E4200DD66CF /* BolusProgressTableViewCell.swift */; }; @@ -650,7 +651,6 @@ 43785E9A2120E7060057DED1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = ""; }; 43785E9F2122774A0057DED1 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Intents.strings; sourceTree = ""; }; 43785EA12122774B0057DED1 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Intents.strings; sourceTree = ""; }; - 43785EA32122774B0057DED1 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Intents.strings; sourceTree = ""; }; 4379CFEF21112CF700AADC79 /* ShareClientUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ShareClientUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 437AFEE6203688CF008C4892 /* LoopKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LoopKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 437CEEBD1CD6E0CB003C8C80 /* LoopCompletionHUDView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoopCompletionHUDView.swift; sourceTree = ""; }; @@ -1017,6 +1017,13 @@ C10428961D17BAD400DD539A /* NightscoutUploadKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = NightscoutUploadKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C10B28451EA9BA5E006EA1FC /* far_future_high_bg_forecast.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = far_future_high_bg_forecast.json; sourceTree = ""; }; C125F31A22FE7CE200FD0545 /* copy-frameworks.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "copy-frameworks.sh"; sourceTree = ""; }; + C12CB9AC23106A3C00F84978 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Intents.strings; sourceTree = ""; }; + C12CB9AE23106A5C00F84978 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Intents.strings; sourceTree = ""; }; + C12CB9B023106A5F00F84978 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Intents.strings; sourceTree = ""; }; + C12CB9B223106A6000F84978 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Intents.strings"; sourceTree = ""; }; + C12CB9B423106A6100F84978 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Intents.strings; sourceTree = ""; }; + C12CB9B623106A6200F84978 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Intents.strings; sourceTree = ""; }; + C12CB9B823106A6300F84978 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Intents.strings; sourceTree = ""; }; C12F21A61DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_temp_basal_very_low_end_in_range.json; sourceTree = ""; }; C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MealBolusNightscoutTreatment.swift; sourceTree = ""; }; C16DA84122E8E112008624C2 /* LoopPlugins.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopPlugins.swift; sourceTree = ""; }; @@ -1034,9 +1041,9 @@ C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_temp_basal_dropping_then_rising.json; sourceTree = ""; }; C1C7F1BF220D675700689850 /* OmniKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OmniKit.framework; path = Carthage/Build/iOS/OmniKit.framework; sourceTree = ""; }; C1C7F1C0220D675700689850 /* OmniKitUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OmniKitUI.framework; path = Carthage/Build/iOS/OmniKitUI.framework; sourceTree = ""; }; - C1D289B422F90A52003FFBD9 /* BasalDeliveryState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasalDeliveryState.swift; sourceTree = ""; }; C1D1405822FB69CC00DA6242 /* DerivedAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DerivedAssets.xcassets; sourceTree = BUILT_PRODUCTS_DIR; }; C1D1405E22FB7ADF00DA6242 /* DerivedWatchAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DerivedWatchAssets.xcassets; sourceTree = BUILT_PRODUCTS_DIR; }; + C1D289B422F90A52003FFBD9 /* BasalDeliveryState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasalDeliveryState.swift; sourceTree = ""; }; C1E2773D224177C000354103 /* ClockKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ClockKit.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk/System/Library/Frameworks/ClockKit.framework; sourceTree = DEVELOPER_DIR; }; C1E2774722433D7A00354103 /* MKRingProgressView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MKRingProgressView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C1F8B1D122375E4200DD66CF /* BolusProgressTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusProgressTableViewCell.swift; sourceTree = ""; }; @@ -2805,6 +2812,7 @@ 43BFF0CD1E466C8400FF19A9 /* StateColorPalette.swift in Sources */, 4FC8C8021DEB943800A1452E /* NSUserDefaults+StatusExtension.swift in Sources */, 43BFF0BF1E45C8EA00FF19A9 /* UIColor+Widget.swift in Sources */, + C136AA2423109CC6008A320D /* LoopPlugins.swift in Sources */, 4F70C2121DE900EA006380B7 /* StatusExtensionContext.swift in Sources */, 4F70C1E11DE8DCA7006380B7 /* StatusViewController.swift in Sources */, ); @@ -2963,8 +2971,14 @@ 43785E9A2120E7060057DED1 /* Base */, 43785E9F2122774A0057DED1 /* es */, 43785EA12122774B0057DED1 /* ru */, - 43785EA32122774B0057DED1 /* it */, 43C98058212A799E003B5D17 /* en */, + C12CB9AC23106A3C00F84978 /* it */, + C12CB9AE23106A5C00F84978 /* fr */, + C12CB9B023106A5F00F84978 /* de */, + C12CB9B223106A6000F84978 /* zh-Hans */, + C12CB9B423106A6100F84978 /* nl */, + C12CB9B623106A6200F84978 /* nb */, + C12CB9B823106A6300F84978 /* pl */, ); name = Intents.intentdefinition; sourceTree = ""; diff --git a/LoopCore/LoopSettings.swift b/LoopCore/LoopSettings.swift index f5e83d0c9e..f967be355a 100644 --- a/LoopCore/LoopSettings.swift +++ b/LoopCore/LoopSettings.swift @@ -46,6 +46,10 @@ public struct LoopSettings: Equatable { public let minimumChartWidthPerHour: CGFloat = 50 public let statusChartMinimumHistoryDisplay: TimeInterval = .hours(1) + + public var glucoseUnit: HKUnit? { + return glucoseTargetRangeSchedule?.unit + } // MARK - Guardrails From 7ee047117f10ee7946fd1cdf9f7a2ad5fcabcb2a Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 23 Aug 2019 23:37:12 -0500 Subject: [PATCH 085/132] Revert temp basal enact ui in status bar --- Loop.xcodeproj/project.pbxproj | 2 ++ .../View Controllers/StatusTableViewController.swift | 12 ------------ 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index d8c606e08a..054c38a84f 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -3324,6 +3324,7 @@ CODE_SIGN_ENTITLEMENTS = Loop/Loop.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; DEVELOPMENT_TEAM = ""; + ENABLE_BITCODE = YES; INFOPLIST_FILE = Loop/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; "OTHER_SWIFT_FLAGS[arch=*]" = "-DDEBUG"; @@ -3342,6 +3343,7 @@ CODE_SIGN_ENTITLEMENTS = Loop/Loop.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; DEVELOPMENT_TEAM = ""; + ENABLE_BITCODE = YES; INFOPLIST_FILE = Loop/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(MAIN_APP_BUNDLE_IDENTIFIER)"; diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index 238faf3aed..3155910906 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -556,7 +556,6 @@ final class StatusTableViewController: ChartsTableViewController { case hidden case recommendedTempBasal(tempBasal: TempBasalRecommendation, at: Date, enacting: Bool) case scheduleOverrideEnabled(TemporaryScheduleOverride) - case enactingTempbasal case enactingBolus case bolusing(dose: DoseEntry) case cancelingBolus @@ -581,8 +580,6 @@ final class StatusTableViewController: ChartsTableViewController { statusRowMode = .enactingBolus } else if case .canceling = bolusState { statusRowMode = .cancelingBolus - } else if case .initiatingTempBasal = basalDeliveryState { - statusRowMode = .enactingTempbasal } else if case .suspended = basalDeliveryState { statusRowMode = .pumpSuspended(resuming: false) } else if self.basalDeliveryState == .resuming { @@ -844,15 +841,6 @@ final class StatusTableViewController: ChartsTableViewController { cell.titleLabel.text = NSLocalizedString("Starting Bolus", comment: "The title of the cell indicating a bolus is being sent") cell.subtitleLabel.text = nil - let indicatorView = UIActivityIndicatorView(style: .gray) - indicatorView.startAnimating() - cell.accessoryView = indicatorView - return cell - case .enactingTempbasal: - let cell = getTitleSubtitleCell() - cell.titleLabel.text = NSLocalizedString("Enacting Temp Basal", comment: "The title of the cell indicating a temp basal is being enacted") - cell.subtitleLabel.text = nil - let indicatorView = UIActivityIndicatorView(style: .gray) indicatorView.startAnimating() cell.accessoryView = indicatorView From a102e603e60f1921650a5ef6d6202860d35467e8 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 24 Aug 2019 07:59:08 -0500 Subject: [PATCH 086/132] Update carthage revs --- Cartfile.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index e96e802f1e..d9e8339457 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,7 +1,7 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "fe92d93c24d18ff9755ff027e9b036d3769a8a0a" github "LoopKit/G4ShareSpy" "f6ae0cf1753131135f26046ddf07e096da884b84" -github "LoopKit/LoopKit" "72ef9c6067b7f19b926471f6d5ecd7a7b2d8118e" +github "LoopKit/LoopKit" "6f3c1e174a6d5bc952c62f4540567c13da54c2ce" github "LoopKit/dexcom-share-client-swift" "cdbb1cd19cf54b37add6d6c25300445a6abe24da" github "i-schuetz/SwiftCharts" "0.6.5" github "maxkonovalov/MKRingProgressView" "2.2.2" From c96e0b177e1a64ffd93f4dba5c4dbc229f0eb5e1 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 26 Aug 2019 22:12:45 -0500 Subject: [PATCH 087/132] Use Minimed and Omnipod PumpManagers as Plugins (#1079) * Update to plugin versions of Minimed and Omnipod pumpmanagers * Add support for plugins via Carthage * App extension uses separate path when initializing PluginManager * Bump cartfile revs * Use copy-frameworks.sh script fo DoseMathTests * Bump rileylink_ios rev * Handle EXPANDED_CODE_SIGN_IDENTITY being unset during command line builds --- Cartfile.resolved | 4 +- Common/Models/PumpManager.swift | 4 -- Common/Models/PumpManagerUI.swift | 4 -- LICENSE.md | 2 +- .../StatusViewController.swift | 5 +- Loop.xcodeproj/project.pbxproj | 47 ++----------- Loop/Plugins/LoopPlugins.swift | 4 +- Scripts/copy-frameworks.sh | 6 -- Scripts/copy-plugins.sh | 69 +++++++++++-------- 9 files changed, 55 insertions(+), 90 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index d9e8339457..e89047166e 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -2,7 +2,7 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "fe92d93c24d18ff9755ff027e9b036d3769a8a0a" github "LoopKit/G4ShareSpy" "f6ae0cf1753131135f26046ddf07e096da884b84" github "LoopKit/LoopKit" "6f3c1e174a6d5bc952c62f4540567c13da54c2ce" +github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" github "LoopKit/dexcom-share-client-swift" "cdbb1cd19cf54b37add6d6c25300445a6abe24da" github "i-schuetz/SwiftCharts" "0.6.5" -github "maxkonovalov/MKRingProgressView" "2.2.2" -github "ps2/rileylink_ios" "557f74a16bf31189a275e2610e0b8e922c0cdf92" +github "ps2/rileylink_ios" "38c52040600fcb877e81a2b850811e61ce0b3a42" diff --git a/Common/Models/PumpManager.swift b/Common/Models/PumpManager.swift index 7b3d97e95b..010bda2459 100644 --- a/Common/Models/PumpManager.swift +++ b/Common/Models/PumpManager.swift @@ -7,8 +7,6 @@ import Foundation import LoopKit -import MinimedKit -import OmniKit import MockKit public struct AvailableDevice { @@ -18,9 +16,7 @@ public struct AvailableDevice { let staticPumpManagers: [PumpManager.Type] = [ - MinimedPumpManager.self, MockPumpManager.self, - OmnipodPumpManager.self, ] let staticPumpManagersByIdentifier: [String: PumpManager.Type] = staticPumpManagers.reduce(into: [:]) { (map, Type) in diff --git a/Common/Models/PumpManagerUI.swift b/Common/Models/PumpManagerUI.swift index 6a3d896e58..16b602c8e1 100644 --- a/Common/Models/PumpManagerUI.swift +++ b/Common/Models/PumpManagerUI.swift @@ -9,10 +9,6 @@ import Foundation import LoopKit import LoopKitUI -import MinimedKit -import MinimedKitUI -import OmniKit -import OmniKitUI private let managersByIdentifier: [String: PumpManagerUI.Type] = staticPumpManagers.compactMap{ $0 as? PumpManagerUI.Type}.reduce(into: [:]) { (map, Type) in map[Type.managerIdentifier] = Type diff --git a/LICENSE.md b/LICENSE.md index 9a737abb2c..15256eda23 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -41,7 +41,7 @@ Copyright (c) 2015 Nathan Racklyeft Copyright (c) 2016 LoopKit Authors ## RileyLinkKit.framework -*Including MinimedKit.framework, NightscoutUploadKit.framework, and RileyLinkBLEKit.framework* +*Including MinimedKit.framework, NightscoutUploadKit.framework, OmniKit.framework, and RileyLinkBLEKit.framework* Copyright (c) 2015 Pete Schwamb diff --git a/Loop Status Extension/StatusViewController.swift b/Loop Status Extension/StatusViewController.swift index 52af6408dc..42cbad8a29 100644 --- a/Loop Status Extension/StatusViewController.swift +++ b/Loop Status Extension/StatusViewController.swift @@ -81,7 +81,10 @@ class StatusViewController: UIViewController, NCWidgetProviding { insulinSensitivitySchedule: defaults?.insulinSensitivitySchedule ) - private var pluginManager = PluginManager() + private var pluginManager: PluginManager = { + let containingAppFrameworksURL = Bundle.main.privateFrameworksURL?.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().appendingPathComponent("Frameworks") + return PluginManager(pluginsURL: containingAppFrameworksURL) + }() override func viewDidLoad() { super.viewDidLoad() diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index bafe172155..3ce06dc543 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -79,14 +79,12 @@ 43511CE221FD80E400566C63 /* RetrospectiveCorrection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43511CDF21FD80E400566C63 /* RetrospectiveCorrection.swift */; }; 43511CE321FD80E400566C63 /* StandardRetrospectiveCorrection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43511CE021FD80E400566C63 /* StandardRetrospectiveCorrection.swift */; }; 43511CEE220FC61700566C63 /* HUDRowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43511CED220FC61700566C63 /* HUDRowController.swift */; }; - 43523EDB1CC35083001850F1 /* RileyLinkKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43523EDA1CC35083001850F1 /* RileyLinkKit.framework */; }; 435400341C9F878D00D5819C /* SetBolusUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435400331C9F878D00D5819C /* SetBolusUserInfo.swift */; }; 435400351C9F878D00D5819C /* SetBolusUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435400331C9F878D00D5819C /* SetBolusUserInfo.swift */; }; 435CB6231F37967800C320C7 /* InsulinModelSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435CB6221F37967800C320C7 /* InsulinModelSettingsViewController.swift */; }; 436961911F19D11E00447E89 /* ChartPointsContextFillLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4369618F1F19C86400447E89 /* ChartPointsContextFillLayer.swift */; }; 436A0DA51D236A2A00104B24 /* LoopError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 436A0DA41D236A2A00104B24 /* LoopError.swift */; }; 436D9BF81F6F4EA100CFA75F /* recommended_temp_start_low_end_just_above_range.json in Resources */ = {isa = PBXBuildFile; fileRef = 436D9BF71F6F4EA100CFA75F /* recommended_temp_start_low_end_just_above_range.json */; }; - 43709AEA20DF3F8200F941B3 /* MinimedKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43709AE920DF3F8200F941B3 /* MinimedKitUI.framework */; }; 4372E484213A63FB0068E043 /* ChartHUDController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FFEDFBE20E5CF22000BFC58 /* ChartHUDController.swift */; }; 4372E487213C86240068E043 /* SampleValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4372E486213C86240068E043 /* SampleValue.swift */; }; 4372E488213C862B0068E043 /* SampleValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4372E486213C86240068E043 /* SampleValue.swift */; }; @@ -104,7 +102,6 @@ 43785E972120E4500057DED1 /* INRelevantShortcutStore+Loop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43785E952120E4010057DED1 /* INRelevantShortcutStore+Loop.swift */; }; 43785E982120E7060057DED1 /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 43785E9B2120E7060057DED1 /* Intents.intentdefinition */; }; 4379CFF021112CF700AADC79 /* ShareClientUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4379CFEF21112CF700AADC79 /* ShareClientUI.framework */; }; - 437AFEE42035252A008C4892 /* RileyLinkBLEKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 434AB0B11CBB4C3300422F4A /* RileyLinkBLEKit.framework */; }; 437AFEE520352591008C4892 /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F70C1DD1DE8DCA7006380B7 /* NotificationCenter.framework */; }; 437AFEE8203689FE008C4892 /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43F78D4B1C914197002152D1 /* LoopKit.framework */; }; 437CEEE41CDE5C0A003C8C80 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 437CEEE31CDE5C0A003C8C80 /* UIImage.swift */; }; @@ -177,7 +174,6 @@ 43C728F5222266F000C62969 /* ModalDayLesson.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C728F4222266F000C62969 /* ModalDayLesson.swift */; }; 43C728F72222700000C62969 /* DateIntervalEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C728F62222700000C62969 /* DateIntervalEntry.swift */; }; 43C728F9222A448700C62969 /* DayCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C728F8222A448700C62969 /* DayCalculator.swift */; }; - 43CA93371CB98079000026B5 /* MinimedKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43CA93361CB98079000026B5 /* MinimedKit.framework */; }; 43CB2B2B1D924D450079823D /* WCSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CB2B2A1D924D450079823D /* WCSession.swift */; }; 43CE7CDE1CA8B63E003CC1B0 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CE7CDD1CA8B63E003CC1B0 /* Data.swift */; }; 43CEE6E61E56AFD400CB9116 /* NightscoutUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43CEE6E51E56AFD400CB9116 /* NightscoutUploader.swift */; }; @@ -362,8 +358,6 @@ 89E26800229267DF00A3F2AF /* Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89E267FE229267DF00A3F2AF /* Optional.swift */; }; C10428971D17BAD400DD539A /* NightscoutUploadKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C10428961D17BAD400DD539A /* NightscoutUploadKit.framework */; }; C10B28461EA9BA5E006EA1FC /* far_future_high_bg_forecast.json in Resources */ = {isa = PBXBuildFile; fileRef = C10B28451EA9BA5E006EA1FC /* far_future_high_bg_forecast.json */; }; - C110A0E7221BC2400016560B /* OmniKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C7F1BF220D675700689850 /* OmniKit.framework */; }; - C110A0E8221BC2430016560B /* OmniKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C7F1C0220D675700689850 /* OmniKitUI.framework */; }; C11C87DE1E21EAAD00BB71D3 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F526D5E1DF2459000A04910 /* HKUnit.swift */; }; C125F31B22FE7CE200FD0545 /* copy-frameworks.sh in Resources */ = {isa = PBXBuildFile; fileRef = C125F31A22FE7CE200FD0545 /* copy-frameworks.sh */; }; C12F21A71DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json in Resources */ = {isa = PBXBuildFile; fileRef = C12F21A61DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json */; }; @@ -378,13 +372,10 @@ C17824A51E1AD4D100D9D25C /* BolusRecommendation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17824A41E1AD4D100D9D25C /* BolusRecommendation.swift */; }; C17824A61E1AF91F00D9D25C /* BolusRecommendation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17824A41E1AD4D100D9D25C /* BolusRecommendation.swift */; }; C1814B86225E507C008D2D8E /* Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1814B85225E507C008D2D8E /* Sequence.swift */; }; - C18852E22082AB1B00BECC8C /* RileyLinkKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C18852E12082AB1A00BECC8C /* RileyLinkKitUI.framework */; }; C18C8C511D5A351900E043FB /* NightscoutDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */; }; C1C0BE2A224C0FA000C03B4D /* SwiftCharts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4346D1EF1C781BEA00ABAFE3 /* SwiftCharts.framework */; }; C1C6591C1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json in Resources */ = {isa = PBXBuildFile; fileRef = C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */; }; C1C73F0D1DE3D0270022FC89 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1C73F0F1DE3D0270022FC89 /* InfoPlist.strings */; }; - C1C7F1C1220D675800689850 /* OmniKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C7F1BF220D675700689850 /* OmniKit.framework */; }; - C1C7F1C2220D675800689850 /* OmniKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1C7F1C0220D675700689850 /* OmniKitUI.framework */; }; C1D1405922FB69CC00DA6242 /* DerivedAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1D1405822FB69CC00DA6242 /* DerivedAssets.xcassets */; }; C1D1405F22FB7ADF00DA6242 /* DerivedWatchAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1D1405E22FB7ADF00DA6242 /* DerivedWatchAssets.xcassets */; }; C1D289B522F90A52003FFBD9 /* BasalDeliveryState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D289B422F90A52003FFBD9 /* BasalDeliveryState.swift */; }; @@ -616,7 +607,6 @@ 4345E40521F68E18009E00E5 /* CarbEntryListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbEntryListController.swift; sourceTree = ""; }; 4346D1E61C77F5FE00ABAFE3 /* ChartTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ChartTableViewCell.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 4346D1EF1C781BEA00ABAFE3 /* SwiftCharts.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftCharts.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 434AB0B11CBB4C3300422F4A /* RileyLinkBLEKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RileyLinkBLEKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 434F54561D287FDB002A9274 /* NibLoadable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NibLoadable.swift; sourceTree = ""; }; 434FB6451D68F1CD007B9C70 /* Amplitude.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Amplitude.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 434FF1E91CF26C29000DB779 /* IdentifiableClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentifiableClass.swift; sourceTree = ""; }; @@ -624,7 +614,6 @@ 43511CDF21FD80E400566C63 /* RetrospectiveCorrection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RetrospectiveCorrection.swift; sourceTree = ""; }; 43511CE021FD80E400566C63 /* StandardRetrospectiveCorrection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StandardRetrospectiveCorrection.swift; sourceTree = ""; }; 43511CED220FC61700566C63 /* HUDRowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HUDRowController.swift; sourceTree = ""; }; - 43523EDA1CC35083001850F1 /* RileyLinkKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RileyLinkKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 435400331C9F878D00D5819C /* SetBolusUserInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetBolusUserInfo.swift; sourceTree = ""; }; 435CB6221F37967800C320C7 /* InsulinModelSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InsulinModelSettingsViewController.swift; sourceTree = ""; }; 435CB6241F37ABFC00C320C7 /* ExponentialInsulinModelPreset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExponentialInsulinModelPreset.swift; sourceTree = ""; }; @@ -634,7 +623,6 @@ 4369618F1F19C86400447E89 /* ChartPointsContextFillLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartPointsContextFillLayer.swift; sourceTree = ""; }; 436A0DA41D236A2A00104B24 /* LoopError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoopError.swift; sourceTree = ""; }; 436D9BF71F6F4EA100CFA75F /* recommended_temp_start_low_end_just_above_range.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommended_temp_start_low_end_just_above_range.json; sourceTree = ""; }; - 43709AE920DF3F8200F941B3 /* MinimedKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MinimedKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4372E486213C86240068E043 /* SampleValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleValue.swift; sourceTree = ""; }; 4372E48A213CB5F00068E043 /* Double.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Double.swift; sourceTree = ""; }; 4372E48F213CFCE70068E043 /* LoopSettingsUserInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopSettingsUserInfo.swift; sourceTree = ""; }; @@ -715,7 +703,6 @@ 43C728F62222700000C62969 /* DateIntervalEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateIntervalEntry.swift; sourceTree = ""; }; 43C728F8222A448700C62969 /* DayCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DayCalculator.swift; sourceTree = ""; }; 43C98058212A799E003B5D17 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Intents.strings; sourceTree = ""; }; - 43CA93361CB98079000026B5 /* MinimedKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MinimedKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 43CB2B2A1D924D450079823D /* WCSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WCSession.swift; sourceTree = ""; }; 43CE7CDD1CA8B63E003CC1B0 /* Data.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = ""; }; 43CEE6E51E56AFD400CB9116 /* NightscoutUploader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutUploader.swift; sourceTree = ""; }; @@ -1032,15 +1019,12 @@ C17824A21E19EAB600D9D25C /* recommend_temp_basal_start_very_low_end_high.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_temp_basal_start_very_low_end_high.json; sourceTree = ""; }; C17824A41E1AD4D100D9D25C /* BolusRecommendation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BolusRecommendation.swift; sourceTree = ""; }; C1814B85225E507C008D2D8E /* Sequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sequence.swift; sourceTree = ""; }; - C18852E12082AB1A00BECC8C /* RileyLinkKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RileyLinkKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C18A491222FCC22800FDA733 /* build-derived-assets.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "build-derived-assets.sh"; sourceTree = ""; }; C18A491322FCC22900FDA733 /* make_scenario.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = make_scenario.py; sourceTree = ""; }; C18A491422FCC22900FDA733 /* build-derived-watch-assets.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "build-derived-watch-assets.sh"; sourceTree = ""; }; C18A491522FCC22900FDA733 /* copy-plugins.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "copy-plugins.sh"; sourceTree = ""; }; C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutDataManager.swift; sourceTree = ""; }; C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_temp_basal_dropping_then_rising.json; sourceTree = ""; }; - C1C7F1BF220D675700689850 /* OmniKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OmniKit.framework; path = Carthage/Build/iOS/OmniKit.framework; sourceTree = ""; }; - C1C7F1C0220D675700689850 /* OmniKitUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OmniKitUI.framework; path = Carthage/Build/iOS/OmniKitUI.framework; sourceTree = ""; }; C1D1405822FB69CC00DA6242 /* DerivedAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DerivedAssets.xcassets; sourceTree = BUILT_PRODUCTS_DIR; }; C1D1405E22FB7ADF00DA6242 /* DerivedWatchAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DerivedWatchAssets.xcassets; sourceTree = BUILT_PRODUCTS_DIR; }; C1D289B422F90A52003FFBD9 /* BasalDeliveryState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasalDeliveryState.swift; sourceTree = ""; }; @@ -1074,17 +1058,10 @@ 43F78D4F1C914197002152D1 /* LoopKit.framework in Frameworks */, 892A5D5B222F0D7C008961AB /* LoopTestingKit.framework in Frameworks */, 4F7528941DFE1E9500C322D6 /* LoopUI.framework in Frameworks */, - 43CA93371CB98079000026B5 /* MinimedKit.framework in Frameworks */, - 43709AEA20DF3F8200F941B3 /* MinimedKitUI.framework in Frameworks */, C1E2774822433D7A00354103 /* MKRingProgressView.framework in Frameworks */, 892A5D2A222EF60A008961AB /* MockKit.framework in Frameworks */, 892A5D2C222EF60A008961AB /* MockKitUI.framework in Frameworks */, C10428971D17BAD400DD539A /* NightscoutUploadKit.framework in Frameworks */, - C1C7F1C1220D675800689850 /* OmniKit.framework in Frameworks */, - C1C7F1C2220D675800689850 /* OmniKitUI.framework in Frameworks */, - 437AFEE42035252A008C4892 /* RileyLinkBLEKit.framework in Frameworks */, - 43523EDB1CC35083001850F1 /* RileyLinkKit.framework in Frameworks */, - C18852E22082AB1B00BECC8C /* RileyLinkKitUI.framework in Frameworks */, 43B371881CE597D10013C5A6 /* ShareClient.framework in Frameworks */, 4F08DE9B1E7BC4ED006741EA /* SwiftCharts.framework in Frameworks */, 4379CFF021112CF700AADC79 /* ShareClientUI.framework in Frameworks */, @@ -1160,8 +1137,6 @@ 43FCEEBF22220CF30013DD30 /* LoopKitUI.framework in Frameworks */, 4F7528951DFE1E9B00C322D6 /* LoopUI.framework in Frameworks */, 437AFEE520352591008C4892 /* NotificationCenter.framework in Frameworks */, - C110A0E7221BC2400016560B /* OmniKit.framework in Frameworks */, - C110A0E8221BC2430016560B /* OmniKitUI.framework in Frameworks */, C1C0BE2A224C0FA000C03B4D /* SwiftCharts.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1812,18 +1787,11 @@ 4344628320A7A3BE00C4BE6F /* LoopKit.framework */, 437AFEE6203688CF008C4892 /* LoopKitUI.framework */, 892A5D5A222F0D7C008961AB /* LoopTestingKit.framework */, - 43CA93361CB98079000026B5 /* MinimedKit.framework */, - 43709AE920DF3F8200F941B3 /* MinimedKitUI.framework */, C1E2774722433D7A00354103 /* MKRingProgressView.framework */, 892A5D29222EF60A008961AB /* MockKit.framework */, 892A5D2B222EF60A008961AB /* MockKitUI.framework */, C10428961D17BAD400DD539A /* NightscoutUploadKit.framework */, 4F70C1DD1DE8DCA7006380B7 /* NotificationCenter.framework */, - C1C7F1BF220D675700689850 /* OmniKit.framework */, - C1C7F1C0220D675700689850 /* OmniKitUI.framework */, - 434AB0B11CBB4C3300422F4A /* RileyLinkBLEKit.framework */, - 43523EDA1CC35083001850F1 /* RileyLinkKit.framework */, - C18852E12082AB1A00BECC8C /* RileyLinkKitUI.framework */, 43B371871CE597D10013C5A6 /* ShareClient.framework */, 4379CFEF21112CF700AADC79 /* ShareClientUI.framework */, 4346D1EF1C781BEA00ABAFE3 /* SwiftCharts.framework */, @@ -1894,7 +1862,7 @@ 43A9439C1B926B7B0051FA24 /* Embed Watch Content */, 43A943AE1B928D400051FA24 /* Embed Frameworks */, 43EDDBEF1C361BCE007D89B5 /* Copy Frameworks with Carthage */, - C16DA84322E8E5FF008624C2 /* Copy Plugins */, + C16DA84322E8E5FF008624C2 /* Install Plugins */, 4F70C1EC1DE8DCA8006380B7 /* Embed App Extensions */, ); buildRules = ( @@ -2412,19 +2380,12 @@ "$(BUILT_PRODUCTS_DIR)/CGMBLEKit.framework", "$(BUILT_PRODUCTS_DIR)/LoopKit.framework", "$(BUILT_PRODUCTS_DIR)/SwiftCharts.framework", - "$(BUILT_PRODUCTS_DIR)/MinimedKit.framework", - "$(BUILT_PRODUCTS_DIR)/RileyLinkBLEKit.framework", - "$(BUILT_PRODUCTS_DIR)/RileyLinkKit.framework", "$(BUILT_PRODUCTS_DIR)/Amplitude.framework", "$(BUILT_PRODUCTS_DIR)/ShareClient.framework", "$(BUILT_PRODUCTS_DIR)/NightscoutUploadKit.framework", "$(BUILT_PRODUCTS_DIR)/Crypto.framework", "$(BUILT_PRODUCTS_DIR)/G4ShareSpy.framework", - "$(BUILT_PRODUCTS_DIR)/RileyLinkKitUI.framework", - "$(BUILT_PRODUCTS_DIR)/OmniKit.framework", - "$(BUILT_PRODUCTS_DIR)/OmniKitUI.framework", "$(BUILT_PRODUCTS_DIR)/LoopKitUI.framework", - "$(BUILT_PRODUCTS_DIR)/MinimedKitUI.framework", "$(BUILT_PRODUCTS_DIR)/CGMBLEKitUI.framework", "$(BUILT_PRODUCTS_DIR)/ShareClientUI.framework", "$(BUILT_PRODUCTS_DIR)/LoopTestingKit.framework", @@ -2474,9 +2435,9 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "CARTHAGE_BUILD_DIR=\"${SRCROOT}/Carthage/Build\"\nif [ -n \"${IPHONEOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/iOS\"\nelif [ -n \"${WATCHOS_DEPLOYMENT_TARGET}\" ]; then\n CARTHAGE_BUILD_DIR=\"${CARTHAGE_BUILD_DIR}/watchOS\"\nelse\n echo \"ERROR: Unexpected deployment target type\"\n exit 1\nfi\n\nfor SCRIPT_INPUT_FILE in ${!SCRIPT_INPUT_FILE_*}; do\n CARTHAGE_BUILD_FILE=\"${!SCRIPT_INPUT_FILE/${BUILT_PRODUCTS_DIR}/${CARTHAGE_BUILD_DIR}}\"\n if [ -e \"${CARTHAGE_BUILD_FILE}\" ]; then\n if [ -e \"${SCRIPT_INPUT_FILE}\" ]; then\n echo \"ERROR: Duplicate frameworks found at:\"\n echo \" ${SCRIPT_INPUT_FILE}\"\n echo \" ${CARTHAGE_BUILD_FILE}\"\n exit 1\n fi\n echo \"Substituting \\\"${CARTHAGE_BUILD_FILE}\\\" for \\\"${!SCRIPT_INPUT_FILE}\\\"\"\n export ${SCRIPT_INPUT_FILE}=\"${CARTHAGE_BUILD_FILE}\"\n fi\ndone\n\necho \"Copy Frameworks with Carthage\"\ncarthage copy-frameworks\n"; + shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n\n"; }; - C16DA84322E8E5FF008624C2 /* Copy Plugins */ = { + C16DA84322E8E5FF008624C2 /* Install Plugins */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -2485,7 +2446,7 @@ ); inputPaths = ( ); - name = "Copy Plugins"; + name = "Install Plugins"; outputFileListPaths = ( ); outputPaths = ( diff --git a/Loop/Plugins/LoopPlugins.swift b/Loop/Plugins/LoopPlugins.swift index 033ae6ae6f..4f3d41e344 100644 --- a/Loop/Plugins/LoopPlugins.swift +++ b/Loop/Plugins/LoopPlugins.swift @@ -13,10 +13,10 @@ import LoopKitUI class PluginManager { private let pluginBundles: [Bundle] - public init() { + public init(pluginsURL: URL? = Bundle.main.privateFrameworksURL) { var bundles = [Bundle]() - if let pluginsURL = Bundle.main.privateFrameworksURL { + if let pluginsURL = pluginsURL { do { for pluginURL in try FileManager.default.contentsOfDirectory(at: pluginsURL, includingPropertiesForKeys: nil).filter{$0.path.hasSuffix(".framework")} { if let bundle = Bundle(url: pluginURL), bundle.isLoopPlugin { diff --git a/Scripts/copy-frameworks.sh b/Scripts/copy-frameworks.sh index a5082d1ff2..aa5424eaf7 100755 --- a/Scripts/copy-frameworks.sh +++ b/Scripts/copy-frameworks.sh @@ -21,12 +21,6 @@ for COUNTER in $(seq 0 $(($SCRIPT_INPUT_FILE_COUNT - 1))); do SCRIPT_INPUT_VAR="SCRIPT_INPUT_FILE_${COUNTER}" CARTHAGE_BUILD_FILE="${!SCRIPT_INPUT_VAR/${BUILT_PRODUCTS_DIR}/${CARTHAGE_BUILD_DIR}}" if [ -e "${CARTHAGE_BUILD_FILE}" ]; then - if [ -e "${!SCRIPT_INPUT_VAR}" ]; then - echo "ERROR: Duplicate frameworks found at:" - echo " ${!SCRIPT_INPUT_VAR}" - echo " ${CARTHAGE_BUILD_FILE}" - exit 1 - fi echo "Substituting \"${CARTHAGE_BUILD_FILE}\" for \"${!SCRIPT_INPUT_VAR}\"" export ${SCRIPT_INPUT_VAR}="${CARTHAGE_BUILD_FILE}" elif [ -e "${!SCRIPT_INPUT_VAR}" ]; then diff --git a/Scripts/copy-plugins.sh b/Scripts/copy-plugins.sh index c6e950df99..0b723cfba8 100755 --- a/Scripts/copy-plugins.sh +++ b/Scripts/copy-plugins.sh @@ -1,38 +1,53 @@ -#!/bin/sh -eu +#!/bin/sh -e # copy-plugins.sh # Loop # # Copyright © 2019 LoopKit Authors. All rights reserved. -echo "Looking for plugins in $BUILT_PRODUCTS_DIR" shopt -s nullglob # Copy device plugins -for f in "${BUILT_PRODUCTS_DIR}"/*.loopplugin; do - plugin=$(basename "$f") - echo Copying device plugin: $plugin to frameworks directory in app - plugin_path="$(readlink "$f" || echo "$f")" - plugin_as_framework_path="${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/${plugin%.*}.framework" - rsync -va --exclude=Frameworks "$plugin_path/." "${plugin_as_framework_path}" - # Rename .plugin to .framework - if [ "$EXPANDED_CODE_SIGN_IDENTITY" != "-" ]; then - export CODESIGN_ALLOCATE=${DT_TOOLCHAIN_DIR}/usr/bin/codesign_allocate - echo "Signing ${plugin} with ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" - /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} --timestamp=none --preserve-metadata=identifier,entitlements,flags "$plugin_as_framework_path" - else - echo "Skipping signing, no identity set" - fi - for framework_path in "${f}"/Frameworks/*.framework; do - framework=$(basename "$framework_path") - echo Copying "$framework_path/." to "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/${framework}" - cp -a "$framework_path/." "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/${framework}" - plugin_path="$(readlink "$f" || echo "$f")" - if [ "$EXPANDED_CODE_SIGN_IDENTITY" != "-" ]; then - echo "Signing $framework for $plugin with $EXPANDED_CODE_SIGN_IDENTITY_NAME" - /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} --timestamp=none --preserve-metadata=identifier,entitlements,flags "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/${framework}" - fi - done -done +function copy_plugins { + echo "Looking for plugins in $1" + for f in "$1"/*.loopplugin; do + plugin=$(basename "$f") + echo Copying device plugin: $plugin to frameworks directory in app + plugin_path="$(readlink "$f" || echo "$f")" + plugin_as_framework_path="${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/${plugin%.*}.framework" + rsync -va --exclude=Frameworks "$plugin_path/." "${plugin_as_framework_path}" + # Rename .plugin to .framework + if [ "$EXPANDED_CODE_SIGN_IDENTITY" != "-" ] && [ "$EXPANDED_CODE_SIGN_IDENTITY" != "" ]; then + export CODESIGN_ALLOCATE=${DT_TOOLCHAIN_DIR}/usr/bin/codesign_allocate + echo "Signing ${plugin} with ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" + /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} --timestamp=none --preserve-metadata=identifier,entitlements,flags "$plugin_as_framework_path" + else + echo "Skipping signing, no identity set" + fi + for framework_path in "${f}"/Frameworks/*.framework; do + framework=$(basename "$framework_path") + echo Copying "$framework_path/." to "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/${framework}" + cp -a "$framework_path/." "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/${framework}" + plugin_path="$(readlink "$f" || echo "$f")" + if [ "$EXPANDED_CODE_SIGN_IDENTITY" != "-" ] && [ "$EXPANDED_CODE_SIGN_IDENTITY" != "" ]; then + echo "Signing $framework for $plugin with $EXPANDED_CODE_SIGN_IDENTITY_NAME" + /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} --timestamp=none --preserve-metadata=identifier,entitlements,flags "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/${framework}" + fi + done + done +} +copy_plugins "$BUILT_PRODUCTS_DIR" + +CARTHAGE_BUILD_DIR="${SRCROOT}/Carthage/Build" +if [ -n "${IPHONEOS_DEPLOYMENT_TARGET}" ]; then +CARTHAGE_BUILD_DIR="${CARTHAGE_BUILD_DIR}/iOS" +elif [ -n "${WATCHOS_DEPLOYMENT_TARGET}" ]; then +CARTHAGE_BUILD_DIR="${CARTHAGE_BUILD_DIR}/watchOS" +else +echo "ERROR: Unexpected deployment target type" +exit 1 +fi + +copy_plugins "$CARTHAGE_BUILD_DIR" From fb3419193395a71781359dbf689dc2d3192124d2 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 2 Sep 2019 21:50:03 -0500 Subject: [PATCH 088/132] Add syncidentifiers to NS treatments (#1089) --- Cartfile.resolved | 4 ++-- Loop/Extensions/PersistedPumpEvent.swift | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index e89047166e..2c2914b48a 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,8 +1,8 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "fe92d93c24d18ff9755ff027e9b036d3769a8a0a" github "LoopKit/G4ShareSpy" "f6ae0cf1753131135f26046ddf07e096da884b84" -github "LoopKit/LoopKit" "6f3c1e174a6d5bc952c62f4540567c13da54c2ce" +github "LoopKit/LoopKit" "b47d60e6ed202ca30e4adc63c6d237ac100e72b4" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" github "LoopKit/dexcom-share-client-swift" "cdbb1cd19cf54b37add6d6c25300445a6abe24da" github "i-schuetz/SwiftCharts" "0.6.5" -github "ps2/rileylink_ios" "38c52040600fcb877e81a2b850811e61ce0b3a42" +github "ps2/rileylink_ios" "193bbd2878119325658411c63173ae51711e40df" diff --git a/Loop/Extensions/PersistedPumpEvent.swift b/Loop/Extensions/PersistedPumpEvent.swift index 3a60554c92..95b9a63502 100644 --- a/Loop/Extensions/PersistedPumpEvent.swift +++ b/Loop/Extensions/PersistedPumpEvent.swift @@ -32,7 +32,8 @@ extension PersistedPumpEvent { unabsorbed: 0, // The pump's reported IOB isn't relevant, nor stored duration: duration, carbs: 0, - ratio: 0 + ratio: 0, + id: dose.syncIdentifier ) case .resume: return PumpResumeTreatment(timestamp: dose.startDate, enteredBy: source) @@ -45,7 +46,8 @@ extension PersistedPumpEvent { temp: .Absolute, // DoseEntry only supports .absolute types rate: dose.unitsPerHour, absolute: dose.unitsPerHour, - duration: Int(dose.endDate.timeIntervalSince(dose.startDate).minutes) + duration: Int(dose.endDate.timeIntervalSince(dose.startDate).minutes), + id: dose.syncIdentifier ) } } From c2cbca3c3730f641702ad3ed615ead6104f03e59 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 8 Sep 2019 13:16:56 -0500 Subject: [PATCH 089/132] Use non-integer time duration (#1095) --- Loop/Extensions/PersistedPumpEvent.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Loop/Extensions/PersistedPumpEvent.swift b/Loop/Extensions/PersistedPumpEvent.swift index 95b9a63502..37a9e6c1ee 100644 --- a/Loop/Extensions/PersistedPumpEvent.swift +++ b/Loop/Extensions/PersistedPumpEvent.swift @@ -46,7 +46,7 @@ extension PersistedPumpEvent { temp: .Absolute, // DoseEntry only supports .absolute types rate: dose.unitsPerHour, absolute: dose.unitsPerHour, - duration: Int(dose.endDate.timeIntervalSince(dose.startDate).minutes), + duration: dose.endDate.timeIntervalSince(dose.startDate), id: dose.syncIdentifier ) } From f53154983d7659c0dd79d42b7d8dad32776ca4e1 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 8 Sep 2019 13:21:12 -0500 Subject: [PATCH 090/132] Upload delivered volume for temp basals to Nightscout (#1090) * Upload delivered volume for temp basals to Nightscout * Update carthage revs * Update Cartfile.resolved --- Cartfile.resolved | 2 +- Loop/Extensions/PersistedPumpEvent.swift | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 2c2914b48a..11e1dd1d69 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -5,4 +5,4 @@ github "LoopKit/LoopKit" "b47d60e6ed202ca30e4adc63c6d237ac100e72b4" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" github "LoopKit/dexcom-share-client-swift" "cdbb1cd19cf54b37add6d6c25300445a6abe24da" github "i-schuetz/SwiftCharts" "0.6.5" -github "ps2/rileylink_ios" "193bbd2878119325658411c63173ae51711e40df" +github "ps2/rileylink_ios" "43789bd2d25e8ce32a0ccae31a4b85f419eb8aa9" diff --git a/Loop/Extensions/PersistedPumpEvent.swift b/Loop/Extensions/PersistedPumpEvent.swift index 37a9e6c1ee..d4f3e4a75f 100644 --- a/Loop/Extensions/PersistedPumpEvent.swift +++ b/Loop/Extensions/PersistedPumpEvent.swift @@ -47,6 +47,7 @@ extension PersistedPumpEvent { rate: dose.unitsPerHour, absolute: dose.unitsPerHour, duration: dose.endDate.timeIntervalSince(dose.startDate), + amount: dose.deliveredUnits, id: dose.syncIdentifier ) } From aadc3d94a547b995a68c4bbef3368d3abc3a8ed0 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 13 Sep 2019 17:28:15 -0500 Subject: [PATCH 091/132] Xcode11 (#1081) * Updates for Xcode 11 * Fix dark mode colors for status extension labels * Update status extension even if LoopDataManager has error * Turn off asset overrides to avoid error in Xcode 11 * Use xcode11 branches * Add comment, and remove unused code * Add NSBluetoothAlwaysUsageDescription key * Use LOOP_MARKETING_VERSION * Separate bundle ids by platform to fix App Store Connect error * Bump carthage revs --- .travis.yml | 6 +- Cartfile.resolved | 10 +- Common/Extensions/UIColor+HIG.swift | 34 +-- Common/Extensions/UIColor.swift | 54 +++- DoseMathTests/Info.plist | 2 +- Learn/Info.plist | 2 +- .../Base.lproj/MainInterface.storyboard | 9 +- .../StatusChartsManager.swift | 5 +- .../StatusViewController.swift | 17 +- Loop.xcconfig | 2 +- Loop.xcodeproj/project.pbxproj | 16 +- Loop/AppDelegate.swift | 2 - Loop/Base.lproj/LaunchScreen.storyboard | 14 +- Loop/Base.lproj/Main.storyboard | 275 +++++++++--------- Loop/Extensions/UIActivityIndicatorView.swift | 19 ++ Loop/Extensions/UIColor+Loop.swift | 36 ++- Loop/Info.plist | 2 + Loop/Managers/StatusChartsManager.swift | 4 +- .../Managers/StatusExtensionDataManager.swift | 20 +- .../CarbAbsorptionViewController.swift | 6 +- .../ChartsTableViewController.swift | 6 + .../InsulinModelSettingsViewController.swift | 2 +- .../PredictionTableViewController.swift | 2 +- .../SettingsTableViewController.swift | 151 +++++----- .../StatusTableViewController.swift | 27 +- Loop/Views/BolusProgressTableViewCell.swift | 17 +- Loop/Views/BolusProgressTableViewCell.xib | 30 +- .../PredictionSettingTableViewCell.swift | 16 + Loop/Views/TitleSubtitleTableViewCell.swift | 16 +- LoopCore/Info.plist | 2 +- LoopTests/Info.plist | 2 +- LoopUI/Charts/COBChart.swift | 2 +- LoopUI/Charts/CarbEffectChart.swift | 17 +- LoopUI/Charts/ChartsManager.swift | 13 +- LoopUI/Charts/DoseChart.swift | 2 +- LoopUI/Charts/IOBChart.swift | 2 +- LoopUI/Charts/InsulinModelChart.swift | 2 +- LoopUI/Charts/PredictedGlucoseChart.swift | 2 +- LoopUI/HUDView.xib | 22 +- ...rtPointsTouchHighlightLayerViewCache.swift | 6 +- Scripts/copy-plugins.sh | 4 +- 41 files changed, 507 insertions(+), 371 deletions(-) create mode 100644 Loop/Extensions/UIActivityIndicatorView.swift create mode 100644 Loop/Views/PredictionSettingTableViewCell.swift diff --git a/.travis.yml b/.travis.yml index 7dcfe36544..7e9c1d26d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -osx_image: xcode10.2 +osx_image: xcode11 # xcode_sdk: iphonesimulator11 # xcode_project: Loop.xcodeproj # xcode_scheme: Loop @@ -17,5 +17,5 @@ script: - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme Loop build CODE_SIGN_IDENTITY="" CODE_SIGNING_ALLOWED=NO | xcpretty - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme Learn build CODE_SIGN_IDENTITY="" CODE_SIGNING_ALLOWED=NO | xcpretty # Run the test target - - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme LoopTests -destination 'name=iPhone SE' test | xcpretty - - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme DoseMathTests -destination 'name=iPhone SE' test | xcpretty + - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme LoopTests -destination 'name=iPhone 6' test | xcpretty + - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme DoseMathTests -destination 'name=iPhone 6' test | xcpretty diff --git a/Cartfile.resolved b/Cartfile.resolved index 11e1dd1d69..b442b48576 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,8 +1,8 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" -github "LoopKit/CGMBLEKit" "fe92d93c24d18ff9755ff027e9b036d3769a8a0a" -github "LoopKit/G4ShareSpy" "f6ae0cf1753131135f26046ddf07e096da884b84" -github "LoopKit/LoopKit" "b47d60e6ed202ca30e4adc63c6d237ac100e72b4" +github "LoopKit/CGMBLEKit" "7417605dd898bf89378171941126c85d85dc642c" +github "LoopKit/G4ShareSpy" "e62d296067180c6659166272ff9cc406f470ec9e" +github "LoopKit/LoopKit" "18a5a04afd310e945ac54f8c43a44838a16503c2" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" -github "LoopKit/dexcom-share-client-swift" "cdbb1cd19cf54b37add6d6c25300445a6abe24da" +github "LoopKit/dexcom-share-client-swift" "c4f3d48e56e5b3ad786486ccd1bbc753034096d2" github "i-schuetz/SwiftCharts" "0.6.5" -github "ps2/rileylink_ios" "43789bd2d25e8ce32a0ccae31a4b85f419eb8aa9" +github "ps2/rileylink_ios" "f171e97de62ed382180c2d82aa6d24607aec964d" diff --git a/Common/Extensions/UIColor+HIG.swift b/Common/Extensions/UIColor+HIG.swift index 8c3c382ce8..79512dcf4c 100644 --- a/Common/Extensions/UIColor+HIG.swift +++ b/Common/Extensions/UIColor+HIG.swift @@ -13,40 +13,8 @@ extension UIColor { // MARK: - HIG colors // See: https://developer.apple.com/ios/human-interface-guidelines/visual-design/color/ - static func HIGTealBlueColor() -> UIColor { - return UIColor(red: 90 / 255, green: 200 / 255, blue: 250 / 255, alpha: 1) - } - - static func HIGYellowColor() -> UIColor { - return UIColor(red: 1, green: 204 / 255, blue: 0, alpha: 1) - } - - static func HIGOrangeColor() -> UIColor { - return UIColor(red: 1, green: 149 / 255, blue: 0 / 255, alpha: 1) - } - - static func HIGPinkColor() -> UIColor { - return UIColor(red: 1, green: 45 / 255, blue: 85 / 255, alpha: 1) - } - - static func HIGBlueColor() -> UIColor { - return UIColor(red: 0, green: 122 / 255, blue: 1, alpha: 1) - } - + // HIG Green has changed for iOS 13. This is the legacy color. static func HIGGreenColor() -> UIColor { return UIColor(red: 76 / 255, green: 217 / 255, blue: 100 / 255, alpha: 1) } - - static func HIGRedColor() -> UIColor { - return UIColor(red: 1, green: 59 / 255, blue: 48 / 255, alpha: 1) - } - - static func HIGPurpleColor() -> UIColor { - return UIColor(red: 88 / 255, green: 86 / 255, blue: 214 / 255, alpha: 1) - } - - static func HIGGrayColor() -> UIColor { - return UIColor(red: 142 / 255, green: 143 / 255, blue: 147 / 255, alpha: 1) - } - } diff --git a/Common/Extensions/UIColor.swift b/Common/Extensions/UIColor.swift index 452d62161c..ad6bd3cca0 100644 --- a/Common/Extensions/UIColor.swift +++ b/Common/Extensions/UIColor.swift @@ -10,21 +10,57 @@ import UIKit extension UIColor { - @nonobjc static var tintColor: UIColor? = nil + @nonobjc static let secondaryLabelColor: UIColor = { + if #available(iOSApplicationExtension 13.0, iOS 13.0, *) { + return UIColor.secondaryLabel + } else { + return UIColor.systemGray + } + }() - @nonobjc static let secondaryLabelColor = UIColor.HIGGrayColor() + @nonobjc static let cellBackgroundColor: UIColor = { + if #available(iOSApplicationExtension 13.0, iOS 13.0, *) { + return .secondarySystemBackground + } else { + return UIColor(white: 239 / 255, alpha: 1) + } + }() - @nonobjc static let cellBackgroundColor = UIColor(white: 239 / 255, alpha: 1) + @nonobjc static let IOBTintColor = UIColor.systemOrange - @nonobjc static let IOBTintColor = UIColor.HIGOrangeColor() + @nonobjc static let COBTintColor: UIColor = { + if #available(iOSApplicationExtension 13.0, iOS 13.0, *) { + return UIColor(dynamicProvider: { (traitCollection) in + // If we're in accessibility mode, return the system color + guard case .normal = traitCollection.accessibilityContrast else { + return .systemGreen + } - @nonobjc static let COBTintColor = UIColor(red: 99 / 255, green: 218 / 255, blue: 56 / 255, alpha: 1) + switch traitCollection.userInterfaceStyle { + case .unspecified, .light: + return UIColor(red: 99 / 255, green: 218 / 255, blue: 56 / 255, alpha: 1) + case .dark: + return UIColor(red: 89 / 255, green: 228 / 255, blue: 51 / 255, alpha: 1) + @unknown default: + return UIColor(red: 99 / 255, green: 218 / 255, blue: 56 / 255, alpha: 1) + } + }) + } else { + return UIColor(red: 99 / 255, green: 218 / 255, blue: 56 / 255, alpha: 1) + } + }() - @nonobjc static let agingColor = UIColor.HIGYellowColor() + @nonobjc static let agingColor = UIColor.systemYellow - @nonobjc static let staleColor = UIColor.HIGRedColor() + @nonobjc static let staleColor = UIColor.systemRed - @nonobjc static let unknownColor = UIColor(red: 198 / 255, green: 199 / 255, blue: 201 / 255, alpha: 1) + @nonobjc static let unknownColor: UIColor = { + if #available(iOSApplicationExtension 13.0, iOS 13.0, *) { + return .systemGray4 + } else { + return UIColor(red: 198 / 255, green: 199 / 255, blue: 201 / 255, alpha: 1) + } + }() - static let delete = UIColor.HIGRedColor() + static let delete = UIColor.systemRed } diff --git a/DoseMathTests/Info.plist b/DoseMathTests/Info.plist index cf0ee080ee..0be708bea3 100644 --- a/DoseMathTests/Info.plist +++ b/DoseMathTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.10.1 + $(LOOP_MARKETING_VERSION) CFBundleSignature ???? CFBundleVersion diff --git a/Learn/Info.plist b/Learn/Info.plist index f870dba92f..cd88cba1d1 100644 --- a/Learn/Info.plist +++ b/Learn/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.10.1 + $(LOOP_MARKETING_VERSION) CFBundleVersion $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS diff --git a/Loop Status Extension/Base.lproj/MainInterface.storyboard b/Loop Status Extension/Base.lproj/MainInterface.storyboard index 5b5eb74e56..3c66dec3b8 100644 --- a/Loop Status Extension/Base.lproj/MainInterface.storyboard +++ b/Loop Status Extension/Base.lproj/MainInterface.storyboard @@ -1,11 +1,9 @@ - - - - + + - + @@ -65,7 +63,6 @@ - diff --git a/Loop Status Extension/StatusChartsManager.swift b/Loop Status Extension/StatusChartsManager.swift index b0d87ad1b0..9473ef17c1 100644 --- a/Loop Status Extension/StatusChartsManager.swift +++ b/Loop Status Extension/StatusChartsManager.swift @@ -8,11 +8,12 @@ import Foundation import LoopUI import SwiftCharts +import UIKit class StatusChartsManager: ChartsManager { let predictedGlucose = PredictedGlucoseChart() - init(colors: ChartColorPalette, settings: ChartSettings) { - super.init(colors: colors, settings: settings, charts: [predictedGlucose]) + init(colors: ChartColorPalette, settings: ChartSettings, traitCollection: UITraitCollection) { + super.init(colors: colors, settings: settings, charts: [predictedGlucose], traitCollection: traitCollection) } } diff --git a/Loop Status Extension/StatusViewController.swift b/Loop Status Extension/StatusViewController.swift index 42cbad8a29..f009729b5d 100644 --- a/Loop Status Extension/StatusViewController.swift +++ b/Loop Status Extension/StatusViewController.swift @@ -48,7 +48,8 @@ class StatusViewController: UIViewController, NCWidgetProviding { settings.labelsToAxisSpacingX = 6 settings.clipInnerFrame = false return settings - }() + }(), + traitCollection: traitCollection ) charts.predictedGlucose.glucoseDisplayRange = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 100)...HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 175) @@ -90,9 +91,15 @@ class StatusViewController: UIViewController, NCWidgetProviding { super.viewDidLoad() subtitleLabel.isHidden = true - subtitleLabel.textColor = .subtitleLabelColor + if #available(iOSApplicationExtension 13.0, iOS 13.0, *) { + subtitleLabel.textColor = .secondaryLabel + insulinLabel.textColor = .secondaryLabel + } else { + subtitleLabel.textColor = .subtitleLabelColor + insulinLabel.textColor = .subtitleLabelColor + } + insulinLabel.isHidden = true - insulinLabel.textColor = .subtitleLabelColor let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openLoopApp(_:))) view.addGestureRecognizer(tapGestureRecognizer) @@ -145,6 +152,10 @@ class StatusViewController: UIViewController, NCWidgetProviding { self.glucoseChartContentView.isHidden = self.extensionContext?.widgetActiveDisplayMode != .expanded }) } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + charts.traitCollection = traitCollection + } @objc private func openLoopApp(_: Any) { if let url = Bundle.main.mainAppUrl { diff --git a/Loop.xcconfig b/Loop.xcconfig index 492e0a3ca0..e36fb844ef 100644 --- a/Loop.xcconfig +++ b/Loop.xcconfig @@ -11,7 +11,7 @@ MAIN_APP_BUNDLE_IDENTIFIER = com.${DEVELOPMENT_TEAM}.loopkit MAIN_APP_DISPLAY_NAME = Loop -LOOP_MARKETING_VERSION = 1.10.0 +LOOP_MARKETING_VERSION = 1.10.1 APPICON_NAME = AppIcon diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 3ce06dc543..73e450ec03 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -116,6 +116,7 @@ 438D42FB1D7D11A4003244B0 /* PredictionInputEffectTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 438D42FA1D7D11A4003244B0 /* PredictionInputEffectTableViewCell.swift */; }; 43947D731F529FAA00A07D31 /* GlucoseRangeSchedule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C513181E864C4E001547C7 /* GlucoseRangeSchedule.swift */; }; 4396BD50225159C0005AA4D3 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43D9002C21EB225D00AF44BF /* HealthKit.framework */; }; + 439706E622D2E84900C81566 /* PredictionSettingTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439706E522D2E84900C81566 /* PredictionSettingTableViewCell.swift */; }; 439897371CD2F80600223065 /* AnalyticsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439897361CD2F80600223065 /* AnalyticsManager.swift */; }; 439A7942211F631C0041B75F /* RootNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439A7941211F631C0041B75F /* RootNavigationController.swift */; }; 439A7944211FE22F0041B75F /* NSUserActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439A7943211FE22F0041B75F /* NSUserActivity.swift */; }; @@ -248,6 +249,7 @@ 43F64DD91D9C92C900D24DC6 /* TitleSubtitleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F64DD81D9C92C900D24DC6 /* TitleSubtitleTableViewCell.swift */; }; 43F78D261C8FC000002152D1 /* DoseMath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F78D251C8FC000002152D1 /* DoseMath.swift */; }; 43F78D4F1C914197002152D1 /* LoopKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43F78D4B1C914197002152D1 /* LoopKit.framework */; }; + 43F89CA322BDFBBD006BB54E /* UIActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F89CA222BDFBBC006BB54E /* UIActivityIndicatorView.swift */; }; 43FCBBC21E51710B00343C1B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 43776F9A1B8022E90074EA36 /* LaunchScreen.storyboard */; }; 43FCEEA9221A615B0013DD30 /* StatusChartsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43FCEEA8221A615B0013DD30 /* StatusChartsManager.swift */; }; 43FCEEAB221A61B40013DD30 /* IOBChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43FCEEAA221A61B40013DD30 /* IOBChart.swift */; }; @@ -360,6 +362,7 @@ C10B28461EA9BA5E006EA1FC /* far_future_high_bg_forecast.json in Resources */ = {isa = PBXBuildFile; fileRef = C10B28451EA9BA5E006EA1FC /* far_future_high_bg_forecast.json */; }; C11C87DE1E21EAAD00BB71D3 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F526D5E1DF2459000A04910 /* HKUnit.swift */; }; C125F31B22FE7CE200FD0545 /* copy-frameworks.sh in Resources */ = {isa = PBXBuildFile; fileRef = C125F31A22FE7CE200FD0545 /* copy-frameworks.sh */; }; + C1265BEE231BF7F700652B84 /* DefaultAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 43776F981B8022E90074EA36 /* DefaultAssets.xcassets */; }; C12F21A71DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json in Resources */ = {isa = PBXBuildFile; fileRef = C12F21A61DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json */; }; C13255D6223E7BE2008AF50C /* BolusProgressTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C1F8B1DB223862D500DD66CF /* BolusProgressTableViewCell.xib */; }; C136AA2423109CC6008A320D /* LoopPlugins.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DA84122E8E112008624C2 /* LoopPlugins.swift */; }; @@ -376,7 +379,6 @@ C1C0BE2A224C0FA000C03B4D /* SwiftCharts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4346D1EF1C781BEA00ABAFE3 /* SwiftCharts.framework */; }; C1C6591C1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json in Resources */ = {isa = PBXBuildFile; fileRef = C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */; }; C1C73F0D1DE3D0270022FC89 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1C73F0F1DE3D0270022FC89 /* InfoPlist.strings */; }; - C1D1405922FB69CC00DA6242 /* DerivedAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1D1405822FB69CC00DA6242 /* DerivedAssets.xcassets */; }; C1D1405F22FB7ADF00DA6242 /* DerivedWatchAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1D1405E22FB7ADF00DA6242 /* DerivedWatchAssets.xcassets */; }; C1D289B522F90A52003FFBD9 /* BasalDeliveryState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D289B422F90A52003FFBD9 /* BasalDeliveryState.swift */; }; C1E2773E224177C000354103 /* ClockKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1E2773D224177C000354103 /* ClockKit.framework */; }; @@ -657,6 +659,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 = ""; }; + 439706E522D2E84900C81566 /* PredictionSettingTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PredictionSettingTableViewCell.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; }; 439A7941211F631C0041B75F /* RootNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootNavigationController.swift; sourceTree = ""; }; @@ -764,6 +767,7 @@ 43F64DD81D9C92C900D24DC6 /* TitleSubtitleTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TitleSubtitleTableViewCell.swift; sourceTree = ""; }; 43F78D251C8FC000002152D1 /* DoseMath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoseMath.swift; sourceTree = ""; }; 43F78D4B1C914197002152D1 /* LoopKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LoopKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 43F89CA222BDFBBC006BB54E /* UIActivityIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIActivityIndicatorView.swift; sourceTree = ""; }; 43FCEEA8221A615B0013DD30 /* StatusChartsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusChartsManager.swift; sourceTree = ""; }; 43FCEEAA221A61B40013DD30 /* IOBChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOBChart.swift; sourceTree = ""; }; 43FCEEAC221A66780013DD30 /* DateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFormatter.swift; sourceTree = ""; }; @@ -1513,6 +1517,7 @@ 43DACFFF20A2736F000F8529 /* PersistedPumpEvent.swift */, 892A5D682230C41D008961AB /* RangeReplaceableCollection.swift */, C1FB428B217806A300FAB378 /* StateColorPalette.swift */, + 43F89CA222BDFBBC006BB54E /* UIActivityIndicatorView.swift */, 43F41C361D3BF32400C11ED6 /* UIAlertController.swift */, 43BFF0BB1E45C80600FF19A9 /* UIColor+Loop.swift */, 437CEEE31CDE5C0A003C8C80 /* UIImage.swift */, @@ -1549,6 +1554,8 @@ 43F5C2CF1B92A2ED003EB13D /* Views */ = { isa = PBXGroup; children = ( + C1F8B1D122375E4200DD66CF /* BolusProgressTableViewCell.swift */, + C1F8B1DB223862D500DD66CF /* BolusProgressTableViewCell.xib */, 43B260481ED248FB008CAA77 /* CarbEntryTableViewCell.swift */, 4346D1E61C77F5FE00ABAFE3 /* ChartTableViewCell.swift */, 431A8C3F1EC6E8AB00823B9C /* CircleMaskView.swift */, @@ -1558,8 +1565,7 @@ 43C3B6EB20B650A80026CAFA /* SettingsImageTableViewCell.swift */, 43F64DD81D9C92C900D24DC6 /* TitleSubtitleTableViewCell.swift */, 4311FB9A1F37FE1B00D4C0A7 /* TitleSubtitleTextFieldTableViewCell.swift */, - C1F8B1D122375E4200DD66CF /* BolusProgressTableViewCell.swift */, - C1F8B1DB223862D500DD66CF /* BolusProgressTableViewCell.xib */, + 439706E522D2E84900C81566 /* PredictionSettingTableViewCell.swift */, ); path = Views; sourceTree = ""; @@ -2210,13 +2216,13 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - C1D1405922FB69CC00DA6242 /* DerivedAssets.xcassets in Resources */, C13255D6223E7BE2008AF50C /* BolusProgressTableViewCell.xib in Resources */, 43FCBBC21E51710B00343C1B /* LaunchScreen.storyboard in Resources */, 7D70764F1FE06EE1004AC8EA /* InfoPlist.strings in Resources */, C125F31B22FE7CE200FD0545 /* copy-frameworks.sh in Resources */, 7D7076631FE06EE4004AC8EA /* Localizable.strings in Resources */, 43776F971B8022E90074EA36 /* Main.storyboard in Resources */, + C1265BEE231BF7F700652B84 /* DefaultAssets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2567,6 +2573,7 @@ C1FB428C217806A400FAB378 /* StateColorPalette.swift in Sources */, 4F08DE8F1E7BB871006741EA /* CollectionType+Loop.swift in Sources */, 435400341C9F878D00D5819C /* SetBolusUserInfo.swift in Sources */, + 43F89CA322BDFBBD006BB54E /* UIActivityIndicatorView.swift in Sources */, 437D9BA31D7BC977007245E8 /* PredictionTableViewController.swift in Sources */, 4344628F20A7ADD500C4BE6F /* UserDefaults+CGM.swift in Sources */, 43F41C371D3BF32400C11ED6 /* UIAlertController.swift in Sources */, @@ -2597,6 +2604,7 @@ 431A8C401EC6E8AB00823B9C /* CircleMaskView.swift in Sources */, 439897371CD2F80600223065 /* AnalyticsManager.swift in Sources */, 895FE0952201234000FCF18A /* OverrideSelectionViewController.swift in Sources */, + 439706E622D2E84900C81566 /* PredictionSettingTableViewCell.swift in Sources */, 430D85891F44037000AF2D4F /* HUDViewTableViewCell.swift in Sources */, 43A51E211EB6DBDD000736CC /* ChartsTableViewController.swift in Sources */, 438849EE1D2A1EBB003B3F23 /* MLabService.swift in Sources */, diff --git a/Loop/AppDelegate.swift b/Loop/AppDelegate.swift index 5ce8efe461..ed8d9e6333 100644 --- a/Loop/AppDelegate.swift +++ b/Loop/AppDelegate.swift @@ -25,8 +25,6 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { } func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - window?.tintColor = UIColor.tintColor - NotificationManager.authorize(delegate: self) log.info(#function) diff --git a/Loop/Base.lproj/LaunchScreen.storyboard b/Loop/Base.lproj/LaunchScreen.storyboard index 9e724c0695..57331f0bef 100644 --- a/Loop/Base.lproj/LaunchScreen.storyboard +++ b/Loop/Base.lproj/LaunchScreen.storyboard @@ -1,8 +1,9 @@ - - + + + - + @@ -12,11 +13,10 @@ - - + @@ -36,9 +36,9 @@ - + - + diff --git a/Loop/Base.lproj/Main.storyboard b/Loop/Base.lproj/Main.storyboard index aea4a9192c..364c95188e 100644 --- a/Loop/Base.lproj/Main.storyboard +++ b/Loop/Base.lproj/Main.storyboard @@ -1,11 +1,10 @@ - - - - + + - + + @@ -14,57 +13,34 @@ - + - + - - - - - - - - - - - - - + - + + @@ -94,7 +70,7 @@ @@ -118,18 +94,17 @@ - + - + - @@ -138,7 +113,7 @@ Future glucose is predicted by combining the effects of multiple inputs. Use this tool to toggle various inputs to see how they compare to the final prediction. - + - + - + @@ -296,7 +270,7 @@ - + @@ -362,39 +336,39 @@ - + - + - + - + - + @@ -402,32 +376,32 @@ - + - + - + @@ -457,55 +431,55 @@ - + - + - - + + - + - + - + - + @@ -513,7 +487,7 @@ - + @@ -571,7 +545,7 @@ - + @@ -582,7 +556,6 @@ - @@ -605,7 +578,6 @@ - - @@ -731,122 +702,132 @@ - + - + - + - + + - + - + - + + + + + + + + + + - + - + - + - + + - + - + - + @@ -858,10 +839,10 @@ @@ -869,18 +850,23 @@ - - + + + + + + + - + - + - + + + + + + + + From 0a8feea2886f6d80a4d61a2f01de3c53b43e6628 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 7 Oct 2019 09:41:19 -0500 Subject: [PATCH 105/132] bump carthage revisions --- Cartfile.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 0780e903e5..908fc64d14 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -5,4 +5,4 @@ github "LoopKit/LoopKit" "18a5a04afd310e945ac54f8c43a44838a16503c2" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" github "LoopKit/dexcom-share-client-swift" "c4f3d48e56e5b3ad786486ccd1bbc753034096d2" github "i-schuetz/SwiftCharts" "0.6.5" -github "ps2/rileylink_ios" "4bd84e45f24ae97f50f701443f056439615f5080" +github "ps2/rileylink_ios" "4b756c126317320209474f34e909ecf254d8a5b9" From 0ed6a639162f93c39b510e8889e6bdc9bef4118c Mon Sep 17 00:00:00 2001 From: katie disimone Date: Sun, 13 Oct 2019 06:42:28 -0700 Subject: [PATCH 106/132] hide the saved API secret by default in NS services display (#1142) --- Loop/Models/ServiceAuthentication/NightscoutService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Loop/Models/ServiceAuthentication/NightscoutService.swift b/Loop/Models/ServiceAuthentication/NightscoutService.swift index 140fa044de..af594b7dc3 100644 --- a/Loop/Models/ServiceAuthentication/NightscoutService.swift +++ b/Loop/Models/ServiceAuthentication/NightscoutService.swift @@ -36,7 +36,7 @@ class NightscoutService: ServiceAuthenticationUI { ServiceCredential( title: NSLocalizedString("API Secret", comment: "The title of the nightscout API secret credential"), placeholder: nil, - isSecret: false, + isSecret: true, keyboardType: .asciiCapable ) ] From af58beb9647b826a94b4a2fd006eac21912c4bf8 Mon Sep 17 00:00:00 2001 From: katie disimone Date: Sun, 13 Oct 2019 06:53:42 -0700 Subject: [PATCH 107/132] display - for HUD CGM when value is >15 min old (#1140) --- LoopUI/Views/GlucoseHUDView.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/LoopUI/Views/GlucoseHUDView.swift b/LoopUI/Views/GlucoseHUDView.swift index 8d18968008..c5380db1c9 100644 --- a/LoopUI/Views/GlucoseHUDView.swift +++ b/LoopUI/Views/GlucoseHUDView.swift @@ -96,16 +96,22 @@ public final class GlucoseHUDView: BaseHUDView { let time = timeFormatter.string(from: glucoseStartDate) caption?.text = time + + let sensorDataCurrent = glucoseStartDate.timeIntervalSinceNow > TimeInterval(minutes: -15) let numberFormatter = NumberFormatter.glucoseFormatter(for: unit) if let valueString = numberFormatter.string(from: glucoseQuantity) { - glucoseLabel.text = valueString + if sensorDataCurrent { + glucoseLabel.text = valueString + } else { + glucoseLabel.text = "-" + } accessibilityStrings.append(String(format: LocalizedString("%1$@ at %2$@", comment: "Accessbility format value describing glucose: (1: glucose number)(2: glucose time)"), valueString, time)) } var unitStrings = [unit.localizedShortUnitString] - if let trend = sensor?.trendType { + if let trend = sensor?.trendType, sensorDataCurrent { unitStrings.append(trend.symbol) accessibilityStrings.append(trend.localizedDescription) } From 66c8773fc5b21ad81e0eb1e21b475c0ef1193f44 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 13 Oct 2019 08:58:10 -0500 Subject: [PATCH 108/132] Fix issue with PumpManager ble update coming in right after cgm update arrives (#1096) --- Loop/Managers/DeviceDataManager.swift | 74 +++++++++++++++------------ 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index ca332379b1..73f973525f 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -33,7 +33,7 @@ final class DeviceDataManager { /// Should be accessed only on the main queue private(set) var lastError: (date: Date, error: Error)? - /// The last time a BLE heartbeat was received by the pump manager + /// The last time a BLE heartbeat was received and acted upon. private var lastBLEDrivenUpdate = Date.distantPast // MARK: - CGM @@ -155,6 +155,40 @@ final class DeviceDataManager { return Manager.init(rawState: rawState) as? PumpManagerUI } + + private func processCGMResult(_ manager: CGMManager, result: CGMResult) { + switch result { + case .newData(let values): + log.default("CGMManager:\(type(of: manager)) did update with \(values.count) values") + + loopManager.addGlucose(values) { result in + if manager.shouldSyncToRemoteService { + switch result { + case .success(let values): + self.nightscoutDataManager.uploadGlucose(values, sensorState: manager.sensorState) + case .failure: + break + } + } + + self.log.default("Asserting current pump data") + self.pumpManager?.assertCurrentPumpData() + } + case .noData: + log.default("CGMManager:\(type(of: manager)) did update with no data") + + pumpManager?.assertCurrentPumpData() + case .error(let error): + log.default("CGMManager:\(type(of: manager)) did update with error: \(error)") + + self.setLastError(error: error) + log.default("Asserting current pump data") + pumpManager?.assertCurrentPumpData() + } + + updatePumpManagerBLEHeartbeatPreference() + } + } @@ -269,36 +303,8 @@ extension DeviceDataManager: CGMManagerDelegate { func cgmManager(_ manager: CGMManager, didUpdateWith result: CGMResult) { dispatchPrecondition(condition: .onQueue(queue)) - switch result { - case .newData(let values): - log.default("CGMManager:\(type(of: manager)) did update with \(values.count) values") - - loopManager.addGlucose(values) { result in - if manager.shouldSyncToRemoteService { - switch result { - case .success(let values): - self.nightscoutDataManager.uploadGlucose(values, sensorState: manager.sensorState) - case .failure: - break - } - } - - self.log.default("Asserting current pump data") - self.pumpManager?.assertCurrentPumpData() - } - case .noData: - log.default("CGMManager:\(type(of: manager)) did update with no data") - - pumpManager?.assertCurrentPumpData() - case .error(let error): - log.default("CGMManager:\(type(of: manager)) did update with error: \(error)") - - self.setLastError(error: error) - log.default("Asserting current pump data") - pumpManager?.assertCurrentPumpData() - } - - updatePumpManagerBLEHeartbeatPreference() + lastBLEDrivenUpdate = Date() + processCGMResult(manager, result: result); } func startDateToFilterNewData(for manager: CGMManager) -> Date? { @@ -346,12 +352,12 @@ extension DeviceDataManager: PumpManagerDelegate { bleHeartbeatUpdateInterval = .minutes(1) case let interval?: // If we looped successfully less than 5 minutes ago, ignore the heartbeat. - log.default("PumpManager:\(type(of: pumpManager)) ignoring heartbeat. Last loop completed \(interval.minutes) minutes ago") + log.default("PumpManager:\(type(of: pumpManager)) ignoring pumpManager heartbeat. Last loop completed \(-interval.minutes) minutes ago") return } guard lastBLEDrivenUpdate.timeIntervalSinceNow <= -bleHeartbeatUpdateInterval else { - log.default("PumpManager:\(type(of: pumpManager)) ignoring heartbeat. Last update \(lastBLEDrivenUpdate)") + log.default("PumpManager:\(type(of: pumpManager)) ignoring pumpManager heartbeat. Last ble update \(lastBLEDrivenUpdate)") return } lastBLEDrivenUpdate = Date() @@ -363,7 +369,7 @@ extension DeviceDataManager: PumpManagerDelegate { if let manager = self.cgmManager { self.queue.async { - self.cgmManager(manager, didUpdateWith: result) + self.processCGMResult(manager, result: result) } } } From 31392623d99b34d4a40f133c151df24f26911b25 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 13 Oct 2019 09:28:16 -0500 Subject: [PATCH 109/132] New strategy for asset overrides. Fixes popup warnings (#1145) * New strategy for asset overrides. Fixes popup warnings * Add DefaultAssets.xcassets back to WatchApp target --- .gitignore | 4 ++++ Loop.xcodeproj/project.pbxproj | 16 ++++++++++------ Loop/DerivedAssets.xcassets/Contents.json | 6 ++++++ Scripts/build-derived-assets.sh | 5 ++--- Scripts/build-derived-watch-assets.sh | 10 ++++------ WatchApp/DerivedAssets.xcassets/Contents.json | 6 ++++++ 6 files changed, 32 insertions(+), 15 deletions(-) create mode 100644 Loop/DerivedAssets.xcassets/Contents.json create mode 100644 WatchApp/DerivedAssets.xcassets/Contents.json diff --git a/.gitignore b/.gitignore index d8ac3c4b53..25319f7984 100644 --- a/.gitignore +++ b/.gitignore @@ -70,3 +70,7 @@ RemoteSettings.plist # Framework development Loop.xcworkspace + +# Avoid checking in built assets +Loop/DerivedAssets.xcassets/ +WatchApp/DerivedAssets.xcassets/ diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 642937a34e..d37871dcfb 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -378,10 +378,12 @@ C17824A61E1AF91F00D9D25C /* BolusRecommendation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17824A41E1AD4D100D9D25C /* BolusRecommendation.swift */; }; C1814B86225E507C008D2D8E /* Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1814B85225E507C008D2D8E /* Sequence.swift */; }; C18C8C511D5A351900E043FB /* NightscoutDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */; }; + C1A3EED2235233E1007672E3 /* DerivedAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1A3EED1235233E1007672E3 /* DerivedAssets.xcassets */; }; + C1A3EED423523551007672E3 /* DerivedAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1A3EED323523551007672E3 /* DerivedAssets.xcassets */; }; + C1A3EED523535FFF007672E3 /* DefaultAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 894F71E11FFEC4D8007D365C /* DefaultAssets.xcassets */; }; C1C0BE2A224C0FA000C03B4D /* SwiftCharts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4346D1EF1C781BEA00ABAFE3 /* SwiftCharts.framework */; }; C1C6591C1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json in Resources */ = {isa = PBXBuildFile; fileRef = C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */; }; C1C73F0D1DE3D0270022FC89 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1C73F0F1DE3D0270022FC89 /* InfoPlist.strings */; }; - C1D1405F22FB7ADF00DA6242 /* DerivedWatchAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1D1405E22FB7ADF00DA6242 /* DerivedWatchAssets.xcassets */; }; C1D289B522F90A52003FFBD9 /* BasalDeliveryState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D289B422F90A52003FFBD9 /* BasalDeliveryState.swift */; }; C1E2773E224177C000354103 /* ClockKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1E2773D224177C000354103 /* ClockKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; C1E2774822433D7A00354103 /* MKRingProgressView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1E2774722433D7A00354103 /* MKRingProgressView.framework */; }; @@ -1033,9 +1035,9 @@ C18A491422FCC22900FDA733 /* build-derived-watch-assets.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "build-derived-watch-assets.sh"; sourceTree = ""; }; C18A491522FCC22900FDA733 /* copy-plugins.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "copy-plugins.sh"; sourceTree = ""; }; C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutDataManager.swift; sourceTree = ""; }; + C1A3EED1235233E1007672E3 /* DerivedAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DerivedAssets.xcassets; sourceTree = ""; }; + C1A3EED323523551007672E3 /* DerivedAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DerivedAssets.xcassets; sourceTree = ""; }; C1C6591B1E1B1FDA0025CC58 /* recommend_temp_basal_dropping_then_rising.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_temp_basal_dropping_then_rising.json; sourceTree = ""; }; - C1D1405822FB69CC00DA6242 /* DerivedAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DerivedAssets.xcassets; sourceTree = BUILT_PRODUCTS_DIR; }; - C1D1405E22FB7ADF00DA6242 /* DerivedWatchAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DerivedWatchAssets.xcassets; sourceTree = BUILT_PRODUCTS_DIR; }; C1D197FE232CF92D0096D646 /* capture-build-details.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "capture-build-details.sh"; sourceTree = ""; }; C1D289B422F90A52003FFBD9 /* BasalDeliveryState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasalDeliveryState.swift; sourceTree = ""; }; C1E2773D224177C000354103 /* ClockKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ClockKit.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk/System/Library/Frameworks/ClockKit.framework; sourceTree = DEVELOPER_DIR; }; @@ -1289,7 +1291,7 @@ 43F5C2D41B92A4A6003EB13D /* Info.plist */, 43776F8F1B8022E90074EA36 /* AppDelegate.swift */, 43776F981B8022E90074EA36 /* DefaultAssets.xcassets */, - C1D1405822FB69CC00DA6242 /* DerivedAssets.xcassets */, + C1A3EED1235233E1007672E3 /* DerivedAssets.xcassets */, 43776F9A1B8022E90074EA36 /* LaunchScreen.storyboard */, 43776F951B8022E90074EA36 /* Main.storyboard */, 43E344A01B9E144300C85C07 /* Extensions */, @@ -1324,7 +1326,7 @@ isa = PBXGroup; children = ( 894F71E11FFEC4D8007D365C /* DefaultAssets.xcassets */, - C1D1405E22FB7ADF00DA6242 /* DerivedWatchAssets.xcassets */, + C1A3EED323523551007672E3 /* DerivedAssets.xcassets */, C1C73F0F1DE3D0270022FC89 /* InfoPlist.strings */, 43F5C2D61B92A4DC003EB13D /* Info.plist */, 43A943741B926B7B0051FA24 /* Interface.storyboard */, @@ -2230,6 +2232,7 @@ C13255D6223E7BE2008AF50C /* BolusProgressTableViewCell.xib in Resources */, 43FCBBC21E51710B00343C1B /* LaunchScreen.storyboard in Resources */, 7D70764F1FE06EE1004AC8EA /* InfoPlist.strings in Resources */, + C1A3EED2235233E1007672E3 /* DerivedAssets.xcassets in Resources */, 7D7076631FE06EE4004AC8EA /* Localizable.strings in Resources */, 43776F971B8022E90074EA36 /* Main.storyboard in Resources */, C1265BEE231BF7F700652B84 /* DefaultAssets.xcassets in Resources */, @@ -2240,9 +2243,10 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - C1D1405F22FB7ADF00DA6242 /* DerivedWatchAssets.xcassets in Resources */, C1C73F0D1DE3D0270022FC89 /* InfoPlist.strings in Resources */, 43A943761B926B7B0051FA24 /* Interface.storyboard in Resources */, + C1A3EED523535FFF007672E3 /* DefaultAssets.xcassets in Resources */, + C1A3EED423523551007672E3 /* DerivedAssets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Loop/DerivedAssets.xcassets/Contents.json b/Loop/DerivedAssets.xcassets/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/Loop/DerivedAssets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Scripts/build-derived-assets.sh b/Scripts/build-derived-assets.sh index cd0f985003..33e82d8cf3 100755 --- a/Scripts/build-derived-assets.sh +++ b/Scripts/build-derived-assets.sh @@ -8,10 +8,9 @@ echo "Building DerivedAssets.xcassets" -output="${BUILT_PRODUCTS_DIR}/DerivedAssets.xcassets" -cp -a "${PROJECT_DIR}/Loop/DefaultAssets.xcassets/." "$output" +output="${PROJECT_DIR}/Loop/DerivedAssets.xcassets" -override="${PROJECT_DIR}/../AssetOverrides.xcassets/." +override="${PROJECT_DIR}/../AdditionalAssets.xcassets/." if [ -d $override ]; then echo "Adding asset overrides to DerivedAssets.xcassets" diff --git a/Scripts/build-derived-watch-assets.sh b/Scripts/build-derived-watch-assets.sh index 217b2fc252..b1ca63156b 100755 --- a/Scripts/build-derived-watch-assets.sh +++ b/Scripts/build-derived-watch-assets.sh @@ -5,15 +5,13 @@ # # Copyright © 2019 LoopKit Authors. All rights reserved. -echo "Building WatchDerivedAssets.xcassets" +echo "Building DerivedAssets.xcassets for Watch App" -watch_output="${BUILT_PRODUCTS_DIR}/DerivedWatchAssets.xcassets" -cp -a "${PROJECT_DIR}/WatchApp/DefaultAssets.xcassets/." "$watch_output" +watch_output="${PROJECT_DIR}/WatchApp/DerivedAssets.xcassets" - -watch_override="${PROJECT_DIR}/../WatchAssetOverrides.xcassets/." +watch_override="${PROJECT_DIR}/../AdditionalWatchAssets.xcassets/." if [ -d $watch_override ]; then - echo "Adding asset overrides to DerivedWatchAssets.xcassets" + echo "Adding asset overrides to WatchApp/DerivedAssets.xcassets" cp -a "$watch_override" "$watch_output" fi diff --git a/WatchApp/DerivedAssets.xcassets/Contents.json b/WatchApp/DerivedAssets.xcassets/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/WatchApp/DerivedAssets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file From 14191adeef8d5f517127d5f15f8219579309be5d Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 13 Oct 2019 11:00:22 -0500 Subject: [PATCH 110/132] Remote overrides (#1131) * Allow overrides to be enabled via APNs * Update to NS sent notifications * Cleanup prints * Query override history for NS upload * Bump submodule revs * Handle indefinite duration override treatment uploads * Use separate representation for indefinite duration treatments * Bump cartfile rev * Send any override changes when uploading loop status * Bump carthage revisions * Bump carthage revision * Fix typo, and log device token * Carthage dependency revisions --- Cartfile.resolved | 4 +- Loop.xcodeproj/project.pbxproj | 4 + Loop/AppDelegate.swift | 35 +++++- Loop/Info.plist | 1 + Loop/Loop.entitlements | 2 + Loop/Managers/AnalyticsManager.swift | 4 +- Loop/Managers/DeviceDataManager.swift | 18 +++ Loop/Managers/LoopDataManager.swift | 21 ++-- Loop/Managers/NightscoutDataManager.swift | 118 +++++++++++++++--- Loop/Managers/NotificationManager.swift | 11 +- Loop/Models/RemoteCommand.swift | 44 +++++++ LoopCore/LoopSettings.swift | 14 ++- .../Controllers/ActionHUDController.swift | 2 +- .../Scenes/GlucoseChartScene.swift | 9 +- 14 files changed, 254 insertions(+), 33 deletions(-) create mode 100644 Loop/Models/RemoteCommand.swift diff --git a/Cartfile.resolved b/Cartfile.resolved index 908fc64d14..aa6357ee5f 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,8 +1,8 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "7417605dd898bf89378171941126c85d85dc642c" github "LoopKit/G4ShareSpy" "e62d296067180c6659166272ff9cc406f470ec9e" -github "LoopKit/LoopKit" "18a5a04afd310e945ac54f8c43a44838a16503c2" +github "LoopKit/LoopKit" "a08405ec7a4e38fa96e39ae079114db30f393a2a" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" github "LoopKit/dexcom-share-client-swift" "c4f3d48e56e5b3ad786486ccd1bbc753034096d2" github "i-schuetz/SwiftCharts" "0.6.5" -github "ps2/rileylink_ios" "4b756c126317320209474f34e909ecf254d8a5b9" +github "ps2/rileylink_ios" "51d6a20cc63c0c0b2071a4a78b4065e9a11ba81f" diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index d37871dcfb..75190c3519 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -370,6 +370,7 @@ C136AA2423109CC6008A320D /* LoopPlugins.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DA84122E8E112008624C2 /* LoopPlugins.swift */; }; C13BAD941E8009B000050CB5 /* NumberFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43BFF0B31E45C1BE00FF19A9 /* NumberFormatter.swift */; }; C15713821DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */; }; + C165B8CE23302C5D0004112E /* RemoteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C165B8CD23302C5D0004112E /* RemoteCommand.swift */; }; C16DA84222E8E112008624C2 /* LoopPlugins.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DA84122E8E112008624C2 /* LoopPlugins.swift */; }; C178249A1E1999FA00D9D25C /* CaseCountable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17824991E1999FA00D9D25C /* CaseCountable.swift */; }; C17824A01E19CF9800D9D25C /* GlucoseThresholdTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C178249F1E19CF9800D9D25C /* GlucoseThresholdTableViewController.swift */; }; @@ -1024,6 +1025,7 @@ C12CB9B823106A6300F84978 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Intents.strings; sourceTree = ""; }; C12F21A61DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommend_temp_basal_very_low_end_in_range.json; sourceTree = ""; }; C15713811DAC6983005BC4D2 /* MealBolusNightscoutTreatment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MealBolusNightscoutTreatment.swift; sourceTree = ""; }; + C165B8CD23302C5D0004112E /* RemoteCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteCommand.swift; sourceTree = ""; }; C16DA84122E8E112008624C2 /* LoopPlugins.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopPlugins.swift; sourceTree = ""; }; C17824991E1999FA00D9D25C /* CaseCountable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaseCountable.swift; sourceTree = ""; }; C178249F1E19CF9800D9D25C /* GlucoseThresholdTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseThresholdTableViewController.swift; sourceTree = ""; }; @@ -1239,6 +1241,7 @@ 438D42F81D7C88BC003244B0 /* PredictionInputEffect.swift */, 43441A9B1EDB34810087958C /* StatusExtensionContext+LoopKit.swift */, 4328E0311CFC068900E199AA /* WatchContext+LoopKit.swift */, + C165B8CD23302C5D0004112E /* RemoteCommand.swift */, ); path = Models; sourceTree = ""; @@ -2604,6 +2607,7 @@ 434FF1EE1CF27EEF000DB779 /* UITableViewCell.swift in Sources */, 439BED2A1E76093C00B0AED5 /* CGMManager.swift in Sources */, C18C8C511D5A351900E043FB /* NightscoutDataManager.swift in Sources */, + C165B8CE23302C5D0004112E /* RemoteCommand.swift in Sources */, 438849EA1D297CB6003B3F23 /* NightscoutService.swift in Sources */, 438172D91F4E9E37003C3328 /* NewPumpEvent.swift in Sources */, 4389916B1E91B689000EEF90 /* ChartSettings+Loop.swift in Sources */, diff --git a/Loop/AppDelegate.swift b/Loop/AppDelegate.swift index ed8d9e6333..df2430f732 100644 --- a/Loop/AppDelegate.swift +++ b/Loop/AppDelegate.swift @@ -26,12 +26,18 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { NotificationManager.authorize(delegate: self) - + log.info(#function) AnalyticsManager.shared.application(application, didFinishLaunchingWithOptions: launchOptions) rootViewController.rootViewController.deviceManager = deviceManager + + let notificationOption = launchOptions?[.remoteNotification] + + if let notification = notificationOption as? [String: AnyObject] { + deviceManager.handleRemoteNotification(notification) + } return true } @@ -74,6 +80,32 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { return false } } + + // MARK: - Remote notifications + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) } + let token = tokenParts.joined() + log.default("RemoteNotifications device token: \(token)") + deviceManager.loopManager.settings.deviceToken = deviceToken + } + + func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { + log.error("Failed to register: \(error)") + } + + func application(_ application: UIApplication, + didReceiveRemoteNotification userInfo: [AnyHashable : Any], + fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void + ) { + guard let notification = userInfo as? [String: AnyObject] else { + completionHandler(.failed) + return + } + + deviceManager.handleRemoteNotification(notification) + completionHandler(.noData) + } + } @@ -102,4 +134,5 @@ extension AppDelegate: UNUserNotificationCenterDelegate { func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { completionHandler([.badge, .sound, .alert]) } + } diff --git a/Loop/Info.plist b/Loop/Info.plist index 7c55571e92..f8907e07d0 100644 --- a/Loop/Info.plist +++ b/Loop/Info.plist @@ -62,6 +62,7 @@ UIBackgroundModes bluetooth-central + remote-notification UILaunchStoryboardName LaunchScreen diff --git a/Loop/Loop.entitlements b/Loop/Loop.entitlements index d590778126..a6fd580f0b 100644 --- a/Loop/Loop.entitlements +++ b/Loop/Loop.entitlements @@ -2,6 +2,8 @@ + aps-environment + development com.apple.developer.healthkit com.apple.developer.healthkit.access diff --git a/Loop/Managers/AnalyticsManager.swift b/Loop/Managers/AnalyticsManager.swift index 5dca68162f..78d94d8966 100644 --- a/Loop/Managers/AnalyticsManager.swift +++ b/Loop/Managers/AnalyticsManager.swift @@ -150,8 +150,8 @@ final class AnalyticsManager: IdentifiableClass { logEvent("CGM Fetch", outOfSession: true) } - func loopDidSucceed() { - logEvent("Loop success", outOfSession: true) + func loopDidSucceed(_ duration: TimeInterval) { + logEvent("Loop success", withProperties: ["duration": duration], outOfSession: true) } func loopDidError() { diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index 73f973525f..67a9174a94 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -653,3 +653,21 @@ extension Notification.Name { static let PumpEventsAdded = Notification.Name(rawValue: "com.loopKit.notification.PumpEventsAdded") } +// MARK: - Remote Notification Handling +extension DeviceDataManager { + func handleRemoteNotification(_ notification: [String: AnyObject]) { + + if let command = RemoteCommand(notification: notification, allowedPresets: loopManager.settings.overridePresets) { + switch command { + case .temporaryScheduleOverride(let override): + log.default("Enacting remote temporary override: \(override)") + loopManager.settings.scheduleOverride = override + case .cancelTemporaryOverride: + log.default("Canceling temporary override from remote command") + loopManager.settings.scheduleOverride = nil + } + } else { + log.info("Unhandled remote notification: \(notification)") + } + } +} diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index a7f02eaae9..aac8e72500 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -216,6 +216,8 @@ final class LoopDataManager { private let lockedBasalDeliveryState: Locked fileprivate var lastRequestedBolus: DoseEntry? + + private var lastLoopStarted: Date? /// The last date at which a loop completed, from prediction to dose (if dosing is enabled) var lastLoopCompleted: Date? { @@ -224,11 +226,6 @@ final class LoopDataManager { } set { lockedLastLoopCompleted.value = newValue - - NotificationManager.clearLoopNotRunningNotifications() - NotificationManager.scheduleLoopNotRunningNotifications() - AnalyticsManager.shared.loopDidSucceed() - NotificationCenter.default.post(name: .LoopCompleted, object: self) } } private let lockedLastLoopCompleted: Locked @@ -269,6 +266,15 @@ final class LoopDataManager { backgroundTask = .invalid } } + + private func loopDidComplete(date: Date, duration: TimeInterval) { + lastLoopCompleted = date + NotificationManager.clearLoopNotRunningNotifications() + NotificationManager.scheduleLoopNotRunningNotifications() + AnalyticsManager.shared.loopDidSucceed(duration) + NotificationCenter.default.post(name: .LoopCompleted, object: self) + + } } // MARK: Background task management @@ -635,6 +641,7 @@ extension LoopDataManager { NotificationCenter.default.post(name: .LoopRunning, object: self) self.lastLoopError = nil + let startDate = Date() do { try self.update() @@ -646,7 +653,7 @@ extension LoopDataManager { if let error = error { self.logger.error(error) } else { - self.lastLoopCompleted = Date() + self.loopDidComplete(date: Date(), duration: -startDate.timeIntervalSinceNow) } self.logger.default("Loop ended") self.notify(forChange: .tempBasal) @@ -655,7 +662,7 @@ extension LoopDataManager { // Delay the notification until we know the result of the temp basal return } else { - self.lastLoopCompleted = Date() + self.loopDidComplete(date: Date(), duration: -startDate.timeIntervalSinceNow) } } catch let error { self.lastLoopError = error diff --git a/Loop/Managers/NightscoutDataManager.swift b/Loop/Managers/NightscoutDataManager.swift index 320f1a174a..ea8899ea15 100644 --- a/Loop/Managers/NightscoutDataManager.swift +++ b/Loop/Managers/NightscoutDataManager.swift @@ -27,6 +27,9 @@ final class NightscoutDataManager { // Last time settings were updated var lastSettingsUpdate: Date = .distantPast + + // Override history query anchor + var overrideHistoryQueryAnchor: TemporaryScheduleOverrideHistory.QueryAnchor? init(deviceDataManager: DeviceDataManager) { self.deviceManager = deviceDataManager @@ -47,12 +50,44 @@ final class NightscoutDataManager { lastSettingsUpdate = Date() uploadSettings() + uploadOverridesUpdates() + } + + private func uploadOverridesUpdates() { + guard let uploader = deviceManager.remoteDataManager.nightscoutService.uploader else { + return + } + + let (overrides, deletedOverrides, newAnchor) = deviceManager.loopManager.overrideHistory.queryByAnchor(overrideHistoryQueryAnchor) + + let updates = overrides.map { OverrideTreatment(override: $0) } + + let deletions = deletedOverrides.map { $0.syncIdentifier.uuidString } + uploader.deleteTreatmentsByClientId(deletions, completionHandler: { (error) in + if let error = error { + self.log.error("Overrides deletions failed to delete %{public}@: %{public}@", String(describing: deletions), String(describing: error)) + } else { + if deletions.count > 0 { + self.log.debug("Deleted ids: %@", deletions) + } + uploader.upload(updates) { (result) in + switch result { + case .failure(let error): + self.log.error("Failed to upload overrides %{public}@: %{public}@", String(describing: updates.map {$0.dictionaryRepresentation}), String(describing: error)) + case .success: + self.log.debug("Uploaded overrides %@", String(describing: updates.map {$0.dictionaryRepresentation})) + self.overrideHistoryQueryAnchor = newAnchor + } + } + } + }) } private func uploadSettings() { + let settings = deviceManager.loopManager.settings + guard let uploader = deviceManager.remoteDataManager.nightscoutService.uploader, - let settings = UserDefaults.appGroup?.loopSettings, let basalRateSchedule = UserDefaults.appGroup?.basalRateSchedule, let insulinModelSettings = UserDefaults.appGroup?.insulinModelSettings, let carbRatioSchedule = UserDefaults.appGroup?.carbRatioSchedule, @@ -63,7 +98,7 @@ final class NightscoutDataManager { log.default("Not uploading due to incomplete configuration") return } - + let targetLowItems = correctionSchedule.items.map { (item) -> ProfileSet.ScheduleItem in return ProfileSet.ScheduleItem(offset: item.startTime, value: item.value.minValue) } @@ -90,7 +125,9 @@ final class NightscoutDataManager { minimumBGGuard: settings.suspendThreshold?.quantity.doubleValue(for: preferredUnit), preMealTargetRange: nsPreMealTargetRange, maximumBasalRatePerHour: settings.maximumBasalRatePerHour, - maximumBolus: settings.maximumBolus) + maximumBolus: settings.maximumBolus, + deviceToken: settings.deviceToken, + bundleIdentifier: Bundle.main.bundleIdentifier) let profile = ProfileSet.Profile( timezone: basalRateSchedule.timeZone, @@ -170,6 +207,8 @@ final class NightscoutDataManager { if self.lastSettingsUpdate > self.lastSettingsUpload { self.uploadSettings() } + + self.uploadOverridesUpdates() } } } @@ -281,8 +320,8 @@ final class NightscoutDataManager { } else { pumpStatus = nil } + //add overrideStatus - let overrideStatus: NightscoutUploadKit.OverrideStatus? let settings = deviceManager.loopManager.settings let unit: HKUnit = settings.glucoseTargetRangeSchedule?.unit ?? HKUnit.milligramsPerDeciliter @@ -326,7 +365,6 @@ final class NightscoutDataManager { } log.default("Uploading loop status") upload(pumpStatus: pumpStatus, loopStatus: loopStatus, deviceName: nil, firmwareVersion: nil, uploaderStatus: getUploaderStatus(), overrideStatus: overrideStatus) - } private func getUploaderStatus() -> UploaderStatus { @@ -410,15 +448,16 @@ private extension Array where Element == RepeatingScheduleValue { } } +// Likely this will be deprecated, in favor of override history uploading to NS treatments private extension LoopKit.TemporaryScheduleOverride { func nsScheduleOverride(for unit: HKUnit) -> NightscoutUploadKit.TemporaryScheduleOverride { - let nsTargetRange: ClosedRange? + let nsCorrectionRange: ClosedRange? if let targetRange = settings.targetRange { - nsTargetRange = ClosedRange(uncheckedBounds: ( + nsCorrectionRange = ClosedRange(uncheckedBounds: ( lower: targetRange.lowerBound.doubleValue(for: unit), upper: targetRange.upperBound.doubleValue(for: unit))) } else { - nsTargetRange = nil + nsCorrectionRange = nil } let nsDuration: TimeInterval @@ -448,23 +487,23 @@ private extension LoopKit.TemporaryScheduleOverride { } return NightscoutUploadKit.TemporaryScheduleOverride( - targetRange: nsTargetRange, + duration: nsDuration, + targetRange: nsCorrectionRange, insulinNeedsScaleFactor: settings.insulinNeedsScaleFactor, symbol: symbol, - duration: nsDuration, name: name) } } private extension LoopKit.TemporaryScheduleOverridePreset { func nsScheduleOverride(for unit: HKUnit) -> NightscoutUploadKit.TemporaryScheduleOverride { - let nsTargetRange: ClosedRange? + let nsCorrectionRange: ClosedRange? if let targetRange = settings.targetRange { - nsTargetRange = ClosedRange(uncheckedBounds: ( + nsCorrectionRange = ClosedRange(uncheckedBounds: ( lower: targetRange.lowerBound.doubleValue(for: unit), upper: targetRange.upperBound.doubleValue(for: unit))) } else { - nsTargetRange = nil + nsCorrectionRange = nil } let nsDuration: TimeInterval @@ -476,10 +515,59 @@ private extension LoopKit.TemporaryScheduleOverridePreset { } return NightscoutUploadKit.TemporaryScheduleOverride( - targetRange: nsTargetRange, + duration: nsDuration, + targetRange: nsCorrectionRange, insulinNeedsScaleFactor: settings.insulinNeedsScaleFactor, symbol: self.symbol, - duration: nsDuration, name: self.name) } } + +private extension OverrideTreatment { + convenience init(override: LoopKit.TemporaryScheduleOverride) { + + // NS Treatments should be in mg/dL + let unit: HKUnit = .milligramsPerDeciliter + + let nsTargetRange: ClosedRange? + if let targetRange = override.settings.targetRange { + nsTargetRange = ClosedRange(uncheckedBounds: ( + lower: targetRange.lowerBound.doubleValue(for: unit), + upper: targetRange.upperBound.doubleValue(for: unit))) + } else { + nsTargetRange = nil + } + + let reason: String + switch override.context { + case .custom: + reason = NSLocalizedString("Custom Override", comment: "Name of custom override") + case .legacyWorkout: + reason = NSLocalizedString("Workout", comment: "Name of legacy workout override") + case .preMeal: + reason = NSLocalizedString("Pre-Meal", comment: "Name of pre-meal workout override") + case .preset(let preset): + reason = preset.symbol + " " + preset.name + } + + let remoteAddress: String? + let enteredBy: String + if case .remote(let address) = override.enactTrigger { + remoteAddress = address + enteredBy = "Loop (via remote command)" + } else { + remoteAddress = nil + enteredBy = "Loop" + } + + let duration: OverrideTreatment.Duration + switch override.duration { + case .finite(let time): + duration = .finite(time) + case .indefinite: + duration = .indefinite + } + + self.init(startDate: override.startDate, enteredBy: enteredBy, reason: reason, duration: duration, correctionRange: nsTargetRange, insulinNeedsScaleFactor: override.settings.insulinNeedsScaleFactor, remoteAddress:remoteAddress, id: override.syncIdentifier.uuidString) + } +} diff --git a/Loop/Managers/NotificationManager.swift b/Loop/Managers/NotificationManager.swift index 8f93f322ec..967be1cb1d 100644 --- a/Loop/Managers/NotificationManager.swift +++ b/Loop/Managers/NotificationManager.swift @@ -44,9 +44,18 @@ struct NotificationManager { let center = UNUserNotificationCenter.current() center.delegate = delegate - center.requestAuthorization(options: [.badge, .sound, .alert], completionHandler: { _, _ in }) + center.requestAuthorization(options: [.badge, .sound, .alert]) { (granted, error) in + guard granted else { return } + UNUserNotificationCenter.current().getNotificationSettings { settings in + guard settings.authorizationStatus == .authorized else { return } + DispatchQueue.main.async { + UIApplication.shared.registerForRemoteNotifications() + } + } + } center.setNotificationCategories(notificationCategories) } + // MARK: - Notifications diff --git a/Loop/Models/RemoteCommand.swift b/Loop/Models/RemoteCommand.swift new file mode 100644 index 0000000000..20a80b166f --- /dev/null +++ b/Loop/Models/RemoteCommand.swift @@ -0,0 +1,44 @@ +// +// RemoteCommand.swift +// Loop +// +// Created by Pete Schwamb on 9/16/19. +// Copyright © 2019 LoopKit Authors. All rights reserved. +// + +import Foundation +import LoopKit + +public enum RemoteCommandError: Error { + case expired +} + + +enum RemoteCommand { + typealias RawValue = [String: Any] + + case temporaryScheduleOverride(TemporaryScheduleOverride) + case cancelTemporaryOverride +} + + +// Push Notifications +extension RemoteCommand { + init?(notification: [String: Any], allowedPresets: [TemporaryScheduleOverridePreset]) { + if let overrideEnactName = notification["override-name"] as? String, + let preset = allowedPresets.first(where: { $0.name == overrideEnactName }), + let remoteAddress = notification["remote-address"] as? String + { + var override = preset.createOverride(enactTrigger: .remote(remoteAddress)) + if let overrideDurationMinutes = notification["override-duration-minutes"] as? Double { + override.duration = .finite(TimeInterval(minutes: overrideDurationMinutes)) + } + self = .temporaryScheduleOverride(override) + } else if let _ = notification["cancel-temporary-override"] as? String { + self = .cancelTemporaryOverride + } + else { + return nil + } + } +} diff --git a/LoopCore/LoopSettings.swift b/LoopCore/LoopSettings.swift index 17f84c13af..b524e3495c 100644 --- a/LoopCore/LoopSettings.swift +++ b/LoopCore/LoopSettings.swift @@ -54,7 +54,11 @@ public struct LoopSettings: Equatable { public var glucoseUnit: HKUnit? { return glucoseTargetRangeSchedule?.unit } - + + // MARK - Push Notifications + + public var deviceToken: Data? + // MARK - Guardrails public func allowedSensitivityValues(for unit: HKUnit) -> [Double] { @@ -136,7 +140,9 @@ extension LoopSettings { context: .preMeal, settings: TemporaryScheduleOverrideSettings(unit: unit, targetRange: premealTargetRange), startDate: date, - duration: .finite(duration) + duration: .finite(duration), + enactTrigger: .local, + syncIdentifier: UUID() ) } @@ -152,7 +158,9 @@ extension LoopSettings { context: .legacyWorkout, settings: TemporaryScheduleOverrideSettings(unit: unit, targetRange: legacyWorkoutTargetRange), startDate: date, - duration: duration.isInfinite ? .indefinite : .finite(duration) + duration: duration.isInfinite ? .indefinite : .finite(duration), + enactTrigger: .local, + syncIdentifier: UUID() ) } diff --git a/WatchApp Extension/Controllers/ActionHUDController.swift b/WatchApp Extension/Controllers/ActionHUDController.swift index 5dc6e3b496..039886722e 100644 --- a/WatchApp Extension/Controllers/ActionHUDController.swift +++ b/WatchApp Extension/Controllers/ActionHUDController.swift @@ -164,7 +164,7 @@ final class ActionHUDController: HUDInterfaceController { extension ActionHUDController: OverrideSelectionControllerDelegate { func overrideSelectionController(_ controller: OverrideSelectionController, didSelectPreset preset: TemporaryScheduleOverridePreset) { - let override = preset.createOverride() + let override = preset.createOverride(enactTrigger: .local) sendOverride(override) } } diff --git a/WatchApp Extension/Scenes/GlucoseChartScene.swift b/WatchApp Extension/Scenes/GlucoseChartScene.swift index 0188ab6d75..c9b8315a99 100644 --- a/WatchApp Extension/Scenes/GlucoseChartScene.swift +++ b/WatchApp Extension/Scenes/GlucoseChartScene.swift @@ -259,7 +259,14 @@ class GlucoseChartScene: SKScene { if rangeHashable.end < spannedInterval.end { let extendedDuration = spannedInterval.end.timeIntervalSince(rangeHashable.start) - let extendedRange = TemporaryScheduleOverride(context: rangeHashable.override.context, settings: rangeHashable.override.settings, startDate: rangeHashable.start, duration: .finite(extendedDuration)) + let extendedRange = TemporaryScheduleOverride( + context: rangeHashable.override.context, + settings: rangeHashable.override.settings, + startDate: rangeHashable.start, + duration: .finite(extendedDuration), + enactTrigger: .local, + syncIdentifier: UUID() + ) let extendedRangeHashable = TemporaryScheduleOverrideHashable(extendedRange)! // Target range already known to be non-nil let (sprite2, created) = getSprite(forHash: extendedRangeHashable.chartHashValue) sprite2.color = UIColor.glucose.withAlphaComponent(0.25) From b42c91b015b1e94559604849b1b887b2a835085b Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 26 Oct 2019 09:13:28 -0600 Subject: [PATCH 111/132] Update carthage revs --- Cartfile.resolved | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index aa6357ee5f..f18be4f6df 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,8 +1,8 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "7417605dd898bf89378171941126c85d85dc642c" github "LoopKit/G4ShareSpy" "e62d296067180c6659166272ff9cc406f470ec9e" -github "LoopKit/LoopKit" "a08405ec7a4e38fa96e39ae079114db30f393a2a" +github "LoopKit/LoopKit" "2dfd7b076fda52bb0710e9beba12cf092a67a769" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" github "LoopKit/dexcom-share-client-swift" "c4f3d48e56e5b3ad786486ccd1bbc753034096d2" github "i-schuetz/SwiftCharts" "0.6.5" -github "ps2/rileylink_ios" "51d6a20cc63c0c0b2071a4a78b4065e9a11ba81f" +github "ps2/rileylink_ios" "88530730f45c6a846f9bb9b2d87893b1350993e2" From 9407f8c3d8a2f82c84afee36d4d52eff02168cc2 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 26 Oct 2019 14:37:21 -0600 Subject: [PATCH 112/132] Update travis to use iPhone 8 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7e9c1d26d8..c582b6b5a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,5 +17,5 @@ script: - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme Loop build CODE_SIGN_IDENTITY="" CODE_SIGNING_ALLOWED=NO | xcpretty - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme Learn build CODE_SIGN_IDENTITY="" CODE_SIGNING_ALLOWED=NO | xcpretty # Run the test target - - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme LoopTests -destination 'name=iPhone 6' test | xcpretty - - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme DoseMathTests -destination 'name=iPhone 6' test | xcpretty + - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme LoopTests -destination 'name=iPhone 8' test | xcpretty + - set -o pipefail && xcodebuild -project Loop.xcodeproj -scheme DoseMathTests -destination 'name=iPhone 8' test | xcpretty From 2c50733b4b29f859383bc23937dc0743a72f7962 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 7 Nov 2019 00:26:24 -0600 Subject: [PATCH 113/132] Notification names changed in LoopKit (#1165) * Notification names changed in LoopKit * Bump carthage revs --- Cartfile.resolved | 4 ++-- Loop/Managers/LoopDataManager.swift | 4 ++-- WatchApp Extension/Controllers/CarbEntryListController.swift | 2 +- WatchApp Extension/Controllers/ChartHUDController.swift | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index f18be4f6df..f63e84ee1e 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,8 +1,8 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "7417605dd898bf89378171941126c85d85dc642c" github "LoopKit/G4ShareSpy" "e62d296067180c6659166272ff9cc406f470ec9e" -github "LoopKit/LoopKit" "2dfd7b076fda52bb0710e9beba12cf092a67a769" +github "LoopKit/LoopKit" "16276559436956e2f7cb343b73b767779f9f477f" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" github "LoopKit/dexcom-share-client-swift" "c4f3d48e56e5b3ad786486ccd1bbc753034096d2" github "i-schuetz/SwiftCharts" "0.6.5" -github "ps2/rileylink_ios" "88530730f45c6a846f9bb9b2d87893b1350993e2" +github "ps2/rileylink_ios" "92314a08f26e94e271961a629e4d7b898e4e2e4d" diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index aac8e72500..5bbc7611c6 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -91,7 +91,7 @@ final class LoopDataManager { // Observe changes notificationObservers = [ NotificationCenter.default.addObserver( - forName: .CarbEntriesDidUpdate, + forName: CarbStore.carbEntriesDidUpdate, object: carbStore, queue: nil ) { (note) -> Void in @@ -104,7 +104,7 @@ final class LoopDataManager { } }, NotificationCenter.default.addObserver( - forName: .GlucoseSamplesDidChange, + forName: GlucoseStore.glucoseSamplesDidChange, object: glucoseStore, queue: nil ) { (note) in diff --git a/WatchApp Extension/Controllers/CarbEntryListController.swift b/WatchApp Extension/Controllers/CarbEntryListController.swift index bc933b160d..cafdd5863f 100644 --- a/WatchApp Extension/Controllers/CarbEntryListController.swift +++ b/WatchApp Extension/Controllers/CarbEntryListController.swift @@ -46,7 +46,7 @@ class CarbEntryListController: WKInterfaceController, IdentifiableClass { override func willActivate() { observers = [ - NotificationCenter.default.addObserver(forName: .CarbEntriesDidUpdate, object: loopManager.carbStore, queue: nil) { [weak self] (note) in + NotificationCenter.default.addObserver(forName: CarbStore.carbEntriesDidUpdate, object: loopManager.carbStore, queue: nil) { [weak self] (note) in self?.log.default("Received CarbEntriesDidUpdate notification: %{public}@. Updating list", String(describing: note.userInfo ?? [:])) DispatchQueue.main.async { diff --git a/WatchApp Extension/Controllers/ChartHUDController.swift b/WatchApp Extension/Controllers/ChartHUDController.swift index 4d2ca3e166..ee3febba4f 100644 --- a/WatchApp Extension/Controllers/ChartHUDController.swift +++ b/WatchApp Extension/Controllers/ChartHUDController.swift @@ -118,7 +118,7 @@ final class ChartHUDController: HUDInterfaceController, WKCrownDelegate { super.willActivate() observers = [ - NotificationCenter.default.addObserver(forName: .GlucoseSamplesDidChange, object: loopManager.glucoseStore, queue: nil) { [weak self] (note) in + NotificationCenter.default.addObserver(forName: GlucoseStore.glucoseSamplesDidChange, object: loopManager.glucoseStore, queue: nil) { [weak self] (note) in self?.log.default("Received GlucoseSamplesDidChange notification: %{public}@. Updating chart", String(describing: note.userInfo ?? [:])) DispatchQueue.main.async { From 6d15f59781bcca9df58853da373900664c8c2e08 Mon Sep 17 00:00:00 2001 From: Alin Artiom Kenibasov Date: Wed, 13 Nov 2019 23:38:03 +0100 Subject: [PATCH 114/132] Fixed building from directories containing spaces (#1153) * Fixed building from directories containing space * Fixed build from directory containing spaces --- Loop.xcodeproj/project.pbxproj | 16 ++++++++-------- Scripts/capture-build-details.sh | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 75190c3519..18532c5a47 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -2395,7 +2395,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n\n"; + shellScript = "\"${SRCROOT}/Scripts/copy-frameworks.sh\"\n\n"; }; 43EDDBEF1C361BCE007D89B5 /* Copy Frameworks with Carthage */ = { isa = PBXShellScriptBuildPhase; @@ -2439,7 +2439,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n"; + shellScript = "\"${SRCROOT}/Scripts/copy-frameworks.sh\"\n"; }; 43FF3DF620A8EFE800F8E62C /* Copy Frameworks with Carthage */ = { isa = PBXShellScriptBuildPhase; @@ -2459,7 +2459,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n"; + shellScript = "\"${SRCROOT}/Scripts/copy-frameworks.sh\"\n"; }; A942E448225FD9D500DD4980 /* Copy Frameworks with Carthage */ = { isa = PBXShellScriptBuildPhase; @@ -2479,7 +2479,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "${SRCROOT}/Scripts/copy-frameworks.sh\n\n"; + shellScript = "\"${SRCROOT}/Scripts/copy-frameworks.sh\"\n\n"; }; C16DA84322E8E5FF008624C2 /* Install Plugins */ = { isa = PBXShellScriptBuildPhase; @@ -2497,7 +2497,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "${SRCROOT}/Scripts/copy-plugins.sh\n"; + shellScript = "\"${SRCROOT}/Scripts/copy-plugins.sh\"\n"; }; C1D1405722FB66DF00DA6242 /* Build Derived Assets */ = { isa = PBXShellScriptBuildPhase; @@ -2515,7 +2515,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "${SRCROOT}/Scripts/build-derived-assets.sh\n"; + shellScript = "\"${SRCROOT}/Scripts/build-derived-assets.sh\"\n"; }; C1D1406222FB7ED200DA6242 /* Build Derived Watch Assets */ = { isa = PBXShellScriptBuildPhase; @@ -2533,7 +2533,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "${SRCROOT}/Scripts/build-derived-watch-assets.sh\n"; + shellScript = "\"${SRCROOT}/Scripts/build-derived-watch-assets.sh\"\n"; }; C1D19800232CFA2A0096D646 /* Capture Build Details */ = { isa = PBXShellScriptBuildPhase; @@ -2551,7 +2551,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "${SRCROOT}/Scripts/capture-build-details.sh\n"; + shellScript = "\"${SRCROOT}/Scripts/capture-build-details.sh\"\n"; }; /* End PBXShellScriptBuildPhase section */ diff --git a/Scripts/capture-build-details.sh b/Scripts/capture-build-details.sh index cb79d0e14d..1f049d80ee 100755 --- a/Scripts/capture-build-details.sh +++ b/Scripts/capture-build-details.sh @@ -6,7 +6,7 @@ # Copyright © 2019 LoopKit Authors. All rights reserved. echo "Gathering build details in ${SRCROOT}" -cd ${SRCROOT} +cd "${SRCROOT}" plist="${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}" @@ -16,7 +16,7 @@ if [ -e .git ]; then branch=$(git branch | grep \* | cut -d ' ' -f2-) plutil -replace com-loopkit-Loop-git-branch -string "${branch}" "${plist}" fi; -plutil -replace com-loopkit-Loop-srcroot -string ${SRCROOT} "${plist}" +plutil -replace com-loopkit-Loop-srcroot -string "${SRCROOT}" "${plist}" plutil -replace com-loopkit-Loop-build-date -string "$(date)" "${plist}" plutil -replace com-loopkit-Loop-xcode-version -string "${XCODE_PRODUCT_BUILD_VERSION}" "${plist}" From 49412b6cae35400becd79a31e118910849b295fe Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 15 Nov 2019 10:23:40 -0600 Subject: [PATCH 115/132] Bump carthage revisions --- Cartfile.resolved | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index f63e84ee1e..6a67aa623f 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,8 +1,8 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" -github "LoopKit/CGMBLEKit" "7417605dd898bf89378171941126c85d85dc642c" +github "LoopKit/CGMBLEKit" "8983147b950a21f736c3e51c1e5ac5c82a6e22d0" github "LoopKit/G4ShareSpy" "e62d296067180c6659166272ff9cc406f470ec9e" -github "LoopKit/LoopKit" "16276559436956e2f7cb343b73b767779f9f477f" +github "LoopKit/LoopKit" "c49025f74aa22845eec58546d4b00f189632246b" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" github "LoopKit/dexcom-share-client-swift" "c4f3d48e56e5b3ad786486ccd1bbc753034096d2" github "i-schuetz/SwiftCharts" "0.6.5" -github "ps2/rileylink_ios" "92314a08f26e94e271961a629e4d7b898e4e2e4d" +github "ps2/rileylink_ios" "3082367b3484586cd3a70a7134edef3ea35c6b9b" From ac31e0b26bdbdab7688be702d5f4c1f61940de44 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 26 Nov 2019 16:09:03 -0600 Subject: [PATCH 116/132] Bump carthage revs --- Cartfile.resolved | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 6a67aa623f..d0129bb0e6 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,8 +1,8 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" -github "LoopKit/CGMBLEKit" "8983147b950a21f736c3e51c1e5ac5c82a6e22d0" +github "LoopKit/CGMBLEKit" "b0794f7b00509ca2bc79cb71e765555b27fd9172" github "LoopKit/G4ShareSpy" "e62d296067180c6659166272ff9cc406f470ec9e" -github "LoopKit/LoopKit" "c49025f74aa22845eec58546d4b00f189632246b" +github "LoopKit/LoopKit" "a931da1ca106697b791d1508bad357c47f73dec0" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" github "LoopKit/dexcom-share-client-swift" "c4f3d48e56e5b3ad786486ccd1bbc753034096d2" github "i-schuetz/SwiftCharts" "0.6.5" -github "ps2/rileylink_ios" "3082367b3484586cd3a70a7134edef3ea35c6b9b" +github "ps2/rileylink_ios" "26931d3545da011a5bd4df4d6b2b0462b0cce71a" From d22b8370c200b0e638d27aaf32c44bccc02f7d5b Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Wed, 18 Dec 2019 21:10:25 -0600 Subject: [PATCH 117/132] Pause launch if launching before first unlock (#1198) --- Loop/AppDelegate.swift | 73 ++++++++++++++++++++++++++++----- Loop/Base.lproj/Main.storyboard | 52 +++++++++++------------ 2 files changed, 86 insertions(+), 39 deletions(-) diff --git a/Loop/AppDelegate.swift b/Loop/AppDelegate.swift index df2430f732..401ae1c467 100644 --- a/Loop/AppDelegate.swift +++ b/Loop/AppDelegate.swift @@ -18,49 +18,92 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - private(set) lazy var deviceManager = DeviceDataManager() + private var deviceManager: DeviceDataManager? private var rootViewController: RootNavigationController! { return window?.rootViewController as? RootNavigationController } - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + private var isAfterFirstUnlock: Bool { + let fileManager = FileManager.default + do { + let documentDirectory = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false) + let fileURL = documentDirectory.appendingPathComponent("protection.test") + guard fileManager.fileExists(atPath: fileURL.path) else { + let contents = Data("unimportant".utf8) + try? contents.write(to: fileURL, options: .completeFileProtectionUntilFirstUserAuthentication) + // If file doesn't exist, we're at first start, which will be user directed. + return true + } + let contents = try? Data(contentsOf: fileURL) + return contents != nil + } catch { + log.error(error) + } + return false + } + + private func finishLaunch() { + log.default("Finishing launching") + + deviceManager = DeviceDataManager() + NotificationManager.authorize(delegate: self) + + let mainStatusViewControllers = UIStoryboard(name: "Main", bundle: Bundle(for: AppDelegate.self)).instantiateViewController(withIdentifier: "MainStatusViewController") as! StatusTableViewController - log.info(#function) - - AnalyticsManager.shared.application(application, didFinishLaunchingWithOptions: launchOptions) + rootViewController.pushViewController(mainStatusViewControllers, animated: false) rootViewController.rootViewController.deviceManager = deviceManager + } + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + log.default("didFinishLaunchingWithOptions \(String(describing: launchOptions))") + + AnalyticsManager.shared.application(application, didFinishLaunchingWithOptions: launchOptions) + + guard isAfterFirstUnlock else { + log.default("Launching before first unlock; pausing launch...") + return false + } + + finishLaunch() + let notificationOption = launchOptions?[.remoteNotification] if let notification = notificationOption as? [String: AnyObject] { - deviceManager.handleRemoteNotification(notification) + deviceManager?.handleRemoteNotification(notification) } return true } func applicationWillResignActive(_ application: UIApplication) { + log.default(#function) } func applicationDidEnterBackground(_ application: UIApplication) { + log.default(#function) } func applicationWillEnterForeground(_ application: UIApplication) { + log.default(#function) } func applicationDidBecomeActive(_ application: UIApplication) { - deviceManager.updatePumpManagerBLEHeartbeatPreference() + deviceManager?.updatePumpManagerBLEHeartbeatPreference() } func applicationWillTerminate(_ application: UIApplication) { + log.default(#function) } // MARK: - Continuity func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + log.default(#function) if #available(iOS 12.0, *) { if userActivity.activityType == NewCarbEntryIntent.className { @@ -86,7 +129,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) } let token = tokenParts.joined() log.default("RemoteNotifications device token: \(token)") - deviceManager.loopManager.settings.deviceToken = deviceToken + deviceManager?.loopManager.settings.deviceToken = deviceToken } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { @@ -102,9 +145,17 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { return } - deviceManager.handleRemoteNotification(notification) + deviceManager?.handleRemoteNotification(notification) completionHandler(.noData) } + + func applicationProtectedDataDidBecomeAvailable(_ application: UIApplication) { + log.default("applicationProtectedDataDidBecomeAvailable") + + if deviceManager == nil { + finishLaunch() + } + } } @@ -119,7 +170,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate { { AnalyticsManager.shared.didRetryBolus() - deviceManager.enactBolus(units: units, at: startDate) { (_) in + deviceManager?.enactBolus(units: units, at: startDate) { (_) in completionHandler() } return diff --git a/Loop/Base.lproj/Main.storyboard b/Loop/Base.lproj/Main.storyboard index 364c95188e..b59845e58e 100644 --- a/Loop/Base.lproj/Main.storyboard +++ b/Loop/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -21,7 +21,7 @@ - + @@ -70,7 +70,7 @@ @@ -113,7 +113,7 @@ Future glucose is predicted by combining the effects of multiple inputs. Use this tool to toggle various inputs to see how they compare to the final prediction. - + @@ -270,7 +270,7 @@ - + @@ -368,7 +368,7 @@ @@ -401,7 +401,7 @@ @@ -462,7 +462,7 @@ @@ -541,7 +541,7 @@ - + @@ -775,16 +775,16 @@ - + @@ -1028,7 +1025,7 @@ An insulin activity model is used to estimate effects of insulin on glucose levels. An accurate model can help prevent insulin stacking and safely recommend corrective treatments. - + @@ -1067,7 +1064,6 @@ - @@ -1130,8 +1126,8 @@ - - + +
From 650ffd492b1dd5b9d92992d9ad1695a22b9dab42 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Wed, 18 Dec 2019 21:43:13 -0600 Subject: [PATCH 118/132] Bump version for dev to 1.10.3 (#1204) --- Loop.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Loop.xcconfig b/Loop.xcconfig index e36fb844ef..1d0542afda 100644 --- a/Loop.xcconfig +++ b/Loop.xcconfig @@ -11,7 +11,7 @@ MAIN_APP_BUNDLE_IDENTIFIER = com.${DEVELOPMENT_TEAM}.loopkit MAIN_APP_DISPLAY_NAME = Loop -LOOP_MARKETING_VERSION = 1.10.1 +LOOP_MARKETING_VERSION = 1.10.3 APPICON_NAME = AppIcon From 94d32790eeae0a7e2a95f815ccb10a229d0b209c Mon Sep 17 00:00:00 2001 From: mpangburn Date: Wed, 27 Nov 2019 13:01:05 -0800 Subject: [PATCH 119/132] One ! --- Loop/View Controllers/StatusTableViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index 65aa576387..8071614e6f 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -1297,7 +1297,7 @@ final class StatusTableViewController: ChartsTableViewController { // MARK: - Testing scenarios override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) { - if let testingScenariosManager = deviceManager.testingScenariosManager, testingScenariosManager.scenarioURLs.isEmpty { + if let testingScenariosManager = deviceManager.testingScenariosManager, !testingScenariosManager.scenarioURLs.isEmpty { if motion == .motionShake { presentScenarioSelector() } From d0eec9813865cf1e7d593b0eccd9fcd751c6e62e Mon Sep 17 00:00:00 2001 From: mpangburn Date: Sun, 17 Nov 2019 16:06:36 -0800 Subject: [PATCH 120/132] Remove override presets cell in settings --- Loop.xcodeproj/project.pbxproj | 2 +- .../SettingsTableViewController.swift | 33 +------------------ .../StatusTableViewController.swift | 4 +++ 3 files changed, 6 insertions(+), 33 deletions(-) diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 18532c5a47..df07280734 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -3506,7 +3506,7 @@ CODE_SIGN_ENTITLEMENTS = "WatchApp Extension/WatchApp Extension.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 5Q5Q8W9ATZ; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; diff --git a/Loop/View Controllers/SettingsTableViewController.swift b/Loop/View Controllers/SettingsTableViewController.swift index 8cfabe877f..e806e7776b 100644 --- a/Loop/View Controllers/SettingsTableViewController.swift +++ b/Loop/View Controllers/SettingsTableViewController.swift @@ -72,7 +72,6 @@ final class SettingsTableViewController: UITableViewController { case insulinModel case carbRatio case insulinSensitivity - case overridePresets } fileprivate enum ServiceRow: Int, CaseCountable { @@ -143,11 +142,7 @@ final class SettingsTableViewController: UITableViewController { case .cgm: return CGMRow.count case .configuration: - if FeatureFlags.sensitivityOverridesEnabled { - return ConfigurationRow.count - } else { - return ConfigurationRow.count - 1 - } + return ConfigurationRow.count case .services: return ServiceRow.count case .testingPumpDataDeletion, .testingCGMDataDeletion: @@ -286,14 +281,6 @@ final class SettingsTableViewController: UITableViewController { } else { configCell.detailTextLabel?.text = SettingsTableViewCell.TapToSetString } - case .overridePresets: - configCell.textLabel?.text = NSLocalizedString("Override Presets", comment: "The title text for the override presets") - let maxPreviewSymbolCount = 3 - let presetPreviewText = dataManager.loopManager.settings.overridePresets - .prefix(maxPreviewSymbolCount) - .map { $0.symbol } - .joined(separator: " ") - configCell.detailTextLabel?.text = presetPreviewText } configCell.accessoryType = .disclosureIndicator @@ -550,15 +537,6 @@ final class SettingsTableViewController: UITableViewController { vc.delegate = self vc.syncSource = pumpManager - show(vc, sender: sender) - case .overridePresets: - guard let glucoseUnit = dataManager.loopManager.glucoseStore.preferredUnit else { break } - let vc = OverridePresetTableViewController( - glucoseUnit: glucoseUnit, - presets: dataManager.loopManager.settings.overridePresets - ) - vc.delegate = self - show(vc, sender: sender) } case .loop: @@ -852,15 +830,6 @@ extension SettingsTableViewController: DeliveryLimitSettingsTableViewControllerD } -extension SettingsTableViewController: OverridePresetTableViewControllerDelegate { - func overridePresetTableViewControllerDidUpdatePresets(_ vc: OverridePresetTableViewController) { - dataManager.loopManager.settings.overridePresets = vc.presets - - tableView.reloadRows(at: [[Section.configuration.rawValue, ConfigurationRow.overridePresets.rawValue]], with: .none) - } -} - - private extension UIAlertController { convenience init(pumpDataDeletionHandler handler: @escaping () -> Void) { self.init( diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index 8071614e6f..9e7f37c6f2 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -1370,6 +1370,10 @@ extension StatusTableViewController: DoseProgressObserver { } extension StatusTableViewController: OverrideSelectionViewControllerDelegate { + func overrideSelectionViewController(_ vc: OverrideSelectionViewController, didUpdatePresets presets: [TemporaryScheduleOverridePreset]) { + deviceManager.loopManager.settings.overridePresets = presets + } + func overrideSelectionViewController(_ vc: OverrideSelectionViewController, didConfirmOverride override: TemporaryScheduleOverride) { deviceManager.loopManager.settings.scheduleOverride = override } From a098d6a2b6c9ba8d542a1281bde693f071e63d57 Mon Sep 17 00:00:00 2001 From: mpangburn Date: Mon, 18 Nov 2019 14:12:51 -0800 Subject: [PATCH 121/132] Revert dev team change --- Loop.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index df07280734..18532c5a47 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -3506,7 +3506,7 @@ CODE_SIGN_ENTITLEMENTS = "WatchApp Extension/WatchApp Extension.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = 5Q5Q8W9ATZ; + DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; From 2c479629e1d091f9664e5e450fca09349de4dd70 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 23 Dec 2019 20:41:24 -0600 Subject: [PATCH 122/132] Bump carthage revs --- Cartfile.resolved | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index d0129bb0e6..4059be6a76 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,8 +1,8 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" -github "LoopKit/CGMBLEKit" "b0794f7b00509ca2bc79cb71e765555b27fd9172" +github "LoopKit/CGMBLEKit" "d49d37bb858e7b7a946a3defab392920731cc65e" github "LoopKit/G4ShareSpy" "e62d296067180c6659166272ff9cc406f470ec9e" -github "LoopKit/LoopKit" "a931da1ca106697b791d1508bad357c47f73dec0" +github "LoopKit/LoopKit" "c8ab966bea813ef193d2c5657ffb2ae2aa77277b" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" github "LoopKit/dexcom-share-client-swift" "c4f3d48e56e5b3ad786486ccd1bbc753034096d2" github "i-schuetz/SwiftCharts" "0.6.5" -github "ps2/rileylink_ios" "26931d3545da011a5bd4df4d6b2b0462b0cce71a" +github "ps2/rileylink_ios" "4012c8362e456003d20535747a7734f66530e71d" From 0a26361c41d7c0a18bf29bbbe79482f236c9cc04 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 23 Dec 2019 23:33:21 -0600 Subject: [PATCH 123/132] Bump carthage revs --- Cartfile.resolved | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 4059be6a76..d72fab3bf4 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,8 +1,8 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" -github "LoopKit/CGMBLEKit" "d49d37bb858e7b7a946a3defab392920731cc65e" -github "LoopKit/G4ShareSpy" "e62d296067180c6659166272ff9cc406f470ec9e" -github "LoopKit/LoopKit" "c8ab966bea813ef193d2c5657ffb2ae2aa77277b" +github "LoopKit/CGMBLEKit" "8b7c7c26ab84d86bb342960fee12a8e67174b19d" +github "LoopKit/G4ShareSpy" "9d5f88597e8b002bd3058cb5c1ae429857d79059" +github "LoopKit/LoopKit" "3ea3bb600b599701f4fe0196409b1249c3d4e8bc" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" -github "LoopKit/dexcom-share-client-swift" "c4f3d48e56e5b3ad786486ccd1bbc753034096d2" +github "LoopKit/dexcom-share-client-swift" "5ccfad205de4ffd1a8db1b99c431a4fee692bf32" github "i-schuetz/SwiftCharts" "0.6.5" -github "ps2/rileylink_ios" "4012c8362e456003d20535747a7734f66530e71d" +github "ps2/rileylink_ios" "d6023f9f190217925dd247761ca50e1ffe261404" From fd22ca0cbc1fa0aa7cc274480d7cd789122c7686 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 28 Dec 2019 10:12:47 -0600 Subject: [PATCH 124/132] Bump cartfile revs --- Cartfile.resolved | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index d72fab3bf4..aa065ab191 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,8 +1,8 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" -github "LoopKit/CGMBLEKit" "8b7c7c26ab84d86bb342960fee12a8e67174b19d" -github "LoopKit/G4ShareSpy" "9d5f88597e8b002bd3058cb5c1ae429857d79059" +github "LoopKit/CGMBLEKit" "96de871ed517630cfc2c9c68ac9698870086d6c9" +github "LoopKit/G4ShareSpy" "af36bab46d5a39e500e9eabc4bbb147b24d0f2c4" github "LoopKit/LoopKit" "3ea3bb600b599701f4fe0196409b1249c3d4e8bc" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" -github "LoopKit/dexcom-share-client-swift" "5ccfad205de4ffd1a8db1b99c431a4fee692bf32" +github "LoopKit/dexcom-share-client-swift" "faf2b77419b690d32e488a16625314888455ba08" github "i-schuetz/SwiftCharts" "0.6.5" -github "ps2/rileylink_ios" "d6023f9f190217925dd247761ca50e1ffe261404" +github "ps2/rileylink_ios" "4850a9ecf656a80f241c2fcc81f228c46e400fb4" From b4189bcae6a353398aeacf59276df1e98ef266fd Mon Sep 17 00:00:00 2001 From: katie disimone Date: Sat, 28 Dec 2019 11:22:02 -0800 Subject: [PATCH 125/132] Translation updates (#1167) * Updating genstrings, Adding new localizable.strings, adding some new translations to existing languages * cleaning up unused files and adding Vietnamese, Swedish, Danish, Finnish, Japanese, Portuguese (BRA) * typos and minor missing translations. * cleanup unnecessary .strings and typo fixes * Adding Romanian * Italian updates * Danish translations * German touchups * Finnish touchups * Japanese and French touchups * Norwegian, dutch, Portuguese, Romanian, russian, swedish, vietnamese, and chinese finished * polish Co-authored-by: Pete Schwamb --- Common/da.lproj/Intents.strings | 9 + Common/da.lproj/Localizable.strings | 54 ++ Common/de.lproj/Intents.strings | 9 +- Common/de.lproj/Localizable.strings | 24 + Common/en.lproj/Localizable.strings | 24 + Common/es.lproj/Intents.strings | 6 +- Common/es.lproj/Localizable.strings | 23 + Common/fi.lproj/Intents.strings | 9 + Common/fi.lproj/Localizable.strings | 57 ++ Common/fr.lproj/Intents.strings | 9 +- Common/fr.lproj/Localizable.strings | 24 + Common/it.lproj/Intents.strings | 9 +- Common/it.lproj/Localizable.strings | 54 ++ Common/ja.lproj/Intents.strings | 9 + Common/ja.lproj/Localizable.strings | 54 ++ Common/nb.lproj/Intents.strings | 9 +- Common/nb.lproj/Localizable.strings | 54 ++ Common/nl.lproj/Intents.strings | 9 +- Common/nl.lproj/Localizable.strings | 57 ++ Common/pl.lproj/Intents.strings | 4 +- Common/pl.lproj/Localizable.strings | 23 + Common/pt-BR.lproj/Intents.strings | 9 + Common/pt-BR.lproj/Localizable.strings | 54 ++ Common/ro.lproj/Intents.strings | 9 + Common/ro.lproj/Localizable.strings | 57 ++ Common/ru.lproj/Intents.strings | 9 +- Common/ru.lproj/Localizable.strings | 24 + Common/sv.lproj/Intents.strings | 9 + Common/sv.lproj/Localizable.strings | 57 ++ Common/vi.lproj/Intents.strings | 9 + Common/vi.lproj/Localizable.strings | 24 + Common/zh-Hans.lproj/Intents.strings | 9 +- Common/zh-Hans.lproj/Localizable.strings | 54 ++ DoseMathTests/da.lproj/Localizable.strings | 27 + DoseMathTests/de.lproj/InfoPlist.strings | 3 - DoseMathTests/de.lproj/Localizable.strings | 1 + DoseMathTests/es.lproj/InfoPlist.strings | 0 DoseMathTests/fi.lproj/Localizable.strings | 27 + DoseMathTests/fr.lproj/InfoPlist.strings | 1 - DoseMathTests/it.lproj/InfoPlist.strings | 3 - DoseMathTests/ja.lproj/Localizable.strings | 27 + DoseMathTests/nb.lproj/InfoPlist.strings | 1 - DoseMathTests/nl.lproj/InfoPlist.strings | 1 - DoseMathTests/pl.lproj/InfoPlist.strings | 1 - DoseMathTests/pt-BR.lproj/Localizable.strings | 27 + DoseMathTests/ro.lproj/Localizable.strings | 27 + DoseMathTests/ru.lproj/InfoPlist.strings | 0 DoseMathTests/sv.lproj/Localizable.strings | 27 + DoseMathTests/vi.lproj/Localizable.strings | 27 + DoseMathTests/zh-Hans.lproj/InfoPlist.strings | 3 - Learn/da.lproj/Localizable.strings | 33 ++ Learn/da.lproj/Main.strings | 3 + Learn/de.lproj/Localizable.strings | 33 ++ Learn/de.lproj/Main.strings | 3 + Learn/en.lproj/Localizable.strings | 32 ++ Learn/en.lproj/Main.strings | 3 + Learn/es.lproj/Localizable.strings | 32 ++ Learn/es.lproj/Main.strings | 3 + Learn/fi.lproj/Localizable.strings | 33 ++ Learn/fi.lproj/Main.strings | 3 + Learn/fr.lproj/Localizable.strings | 33 ++ Learn/fr.lproj/Main.strings | 3 + Learn/it.lproj/Localizable.strings | 33 ++ Learn/it.lproj/Main.strings | 3 + Learn/ja.lproj/Localizable.strings | 33 ++ Learn/ja.lproj/Main.strings | 3 + Learn/nb.lproj/Localizable.strings | 33 ++ Learn/nb.lproj/Main.strings | 3 + Learn/nl.lproj/Localizable.strings | 33 ++ Learn/nl.lproj/Main.strings | 3 + Learn/pl.lproj/Localizable.strings | 32 ++ Learn/pl.lproj/Main.strings | 3 + Learn/pt-BR.lproj/Localizable.strings | 33 ++ Learn/pt-BR.lproj/Main.strings | 3 + Learn/ro.lproj/Localizable.strings | 33 ++ Learn/ro.lproj/Main.strings | 3 + Learn/ru.lproj/Localizable.strings | 33 ++ Learn/ru.lproj/Main.strings | 3 + Learn/sv.lproj/Localizable.strings | 33 ++ Learn/sv.lproj/Main.strings | 3 + Learn/vi.lproj/Localizable.strings | 33 ++ Learn/vi.lproj/Main.strings | 3 + Learn/zh-Hans.lproj/Localizable.strings | 33 ++ Learn/zh-Hans.lproj/Main.strings | 3 + .../Base.lproj/Localizable.strings | 21 - .../da.lproj/Localizable.strings | 9 + .../da.lproj/MainInterface.strings | 6 + .../de.lproj/InfoPlist.strings | 6 - .../de.lproj/Localizable.strings | 21 +- .../de.lproj/MainInterface.strings | 2 +- .../en.lproj/Localizable.strings | 5 + .../en.lproj/MainInterface.strings | 6 + .../es.lproj/InfoPlist.strings | 6 - .../es.lproj/Localizable.strings | 21 - .../fi.lproj/Localizable.strings | 6 + .../fi.lproj/MainInterface.strings | 6 + .../fr.lproj/InfoPlist.strings | 6 - .../fr.lproj/Localizable.strings | 20 - .../it.lproj/InfoPlist.strings | 6 - .../it.lproj/Localizable.strings | 20 - .../ja.lproj/Localizable.strings | 6 + .../ja.lproj/MainInterface.strings | 6 + .../nb.lproj/InfoPlist.strings | 6 - .../nb.lproj/Localizable.strings | 18 - .../nl.lproj/InfoPlist.strings | 6 - .../nl.lproj/Localizable.strings | 19 +- .../pl.lproj/InfoPlist.strings | 6 - .../pl.lproj/Localizable.strings | 21 - .../pt-BR.lproj/Localizable.strings | 6 + .../pt-BR.lproj/MainInterface.strings | 6 + .../ro.lproj/Localizable.strings | 6 + .../ro.lproj/MainInterface.strings | 6 + .../ru.lproj/InfoPlist.strings | 0 .../ru.lproj/Localizable.strings | 20 - .../sv.lproj/Localizable.strings | 9 + .../sv.lproj/MainInterface.strings | 6 + .../vi.lproj/Localizable.strings | 6 + .../vi.lproj/MainInterface.strings | 6 + .../zh-Hans.lproj/InfoPlist.strings | 6 - .../zh-Hans.lproj/Localizable.strings | 20 - Loop.xcodeproj/project.pbxproj | 497 ++++++++++-------- Loop/Base.lproj/InfoPlist.strings | 5 +- Loop/Base.lproj/Localizable.strings | 112 ++-- Loop/da.lproj/InfoPlist.strings | 18 + Loop/da.lproj/Localizable.strings | 395 ++++++++++++++ Loop/da.lproj/Main.strings | 144 +++++ Loop/de.lproj/InfoPlist.strings | 8 +- Loop/de.lproj/LaunchScreen.strings | 1 - Loop/de.lproj/Localizable.strings | 189 +++---- Loop/de.lproj/Main.strings | 3 - Loop/en.lproj/InfoPlist.strings | 18 + Loop/en.lproj/Localizable.strings | 392 ++++++++++++++ Loop/en.lproj/Main.strings | 144 +++++ Loop/es.lproj/InfoPlist.strings | 6 +- Loop/es.lproj/LaunchScreen.strings | 1 - Loop/es.lproj/Localizable.strings | 108 ++-- Loop/fi.lproj/InfoPlist.strings | 18 + Loop/fi.lproj/Localizable.strings | 395 ++++++++++++++ Loop/fi.lproj/Main.strings | 144 +++++ Loop/fr.lproj/InfoPlist.strings | 6 +- Loop/fr.lproj/LaunchScreen.strings | 1 - Loop/fr.lproj/Localizable.strings | 131 ++--- Loop/fr.lproj/Main.strings | 3 - Loop/it.lproj/InfoPlist.strings | 6 +- Loop/it.lproj/LaunchScreen.strings | 1 - Loop/it.lproj/Localizable.strings | 128 ++--- Loop/it.lproj/Main.strings | 3 - Loop/ja.lproj/InfoPlist.strings | 18 + Loop/ja.lproj/Localizable.strings | 389 ++++++++++++++ Loop/ja.lproj/Main.strings | 144 +++++ Loop/nb.lproj/InfoPlist.strings | 6 +- Loop/nb.lproj/LaunchScreen.strings | 1 - Loop/nb.lproj/Localizable.strings | 130 ++--- Loop/nb.lproj/Main.strings | 3 - Loop/nl.lproj/InfoPlist.strings | 8 +- Loop/nl.lproj/LaunchScreen.strings | 1 - Loop/nl.lproj/Localizable.strings | 128 ++--- Loop/nl.lproj/Main.strings | 3 - Loop/pl.lproj/InfoPlist.strings | 6 +- Loop/pl.lproj/LaunchScreen.strings | 1 - Loop/pl.lproj/Localizable.strings | 107 ++-- Loop/pl.lproj/Main.strings | 4 +- Loop/pt-BR.lproj/InfoPlist.strings | 18 + Loop/pt-BR.lproj/Localizable.strings | 395 ++++++++++++++ Loop/pt-BR.lproj/Main.strings | 144 +++++ Loop/ro.lproj/InfoPlist.strings | 18 + Loop/ro.lproj/Localizable.strings | 395 ++++++++++++++ Loop/ro.lproj/Main.strings | 144 +++++ Loop/ru.lproj/InfoPlist.strings | 6 +- Loop/ru.lproj/LaunchScreen.strings | 1 - Loop/ru.lproj/Localizable.strings | 135 ++--- Loop/ru.lproj/Main.strings | 3 - Loop/sv.lproj/InfoPlist.strings | 18 + Loop/sv.lproj/Localizable.strings | 395 ++++++++++++++ Loop/sv.lproj/Main.strings | 144 +++++ Loop/vi.lproj/InfoPlist.strings | 18 + Loop/vi.lproj/Localizable.strings | 395 ++++++++++++++ Loop/vi.lproj/Main.strings | 144 +++++ Loop/zh-Hans.lproj/InfoPlist.strings | 6 +- Loop/zh-Hans.lproj/LaunchScreen.strings | 1 - Loop/zh-Hans.lproj/Localizable.strings | 130 ++--- Loop/zh-Hans.lproj/Main.strings | 3 - LoopTests/de.lproj/InfoPlist.strings | 3 - LoopTests/es.lproj/InfoPlist.strings | 3 - LoopTests/fr.lproj/InfoPlist.strings | 3 - LoopTests/it.lproj/InfoPlist.strings | 3 - LoopTests/nb.lproj/InfoPlist.strings | 3 - LoopTests/nl.lproj/InfoPlist.strings | 3 - LoopTests/pl.lproj/InfoPlist.strings | 3 - LoopTests/ru.lproj/InfoPlist.strings | 3 - LoopTests/zh-Hans.lproj/InfoPlist.strings | 3 - LoopUI/Base.lproj/Localizable.strings | 37 +- LoopUI/da.lproj/Localizable.strings | 36 ++ LoopUI/de.lproj/InfoPlist.strings | 6 - LoopUI/de.lproj/Localizable.strings | 26 +- LoopUI/en.lproj/Localizable.strings | 32 ++ LoopUI/es.lproj/InfoPlist.strings | 6 - LoopUI/es.lproj/Localizable.strings | 38 +- LoopUI/fi.lproj/Localizable.strings | 33 ++ LoopUI/fr.lproj/InfoPlist.strings | 6 - LoopUI/fr.lproj/Localizable.strings | 25 +- LoopUI/it.lproj/InfoPlist.strings | 6 - LoopUI/it.lproj/Localizable.strings | 25 +- LoopUI/ja.lproj/Localizable.strings | 33 ++ LoopUI/nb.lproj/InfoPlist.strings | 6 - LoopUI/nb.lproj/Localizable.strings | 24 +- LoopUI/nl.lproj/InfoPlist.strings | 6 - LoopUI/nl.lproj/Localizable.strings | 24 +- LoopUI/pl.lproj/InfoPlist.strings | 6 - LoopUI/pl.lproj/Localizable.strings | 37 +- LoopUI/pt-BR.lproj/Localizable.strings | 33 ++ LoopUI/ro.lproj/Localizable.strings | 33 ++ LoopUI/ru.lproj/InfoPlist.strings | 6 - LoopUI/ru.lproj/Localizable.strings | 25 +- LoopUI/sv.lproj/Localizable.strings | 36 ++ LoopUI/vi.lproj/Localizable.strings | 33 ++ LoopUI/zh-Hans.lproj/InfoPlist.strings | 6 - LoopUI/zh-Hans.lproj/Localizable.strings | 25 +- .../Base.lproj/Localizable.strings | 39 +- .../da.lproj/Localizable.strings | 45 ++ WatchApp Extension/de.lproj/InfoPlist.strings | 6 - .../de.lproj/Localizable.strings | 36 +- .../de.lproj/ckcomplication.strings | 3 - .../en.lproj/Localizable.strings | 38 ++ WatchApp Extension/es.lproj/InfoPlist.strings | 6 - .../es.lproj/Localizable.strings | 32 +- .../es.lproj/ckcomplication.strings | 3 - .../fi.lproj/Localizable.strings | 42 ++ WatchApp Extension/fr.lproj/InfoPlist.strings | 6 - .../fr.lproj/Localizable.strings | 33 +- .../fr.lproj/ckcomplication.strings | 12 - WatchApp Extension/it.lproj/InfoPlist.strings | 6 - .../it.lproj/Localizable.strings | 36 +- .../it.lproj/ckcomplication.strings | 3 - .../ja.lproj/Localizable.strings | 42 ++ WatchApp Extension/nb.lproj/InfoPlist.strings | 6 - .../nb.lproj/Localizable.strings | 32 +- .../nb.lproj/ckcomplication.strings | 10 - WatchApp Extension/nl.lproj/InfoPlist.strings | 6 - .../nl.lproj/Localizable.strings | 35 +- .../nl.lproj/ckcomplication.strings | 10 - WatchApp Extension/pl.lproj/InfoPlist.strings | 6 - .../pl.lproj/Localizable.strings | 39 +- .../pl.lproj/ckcomplication.strings | 10 - .../pt-BR.lproj/Localizable.strings | 39 ++ .../ro.lproj/Localizable.strings | 42 ++ WatchApp Extension/ru.lproj/InfoPlist.strings | 6 - .../ru.lproj/Localizable.strings | 33 +- .../ru.lproj/ckcomplication.strings | 3 - .../sv.lproj/Localizable.strings | 45 ++ .../vi.lproj/Localizable.strings | 42 ++ .../zh-Hans.lproj/InfoPlist.strings | 6 - .../zh-Hans.lproj/Localizable.strings | 36 +- .../zh-Hans.lproj/ckcomplication.strings | 3 - WatchApp/da.lproj/Interface.strings | 120 +++++ WatchApp/de.lproj/InfoPlist.strings | 6 - WatchApp/de.lproj/Interface.strings | 52 +- WatchApp/en.lproj/Interface.strings | 120 +++++ WatchApp/es.lproj/InfoPlist.strings | 6 - WatchApp/es.lproj/Interface.strings | 82 ++- WatchApp/fi.lproj/Interface.strings | 120 +++++ WatchApp/fr.lproj/InfoPlist.strings | 6 - WatchApp/fr.lproj/Interface.strings | 52 +- WatchApp/it.lproj/InfoPlist.strings | 6 - WatchApp/it.lproj/Interface.strings | 54 +- WatchApp/ja.lproj/Interface.strings | 120 +++++ WatchApp/nb.lproj/InfoPlist.strings | 6 - WatchApp/nb.lproj/Interface.strings | 52 +- WatchApp/nl.lproj/InfoPlist.strings | 6 - WatchApp/nl.lproj/Interface.strings | 52 +- WatchApp/pl.lproj/InfoPlist.strings | 6 - WatchApp/pl.lproj/Interface.strings | 58 +- WatchApp/pt-BR.lproj/Interface.strings | 120 +++++ WatchApp/ro.lproj/Interface.strings | 120 +++++ WatchApp/ru.lproj/InfoPlist.strings | 6 - WatchApp/ru.lproj/Interface.strings | 56 +- WatchApp/sv.lproj/Interface.strings | 120 +++++ WatchApp/vi.lproj/Interface.strings | 120 +++++ WatchApp/zh-Hans.lproj/InfoPlist.strings | 6 - WatchApp/zh-Hans.lproj/Interface.strings | 52 +- 280 files changed, 9457 insertions(+), 1857 deletions(-) create mode 100644 Common/da.lproj/Intents.strings create mode 100644 Common/da.lproj/Localizable.strings create mode 100644 Common/de.lproj/Localizable.strings create mode 100644 Common/en.lproj/Localizable.strings create mode 100644 Common/es.lproj/Localizable.strings create mode 100644 Common/fi.lproj/Intents.strings create mode 100644 Common/fi.lproj/Localizable.strings create mode 100644 Common/fr.lproj/Localizable.strings create mode 100644 Common/it.lproj/Localizable.strings create mode 100644 Common/ja.lproj/Intents.strings create mode 100644 Common/ja.lproj/Localizable.strings create mode 100644 Common/nb.lproj/Localizable.strings create mode 100644 Common/nl.lproj/Localizable.strings create mode 100644 Common/pl.lproj/Localizable.strings create mode 100644 Common/pt-BR.lproj/Intents.strings create mode 100644 Common/pt-BR.lproj/Localizable.strings create mode 100644 Common/ro.lproj/Intents.strings create mode 100644 Common/ro.lproj/Localizable.strings create mode 100644 Common/ru.lproj/Localizable.strings create mode 100644 Common/sv.lproj/Intents.strings create mode 100644 Common/sv.lproj/Localizable.strings create mode 100644 Common/vi.lproj/Intents.strings create mode 100644 Common/vi.lproj/Localizable.strings create mode 100644 Common/zh-Hans.lproj/Localizable.strings create mode 100644 DoseMathTests/da.lproj/Localizable.strings delete mode 100644 DoseMathTests/de.lproj/InfoPlist.strings delete mode 100644 DoseMathTests/es.lproj/InfoPlist.strings create mode 100644 DoseMathTests/fi.lproj/Localizable.strings delete mode 100644 DoseMathTests/fr.lproj/InfoPlist.strings delete mode 100644 DoseMathTests/it.lproj/InfoPlist.strings create mode 100644 DoseMathTests/ja.lproj/Localizable.strings delete mode 100644 DoseMathTests/nb.lproj/InfoPlist.strings delete mode 100644 DoseMathTests/nl.lproj/InfoPlist.strings delete mode 100644 DoseMathTests/pl.lproj/InfoPlist.strings create mode 100644 DoseMathTests/pt-BR.lproj/Localizable.strings create mode 100644 DoseMathTests/ro.lproj/Localizable.strings delete mode 100644 DoseMathTests/ru.lproj/InfoPlist.strings create mode 100644 DoseMathTests/sv.lproj/Localizable.strings create mode 100644 DoseMathTests/vi.lproj/Localizable.strings delete mode 100644 DoseMathTests/zh-Hans.lproj/InfoPlist.strings create mode 100644 Learn/da.lproj/Localizable.strings create mode 100644 Learn/da.lproj/Main.strings create mode 100644 Learn/de.lproj/Localizable.strings create mode 100644 Learn/de.lproj/Main.strings create mode 100644 Learn/en.lproj/Localizable.strings create mode 100644 Learn/en.lproj/Main.strings create mode 100644 Learn/es.lproj/Localizable.strings create mode 100644 Learn/es.lproj/Main.strings create mode 100644 Learn/fi.lproj/Localizable.strings create mode 100644 Learn/fi.lproj/Main.strings create mode 100644 Learn/fr.lproj/Localizable.strings create mode 100644 Learn/fr.lproj/Main.strings create mode 100644 Learn/it.lproj/Localizable.strings create mode 100644 Learn/it.lproj/Main.strings create mode 100644 Learn/ja.lproj/Localizable.strings create mode 100644 Learn/ja.lproj/Main.strings create mode 100644 Learn/nb.lproj/Localizable.strings create mode 100644 Learn/nb.lproj/Main.strings create mode 100644 Learn/nl.lproj/Localizable.strings create mode 100644 Learn/nl.lproj/Main.strings create mode 100644 Learn/pl.lproj/Localizable.strings create mode 100644 Learn/pl.lproj/Main.strings create mode 100644 Learn/pt-BR.lproj/Localizable.strings create mode 100644 Learn/pt-BR.lproj/Main.strings create mode 100644 Learn/ro.lproj/Localizable.strings create mode 100644 Learn/ro.lproj/Main.strings create mode 100644 Learn/ru.lproj/Localizable.strings create mode 100644 Learn/ru.lproj/Main.strings create mode 100644 Learn/sv.lproj/Localizable.strings create mode 100644 Learn/sv.lproj/Main.strings create mode 100644 Learn/vi.lproj/Localizable.strings create mode 100644 Learn/vi.lproj/Main.strings create mode 100644 Learn/zh-Hans.lproj/Localizable.strings create mode 100644 Learn/zh-Hans.lproj/Main.strings create mode 100644 Loop Status Extension/da.lproj/Localizable.strings create mode 100644 Loop Status Extension/da.lproj/MainInterface.strings delete mode 100644 Loop Status Extension/de.lproj/InfoPlist.strings create mode 100644 Loop Status Extension/en.lproj/Localizable.strings create mode 100644 Loop Status Extension/en.lproj/MainInterface.strings delete mode 100644 Loop Status Extension/es.lproj/InfoPlist.strings create mode 100644 Loop Status Extension/fi.lproj/Localizable.strings create mode 100644 Loop Status Extension/fi.lproj/MainInterface.strings delete mode 100644 Loop Status Extension/fr.lproj/InfoPlist.strings delete mode 100644 Loop Status Extension/it.lproj/InfoPlist.strings create mode 100644 Loop Status Extension/ja.lproj/Localizable.strings create mode 100644 Loop Status Extension/ja.lproj/MainInterface.strings delete mode 100644 Loop Status Extension/nb.lproj/InfoPlist.strings delete mode 100644 Loop Status Extension/nl.lproj/InfoPlist.strings delete mode 100644 Loop Status Extension/pl.lproj/InfoPlist.strings create mode 100644 Loop Status Extension/pt-BR.lproj/Localizable.strings create mode 100644 Loop Status Extension/pt-BR.lproj/MainInterface.strings create mode 100644 Loop Status Extension/ro.lproj/Localizable.strings create mode 100644 Loop Status Extension/ro.lproj/MainInterface.strings delete mode 100644 Loop Status Extension/ru.lproj/InfoPlist.strings create mode 100644 Loop Status Extension/sv.lproj/Localizable.strings create mode 100644 Loop Status Extension/sv.lproj/MainInterface.strings create mode 100644 Loop Status Extension/vi.lproj/Localizable.strings create mode 100644 Loop Status Extension/vi.lproj/MainInterface.strings delete mode 100644 Loop Status Extension/zh-Hans.lproj/InfoPlist.strings create mode 100644 Loop/da.lproj/InfoPlist.strings create mode 100644 Loop/da.lproj/Localizable.strings create mode 100644 Loop/da.lproj/Main.strings delete mode 100644 Loop/de.lproj/LaunchScreen.strings create mode 100644 Loop/en.lproj/InfoPlist.strings create mode 100644 Loop/en.lproj/Localizable.strings create mode 100644 Loop/en.lproj/Main.strings delete mode 100644 Loop/es.lproj/LaunchScreen.strings create mode 100644 Loop/fi.lproj/InfoPlist.strings create mode 100644 Loop/fi.lproj/Localizable.strings create mode 100644 Loop/fi.lproj/Main.strings delete mode 100644 Loop/fr.lproj/LaunchScreen.strings delete mode 100644 Loop/it.lproj/LaunchScreen.strings create mode 100644 Loop/ja.lproj/InfoPlist.strings create mode 100644 Loop/ja.lproj/Localizable.strings create mode 100644 Loop/ja.lproj/Main.strings delete mode 100644 Loop/nb.lproj/LaunchScreen.strings delete mode 100644 Loop/nl.lproj/LaunchScreen.strings delete mode 100644 Loop/pl.lproj/LaunchScreen.strings create mode 100644 Loop/pt-BR.lproj/InfoPlist.strings create mode 100644 Loop/pt-BR.lproj/Localizable.strings create mode 100644 Loop/pt-BR.lproj/Main.strings create mode 100644 Loop/ro.lproj/InfoPlist.strings create mode 100644 Loop/ro.lproj/Localizable.strings create mode 100644 Loop/ro.lproj/Main.strings delete mode 100644 Loop/ru.lproj/LaunchScreen.strings create mode 100644 Loop/sv.lproj/InfoPlist.strings create mode 100644 Loop/sv.lproj/Localizable.strings create mode 100644 Loop/sv.lproj/Main.strings create mode 100644 Loop/vi.lproj/InfoPlist.strings create mode 100644 Loop/vi.lproj/Localizable.strings create mode 100644 Loop/vi.lproj/Main.strings delete mode 100644 Loop/zh-Hans.lproj/LaunchScreen.strings delete mode 100644 LoopTests/de.lproj/InfoPlist.strings delete mode 100644 LoopTests/es.lproj/InfoPlist.strings delete mode 100644 LoopTests/fr.lproj/InfoPlist.strings delete mode 100644 LoopTests/it.lproj/InfoPlist.strings delete mode 100644 LoopTests/nb.lproj/InfoPlist.strings delete mode 100644 LoopTests/nl.lproj/InfoPlist.strings delete mode 100644 LoopTests/pl.lproj/InfoPlist.strings delete mode 100644 LoopTests/ru.lproj/InfoPlist.strings delete mode 100644 LoopTests/zh-Hans.lproj/InfoPlist.strings create mode 100644 LoopUI/da.lproj/Localizable.strings delete mode 100644 LoopUI/de.lproj/InfoPlist.strings create mode 100644 LoopUI/en.lproj/Localizable.strings delete mode 100644 LoopUI/es.lproj/InfoPlist.strings create mode 100644 LoopUI/fi.lproj/Localizable.strings delete mode 100644 LoopUI/fr.lproj/InfoPlist.strings delete mode 100644 LoopUI/it.lproj/InfoPlist.strings create mode 100644 LoopUI/ja.lproj/Localizable.strings delete mode 100644 LoopUI/nb.lproj/InfoPlist.strings delete mode 100644 LoopUI/nl.lproj/InfoPlist.strings delete mode 100644 LoopUI/pl.lproj/InfoPlist.strings create mode 100644 LoopUI/pt-BR.lproj/Localizable.strings create mode 100644 LoopUI/ro.lproj/Localizable.strings delete mode 100644 LoopUI/ru.lproj/InfoPlist.strings create mode 100644 LoopUI/sv.lproj/Localizable.strings create mode 100644 LoopUI/vi.lproj/Localizable.strings delete mode 100644 LoopUI/zh-Hans.lproj/InfoPlist.strings create mode 100644 WatchApp Extension/da.lproj/Localizable.strings delete mode 100644 WatchApp Extension/de.lproj/InfoPlist.strings delete mode 100644 WatchApp Extension/de.lproj/ckcomplication.strings create mode 100644 WatchApp Extension/en.lproj/Localizable.strings delete mode 100644 WatchApp Extension/es.lproj/InfoPlist.strings delete mode 100644 WatchApp Extension/es.lproj/ckcomplication.strings create mode 100644 WatchApp Extension/fi.lproj/Localizable.strings delete mode 100644 WatchApp Extension/fr.lproj/InfoPlist.strings delete mode 100644 WatchApp Extension/fr.lproj/ckcomplication.strings delete mode 100644 WatchApp Extension/it.lproj/InfoPlist.strings delete mode 100644 WatchApp Extension/it.lproj/ckcomplication.strings create mode 100644 WatchApp Extension/ja.lproj/Localizable.strings delete mode 100644 WatchApp Extension/nb.lproj/InfoPlist.strings delete mode 100644 WatchApp Extension/nb.lproj/ckcomplication.strings delete mode 100644 WatchApp Extension/nl.lproj/InfoPlist.strings delete mode 100644 WatchApp Extension/nl.lproj/ckcomplication.strings delete mode 100644 WatchApp Extension/pl.lproj/InfoPlist.strings delete mode 100644 WatchApp Extension/pl.lproj/ckcomplication.strings create mode 100644 WatchApp Extension/pt-BR.lproj/Localizable.strings create mode 100644 WatchApp Extension/ro.lproj/Localizable.strings delete mode 100644 WatchApp Extension/ru.lproj/InfoPlist.strings delete mode 100644 WatchApp Extension/ru.lproj/ckcomplication.strings create mode 100644 WatchApp Extension/sv.lproj/Localizable.strings create mode 100644 WatchApp Extension/vi.lproj/Localizable.strings delete mode 100644 WatchApp Extension/zh-Hans.lproj/InfoPlist.strings delete mode 100644 WatchApp Extension/zh-Hans.lproj/ckcomplication.strings create mode 100644 WatchApp/da.lproj/Interface.strings delete mode 100644 WatchApp/de.lproj/InfoPlist.strings create mode 100644 WatchApp/en.lproj/Interface.strings delete mode 100644 WatchApp/es.lproj/InfoPlist.strings create mode 100644 WatchApp/fi.lproj/Interface.strings delete mode 100644 WatchApp/fr.lproj/InfoPlist.strings delete mode 100644 WatchApp/it.lproj/InfoPlist.strings create mode 100644 WatchApp/ja.lproj/Interface.strings delete mode 100644 WatchApp/nb.lproj/InfoPlist.strings delete mode 100644 WatchApp/nl.lproj/InfoPlist.strings delete mode 100644 WatchApp/pl.lproj/InfoPlist.strings create mode 100644 WatchApp/pt-BR.lproj/Interface.strings create mode 100644 WatchApp/ro.lproj/Interface.strings delete mode 100644 WatchApp/ru.lproj/InfoPlist.strings create mode 100644 WatchApp/sv.lproj/Interface.strings create mode 100644 WatchApp/vi.lproj/Interface.strings delete mode 100644 WatchApp/zh-Hans.lproj/InfoPlist.strings diff --git a/Common/da.lproj/Intents.strings b/Common/da.lproj/Intents.strings new file mode 100644 index 0000000000..674ad2a182 --- /dev/null +++ b/Common/da.lproj/Intents.strings @@ -0,0 +1,9 @@ +/* (No Comment) */ +"80eo5o" = "Tilføj kulhydrater"; + +/* (No Comment) */ +"OcNxIj" = "Tilføj kulhydrater"; + +/* (No Comment) */ +"yc02Yq" = "Tilføj kulhydrater til Loop."; + diff --git a/Common/da.lproj/Localizable.strings b/Common/da.lproj/Localizable.strings new file mode 100644 index 0000000000..d19cbd7249 --- /dev/null +++ b/Common/da.lproj/Localizable.strings @@ -0,0 +1,54 @@ +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Tilføj kulhydrater"; + +/* Title of the button to begin lesson execution */ +"Continue" = "Fortsæt"; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* Placeholder for upper range entry */ +"Maximum" = "Maximum"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* Placeholder for lower range entry */ +"Minimum" = "Minimum"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Lesson title */ +"Modal Day" = "Modal Dag"; + +/* Lesson result text for no data */ +"No data available" = "Ingen data tilgængelige"; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* Section title for glucose range */ +"Range" = "Interval"; + +/* Title of config entry */ +"Start Date" = "Start Dato"; + +/* Lesson title */ +"Time in Range" = "Tme in Range"; + +/* The short unit display string for international units of insulin */ +"U" = "E"; + +/* Lesson subtitle */ +"Visualizes the most frequent glucose values by time of day" = "Visualiserer de hyppigste blodsukker værdier fordelt på dagen"; + +/* Unit string for a count of calendar weeks */ +"Weeks" = "Uger"; + diff --git a/Common/de.lproj/Intents.strings b/Common/de.lproj/Intents.strings index 46410e152e..089a333bfa 100644 --- a/Common/de.lproj/Intents.strings +++ b/Common/de.lproj/Intents.strings @@ -1,6 +1,9 @@ -"80eo5o" = "Add Carb Entry"; +/* (No Comment) */ +"80eo5o" = "KH-Eintrag hinzufügen"; -"OcNxIj" = "Add Carb Entry"; +/* (No Comment) */ +"OcNxIj" = "KH-Eintrag hinzufügen"; -"yc02Yq" = "Add a carb entry to Loop"; +/* (No Comment) */ +"yc02Yq" = "Fügen Sie einen KH-Eintrag zu Loop hinzu."; diff --git a/Common/de.lproj/Localizable.strings b/Common/de.lproj/Localizable.strings new file mode 100644 index 0000000000..5f6a4f54d8 --- /dev/null +++ b/Common/de.lproj/Localizable.strings @@ -0,0 +1,24 @@ +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "KH-Eintrag hinzufügen"; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* The short unit display string for international units of insulin */ +"U" = "IE"; + diff --git a/Common/en.lproj/Localizable.strings b/Common/en.lproj/Localizable.strings new file mode 100644 index 0000000000..e0fb9dff1b --- /dev/null +++ b/Common/en.lproj/Localizable.strings @@ -0,0 +1,24 @@ +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Add Carb Entry"; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* The short unit display string for international units of insulin */ +"U" = "U"; + diff --git a/Common/es.lproj/Intents.strings b/Common/es.lproj/Intents.strings index 34a25016e7..a2ede4fc26 100644 --- a/Common/es.lproj/Intents.strings +++ b/Common/es.lproj/Intents.strings @@ -1,8 +1,8 @@ /* INIntentTitle */ -"80eo5o" = "Agregar entrada de carbohidratos"; +"80eo5o" = "Agregar Registro de Carbs"; /* INIntentParameterCombinationTitle */ -"OcNxIj" = "Agregar entrada de carbohidratos"; +"OcNxIj" = "Agregar Registro de Carbs"; /* INIntentDescription */ -"yc02Yq" = "Add a carb entry to Loop"; +"yc02Yq" = "Agregar registro de carbs a Loop"; diff --git a/Common/es.lproj/Localizable.strings b/Common/es.lproj/Localizable.strings new file mode 100644 index 0000000000..61b37df354 --- /dev/null +++ b/Common/es.lproj/Localizable.strings @@ -0,0 +1,23 @@ +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Agregar Registro de Carbs"; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* The short unit display string for international units of insulin */ +"U" = "U"; diff --git a/Common/fi.lproj/Intents.strings b/Common/fi.lproj/Intents.strings new file mode 100644 index 0000000000..b51583e515 --- /dev/null +++ b/Common/fi.lproj/Intents.strings @@ -0,0 +1,9 @@ +/* (No Comment) */ +"80eo5o" = "Lisää hiilihydraatteja"; + +/* (No Comment) */ +"OcNxIj" = "Lisää hiilihydraatteja"; + +/* (No Comment) */ +"yc02Yq" = "Lisää hiilihydraatteja Loopiin"; + diff --git a/Common/fi.lproj/Localizable.strings b/Common/fi.lproj/Localizable.strings new file mode 100644 index 0000000000..4601e886dc --- /dev/null +++ b/Common/fi.lproj/Localizable.strings @@ -0,0 +1,57 @@ +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Lisää hiilihydraatteja"; + +/* Lesson subtitle */ +"Computes the percentage of glucose measurements within a specified range" = "Laskee glukoosimittausten prosenttimäärän määritellyllä alueella"; + +/* Title of the button to begin lesson execution */ +"Continue" = "Jatka"; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* Placeholder for upper range entry */ +"Maximum" = "Maksimi"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* Placeholder for lower range entry */ +"Minimum" = "Minimi"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Lesson title */ +"Modal Day" = "Tyypillinen päivä"; + +/* Lesson result text for no data */ +"No data available" = "Tietoja ei saatavilla"; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* Section title for glucose range */ +"Range" = "Alue"; + +/* Title of config entry */ +"Start Date" = "Aloitusaika"; + +/* Lesson title */ +"Time in Range" = "Aika tavoitealueella"; + +/* The short unit display string for international units of insulin */ +"U" = "U"; + +/* Lesson subtitle */ +"Visualizes the most frequent glucose values by time of day" = "Näyttää yleisimmät glukoosiarvot vuorokaudenajan mukaan"; + +/* Unit string for a count of calendar weeks */ +"Weeks" = "Viikkoa"; + diff --git a/Common/fr.lproj/Intents.strings b/Common/fr.lproj/Intents.strings index 46410e152e..d1a73fa03a 100644 --- a/Common/fr.lproj/Intents.strings +++ b/Common/fr.lproj/Intents.strings @@ -1,6 +1,9 @@ -"80eo5o" = "Add Carb Entry"; +/* (No Comment) */ +"80eo5o" = "Ajouter des glucides"; -"OcNxIj" = "Add Carb Entry"; +/* (No Comment) */ +"OcNxIj" = "Ajouter des glucides"; -"yc02Yq" = "Add a carb entry to Loop"; +/* (No Comment) */ +"yc02Yq" = "Ajouter des glucides à Loop"; diff --git a/Common/fr.lproj/Localizable.strings b/Common/fr.lproj/Localizable.strings new file mode 100644 index 0000000000..29ab8e8030 --- /dev/null +++ b/Common/fr.lproj/Localizable.strings @@ -0,0 +1,24 @@ +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Ajouter des glucides"; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* The short unit display string for international units of insulin */ +"U" = "U"; + diff --git a/Common/it.lproj/Intents.strings b/Common/it.lproj/Intents.strings index 46410e152e..7b32eb3aba 100644 --- a/Common/it.lproj/Intents.strings +++ b/Common/it.lproj/Intents.strings @@ -1,6 +1,9 @@ -"80eo5o" = "Add Carb Entry"; +/* (No Comment) */ +"80eo5o" = "Aggiungi carboidrati assunti"; -"OcNxIj" = "Add Carb Entry"; +/* (No Comment) */ +"OcNxIj" = "Aggiungi carboidrati assunti"; -"yc02Yq" = "Add a carb entry to Loop"; +/* (No Comment) */ +"yc02Yq" = "Aggiungi carboidrati assunti a Loop"; diff --git a/Common/it.lproj/Localizable.strings b/Common/it.lproj/Localizable.strings new file mode 100644 index 0000000000..6da1a77ad0 --- /dev/null +++ b/Common/it.lproj/Localizable.strings @@ -0,0 +1,54 @@ +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Aggiungi carboidrati assunti"; + +/* Title of the button to begin lesson execution */ +"Continue" = "Continua"; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* Placeholder for upper range entry */ +"Maximum" = "Massimo"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* Placeholder for lower range entry */ +"Minimum" = "Minimo"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Lesson title */ +"Modal Day" = "Modalità giornaliera"; + +/* Lesson result text for no data */ +"No data available" = "Nessun dato disponibile"; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* Section title for glucose range */ +"Range" = "Intervallo"; + +/* Title of config entry */ +"Start Date" = "Data di inizio"; + +/* Lesson title */ +"Time in Range" = "Tempo nell’intervallo"; + +/* The short unit display string for international units of insulin */ +"U" = "U"; + +/* Lesson subtitle */ +"Visualizes the most frequent glucose values by time of day" = "Visualizza i valori di glucosio più frequenti per ora del giorno"; + +/* Unit string for a count of calendar weeks */ +"Weeks" = "Settimane"; + diff --git a/Common/ja.lproj/Intents.strings b/Common/ja.lproj/Intents.strings new file mode 100644 index 0000000000..79c1af0d9d --- /dev/null +++ b/Common/ja.lproj/Intents.strings @@ -0,0 +1,9 @@ +/* (No Comment) */ +"80eo5o" = "カーボを追加"; + +/* (No Comment) */ +"OcNxIj" = "カーボを追加"; + +/* (No Comment) */ +"yc02Yq" = "ループにカーボを追加"; + diff --git a/Common/ja.lproj/Localizable.strings b/Common/ja.lproj/Localizable.strings new file mode 100644 index 0000000000..807227d02a --- /dev/null +++ b/Common/ja.lproj/Localizable.strings @@ -0,0 +1,54 @@ +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "カーボを追加"; + +/* Lesson subtitle */ +"Computes the percentage of glucose measurements within a specified range" = "指定範囲内の測定値の割合を算出"; + +/* Title of the button to begin lesson execution */ +"Continue" = "次へ"; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* Placeholder for upper range entry */ +"Maximum" = "最大"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* Placeholder for lower range entry */ +"Minimum" = "最小"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Lesson result text for no data */ +"No data available" = "データがありません"; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* Section title for glucose range */ +"Range" = "範囲"; + +/* Title of config entry */ +"Start Date" = "開始日"; + +/* Lesson title */ +"Time in Range" = "タイムインレンジ"; + +/* The short unit display string for international units of insulin */ +"U" = "U"; + +/* Lesson subtitle */ +"Visualizes the most frequent glucose values by time of day" = "頻度の高い測定値を時間ごとに表示"; + +/* Unit string for a count of calendar weeks */ +"Weeks" = "週"; + diff --git a/Common/nb.lproj/Intents.strings b/Common/nb.lproj/Intents.strings index 46410e152e..494fb5be3c 100644 --- a/Common/nb.lproj/Intents.strings +++ b/Common/nb.lproj/Intents.strings @@ -1,6 +1,9 @@ -"80eo5o" = "Add Carb Entry"; +/* (No Comment) */ +"80eo5o" = "Legg til karbohydrater"; -"OcNxIj" = "Add Carb Entry"; +/* (No Comment) */ +"OcNxIj" = "Legg til karbohydrater"; -"yc02Yq" = "Add a carb entry to Loop"; +/* (No Comment) */ +"yc02Yq" = "Legg til karbohydrater i Loop"; diff --git a/Common/nb.lproj/Localizable.strings b/Common/nb.lproj/Localizable.strings new file mode 100644 index 0000000000..32bdd8539c --- /dev/null +++ b/Common/nb.lproj/Localizable.strings @@ -0,0 +1,54 @@ +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Legg til karbohydrater"; + +/* Title of the button to begin lesson execution */ +"Continue" = "Fortsett"; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* Placeholder for upper range entry */ +"Maximum" = "Maksimum"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* Placeholder for lower range entry */ +"Minimum" = "Minimum"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Lesson title */ +"Modal Day" = "Modal dag"; + +/* Lesson result text for no data */ +"No data available" = "Ingen data tilgjengelig"; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* Section title for glucose range */ +"Range" = "Målområde"; + +/* Title of config entry */ +"Start Date" = "Startdato"; + +/* Lesson title */ +"Time in Range" = "Tid i målområdet"; + +/* The short unit display string for international units of insulin */ +"U" = "E"; + +/* Lesson subtitle */ +"Visualizes the most frequent glucose values by time of day" = "Visualiser de nyeste blodsukkerverdier etter tid på døgnet"; + +/* Unit string for a count of calendar weeks */ +"Weeks" = "Uker"; + diff --git a/Common/nl.lproj/Intents.strings b/Common/nl.lproj/Intents.strings index 46410e152e..86c99c925a 100644 --- a/Common/nl.lproj/Intents.strings +++ b/Common/nl.lproj/Intents.strings @@ -1,6 +1,9 @@ -"80eo5o" = "Add Carb Entry"; +/* (No Comment) */ +"80eo5o" = "Voeg koolhydraten toe"; -"OcNxIj" = "Add Carb Entry"; +/* (No Comment) */ +"OcNxIj" = "Voeg koolhydraten toe"; -"yc02Yq" = "Add a carb entry to Loop"; +/* (No Comment) */ +"yc02Yq" = "Voeg koolhydraten toe aan Loop"; diff --git a/Common/nl.lproj/Localizable.strings b/Common/nl.lproj/Localizable.strings new file mode 100644 index 0000000000..e98674e351 --- /dev/null +++ b/Common/nl.lproj/Localizable.strings @@ -0,0 +1,57 @@ +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Voeg koolhydraten toe"; + +/* Lesson subtitle */ +"Computes the percentage of glucose measurements within a specified range" = "Berekent het percentage glucosemetingen in een specifiek bereik"; + +/* Title of the button to begin lesson execution */ +"Continue" = "Ga verder"; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* Placeholder for upper range entry */ +"Maximum" = "Maximum"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* Placeholder for lower range entry */ +"Minimum" = "Minimum"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Lesson title */ +"Modal Day" = "Modale dag"; + +/* Lesson result text for no data */ +"No data available" = "Geen data beschikbaar"; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* Section title for glucose range */ +"Range" = "Bereik"; + +/* Title of config entry */ +"Start Date" = "Start datum"; + +/* Lesson title */ +"Time in Range" = "Tijd binnen bereik"; + +/* The short unit display string for international units of insulin */ +"U" = "E"; + +/* Lesson subtitle */ +"Visualizes the most frequent glucose values by time of day" = "Geeft de meest voorkomende glucose waardes weer per moment van de dag "; + +/* Unit string for a count of calendar weeks */ +"Weeks" = "Weken"; + diff --git a/Common/pl.lproj/Intents.strings b/Common/pl.lproj/Intents.strings index 46410e152e..5553d89cde 100644 --- a/Common/pl.lproj/Intents.strings +++ b/Common/pl.lproj/Intents.strings @@ -1,6 +1,8 @@ +/* INIntentTitle */ "80eo5o" = "Add Carb Entry"; +/* INIntentParameterCombinationTitle */ "OcNxIj" = "Add Carb Entry"; +/* INIntentDescription */ "yc02Yq" = "Add a carb entry to Loop"; - diff --git a/Common/pl.lproj/Localizable.strings b/Common/pl.lproj/Localizable.strings new file mode 100644 index 0000000000..f630a70c5d --- /dev/null +++ b/Common/pl.lproj/Localizable.strings @@ -0,0 +1,23 @@ +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Dodaj pozycję dla węglowodanów"; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* The short unit display string for international units of insulin */ +"U" = "J"; diff --git a/Common/pt-BR.lproj/Intents.strings b/Common/pt-BR.lproj/Intents.strings new file mode 100644 index 0000000000..71d0f90730 --- /dev/null +++ b/Common/pt-BR.lproj/Intents.strings @@ -0,0 +1,9 @@ +/* (No Comment) */ +"80eo5o" = "Adicionar Carboidratos"; + +/* (No Comment) */ +"OcNxIj" = "Adicionar Carboidratos"; + +/* (No Comment) */ +"yc02Yq" = "Adicionar Carboidratos ao Loop"; + diff --git a/Common/pt-BR.lproj/Localizable.strings b/Common/pt-BR.lproj/Localizable.strings new file mode 100644 index 0000000000..3d70695dd1 --- /dev/null +++ b/Common/pt-BR.lproj/Localizable.strings @@ -0,0 +1,54 @@ +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Adicionar Carboidratos"; + +/* Title of the button to begin lesson execution */ +"Continue" = "Continuar"; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* Placeholder for upper range entry */ +"Maximum" = "Máximo"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* Placeholder for lower range entry */ +"Minimum" = "Mínimo"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Lesson title */ +"Modal Day" = "Dia Modal"; + +/* Lesson result text for no data */ +"No data available" = "Não há dados disponíveis"; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* Section title for glucose range */ +"Range" = "Variação"; + +/* Title of config entry */ +"Start Date" = "Data de Início"; + +/* Lesson title */ +"Time in Range" = "Tempo na Meta"; + +/* The short unit display string for international units of insulin */ +"U" = "U"; + +/* Lesson subtitle */ +"Visualizes the most frequent glucose values by time of day" = "Visualiza os valores de glicose mais frequentes por hora do dia"; + +/* Unit string for a count of calendar weeks */ +"Weeks" = "Semanas"; + diff --git a/Common/ro.lproj/Intents.strings b/Common/ro.lproj/Intents.strings new file mode 100644 index 0000000000..112c6f32d5 --- /dev/null +++ b/Common/ro.lproj/Intents.strings @@ -0,0 +1,9 @@ +/* (No Comment) */ +"80eo5o" = "Adăugare carbohidrați"; + +/* (No Comment) */ +"OcNxIj" = "Adăugare carbohidrați"; + +/* (No Comment) */ +"yc02Yq" = "Adaugă carbohidrați în Loop"; + diff --git a/Common/ro.lproj/Localizable.strings b/Common/ro.lproj/Localizable.strings new file mode 100644 index 0000000000..13e8f4fd76 --- /dev/null +++ b/Common/ro.lproj/Localizable.strings @@ -0,0 +1,57 @@ +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Adăugare carbohidrați"; + +/* Lesson subtitle */ +"Computes the percentage of glucose measurements within a specified range" = "Calculează procentul măsurătorilor glicemice dintr-un interval specificat"; + +/* Title of the button to begin lesson execution */ +"Continue" = "Continuă"; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* Placeholder for upper range entry */ +"Maximum" = "Maxim"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* Placeholder for lower range entry */ +"Minimum" = "Minim"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Lesson title */ +"Modal Day" = "Zi modală"; + +/* Lesson result text for no data */ +"No data available" = "Date inexistente"; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* Section title for glucose range */ +"Range" = "Interval"; + +/* Title of config entry */ +"Start Date" = "Dată inițială"; + +/* Lesson title */ +"Time in Range" = "Timp petrecut în interval"; + +/* The short unit display string for international units of insulin */ +"U" = "U"; + +/* Lesson subtitle */ +"Visualizes the most frequent glucose values by time of day" = "Vizualizează cele mai frecvente valori glicemice în funcție de oră"; + +/* Unit string for a count of calendar weeks */ +"Weeks" = "Săptămâni"; + diff --git a/Common/ru.lproj/Intents.strings b/Common/ru.lproj/Intents.strings index e3311d49c4..5ec019a755 100644 --- a/Common/ru.lproj/Intents.strings +++ b/Common/ru.lproj/Intents.strings @@ -1,8 +1,9 @@ -/* INIntentTitle */ +/* (No Comment) */ "80eo5o" = "Добавить запись углеводов"; -/* INIntentParameterCombinationTitle */ +/* (No Comment) */ "OcNxIj" = "Добавить запись углеводов"; -/* INIntentDescription */ -"yc02Yq" = "Add a carb entry to Loop"; +/* (No Comment) */ +"yc02Yq" = "Добавьте запись углеводов в алгоритм цикла"; + diff --git a/Common/ru.lproj/Localizable.strings b/Common/ru.lproj/Localizable.strings new file mode 100644 index 0000000000..bdee38ba11 --- /dev/null +++ b/Common/ru.lproj/Localizable.strings @@ -0,0 +1,24 @@ +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Введите углеводы"; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "г"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "мг/дл"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "ммоль/л"; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* The short unit display string for international units of insulin */ +"U" = "ед"; + diff --git a/Common/sv.lproj/Intents.strings b/Common/sv.lproj/Intents.strings new file mode 100644 index 0000000000..f4ec534f26 --- /dev/null +++ b/Common/sv.lproj/Intents.strings @@ -0,0 +1,9 @@ +/* (No Comment) */ +"80eo5o" = "Lägg till kolhydrater"; + +/* (No Comment) */ +"OcNxIj" = "Lägg till kolhydrater"; + +/* (No Comment) */ +"yc02Yq" = "Lägg till kolhydrater för att loopa"; + diff --git a/Common/sv.lproj/Localizable.strings b/Common/sv.lproj/Localizable.strings new file mode 100644 index 0000000000..6844bcc037 --- /dev/null +++ b/Common/sv.lproj/Localizable.strings @@ -0,0 +1,57 @@ +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Lägg till kolhydrater"; + +/* Lesson subtitle */ +"Computes the percentage of glucose measurements within a specified range" = "Beräknar procentandelen glukosmätningar inom ett specifikt målvärde"; + +/* Title of the button to begin lesson execution */ +"Continue" = "Fortsätt"; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* Placeholder for upper range entry */ +"Maximum" = "Maximum"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* Placeholder for lower range entry */ +"Minimum" = "Minimum"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Lesson title */ +"Modal Day" = "Genomsnittlig dag"; + +/* Lesson result text for no data */ +"No data available" = "Ingen data tillgänglig"; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* Section title for glucose range */ +"Range" = "Målvärde"; + +/* Title of config entry */ +"Start Date" = "Starttid"; + +/* Lesson title */ +"Time in Range" = "Tid inom målvärde"; + +/* The short unit display string for international units of insulin */ +"U" = "E"; + +/* Lesson subtitle */ +"Visualizes the most frequent glucose values by time of day" = "Visar de vanligaste glukosvärdena under olika tider på dagen"; + +/* Unit string for a count of calendar weeks */ +"Weeks" = "Veckor"; + diff --git a/Common/vi.lproj/Intents.strings b/Common/vi.lproj/Intents.strings new file mode 100644 index 0000000000..5bd02d75d9 --- /dev/null +++ b/Common/vi.lproj/Intents.strings @@ -0,0 +1,9 @@ +/* (No Comment) */ +"80eo5o" = "Khai báo khối lượng Carb"; + +/* (No Comment) */ +"OcNxIj" = "Khai báo khối lượng Carb"; + +/* (No Comment) */ +"yc02Yq" = "Khai báo khối lượng Carb cho Loop"; + diff --git a/Common/vi.lproj/Localizable.strings b/Common/vi.lproj/Localizable.strings new file mode 100644 index 0000000000..b97378302c --- /dev/null +++ b/Common/vi.lproj/Localizable.strings @@ -0,0 +1,24 @@ +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Khai báo khối lượng Carb"; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* The short unit display string for international units of insulin */ +"U" = "U"; + diff --git a/Common/zh-Hans.lproj/Intents.strings b/Common/zh-Hans.lproj/Intents.strings index 46410e152e..16f21e6b21 100644 --- a/Common/zh-Hans.lproj/Intents.strings +++ b/Common/zh-Hans.lproj/Intents.strings @@ -1,6 +1,9 @@ -"80eo5o" = "Add Carb Entry"; +/* (No Comment) */ +"80eo5o" = "添加碳水化合物"; -"OcNxIj" = "Add Carb Entry"; +/* (No Comment) */ +"OcNxIj" = "添加碳水化合物"; -"yc02Yq" = "Add a carb entry to Loop"; +/* (No Comment) */ +"yc02Yq" = "将碳水化合物添加到Loop"; diff --git a/Common/zh-Hans.lproj/Localizable.strings b/Common/zh-Hans.lproj/Localizable.strings new file mode 100644 index 0000000000..be5965c657 --- /dev/null +++ b/Common/zh-Hans.lproj/Localizable.strings @@ -0,0 +1,54 @@ +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "添加碳水化合物"; + +/* Lesson subtitle */ +"Computes the percentage of glucose measurements within a specified range" = "计算在指定范围内的血糖测量值的百分比"; + +/* Title of the button to begin lesson execution */ +"Continue" = "继续"; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "克"; + +/* Placeholder for upper range entry */ +"Maximum" = "最大"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "毫克/分升"; + +/* Placeholder for lower range entry */ +"Minimum" = "最小"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "毫摩尔/升"; + +/* Lesson result text for no data */ +"No data available" = "无数据"; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* Section title for glucose range */ +"Range" = "范围"; + +/* Title of config entry */ +"Start Date" = "开始日期"; + +/* Lesson title */ +"Time in Range" = "在目标范围的时间"; + +/* The short unit display string for international units of insulin */ +"U" = "单位"; + +/* Lesson subtitle */ +"Visualizes the most frequent glucose values by time of day" = "全天血糖数据"; + +/* Unit string for a count of calendar weeks */ +"Weeks" = "周"; + diff --git a/DoseMathTests/da.lproj/Localizable.strings b/DoseMathTests/da.lproj/Localizable.strings new file mode 100644 index 0000000000..13db793a4f --- /dev/null +++ b/DoseMathTests/da.lproj/Localizable.strings @@ -0,0 +1,27 @@ +/* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ +"Current glucose of %1$@ is below correction range." = "Current glucose of %1$@ is below correction range."; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Message when offering bolus recommendation even though bg is below range and minBG is in future. (1: glucose time)(2: glucose number) */ +"Predicted glucose at %1$@ is %2$@." = "Predicted glucose at %1$@ is %2$@."; + +/* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ +"Predicted glucose of %1$@ is below your suspend threshold setting." = "Predicted glucose of %1$@ is below your suspend threshold setting."; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* The short unit display string for international units of insulin */ +"U" = "E"; + diff --git a/DoseMathTests/de.lproj/InfoPlist.strings b/DoseMathTests/de.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f9040..0000000000 --- a/DoseMathTests/de.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/DoseMathTests/de.lproj/Localizable.strings b/DoseMathTests/de.lproj/Localizable.strings index 33164c4923..b0c9404b8c 100644 --- a/DoseMathTests/de.lproj/Localizable.strings +++ b/DoseMathTests/de.lproj/Localizable.strings @@ -24,3 +24,4 @@ /* The short unit display string for international units of insulin */ "U" = "IE"; + diff --git a/DoseMathTests/es.lproj/InfoPlist.strings b/DoseMathTests/es.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/DoseMathTests/fi.lproj/Localizable.strings b/DoseMathTests/fi.lproj/Localizable.strings new file mode 100644 index 0000000000..95e33d98bb --- /dev/null +++ b/DoseMathTests/fi.lproj/Localizable.strings @@ -0,0 +1,27 @@ +/* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ +"Current glucose of %1$@ is below correction range." = "Current glucose of %1$@ is below correction range."; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Message when offering bolus recommendation even though bg is below range and minBG is in future. (1: glucose time)(2: glucose number) */ +"Predicted glucose at %1$@ is %2$@." = "Predicted glucose at %1$@ is %2$@."; + +/* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ +"Predicted glucose of %1$@ is below your suspend threshold setting." = "Predicted glucose of %1$@ is below your suspend threshold setting."; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* The short unit display string for international units of insulin */ +"U" = "U"; + diff --git a/DoseMathTests/fr.lproj/InfoPlist.strings b/DoseMathTests/fr.lproj/InfoPlist.strings deleted file mode 100644 index 874e8a4532..0000000000 --- a/DoseMathTests/fr.lproj/InfoPlist.strings +++ /dev/null @@ -1 +0,0 @@ -/* No Localized Strings */ diff --git a/DoseMathTests/it.lproj/InfoPlist.strings b/DoseMathTests/it.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f9040..0000000000 --- a/DoseMathTests/it.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/DoseMathTests/ja.lproj/Localizable.strings b/DoseMathTests/ja.lproj/Localizable.strings new file mode 100644 index 0000000000..95e33d98bb --- /dev/null +++ b/DoseMathTests/ja.lproj/Localizable.strings @@ -0,0 +1,27 @@ +/* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ +"Current glucose of %1$@ is below correction range." = "Current glucose of %1$@ is below correction range."; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Message when offering bolus recommendation even though bg is below range and minBG is in future. (1: glucose time)(2: glucose number) */ +"Predicted glucose at %1$@ is %2$@." = "Predicted glucose at %1$@ is %2$@."; + +/* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ +"Predicted glucose of %1$@ is below your suspend threshold setting." = "Predicted glucose of %1$@ is below your suspend threshold setting."; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* The short unit display string for international units of insulin */ +"U" = "U"; + diff --git a/DoseMathTests/nb.lproj/InfoPlist.strings b/DoseMathTests/nb.lproj/InfoPlist.strings deleted file mode 100644 index 874e8a4532..0000000000 --- a/DoseMathTests/nb.lproj/InfoPlist.strings +++ /dev/null @@ -1 +0,0 @@ -/* No Localized Strings */ diff --git a/DoseMathTests/nl.lproj/InfoPlist.strings b/DoseMathTests/nl.lproj/InfoPlist.strings deleted file mode 100644 index 874e8a4532..0000000000 --- a/DoseMathTests/nl.lproj/InfoPlist.strings +++ /dev/null @@ -1 +0,0 @@ -/* No Localized Strings */ diff --git a/DoseMathTests/pl.lproj/InfoPlist.strings b/DoseMathTests/pl.lproj/InfoPlist.strings deleted file mode 100644 index 874e8a4532..0000000000 --- a/DoseMathTests/pl.lproj/InfoPlist.strings +++ /dev/null @@ -1 +0,0 @@ -/* No Localized Strings */ diff --git a/DoseMathTests/pt-BR.lproj/Localizable.strings b/DoseMathTests/pt-BR.lproj/Localizable.strings new file mode 100644 index 0000000000..95e33d98bb --- /dev/null +++ b/DoseMathTests/pt-BR.lproj/Localizable.strings @@ -0,0 +1,27 @@ +/* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ +"Current glucose of %1$@ is below correction range." = "Current glucose of %1$@ is below correction range."; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Message when offering bolus recommendation even though bg is below range and minBG is in future. (1: glucose time)(2: glucose number) */ +"Predicted glucose at %1$@ is %2$@." = "Predicted glucose at %1$@ is %2$@."; + +/* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ +"Predicted glucose of %1$@ is below your suspend threshold setting." = "Predicted glucose of %1$@ is below your suspend threshold setting."; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* The short unit display string for international units of insulin */ +"U" = "U"; + diff --git a/DoseMathTests/ro.lproj/Localizable.strings b/DoseMathTests/ro.lproj/Localizable.strings new file mode 100644 index 0000000000..95e33d98bb --- /dev/null +++ b/DoseMathTests/ro.lproj/Localizable.strings @@ -0,0 +1,27 @@ +/* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ +"Current glucose of %1$@ is below correction range." = "Current glucose of %1$@ is below correction range."; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Message when offering bolus recommendation even though bg is below range and minBG is in future. (1: glucose time)(2: glucose number) */ +"Predicted glucose at %1$@ is %2$@." = "Predicted glucose at %1$@ is %2$@."; + +/* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ +"Predicted glucose of %1$@ is below your suspend threshold setting." = "Predicted glucose of %1$@ is below your suspend threshold setting."; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* The short unit display string for international units of insulin */ +"U" = "U"; + diff --git a/DoseMathTests/ru.lproj/InfoPlist.strings b/DoseMathTests/ru.lproj/InfoPlist.strings deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/DoseMathTests/sv.lproj/Localizable.strings b/DoseMathTests/sv.lproj/Localizable.strings new file mode 100644 index 0000000000..13db793a4f --- /dev/null +++ b/DoseMathTests/sv.lproj/Localizable.strings @@ -0,0 +1,27 @@ +/* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ +"Current glucose of %1$@ is below correction range." = "Current glucose of %1$@ is below correction range."; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Message when offering bolus recommendation even though bg is below range and minBG is in future. (1: glucose time)(2: glucose number) */ +"Predicted glucose at %1$@ is %2$@." = "Predicted glucose at %1$@ is %2$@."; + +/* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ +"Predicted glucose of %1$@ is below your suspend threshold setting." = "Predicted glucose of %1$@ is below your suspend threshold setting."; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* The short unit display string for international units of insulin */ +"U" = "E"; + diff --git a/DoseMathTests/vi.lproj/Localizable.strings b/DoseMathTests/vi.lproj/Localizable.strings new file mode 100644 index 0000000000..95e33d98bb --- /dev/null +++ b/DoseMathTests/vi.lproj/Localizable.strings @@ -0,0 +1,27 @@ +/* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ +"Current glucose of %1$@ is below correction range." = "Current glucose of %1$@ is below correction range."; + +/* The short unit display string for decibles */ +"dB" = "dB"; + +/* The short unit display string for grams */ +"g" = "g"; + +/* The short unit display string for milligrams of glucose per decilter */ +"mg/dL" = "mg/dL"; + +/* The short unit display string for millimoles of glucose per liter */ +"mmol/L" = "mmol/L"; + +/* Message when offering bolus recommendation even though bg is below range and minBG is in future. (1: glucose time)(2: glucose number) */ +"Predicted glucose at %1$@ is %2$@." = "Predicted glucose at %1$@ is %2$@."; + +/* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ +"Predicted glucose of %1$@ is below your suspend threshold setting." = "Predicted glucose of %1$@ is below your suspend threshold setting."; + +/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ +"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; + +/* The short unit display string for international units of insulin */ +"U" = "U"; + diff --git a/DoseMathTests/zh-Hans.lproj/InfoPlist.strings b/DoseMathTests/zh-Hans.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f9040..0000000000 --- a/DoseMathTests/zh-Hans.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/Learn/da.lproj/Localizable.strings b/Learn/da.lproj/Localizable.strings new file mode 100644 index 0000000000..e165a1d655 --- /dev/null +++ b/Learn/da.lproj/Localizable.strings @@ -0,0 +1,33 @@ +/* Lesson subtitle */ +"Computes the percentage of glucose measurements within a specified range" = "Beregner procentdelen af ​​glukosemålinger inden for et specificeret interval"; + +/* Title of the button to begin lesson execution */ +"Continue" = "Fortsæt"; + +/* Placeholder for upper range entry */ +"Maximum" = "Maximum"; + +/* Placeholder for lower range entry */ +"Minimum" = "Minimum"; + +/* Lesson title */ +"Modal Day" = "Modal Dag"; + +/* Lesson result text for no data */ +"No data available" = "Ingen data tilgængelige"; + +/* Section title for glucose range */ +"Range" = "Interval"; + +/* Title of config entry */ +"Start Date" = "Start Dato"; + +/* Lesson title */ +"Time in Range" = "Tme in Range"; + +/* Lesson subtitle */ +"Visualizes the most frequent glucose values by time of day" = "Visualiserer de hyppigste blodsukker værdier fordelt på dagen"; + +/* Unit string for a count of calendar weeks */ +"Weeks" = "Weeks"; + diff --git a/Learn/da.lproj/Main.strings b/Learn/da.lproj/Main.strings new file mode 100644 index 0000000000..50fa41e306 --- /dev/null +++ b/Learn/da.lproj/Main.strings @@ -0,0 +1,3 @@ +/* Class = "UINavigationItem"; title = "Learn"; ObjectID = "8hF-Ij-B7m"; */ +"8hF-Ij-B7m.title" = "Learn"; + diff --git a/Learn/de.lproj/Localizable.strings b/Learn/de.lproj/Localizable.strings new file mode 100644 index 0000000000..40e3ea7b99 --- /dev/null +++ b/Learn/de.lproj/Localizable.strings @@ -0,0 +1,33 @@ +/* Lesson subtitle */ +"Computes the percentage of glucose measurements within a specified range" = "Computes the percentage of glucose measurements within a specified range"; + +/* Title of the button to begin lesson execution */ +"Continue" = "Continue"; + +/* Placeholder for upper range entry */ +"Maximum" = "Maximum"; + +/* Placeholder for lower range entry */ +"Minimum" = "Minimum"; + +/* Lesson title */ +"Modal Day" = "Modal Day"; + +/* Lesson result text for no data */ +"No data available" = "No data available"; + +/* Section title for glucose range */ +"Range" = "Range"; + +/* Title of config entry */ +"Start Date" = "Start Date"; + +/* Lesson title */ +"Time in Range" = "Time in Range"; + +/* Lesson subtitle */ +"Visualizes the most frequent glucose values by time of day" = "Visualizes the most frequent glucose values by time of day"; + +/* Unit string for a count of calendar weeks */ +"Weeks" = "Weeks"; + diff --git a/Learn/de.lproj/Main.strings b/Learn/de.lproj/Main.strings new file mode 100644 index 0000000000..50fa41e306 --- /dev/null +++ b/Learn/de.lproj/Main.strings @@ -0,0 +1,3 @@ +/* Class = "UINavigationItem"; title = "Learn"; ObjectID = "8hF-Ij-B7m"; */ +"8hF-Ij-B7m.title" = "Learn"; + diff --git a/Learn/en.lproj/Localizable.strings b/Learn/en.lproj/Localizable.strings new file mode 100644 index 0000000000..44fdc3083b --- /dev/null +++ b/Learn/en.lproj/Localizable.strings @@ -0,0 +1,32 @@ +/* Lesson subtitle */ +"Computes the percentage of glucose measurements within a specified range" = "Computes the percentage of glucose measurements within a specified range"; + +/* Title of the button to begin lesson execution */ +"Continue" = "Continue"; + +/* Placeholder for upper range entry */ +"Maximum" = "Maximum"; + +/* Placeholder for lower range entry */ +"Minimum" = "Minimum"; + +/* Lesson title */ +"Modal Day" = "Modal Day"; + +/* Lesson result text for no data */ +"No data available" = "No data available"; + +/* Section title for glucose range */ +"Range" = "Range"; + +/* Title of config entry */ +"Start Date" = "Start Date"; + +/* Lesson title */ +"Time in Range" = "Time in Range"; + +/* Lesson subtitle */ +"Visualizes the most frequent glucose values by time of day" = "Visualizes the most frequent glucose values by time of day"; + +/* Unit string for a count of calendar weeks */ +"Weeks" = "Weeks"; diff --git a/Learn/en.lproj/Main.strings b/Learn/en.lproj/Main.strings new file mode 100644 index 0000000000..6b8f04c045 --- /dev/null +++ b/Learn/en.lproj/Main.strings @@ -0,0 +1,3 @@ + +/* Class = "UINavigationItem"; title = "Learn"; ObjectID = "8hF-Ij-B7m"; */ +"8hF-Ij-B7m.title" = "Learn"; diff --git a/Learn/es.lproj/Localizable.strings b/Learn/es.lproj/Localizable.strings new file mode 100644 index 0000000000..c4c2a22a88 --- /dev/null +++ b/Learn/es.lproj/Localizable.strings @@ -0,0 +1,32 @@ +/* Lesson subtitle */ +"Computes the percentage of glucose measurements within a specified range" = "Calcula el porcentaje de mediciones de glucosa en un rango específico"; + +/* Title of the button to begin lesson execution */ +"Continue" = "Continuar"; + +/* Placeholder for upper range entry */ +"Maximum" = "Máximo"; + +/* Placeholder for lower range entry */ +"Minimum" = "Mínimo"; + +/* Lesson title */ +"Modal Day" = "Día modal"; + +/* Lesson result text for no data */ +"No data available" = "No hay datos disponibles"; + +/* Section title for glucose range */ +"Range" = "Rango"; + +/* Title of config entry */ +"Start Date" = "Fecha de inicio"; + +/* Lesson title */ +"Time in Range" = "Tiempo en rango"; + +/* Lesson subtitle */ +"Visualizes the most frequent glucose values by time of day" = "Muestra los valores de glucosa más frecuentes en un momento del día"; + +/* Unit string for a count of calendar weeks */ +"Weeks" = "Semanas"; diff --git a/Learn/es.lproj/Main.strings b/Learn/es.lproj/Main.strings new file mode 100644 index 0000000000..912b8c2cc8 --- /dev/null +++ b/Learn/es.lproj/Main.strings @@ -0,0 +1,3 @@ + +/* Class = "UINavigationItem"; title = "Learn"; ObjectID = "8hF-Ij-B7m"; */ +"8hF-Ij-B7m.title" = "Aprender"; diff --git a/Learn/fi.lproj/Localizable.strings b/Learn/fi.lproj/Localizable.strings new file mode 100644 index 0000000000..4b9c0d0d94 --- /dev/null +++ b/Learn/fi.lproj/Localizable.strings @@ -0,0 +1,33 @@ +/* Lesson subtitle */ +"Computes the percentage of glucose measurements within a specified range" = "Laskee glukoosimittausten prosenttimäärän määritellyllä alueella"; + +/* Title of the button to begin lesson execution */ +"Continue" = "Jatka"; + +/* Placeholder for upper range entry */ +"Maximum" = "Maksimi"; + +/* Placeholder for lower range entry */ +"Minimum" = "Minimi"; + +/* Lesson title */ +"Modal Day" = "Tyypillinen päivä"; + +/* Lesson result text for no data */ +"No data available" = "Tietoja ei saatavilla"; + +/* Section title for glucose range */ +"Range" = "Alue"; + +/* Title of config entry */ +"Start Date" = "Aloitusaika"; + +/* Lesson title */ +"Time in Range" = "Aika tavoitealueella"; + +/* Lesson subtitle */ +"Visualizes the most frequent glucose values by time of day" = "Näyttää yleisimmät glukoosiarvot vuorokaudenajan mukaan"; + +/* Unit string for a count of calendar weeks */ +"Weeks" = "Viikkoa"; + diff --git a/Learn/fi.lproj/Main.strings b/Learn/fi.lproj/Main.strings new file mode 100644 index 0000000000..50fa41e306 --- /dev/null +++ b/Learn/fi.lproj/Main.strings @@ -0,0 +1,3 @@ +/* Class = "UINavigationItem"; title = "Learn"; ObjectID = "8hF-Ij-B7m"; */ +"8hF-Ij-B7m.title" = "Learn"; + diff --git a/Learn/fr.lproj/Localizable.strings b/Learn/fr.lproj/Localizable.strings new file mode 100644 index 0000000000..bf6bad0dbb --- /dev/null +++ b/Learn/fr.lproj/Localizable.strings @@ -0,0 +1,33 @@ +/* Lesson subtitle */ +"Computes the percentage of glucose measurements within a specified range" = "Ceci calcule le pourcentage des mesures de glycémie dans une plage spécifique"; + +/* Title of the button to begin lesson execution */ +"Continue" = "Continuer"; + +/* Placeholder for upper range entry */ +"Maximum" = "Maximum"; + +/* Placeholder for lower range entry */ +"Minimum" = "Minimum"; + +/* Lesson title */ +"Modal Day" = "Journée type"; }; 4FF4D0FF1E18374700846527 /* WatchContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchContext.swift; sourceTree = ""; }; 4FFEDFBE20E5CF22000BFC58 /* ChartHUDController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartHUDController.swift; sourceTree = ""; }; - 7D199D92212A067600241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/LaunchScreen.strings; sourceTree = ""; }; 7D199D93212A067600241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Main.strings; sourceTree = ""; }; 7D199D94212A067600241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/MainInterface.strings; sourceTree = ""; }; 7D199D95212A067600241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Interface.strings; sourceTree = ""; }; 7D199D96212A067600241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; 7D199D97212A067600241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D199D98212A067600241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; 7D199D99212A067600241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; 7D199D9A212A067600241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; - 7D199D9B212A067600241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D199D9C212A067700241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; 7D199D9D212A067700241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; - 7D199D9E212A067700241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/ckcomplication.strings; sourceTree = ""; }; - 7D199D9F212A067700241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D199DA0212A067700241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D199DA1212A067700241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; 7D199DA2212A067700241026 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; 7D23667521250BE30028B67D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; 7D23667621250BF70028B67D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23667721250C280028B67D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = ""; }; 7D23667821250C2D0028B67D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; 7D23667921250C440028B67D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; 7D23667A21250C480028B67D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = ""; }; 7D23667C21250C7E0028B67D /* LocalizedString.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LocalizedString.swift; path = LoopUI/Common/LocalizedString.swift; sourceTree = SOURCE_ROOT; }; 7D23667E21250CAC0028B67D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = ""; }; 7D23667F21250CB80028B67D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; - 7D23668021250CBE0028B67D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23668121250CC50028B67D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/ckcomplication.strings; sourceTree = ""; }; - 7D23668221250CF60028B67D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = ""; }; 7D23668321250CFB0028B67D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; - 7D23668421250D180028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/LaunchScreen.strings; sourceTree = ""; }; 7D23668521250D180028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Main.strings; sourceTree = ""; }; 7D23668621250D180028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/MainInterface.strings; sourceTree = ""; }; 7D23668721250D180028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Interface.strings; sourceTree = ""; }; 7D23668821250D180028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; 7D23668921250D180028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23668A21250D180028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; 7D23668B21250D180028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; 7D23668C21250D190028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; - 7D23668D21250D190028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23668E21250D190028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; 7D23668F21250D190028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; - 7D23669021250D190028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/ckcomplication.strings; sourceTree = ""; }; - 7D23669121250D190028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23669221250D190028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; 7D23669321250D190028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; - 7D23669421250D220028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/LaunchScreen.strings; sourceTree = ""; }; 7D23669521250D220028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Main.strings; sourceTree = ""; }; 7D23669621250D230028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/MainInterface.strings; sourceTree = ""; }; 7D23669721250D230028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Interface.strings; sourceTree = ""; }; 7D23669821250D230028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 7D23669921250D230028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23669A21250D230028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; 7D23669B21250D230028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 7D23669C21250D230028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; - 7D23669D21250D230028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D23669E21250D230028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; 7D23669F21250D240028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; - 7D2366A021250D240028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/ckcomplication.strings; sourceTree = ""; }; - 7D2366A121250D240028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2366A221250D240028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; 7D2366A321250D240028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; - 7D2366A421250D2C0028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/LaunchScreen.strings"; sourceTree = ""; }; 7D2366A521250D2C0028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Main.strings"; sourceTree = ""; }; 7D2366A621250D2C0028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/MainInterface.strings"; sourceTree = ""; }; 7D2366A721250D2C0028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Interface.strings"; sourceTree = ""; }; 7D2366A821250D2C0028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 7D2366A921250D2C0028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; - 7D2366AA21250D2C0028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 7D2366AB21250D2D0028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 7D2366AC21250D2D0028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; - 7D2366AD21250D2D0028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; - 7D2366AE21250D2D0028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 7D2366AF21250D2D0028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; - 7D2366B021250D2D0028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/ckcomplication.strings"; sourceTree = ""; }; - 7D2366B121250D2D0028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; - 7D2366B221250D2D0028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; 7D2366B321250D2D0028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 7D2366B421250D350028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Interface.strings; sourceTree = ""; }; - 7D2366B521250D360028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2366B621250D360028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/LaunchScreen.strings; sourceTree = ""; }; 7D2366B721250D360028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Main.strings; sourceTree = ""; }; 7D2366B821250D360028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/MainInterface.strings; sourceTree = ""; }; 7D2366B921250D360028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; 7D2366BA21250D360028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2366BB21250D360028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; 7D2366BC21250D360028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; 7D2366BD21250D360028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; - 7D2366BE21250D360028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; 7D2366BF21250D370028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; - 7D2366C021250D370028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/ckcomplication.strings; sourceTree = ""; }; - 7D2366C121250D370028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2366C221250D370028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; 7D2366C321250D370028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; - 7D2366C421250D3F0028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/LaunchScreen.strings; sourceTree = ""; }; 7D2366C521250D3F0028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Main.strings; sourceTree = ""; }; 7D2366C621250D3F0028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/MainInterface.strings; sourceTree = ""; }; 7D2366C721250D3F0028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Interface.strings; sourceTree = ""; }; 7D2366C821250D400028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; 7D2366C921250D400028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2366CA21250D400028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; 7D2366CB21250D400028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; 7D2366CC21250D400028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; - 7D2366CD21250D400028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2366CE21250D400028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; 7D2366CF21250D400028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; - 7D2366D021250D400028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/ckcomplication.strings; sourceTree = ""; }; - 7D2366D121250D410028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2366D221250D410028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; 7D2366D321250D410028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; - 7D2366D421250D4A0028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/LaunchScreen.strings; sourceTree = ""; }; 7D2366D521250D4A0028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Main.strings; sourceTree = ""; }; 7D2366D621250D4A0028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/MainInterface.strings; sourceTree = ""; }; 7D2366D721250D4A0028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Interface.strings; sourceTree = ""; }; 7D2366D821250D4A0028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; 7D2366D921250D4A0028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2366DA21250D4A0028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; 7D2366DB21250D4A0028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; 7D2366DC21250D4B0028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; - 7D2366DD21250D4B0028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2366DE21250D4B0028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; 7D2366DF21250D4B0028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; - 7D2366E021250D4B0028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/ckcomplication.strings; sourceTree = ""; }; - 7D2366E121250D4B0028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2366E221250D4B0028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; 7D2366E321250D4B0028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; - 7D2366E521250E0A0028B67D /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2366E721250E7B0028B67D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2366E921250E8F0028B67D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2366EA21250EA40028B67D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2366EB21250EB80028B67D /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2366EC21250ECE0028B67D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; - 7D2366ED21250F010028B67D /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D2366EE21250F170028B67D /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D68AAA91FE2DB0A00522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/LaunchScreen.strings; sourceTree = ""; }; 7D68AAAA1FE2DB0A00522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Main.strings; sourceTree = ""; }; 7D68AAAB1FE2DB0A00522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/MainInterface.strings; sourceTree = ""; }; 7D68AAAC1FE2DB0A00522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Interface.strings; sourceTree = ""; }; 7D68AAAD1FE2E8D400522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; - 7D68AAAE1FE2E8D400522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D68AAAF1FE2E8D500522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D68AAB01FE2E8D500522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/ckcomplication.strings; sourceTree = ""; }; - 7D68AAB11FE2E8D500522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D68AAB21FE2E8D500522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; 7D68AAB31FE2E8D500522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 7D68AAB41FE2E8D600522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D68AAB51FE2E8D600522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; 7D68AAB61FE2E8D600522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 7D68AAB71FE2E8D600522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 7D68AAB81FE2E8D700522C49 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 7D7076361FE06EDE004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; - 7D70763B1FE06EDF004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D7076401FE06EDF004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/ckcomplication.strings; sourceTree = ""; }; - 7D7076421FE06EE0004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D7076461FE06EE0004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; 7D70764B1FE06EE1004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; - 7D7076501FE06EE1004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; - 7D7076551FE06EE2004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; 7D70765A1FE06EE2004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; 7D70765F1FE06EE3004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; 7D7076641FE06EE4004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; - 7D7076691FE0702F004AC8EA /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; - 7DD382761F8DBFC60071272B /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/LaunchScreen.strings; sourceTree = ""; }; + 7D9BEED52335A3CB005DCFD6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + 7D9BEED72335A489005DCFD6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Main.strings; sourceTree = ""; }; + 7D9BEED82335A4F7005DCFD6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEEDA2335A522005DCFD6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/MainInterface.strings; sourceTree = ""; }; + 7D9BEEDB2335A587005DCFD6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEEDD2335A5CC005DCFD6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Interface.strings; sourceTree = ""; }; + 7D9BEEDE2335A5F7005DCFD6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEEE62335A6B3005DCFD6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEEE82335A6B9005DCFD6 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; + 7D9BEEE92335A6BB005DCFD6 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEEEA2335A6BC005DCFD6 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEEEB2335A6BD005DCFD6 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEEEC2335A6BE005DCFD6 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEEED2335A6BF005DCFD6 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEEEE2335A6BF005DCFD6 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEEEF2335A6C0005DCFD6 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEEF02335A6C1005DCFD6 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEEF42335CF8D005DCFD6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEEF62335CF90005DCFD6 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; + 7D9BEEF72335CF91005DCFD6 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEEF82335CF93005DCFD6 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEEF92335CF93005DCFD6 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEEFA2335CF94005DCFD6 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEEFB2335CF95005DCFD6 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEEFC2335CF96005DCFD6 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEEFD2335CF97005DCFD6 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEEFE2335CF97005DCFD6 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF002335D67D005DCFD6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Main.strings; sourceTree = ""; }; + 7D9BEF022335D687005DCFD6 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Main.strings"; sourceTree = ""; }; + 7D9BEF042335D68A005DCFD6 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Main.strings; sourceTree = ""; }; + 7D9BEF062335D68C005DCFD6 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Main.strings; sourceTree = ""; }; + 7D9BEF082335D68D005DCFD6 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Main.strings; sourceTree = ""; }; + 7D9BEF0A2335D68F005DCFD6 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Main.strings; sourceTree = ""; }; + 7D9BEF0C2335D690005DCFD6 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Main.strings; sourceTree = ""; }; + 7D9BEF0E2335D691005DCFD6 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Main.strings; sourceTree = ""; }; + 7D9BEF102335D693005DCFD6 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Main.strings; sourceTree = ""; }; + 7D9BEF122335D694005DCFD6 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Main.strings; sourceTree = ""; }; + 7D9BEF132335EC4B005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Intents.strings; sourceTree = ""; }; + 7D9BEF152335EC4B005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Main.strings; sourceTree = ""; }; + 7D9BEF162335EC4B005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/MainInterface.strings; sourceTree = ""; }; + 7D9BEF172335EC4C005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Interface.strings; sourceTree = ""; }; + 7D9BEF182335EC4C005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Main.strings; sourceTree = ""; }; + 7D9BEF1A2335EC4C005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF1B2335EC4C005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF1C2335EC4C005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoPlist.strings; sourceTree = ""; }; + 7D9BEF1E2335EC4D005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF1F2335EC4D005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF222335EC4D005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF272335EC4E005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF282335EC4E005DCFD6 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF292335EC58005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Intents.strings"; sourceTree = ""; }; + 7D9BEF2B2335EC59005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Main.strings"; sourceTree = ""; }; + 7D9BEF2C2335EC59005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/MainInterface.strings"; sourceTree = ""; }; + 7D9BEF2D2335EC59005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Interface.strings"; sourceTree = ""; }; + 7D9BEF2E2335EC59005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Main.strings"; sourceTree = ""; }; + 7D9BEF302335EC59005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + 7D9BEF312335EC59005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + 7D9BEF322335EC59005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/InfoPlist.strings"; sourceTree = ""; }; + 7D9BEF342335EC59005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + 7D9BEF352335EC59005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + 7D9BEF382335EC5A005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + 7D9BEF3D2335EC5A005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + 7D9BEF3E2335EC5A005DCFD6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; + 7D9BEF3F2335EC62005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Intents.strings; sourceTree = ""; }; + 7D9BEF412335EC62005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Main.strings; sourceTree = ""; }; + 7D9BEF422335EC62005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/MainInterface.strings; sourceTree = ""; }; + 7D9BEF432335EC62005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Interface.strings; sourceTree = ""; }; + 7D9BEF442335EC62005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Main.strings; sourceTree = ""; }; + 7D9BEF462335EC62005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF472335EC62005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF4A2335EC63005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF4B2335EC63005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF4E2335EC63005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF532335EC63005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF542335EC64005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF552335EC6E005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Intents.strings; sourceTree = ""; }; + 7D9BEF572335EC6E005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Main.strings; sourceTree = ""; }; + 7D9BEF582335EC6E005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/MainInterface.strings; sourceTree = ""; }; + 7D9BEF592335EC6E005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Interface.strings; sourceTree = ""; }; + 7D9BEF5A2335EC6E005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Main.strings; sourceTree = ""; }; + 7D9BEF5C2335EC6F005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF5D2335EC6F005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF5E2335EC6F005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/InfoPlist.strings; sourceTree = ""; }; + 7D9BEF602335EC6F005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF612335EC6F005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF642335EC6F005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF692335EC70005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF6A2335EC70005DCFD6 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF6B2335EC7D005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Intents.strings; sourceTree = ""; }; + 7D9BEF6D2335EC7D005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Main.strings; sourceTree = ""; }; + 7D9BEF6E2335EC7D005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/MainInterface.strings; sourceTree = ""; }; + 7D9BEF6F2335EC7D005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Interface.strings; sourceTree = ""; }; + 7D9BEF702335EC7D005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Main.strings; sourceTree = ""; }; + 7D9BEF722335EC7D005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF732335EC7D005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF762335EC7D005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF772335EC7E005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF7A2335EC7E005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF7F2335EC7E005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF802335EC7E005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF812335EC8B005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Intents.strings; sourceTree = ""; }; + 7D9BEF832335EC8B005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Main.strings; sourceTree = ""; }; + 7D9BEF842335EC8B005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/MainInterface.strings; sourceTree = ""; }; + 7D9BEF852335EC8B005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Interface.strings; sourceTree = ""; }; + 7D9BEF862335EC8B005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Main.strings; sourceTree = ""; }; + 7D9BEF882335EC8C005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF892335EC8C005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF8A2335EC8C005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/InfoPlist.strings; sourceTree = ""; }; + 7D9BEF8C2335EC8C005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF8D2335EC8C005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF902335EC8C005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF952335EC8D005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF962335EC8D005DCFD6 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF972335F667005DCFD6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BEF98233600D6005DCFD6 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; + 7D9BEF99233600D8005DCFD6 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/InfoPlist.strings; sourceTree = ""; }; + 7D9BEF9A233600D9005DCFD6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/InfoPlist.strings; sourceTree = ""; }; + 7D9BF13A23370E8B005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Intents.strings; sourceTree = ""; }; + 7D9BF13B23370E8B005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Main.strings; sourceTree = ""; }; + 7D9BF13C23370E8B005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/MainInterface.strings; sourceTree = ""; }; + 7D9BF13D23370E8B005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Interface.strings; sourceTree = ""; }; + 7D9BF13E23370E8C005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Main.strings; sourceTree = ""; }; + 7D9BF13F23370E8C005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF14023370E8C005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF14123370E8C005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/InfoPlist.strings; sourceTree = ""; }; + 7D9BF14223370E8C005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF14323370E8C005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF14423370E8D005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF14523370E8D005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; + 7D9BF14623370E8D005DCFD6 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; 7DD382771F8DBFC60071272B /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Main.strings; sourceTree = ""; }; 7DD382781F8DBFC60071272B /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/MainInterface.strings; sourceTree = ""; }; 7DD382791F8DBFC60071272B /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Interface.strings; sourceTree = ""; }; @@ -1341,8 +1391,6 @@ isa = PBXGroup; children = ( 7D7076601FE06EE3004AC8EA /* Localizable.strings */, - 7D7076411FE06EDF004AC8EA /* ckcomplication.strings */, - 7D70763C1FE06EDF004AC8EA /* InfoPlist.strings */, 43D533BB1CFD1DD7009E3085 /* WatchApp Extension.entitlements */, 43A943911B926B7B0051FA24 /* Info.plist */, 43A9438D1B926B7B0051FA24 /* ComplicationController.swift */, @@ -1445,6 +1493,7 @@ 43D9FFAB21EA9A0F00AF44BF /* Assets.xcassets */, 43D9FFAD21EA9A0F00AF44BF /* LaunchScreen.storyboard */, 43D9FFB021EA9A0F00AF44BF /* Info.plist */, + 7D9BEEE72335A6B3005DCFD6 /* Localizable.strings */, ); path = Learn; sourceTree = ""; @@ -1479,7 +1528,6 @@ 43E2D8D21D20BF42004DA55F /* DoseMathTests */ = { isa = PBXGroup; children = ( - 7D70766A1FE0702F004AC8EA /* InfoPlist.strings */, 7D70765B1FE06EE2004AC8EA /* Localizable.strings */, 43E2D8E01D20C0CB004DA55F /* Fixtures */, 43E2D8D31D20BF42004DA55F /* DoseMathTests.swift */, @@ -1610,7 +1658,6 @@ isa = PBXGroup; children = ( 43E2D90F1D20C581004DA55F /* Info.plist */, - 7D2366E421250E0A0028B67D /* InfoPlist.strings */, 43E2D8C91D20B9E7004DA55F /* KeychainManagerTests.swift */, ); path = LoopTests; @@ -1619,7 +1666,6 @@ 4F70C1DF1DE8DCA7006380B7 /* Loop Status Extension */ = { isa = PBXGroup; children = ( - 7D7076561FE06EE2004AC8EA /* InfoPlist.strings */, 7D7076371FE06EDE004AC8EA /* Localizable.strings */, 4F70C1FD1DE8E662006380B7 /* Loop Status Extension.entitlements */, 4F70C1E51DE8DCA7006380B7 /* Info.plist */, @@ -1732,6 +1778,7 @@ 4FF4D0FB1E1834C400846527 /* Models */, 43785E9B2120E7060057DED1 /* Intents.intentdefinition */, 89E267FB2292456700A3F2AF /* FeatureFlags.swift */, + 7D9BEEF52335CF8D005DCFD6 /* Localizable.strings */, ); path = Common; sourceTree = ""; @@ -2206,6 +2253,13 @@ es, pl, ru, + ja, + "pt-BR", + vi, + da, + sv, + fi, + ro, ); mainGroup = 43776F831B8022E90074EA36; productRefGroup = 43776F8D1B8022E90074EA36 /* Products */; @@ -2258,8 +2312,6 @@ buildActionMask = 2147483647; files = ( 7D70765E1FE06EE3004AC8EA /* Localizable.strings in Resources */, - 7D70763A1FE06EDF004AC8EA /* InfoPlist.strings in Resources */, - 7D70763F1FE06EDF004AC8EA /* ckcomplication.strings in Resources */, 43A943901B926B7B0051FA24 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2275,7 +2327,9 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7D9BEEF32335CF8D005DCFD6 /* Localizable.strings in Resources */, 43D9FFAF21EA9A0F00AF44BF /* LaunchScreen.storyboard in Resources */, + 7D9BEEE52335A6B3005DCFD6 /* Localizable.strings in Resources */, 43D9FFAC21EA9A0F00AF44BF /* Assets.xcassets in Resources */, 43D9FFAA21EA9A0C00AF44BF /* Main.storyboard in Resources */, ); @@ -2299,7 +2353,6 @@ 43E2D8F41D20C0DB004DA55F /* recommend_temp_basal_start_high_end_low.json in Resources */, 43E2D8EF1D20C0DB004DA55F /* recommend_temp_basal_high_and_falling.json in Resources */, 436D9BF81F6F4EA100CFA75F /* recommended_temp_start_low_end_just_above_range.json in Resources */, - 7D7076681FE0702F004AC8EA /* InfoPlist.strings in Resources */, 43E2D8ED1D20C0DB004DA55F /* recommend_temp_basal_correct_low_at_min.json in Resources */, 43E2D8F01D20C0DB004DA55F /* recommend_temp_basal_high_and_rising.json in Resources */, C12F21A71DFA79CB00748193 /* recommend_temp_basal_very_low_end_in_range.json in Resources */, @@ -2317,7 +2370,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 7D2366E621250E0A0028B67D /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2326,7 +2378,6 @@ buildActionMask = 2147483647; files = ( 4F70C1E41DE8DCA7006380B7 /* MainInterface.storyboard in Resources */, - 7D7076541FE06EE2004AC8EA /* InfoPlist.strings in Resources */, 7D7076351FE06EDE004AC8EA /* Localizable.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2973,6 +3024,14 @@ 7D2366C521250D3F0028B67D /* nl */, 7D2366D521250D4A0028B67D /* nb */, 7D199D93212A067600241026 /* pl */, + 7D9BEED72335A489005DCFD6 /* en */, + 7D9BEF152335EC4B005DCFD6 /* ja */, + 7D9BEF2B2335EC59005DCFD6 /* pt-BR */, + 7D9BEF412335EC62005DCFD6 /* vi */, + 7D9BEF572335EC6E005DCFD6 /* da */, + 7D9BEF6D2335EC7D005DCFD6 /* sv */, + 7D9BEF832335EC8B005DCFD6 /* fi */, + 7D9BF13B23370E8B005DCFD6 /* ro */, ); name = Main.storyboard; sourceTree = ""; @@ -2981,15 +3040,6 @@ isa = PBXVariantGroup; children = ( 43776F9B1B8022E90074EA36 /* Base */, - 7DD382761F8DBFC60071272B /* es */, - 7D68AAA91FE2DB0A00522C49 /* ru */, - 7D23668421250D180028B67D /* fr */, - 7D23669421250D220028B67D /* de */, - 7D2366A421250D2C0028B67D /* zh-Hans */, - 7D2366B621250D360028B67D /* it */, - 7D2366C421250D3F0028B67D /* nl */, - 7D2366D421250D4A0028B67D /* nb */, - 7D199D92212A067600241026 /* pl */, ); name = LaunchScreen.storyboard; sourceTree = ""; @@ -3008,6 +3058,13 @@ C12CB9B423106A6100F84978 /* nl */, C12CB9B623106A6200F84978 /* nb */, C12CB9B823106A6300F84978 /* pl */, + 7D9BEF132335EC4B005DCFD6 /* ja */, + 7D9BEF292335EC58005DCFD6 /* pt-BR */, + 7D9BEF3F2335EC62005DCFD6 /* vi */, + 7D9BEF552335EC6E005DCFD6 /* da */, + 7D9BEF6B2335EC7D005DCFD6 /* sv */, + 7D9BEF812335EC8B005DCFD6 /* fi */, + 7D9BF13A23370E8B005DCFD6 /* ro */, ); name = Intents.intentdefinition; sourceTree = ""; @@ -3025,6 +3082,14 @@ 7D2366C721250D3F0028B67D /* nl */, 7D2366D721250D4A0028B67D /* nb */, 7D199D95212A067600241026 /* pl */, + 7D9BEEDD2335A5CC005DCFD6 /* en */, + 7D9BEF172335EC4C005DCFD6 /* ja */, + 7D9BEF2D2335EC59005DCFD6 /* pt-BR */, + 7D9BEF432335EC62005DCFD6 /* vi */, + 7D9BEF592335EC6E005DCFD6 /* da */, + 7D9BEF6F2335EC7D005DCFD6 /* sv */, + 7D9BEF852335EC8B005DCFD6 /* fi */, + 7D9BF13D23370E8B005DCFD6 /* ro */, ); name = Interface.storyboard; sourceTree = ""; @@ -3033,6 +3098,23 @@ isa = PBXVariantGroup; children = ( 43D9FFA921EA9A0C00AF44BF /* Base */, + 7D9BEF002335D67D005DCFD6 /* en */, + 7D9BEF022335D687005DCFD6 /* zh-Hans */, + 7D9BEF042335D68A005DCFD6 /* nl */, + 7D9BEF062335D68C005DCFD6 /* fr */, + 7D9BEF082335D68D005DCFD6 /* de */, + 7D9BEF0A2335D68F005DCFD6 /* it */, + 7D9BEF0C2335D690005DCFD6 /* nb */, + 7D9BEF0E2335D691005DCFD6 /* pl */, + 7D9BEF102335D693005DCFD6 /* ru */, + 7D9BEF122335D694005DCFD6 /* es */, + 7D9BEF182335EC4C005DCFD6 /* ja */, + 7D9BEF2E2335EC59005DCFD6 /* pt-BR */, + 7D9BEF442335EC62005DCFD6 /* vi */, + 7D9BEF5A2335EC6E005DCFD6 /* da */, + 7D9BEF702335EC7D005DCFD6 /* sv */, + 7D9BEF862335EC8B005DCFD6 /* fi */, + 7D9BF13E23370E8C005DCFD6 /* ro */, ); name = Main.storyboard; sourceTree = ""; @@ -3058,26 +3140,18 @@ 7D2366C621250D3F0028B67D /* nl */, 7D2366D621250D4A0028B67D /* nb */, 7D199D94212A067600241026 /* pl */, + 7D9BEEDA2335A522005DCFD6 /* en */, + 7D9BEF162335EC4B005DCFD6 /* ja */, + 7D9BEF2C2335EC59005DCFD6 /* pt-BR */, + 7D9BEF422335EC62005DCFD6 /* vi */, + 7D9BEF582335EC6E005DCFD6 /* da */, + 7D9BEF6E2335EC7D005DCFD6 /* sv */, + 7D9BEF842335EC8B005DCFD6 /* fi */, + 7D9BF13C23370E8B005DCFD6 /* ro */, ); name = MainInterface.storyboard; sourceTree = ""; }; - 7D2366E421250E0A0028B67D /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - 7D2366E521250E0A0028B67D /* es */, - 7D2366E721250E7B0028B67D /* de */, - 7D2366E921250E8F0028B67D /* fr */, - 7D2366EA21250EA40028B67D /* it */, - 7D2366EB21250EB80028B67D /* nb */, - 7D2366EC21250ECE0028B67D /* zh-Hans */, - 7D2366ED21250F010028B67D /* ru */, - 7D2366EE21250F170028B67D /* nl */, - 7D199DA0212A067700241026 /* pl */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; 7D7076371FE06EDE004AC8EA /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( @@ -3091,57 +3165,22 @@ 7D2366CB21250D400028B67D /* nl */, 7D2366DB21250D4A0028B67D /* nb */, 7D199D99212A067600241026 /* pl */, + 7D9BEED82335A4F7005DCFD6 /* en */, + 7D9BEF1E2335EC4D005DCFD6 /* ja */, + 7D9BEF342335EC59005DCFD6 /* pt-BR */, + 7D9BEF4A2335EC63005DCFD6 /* vi */, + 7D9BEF602335EC6F005DCFD6 /* da */, + 7D9BEF762335EC7D005DCFD6 /* sv */, + 7D9BEF8C2335EC8C005DCFD6 /* fi */, + 7D9BF14223370E8C005DCFD6 /* ro */, ); name = Localizable.strings; sourceTree = ""; }; - 7D70763C1FE06EDF004AC8EA /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - 7D70763B1FE06EDF004AC8EA /* es */, - 7D68AAAF1FE2E8D500522C49 /* ru */, - 7D23668021250CBE0028B67D /* Base */, - 7D23669121250D190028B67D /* fr */, - 7D2366A121250D240028B67D /* de */, - 7D2366B121250D2D0028B67D /* zh-Hans */, - 7D2366C121250D370028B67D /* it */, - 7D2366D121250D410028B67D /* nl */, - 7D2366E121250D4B0028B67D /* nb */, - 7D199D9F212A067700241026 /* pl */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; - 7D7076411FE06EDF004AC8EA /* ckcomplication.strings */ = { - isa = PBXVariantGroup; - children = ( - 7D7076401FE06EDF004AC8EA /* es */, - 7D68AAB01FE2E8D500522C49 /* ru */, - 7D23668121250CC50028B67D /* Base */, - 7D23669021250D190028B67D /* fr */, - 7D2366A021250D240028B67D /* de */, - 7D2366B021250D2D0028B67D /* zh-Hans */, - 7D2366C021250D370028B67D /* it */, - 7D2366D021250D400028B67D /* nl */, - 7D2366E021250D4B0028B67D /* nb */, - 7D199D9E212A067700241026 /* pl */, - ); - name = ckcomplication.strings; - sourceTree = ""; - }; 7D7076471FE06EE0004AC8EA /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( - 7D7076461FE06EE0004AC8EA /* es */, - 7D68AAB21FE2E8D500522C49 /* ru */, 7D23667A21250C480028B67D /* Base */, - 7D23668D21250D190028B67D /* fr */, - 7D23669D21250D230028B67D /* de */, - 7D2366AD21250D2D0028B67D /* zh-Hans */, - 7D2366BE21250D360028B67D /* it */, - 7D2366CD21250D400028B67D /* nl */, - 7D2366DD21250D4B0028B67D /* nb */, - 7D199D9B212A067600241026 /* pl */, ); name = InfoPlist.strings; sourceTree = ""; @@ -3159,6 +3198,14 @@ 7D2366CC21250D400028B67D /* nl */, 7D2366DC21250D4B0028B67D /* nb */, 7D199D9A212A067600241026 /* pl */, + 7D9BEEDB2335A587005DCFD6 /* en */, + 7D9BEF1F2335EC4D005DCFD6 /* ja */, + 7D9BEF352335EC59005DCFD6 /* pt-BR */, + 7D9BEF4B2335EC63005DCFD6 /* vi */, + 7D9BEF612335EC6F005DCFD6 /* da */, + 7D9BEF772335EC7E005DCFD6 /* sv */, + 7D9BEF8D2335EC8C005DCFD6 /* fi */, + 7D9BF14323370E8C005DCFD6 /* ro */, ); name = Localizable.strings; sourceTree = ""; @@ -3166,7 +3213,6 @@ 7D7076511FE06EE1004AC8EA /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( - 7D7076501FE06EE1004AC8EA /* es */, 7D68AAB41FE2E8D600522C49 /* ru */, 7D23667621250BF70028B67D /* Base */, 7D23668921250D180028B67D /* fr */, @@ -3176,23 +3222,15 @@ 7D2366C921250D400028B67D /* nl */, 7D2366D921250D4A0028B67D /* nb */, 7D199D97212A067600241026 /* pl */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; - 7D7076561FE06EE2004AC8EA /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - 7D7076551FE06EE2004AC8EA /* es */, - 7D68AAB51FE2E8D600522C49 /* ru */, - 7D23667721250C280028B67D /* Base */, - 7D23668A21250D180028B67D /* fr */, - 7D23669A21250D230028B67D /* de */, - 7D2366AA21250D2C0028B67D /* zh-Hans */, - 7D2366BB21250D360028B67D /* it */, - 7D2366CA21250D400028B67D /* nl */, - 7D2366DA21250D4A0028B67D /* nb */, - 7D199D98212A067600241026 /* pl */, + 7D9BEED52335A3CB005DCFD6 /* en */, + 7D9BEF1C2335EC4C005DCFD6 /* ja */, + 7D9BEF322335EC59005DCFD6 /* pt-BR */, + 7D9BEF5E2335EC6F005DCFD6 /* da */, + 7D9BEF8A2335EC8C005DCFD6 /* fi */, + 7D9BEF98233600D6005DCFD6 /* es */, + 7D9BEF99233600D8005DCFD6 /* sv */, + 7D9BEF9A233600D9005DCFD6 /* vi */, + 7D9BF14123370E8C005DCFD6 /* ro */, ); name = InfoPlist.strings; sourceTree = ""; @@ -3210,6 +3248,13 @@ 7D2366D321250D410028B67D /* nl */, 7D2366E321250D4B0028B67D /* nb */, 7D199DA2212A067700241026 /* pl */, + 7D9BEF272335EC4E005DCFD6 /* ja */, + 7D9BEF3D2335EC5A005DCFD6 /* pt-BR */, + 7D9BEF532335EC63005DCFD6 /* vi */, + 7D9BEF692335EC70005DCFD6 /* da */, + 7D9BEF7F2335EC7E005DCFD6 /* sv */, + 7D9BEF952335EC8D005DCFD6 /* fi */, + 7D9BF14523370E8D005DCFD6 /* ro */, ); name = Localizable.strings; sourceTree = ""; @@ -3227,6 +3272,14 @@ 7D2366CF21250D400028B67D /* nl */, 7D2366DF21250D4B0028B67D /* nb */, 7D199D9D212A067700241026 /* pl */, + 7D9BEEDE2335A5F7005DCFD6 /* en */, + 7D9BEF222335EC4D005DCFD6 /* ja */, + 7D9BEF382335EC5A005DCFD6 /* pt-BR */, + 7D9BEF4E2335EC63005DCFD6 /* vi */, + 7D9BEF642335EC6F005DCFD6 /* da */, + 7D9BEF7A2335EC7E005DCFD6 /* sv */, + 7D9BEF902335EC8C005DCFD6 /* fi */, + 7D9BF14423370E8D005DCFD6 /* ro */, ); name = Localizable.strings; sourceTree = ""; @@ -3244,40 +3297,70 @@ 7D2366C821250D400028B67D /* nl */, 7D2366D821250D4A0028B67D /* nb */, 7D199D96212A067600241026 /* pl */, + 7D9BEF1B2335EC4C005DCFD6 /* ja */, + 7D9BEF312335EC59005DCFD6 /* pt-BR */, + 7D9BEF472335EC62005DCFD6 /* vi */, + 7D9BEF5D2335EC6F005DCFD6 /* da */, + 7D9BEF732335EC7D005DCFD6 /* sv */, + 7D9BEF892335EC8C005DCFD6 /* fi */, + 7D9BEF972335F667005DCFD6 /* en */, + 7D9BF14023370E8C005DCFD6 /* ro */, ); name = Localizable.strings; sourceTree = ""; }; - 7D70766A1FE0702F004AC8EA /* InfoPlist.strings */ = { + 7D9BEEE72335A6B3005DCFD6 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( - 7D7076691FE0702F004AC8EA /* es */, - 7D68AAAE1FE2E8D400522C49 /* ru */, - 7D23668221250CF60028B67D /* Base */, - 7D23669221250D190028B67D /* fr */, - 7D2366A221250D240028B67D /* de */, - 7D2366B221250D2D0028B67D /* zh-Hans */, - 7D2366C221250D370028B67D /* it */, - 7D2366D221250D410028B67D /* nl */, - 7D2366E221250D4B0028B67D /* nb */, - 7D199DA1212A067700241026 /* pl */, + 7D9BEEE62335A6B3005DCFD6 /* en */, + 7D9BEEE82335A6B9005DCFD6 /* zh-Hans */, + 7D9BEEE92335A6BB005DCFD6 /* nl */, + 7D9BEEEA2335A6BC005DCFD6 /* fr */, + 7D9BEEEB2335A6BD005DCFD6 /* de */, + 7D9BEEEC2335A6BE005DCFD6 /* it */, + 7D9BEEED2335A6BF005DCFD6 /* nb */, + 7D9BEEEE2335A6BF005DCFD6 /* pl */, + 7D9BEEEF2335A6C0005DCFD6 /* ru */, + 7D9BEEF02335A6C1005DCFD6 /* es */, + 7D9BEF282335EC4E005DCFD6 /* ja */, + 7D9BEF3E2335EC5A005DCFD6 /* pt-BR */, + 7D9BEF542335EC64005DCFD6 /* vi */, + 7D9BEF6A2335EC70005DCFD6 /* da */, + 7D9BEF802335EC7E005DCFD6 /* sv */, + 7D9BEF962335EC8D005DCFD6 /* fi */, + 7D9BF14623370E8D005DCFD6 /* ro */, ); - name = InfoPlist.strings; + name = Localizable.strings; + sourceTree = ""; + }; + 7D9BEEF52335CF8D005DCFD6 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 7D9BEEF42335CF8D005DCFD6 /* en */, + 7D9BEEF62335CF90005DCFD6 /* zh-Hans */, + 7D9BEEF72335CF91005DCFD6 /* nl */, + 7D9BEEF82335CF93005DCFD6 /* fr */, + 7D9BEEF92335CF93005DCFD6 /* de */, + 7D9BEEFA2335CF94005DCFD6 /* it */, + 7D9BEEFB2335CF95005DCFD6 /* nb */, + 7D9BEEFC2335CF96005DCFD6 /* pl */, + 7D9BEEFD2335CF97005DCFD6 /* ru */, + 7D9BEEFE2335CF97005DCFD6 /* es */, + 7D9BEF1A2335EC4C005DCFD6 /* ja */, + 7D9BEF302335EC59005DCFD6 /* pt-BR */, + 7D9BEF462335EC62005DCFD6 /* vi */, + 7D9BEF5C2335EC6F005DCFD6 /* da */, + 7D9BEF722335EC7D005DCFD6 /* sv */, + 7D9BEF882335EC8C005DCFD6 /* fi */, + 7D9BF13F23370E8C005DCFD6 /* ro */, + ); + name = Localizable.strings; sourceTree = ""; }; C1C73F0F1DE3D0270022FC89 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( - 7D7076421FE06EE0004AC8EA /* es */, - 7D68AAB11FE2E8D500522C49 /* ru */, 7D23667E21250CAC0028B67D /* Base */, - 7D23668E21250D190028B67D /* fr */, - 7D23669E21250D230028B67D /* de */, - 7D2366AE21250D2D0028B67D /* zh-Hans */, - 7D2366B521250D360028B67D /* it */, - 7D2366CE21250D400028B67D /* nl */, - 7D2366DE21250D4B0028B67D /* nb */, - 7D199D9C212A067700241026 /* pl */, ); name = InfoPlist.strings; sourceTree = ""; @@ -3446,7 +3529,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_ENTITLEMENTS = Loop/Loop.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 83JUVX6ELC; ENABLE_BITCODE = YES; INFOPLIST_FILE = Loop/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -3465,7 +3548,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_ENTITLEMENTS = Loop/Loop.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 83JUVX6ELC; ENABLE_BITCODE = YES; INFOPLIST_FILE = Loop/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -3483,7 +3566,7 @@ CODE_SIGN_ENTITLEMENTS = "WatchApp Extension/WatchApp Extension.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 83JUVX6ELC; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; @@ -3506,7 +3589,7 @@ CODE_SIGN_ENTITLEMENTS = "WatchApp Extension/WatchApp Extension.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 83JUVX6ELC; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; @@ -3527,7 +3610,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 83JUVX6ELC; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; IBSC_MODULE = WatchApp_Extension; INFOPLIST_FILE = WatchApp/Info.plist; @@ -3548,7 +3631,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 83JUVX6ELC; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; IBSC_MODULE = WatchApp_Extension; INFOPLIST_FILE = WatchApp/Info.plist; @@ -3828,7 +3911,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 83JUVX6ELC; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Loop Status Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; @@ -3850,7 +3933,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 83JUVX6ELC; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Loop Status Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; diff --git a/Loop/Base.lproj/InfoPlist.strings b/Loop/Base.lproj/InfoPlist.strings index 14e4d82681..dc7c061704 100644 --- a/Loop/Base.lproj/InfoPlist.strings +++ b/Loop/Base.lproj/InfoPlist.strings @@ -1,7 +1,9 @@ - /* Bundle name */ "CFBundleName" = "$(PRODUCT_NAME)"; +/* Privacy - Bluetooth Always Usage Description */ +"NSBluetoothAlwaysUsageDescription" = "Bluetooth is used to communicate with insulin pump and continuous glucose monitor devices."; + /* Privacy - Bluetooth Peripheral Usage Description */ "NSBluetoothPeripheralUsageDescription" = "Bluetooth is used to communicate with insulin pump and continuous glucose monitor devices."; @@ -13,3 +15,4 @@ /* Privacy - Health Update Usage Description */ "NSHealthUpdateUsageDescription" = "Carbohydrate meal data entered in the app and on the watch is stored in the Health database. Glucose data retrieved from the CGM is stored securely in HealthKit."; + diff --git a/Loop/Base.lproj/Localizable.strings b/Loop/Base.lproj/Localizable.strings index 241d894499..bc11213827 100644 --- a/Loop/Base.lproj/Localizable.strings +++ b/Loop/Base.lproj/Localizable.strings @@ -1,10 +1,7 @@ -/* Created with Localizable Strings Merge, the swiss-army knife for iOS and OS X localization. */ - /* The string format appended to active insulin that describes pending insulin. (1: pending insulin) */ -" (pending: %@)" = "(pending: %@)"; +" (pending: %@)" = " (pending: %@)"; -/* Format string for carb ratio average. (1: value)(2: carb unit) - Format string for insulin sensitivity average (1: value)(2: glucose unit) */ +/* Format string for carb ratio average. (1: value)(2: carb unit) */ "%1$@ %2$@/U" = "%1$@ %2$@/U"; /* Formats (1: carb start time) and (2: carb absorption duration) */ @@ -25,12 +22,12 @@ /* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ "%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; -/* Format string describing glucose units per minute (1: glucose unit string) */ -"%1$@/min" = "%1$@/min"; - /* Formats (1: carb value) and (2: food type) */ "%1$@: %2$@" = "%1$@: %2$@"; +/* The format for an active override preset. (1: preset symbol)(2: preset name) */ +"%@ %@" = "%1$@ %2$@"; + /* Formats absorbed carb value */ "%@ absorbed" = "%@ absorbed"; @@ -41,10 +38,10 @@ "%@." = "%@."; /* Description of the prediction input effect for glucose momentum */ -"15 min glucose regression coefficient (b₁), continued with decay over 30 min" = "15 min glucose regression coefficient (b₁), continued with decay over 30 min."; +"15 min glucose regression coefficient (b₁), continued with decay over 30 min" = "15 min glucose regression coefficient (b₁), continued with decay over 30 min"; /* Description of the prediction input effect for retrospective correction */ -"30 min comparison of glucose prediction vs actual, continued with decay over 60 min" = "30 min comparison of glucose prediction vs actual, continued with decay over 60 min."; +"30 min comparison of glucose prediction vs actual, continued with decay over 60 min" = "30 min comparison of glucose prediction vs actual, continued with decay over 60 min"; /* Subtitle of Fiasp preset */ "A model based on the published absorption of Fiasp insulin." = "A model based on the published absorption of Fiasp insulin."; @@ -67,15 +64,17 @@ /* The string format describing active insulin. (1: localized insulin value description) */ "Active Insulin: %@" = "Active Insulin: %@"; -/* The label of the carb entry button */ +/* Action sheet title selecting CGM + Title text for button to set up a CGM */ +"Add CGM" = "Add CGM"; + +/* The label of the meal button */ "Add Meal" = "Add Meal"; -/* Title text for button to set up a new pump */ +/* Action sheet title selecting Pump + Title text for button to set up a new pump */ "Add Pump" = "Add Pump"; -/* The title of the section containing algorithm settings */ -"Algorithm Settings" = "Algorithm Settings"; - /* The title of the Amplitude service */ "Amplitude" = "Amplitude"; @@ -88,6 +87,9 @@ /* The title of the nightscout API secret credential */ "API Secret" = "API Secret"; +/* Confirmation message for deleting a CGM */ +"Are you sure you want to delete this CGM?" = "Are you sure you want to delete this CGM?"; + /* Format fragment for a specific time */ "at %@" = "at %@"; @@ -105,9 +107,18 @@ The notification title for a bolus failure */ "Bolus" = "Bolus"; +/* The format string for bolus progress. (1: delivered volume)(2: total volume) */ +"Bolused %1$@ of %2$@" = "Bolused %1$@ of %2$@"; + +/* The format string for bolus in progress showing total volume. (1: total volume) */ +"Bolusing %1$@" = "Bolusing %1$@"; + /* The title of the cancel action in an action sheet */ "Cancel" = "Cancel"; +/* The title of the cell indicating a bolus is being canceled */ +"Canceling Bolus" = "Canceling Bolus"; + /* Details for missing data error when carb effects are missing */ "Carb effects" = "Carb effects"; @@ -139,6 +150,9 @@ /* The title text for the looping enabled switch cell */ "Closed Loop" = "Closed Loop"; +/* The title of the action used to dismiss an error alert */ +"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; + /* The title of the configuration section in settings */ "Configuration" = "Configuration"; @@ -153,16 +167,16 @@ "Correction Range" = "Correction Range"; /* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ -"Current glucose of %1$@ is below correction range." = " Current glucose of %1$@ is below correction range."; +"Current glucose of %1$@ is below correction range." = "Current glucose of %1$@ is below correction range."; + +/* The title of the cell indicating a generic temporary override is enabled */ +"Custom Override" = "Custom Override"; /* The title of the Loggly customer token credential */ "Customer Token" = "Customer Token"; -/* The short unit display string for decibles */ -"dB" = "dB"; - -/* The title of the button to remove the credentials for a service */ -"Delete Account" = "Delete Account"; +/* Button title to delete CGM */ +"Delete CGM" = "Delete CGM"; /* Title text for delivery limits */ "Delivery Limits" = "Delivery Limits"; @@ -170,17 +184,17 @@ /* The action hint of the workout mode toggle button when enabled */ "Disables" = "Disables"; -/* Title of the switch which toggles retrospective correction effects */ -"Enable Retrospective Correction" = "Enable Retrospective Correction"; - /* The action hint of the workout mode toggle button when disabled */ "Enables" = "Enables"; /* The placeholder text instructing users to enter a suspend treshold */ "Enter suspend threshold" = "Enter suspend threshold"; -/* The placeholder text instructing users how to enter a transmitter ID */ -"Enter the 6-digit transmitter ID" = "Enter the 6-digit transmitter ID"; +/* The alert title for an error while canceling a bolus */ +"Error Canceling Bolus" = "Error Canceling Bolus"; + +/* The alert title for a resume error */ +"Error Resuming" = "Error Resuming"; /* The subtitle format describing eventual glucose. (1: localized glucose value description) */ "Eventually %@" = "Eventually %@"; @@ -194,15 +208,6 @@ /* The format string used to describe a finite workout targets duration */ "For %1$@" = "For %1$@"; -/* The short unit display string for grams */ -"g" = "g"; - -/* The title text for the G4 Share Receiver switch cell */ -"G4 Share Receiver" = "G4 Share Receiver"; - -/* The title text for the G5/G6 Transmitter switch cell */ -"G5/G6 Transmitter" = "G5/G6 Transmitter"; - /* The title of the glucose and prediction graph */ "Glucose" = "Glucose"; @@ -247,9 +252,6 @@ /* The title text for the issue report cell */ "Issue Report" = "Issue Report"; -/* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ -"prediction-description-retrospective-correction" = "Predicted: %1$@\nActual: %2$@ (%3$@)"; - /* Glucose HUD accessibility hint */ "Launches CGM app" = "Launches CGM app"; @@ -265,27 +267,21 @@ /* The notification alert describing a long-lasting loop failure. The substitution parameter is the time interval since the last loop */ "Loop has not completed successfully in %@" = "Loop has not completed successfully in %@"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; - /* The error message for missing data. (1: missing data details) */ "Missing data: %1$@" = "Missing data: %1$@"; -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; - /* Details for missing data error when momentum effects are missing */ "Momentum effects" = "Momentum effects"; -/* Sensor state description for the non-valid state */ -"Needs Attention" = "Needs Attention"; - /* The title of the Nightscout service */ "Nightscout" = "Nightscout"; /* The error message displayed for device connection errors. */ "No connected devices, or failure during device connection" = "No connected devices, or failure during device connection"; +/* The title text for the override presets */ +"Override Presets" = "Override Presets"; + /* The label of the pre-meal mode toggle button */ "Pre-Meal Targets" = "Pre-Meal Targets"; @@ -295,6 +291,9 @@ /* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ "Predicted glucose of %1$@ is below your suspend threshold setting." = "Predicted glucose of %1$@ is below your suspend threshold setting."; +/* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ +"Predicted: %1$@\nActual: %2$@ (%3$@)" = "Predicted: %1$@\nActual: %2$@ (%3$@)"; + /* The title of the pump section in settings */ "Pump" = "Pump"; @@ -313,6 +312,9 @@ /* The notification title for a low pump reservoir */ "Pump Reservoir Low" = "Pump Reservoir Low"; +/* The title of the cell indicating the pump is suspended */ +"Pump Suspended" = "Pump Suspended"; + /* Title of insulin model preset */ "Rapid-Acting – Adults" = "Rapid-Acting – Adults"; @@ -352,8 +354,8 @@ /* The title of the nightscout site URL credential */ "Site URL" = "Site URL"; -/* The title text for the Medtronic sensor switch cell */ -"Sof-Sensor / Enlite" = "Sof-Sensor / Enlite"; +/* The format for the description of a temporary override start date */ +"starting at %@" = "starting at %@"; /* The title of the cell indicating a bolus is being sent */ "Starting Bolus" = "Starting Bolus"; @@ -361,21 +363,21 @@ /* The title text in settings */ "Suspend Threshold" = "Suspend Threshold"; +/* The subtitle of the cell displaying an action to resume insulin delivery */ +"Tap to Resume" = "Tap to Resume"; + /* Subtitle description of Walsh insulin model setting */ "The legacy model used by Loop, allowing customization of action duration." = "The legacy model used by Loop, allowing customization of action duration."; /* Body of the alert describing a maximum bolus validation error. (1: The localized max bolus value) */ "The maximum bolus amount is %@ Units" = "The maximum bolus amount is %@ Units"; -/* Instructions on where to find the transmitter ID */ -"The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app." = "The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app."; - -/* The description of the switch which toggles retrospective correction effects */ -"This will more aggresively increase or decrease basal delivery when glucose movement doesn't match the carbohydrate and insulin-based model." = "This will more aggresively increase or decrease basal delivery when glucose movement doesn't match the carbohydrate and insulin-based model."; - /* The short unit display string for international units of insulin */ "U" = "U"; +/* The format for the description of a temporary override end date */ +"until %@" = "until %@"; + /* The title of the alert controller used to select a duration for workout targets */ "Use Workout Glucose Targets" = "Use Workout Glucose Targets"; diff --git a/Loop/da.lproj/InfoPlist.strings b/Loop/da.lproj/InfoPlist.strings new file mode 100644 index 0000000000..7ecc4483b1 --- /dev/null +++ b/Loop/da.lproj/InfoPlist.strings @@ -0,0 +1,18 @@ +/* Bundle name */ +"CFBundleName" = "$(PRODUCT_NAME)"; + +/* Privacy - Bluetooth Always Usage Description */ +"NSBluetoothAlwaysUsageDescription" = "Bluetooth bliver brugt til at kommunikere med din insulin pumpe og din glukose monitor."; + +/* Privacy - Bluetooth Peripheral Usage Description */ +"NSBluetoothPeripheralUsageDescription" = "Bluetooth bliver brugt til at kommunikere med din insulin pumpe og din glukose monitor."; + +/* Privacy - Face ID Usage Description */ +"NSFaceIDUsageDescription" = "Face ID bliver brugt til at godkende en insulin bolus."; + +/* Privacy - Health Share Usage Description */ +"NSHealthShareUsageDescription" = "Måltidsdata fra Apple Sundhed bliver brugt til at glukosens effekt på dit blodsukker. Glukose data fra Apple Sundhed bliver brugt til at danne grafer og udregninger."; + +/* Privacy - Health Update Usage Description */ +"NSHealthUpdateUsageDescription" = "Kulhydrater indtastet i appen og på uret gemmes i Apple Sundhedsdatabasen. Glukosedata hentet fra CGM gemmes sikkert i SundhedsKit."; + diff --git a/Loop/da.lproj/Localizable.strings b/Loop/da.lproj/Localizable.strings new file mode 100644 index 0000000000..01fe077f3a --- /dev/null +++ b/Loop/da.lproj/Localizable.strings @@ -0,0 +1,395 @@ +/* The string format appended to active insulin that describes pending insulin. (1: pending insulin) */ +" (pending: %@)" = " (afventer: %@)"; + +/* The format for an active override preset. (1: preset symbol)(2: preset name) */ +"%@ %@" = "%1$@ %2$@"; + +/* Formats absorbed carb value */ +"%@ absorbed" = "%@ optaget"; + +/* The subtitle format describing total insulin. (1: localized insulin total) */ +"%@ U Total" = "%@ E Total"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ +"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; + +/* Format string for carb ratio average. (1: value)(2: carb unit) */ +"%1$@ %2$@/U" = "%1$@ %2$@/E"; + +/* Formats (1: carb start time) and (2: carb absorption duration) */ +"%1$@ + %2$@" = "%1$@ + %2$@"; + +/* Low reservoir alert format string. (1: Number of units remaining) */ +"%1$@ U left" = "%1$@ E tilbage"; + +/* Low reservoir alert with time remaining format string. (1: Number of units remaining)(2: approximate time remaining) */ +"%1$@ U left: %2$@" = "%1$@ E tilbage: %2$@"; + +/* The format for recommended temp basal rate and time. (1: localized rate number)(2: localized time) */ +"%1$@ U/hour @ %2$@" = "%1$@ E/time @ %2$@"; + +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Formats (1: carb value) and (2: food type) */ +"%1$@: %2$@" = "%1$@: %2$@"; + +/* Description of the prediction input effect for glucose momentum */ +"15 min glucose regression coefficient (b₁), continued with decay over 30 min" = "15 min. glukoseregressionskoefficient (b₁), fortsætter med henfald over 30 minutter."; + +/* Description of the prediction input effect for retrospective correction */ +"30 min comparison of glucose prediction vs actual, continued with decay over 60 min" = "30 min. sammenligning af glukose-forudsigelse kontra faktisk målt glukose, med henfald over 60 minutter."; + +/* Subtitle of Fiasp preset */ +"A model based on the published absorption of Fiasp insulin." = "En model baseret på publiceret data om absorption af Fiasp insulin."; + +/* Subtitle of Rapid-Acting – Adult preset */ +"A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults." = "En model baseret på publiceret data om absorption Humalog, Novolog, og Apidra insulin hos voksne."; + +/* Action to copy the recommended Bolus value to the actual Bolus Field */ +"AcceptRecommendedBolus" = "AccepterAnbefaletBolus"; + +/* The title of the Carbs On-Board graph */ +"Active Carbohydrates" = "Aktive kulhydrater"; + +/* The string format describing active carbohydrates. (1: localized glucose value description) */ +"Active Carbohydrates: %@" = "Aktive kulhydrater: %@"; + +/* The title of the Insulin On-Board graph */ +"Active Insulin" = "Aktiv insulin"; + +/* The string format describing active insulin. (1: localized insulin value description) */ +"Active Insulin: %@" = "Aktiv insulin: %@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Tilføj kulhydrater"; + +/* Action sheet title selecting CGM + Title text for button to set up a CGM */ +"Add CGM" = "Tilføj CGM"; + +/* The label of the carb entry button */ +"Add Meal" = "Tilføj måltid"; + +/* Action sheet title selecting Pump + Title text for button to set up a new pump */ +"Add Pump" = "Tilføj pumpe"; + +/* The title of the Amplitude service */ +"Amplitude" = "Amplitude"; + +/* Subtitle of Rapid-Acting – Children preset */ +"An adjustment to the adult model based on empirical effects in children." = "En tilpasning til voksenmodellen baseret på empiriske data hos børn."; + +/* The title of the amplitude API key credential */ +"API Key" = "API nøgle"; + +/* The title of the nightscout API secret credential */ +"API Secret" = "API kodeord"; + +/* Confirmation message for deleting a CGM */ +"Are you sure you want to delete this CGM?" = "Er du sikker på at du vil slette denne CGM?"; + +/* Format fragment for a specific time */ +"at %@" = "at %@"; + +/* The message displayed during a device authentication prompt for bolus specification */ +"Authenticate to Bolus %@ Units" = "Godkend bolus af %@ Enheder"; + +/* Details for configuration error when basal rate schedule is missing */ +"Basal Rate Schedule" = "Basal rater skema"; + +/* The title of the basal rate profile screen + The title text for the basal rate schedule */ +"Basal Rates" = "Basal rater"; + +/* The label of the bolus entry button + The notification title for a bolus failure */ +"Bolus" = "Bolus"; + +/* The format string for bolus progress. (1: delivered volume)(2: total volume) */ +"Bolused %1$@ of %2$@" = "Bolus %1$@ af %2$@"; + +/* The format string for bolus in progress showing total volume. (1: total volume) */ +"Bolusing %1$@" = "Bolus %1$@"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Annuller"; + +/* The title of the cell indicating a bolus is being canceled */ +"Canceling Bolus" = "Annullerer bolus"; + +/* Details for missing data error when carb effects are missing */ +"Carb effects" = "Kulhydrat effekt"; + +/* The title of the carb ratios schedule screen + The title text for the carb ratio schedule */ +"Carb Ratios" = "Kulhydrat ratios"; + +/* Title of the prediction input effect for carbohydrates */ +"Carbohydrates" = "Kulhydrater"; + +/* Description of the prediction input effect for carbohydrates. (1: The glucose unit string) */ +"Carbs Absorbed (g) ÷ Carb Ratio (g/U) × Insulin Sensitivity (%1$@/U)" = "Kulhydrater absorberet (g) ÷ Kulhydrat ratio (g/E) × Insulin følsomhed (%1$@/E)"; + +/* The notification alert describing a low pump battery */ +"Change the pump battery immediately" = "Udskift pumpe batteri omgående"; + +/* The notification alert describing an empty pump reservoir */ +"Change the pump reservoir now" = "Skift pumpe reservoir nu"; + +/* Details for configuration error when one or more loop settings are missing */ +"Check settings" = "Kontroller indstillinger"; + +/* Recovery suggestion when reservoir data is missing */ +"Check that your pump is in range" = "Kontroller, at din pumpe er indenfor rækkevidde"; + +/* Recovery suggestion when glucose data is missing */ +"Check your CGM data source" = "Kontroller din CGM data kilde"; + +/* The title text for the looping enabled switch cell */ +"Closed Loop" = "Lukket Loop"; + +/* The title of the action used to dismiss an error alert */ +"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; + +/* The title of the configuration section in settings */ +"Configuration" = "Konfiguration"; + +/* The error message displayed for configuration errors. (1: configuration error details) */ +"Configuration Error: %1$@" = "Konfigurations fejl: %1$@"; + +/* The title of the continuous glucose monitor section in settings */ +"Continuous Glucose Monitor" = "Kontinuerlig Blodsukker Måler"; + +/* The title of the glucose target range schedule screen + The title text for the glucose target range schedule */ +"Correction Range" = "Korrektionsområde"; + +/* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ +"Current glucose of %1$@ is below correction range." = " Nuværende blodsukker på %1$@ er under dit korrektionsområde."; + +/* The title of the cell indicating a generic temporary override is enabled */ +"Custom Override" = "Overstyr"; + +/* The title of the Loggly customer token credential */ +"Customer Token" = "Kunde Token"; + +/* Button title to delete CGM */ +"Delete CGM" = "Slet CGM"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Indgivningsgrænser"; + +/* The action hint of the workout mode toggle button when enabled */ +"Disables" = "Deaktiverer"; + +/* The action hint of the workout mode toggle button when disabled */ +"Enables" = "Aktiverer"; + +/* The placeholder text instructing users to enter a suspend treshold */ +"Enter suspend threshold" = "Indtast grænse for suspendering"; + +/* The alert title for an error while canceling a bolus */ +"Error Canceling Bolus" = "Fejl ved annullering af bolus"; + +/* The alert title for a resume error */ +"Error Resuming" = "Fejl ved forsøg på genoptagelse"; + +/* The subtitle format describing eventual glucose. (1: localized glucose value description) */ +"Eventually %@" = "Med tiden %@"; + +/* The title of the alert describing a maximum bolus validation error */ +"Exceeds Maximum Bolus" = "Overstiger maximum bolus"; + +/* Title of insulin model preset */ +"Fiasp" = "Fiasp"; + +/* The format string used to describe a finite workout targets duration */ +"For %1$@" = "I %1$@"; + +/* The title of the glucose and prediction graph */ +"Glucose" = "Blodsukker"; + +/* The error message when glucose data is too old to be used. (1: glucose data age in minutes) */ +"Glucose data is %1$@ old" = "Blodsukker data er %1$@ gamle"; + +/* Description of error when glucose data is missing */ +"Glucose data not available" = "Blodsukker data ikke tilgængelige"; + +/* Title of the prediction input effect for glucose momentum */ +"Glucose Momentum" = "Blodsukker momentum"; + +/* The placeholder text for the nightscout site URL credential */ +"https://mysite.herokuapp.com" = "https://minside.herokuapp.com"; + +/* The title of a target alert action specifying an indefinitely long workout targets duration */ +"Indefinitely" = "Uendelig"; + +/* Title of the prediction input effect for insulin */ +"Insulin" = "Insulin"; + +/* Description of the prediction input effect for insulin */ +"Insulin Absorbed (U) × Insulin Sensitivity (%1$@/U)" = "Insulin absorberet (E) × Insulin følsomhed (%1$@/E)"; + +/* The title of the insulin delivery graph */ +"Insulin Delivery" = "Insulin Indgivelse"; + +/* Details for missing data error when insulin effects are missing */ +"Insulin effects" = "Insulin effekter"; + +/* Details for configuration error when insulin model is missing + The title text for the insulin model setting row */ +"Insulin Model" = "Insulin Model"; + +/* The title of the insulin sensitivities schedule screen + The title text for the insulin sensitivity schedule */ +"Insulin Sensitivities" = "Insulin Følsomheder"; + +/* The error message when invalid data was encountered. (1: details of invalid data) */ +"Invalid data: %1$@" = "Fejlagtige data: %1$@"; + +/* The title text for the issue report cell */ +"Issue Report" = "Fejl Rapport"; + +/* Glucose HUD accessibility hint */ +"Launches CGM app" = "Åbner CGM app’en"; + +/* The loading message for the diagnostic report screen */ +"Loading..." = "Danner..."; + +/* The title of the loggly service */ +"Loggly" = "Loggly"; + +/* The notification title for a loop failure */ +"Loop Failure" = "Loop Fejl"; + +/* The notification alert describing a long-lasting loop failure. The substitution parameter is the time interval since the last loop */ +"Loop has not completed successfully in %@" = "Loop har ikke kørt korrekt i %@"; + +/* The error message for missing data. (1: missing data details) */ +"Missing data: %1$@" = "Manglende data: %1$@"; + +/* Details for missing data error when momentum effects are missing */ +"Momentum effects" = "Momentum effekter"; + +/* The title of the Nightscout service */ +"Nightscout" = "Nightscout"; + +/* The error message displayed for device connection errors. */ +"No connected devices, or failure during device connection" = "Ingen tilsluttede enheder, eller fejl under forbindelse til enhed"; + +/* The title text for the override presets */ +"Override Presets" = "Overstyring Forudinstillinger"; + +/* The label of the pre-meal mode toggle button */ +"Pre-Meal Targets" = "Før-Måltid Mål"; + +/* Message when offering bolus recommendation even though bg is below range and minBG is in future. (1: glucose time)(2: glucose number) */ +"Predicted glucose at %1$@ is %2$@." = "Forventet blodsukker ved %1$@ er %2$@."; + +/* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ +"Predicted glucose of %1$@ is below your suspend threshold setting." = "Forventet blodsukker på %1$@ er under den indstillede suspenderingsgrænse."; + +/* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ +"Predicted: %1$@\nActual: %2$@ (%3$@)" = "Forventet: %1$@\nActual: %2$@ (%3$@)"; + +/* The title of the pump section in settings */ +"Pump" = "Pumpe"; + +/* The notification title for a low pump battery */ +"Pump Battery Low" = "Pumpe Batteri Lav"; + +/* The error message when pump data is too old to be used. (1: pump data age in minutes) */ +"Pump data is %1$@ old" = "Pumpe data er %1$@ gamle"; + +/* Details for configuration error when pump manager is missing */ +"Pump Manager" = "Pumpe Manager"; + +/* The notification title for an empty pump reservoir */ +"Pump Reservoir Empty" = "Pumpe Reservoir Tomt"; + +/* The notification title for a low pump reservoir */ +"Pump Reservoir Low" = "Pumpe Reservoir Lavt"; + +/* The title of the cell indicating the pump is suspended */ +"Pump Suspended" = "Pumpe Pauset"; + +/* Title of insulin model preset */ +"Rapid-Acting – Adults" = "Hurtigt-Virkende – Voksne"; + +/* Title of insulin model preset */ +"Rapid-Acting – Children" = "Hurtigt-Virkende – Børn"; + +/* The error message when a recommendation has expired. (1: age of recommendation in minutes) */ +"Recommendation expired: %1$@ old" = "Forslag udløbet: %1$@ gamle"; + +/* The title of the cell displaying a recommended temp basal value */ +"Recommended Basal" = "Foreslået Basal"; + +/* Accessibility hint describing recommended bolus units */ +"Recommended Bolus: %@ Units" = "Foreslået: %@ E"; + +/* Details for missing data error when reservoir data is missing */ +"Reservoir" = "Reservoir"; + +/* Title of the prediction input effect for retrospective correction */ +"Retrospective Correction" = "Tilbagevirkende Korrektion"; + +/* The title of the notification action to retry a bolus command */ +"Retry" = "Forsøg igen"; + +/* The title of the services section in settings */ +"Services" = "Services"; + +/* The label of the settings button */ +"Settings" = "Indstillinger"; + +/* Loop Completion HUD accessibility hint */ +"Shows last loop error" = "Viser sidste Loop fejl"; + +/* Format fragment for a start time */ +"since %@" = "siden %@"; + +/* The title of the nightscout site URL credential */ +"Site URL" = "Side URL"; + +/* The format for the description of a temporary override start date */ +"starting at %@" = "starter ved %@"; + +/* The title of the cell indicating a bolus is being sent */ +"Starting Bolus" = "Starter Bolus"; + +/* The title text in settings */ +"Suspend Threshold" = "Pause Grænseværdi"; + +/* The subtitle of the cell displaying an action to resume insulin delivery */ +"Tap to Resume" = "Tryk for at Fortsætte"; + +/* Subtitle description of Walsh insulin model setting */ +"The legacy model used by Loop, allowing customization of action duration." = "Den model, der bruges af Loop, tillader tilpasning af handlingens varighed."; + +/* Body of the alert describing a maximum bolus validation error. (1: The localized max bolus value) */ +"The maximum bolus amount is %@ Units" = "Den maksimale bolus mængde er %@ Enheder"; + +/* The short unit display string for international units of insulin */ +"U" = "E"; + +/* The format for the description of a temporary override end date */ +"until %@" = "indtil %@"; + +/* The title of the alert controller used to select a duration for workout targets */ +"Use Workout Glucose Targets" = "Anvend Motion Blodsukker Mål"; + +/* Title of insulin model setting */ +"Walsh" = "Walsh"; + +/* Explanation of suspend threshold */ +"When current or forecasted glucose is below the suspend threshold, Loop will not recommend a bolus, and will always recommend a temporary basal rate of 0 units per hour." = "Når nuværende og forventet glukose er under suspenderingsgrænsen, vil Loop ikke anbefale en bolus, og vil altid anbefale en midlertidig basal rate på 0 enheder i timen."; + +/* The label of the workout mode toggle button */ +"Workout Targets" = "Motion Mål"; + diff --git a/Loop/da.lproj/Main.strings b/Loop/da.lproj/Main.strings new file mode 100644 index 0000000000..8df86fc95a --- /dev/null +++ b/Loop/da.lproj/Main.strings @@ -0,0 +1,144 @@ +/* Class = "UINavigationItem"; title = "Status"; ObjectID = "3kU-n2-fha"; */ +"3kU-n2-fha.title" = "Status"; + +/* Class = "UILabel"; text = "3.5 U/hour @ 12:12 PM"; ObjectID = "5gz-kZ-iF1"; */ +"5gz-kZ-iF1.text" = "3.5 E/time @ 12:12 PM"; + +/* Class = "UILabel"; text = "Bolus"; ObjectID = "5oA-6d-ZTL"; */ +"5oA-6d-ZTL.text" = "Bolus"; + +/* Class = "UILabel"; text = "Pump ID"; ObjectID = "5TX-kX-nBo"; */ +"5TX-kX-nBo.text" = "Pumpe ID"; + +/* Class = "UITextField"; accessibilityLabel = "Bolus Amount"; ObjectID = "7LT-50-ZzK"; */ +"7LT-50-ZzK.accessibilityLabel" = "Bolus Mængde"; + +/* Class = "UITextField"; placeholder = "0.0"; ObjectID = "7LT-50-ZzK"; */ +"7LT-50-ZzK.placeholder" = "0.0"; + +/* Class = "UILabel"; text = "Predicted"; ObjectID = "87H-N1-0vJ"; */ +"87H-N1-0vJ.text" = "Forventet"; + +/* Class = "UILabel"; text = "Detail"; ObjectID = "aCb-Qs-bpu"; */ +"aCb-Qs-bpu.text" = "Detalje"; + +/* Class = "UINavigationItem"; title = "Bolus"; ObjectID = "aiu-ZA-zVa"; */ +"aiu-ZA-zVa.title" = "Bolus"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "bIL-Ub-qYp"; */ +"bIL-Ub-qYp.text" = "Label"; + +/* Class = "UILabel"; text = "Glucose Change"; ObjectID = "bq4-98-cQU"; */ +"bq4-98-cQU.text" = "Blodsukker Ændring"; + +/* Class = "UILabel"; accessibilityLabel = "Units"; ObjectID = "BR0-dr-Fj2"; */ +"BR0-dr-Fj2.accessibilityLabel" = "Enheder"; + +/* Class = "UILabel"; text = "U"; ObjectID = "BR0-dr-Fj2"; */ +"BR0-dr-Fj2.text" = "E"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "cpo-Po-gFM"; */ +"cpo-Po-gFM.text" = "Label"; + +/* Class = "UILabel"; text = "g Total"; ObjectID = "d3X-AN-tA5"; */ +"d3X-AN-tA5.text" = "g Total"; + +/* Class = "UILabel"; text = "Future glucose is predicted by combining the effects of multiple inputs. Use this tool to toggle various inputs to see how they compare to the final prediction."; ObjectID = "D4C-I2-dhA"; */ +"D4C-I2-dhA.text" = "Fremtidige blodsukre er beregnet ved at kombinere effekterne af mange inputs. Brug dette værktøj til at vælge mellem forskellige inputs, for at se hvordan de passer med den endelige forudsigelse."; + +/* Class = "UILabel"; text = "Label"; ObjectID = "d6m-qV-wWi"; */ +"d6m-qV-wWi.text" = "Label"; + +/* Class = "UINavigationItem"; title = "Settings"; ObjectID = "dmB-PQ-B44"; */ +"dmB-PQ-B44.title" = "Indstillinger"; + +/* Class = "UILabel"; text = "DEVICES"; ObjectID = "DyC-Sv-qP8"; */ +"DyC-Sv-qP8.text" = "ENHEDER"; + +/* Class = "UILabel"; text = "eventually 92 mg/dL"; ObjectID = "E41-FN-nkk"; */ +"E41-FN-nkk.text" = "eventually 92 mg/dL"; + +/* Class = "UILabel"; text = "Observed"; ObjectID = "EAn-Ja-S1d"; */ +"EAn-Ja-S1d.text" = "Observed"; + +/* Class = "UILabel"; text = "Active Carbohydrates: 40g"; ObjectID = "hHZ-uY-aKw"; */ +"hHZ-uY-aKw.text" = "Aktive Kulhydrater: 40g"; + +/* Class = "UITableViewController"; title = "Carbohydrate Effects"; ObjectID = "hZZ-2S-lrd"; */ +"hZZ-2S-lrd.title" = "Kulhydrate Effekt"; + +/* Class = "UILabel"; text = "Observed changes in glucose, subtracting changes modeled from insulin delivery, can be used to estimate carbohydrate absorption."; ObjectID = "IxU-As-glo"; */ +"IxU-As-glo.text" = "Observed changes in glucose, subtracting changes modeled from insulin delivery, can be used to estimate carbohydrate absorption."; + +/* Class = "UILabel"; text = "Detail"; ObjectID = "J7x-W5-gwo"; */ +"J7x-W5-gwo.text" = "Detail"; + +/* Class = "UILabel"; text = "⚠ Glucose Predicted Below Range"; ObjectID = "k0g-P7-OVN"; */ +"k0g-P7-OVN.text" = "⚠ Blodsukker forventes under interval"; + +/* Class = "UILabel"; text = "Recommended Basal"; ObjectID = "k3F-Na-7mn"; */ +"k3F-Na-7mn.text" = "Foreslået Basal"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "Krd-Aa-ret"; */ +"Krd-Aa-ret.text" = "Label"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "l7l-mw-Oc9"; */ +"l7l-mw-Oc9.text" = "Label"; + +/* Class = "UILabel"; text = "Tap to set"; ObjectID = "m9c-SQ-djE"; */ +"m9c-SQ-djE.text" = "Tryk for at gemme"; + +/* Class = "UILabel"; accessibilityLabel = "Units"; ObjectID = "mVz-dr-xLU"; */ +"mVz-dr-xLU.accessibilityLabel" = "Enheder"; + +/* Class = "UILabel"; text = "U"; ObjectID = "mVz-dr-xLU"; */ +"mVz-dr-xLU.text" = "E"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "OFA-qT-ZAg"; */ +"OFA-qT-ZAg.text" = "Label"; + +/* Class = "UITableViewController"; title = "Predicted Glucose"; ObjectID = "PA3-sP-cWY"; */ +"PA3-sP-cWY.title" = "Predicted Glucose"; + +/* Class = "UITableViewController"; title = "Insulin Model"; ObjectID = "pi6-Dh-72V"; */ +"pi6-Dh-72V.title" = "Insulin Model"; + +/* Class = "UILabel"; text = "An insulin activity model is used to estimate effects of insulin on glucose levels. An accurate model can help prevent insulin stacking and safely recommend corrective treatments."; ObjectID = "PJv-p9-cFe"; */ +"PJv-p9-cFe.text" = "An insulin activity model is used to estimate effects of insulin on glucose levels. An accurate model can help prevent insulin stacking and safely recommend corrective treatments."; + +/* Class = "UILabel"; text = "eventually 92 mg/dL"; ObjectID = "Rse-x8-amW"; */ +"Rse-x8-amW.text" = "Til sidst 92 mg/dL"; + +/* Class = "UILabel"; text = "g COB"; ObjectID = "SQx-au-ZcM"; */ +"SQx-au-ZcM.text" = "g COB"; + +/* Class = "UILabel"; text = "Glucose"; ObjectID = "tuw-av-A3x"; */ +"tuw-av-A3x.text" = "Blodsukker"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "ufi-Kj-33k"; */ +"ufi-Kj-33k.text" = "Label"; + +/* Class = "UILabel"; text = "Active Insulin: 1.5U"; ObjectID = "viU-7a-kbI"; */ +"viU-7a-kbI.text" = "Aktiv Insulin: 1.5E"; + +/* Class = "UINavigationItem"; title = "Carbohydrates"; ObjectID = "Vpi-5b-bY5"; */ +"Vpi-5b-bY5.title" = "Kulhydrater"; + +/* Class = "UITextField"; text = "4 hour"; ObjectID = "Wk3-xv-IM5"; */ +"Wk3-xv-IM5.text" = "4 timer"; + +/* Class = "UIButton"; normalTitle = "Deliver"; ObjectID = "Ya0-9b-ZAS"; */ +"Ya0-9b-ZAS.normalTitle" = "Indgiv"; + +/* Class = "UILabel"; text = "0"; ObjectID = "yn7-2M-jZz"; */ +"yn7-2M-jZz.text" = "0"; + +/* Class = "UILabel"; text = "Recommended"; ObjectID = "ywT-OR-NnU"; */ +"ywT-OR-NnU.text" = "Foreslået"; + +/* Class = "UILabel"; text = "Title"; ObjectID = "zbc-87-wxZ"; */ +"zbc-87-wxZ.text" = "Titel"; + +/* Class = "UILabel"; text = "0"; ObjectID = "zvZ-uf-zMX"; */ +"zvZ-uf-zMX.text" = "0"; + diff --git a/Loop/de.lproj/InfoPlist.strings b/Loop/de.lproj/InfoPlist.strings index bd9feedcaa..f1f820bc44 100644 --- a/Loop/de.lproj/InfoPlist.strings +++ b/Loop/de.lproj/InfoPlist.strings @@ -1,11 +1,11 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "Loop"; - /* Bundle name */ "CFBundleName" = "$(PRODUCT_NAME)"; +/* Privacy - Bluetooth Always Usage Description */ +"NSBluetoothAlwaysUsageDescription" = "Bluetooth wird verwendet, um mit Insulinpumpen und kontinuierlichen Blutzuckermessgeräten zu kommunizieren."; + /* Privacy - Bluetooth Peripheral Usage Description */ -"NSBluetoothPeripheralUsageDescription" = "Bluetooth wird verwendet, um mit Insulinpumpen und kontinuierlichen Blutzuckermessgeräten zu kommunizieren"; +"NSBluetoothPeripheralUsageDescription" = "Bluetooth wird verwendet, um mit Insulinpumpen und kontinuierlichen Blutzuckermessgeräten zu kommunizieren."; /* Privacy - Face ID Usage Description */ "NSFaceIDUsageDescription" = "Face ID wird benutzt, um einen Insulin-Bolus zu authentifizieren."; diff --git a/Loop/de.lproj/LaunchScreen.strings b/Loop/de.lproj/LaunchScreen.strings deleted file mode 100644 index 8b13789179..0000000000 --- a/Loop/de.lproj/LaunchScreen.strings +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Loop/de.lproj/Localizable.strings b/Loop/de.lproj/Localizable.strings index fd6e4145df..6ab5ea3514 100755 --- a/Loop/de.lproj/Localizable.strings +++ b/Loop/de.lproj/Localizable.strings @@ -1,10 +1,22 @@ -/* Created with Localizable Strings Merge, the swiss-army knife for iOS and OS X localization. */ - /* The string format appended to active insulin that describes pending insulin. (1: pending insulin) */ -" (pending: %@)" = " (steht aus: %@)"; +" (pending: %@)" = " (stehen aus: %@)"; + +/* The format for an active override preset. (1: preset symbol)(2: preset name) */ +"%@ %@" = "%1$@ %2$@"; + +/* Formats absorbed carb value */ +"%@ absorbed" = "%@ absorbiert"; + +/* The subtitle format describing total insulin. (1: localized insulin total) */ +"%@ U Total" = "%@ Gesamt-IE"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ +"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; -/* Format string for carb ratio average. (1: value)(2: carb unit) - Format string for insulin sensitivity average (1: value)(2: glucose unit) */ +/* Format string for carb ratio average. (1: value)(2: carb unit) */ "%1$@ %2$@/U" = "%1$@ %2$@/IE"; /* Formats (1: carb start time) and (2: carb absorption duration) */ @@ -17,29 +29,14 @@ "%1$@ U left: %2$@" = "%1$@ IE verbleibend: %2$@"; /* The format for recommended temp basal rate and time. (1: localized rate number)(2: localized time) */ -"%1$@ U/hour @ %2$@" = "%1$@ IE/h @ %2$@"; +"%1$@ U/hour @ %2$@" = "%1$@ IE/Std. @ %2$@"; /* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ "%1$@ v%2$@" = "%1$@ v%2$@"; -/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ -"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; - -/* Format string describing glucose units per minute (1: glucose unit string) */ -"%1$@/min" = "%1$@/min"; - /* Formats (1: carb value) and (2: food type) */ "%1$@: %2$@" = "%1$@: %2$@"; -/* Formats absorbed carb value */ -"%@ absorbed" = "%@ absorbiert"; - -/* The subtitle format describing total insulin. (1: localized insulin total) */ -"%@ U Total" = "%@ Gesamt-IE"; - -/* Appends a full-stop to a statement */ -"%@." = "%@."; - /* Description of the prediction input effect for glucose momentum */ "15 min glucose regression coefficient (b₁), continued with decay over 30 min" = "15 Minuten Blutzucker-Regressionskoeffizient (b1), fortgesetzt mit Abfall über 30 min"; @@ -67,15 +64,20 @@ /* The string format describing active insulin. (1: localized insulin value description) */ "Active Insulin: %@" = "Aktives Insulin: %@"; +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "KH-Eintrag hinzufügen"; + +/* Action sheet title selecting CGM + Title text for button to set up a CGM */ +"Add CGM" = "CGM hinzufügen"; + /* The label of the carb entry button */ "Add Meal" = "Mahlzeit hinzufügen"; -/* Title text for button to set up a new pump */ +/* Action sheet title selecting Pump + Title text for button to set up a new pump */ "Add Pump" = "Pumpe hinzufügen"; -/* The title of the section containing algorithm settings */ -"Algorithm Settings" = "Algorithmus-Einstellungen"; - /* The title of the Amplitude service */ "Amplitude" = "Amplitude"; @@ -88,6 +90,9 @@ /* The title of the nightscout API secret credential */ "API Secret" = "API Secret"; +/* Confirmation message for deleting a CGM */ +"Are you sure you want to delete this CGM?" = "Möchten Sie dieses CGM wirklich löschen?"; + /* Format fragment for a specific time */ "at %@" = "bei %@"; @@ -105,39 +110,51 @@ The notification title for a bolus failure */ "Bolus" = "Bolus"; +/* The format string for bolus progress. (1: delivered volume)(2: total volume) */ +"Bolused %1$@ of %2$@" = "Abgegeben %1$@ of %2$@"; + +/* The format string for bolus in progress showing total volume. (1: total volume) */ +"Bolusing %1$@" = "Abgabe %1$@"; + /* The title of the cancel action in an action sheet */ "Cancel" = "Abbrechen"; +/* The title of the cell indicating a bolus is being canceled */ +"Canceling Bolus" = "Bolus abbrechen"; + /* Details for missing data error when carb effects are missing */ "Carb effects" = "Kohlenhydrat-Wirkungen"; /* The title of the carb ratios schedule screen The title text for the carb ratio schedule */ -"Carb Ratios" = "Kohlenhydrat-Verhältnisse"; +"Carb Ratios" = "Kohlenhydratfaktoren"; /* Title of the prediction input effect for carbohydrates */ "Carbohydrates" = "Kohlenhydrate"; /* Description of the prediction input effect for carbohydrates. (1: The glucose unit string) */ -"Carbs Absorbed (g) ÷ Carb Ratio (g/U) × Insulin Sensitivity (%1$@/U)" = "Aufgenommene Kohlenhydrate (G) ÷ Kohlenhydrat-Verhältnis (G/E) × Insulinempfindlichkeit (%1$@/E)"; +"Carbs Absorbed (g) ÷ Carb Ratio (g/U) × Insulin Sensitivity (%1$@/U)" = "Absorbierte Kohlenhydrate (G) ÷ Kohlenhydratfaktor (G/E) × Insulinempfindlichkeit (%1$@/E)"; /* The notification alert describing a low pump battery */ -"Change the pump battery immediately" = "Wechseln Sie sofort die Pumpenbatterie"; +"Change the pump battery immediately" = "Wechseln Sie sofort die Pumpenbatterie."; /* The notification alert describing an empty pump reservoir */ -"Change the pump reservoir now" = "Wechseln Sie jetzt den Pumpenbehälter"; +"Change the pump reservoir now" = "Wechseln Sie jetzt das Pumpenreservoir."; /* Details for configuration error when one or more loop settings are missing */ -"Check settings" = "Überprüfen Sie die Einstellungen"; +"Check settings" = "Überprüfen Sie die Einstellungen."; /* Recovery suggestion when reservoir data is missing */ -"Check that your pump is in range" = "Stellen Sie sicher, dass sich Ihre Pumpe in Reichweite befindet"; +"Check that your pump is in range" = "Stellen Sie sicher, dass sich Ihre Pumpe in Reichweite befindet."; /* Recovery suggestion when glucose data is missing */ -"Check your CGM data source" = "Überprüfen Sie Ihre CGM-Datenquelle"; +"Check your CGM data source" = "Überprüfen Sie Ihre CGM-Datenquelle."; /* The title text for the looping enabled switch cell */ -"Closed Loop" = "Geschlossener Loop"; +"Closed Loop" = "Closed Loop"; + +/* The title of the action used to dismiss an error alert */ +"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; /* The title of the configuration section in settings */ "Configuration" = "Konfiguration"; @@ -146,7 +163,7 @@ "Configuration Error: %1$@" = "Konfigurationsfehler: %1$@"; /* The title of the continuous glucose monitor section in settings */ -"Continuous Glucose Monitor" = "Kontinuierliche Blutzuckermessung"; +"Continuous Glucose Monitor" = "Kontinuierliche Glukosemessung"; /* The title of the glucose target range schedule screen The title text for the glucose target range schedule */ @@ -155,14 +172,14 @@ /* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ "Current glucose of %1$@ is below correction range." = "Der aktuelle Blutzucker von %1$@ liegt unter dem Korrekturbereich."; +/* The title of the cell indicating a generic temporary override is enabled */ +"Custom Override" = "Eigener Override"; + /* The title of the Loggly customer token credential */ "Customer Token" = "Kunden-Token"; -/* The short unit display string for decibles */ -"dB" = "dB"; - -/* The title of the button to remove the credentials for a service */ -"Delete Account" = "Konto löschen"; +/* Button title to delete CGM */ +"Delete CGM" = "CGM löschen"; /* Title text for delivery limits */ "Delivery Limits" = "Verabreichungslimits"; @@ -170,17 +187,17 @@ /* The action hint of the workout mode toggle button when enabled */ "Disables" = "Deaktivieren"; -/* Title of the switch which toggles retrospective correction effects */ -"Enable Retrospective Correction" = "Retrospektive Korrektur aktivieren"; - /* The action hint of the workout mode toggle button when disabled */ "Enables" = "Aktivieren"; /* The placeholder text instructing users to enter a suspend treshold */ -"Enter suspend threshold" = "Schwellwert für Suspendierung eingeben"; +"Enter suspend threshold" = "Grenzwert für Hypo-Abschaltung eingeben"; + +/* The alert title for an error while canceling a bolus */ +"Error Canceling Bolus" = "Fehler beim Abbrechen des Bolus"; -/* The placeholder text instructing users how to enter a transmitter ID */ -"Enter the 6-digit transmitter ID" = "Geben Sie die 6-stellige Sender-ID ein"; +/* The alert title for a resume error */ +"Error Resuming" = "Fehler beim Fortfahren"; /* The subtitle format describing eventual glucose. (1: localized glucose value description) */ "Eventually %@" = "Voraussichtlich %@"; @@ -194,32 +211,23 @@ /* The format string used to describe a finite workout targets duration */ "For %1$@" = "Für %1$@"; -/* The short unit display string for grams */ -"g" = "g"; - -/* The title text for the G4 Share Receiver switch cell */ -"G4 Share Receiver" = "G4 Share Empfänger"; - -/* The title text for the G5/G6 Transmitter switch cell */ -"G5/G6 Transmitter" = "G5/G6 Sender"; - /* The title of the glucose and prediction graph */ -"Glucose" = "Blutzucker"; +"Glucose" = "Glukose"; /* The error message when glucose data is too old to be used. (1: glucose data age in minutes) */ -"Glucose data is %1$@ old" = "Blutzuckerdaten sind %1$@ alt"; +"Glucose data is %1$@ old" = "Glukosedaten sind %1$@ alt"; /* Description of error when glucose data is missing */ -"Glucose data not available" = "Blutzuckerdaten sind nicht verfügbar"; +"Glucose data not available" = "Glukosedaten sind nicht verfügbar"; /* Title of the prediction input effect for glucose momentum */ -"Glucose Momentum" = "Blutzucker-Momentum"; +"Glucose Momentum" = "Glukose-Momentum"; /* The placeholder text for the nightscout site URL credential */ "https://mysite.herokuapp.com" = "https://mysite.herokuapp.com"; /* The title of a target alert action specifying an indefinitely long workout targets duration */ -"Indefinitely" = "Unendlich"; +"Indefinitely" = "Unbegrenzt"; /* Title of the prediction input effect for insulin */ "Insulin" = "Insulin"; @@ -239,7 +247,7 @@ /* The title of the insulin sensitivities schedule screen The title text for the insulin sensitivity schedule */ -"Insulin Sensitivities" = "Insulinsensitivitäten"; +"Insulin Sensitivities" = "Insulinempfindlichkeit"; /* The error message when invalid data was encountered. (1: details of invalid data) */ "Invalid data: %1$@" = "Ungültige Daten: %1$@"; @@ -247,38 +255,26 @@ /* The title text for the issue report cell */ "Issue Report" = "Problembericht"; -/* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ -"prediction-description-retrospective-correction" = "Vorausgesagt: %1$@\nAktuell: %2$@ (%3$@)"; - /* Glucose HUD accessibility hint */ "Launches CGM app" = "Startet die CGM-App"; /* The loading message for the diagnostic report screen */ -"Loading..." = "Wird geladen..."; +"Loading..." = "Wird geladen ..."; /* The title of the loggly service */ "Loggly" = "Loggly"; /* The notification title for a loop failure */ -"Loop Failure" = "Loop-Fehler"; +"Loop Failure" = "Loopfehler"; /* The notification alert describing a long-lasting loop failure. The substitution parameter is the time interval since the last loop */ "Loop has not completed successfully in %@" = "Loop wurde nicht erfolgreich abgeschlossen seit %@"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dl"; - /* The error message for missing data. (1: missing data details) */ "Missing data: %1$@" = "Fehlende Daten: %1$@"; -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/l"; - /* Details for missing data error when momentum effects are missing */ -"Momentum effects" = "Momentumwirkungen"; - -/* Sensor state description for the non-valid state */ -"Needs Attention" = "Erfordert Prüfung"; +"Momentum effects" = "Momentum effects"; /* The title of the Nightscout service */ "Nightscout" = "Nightscout"; @@ -286,14 +282,20 @@ /* The error message displayed for device connection errors. */ "No connected devices, or failure during device connection" = "Keine angeschlossenen Geräte oder Fehler während der Geräteverbindung"; +/* The title text for the override presets */ +"Override Presets" = "Override-Voreinstellungen"; + /* The label of the pre-meal mode toggle button */ "Pre-Meal Targets" = "Ziele vor dem Essen"; /* Message when offering bolus recommendation even though bg is below range and minBG is in future. (1: glucose time)(2: glucose number) */ -"Predicted glucose at %1$@ is %2$@." = "Vorhergesagter Blutzucker um %1$@ ist %2$@."; +"Predicted glucose at %1$@ is %2$@." = "Vorhergesagte Glukose um %1$@ ist %2$@."; /* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ -"Predicted glucose of %1$@ is below your suspend threshold setting." = "Der vorhergesagte Blutzucker von %1$@ liegt unter der eingestellten Schwelle."; +"Predicted glucose of %1$@ is below your suspend threshold setting." = "Die vorhergesagte Glukose von %1$@ liegt unter dem Grenzwert für die Hypo-Abschaltung."; + +/* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ +"Predicted: %1$@\nActual: %2$@ (%3$@)" = "Vorausgesagt: %1$@\nAktuell: %2$@ (%3$@)"; /* The title of the pump section in settings */ "Pump" = "Pumpe"; @@ -313,11 +315,14 @@ /* The notification title for a low pump reservoir */ "Pump Reservoir Low" = "Pumpenreservoir niedrig"; +/* The title of the cell indicating the pump is suspended */ +"Pump Suspended" = "Pumpe unterbrochen"; + /* Title of insulin model preset */ -"Rapid-Acting – Adults" = "Schnell wirkend - Erwachsene"; +"Rapid-Acting – Adults" = "Schnellwirkend - Erwachsene"; /* Title of insulin model preset */ -"Rapid-Acting – Children" = "Schnell wirkend - Kinder"; +"Rapid-Acting – Children" = "Schnellwirkend - Kinder"; /* The error message when a recommendation has expired. (1: age of recommendation in minutes) */ "Recommendation expired: %1$@ old" = "Empfehlung abgelaufen: %1$@ alt"; @@ -352,39 +357,39 @@ /* The title of the nightscout site URL credential */ "Site URL" = "Seiten-URL"; -/* The title text for the Medtronic sensor switch cell */ -"Sof-Sensor / Enlite" = "Sof-Sensor / Enlite"; +/* The format for the description of a temporary override start date */ +"starting at %@" = "Beginnt um %@"; /* The title of the cell indicating a bolus is being sent */ "Starting Bolus" = "Starte Bolus"; /* The title text in settings */ -"Suspend Threshold" = "Unterbrechungs-Schwellenwert"; +"Suspend Threshold" = "Wert für die Hypo-Abschaltung"; + +/* The subtitle of the cell displaying an action to resume insulin delivery */ +"Tap to Resume" = "Tap to Resume"; /* Subtitle description of Walsh insulin model setting */ -"The legacy model used by Loop, allowing customization of action duration." = "Das Legacy-Modell, das von Loop verwendet wird und die Anpassung der Wirkdauer ermöglicht."; +"The legacy model used by Loop, allowing customization of action duration." = "Das herkömmliche von Loop verwendete Modell, welches die Anpassung der Wirkungsdauer ermöglicht."; /* Body of the alert describing a maximum bolus validation error. (1: The localized max bolus value) */ "The maximum bolus amount is %@ Units" = "Die maximale Bolusmenge beträgt %@ IE"; -/* Instructions on where to find the transmitter ID */ -"The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app." = "Die Sender-ID finden Sie auf der Rückseite des Geräts, auf der Seite der Verpackung, in der es geliefert wurde, und in den Einstellungsmenüs des G5/G6-Empfängers und der mobilen App."; - -/* The description of the switch which toggles retrospective correction effects */ -"This will more aggresively increase or decrease basal delivery when glucose movement doesn't match the carbohydrate and insulin-based model." = "Dies wird die basale Abgabe stärker erhöhen oder verringern, wenn der Blutzuckerverlauf nicht mit dem Kohlenhydrat- und Insulin-basierten Modell übereinstimmt."; - /* The short unit display string for international units of insulin */ "U" = "IE"; +/* The format for the description of a temporary override end date */ +"until %@" = "Endet um %@"; + /* The title of the alert controller used to select a duration for workout targets */ -"Use Workout Glucose Targets" = "Verwende Trainings-Blutzuckerziele"; +"Use Workout Glucose Targets" = "Zielbereich für Sport verwenden"; /* Title of insulin model setting */ "Walsh" = "Walsh"; /* Explanation of suspend threshold */ -"When current or forecasted glucose is below the suspend threshold, Loop will not recommend a bolus, and will always recommend a temporary basal rate of 0 units per hour." = "Wenn der aktuelle oder prognostizierte Blutzucker unter dem Schwellenwert für die Suspendierung liegt, empfiehlt Loop keinen Bolus und immer eine temporäre Basalrate von 0 Einheiten pro Stunde."; +"When current or forecasted glucose is below the suspend threshold, Loop will not recommend a bolus, and will always recommend a temporary basal rate of 0 units per hour." = "Wenn die aktuelle oder prognostizierte Glukose unter dem Wert für die Hypo-Abschaltung liegt, empfiehlt Loop immer eine temporäre Basalrate von 0 Einheiten pro Stunde und - wenn kein gesonderter Bolusgrenzwert definiert ist - auch keinen Bolus."; /* The label of the workout mode toggle button */ -"Workout Targets" = "Trainings-Blutzuckerziele"; +"Workout Targets" = "Zielbereich für Sport"; diff --git a/Loop/de.lproj/Main.strings b/Loop/de.lproj/Main.strings index f91f714210..52b9b490dc 100644 --- a/Loop/de.lproj/Main.strings +++ b/Loop/de.lproj/Main.strings @@ -25,9 +25,6 @@ /* Class = "UINavigationItem"; title = "Bolus"; ObjectID = "aiu-ZA-zVa"; */ "aiu-ZA-zVa.title" = "Bolus"; -/* Class = "UILabel"; text = "Title"; ObjectID = "B9F-K3-7RI"; */ -"B9F-K3-7RI.text" = "Titel"; - /* Class = "UILabel"; text = "Label"; ObjectID = "bIL-Ub-qYp"; */ "bIL-Ub-qYp.text" = "Beschriftung"; diff --git a/Loop/en.lproj/InfoPlist.strings b/Loop/en.lproj/InfoPlist.strings new file mode 100644 index 0000000000..dd51a9cde8 --- /dev/null +++ b/Loop/en.lproj/InfoPlist.strings @@ -0,0 +1,18 @@ + +/* Bundle name */ +"CFBundleName" = "$(PRODUCT_NAME)"; + +/* Privacy - Bluetooth Always Usage Description */ +"NSBluetoothAlwaysUsageDescription" = "Bluetooth is used to communicate with insulin pump and continuous glucose monitor devices."; + +/* Privacy - Bluetooth Peripheral Usage Description */ +"NSBluetoothPeripheralUsageDescription" = "Bluetooth is used to communicate with insulin pump and continuous glucose monitor devices."; + +/* Privacy - Face ID Usage Description */ +"NSFaceIDUsageDescription" = "Face ID is used to authenticate insulin bolus."; + +/* Privacy - Health Share Usage Description */ +"NSHealthShareUsageDescription" = "Meal data from the Health database is used to determine glucose effects. Glucose data from the Health database is used for graphing and momentum calculation."; + +/* Privacy - Health Update Usage Description */ +"NSHealthUpdateUsageDescription" = "Carbohydrate meal data entered in the app and on the watch is stored in the Health database. Glucose data retrieved from the CGM is stored securely in HealthKit."; diff --git a/Loop/en.lproj/Localizable.strings b/Loop/en.lproj/Localizable.strings new file mode 100644 index 0000000000..bc11213827 --- /dev/null +++ b/Loop/en.lproj/Localizable.strings @@ -0,0 +1,392 @@ +/* The string format appended to active insulin that describes pending insulin. (1: pending insulin) */ +" (pending: %@)" = " (pending: %@)"; + +/* Format string for carb ratio average. (1: value)(2: carb unit) */ +"%1$@ %2$@/U" = "%1$@ %2$@/U"; + +/* Formats (1: carb start time) and (2: carb absorption duration) */ +"%1$@ + %2$@" = "%1$@ + %2$@"; + +/* Low reservoir alert format string. (1: Number of units remaining) */ +"%1$@ U left" = "%1$@ U left"; + +/* Low reservoir alert with time remaining format string. (1: Number of units remaining)(2: approximate time remaining) */ +"%1$@ U left: %2$@" = "%1$@ U left: %2$@"; + +/* The format for recommended temp basal rate and time. (1: localized rate number)(2: localized time) */ +"%1$@ U/hour @ %2$@" = "%1$@ U/hour @ %2$@"; + +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ +"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; + +/* Formats (1: carb value) and (2: food type) */ +"%1$@: %2$@" = "%1$@: %2$@"; + +/* The format for an active override preset. (1: preset symbol)(2: preset name) */ +"%@ %@" = "%1$@ %2$@"; + +/* Formats absorbed carb value */ +"%@ absorbed" = "%@ absorbed"; + +/* The subtitle format describing total insulin. (1: localized insulin total) */ +"%@ U Total" = "%@ U Total"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Description of the prediction input effect for glucose momentum */ +"15 min glucose regression coefficient (b₁), continued with decay over 30 min" = "15 min glucose regression coefficient (b₁), continued with decay over 30 min"; + +/* Description of the prediction input effect for retrospective correction */ +"30 min comparison of glucose prediction vs actual, continued with decay over 60 min" = "30 min comparison of glucose prediction vs actual, continued with decay over 60 min"; + +/* Subtitle of Fiasp preset */ +"A model based on the published absorption of Fiasp insulin." = "A model based on the published absorption of Fiasp insulin."; + +/* Subtitle of Rapid-Acting – Adult preset */ +"A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults." = "A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults."; + +/* Action to copy the recommended Bolus value to the actual Bolus Field */ +"AcceptRecommendedBolus" = "AcceptRecommendedBolus"; + +/* The title of the Carbs On-Board graph */ +"Active Carbohydrates" = "Active Carbohydrates"; + +/* The string format describing active carbohydrates. (1: localized glucose value description) */ +"Active Carbohydrates: %@" = "Active Carbohydrates: %@"; + +/* The title of the Insulin On-Board graph */ +"Active Insulin" = "Active Insulin"; + +/* The string format describing active insulin. (1: localized insulin value description) */ +"Active Insulin: %@" = "Active Insulin: %@"; + +/* Action sheet title selecting CGM + Title text for button to set up a CGM */ +"Add CGM" = "Add CGM"; + +/* The label of the meal button */ +"Add Meal" = "Add Meal"; + +/* Action sheet title selecting Pump + Title text for button to set up a new pump */ +"Add Pump" = "Add Pump"; + +/* The title of the Amplitude service */ +"Amplitude" = "Amplitude"; + +/* Subtitle of Rapid-Acting – Children preset */ +"An adjustment to the adult model based on empirical effects in children." = "An adjustment to the adult model based on empirical effects in children."; + +/* The title of the amplitude API key credential */ +"API Key" = "API Key"; + +/* The title of the nightscout API secret credential */ +"API Secret" = "API Secret"; + +/* Confirmation message for deleting a CGM */ +"Are you sure you want to delete this CGM?" = "Are you sure you want to delete this CGM?"; + +/* Format fragment for a specific time */ +"at %@" = "at %@"; + +/* The message displayed during a device authentication prompt for bolus specification */ +"Authenticate to Bolus %@ Units" = "Authenticate to Bolus %@ Units"; + +/* Details for configuration error when basal rate schedule is missing */ +"Basal Rate Schedule" = "Basal Rate Schedule"; + +/* The title of the basal rate profile screen + The title text for the basal rate schedule */ +"Basal Rates" = "Basal Rates"; + +/* The label of the bolus entry button + The notification title for a bolus failure */ +"Bolus" = "Bolus"; + +/* The format string for bolus progress. (1: delivered volume)(2: total volume) */ +"Bolused %1$@ of %2$@" = "Bolused %1$@ of %2$@"; + +/* The format string for bolus in progress showing total volume. (1: total volume) */ +"Bolusing %1$@" = "Bolusing %1$@"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Cancel"; + +/* The title of the cell indicating a bolus is being canceled */ +"Canceling Bolus" = "Canceling Bolus"; + +/* Details for missing data error when carb effects are missing */ +"Carb effects" = "Carb effects"; + +/* The title of the carb ratios schedule screen + The title text for the carb ratio schedule */ +"Carb Ratios" = "Carb Ratios"; + +/* Title of the prediction input effect for carbohydrates */ +"Carbohydrates" = "Carbohydrates"; + +/* Description of the prediction input effect for carbohydrates. (1: The glucose unit string) */ +"Carbs Absorbed (g) ÷ Carb Ratio (g/U) × Insulin Sensitivity (%1$@/U)" = "Carbs Absorbed (g) ÷ Carb Ratio (g/U) × Insulin Sensitivity (%1$@/U)"; + +/* The notification alert describing a low pump battery */ +"Change the pump battery immediately" = "Change the pump battery immediately"; + +/* The notification alert describing an empty pump reservoir */ +"Change the pump reservoir now" = "Change the pump reservoir now"; + +/* Details for configuration error when one or more loop settings are missing */ +"Check settings" = "Check settings"; + +/* Recovery suggestion when reservoir data is missing */ +"Check that your pump is in range" = "Check that your pump is in range"; + +/* Recovery suggestion when glucose data is missing */ +"Check your CGM data source" = "Check your CGM data source"; + +/* The title text for the looping enabled switch cell */ +"Closed Loop" = "Closed Loop"; + +/* The title of the action used to dismiss an error alert */ +"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; + +/* The title of the configuration section in settings */ +"Configuration" = "Configuration"; + +/* The error message displayed for configuration errors. (1: configuration error details) */ +"Configuration Error: %1$@" = "Configuration Error: %1$@"; + +/* The title of the continuous glucose monitor section in settings */ +"Continuous Glucose Monitor" = "Continuous Glucose Monitor"; + +/* The title of the glucose target range schedule screen + The title text for the glucose target range schedule */ +"Correction Range" = "Correction Range"; + +/* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ +"Current glucose of %1$@ is below correction range." = "Current glucose of %1$@ is below correction range."; + +/* The title of the cell indicating a generic temporary override is enabled */ +"Custom Override" = "Custom Override"; + +/* The title of the Loggly customer token credential */ +"Customer Token" = "Customer Token"; + +/* Button title to delete CGM */ +"Delete CGM" = "Delete CGM"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Delivery Limits"; + +/* The action hint of the workout mode toggle button when enabled */ +"Disables" = "Disables"; + +/* The action hint of the workout mode toggle button when disabled */ +"Enables" = "Enables"; + +/* The placeholder text instructing users to enter a suspend treshold */ +"Enter suspend threshold" = "Enter suspend threshold"; + +/* The alert title for an error while canceling a bolus */ +"Error Canceling Bolus" = "Error Canceling Bolus"; + +/* The alert title for a resume error */ +"Error Resuming" = "Error Resuming"; + +/* The subtitle format describing eventual glucose. (1: localized glucose value description) */ +"Eventually %@" = "Eventually %@"; + +/* The title of the alert describing a maximum bolus validation error */ +"Exceeds Maximum Bolus" = "Exceeds Maximum Bolus"; + +/* Title of insulin model preset */ +"Fiasp" = "Fiasp"; + +/* The format string used to describe a finite workout targets duration */ +"For %1$@" = "For %1$@"; + +/* The title of the glucose and prediction graph */ +"Glucose" = "Glucose"; + +/* The error message when glucose data is too old to be used. (1: glucose data age in minutes) */ +"Glucose data is %1$@ old" = "Glucose data is %1$@ old"; + +/* Description of error when glucose data is missing */ +"Glucose data not available" = "Glucose data not available"; + +/* Title of the prediction input effect for glucose momentum */ +"Glucose Momentum" = "Glucose Momentum"; + +/* The placeholder text for the nightscout site URL credential */ +"https://mysite.herokuapp.com" = "https://mysite.herokuapp.com"; + +/* The title of a target alert action specifying an indefinitely long workout targets duration */ +"Indefinitely" = "Indefinitely"; + +/* Title of the prediction input effect for insulin */ +"Insulin" = "Insulin"; + +/* Description of the prediction input effect for insulin */ +"Insulin Absorbed (U) × Insulin Sensitivity (%1$@/U)" = "Insulin Absorbed (U) × Insulin Sensitivity (%1$@/U)"; + +/* The title of the insulin delivery graph */ +"Insulin Delivery" = "Insulin Delivery"; + +/* Details for missing data error when insulin effects are missing */ +"Insulin effects" = "Insulin effects"; + +/* Details for configuration error when insulin model is missing + The title text for the insulin model setting row */ +"Insulin Model" = "Insulin Model"; + +/* The title of the insulin sensitivities schedule screen + The title text for the insulin sensitivity schedule */ +"Insulin Sensitivities" = "Insulin Sensitivities"; + +/* The error message when invalid data was encountered. (1: details of invalid data) */ +"Invalid data: %1$@" = "Invalid data: %1$@"; + +/* The title text for the issue report cell */ +"Issue Report" = "Issue Report"; + +/* Glucose HUD accessibility hint */ +"Launches CGM app" = "Launches CGM app"; + +/* The loading message for the diagnostic report screen */ +"Loading..." = "Loading..."; + +/* The title of the loggly service */ +"Loggly" = "Loggly"; + +/* The notification title for a loop failure */ +"Loop Failure" = "Loop Failure"; + +/* The notification alert describing a long-lasting loop failure. The substitution parameter is the time interval since the last loop */ +"Loop has not completed successfully in %@" = "Loop has not completed successfully in %@"; + +/* The error message for missing data. (1: missing data details) */ +"Missing data: %1$@" = "Missing data: %1$@"; + +/* Details for missing data error when momentum effects are missing */ +"Momentum effects" = "Momentum effects"; + +/* The title of the Nightscout service */ +"Nightscout" = "Nightscout"; + +/* The error message displayed for device connection errors. */ +"No connected devices, or failure during device connection" = "No connected devices, or failure during device connection"; + +/* The title text for the override presets */ +"Override Presets" = "Override Presets"; + +/* The label of the pre-meal mode toggle button */ +"Pre-Meal Targets" = "Pre-Meal Targets"; + +/* Message when offering bolus recommendation even though bg is below range and minBG is in future. (1: glucose time)(2: glucose number) */ +"Predicted glucose at %1$@ is %2$@." = "Predicted glucose at %1$@ is %2$@."; + +/* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ +"Predicted glucose of %1$@ is below your suspend threshold setting." = "Predicted glucose of %1$@ is below your suspend threshold setting."; + +/* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ +"Predicted: %1$@\nActual: %2$@ (%3$@)" = "Predicted: %1$@\nActual: %2$@ (%3$@)"; + +/* The title of the pump section in settings */ +"Pump" = "Pump"; + +/* The notification title for a low pump battery */ +"Pump Battery Low" = "Pump Battery Low"; + +/* The error message when pump data is too old to be used. (1: pump data age in minutes) */ +"Pump data is %1$@ old" = "Pump data is %1$@ old"; + +/* Details for configuration error when pump manager is missing */ +"Pump Manager" = "Pump Manager"; + +/* The notification title for an empty pump reservoir */ +"Pump Reservoir Empty" = "Pump Reservoir Empty"; + +/* The notification title for a low pump reservoir */ +"Pump Reservoir Low" = "Pump Reservoir Low"; + +/* The title of the cell indicating the pump is suspended */ +"Pump Suspended" = "Pump Suspended"; + +/* Title of insulin model preset */ +"Rapid-Acting – Adults" = "Rapid-Acting – Adults"; + +/* Title of insulin model preset */ +"Rapid-Acting – Children" = "Rapid-Acting – Children"; + +/* The error message when a recommendation has expired. (1: age of recommendation in minutes) */ +"Recommendation expired: %1$@ old" = "Recommendation expired: %1$@ old"; + +/* The title of the cell displaying a recommended temp basal value */ +"Recommended Basal" = "Recommended Basal"; + +/* Accessibility hint describing recommended bolus units */ +"Recommended Bolus: %@ Units" = "Recommended Bolus: %@ Units"; + +/* Details for missing data error when reservoir data is missing */ +"Reservoir" = "Reservoir"; + +/* Title of the prediction input effect for retrospective correction */ +"Retrospective Correction" = "Retrospective Correction"; + +/* The title of the notification action to retry a bolus command */ +"Retry" = "Retry"; + +/* The title of the services section in settings */ +"Services" = "Services"; + +/* The label of the settings button */ +"Settings" = "Settings"; + +/* Loop Completion HUD accessibility hint */ +"Shows last loop error" = "Shows last loop error"; + +/* Format fragment for a start time */ +"since %@" = "since %@"; + +/* The title of the nightscout site URL credential */ +"Site URL" = "Site URL"; + +/* The format for the description of a temporary override start date */ +"starting at %@" = "starting at %@"; + +/* The title of the cell indicating a bolus is being sent */ +"Starting Bolus" = "Starting Bolus"; + +/* The title text in settings */ +"Suspend Threshold" = "Suspend Threshold"; + +/* The subtitle of the cell displaying an action to resume insulin delivery */ +"Tap to Resume" = "Tap to Resume"; + +/* Subtitle description of Walsh insulin model setting */ +"The legacy model used by Loop, allowing customization of action duration." = "The legacy model used by Loop, allowing customization of action duration."; + +/* Body of the alert describing a maximum bolus validation error. (1: The localized max bolus value) */ +"The maximum bolus amount is %@ Units" = "The maximum bolus amount is %@ Units"; + +/* The short unit display string for international units of insulin */ +"U" = "U"; + +/* The format for the description of a temporary override end date */ +"until %@" = "until %@"; + +/* The title of the alert controller used to select a duration for workout targets */ +"Use Workout Glucose Targets" = "Use Workout Glucose Targets"; + +/* Title of insulin model setting */ +"Walsh" = "Walsh"; + +/* Explanation of suspend threshold */ +"When current or forecasted glucose is below the suspend threshold, Loop will not recommend a bolus, and will always recommend a temporary basal rate of 0 units per hour." = "When current or forecasted glucose is below the suspend threshold, Loop will not recommend a bolus, and will always recommend a temporary basal rate of 0 units per hour."; + +/* The label of the workout mode toggle button */ +"Workout Targets" = "Workout Targets"; + diff --git a/Loop/en.lproj/Main.strings b/Loop/en.lproj/Main.strings new file mode 100644 index 0000000000..9a43d05bab --- /dev/null +++ b/Loop/en.lproj/Main.strings @@ -0,0 +1,144 @@ + +/* Class = "UINavigationItem"; title = "Status"; ObjectID = "3kU-n2-fha"; */ +"3kU-n2-fha.title" = "Status"; + +/* Class = "UILabel"; text = "Pump ID"; ObjectID = "5TX-kX-nBo"; */ +"5TX-kX-nBo.text" = "Pump ID"; + +/* Class = "UILabel"; text = "3.5 U/hour @ 12:12 PM"; ObjectID = "5gz-kZ-iF1"; */ +"5gz-kZ-iF1.text" = "3.5 U/hour @ 12:12 PM"; + +/* Class = "UILabel"; text = "Bolus"; ObjectID = "5oA-6d-ZTL"; */ +"5oA-6d-ZTL.text" = "Bolus"; + +/* Class = "UITextField"; accessibilityLabel = "Bolus Amount"; ObjectID = "7LT-50-ZzK"; */ +"7LT-50-ZzK.accessibilityLabel" = "Bolus Amount"; + +/* Class = "UITextField"; placeholder = "0.0"; ObjectID = "7LT-50-ZzK"; */ +"7LT-50-ZzK.placeholder" = "0.0"; + +/* Class = "UILabel"; text = "Predicted"; ObjectID = "87H-N1-0vJ"; */ +"87H-N1-0vJ.text" = "Predicted"; + +/* Class = "UILabel"; accessibilityLabel = "Units"; ObjectID = "BR0-dr-Fj2"; */ +"BR0-dr-Fj2.accessibilityLabel" = "Units"; + +/* Class = "UILabel"; text = "U"; ObjectID = "BR0-dr-Fj2"; */ +"BR0-dr-Fj2.text" = "U"; + +/* Class = "UILabel"; text = "Future glucose is predicted by combining the effects of multiple inputs. Use this tool to toggle various inputs to see how they compare to the final prediction."; ObjectID = "D4C-I2-dhA"; */ +"D4C-I2-dhA.text" = "Future glucose is predicted by combining the effects of multiple inputs. Use this tool to toggle various inputs to see how they compare to the final prediction."; + +/* Class = "UILabel"; text = "DEVICES"; ObjectID = "DyC-Sv-qP8"; */ +"DyC-Sv-qP8.text" = "DEVICES"; + +/* Class = "UILabel"; text = "eventually 92 mg/dL"; ObjectID = "E41-FN-nkk"; */ +"E41-FN-nkk.text" = "eventually 92 mg/dL"; + +/* Class = "UILabel"; text = "Observed"; ObjectID = "EAn-Ja-S1d"; */ +"EAn-Ja-S1d.text" = "Observed"; + +/* Class = "UILabel"; text = "Observed changes in glucose, subtracting changes modeled from insulin delivery, can be used to estimate carbohydrate absorption."; ObjectID = "IxU-As-glo"; */ +"IxU-As-glo.text" = "Observed changes in glucose, subtracting changes modeled from insulin delivery, can be used to estimate carbohydrate absorption."; + +/* Class = "UILabel"; text = "Detail"; ObjectID = "J7x-W5-gwo"; */ +"J7x-W5-gwo.text" = "Detail"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "Krd-Aa-ret"; */ +"Krd-Aa-ret.text" = "Label"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "OFA-qT-ZAg"; */ +"OFA-qT-ZAg.text" = "Label"; + +/* Class = "UITableViewController"; title = "Predicted Glucose"; ObjectID = "PA3-sP-cWY"; */ +"PA3-sP-cWY.title" = "Predicted Glucose"; + +/* Class = "UILabel"; text = "An insulin activity model is used to estimate effects of insulin on glucose levels. An accurate model can help prevent insulin stacking and safely recommend corrective treatments."; ObjectID = "PJv-p9-cFe"; */ +"PJv-p9-cFe.text" = "An insulin activity model is used to estimate effects of insulin on glucose levels. An accurate model can help prevent insulin stacking and safely recommend corrective treatments."; + +/* Class = "UILabel"; text = "eventually 92 mg/dL"; ObjectID = "Rse-x8-amW"; */ +"Rse-x8-amW.text" = "eventually 92 mg/dL"; + +/* Class = "UILabel"; text = "g COB"; ObjectID = "SQx-au-ZcM"; */ +"SQx-au-ZcM.text" = "g COB"; + +/* Class = "UINavigationItem"; title = "Carbohydrates"; ObjectID = "Vpi-5b-bY5"; */ +"Vpi-5b-bY5.title" = "Carbohydrates"; + +/* Class = "UITextField"; text = "4 hour"; ObjectID = "Wk3-xv-IM5"; */ +"Wk3-xv-IM5.text" = "4 hour"; + +/* Class = "UIButton"; normalTitle = "Deliver"; ObjectID = "Ya0-9b-ZAS"; */ +"Ya0-9b-ZAS.normalTitle" = "Deliver"; + +/* Class = "UILabel"; text = "Detail"; ObjectID = "aCb-Qs-bpu"; */ +"aCb-Qs-bpu.text" = "Detail"; + +/* Class = "UINavigationItem"; title = "Bolus"; ObjectID = "aiu-ZA-zVa"; */ +"aiu-ZA-zVa.title" = "Bolus"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "bIL-Ub-qYp"; */ +"bIL-Ub-qYp.text" = "Label"; + +/* Class = "UILabel"; text = "Glucose Change"; ObjectID = "bq4-98-cQU"; */ +"bq4-98-cQU.text" = "Glucose Change"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "cpo-Po-gFM"; */ +"cpo-Po-gFM.text" = "Label"; + +/* Class = "UILabel"; text = "g Total"; ObjectID = "d3X-AN-tA5"; */ +"d3X-AN-tA5.text" = "g Total"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "d6m-qV-wWi"; */ +"d6m-qV-wWi.text" = "Label"; + +/* Class = "UINavigationItem"; title = "Settings"; ObjectID = "dmB-PQ-B44"; */ +"dmB-PQ-B44.title" = "Settings"; + +/* Class = "UILabel"; text = "Active Carbohydrates: 40g"; ObjectID = "hHZ-uY-aKw"; */ +"hHZ-uY-aKw.text" = "Active Carbohydrates: 40g"; + +/* Class = "UITableViewController"; title = "Carbohydrate Effects"; ObjectID = "hZZ-2S-lrd"; */ +"hZZ-2S-lrd.title" = "Carbohydrate Effects"; + +/* Class = "UILabel"; text = "⚠ Glucose Predicted Below Range"; ObjectID = "k0g-P7-OVN"; */ +"k0g-P7-OVN.text" = "⚠ Glucose Predicted Below Range"; + +/* Class = "UILabel"; text = "Recommended Basal"; ObjectID = "k3F-Na-7mn"; */ +"k3F-Na-7mn.text" = "Recommended Basal"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "l7l-mw-Oc9"; */ +"l7l-mw-Oc9.text" = "Label"; + +/* Class = "UILabel"; text = "Tap to set"; ObjectID = "m9c-SQ-djE"; */ +"m9c-SQ-djE.text" = "Tap to set"; + +/* Class = "UILabel"; accessibilityLabel = "Units"; ObjectID = "mVz-dr-xLU"; */ +"mVz-dr-xLU.accessibilityLabel" = "Units"; + +/* Class = "UILabel"; text = "U"; ObjectID = "mVz-dr-xLU"; */ +"mVz-dr-xLU.text" = "U"; + +/* Class = "UITableViewController"; title = "Insulin Model"; ObjectID = "pi6-Dh-72V"; */ +"pi6-Dh-72V.title" = "Insulin Model"; + +/* Class = "UILabel"; text = "Glucose"; ObjectID = "tuw-av-A3x"; */ +"tuw-av-A3x.text" = "Glucose"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "ufi-Kj-33k"; */ +"ufi-Kj-33k.text" = "Label"; + +/* Class = "UILabel"; text = "Active Insulin: 1.5U"; ObjectID = "viU-7a-kbI"; */ +"viU-7a-kbI.text" = "Active Insulin: 1.5U"; + +/* Class = "UILabel"; text = "0"; ObjectID = "yn7-2M-jZz"; */ +"yn7-2M-jZz.text" = "0"; + +/* Class = "UILabel"; text = "Recommended"; ObjectID = "ywT-OR-NnU"; */ +"ywT-OR-NnU.text" = "Recommended"; + +/* Class = "UILabel"; text = "Title"; ObjectID = "zbc-87-wxZ"; */ +"zbc-87-wxZ.text" = "Title"; + +/* Class = "UILabel"; text = "0"; ObjectID = "zvZ-uf-zMX"; */ +"zvZ-uf-zMX.text" = "0"; diff --git a/Loop/es.lproj/InfoPlist.strings b/Loop/es.lproj/InfoPlist.strings index e30c655ce3..334e8e97fe 100644 --- a/Loop/es.lproj/InfoPlist.strings +++ b/Loop/es.lproj/InfoPlist.strings @@ -1,9 +1,9 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "Loop"; - /* Bundle name */ "CFBundleName" = "$(PRODUCT_NAME)"; +/* Privacy - Bluetooth Always Usage Description */ +"NSBluetoothAlwaysUsageDescription" = "Bluetooth se utiliza para las comunicaciones con la microinfusora y los dispositivos de monitoreo continuo de glucosa."; + /* Privacy - Bluetooth Peripheral Usage Description */ "NSBluetoothPeripheralUsageDescription" = "Bluetooth se utiliza para las comunicaciones con la microinfusora y los dispositivos de monitoreo continuo de glucosa."; diff --git a/Loop/es.lproj/LaunchScreen.strings b/Loop/es.lproj/LaunchScreen.strings deleted file mode 100644 index 874e8a4532..0000000000 --- a/Loop/es.lproj/LaunchScreen.strings +++ /dev/null @@ -1 +0,0 @@ -/* No Localized Strings */ diff --git a/Loop/es.lproj/Localizable.strings b/Loop/es.lproj/Localizable.strings index f222b980b0..2360b0beaf 100644 --- a/Loop/es.lproj/Localizable.strings +++ b/Loop/es.lproj/Localizable.strings @@ -1,10 +1,7 @@ -/* Created with Localizable Strings Merge, the swiss-army knife for iOS and OS X localization. */ - /* The string format appended to active insulin that describes pending insulin. (1: pending insulin) */ -" (pending: %@)" = "(pendiente: %@)"; +" (pending: %@)" = " (pendiente: %@)"; -/* Format string for carb ratio average. (1: value)(2: carb unit) - Format string for insulin sensitivity average (1: value)(2: glucose unit) */ +/* Format string for carb ratio average. (1: value)(2: carb unit) */ "%1$@ %2$@/U" = "%1$@ %2$@/U"; /* Formats (1: carb start time) and (2: carb absorption duration) */ @@ -19,18 +16,15 @@ /* The format for recommended temp basal rate and time. (1: localized rate number)(2: localized time) */ "%1$@ U/hour @ %2$@" = "%1$@ U/hora @ %2$@"; -/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ -"%1$@ v%2$@" = "%1$@ v%2$@"; - /* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ "%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; -/* Format string describing glucose units per minute (1: glucose unit string) */ -"%1$@/min" = "%1$@/min"; - /* Formats (1: carb value) and (2: food type) */ "%1$@: %2$@" = "%1$@: %2$@"; +/* The format for an active override preset. (1: preset symbol)(2: preset name) */ +"%@ %@" = "%1$@ %2$@"; + /* Formats absorbed carb value */ "%@ absorbed" = "%@ absorbido"; @@ -67,15 +61,17 @@ /* The string format describing active insulin. (1: localized insulin value description) */ "Active Insulin: %@" = "Insulina Activa: %@"; +/* Action sheet title selecting CGM + Title text for button to set up a CGM */ +"Add CGM" = "Agregar CGM"; + /* The label of the carb entry button */ "Add Meal" = "Agregar Alimento"; -/* Title text for button to set up a new pump */ +/* Action sheet title selecting Pump +Title text for button to set up a new pump */ "Add Pump" = "Agregar Microinfusora"; -/* The title of the section containing algorithm settings */ -"Algorithm Settings" = "Ajustes de Algoritmo"; - /* The title of the Amplitude service */ "Amplitude" = "Amplitude"; @@ -88,6 +84,9 @@ /* The title of the nightscout API secret credential */ "API Secret" = "API secreto"; +/* Confirmation message for deleting a CGM */ +"Are you sure you want to delete this CGM?" = "¿Está usted seguro de querer eliminar este CGM?"; + /* Format fragment for a specific time */ "at %@" = "a %@"; @@ -99,15 +98,24 @@ /* The title of the basal rate profile screen The title text for the basal rate schedule */ -"Basal Rates" = "Perfil basal"; +"Basal Rates" = "Perfil Basal"; /* The label of the bolus entry button The notification title for a bolus failure */ "Bolus" = "Bolo"; +/* The format string for bolus progress. (1: delivered volume)(2: total volume) */ +"Bolused %1$@ of %2$@" = "Administró bolo %1$@ de %2$@"; + +/* The format string for bolus in progress showing total volume. (1: total volume) */ +"Bolusing %1$@" = "Administrando bolo de %1$@"; + /* The title of the cancel action in an action sheet */ "Cancel" = "Cancelar"; +/* The title of the cell indicating a bolus is being canceled */ +"Canceling Bolus" = "Cancelando bolo"; + /* Details for missing data error when carb effects are missing */ "Carb effects" = "Efectos de carbohidratos"; @@ -139,6 +147,9 @@ /* The title text for the looping enabled switch cell */ "Closed Loop" = "Asa cerrada"; +/* The title of the action used to dismiss an error alert */ +"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; + /* The title of the configuration section in settings */ "Configuration" = "Configuración"; @@ -155,14 +166,14 @@ /* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ "Current glucose of %1$@ is below correction range." = "Glucosa actual de %1$@ está por debajo del rango correctivo."; +/* The title of the cell indicating a generic temporary override is enabled */ +"Custom Override" = "Sobreescritura personalizada"; + /* The title of the Loggly customer token credential */ "Customer Token" = "Token de Usuario"; -/* The short unit display string for decibles */ -"dB" = "dB"; - -/* The title of the button to remove the credentials for a service */ -"Delete Account" = "Eliminar Cuenta"; +/* Button title to delete CGM */ +"Delete CGM" = "Eliminar CGM"; /* Title text for delivery limits */ "Delivery Limits" = "Límites de Dosificación"; @@ -170,17 +181,17 @@ /* The action hint of the workout mode toggle button when enabled */ "Disables" = "Desactivar"; -/* Title of the switch which toggles retrospective correction effects */ -"Enable Retrospective Correction" = "Habilitar corrección retrospectiva"; - /* The action hint of the workout mode toggle button when disabled */ "Enables" = "Permitir"; /* The placeholder text instructing users to enter a suspend treshold */ "Enter suspend threshold" = "Ingrese nivel de suspensión"; -/* The placeholder text instructing users how to enter a transmitter ID */ -"Enter the 6-digit transmitter ID" = "Ingrese ID de 6 dígitios de transmisor"; +/* The alert title for an error while canceling a bolus */ +"Error Canceling Bolus" = "Error de cancelación de bolo"; + +/* The alert title for a resume error */ +"Error Resuming" = "Error de reanudación"; /* The subtitle format describing eventual glucose. (1: localized glucose value description) */ "Eventually %@" = "Eventualmente %@"; @@ -194,15 +205,6 @@ /* The format string used to describe a finite workout targets duration */ "For %1$@" = "Por %1$@"; -/* The short unit display string for grams */ -"g" = "g"; - -/* The title text for the G4 Share Receiver switch cell */ -"G4 Share Receiver" = "Receptor G4 Share"; - -/* The title text for the G5/G6 Transmitter switch cell */ -"G5/G6 Transmitter" = "Transmisor G5/G6"; - /* The title of the glucose and prediction graph */ "Glucose" = "Glucosa"; @@ -262,27 +264,21 @@ /* The notification alert describing a long-lasting loop failure. The substitution parameter is the time interval since the last loop */ "Loop has not completed successfully in %@" = "Loop no ha terminado correctamente en %@"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; - /* The error message for missing data. (1: missing data details) */ "Missing data: %1$@" = "Faltan Datos: %1$@"; -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; - /* Details for missing data error when momentum effects are missing */ "Momentum effects" = "Efectos de Momento"; -/* Sensor state description for the non-valid state */ -"Needs Attention" = "Necesita Atención"; - /* The title of the Nightscout service */ "Nightscout" = "Nightscout"; /* The error message displayed for device connection errors. */ "No connected devices, or failure during device connection" = "No hay dispositivos conectados o falla durante conexión de dispositivo"; +/* The title text for the override presets */ +"Override Presets" = "Sobreescritura de objetivos preestablecidos"; + /* The label of the pre-meal mode toggle button */ "Pre-Meal Targets" = "Objetivos Pre-Comida"; @@ -292,6 +288,9 @@ /* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ "Predicted glucose of %1$@ is below your suspend threshold setting." = "Glucosa proyectada de %1$@ se encuentra por debajo de su nivel de suspensión."; +/* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ +"Predicted: %1$@\nActual: %2$@ (%3$@)" = "Predicción: %1$@\nActual: %2$@ (%3$@)"; + /* The title of the pump section in settings */ "Pump" = "Microinfusora"; @@ -310,6 +309,9 @@ /* The notification title for a low pump reservoir */ "Pump Reservoir Low" = "Reservorio de Microinfusora Bajo"; +/* The title of the cell indicating the pump is suspended */ +"Pump Suspended" = "Microinfusora Suspendida"; + /* Title of insulin model preset */ "Rapid-Acting – Adults" = "Acción Rápida — Adultos"; @@ -347,10 +349,10 @@ "since %@" = "desde %@"; /* The title of the nightscout site URL credential */ -"Site URL" = "URL de Sitio"; +"Site URL" = "Site URL"; -/* The title text for the Medtronic sensor switch cell */ -"Sof-Sensor / Enlite" = "Sof-Sensor / Enlite"; +/* The format for the description of a temporary override start date */ +"starting at %@" = "comenzando a la %@"; /* The title of the cell indicating a bolus is being sent */ "Starting Bolus" = "Comenzando Bolo"; @@ -358,21 +360,21 @@ /* The title text in settings */ "Suspend Threshold" = "Nivel de Suspensión"; +/* The subtitle of the cell displaying an action to resume insulin delivery */ +"Tap to Resume" = "Toque para reanudar"; + /* Subtitle description of Walsh insulin model setting */ "The legacy model used by Loop, allowing customization of action duration." = "El model utilizado por ediciones iniciales de Loop, permite ajustar duración de acción."; /* Body of the alert describing a maximum bolus validation error. (1: The localized max bolus value) */ "The maximum bolus amount is %@ Units" = "Bolo máximo es %@ Unidades"; -/* Instructions on where to find the transmitter ID */ -"The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app." = "El ID de transmisor puede encontrarse impreso en la parte trasera del dispositivo, a un lado de la caja en la que estaba empacado y dentro del menú de ajustes del receptor y la app móvil"; - -/* The description of the switch which toggles retrospective correction effects */ -"This will more aggresively increase or decrease basal delivery when glucose movement doesn't match the carbohydrate and insulin-based model." = "Esto aumentará o disminuirá de manera más agresiva la dosificación de la insulina basal cuando la dirección del nivel de la glucosa no concuerde con el modelo de la insulina y carbohidratos."; - /* The short unit display string for international units of insulin */ "U" = "U"; +/* The format for the description of a temporary override end date */ +"until %@" = "hasta la %@"; + /* The title of the alert controller used to select a duration for workout targets */ "Use Workout Glucose Targets" = "Utilice Objetivos de Glucosa de Ejercicio"; diff --git a/Loop/fi.lproj/InfoPlist.strings b/Loop/fi.lproj/InfoPlist.strings new file mode 100644 index 0000000000..a46e1e3731 --- /dev/null +++ b/Loop/fi.lproj/InfoPlist.strings @@ -0,0 +1,18 @@ +/* Bundle name */ +"CFBundleName" = "$(PRODUCT_NAME)"; + +/* Privacy - Bluetooth Always Usage Description */ +"NSBluetoothAlwaysUsageDescription" = "Bluetoothin avulla kommunikoidaan insuliinipumpun ja glukoosinseurantalaitteen kanssa."; + +/* Privacy - Bluetooth Peripheral Usage Description */ +"NSBluetoothPeripheralUsageDescription" = "Bluetoothin avulla kommunikoidaan insuliinipumpun ja glukoosinseurantalaitteen kanssa."; + +/* Privacy - Face ID Usage Description */ +"NSFaceIDUsageDescription" = "Face ID:tä käytetään vahvistamiseen annettaessa bolusta."; + +/* Privacy - Health Share Usage Description */ +"NSHealthShareUsageDescription" = "Terveys-appin ateriatietoja käytetään glukoosivaikutusten määrittämiseen. Terveys-appin glukoositietoja käytetään graafeissa ja laskelmissa."; + +/* Privacy - Health Update Usage Description */ +"NSHealthUpdateUsageDescription" = "Appin ja kellon kautta tallennetut hiilihydraattitiedot tallennetaan Terveys-appiin. Glukoosiseurannan kautta saadut glukoositiedot tallennetaan turvallisesti HealthKitiin."; + diff --git a/Loop/fi.lproj/Localizable.strings b/Loop/fi.lproj/Localizable.strings new file mode 100644 index 0000000000..3c574dc828 --- /dev/null +++ b/Loop/fi.lproj/Localizable.strings @@ -0,0 +1,395 @@ +/* The string format appended to active insulin that describes pending insulin. (1: pending insulin) */ +" (pending: %@)" = "(odottaa: %@)"; + +/* The format for an active override preset. (1: preset symbol)(2: preset name) */ +"%@ %@" = "%1$@ %2$@"; + +/* Formats absorbed carb value */ +"%@ absorbed" = "%@ imeytynyt"; + +/* The subtitle format describing total insulin. (1: localized insulin total) */ +"%@ U Total" = "%@ U yhteensä"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ +"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; + +/* Format string for carb ratio average. (1: value)(2: carb unit) */ +"%1$@ %2$@/U" = "%1$@ %2$@/U"; + +/* Formats (1: carb start time) and (2: carb absorption duration) */ +"%1$@ + %2$@" = "%1$@ + %2$@"; + +/* Low reservoir alert format string. (1: Number of units remaining) */ +"%1$@ U left" = "%1$@ U jäljellä"; + +/* Low reservoir alert with time remaining format string. (1: Number of units remaining)(2: approximate time remaining) */ +"%1$@ U left: %2$@" = "%1$@ U jäljellä: %2$@"; + +/* The format for recommended temp basal rate and time. (1: localized rate number)(2: localized time) */ +"%1$@ U/hour @ %2$@" = "%1$@ U/h klo %2$@"; + +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Formats (1: carb value) and (2: food type) */ +"%1$@: %2$@" = "%1$@: %2$@"; + +/* Description of the prediction input effect for glucose momentum */ +"15 min glucose regression coefficient (b₁), continued with decay over 30 min" = "15 min glukoosin regressiokerroin (b₁), häipyen 30 min kuluessa."; + +/* Description of the prediction input effect for retrospective correction */ +"30 min comparison of glucose prediction vs actual, continued with decay over 60 min" = "30 min vertailu ennustetun ja todellisen glukoosin välillä, häipyen 60 min kuluessa."; + +/* Subtitle of Fiasp preset */ +"A model based on the published absorption of Fiasp insulin." = "Malli perustuen julkaistuihin Fiasp-insuliinin imeytymisaikoihin."; + +/* Subtitle of Rapid-Acting – Adult preset */ +"A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults." = "Malli perustuen julkaistuihin Humalog-, Novorapid- ja Apidra-insuliinien imeytymisaikoihin aikuisilla."; + +/* Action to copy the recommended Bolus value to the actual Bolus Field */ +"AcceptRecommendedBolus" = "HyväksySuositeltuBolus"; + +/* The title of the Carbs On-Board graph */ +"Active Carbohydrates" = "Aktiivinen hiilihydraatti"; + +/* The string format describing active carbohydrates. (1: localized glucose value description) */ +"Active Carbohydrates: %@" = "Aktiivinen hiilihydraatti: %@"; + +/* The title of the Insulin On-Board graph */ +"Active Insulin" = "Aktiivinen insuliini"; + +/* The string format describing active insulin. (1: localized insulin value description) */ +"Active Insulin: %@" = "Aktiivinen insuliini: %@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Lisää hiilihydraatteja"; + +/* Action sheet title selecting CGM + Title text for button to set up a CGM */ +"Add CGM" = "Lisää CGM"; + +/* The label of the carb entry button */ +"Add Meal" = "Lisää ateria"; + +/* Action sheet title selecting Pump + Title text for button to set up a new pump */ +"Add Pump" = "Lisää pumppu"; + +/* The title of the Amplitude service */ +"Amplitude" = "Amplitude"; + +/* Subtitle of Rapid-Acting – Children preset */ +"An adjustment to the adult model based on empirical effects in children." = "Aikuisten mallista lapsille mukautettu empiirisiin vaikutuksiin perustuva malli."; + +/* The title of the amplitude API key credential */ +"API Key" = "API-avain"; + +/* The title of the nightscout API secret credential */ +"API Secret" = "API-salasana"; + +/* Confirmation message for deleting a CGM */ +"Are you sure you want to delete this CGM?" = "Haluatko varmasti poistaa CGM:n?"; + +/* Format fragment for a specific time */ +"at %@" = "klo %@"; + +/* The message displayed during a device authentication prompt for bolus specification */ +"Authenticate to Bolus %@ Units" = "Vahvista bolus %@ yksikköä"; + +/* Details for configuration error when basal rate schedule is missing */ +"Basal Rate Schedule" = "Basaaliohjelma"; + +/* The title of the basal rate profile screen + The title text for the basal rate schedule */ +"Basal Rates" = "Basaalitasot"; + +/* The label of the bolus entry button + The notification title for a bolus failure */ +"Bolus" = "Bolus"; + +/* The format string for bolus progress. (1: delivered volume)(2: total volume) */ +"Bolused %1$@ of %2$@" = "Bolus %1$@ / %2$@"; + +/* The format string for bolus in progress showing total volume. (1: total volume) */ +"Bolusing %1$@" = "Annetaan bolus %1$@"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Kumoa"; + +/* The title of the cell indicating a bolus is being canceled */ +"Canceling Bolus" = "Kumotaan bolus"; + +/* Details for missing data error when carb effects are missing */ +"Carb effects" = "Hiilihydraattivaikutus"; + +/* The title of the carb ratios schedule screen + The title text for the carb ratio schedule */ +"Carb Ratios" = "Hiilihydraattisuhteet"; + +/* Title of the prediction input effect for carbohydrates */ +"Carbohydrates" = "Hiilihydraatit"; + +/* Description of the prediction input effect for carbohydrates. (1: The glucose unit string) */ +"Carbs Absorbed (g) ÷ Carb Ratio (g/U) × Insulin Sensitivity (%1$@/U)" = "Imeytyneet hiilihydraatit (g) ÷ Hiilihydraattisuhde (g/U) × Insuliiniherkkyys (%1$@/U)"; + +/* The notification alert describing a low pump battery */ +"Change the pump battery immediately" = "Vaihda pumpun paristo välittömästi"; + +/* The notification alert describing an empty pump reservoir */ +"Change the pump reservoir now" = "Vaihda pumpun säiliö"; + +/* Details for configuration error when one or more loop settings are missing */ +"Check settings" = "Tarkista asetukset"; + +/* Recovery suggestion when reservoir data is missing */ +"Check that your pump is in range" = "Tarkista, että pumppu on riittävän lähellä"; + +/* Recovery suggestion when glucose data is missing */ +"Check your CGM data source" = "Tarkista CGM-datalähde"; + +/* The title text for the looping enabled switch cell */ +"Closed Loop" = "Closed Loop"; + +/* The title of the action used to dismiss an error alert */ +"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; + +/* The title of the configuration section in settings */ +"Configuration" = "Kokoonpano"; + +/* The error message displayed for configuration errors. (1: configuration error details) */ +"Configuration Error: %1$@" = "Kokoonpanovirhe: %1$@"; + +/* The title of the continuous glucose monitor section in settings */ +"Continuous Glucose Monitor" = "Glukoosinseuranta (CGM)"; + +/* The title of the glucose target range schedule screen + The title text for the glucose target range schedule */ +"Correction Range" = "Korjausalue"; + +/* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ +"Current glucose of %1$@ is below correction range." = "Nykyinen glukoosi %1$@ on korjausalueen alapuolella."; + +/* The title of the cell indicating a generic temporary override is enabled */ +"Custom Override" = "Tilapäisas"; + +/* The title of the Loggly customer token credential */ +"Customer Token" = "Asiakastunniste"; + +/* Button title to delete CGM */ +"Delete CGM" = "Poista CGM"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Annostelurajat"; + +/* The action hint of the workout mode toggle button when enabled */ +"Disables" = "Poistaa käytöstä"; + +/* The action hint of the workout mode toggle button when disabled */ +"Enables" = "Ottaa käyttöön"; + +/* The placeholder text instructing users to enter a suspend treshold */ +"Enter suspend threshold" = "Aseta pysäytysraja"; + +/* The alert title for an error while canceling a bolus */ +"Error Canceling Bolus" = "Virhe boluksen kumoamisessa"; + +/* The alert title for a resume error */ +"Error Resuming" = "Virhe jatkamisessa"; + +/* The subtitle format describing eventual glucose. (1: localized glucose value description) */ +"Eventually %@" = "Ennuste %@"; + +/* The title of the alert describing a maximum bolus validation error */ +"Exceeds Maximum Bolus" = "Ylittää maksimiboluksen"; + +/* Title of insulin model preset */ +"Fiasp" = "Fiasp"; + +/* The format string used to describe a finite workout targets duration */ +"For %1$@" = "%1$@ ajaksi"; + +/* The title of the glucose and prediction graph */ +"Glucose" = "Glukoosi"; + +/* The error message when glucose data is too old to be used. (1: glucose data age in minutes) */ +"Glucose data is %1$@ old" = "Glukoositieto on %1$@ vanha"; + +/* Description of error when glucose data is missing */ +"Glucose data not available" = "Glukoositietoja ei saatavilla"; + +/* Title of the prediction input effect for glucose momentum */ +"Glucose Momentum" = "Glukoosin liike"; + +/* The placeholder text for the nightscout site URL credential */ +"https://mysite.herokuapp.com" = "https://omaosoite.herokuapp.com"; + +/* The title of a target alert action specifying an indefinitely long workout targets duration */ +"Indefinitely" = "Toistaiseksi"; + +/* Title of the prediction input effect for insulin */ +"Insulin" = "Insuliini"; + +/* Description of the prediction input effect for insulin */ +"Insulin Absorbed (U) × Insulin Sensitivity (%1$@/U)" = "Imeytynyt insuliini (U) × Insuliiniherkkyys (%1$@/U)"; + +/* The title of the insulin delivery graph */ +"Insulin Delivery" = "Insuliinin annostelu"; + +/* Details for missing data error when insulin effects are missing */ +"Insulin effects" = "Insuliinivaikutus"; + +/* Details for configuration error when insulin model is missing + The title text for the insulin model setting row */ +"Insulin Model" = "Insuliinimalli"; + +/* The title of the insulin sensitivities schedule screen + The title text for the insulin sensitivity schedule */ +"Insulin Sensitivities" = "Insuliiniherkkyydet"; + +/* The error message when invalid data was encountered. (1: details of invalid data) */ +"Invalid data: %1$@" = "Virheellinen tieto: %1$@"; + +/* The title text for the issue report cell */ +"Issue Report" = "Ongelmaraportti"; + +/* Glucose HUD accessibility hint */ +"Launches CGM app" = "Avaa CGM-appin"; + +/* The loading message for the diagnostic report screen */ +"Loading..." = "Ladataan..."; + +/* The title of the loggly service */ +"Loggly" = "Loggly"; + +/* The notification title for a loop failure */ +"Loop Failure" = "Loopin häiriö"; + +/* The notification alert describing a long-lasting loop failure. The substitution parameter is the time interval since the last loop */ +"Loop has not completed successfully in %@" = "Häiriö Loopin toiminnassa %@"; + +/* The error message for missing data. (1: missing data details) */ +"Missing data: %1$@" = "Tiedot puuttuvat: %1$@"; + +/* Details for missing data error when momentum effects are missing */ +"Momentum effects" = "Liikevaikutukset (momentum)"; + +/* The title of the Nightscout service */ +"Nightscout" = "Nightscout"; + +/* The error message displayed for device connection errors. */ +"No connected devices, or failure during device connection" = "Ei yhdistettyjä laitteita tai häiriö laiteyhteydessä"; + +/* The title text for the override presets */ +"Override Presets" = "Override Presets"; + +/* The label of the pre-meal mode toggle button */ +"Pre-Meal Targets" = "Ennen ateriaa -tavoite"; + +/* Message when offering bolus recommendation even though bg is below range and minBG is in future. (1: glucose time)(2: glucose number) */ +"Predicted glucose at %1$@ is %2$@." = "Ennustettu glukoosi klo %1$@ on %2$@."; + +/* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ +"Predicted glucose of %1$@ is below your suspend threshold setting." = "Ennustettu glukoosi %1$@ on pysäytysrajan alapuolella."; + +/* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ +"Predicted: %1$@\nActual: %2$@ (%3$@)" = "Ennustettu: %1$@\nTodellinen: %2$@ (%3$@)"; + +/* The title of the pump section in settings */ +"Pump" = "Pumppu"; + +/* The notification title for a low pump battery */ +"Pump Battery Low" = "Pumpun paristo vähissä"; + +/* The error message when pump data is too old to be used. (1: pump data age in minutes) */ +"Pump data is %1$@ old" = "Pumpputieto on %1$@ vanha"; + +/* Details for configuration error when pump manager is missing */ +"Pump Manager" = "Pumpun hallinta"; + +/* The notification title for an empty pump reservoir */ +"Pump Reservoir Empty" = "Pumpun säiliö tyhjä"; + +/* The notification title for a low pump reservoir */ +"Pump Reservoir Low" = "Pumpun säiliö vähissä"; + +/* The title of the cell indicating the pump is suspended */ +"Pump Suspended" = "Pumppu pysäytetty"; + +/* Title of insulin model preset */ +"Rapid-Acting – Adults" = "Nopeavaikutteinen – aikuiset"; + +/* Title of insulin model preset */ +"Rapid-Acting – Children" = "Nopeavaikutteinen – lapset"; + +/* The error message when a recommendation has expired. (1: age of recommendation in minutes) */ +"Recommendation expired: %1$@ old" = "Suositus vanhentunut: %1$@ vanha"; + +/* The title of the cell displaying a recommended temp basal value */ +"Recommended Basal" = "Suositeltu basaali"; + +/* Accessibility hint describing recommended bolus units */ +"Recommended Bolus: %@ Units" = "Suositeltu bolus: %@ yksikköä"; + +/* Details for missing data error when reservoir data is missing */ +"Reservoir" = "Säiliö"; + +/* Title of the prediction input effect for retrospective correction */ +"Retrospective Correction" = "Retrospektiivinen korjaus"; + +/* The title of the notification action to retry a bolus command */ +"Retry" = "Uudelleen"; + +/* The title of the services section in settings */ +"Services" = "Palvelut"; + +/* The label of the settings button */ +"Settings" = "Asetukset"; + +/* Loop Completion HUD accessibility hint */ +"Shows last loop error" = "Näyttää Loopin viimeisimmän virheen"; + +/* Format fragment for a start time */ +"since %@" = "%@ jälkeen"; + +/* The title of the nightscout site URL credential */ +"Site URL" = "Sivuston URL"; + +/* The format for the description of a temporary override start date */ +"starting at %@" = "alkaa %@"; + +/* The title of the cell indicating a bolus is being sent */ +"Starting Bolus" = "Aloitetaan bolus"; + +/* The title text in settings */ +"Suspend Threshold" = "Pysäytysraja"; + +/* The subtitle of the cell displaying an action to resume insulin delivery */ +"Tap to Resume" = "Jatka annostelua"; + +/* Subtitle description of Walsh insulin model setting */ +"The legacy model used by Loop, allowing customization of action duration." = "Loopin aikaisemmin käyttämä insuliinimalli, joka mahdollistaa insuliinin vaikutusajan muokkauksen."; + +/* Body of the alert describing a maximum bolus validation error. (1: The localized max bolus value) */ +"The maximum bolus amount is %@ Units" = "Suurin sallittu bolus on %@ yksikköä"; + +/* The short unit display string for international units of insulin */ +"U" = "U"; + +/* The format for the description of a temporary override end date */ +"until %@" = "%@ asti"; + +/* The title of the alert controller used to select a duration for workout targets */ +"Use Workout Glucose Targets" = "Käytä liikuntatilan glukoositavoitetteita"; + +/* Title of insulin model setting */ +"Walsh" = "Walsh"; + +/* Explanation of suspend threshold */ +"When current or forecasted glucose is below the suspend threshold, Loop will not recommend a bolus, and will always recommend a temporary basal rate of 0 units per hour." = "Kun nykyinen tai ennustettu glukoosi on pysäytysrajan alapuolella, Loop ei suosittele bolusta ja suosittelee aina tilapäiseksi basaaliksi 0 yksikköä tunnissa."; + +/* The label of the workout mode toggle button */ +"Workout Targets" = "Liikuntatavoitteet"; + diff --git a/Loop/fi.lproj/Main.strings b/Loop/fi.lproj/Main.strings new file mode 100644 index 0000000000..59ba2fb749 --- /dev/null +++ b/Loop/fi.lproj/Main.strings @@ -0,0 +1,144 @@ +/* Class = "UINavigationItem"; title = "Status"; ObjectID = "3kU-n2-fha"; */ +"3kU-n2-fha.title" = "Tila"; + +/* Class = "UILabel"; text = "3.5 U/hour @ 12:12 PM"; ObjectID = "5gz-kZ-iF1"; */ +"5gz-kZ-iF1.text" = "3.5 U/h @ 12:12 PM"; + +/* Class = "UILabel"; text = "Bolus"; ObjectID = "5oA-6d-ZTL"; */ +"5oA-6d-ZTL.text" = "Bolus"; + +/* Class = "UILabel"; text = "Pump ID"; ObjectID = "5TX-kX-nBo"; */ +"5TX-kX-nBo.text" = "Pumpun tunniste"; + +/* Class = "UITextField"; accessibilityLabel = "Bolus Amount"; ObjectID = "7LT-50-ZzK"; */ +"7LT-50-ZzK.accessibilityLabel" = "Bolusmäärä"; + +/* Class = "UITextField"; placeholder = "0.0"; ObjectID = "7LT-50-ZzK"; */ +"7LT-50-ZzK.placeholder" = "0.0"; + +/* Class = "UILabel"; text = "Predicted"; ObjectID = "87H-N1-0vJ"; */ +"87H-N1-0vJ.text" = "Ennustettu"; + +/* Class = "UILabel"; text = "Detail"; ObjectID = "aCb-Qs-bpu"; */ +"aCb-Qs-bpu.text" = "Yksityiskohta"; + +/* Class = "UINavigationItem"; title = "Bolus"; ObjectID = "aiu-ZA-zVa"; */ +"aiu-ZA-zVa.title" = "Bolus"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "bIL-Ub-qYp"; */ +"bIL-Ub-qYp.text" = "Nimiö"; + +/* Class = "UILabel"; text = "Glucose Change"; ObjectID = "bq4-98-cQU"; */ +"bq4-98-cQU.text" = "Glukoosin muutos"; + +/* Class = "UILabel"; accessibilityLabel = "Units"; ObjectID = "BR0-dr-Fj2"; */ +"BR0-dr-Fj2.accessibilityLabel" = "Yksikköä"; + +/* Class = "UILabel"; text = "U"; ObjectID = "BR0-dr-Fj2"; */ +"BR0-dr-Fj2.text" = "U"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "cpo-Po-gFM"; */ +"cpo-Po-gFM.text" = "Nimiö"; + +/* Class = "UILabel"; text = "g Total"; ObjectID = "d3X-AN-tA5"; */ +"d3X-AN-tA5.text" = "g yhteensä"; + +/* Class = "UILabel"; text = "Future glucose is predicted by combining the effects of multiple inputs. Use this tool to toggle various inputs to see how they compare to the final prediction."; ObjectID = "D4C-I2-dhA"; */ +"D4C-I2-dhA.text" = "Tulevia glukoosiarvoja ennustetaan yhdistämällä useita eri tekijöiden vaikutuksia. Tämän työkalun avulla voit havainnoida, kuinka ne vaikuttavat lopulliseen ennusteeseen laittamalla eri tekijöitä päälle/pois."; + +/* Class = "UILabel"; text = "Label"; ObjectID = "d6m-qV-wWi"; */ +"d6m-qV-wWi.text" = "Nimiö"; + +/* Class = "UINavigationItem"; title = "Settings"; ObjectID = "dmB-PQ-B44"; */ +"dmB-PQ-B44.title" = "Asetukset"; + +/* Class = "UILabel"; text = "DEVICES"; ObjectID = "DyC-Sv-qP8"; */ +"DyC-Sv-qP8.text" = "LAITTEET"; + +/* Class = "UILabel"; text = "eventually 92 mg/dL"; ObjectID = "E41-FN-nkk"; */ +"E41-FN-nkk.text" = "ennuste 92 mg/dL"; + +/* Class = "UILabel"; text = "Observed"; ObjectID = "EAn-Ja-S1d"; */ +"EAn-Ja-S1d.text" = "Havaittu"; + +/* Class = "UILabel"; text = "Active Carbohydrates: 40g"; ObjectID = "hHZ-uY-aKw"; */ +"hHZ-uY-aKw.text" = "Aktiivinen hiilihydraatti: 40g"; + +/* Class = "UITableViewController"; title = "Carbohydrate Effects"; ObjectID = "hZZ-2S-lrd"; */ +"hZZ-2S-lrd.title" = "Hiilihydraattivaikutus"; + +/* Class = "UILabel"; text = "Observed changes in glucose, subtracting changes modeled from insulin delivery, can be used to estimate carbohydrate absorption."; ObjectID = "IxU-As-glo"; */ +"IxU-As-glo.text" = "Havaittuja glukoosin muutoksia, vähennettynä insuliinin annostelusta mallinnetuilla muutoksilla, voidaan käyttää hiilihydraattien imeytymisen arvioinnissa."; + +/* Class = "UILabel"; text = "Detail"; ObjectID = "J7x-W5-gwo"; */ +"J7x-W5-gwo.text" = "Yksityiskohta"; + +/* Class = "UILabel"; text = "⚠ Glucose Predicted Below Range"; ObjectID = "k0g-P7-OVN"; */ +"k0g-P7-OVN.text" = "⚠ Ennustettu glukoosi alueen alapuolella"; + +/* Class = "UILabel"; text = "Recommended Basal"; ObjectID = "k3F-Na-7mn"; */ +"k3F-Na-7mn.text" = "Suositeltu basaali"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "Krd-Aa-ret"; */ +"Krd-Aa-ret.text" = "Nimiö"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "l7l-mw-Oc9"; */ +"l7l-mw-Oc9.text" = "Nimiö"; + +/* Class = "UILabel"; text = "Tap to set"; ObjectID = "m9c-SQ-djE"; */ +"m9c-SQ-djE.text" = "Napauta asettaaksesi"; + +/* Class = "UILabel"; accessibilityLabel = "Units"; ObjectID = "mVz-dr-xLU"; */ +"mVz-dr-xLU.accessibilityLabel" = "Yksikköä"; + +/* Class = "UILabel"; text = "U"; ObjectID = "mVz-dr-xLU"; */ +"mVz-dr-xLU.text" = "U"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "OFA-qT-ZAg"; */ +"OFA-qT-ZAg.text" = "Nimiö"; + +/* Class = "UITableViewController"; title = "Predicted Glucose"; ObjectID = "PA3-sP-cWY"; */ +"PA3-sP-cWY.title" = "Ennustettu glukoosi"; + +/* Class = "UITableViewController"; title = "Insulin Model"; ObjectID = "pi6-Dh-72V"; */ +"pi6-Dh-72V.title" = "Insuliinimalli"; + +/* Class = "UILabel"; text = "An insulin activity model is used to estimate effects of insulin on glucose levels. An accurate model can help prevent insulin stacking and safely recommend corrective treatments."; ObjectID = "PJv-p9-cFe"; */ +"PJv-p9-cFe.text" = "Insuliiniaktiivisuusmallia käytetään arvioimaan insuliinin vaikutuksia glukoositasoon. Tarkka malli voi auttaa estämään liian suuren insuliinimäärän kertymistä kehoon ja suosittelemaan turvallisia glukoosia korjaavia hoitotoimenpiteitä."; + +/* Class = "UILabel"; text = "eventually 92 mg/dL"; ObjectID = "Rse-x8-amW"; */ +"Rse-x8-amW.text" = "ennuste 92 mg/dL"; + +/* Class = "UILabel"; text = "g COB"; ObjectID = "SQx-au-ZcM"; */ +"SQx-au-ZcM.text" = "g COB"; + +/* Class = "UILabel"; text = "Glucose"; ObjectID = "tuw-av-A3x"; */ +"tuw-av-A3x.text" = "Glukoosi"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "ufi-Kj-33k"; */ +"ufi-Kj-33k.text" = "Nimiö"; + +/* Class = "UILabel"; text = "Active Insulin: 1.5U"; ObjectID = "viU-7a-kbI"; */ +"viU-7a-kbI.text" = "Aktiivinen insuliini: 1.5U"; + +/* Class = "UINavigationItem"; title = "Carbohydrates"; ObjectID = "Vpi-5b-bY5"; */ +"Vpi-5b-bY5.title" = "Hiilihydraatit"; + +/* Class = "UITextField"; text = "4 hour"; ObjectID = "Wk3-xv-IM5"; */ +"Wk3-xv-IM5.text" = "4 tuntia"; + +/* Class = "UIButton"; normalTitle = "Deliver"; ObjectID = "Ya0-9b-ZAS"; */ +"Ya0-9b-ZAS.normalTitle" = "Annostele"; + +/* Class = "UILabel"; text = "0"; ObjectID = "yn7-2M-jZz"; */ +"yn7-2M-jZz.text" = "0"; + +/* Class = "UILabel"; text = "Recommended"; ObjectID = "ywT-OR-NnU"; */ +"ywT-OR-NnU.text" = "Suositus"; + +/* Class = "UILabel"; text = "Title"; ObjectID = "zbc-87-wxZ"; */ +"zbc-87-wxZ.text" = "Otsikko"; + +/* Class = "UILabel"; text = "0"; ObjectID = "zvZ-uf-zMX"; */ +"zvZ-uf-zMX.text" = "0"; + diff --git a/Loop/fr.lproj/InfoPlist.strings b/Loop/fr.lproj/InfoPlist.strings index 796f9060cb..de9a1ce22a 100644 --- a/Loop/fr.lproj/InfoPlist.strings +++ b/Loop/fr.lproj/InfoPlist.strings @@ -1,9 +1,9 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "Loop"; - /* Bundle name */ "CFBundleName" = "$(PRODUCT_NAME)"; +/* Privacy - Bluetooth Always Usage Description */ +"NSBluetoothAlwaysUsageDescription" = "Bluetooth is used to communicate with insulin pump and continuous glucose monitor devices."; + /* Privacy - Bluetooth Peripheral Usage Description */ "NSBluetoothPeripheralUsageDescription" = "Bluetooth est utilisé pour communiquer avec la pompe à insuline et les dispositifs de surveillance continue du glucose."; diff --git a/Loop/fr.lproj/LaunchScreen.strings b/Loop/fr.lproj/LaunchScreen.strings deleted file mode 100644 index 8b13789179..0000000000 --- a/Loop/fr.lproj/LaunchScreen.strings +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Loop/fr.lproj/Localizable.strings b/Loop/fr.lproj/Localizable.strings index dedca621a7..30a425a567 100644 --- a/Loop/fr.lproj/Localizable.strings +++ b/Loop/fr.lproj/Localizable.strings @@ -1,10 +1,22 @@ -/* Created with Localizable Strings Merge, the swiss-army knife for iOS and OS X localization. */ - /* The string format appended to active insulin that describes pending insulin. (1: pending insulin) */ -" (pending: %@)" = "(en suspens: %@)"; +" (pending: %@)" = " (en suspens: %@)"; + +/* The format for an active override preset. (1: preset symbol)(2: preset name) */ +"%@ %@" = "%1$@ %2$@"; + +/* Formats absorbed carb value */ +"%@ absorbed" = "%@ absorbé"; + +/* The subtitle format describing total insulin. (1: localized insulin total) */ +"%@ U Total" = "%@ U Totaux"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ +"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; -/* Format string for carb ratio average. (1: value)(2: carb unit) - Format string for insulin sensitivity average (1: value)(2: glucose unit) */ +/* Format string for carb ratio average. (1: value)(2: carb unit) */ "%1$@ %2$@/U" = "%1$@ %2$@/U"; /* Formats (1: carb start time) and (2: carb absorption duration) */ @@ -19,27 +31,9 @@ /* The format for recommended temp basal rate and time. (1: localized rate number)(2: localized time) */ "%1$@ U/hour @ %2$@" = "%1$@ U/heure @ %2$@"; -/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ -"%1$@ v%2$@" = "%1$@ v%2$@"; - -/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ -"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; - -/* Format string describing glucose units per minute (1: glucose unit string) */ -"%1$@/min" = "%1$@/min"; - /* Formats (1: carb value) and (2: food type) */ "%1$@: %2$@" = "%1$@: %2$@"; -/* Formats absorbed carb value */ -"%@ absorbed" = "%@ absorbé"; - -/* The subtitle format describing total insulin. (1: localized insulin total) */ -"%@ U Total" = "%@ U Totaux"; - -/* Appends a full-stop to a statement */ -"%@." = "%@."; - /* Description of the prediction input effect for glucose momentum */ "15 min glucose regression coefficient (b₁), continued with decay over 30 min" = "Coefficient de régression du glucose de 15 minutes (b1), désintégration poursuivi au delà de 30 min."; @@ -67,15 +61,20 @@ /* The string format describing active insulin. (1: localized insulin value description) */ "Active Insulin: %@" = "Insuline active: %@"; +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Ajouter des glucides"; + +/* Action sheet title selecting CGM + Title text for button to set up a CGM */ +"Add CGM" = "Ajouter CGM"; + /* The label of the carb entry button */ "Add Meal" = "Entrer un repas"; -/* Title text for button to set up a new pump */ +/* Action sheet title selecting Pump + Title text for button to set up a new pump */ "Add Pump" = "Ajouter une pompe"; -/* The title of the section containing algorithm settings */ -"Algorithm Settings" = "Paramètres de l'algorithme"; - /* The title of the Amplitude service */ "Amplitude" = "Amplitude"; @@ -88,6 +87,9 @@ /* The title of the nightscout API secret credential */ "API Secret" = "API Secret"; +/* Confirmation message for deleting a CGM */ +"Are you sure you want to delete this CGM?" = "Voulez-vous vraiment supprimer ce CGM ?"; + /* Format fragment for a specific time */ "at %@" = "at %@"; @@ -105,9 +107,18 @@ The notification title for a bolus failure */ "Bolus" = "Bolus"; +/* The format string for bolus progress. (1: delivered volume)(2: total volume) */ +"Bolused %1$@ of %2$@" = "Bolus délivré %1$@ sur %2$@"; + +/* The format string for bolus in progress showing total volume. (1: total volume) */ +"Bolusing %1$@" = "Bolus en cours %1$@"; + /* The title of the cancel action in an action sheet */ "Cancel" = "Annuler"; +/* The title of the cell indicating a bolus is being canceled */ +"Canceling Bolus" = "Annulation du Bolus en cours"; + /* Details for missing data error when carb effects are missing */ "Carb effects" = "Effets des glucides"; @@ -139,6 +150,9 @@ /* The title text for the looping enabled switch cell */ "Closed Loop" = "Loop Fermé"; +/* The title of the action used to dismiss an error alert */ +"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; + /* The title of the configuration section in settings */ "Configuration" = "Configuration"; @@ -155,14 +169,14 @@ /* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ "Current glucose of %1$@ is below correction range." = "Glycémie actuelle de %1$@ est en dessous de la plage."; +/* The title of the cell indicating a generic temporary override is enabled */ +"Custom Override" = "Surcharge personnalisée"; + /* The title of the Loggly customer token credential */ "Customer Token" = "Token client"; -/* The short unit display string for decibles */ -"dB" = "dB"; - -/* The title of the button to remove the credentials for a service */ -"Delete Account" = "Supprimer le compte"; +/* Button title to delete CGM */ +"Delete CGM" = "Supprimer CGM"; /* Title text for delivery limits */ "Delivery Limits" = "Limites de Administration"; @@ -170,17 +184,17 @@ /* The action hint of the workout mode toggle button when enabled */ "Disables" = "Désactive"; -/* Title of the switch which toggles retrospective correction effects */ -"Enable Retrospective Correction" = "Activer Correction Retrospective"; - /* The action hint of the workout mode toggle button when disabled */ "Enables" = "Active"; /* The placeholder text instructing users to enter a suspend treshold */ "Enter suspend threshold" = "Entrez le seuil de suspension"; -/* The placeholder text instructing users how to enter a transmitter ID */ -"Enter the 6-digit transmitter ID" = "Entrez le numéro d'identification (ID) du transmetteur à 6 chiffres"; +/* The alert title for an error while canceling a bolus */ +"Error Canceling Bolus" = "Erreur lors de l’annulation du Bolus"; + +/* The alert title for a resume error */ +"Error Resuming" = "Erreur lors de la reprise"; /* The subtitle format describing eventual glucose. (1: localized glucose value description) */ "Eventually %@" = "Éventuellement %@"; @@ -194,15 +208,6 @@ /* The format string used to describe a finite workout targets duration */ "For %1$@" = "Pour %1$@"; -/* The short unit display string for grams */ -"g" = "g"; - -/* The title text for the G4 Share Receiver switch cell */ -"G4 Share Receiver" = "Récepteur G4 Share"; - -/* The title text for the G5/G6 Transmitter switch cell */ -"G5/G6 Transmitter" = "Transmetteur G5/G6"; - /* The title of the glucose and prediction graph */ "Glucose" = "Glycémie"; @@ -262,27 +267,21 @@ /* The notification alert describing a long-lasting loop failure. The substitution parameter is the time interval since the last loop */ "Loop has not completed successfully in %@" = "Loop ne s'est pas complété avec succès depuis %@"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; - /* The error message for missing data. (1: missing data details) */ "Missing data: %1$@" = "Données manquantes: %1$@"; -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; - /* Details for missing data error when momentum effects are missing */ "Momentum effects" = "Effets de momentum"; -/* Sensor state description for the non-valid state */ -"Needs Attention" = "Attention Requise"; - /* The title of the Nightscout service */ "Nightscout" = "Nightscout"; /* The error message displayed for device connection errors. */ "No connected devices, or failure during device connection" = "Pas d'appareil connecté, ou échec durant la connectionà l'appareil"; +/* The title text for the override presets */ +"Override Presets" = "Préréglages de surcharges"; + /* The label of the pre-meal mode toggle button */ "Pre-Meal Targets" = "Objectif de Pré-Repas"; @@ -292,6 +291,9 @@ /* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ "Predicted glucose of %1$@ is below your suspend threshold setting." = "Prédiction de la glycémie à %1$@ sous le seuil de suspension défini."; +/* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ +"Predicted: %1$@\nActual: %2$@ (%3$@)" = "Prédit: %1$@\nActuel: %2$@ (%3$@)"; + /* The title of the pump section in settings */ "Pump" = "Pompe"; @@ -310,6 +312,9 @@ /* The notification title for a low pump reservoir */ "Pump Reservoir Low" = "Niveau du réservoir de la pompe bas"; +/* The title of the cell indicating the pump is suspended */ +"Pump Suspended" = "Pompe suspendue"; + /* Title of insulin model preset */ "Rapid-Acting – Adults" = "Action rapide - Adulte"; @@ -349,8 +354,8 @@ /* The title of the nightscout site URL credential */ "Site URL" = "URL du site"; -/* The title text for the Medtronic sensor switch cell */ -"Sof-Sensor / Enlite" = "Sof-Sensor / Enlite"; +/* The format for the description of a temporary override start date */ +"starting at %@" = "commence à %@"; /* The title of the cell indicating a bolus is being sent */ "Starting Bolus" = "Commencer un bolus"; @@ -358,21 +363,21 @@ /* The title text in settings */ "Suspend Threshold" = "Seuil de suspension"; +/* The subtitle of the cell displaying an action to resume insulin delivery */ +"Tap to Resume" = "Taper pour reprendre"; + /* Subtitle description of Walsh insulin model setting */ "The legacy model used by Loop, allowing customization of action duration." = "Le modèle hérité utilisé par Loop, permettant de personnaliser la durée de l’action."; /* Body of the alert describing a maximum bolus validation error. (1: The localized max bolus value) */ "The maximum bolus amount is %@ Units" = "Le bolus maximal est de %@ unités"; -/* Instructions on where to find the transmitter ID */ -"The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app." = "L'identifiant de l'émetteur se trouve à l'arrière de l'appareil, sur le côté de la boîte, et dans les menus de réglage du récepteur et de l'application mobile."; - -/* The description of the switch which toggles retrospective correction effects */ -"This will more aggresively increase or decrease basal delivery when glucose movement doesn't match the carbohydrate and insulin-based model." = "Cela augmentera ou diminuera plus agressivement l'accouchement basal lorsque le mouvement du glucose ne correspond pas au modèle à base de glucides et d'insuline."; - /* The short unit display string for international units of insulin */ "U" = "U"; +/* The format for the description of a temporary override end date */ +"until %@" = "jusqu’à %@"; + /* The title of the alert controller used to select a duration for workout targets */ "Use Workout Glucose Targets" = "Utiliser les objectifs d'entraînement"; diff --git a/Loop/fr.lproj/Main.strings b/Loop/fr.lproj/Main.strings index 0614a69d95..158cebad29 100644 --- a/Loop/fr.lproj/Main.strings +++ b/Loop/fr.lproj/Main.strings @@ -25,9 +25,6 @@ /* Class = "UINavigationItem"; title = "Bolus"; ObjectID = "aiu-ZA-zVa"; */ "aiu-ZA-zVa.title" = "Bolus"; -/* Class = "UILabel"; text = "Title"; ObjectID = "B9F-K3-7RI"; */ -"B9F-K3-7RI.text" = "Titre"; - /* Class = "UILabel"; text = "Label"; ObjectID = "bIL-Ub-qYp"; */ "bIL-Ub-qYp.text" = "Étiquette"; diff --git a/Loop/it.lproj/InfoPlist.strings b/Loop/it.lproj/InfoPlist.strings index eebd32a20a..632467a645 100644 --- a/Loop/it.lproj/InfoPlist.strings +++ b/Loop/it.lproj/InfoPlist.strings @@ -1,9 +1,9 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "Loop"; - /* Bundle name */ "CFBundleName" = "$(PRODUCT_NAME)"; +/* Privacy - Bluetooth Always Usage Description */ +"NSBluetoothAlwaysUsageDescription" = "Il Bluetooth viene utilizzato per comunicare con il microinfusore e i dispositivi CGM."; + /* Privacy - Bluetooth Peripheral Usage Description */ "NSBluetoothPeripheralUsageDescription" = "Il Bluetooth viene utilizzato per comunicare con il microinfusore e i dispositivi CGM."; diff --git a/Loop/it.lproj/LaunchScreen.strings b/Loop/it.lproj/LaunchScreen.strings deleted file mode 100644 index 8b13789179..0000000000 --- a/Loop/it.lproj/LaunchScreen.strings +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Loop/it.lproj/Localizable.strings b/Loop/it.lproj/Localizable.strings index d9274a6759..af798f2997 100644 --- a/Loop/it.lproj/Localizable.strings +++ b/Loop/it.lproj/Localizable.strings @@ -1,10 +1,22 @@ -/* Created with Localizable Strings Merge, the swiss-army knife for iOS and OS X localization. */ - /* The string format appended to active insulin that describes pending insulin. (1: pending insulin) */ " (pending: %@)" = " (prevista l'erogazione di: %@)"; -/* Format string for carb ratio average. (1: value)(2: carb unit) - Format string for insulin sensitivity average (1: value)(2: glucose unit) */ +/* The format for an active override preset. (1: preset symbol)(2: preset name) */ +"%@ %@" = "%1$@ %2$@"; + +/* Formats absorbed carb value */ +"%@ absorbed" = "%@ assorbiti"; + +/* The subtitle format describing total insulin. (1: localized insulin total) */ +"%@ U Total" = "%@ U Totali"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ +"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; + +/* Format string for carb ratio average. (1: value)(2: carb unit) */ "%1$@ %2$@/U" = "%1$@ %2$@/U"; /* Formats (1: carb start time) and (2: carb absorption duration) */ @@ -22,24 +34,9 @@ /* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ "%1$@ v%2$@" = "%1$@ v%2$@"; -/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ -"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; - -/* Format string describing glucose units per minute (1: glucose unit string) */ -"%1$@/min" = "%1$@/min"; - /* Formats (1: carb value) and (2: food type) */ "%1$@: %2$@" = "%1$@: %2$@"; -/* Formats absorbed carb value */ -"%@ absorbed" = "%@ assorbiti"; - -/* The subtitle format describing total insulin. (1: localized insulin total) */ -"%@ U Total" = "%@ U Totali"; - -/* Appends a full-stop to a statement */ -"%@." = "%@."; - /* Description of the prediction input effect for glucose momentum */ "15 min glucose regression coefficient (b₁), continued with decay over 30 min" = "Coefficiente di regressione del glucosio a 15 min (b₁), interpolato con il decadimento a 30 min."; @@ -67,15 +64,20 @@ /* The string format describing active insulin. (1: localized insulin value description) */ "Active Insulin: %@" = "Insulina Attiva: %@"; +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Aggiungi carboidrati assunti"; + +/* Action sheet title selecting CGM + Title text for button to set up a CGM */ +"Add CGM" = "Aggiungi CGM"; + /* The label of the carb entry button */ "Add Meal" = "Aggiungi Pasto"; -/* Title text for button to set up a new pump */ +/* Action sheet title selecting Pump + Title text for button to set up a new pump */ "Add Pump" = "Aggiungi Microinfusore"; -/* The title of the section containing algorithm settings */ -"Algorithm Settings" = "Impostazioni Algoritmo"; - /* The title of the Amplitude service */ "Amplitude" = "Amplitude"; @@ -88,6 +90,9 @@ /* The title of the nightscout API secret credential */ "API Secret" = "API Secret"; +/* Confirmation message for deleting a CGM */ +"Are you sure you want to delete this CGM?" = "Sei sicuro di voler eliminare questo CGM?"; + /* Format fragment for a specific time */ "at %@" = "a %@"; @@ -105,9 +110,18 @@ The notification title for a bolus failure */ "Bolus" = "Bolo"; +/* The format string for bolus progress. (1: delivered volume)(2: total volume) */ +"Bolused %1$@ of %2$@" = "%1$@ somministrato in bolo su %2$@"; + +/* The format string for bolus in progress showing total volume. (1: total volume) */ +"Bolusing %1$@" = "Somministrazione in bolo di %1$@ in corso"; + /* The title of the cancel action in an action sheet */ "Cancel" = "Cancella"; +/* The title of the cell indicating a bolus is being canceled */ +"Canceling Bolus" = "Annullamento bolo in corso"; + /* Details for missing data error when carb effects are missing */ "Carb effects" = "Effetto dei carboidrati"; @@ -139,6 +153,9 @@ /* The title text for the looping enabled switch cell */ "Closed Loop" = "Loop Chiuso"; +/* The title of the action used to dismiss an error alert */ +"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; + /* The title of the configuration section in settings */ "Configuration" = "Configurazione"; @@ -155,14 +172,14 @@ /* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ "Current glucose of %1$@ is below correction range." = "L'attuale valore glicemico di %1$@ e' inferiore al valore inferiore del target glicemico."; +/* The title of the cell indicating a generic temporary override is enabled */ +"Custom Override" = "Regolazione manuale personalizzata"; + /* The title of the Loggly customer token credential */ "Customer Token" = "Token dell'utente"; -/* The short unit display string for decibles */ -"dB" = "dB"; - -/* The title of the button to remove the credentials for a service */ -"Delete Account" = "Cancella Account"; +/* Button title to delete CGM */ +"Delete CGM" = "Elimina CGM"; /* Title text for delivery limits */ "Delivery Limits" = "Limiti Erogazione"; @@ -170,17 +187,17 @@ /* The action hint of the workout mode toggle button when enabled */ "Disables" = "Disabilitato"; -/* Title of the switch which toggles retrospective correction effects */ -"Enable Retrospective Correction" = "Abilita Correzione Retrospettiva"; - /* The action hint of the workout mode toggle button when disabled */ "Enables" = "Abilita"; /* The placeholder text instructing users to enter a suspend treshold */ "Enter suspend threshold" = "Inserisci il valore minimo sotto al quale l'erogazione e' sospesa"; -/* The placeholder text instructing users how to enter a transmitter ID */ -"Enter the 6-digit transmitter ID" = "Inserire l'ID del trasmettitore composto da 6 caratteri"; +/* The alert title for an error while canceling a bolus */ +"Error Canceling Bolus" = "Errore durante l’annullamento del bolo"; + +/* The alert title for a resume error */ +"Error Resuming" = "Errore durante la ripresa"; /* The subtitle format describing eventual glucose. (1: localized glucose value description) */ "Eventually %@" = "Probabile Glic. %@"; @@ -194,15 +211,6 @@ /* The format string used to describe a finite workout targets duration */ "For %1$@" = "Per %1$@"; -/* The short unit display string for grams */ -"g" = "g"; - -/* The title text for the G4 Share Receiver switch cell */ -"G4 Share Receiver" = "G4 Ricevitore Share"; - -/* The title text for the G5/G6 Transmitter switch cell */ -"G5/G6 Transmitter" = "G5/G6 ID Trasmettitore"; - /* The title of the glucose and prediction graph */ "Glucose" = "Glicemie"; @@ -262,27 +270,21 @@ /* The notification alert describing a long-lasting loop failure. The substitution parameter is the time interval since the last loop */ "Loop has not completed successfully in %@" = "Loop non completato con successo da %@"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; - /* The error message for missing data. (1: missing data details) */ "Missing data: %1$@" = "Dati mancanti: %1$@"; -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; - /* Details for missing data error when momentum effects are missing */ "Momentum effects" = "Effetto glicemico attuale"; -/* Sensor state description for the non-valid state */ -"Needs Attention" = "Fai Attenzione"; - /* The title of the Nightscout service */ "Nightscout" = "Nightscout"; /* The error message displayed for device connection errors. */ "No connected devices, or failure during device connection" = "Nessun dispositivo connesso o mancanza di segnale durante la connessione del dispositivo"; +/* The title text for the override presets */ +"Override Presets" = "Impostazioni predefinite regolazione manuale"; + /* The label of the pre-meal mode toggle button */ "Pre-Meal Targets" = "Obiettivo pre-pasto"; @@ -292,6 +294,9 @@ /* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ "Predicted glucose of %1$@ is below your suspend threshold setting." = "La glicemia prevista %1$@ e' inferiore al valore soglia per la sospensione dell'erogazione."; +/* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ +"Predicted: %1$@\nActual: %2$@ (%3$@)" = "Previsto: %1$@\nEffettivo: %2$@ (%3$@)"; + /* The title of the pump section in settings */ "Pump" = "Microinfusore"; @@ -310,6 +315,9 @@ /* The notification title for a low pump reservoir */ "Pump Reservoir Low" = "Serbatoio microinfusore Basso"; +/* The title of the cell indicating the pump is suspended */ +"Pump Suspended" = "Microinfusore sospesa"; + /* Title of insulin model preset */ "Rapid-Acting – Adults" = "Insulina ultrarapida – Adulti"; @@ -349,8 +357,8 @@ /* The title of the nightscout site URL credential */ "Site URL" = "Sito URL"; -/* The title text for the Medtronic sensor switch cell */ -"Sof-Sensor / Enlite" = "Sof-Sensor / Enlite"; +/* The format for the description of a temporary override start date */ +"starting at %@" = "a partire da %@"; /* The title of the cell indicating a bolus is being sent */ "Starting Bolus" = "Invio Bolo"; @@ -358,26 +366,26 @@ /* The title text in settings */ "Suspend Threshold" = "Blocco Erogazione"; +/* The subtitle of the cell displaying an action to resume insulin delivery */ +"Tap to Resume" = "Premi per riprendere"; + /* Subtitle description of Walsh insulin model setting */ "The legacy model used by Loop, allowing customization of action duration." = "ll modello legacy utilizzato da Loop, che consente la personalizzazione della durata dell'azione."; /* Body of the alert describing a maximum bolus validation error. (1: The localized max bolus value) */ "The maximum bolus amount is %@ Units" = "Il massimo bolo eseguibile e' pari a %@ Unità"; -/* Instructions on where to find the transmitter ID */ -"The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app." = "L'ID trasmettitore può essere trovato stampato sul retro del dispositivo, sul lato della scatola, e all'interno del menù impostazione del ricevitore all'interno dell'applicazione mobile."; - -/* The description of the switch which toggles retrospective correction effects */ -"This will more aggresively increase or decrease basal delivery when glucose movement doesn't match the carbohydrate and insulin-based model." = "Questa impostazione rendera' piu' aggressiva l'erogazione o il blocco delle basali quando l'andamento glicemico non corrisponde al numero di carboidrati attivi e all'insulina attiva."; - /* The short unit display string for international units of insulin */ "U" = "U"; +/* The format for the description of a temporary override end date */ +"until %@" = "fino a %@"; + /* The title of the alert controller used to select a duration for workout targets */ "Use Workout Glucose Targets" = "Utilizza i target glicemici personalizzati per l'attivita' sportiva"; /* Title of insulin model setting */ -"Walsh" = "Walsh"; +"Walsh" = "Walsh"; /* Explanation of suspend threshold */ "When current or forecasted glucose is below the suspend threshold, Loop will not recommend a bolus, and will always recommend a temporary basal rate of 0 units per hour." = "Quando l'attuale valore glicemico o un valore previsto nel futuro e' al di sotto del valore minimo previsto per la sospensione dell'erogazione, Loop non consigliera' un bolo e blocchera' l'erogazione delle basali."; diff --git a/Loop/it.lproj/Main.strings b/Loop/it.lproj/Main.strings index aba9288282..80605f8d60 100644 --- a/Loop/it.lproj/Main.strings +++ b/Loop/it.lproj/Main.strings @@ -25,9 +25,6 @@ /* Class = "UINavigationItem"; title = "Bolus"; ObjectID = "aiu-ZA-zVa"; */ "aiu-ZA-zVa.title" = "Bolo"; -/* Class = "UILabel"; text = "Title"; ObjectID = "B9F-K3-7RI"; */ -"B9F-K3-7RI.text" = "Titolo"; - /* Class = "UILabel"; text = "Label"; ObjectID = "bIL-Ub-qYp"; */ "bIL-Ub-qYp.text" = "Etichetta"; diff --git a/Loop/ja.lproj/InfoPlist.strings b/Loop/ja.lproj/InfoPlist.strings new file mode 100644 index 0000000000..2797fd72f0 --- /dev/null +++ b/Loop/ja.lproj/InfoPlist.strings @@ -0,0 +1,18 @@ +/* Bundle name */ +"CFBundleName" = "$(PRODUCT_NAME)"; + +/* Privacy - Bluetooth Always Usage Description */ +"NSBluetoothAlwaysUsageDescription" = "ブルートゥースは、インスリンポンプおよび連続グルコースモニタデバイスと通信するために使用されます"; + +/* Privacy - Bluetooth Peripheral Usage Description */ +"NSBluetoothPeripheralUsageDescription" = "ブルートゥースは、インスリンポンプおよび連続グルコースモニタデバイスと通信するために使用されます"; + +/* Privacy - Face ID Usage Description */ +"NSFaceIDUsageDescription" = "フェイスIDはインスリンボーラスの認証に使用されます"; + +/* Privacy - Health Share Usage Description */ +"NSHealthShareUsageDescription" = "アプリに入力された炭水化物の食事データは、健康データベースに保存されます。 グルコースデータはHealthKitに安全に保存されます"; + +/* Privacy - Health Update Usage Description */ +"NSHealthUpdateUsageDescription" = "健康データベースからの食事データは、グルコース効果を決定するために使用される。 グルコースデータはグラフ作成と解析のためにHealthKitから検索されます"; + diff --git a/Loop/ja.lproj/Localizable.strings b/Loop/ja.lproj/Localizable.strings new file mode 100644 index 0000000000..5b711bdd49 --- /dev/null +++ b/Loop/ja.lproj/Localizable.strings @@ -0,0 +1,389 @@ +/* The string format appended to active insulin that describes pending insulin. (1: pending insulin) */ +" (pending: %@)" = " (保留中: %@)"; + +/* The format for an active override preset. (1: preset symbol)(2: preset name) */ +"%@ %@" = "%1$@ %2$@"; + +/* Formats absorbed carb value */ +"%@ absorbed" = "%@ 吸収済"; + +/* The subtitle format describing total insulin. (1: localized insulin total) */ +"%@ U Total" = "計 %@ 単位"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ +"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; + +/* Format string for carb ratio average. (1: value)(2: carb unit) */ +"%1$@ %2$@/U" = "%1$@ %2$@/U"; + +/* Formats (1: carb start time) and (2: carb absorption duration) */ +"%1$@ + %2$@" = "%1$@ + %2$@"; + +/* Low reservoir alert format string. (1: Number of units remaining) */ +"%1$@ U left" = "残 %1$@単位"; + +/* Low reservoir alert with time remaining format string. (1: Number of units remaining)(2: approximate time remaining) */ +"%1$@ U left: %2$@" = "残 %1$@単位: %2$@"; + +/* The format for recommended temp basal rate and time. (1: localized rate number)(2: localized time) */ +"%1$@ U/hour @ %2$@" = "%1$@ U/時 @ %2$@"; + +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Formats (1: carb value) and (2: food type) */ +"%1$@: %2$@" = "%1$@: %2$@"; + +/* Description of the prediction input effect for glucose momentum */ +"15 min glucose regression coefficient (b₁), continued with decay over 30 min" = "15分 グルコース回帰係数 (b₁)、30分退化適用"; + +/* Description of the prediction input effect for retrospective correction */ +"30 min comparison of glucose prediction vs actual, continued with decay over 60 min" = "30分 グルコース予想値と実際値の比較、60分退化適用"; + +/* Subtitle of Fiasp preset */ +"A model based on the published absorption of Fiasp insulin." = "Fiaspインスリンの公表吸収率に基づいたモデル。"; + +/* Subtitle of Rapid-Acting – Adult preset */ +"A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults." = "大人使用時の Humalog、Novolog、Apidraインスリンの公表吸収率に基づいたモデル。"; + +/* Action to copy the recommended Bolus value to the actual Bolus Field */ +"AcceptRecommendedBolus" = "推奨ボーラス値を使う"; + +/* The title of the Carbs On-Board graph */ +"Active Carbohydrates" = "残存糖質"; + +/* The string format describing active carbohydrates. (1: localized glucose value description) */ +"Active Carbohydrates: %@" = "残存糖質: %@"; + +/* The title of the Insulin On-Board graph */ +"Active Insulin" = "残存インスリン"; + +/* The string format describing active insulin. (1: localized insulin value description) */ +"Active Insulin: %@" = "残存インスリン: %@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "カーボを追加"; + +/* Action sheet title selecting CGM + Title text for button to set up a CGM */ +"Add CGM" = "CGMを追加"; + +/* The label of the carb entry button */ +"Add Meal" = "食事を追加"; + +/* Action sheet title selecting Pump + Title text for button to set up a new pump */ +"Add Pump" = "ポンプを追加"; + +/* The title of the Amplitude service */ +"Amplitude" = "Amplitude"; + +/* Subtitle of Rapid-Acting – Children preset */ +"An adjustment to the adult model based on empirical effects in children." = "大人モデルに経験効果を加算し調整したモデル。"; + +/* The title of the amplitude API key credential */ +"API Key" = "API キー"; + +/* The title of the nightscout API secret credential */ +"API Secret" = "API シークレット"; + +/* Confirmation message for deleting a CGM */ +"Are you sure you want to delete this CGM?" = "このCGMを削除しますか?"; + +/* Format fragment for a specific time */ +"at %@" = "%@ 時点"; + +/* The message displayed during a device authentication prompt for bolus specification */ +"Authenticate to Bolus %@ Units" = "ボーラス %@単位 認証してください"; + +/* Details for configuration error when basal rate schedule is missing */ +"Basal Rate Schedule" = "基礎レートスケジュール"; + +/* The title of the basal rate profile screen + The title text for the basal rate schedule */ +"Basal Rates" = "基礎レート"; + +/* The label of the bolus entry button + The notification title for a bolus failure */ +"Bolus" = "ボーラス"; + +/* The format string for bolus progress. (1: delivered volume)(2: total volume) */ +"Bolused %1$@ of %2$@" = "%2$@ のうち %1$@ ボーラス済"; + +/* The format string for bolus in progress showing total volume. (1: total volume) */ +"Bolusing %1$@" = "%1$@ ボーラス中"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "取消"; + +/* The title of the cell indicating a bolus is being canceled */ +"Canceling Bolus" = "ボーラスをキャンセルします"; + +/* Details for missing data error when carb effects are missing */ +"Carb effects" = "糖質効果"; + +/* The title of the carb ratios schedule screen + The title text for the carb ratio schedule */ +"Carb Ratios" = "糖質比"; + +/* Title of the prediction input effect for carbohydrates */ +"Carbohydrates" = "糖質"; + +/* Description of the prediction input effect for carbohydrates. (1: The glucose unit string) */ +"Carbs Absorbed (g) ÷ Carb Ratio (g/U) × Insulin Sensitivity (%1$@/U)" = "吸収済糖質(g) ÷ 糖質比 (g/U) × インスリン効果値(%1$@/U)"; + +/* The notification alert describing a low pump battery */ +"Change the pump battery immediately" = "すぐにポンプの電池を替えてください"; + +/* The notification alert describing an empty pump reservoir */ +"Change the pump reservoir now" = "ポンプのレザーバを替えてください"; + +/* Details for configuration error when one or more loop settings are missing */ +"Check settings" = "設定を確認する"; + +/* Recovery suggestion when reservoir data is missing */ +"Check that your pump is in range" = "ポンプが近くにあることを確認してください"; + +/* Recovery suggestion when glucose data is missing */ +"Check your CGM data source" = "CGM データソースを確認してください"; + +/* The title text for the looping enabled switch cell */ +"Closed Loop" = "クローズドループ"; + +/* The title of the action used to dismiss an error alert */ +"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; + +/* The title of the configuration section in settings */ +"Configuration" = "コンフィグレーション"; + +/* The error message displayed for configuration errors. (1: configuration error details) */ +"Configuration Error: %1$@" = "設定エラー: %1$@"; + +/* The title of the continuous glucose monitor section in settings */ +"Continuous Glucose Monitor" = "CGM"; + +/* The title of the glucose target range schedule screen + The title text for the glucose target range schedule */ +"Correction Range" = "補正範囲"; + +/* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ +"Current glucose of %1$@ is below correction range." = "現在の血糖値は %1$@ で補正範囲を下回っています。"; + +/* The title of the cell indicating a generic temporary override is enabled */ +"Custom Override" = "カスタムオーバーライド"; + +/* The title of the Loggly customer token credential */ +"Customer Token" = "カスタマートークン"; + +/* Button title to delete CGM */ +"Delete CGM" = "CGMを削除"; + +/* Title text for delivery limits */ +"Delivery Limits" = "注入限度"; + +/* The action hint of the workout mode toggle button when enabled */ +"Disables" = "無効にする"; + +/* The action hint of the workout mode toggle button when disabled */ +"Enables" = "有効にする"; + +/* The placeholder text instructing users to enter a suspend treshold */ +"Enter suspend threshold" = "一時停止値を入力"; + +/* The alert title for an error while canceling a bolus */ +"Error Canceling Bolus" = "ボーラスのキャンセルにエラー"; + +/* The alert title for a resume error */ +"Error Resuming" = "再開エラー"; + +/* The subtitle format describing eventual glucose. (1: localized glucose value description) */ +"Eventually %@" = "予想 %@"; + +/* The title of the alert describing a maximum bolus validation error */ +"Exceeds Maximum Bolus" = "ボーラス最大値を超えています"; + +/* Title of insulin model preset */ +"Fiasp" = "Fiasp"; + +/* The format string used to describe a finite workout targets duration */ +"For %1$@" = "%1$@につき"; + +/* The title of the glucose and prediction graph */ +"Glucose" = "血糖値"; + +/* The error message when glucose data is too old to be used. (1: glucose data age in minutes) */ +"Glucose data is %1$@ old" = "血糖データは %1$@ 前のものです"; + +/* Description of error when glucose data is missing */ +"Glucose data not available" = "グルコースデータがありません"; + +/* Title of the prediction input effect for glucose momentum */ +"Glucose Momentum" = "グルコース モメンタム"; + +/* The title of a target alert action specifying an indefinitely long workout targets duration */ +"Indefinitely" = "無期限"; + +/* Title of the prediction input effect for insulin */ +"Insulin" = "インスリン"; + +/* Description of the prediction input effect for insulin */ +"Insulin Absorbed (U) × Insulin Sensitivity (%1$@/U)" = "吸収済インスリン(U) × インスリン効果値(%1$@/U)"; + +/* The title of the insulin delivery graph */ +"Insulin Delivery" = "インスリン放出"; + +/* Details for missing data error when insulin effects are missing */ +"Insulin effects" = "インスリン効果"; + +/* Details for configuration error when insulin model is missing + The title text for the insulin model setting row */ +"Insulin Model" = "インスリンモデル"; + +/* The title of the insulin sensitivities schedule screen + The title text for the insulin sensitivity schedule */ +"Insulin Sensitivities" = "インスリン効果値"; + +/* The error message when invalid data was encountered. (1: details of invalid data) */ +"Invalid data: %1$@" = "無効データ: %1$@"; + +/* The title text for the issue report cell */ +"Issue Report" = "問題を報告"; + +/* Glucose HUD accessibility hint */ +"Launches CGM app" = "CGMアプリを起動"; + +/* The loading message for the diagnostic report screen */ +"Loading..." = "ロード中..."; + +/* The title of the loggly service */ +"Loggly" = "Loggly"; + +/* The notification title for a loop failure */ +"Loop Failure" = "ループの不良"; + +/* The notification alert describing a long-lasting loop failure. The substitution parameter is the time interval since the last loop */ +"Loop has not completed successfully in %@" = "ループが %@ の間クローズされていません"; + +/* The error message for missing data. (1: missing data details) */ +"Missing data: %1$@" = "データがありません: %1$@"; + +/* Details for missing data error when momentum effects are missing */ +"Momentum effects" = "モメンタム効果"; + +/* The title of the Nightscout service */ +"Nightscout" = "Nightscout"; + +/* The error message displayed for device connection errors. */ +"No connected devices, or failure during device connection" = "機器が未接続、または接続に問題"; + +/* The label of the pre-meal mode toggle button */ +"Pre-Meal Targets" = "食前ターゲット"; + +/* Message when offering bolus recommendation even though bg is below range and minBG is in future. (1: glucose time)(2: glucose number) */ +"Predicted glucose at %1$@ is %2$@." = "%1$@の予想グルコースは %2$@です。"; + +/* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ +"Predicted glucose of %1$@ is below your suspend threshold setting." = "予想グルコースは %1$@ で一時停止値を下回ります。"; + +/* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ +"Predicted: %1$@\nActual: %2$@ (%3$@)" = "Predicted: %1$@\nActual: %2$@ (%3$@)"; + +/* The title of the pump section in settings */ +"Pump" = "ポンプ"; + +/* The notification title for a low pump battery */ +"Pump Battery Low" = "ポンプの電池が不足"; + +/* The error message when pump data is too old to be used. (1: pump data age in minutes) */ +"Pump data is %1$@ old" = "ポンプデータが %1$@前のものです"; + +/* Details for configuration error when pump manager is missing */ +"Pump Manager" = "ポンプ設定"; + +/* The notification title for an empty pump reservoir */ +"Pump Reservoir Empty" = "ポンプのリザーバが空です"; + +/* The notification title for a low pump reservoir */ +"Pump Reservoir Low" = "ポンプのリザーバが低です"; + +/* The title of the cell indicating the pump is suspended */ +"Pump Suspended" = "ポンプ一時停止中"; + +/* Title of insulin model preset */ +"Rapid-Acting – Adults" = "超速攻型 - 大人"; + +/* Title of insulin model preset */ +"Rapid-Acting – Children" = "超速攻型 - 子供"; + +/* The error message when a recommendation has expired. (1: age of recommendation in minutes) */ +"Recommendation expired: %1$@ old" = "推奨が %1$@ 経過したため失効"; + +/* The title of the cell displaying a recommended temp basal value */ +"Recommended Basal" = "推奨基礎分泌量"; + +/* Accessibility hint describing recommended bolus units */ +"Recommended Bolus: %@ Units" = "推奨ボーラス: %@ 単位"; + +/* Details for missing data error when reservoir data is missing */ +"Reservoir" = "リザーバ"; + +/* Title of the prediction input effect for retrospective correction */ +"Retrospective Correction" = "レトロ補正"; + +/* The title of the notification action to retry a bolus command */ +"Retry" = "やり直す"; + +/* The title of the services section in settings */ +"Services" = "サービス"; + +/* The label of the settings button */ +"Settings" = "設定"; + +/* Loop Completion HUD accessibility hint */ +"Shows last loop error" = "前回のループエラー"; + +/* Format fragment for a start time */ +"since %@" = "%@ から"; + +/* The title of the nightscout site URL credential */ +"Site URL" = "アドレス"; + +/* The format for the description of a temporary override start date */ +"starting at %@" = "%@から開始"; + +/* The title of the cell indicating a bolus is being sent */ +"Starting Bolus" = "ボーラス注入を開始"; + +/* The title text in settings */ +"Suspend Threshold" = "一時停止値"; + +/* The subtitle of the cell displaying an action to resume insulin delivery */ +"Tap to Resume" = "タップして再開する"; + +/* Subtitle description of Walsh insulin model setting */ +"The legacy model used by Loop, allowing customization of action duration." = "ループのレガシーモデルで、作用期間をカスタマイズできます。"; + +/* Body of the alert describing a maximum bolus validation error. (1: The localized max bolus value) */ +"The maximum bolus amount is %@ Units" = "最大ボーラス量は %@単位です"; + +/* The short unit display string for international units of insulin */ +"U" = "U"; + +/* The format for the description of a temporary override end date */ +"until %@" = "%@まで"; + +/* The title of the alert controller used to select a duration for workout targets */ +"Use Workout Glucose Targets" = "運動時ターゲットを使用"; + +/* Title of insulin model setting */ +"Walsh" = "Walsh"; + +/* Explanation of suspend threshold */ +"When current or forecasted glucose is below the suspend threshold, Loop will not recommend a bolus, and will always recommend a temporary basal rate of 0 units per hour." = "現在または予測グルコースが一時停止値を下回るため、ループはボーラスを推奨しません。0単位/時の一時的基礎レートを推奨します。"; + +/* The label of the workout mode toggle button */ +"Workout Targets" = "運動時ターゲット"; + diff --git a/Loop/ja.lproj/Main.strings b/Loop/ja.lproj/Main.strings new file mode 100644 index 0000000000..1624294944 --- /dev/null +++ b/Loop/ja.lproj/Main.strings @@ -0,0 +1,144 @@ +/* Class = "UINavigationItem"; title = "Status"; ObjectID = "3kU-n2-fha"; */ +"3kU-n2-fha.title" = "ステータス"; + +/* Class = "UILabel"; text = "3.5 U/hour @ 12:12 PM"; ObjectID = "5gz-kZ-iF1"; */ +"5gz-kZ-iF1.text" = "3.5 U/時 @ 12:12 PM"; + +/* Class = "UILabel"; text = "Bolus"; ObjectID = "5oA-6d-ZTL"; */ +"5oA-6d-ZTL.text" = "ボーラス"; + +/* Class = "UILabel"; text = "Pump ID"; ObjectID = "5TX-kX-nBo"; */ +"5TX-kX-nBo.text" = "ポンプID"; + +/* Class = "UITextField"; accessibilityLabel = "Bolus Amount"; ObjectID = "7LT-50-ZzK"; */ +"7LT-50-ZzK.accessibilityLabel" = "ボーラス量"; + +/* Class = "UITextField"; placeholder = "0.0"; ObjectID = "7LT-50-ZzK"; */ +"7LT-50-ZzK.placeholder" = "0.0"; + +/* Class = "UILabel"; text = "Predicted"; ObjectID = "87H-N1-0vJ"; */ +"87H-N1-0vJ.text" = "予想"; + +/* Class = "UILabel"; text = "Detail"; ObjectID = "aCb-Qs-bpu"; */ +"aCb-Qs-bpu.text" = "詳細"; + +/* Class = "UINavigationItem"; title = "Bolus"; ObjectID = "aiu-ZA-zVa"; */ +"aiu-ZA-zVa.title" = "ボーラス"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "bIL-Ub-qYp"; */ +"bIL-Ub-qYp.text" = "ラベル"; + +/* Class = "UILabel"; text = "Glucose Change"; ObjectID = "bq4-98-cQU"; */ +"bq4-98-cQU.text" = "グルコース変動"; + +/* Class = "UILabel"; accessibilityLabel = "Units"; ObjectID = "BR0-dr-Fj2"; */ +"BR0-dr-Fj2.accessibilityLabel" = "単位"; + +/* Class = "UILabel"; text = "U"; ObjectID = "BR0-dr-Fj2"; */ +"BR0-dr-Fj2.text" = "U"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "cpo-Po-gFM"; */ +"cpo-Po-gFM.text" = "ラベル"; + +/* Class = "UILabel"; text = "g Total"; ObjectID = "d3X-AN-tA5"; */ +"d3X-AN-tA5.text" = "g 合計"; + +/* Class = "UILabel"; text = "Future glucose is predicted by combining the effects of multiple inputs. Use this tool to toggle various inputs to see how they compare to the final prediction."; ObjectID = "D4C-I2-dhA"; */ +"D4C-I2-dhA.text" = "予想グルコースは複数のインプットの効果を組み合わせて算出されます。このツールでは様々なインプットを切り替えて最終予想にどのように関わっているか見ることができます。"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "d6m-qV-wWi"; */ +"d6m-qV-wWi.text" = "ラベル"; + +/* Class = "UINavigationItem"; title = "Settings"; ObjectID = "dmB-PQ-B44"; */ +"dmB-PQ-B44.title" = "設定"; + +/* Class = "UILabel"; text = "DEVICES"; ObjectID = "DyC-Sv-qP8"; */ +"DyC-Sv-qP8.text" = "機器"; + +/* Class = "UILabel"; text = "eventually 92 mg/dL"; ObjectID = "E41-FN-nkk"; */ +"E41-FN-nkk.text" = "予想 92 mg/dL"; + +/* Class = "UILabel"; text = "Observed"; ObjectID = "EAn-Ja-S1d"; */ +"EAn-Ja-S1d.text" = "観察"; + +/* Class = "UILabel"; text = "Active Carbohydrates: 40g"; ObjectID = "hHZ-uY-aKw"; */ +"hHZ-uY-aKw.text" = "残存糖質: 40g"; + +/* Class = "UITableViewController"; title = "Carbohydrate Effects"; ObjectID = "hZZ-2S-lrd"; */ +"hZZ-2S-lrd.title" = "糖質効果"; + +/* Class = "UILabel"; text = "Observed changes in glucose, subtracting changes modeled from insulin delivery, can be used to estimate carbohydrate absorption."; ObjectID = "IxU-As-glo"; */ +"IxU-As-glo.text" = "観察されたグルコース値の変動から、インスリン注入のモデルによる変動を引くことにより、糖質の吸収を推定することができます。"; + +/* Class = "UILabel"; text = "Detail"; ObjectID = "J7x-W5-gwo"; */ +"J7x-W5-gwo.text" = "詳細"; + +/* Class = "UILabel"; text = "⚠ Glucose Predicted Below Range"; ObjectID = "k0g-P7-OVN"; */ +"k0g-P7-OVN.text" = "⚠ 予想グルコースが補正範囲を下回ります"; + +/* Class = "UILabel"; text = "Recommended Basal"; ObjectID = "k3F-Na-7mn"; */ +"k3F-Na-7mn.text" = "推奨基礎分泌量"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "Krd-Aa-ret"; */ +"Krd-Aa-ret.text" = "ラベル"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "l7l-mw-Oc9"; */ +"l7l-mw-Oc9.text" = "ラベル"; + +/* Class = "UILabel"; text = "Tap to set"; ObjectID = "m9c-SQ-djE"; */ +"m9c-SQ-djE.text" = "タップして確定"; + +/* Class = "UILabel"; accessibilityLabel = "Units"; ObjectID = "mVz-dr-xLU"; */ +"mVz-dr-xLU.accessibilityLabel" = "単位"; + +/* Class = "UILabel"; text = "U"; ObjectID = "mVz-dr-xLU"; */ +"mVz-dr-xLU.text" = "U"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "OFA-qT-ZAg"; */ +"OFA-qT-ZAg.text" = "ラベル"; + +/* Class = "UITableViewController"; title = "Predicted Glucose"; ObjectID = "PA3-sP-cWY"; */ +"PA3-sP-cWY.title" = "予想グルコース"; + +/* Class = "UITableViewController"; title = "Insulin Model"; ObjectID = "pi6-Dh-72V"; */ +"pi6-Dh-72V.title" = "インスリンモデル"; + +/* Class = "UILabel"; text = "An insulin activity model is used to estimate effects of insulin on glucose levels. An accurate model can help prevent insulin stacking and safely recommend corrective treatments."; ObjectID = "PJv-p9-cFe"; */ +"PJv-p9-cFe.text" = "インスリン作用モデルはグルコース値へのインスリンの効果を概算するために使われます。正確なモデルにより、インスリンの蓄積を防ぎ、補正治療を安全に推奨することができます。"; + +/* Class = "UILabel"; text = "eventually 92 mg/dL"; ObjectID = "Rse-x8-amW"; */ +"Rse-x8-amW.text" = "予想 92 mg/dL"; + +/* Class = "UILabel"; text = "g COB"; ObjectID = "SQx-au-ZcM"; */ +"SQx-au-ZcM.text" = "g 残存糖質"; + +/* Class = "UILabel"; text = "Glucose"; ObjectID = "tuw-av-A3x"; */ +"tuw-av-A3x.text" = "血糖値"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "ufi-Kj-33k"; */ +"ufi-Kj-33k.text" = "ラベル"; + +/* Class = "UILabel"; text = "Active Insulin: 1.5U"; ObjectID = "viU-7a-kbI"; */ +"viU-7a-kbI.text" = "残存インスリン: 1.5U"; + +/* Class = "UINavigationItem"; title = "Carbohydrates"; ObjectID = "Vpi-5b-bY5"; */ +"Vpi-5b-bY5.title" = "残存糖質"; + +/* Class = "UITextField"; text = "4 hour"; ObjectID = "Wk3-xv-IM5"; */ +"Wk3-xv-IM5.text" = "4時間"; + +/* Class = "UIButton"; normalTitle = "Deliver"; ObjectID = "Ya0-9b-ZAS"; */ +"Ya0-9b-ZAS.normalTitle" = "注入"; + +/* Class = "UILabel"; text = "0"; ObjectID = "yn7-2M-jZz"; */ +"yn7-2M-jZz.text" = "0"; + +/* Class = "UILabel"; text = "Recommended"; ObjectID = "ywT-OR-NnU"; */ +"ywT-OR-NnU.text" = "推奨"; + +/* Class = "UILabel"; text = "Title"; ObjectID = "zbc-87-wxZ"; */ +"zbc-87-wxZ.text" = "タイトル"; + +/* Class = "UILabel"; text = "0"; ObjectID = "zvZ-uf-zMX"; */ +"zvZ-uf-zMX.text" = "0"; + diff --git a/Loop/nb.lproj/InfoPlist.strings b/Loop/nb.lproj/InfoPlist.strings index 38f7988fe6..4168f9e7ed 100644 --- a/Loop/nb.lproj/InfoPlist.strings +++ b/Loop/nb.lproj/InfoPlist.strings @@ -1,9 +1,9 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "Loop"; - /* Bundle name */ "CFBundleName" = "$(PRODUCT_NAME)"; +/* Privacy - Bluetooth Always Usage Description */ +"NSBluetoothAlwaysUsageDescription" = "Bluetooth brukes til å kommunisere med insulinpumpe og kontinuerlige glukosemonitorer."; + /* Privacy - Bluetooth Peripheral Usage Description */ "NSBluetoothPeripheralUsageDescription" = "Bluetooth brukes til å kommunisere med insulinpumpe og kontinuerlige glukosemonitorer."; diff --git a/Loop/nb.lproj/LaunchScreen.strings b/Loop/nb.lproj/LaunchScreen.strings deleted file mode 100644 index 8b13789179..0000000000 --- a/Loop/nb.lproj/LaunchScreen.strings +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Loop/nb.lproj/Localizable.strings b/Loop/nb.lproj/Localizable.strings index 55b8db14c8..6b8e7629be 100644 --- a/Loop/nb.lproj/Localizable.strings +++ b/Loop/nb.lproj/Localizable.strings @@ -1,10 +1,22 @@ -/* Created with Localizable Strings Merge, the swiss-army knife for iOS and OS X localization. */ - /* The string format appended to active insulin that describes pending insulin. (1: pending insulin) */ -" (pending: %@)" = "(Gjenstår: %@)"; +" (pending: %@)" = " (Gjenstår: %@)"; + +/* The format for an active override preset. (1: preset symbol)(2: preset name) */ +"%@ %@" = "%1$@ %2$@"; + +/* Formats absorbed carb value */ +"%@ absorbed" = "%@ absorbert"; + +/* The subtitle format describing total insulin. (1: localized insulin total) */ +"%@ U Total" = "%@ E totalt"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ +"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; -/* Format string for carb ratio average. (1: value)(2: carb unit) - Format string for insulin sensitivity average (1: value)(2: glucose unit) */ +/* Format string for carb ratio average. (1: value)(2: carb unit) */ "%1$@ %2$@/U" = "%1$@ %2$@/E"; /* Formats (1: carb start time) and (2: carb absorption duration) */ @@ -22,24 +34,9 @@ /* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ "%1$@ v%2$@" = "%1$@ v%2$@"; -/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ -"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; - -/* Format string describing glucose units per minute (1: glucose unit string) */ -"%1$@/min" = "%1$@/min"; - /* Formats (1: carb value) and (2: food type) */ "%1$@: %2$@" = "%1$@: %2$@"; -/* Formats absorbed carb value */ -"%@ absorbed" = "%@ absorbert"; - -/* The subtitle format describing total insulin. (1: localized insulin total) */ -"%@ U Total" = "%@ E totalt"; - -/* Appends a full-stop to a statement */ -"%@." = "%@."; - /* Description of the prediction input effect for glucose momentum */ "15 min glucose regression coefficient (b₁), continued with decay over 30 min" = "15 minutters glukose-regresjonskoeffisient (b1), fortsatt med henfall over 30 minutter."; @@ -67,15 +64,20 @@ /* The string format describing active insulin. (1: localized insulin value description) */ "Active Insulin: %@" = "Aktivt insulin: %@"; +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Legg til oppføring med karbohydrater"; + +/* Action sheet title selecting CGM + Title text for button to set up a CGM */ +"Add CGM" = "Legg til CGM"; + /* The label of the carb entry button */ "Add Meal" = "Legg til måltid"; -/* Title text for button to set up a new pump */ +/* Action sheet title selecting Pump + Title text for button to set up a new pump */ "Add Pump" = "Legg til pumpe"; -/* The title of the section containing algorithm settings */ -"Algorithm Settings" = "Algoritmeinnstillinger"; - /* The title of the Amplitude service */ "Amplitude" = "Amplitude"; @@ -88,6 +90,9 @@ /* The title of the nightscout API secret credential */ "API Secret" = "API Secret"; +/* Confirmation message for deleting a CGM */ +"Are you sure you want to delete this CGM?" = "Er du sikker på at du vil slette denne CGM?"; + /* Format fragment for a specific time */ "at %@" = "kl. %@"; @@ -105,9 +110,18 @@ The notification title for a bolus failure */ "Bolus" = "Bolus"; +/* The format string for bolus progress. (1: delivered volume)(2: total volume) */ +"Bolused %1$@ of %2$@" = "Ga bolus %1$@ of %2$"; + +/* The format string for bolus in progress showing total volume. (1: total volume) */ +"Bolusing %1$@" = "Gir bolus %1$@"; + /* The title of the cancel action in an action sheet */ "Cancel" = "Avbryt"; +/* The title of the cell indicating a bolus is being canceled */ +"Canceling Bolus" = "Avbryter bolus"; + /* Details for missing data error when carb effects are missing */ "Carb effects" = "Karbohydrateffekter"; @@ -139,6 +153,9 @@ /* The title text for the looping enabled switch cell */ "Closed Loop" = "Lukket Loop"; +/* The title of the action used to dismiss an error alert */ +"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; + /* The title of the configuration section in settings */ "Configuration" = "Konfigurasjon"; @@ -155,14 +172,14 @@ /* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ "Current glucose of %1$@ is below correction range." = "Nåværende blodsukker på %1$@ er under korreksjonsområde."; +/* The title of the cell indicating a generic temporary override is enabled */ +"Custom Override" = "Tilpassede overstyringer"; + /* The title of the Loggly customer token credential */ "Customer Token" = "Customer Token"; -/* The short unit display string for decibles */ -"dB" = "dB"; - -/* The title of the button to remove the credentials for a service */ -"Delete Account" = "Slett konto"; +/* Button title to delete CGM */ +"Delete CGM" = "Slett CGM"; /* Title text for delivery limits */ "Delivery Limits" = "Leveringsgrense"; @@ -170,17 +187,17 @@ /* The action hint of the workout mode toggle button when enabled */ "Disables" = "Deaktiver"; -/* Title of the switch which toggles retrospective correction effects */ -"Enable Retrospective Correction" = "Aktiver retrospektiv korreksjon"; - /* The action hint of the workout mode toggle button when disabled */ "Enables" = "Aktiverer"; /* The placeholder text instructing users to enter a suspend treshold */ "Enter suspend threshold" = "Legg til grense for insulinstopp"; -/* The placeholder text instructing users how to enter a transmitter ID */ -"Enter the 6-digit transmitter ID" = "Angi 6-sifret sender (CGM) ID"; +/* The alert title for an error while canceling a bolus */ +"Error Canceling Bolus" = "Kunne ikke kansellere bolus"; + +/* The alert title for a resume error */ +"Error Resuming" = "Kunne ikke gjenoppta"; /* The subtitle format describing eventual glucose. (1: localized glucose value description) */ "Eventually %@" = "Omsider %@"; @@ -192,16 +209,7 @@ "Fiasp" = "Fiasp"; /* The format string used to describe a finite workout targets duration */ -"For %1$@" = "For %1$@"; - -/* The short unit display string for grams */ -"g" = "g"; - -/* The title text for the G4 Share Receiver switch cell */ -"G4 Share Receiver" = "G4 Share Receiver"; - -/* The title text for the G5/G6 Transmitter switch cell */ -"G5/G6 Transmitter" = "G5/G6 sender"; +"For %1$@" = "Til %1$@"; /* The title of the glucose and prediction graph */ "Glucose" = "Glukose"; @@ -262,27 +270,21 @@ /* The notification alert describing a long-lasting loop failure. The substitution parameter is the time interval since the last loop */ "Loop has not completed successfully in %@" = "Loop har ikke kjørt vellykket på %@"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; - /* The error message for missing data. (1: missing data details) */ "Missing data: %1$@" = "Mangler data: %1$@"; -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; - /* Details for missing data error when momentum effects are missing */ "Momentum effects" = "Momentumeffekter"; -/* Sensor state description for the non-valid state */ -"Needs Attention" = "Trenger tilsyn"; - /* The title of the Nightscout service */ "Nightscout" = "Nightscout"; /* The error message displayed for device connection errors. */ "No connected devices, or failure during device connection" = "Ingen tilkoblede enheter, eller feil under tilkobling til enhet"; +/* The title text for the override presets */ +"Override Presets" = "Forhåndslagrede overstyringer"; + /* The label of the pre-meal mode toggle button */ "Pre-Meal Targets" = "Pre-måltids mål"; @@ -292,6 +294,9 @@ /* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ "Predicted glucose of %1$@ is below your suspend threshold setting." = "Forventet blodsukker %1$@ er under innstilling for insulinstopp"; +/* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ +"Predicted: %1$@\nActual: %2$@ (%3$@)" = "Forespeilet: %1$@\nFaktisk: %2$@ (%3$@)"; + /* The title of the pump section in settings */ "Pump" = "Pumpe"; @@ -310,6 +315,9 @@ /* The notification title for a low pump reservoir */ "Pump Reservoir Low" = "Pumpereservoar lavt"; +/* The title of the cell indicating the pump is suspended */ +"Pump Suspended" = "Pumpe satt på pause"; + /* Title of insulin model preset */ "Rapid-Acting – Adults" = "Hurtigvirkende – voksen"; @@ -349,8 +357,8 @@ /* The title of the nightscout site URL credential */ "Site URL" = "Nettsted (URL)"; -/* The title text for the Medtronic sensor switch cell */ -"Sof-Sensor / Enlite" = "Sof-Sensor / Enlite"; +/* The format for the description of a temporary override start date */ +"starting at %@" = "startet %@"; /* The title of the cell indicating a bolus is being sent */ "Starting Bolus" = "Starter bolus"; @@ -358,21 +366,21 @@ /* The title text in settings */ "Suspend Threshold" = "Grense for insulinstopp"; +/* The subtitle of the cell displaying an action to resume insulin delivery */ +"Tap to Resume" = "Trykk for å gjenoppta"; + /* Subtitle description of Walsh insulin model setting */ "The legacy model used by Loop, allowing customization of action duration." = "Den gamle modellen brukt av Loop, tillater endring av varighet for handling."; /* Body of the alert describing a maximum bolus validation error. (1: The localized max bolus value) */ "The maximum bolus amount is %@ Units" = "Maks bolus er satt til %@ enheter"; -/* Instructions on where to find the transmitter ID */ -"The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app." = "Sender ID kan du finne på baksiden av senderen, på siden av eska, og fra meny for innstillinger på avsenderen, eller i mobilappen."; - -/* The description of the switch which toggles retrospective correction effects */ -"This will more aggresively increase or decrease basal delivery when glucose movement doesn't match the carbohydrate and insulin-based model." = "Dette vil øke eller redusere basallevering mer aggressivt når glukosebevegelsen ikke samsvarer med karbohydrat- og insulinbasert modell."; - /* The short unit display string for international units of insulin */ "U" = "E"; +/* The format for the description of a temporary override end date */ +"until %@" = "frem til %@"; + /* The title of the alert controller used to select a duration for workout targets */ "Use Workout Glucose Targets" = "Bruk treningsmodus for glukosemålområde"; diff --git a/Loop/nb.lproj/Main.strings b/Loop/nb.lproj/Main.strings index be7639a085..425eb295da 100644 --- a/Loop/nb.lproj/Main.strings +++ b/Loop/nb.lproj/Main.strings @@ -25,9 +25,6 @@ /* Class = "UINavigationItem"; title = "Bolus"; ObjectID = "aiu-ZA-zVa"; */ "aiu-ZA-zVa.title" = "Bolus"; -/* Class = "UILabel"; text = "Title"; ObjectID = "B9F-K3-7RI"; */ -"B9F-K3-7RI.text" = "Tittel"; - /* Class = "UILabel"; text = "Label"; ObjectID = "bIL-Ub-qYp"; */ "bIL-Ub-qYp.text" = "Etikett"; diff --git a/Loop/nl.lproj/InfoPlist.strings b/Loop/nl.lproj/InfoPlist.strings index c88a46adf6..f8b78ef480 100644 --- a/Loop/nl.lproj/InfoPlist.strings +++ b/Loop/nl.lproj/InfoPlist.strings @@ -1,9 +1,9 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "Loop"; - /* Bundle name */ "CFBundleName" = "$(PRODUCT_NAME)"; +/* Privacy - Bluetooth Always Usage Description */ +"NSBluetoothAlwaysUsageDescription" = "Bluetooth wordt gebruikt om te communiceren met de insuline pomp en de continue glucose meter."; + /* Privacy - Bluetooth Peripheral Usage Description */ "NSBluetoothPeripheralUsageDescription" = "Bluetooth wordt gebruikt om te communiceren met de insuline pomp en de continue glucose meter."; @@ -11,7 +11,7 @@ "NSFaceIDUsageDescription" = "Face ID wordt gebruikt om de insuline bolus te verifiëren."; /* Privacy - Health Share Usage Description */ -"NSHealthShareUsageDescription" = "Voedsel informatie van de Gezondheid database wordt gebruikt om de glucose impact te bepalen. Glucose data van de Gezondheid database wordt gebruikt voor de tijdslijn en momentberekening"; +"NSHealthShareUsageDescription" = "Maaltijd data van de Health database wordt gebruikt om de effecten op glucose te bepalen.Glucose data vanuit de Health database worden gebruikt voor het berekenen en weergeven van het momentum "; /* Privacy - Health Update Usage Description */ "NSHealthUpdateUsageDescription" = "Koolhydraten van de maaltijd die worden ingevoerd in de app op de apple watch worden opgeslagen in de Gezondheid database. Glucose gegevens ontvangen van de CGM wordt veilig opgeslagen in Gezondheid."; diff --git a/Loop/nl.lproj/LaunchScreen.strings b/Loop/nl.lproj/LaunchScreen.strings deleted file mode 100644 index 8b13789179..0000000000 --- a/Loop/nl.lproj/LaunchScreen.strings +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Loop/nl.lproj/Localizable.strings b/Loop/nl.lproj/Localizable.strings index 3335a8b669..fa8379559b 100644 --- a/Loop/nl.lproj/Localizable.strings +++ b/Loop/nl.lproj/Localizable.strings @@ -1,10 +1,22 @@ -/* Created with Localizable Strings Merge, the swiss-army knife for iOS and OS X localization. */ - /* The string format appended to active insulin that describes pending insulin. (1: pending insulin) */ " (pending: %@)" = " (Wachten: %@)"; -/* Format string for carb ratio average. (1: value)(2: carb unit) - Format string for insulin sensitivity average (1: value)(2: glucose unit) */ +/* The format for an active override preset. (1: preset symbol)(2: preset name) */ +"%@ %@" = "%1$@ %2$@"; + +/* Formats absorbed carb value */ +"%@ absorbed" = "%@ opgenomen"; + +/* The subtitle format describing total insulin. (1: localized insulin total) */ +"%@ U Total" = "%@ E totaal"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ +"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; + +/* Format string for carb ratio average. (1: value)(2: carb unit) */ "%1$@ %2$@/U" = "%1$@ %2$@/E"; /* Formats (1: carb start time) and (2: carb absorption duration) */ @@ -22,24 +34,9 @@ /* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ "%1$@ v%2$@" = "%1$@ v%2$@"; -/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ -"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; - -/* Format string describing glucose units per minute (1: glucose unit string) */ -"%1$@/min" = "%1$@/min"; - /* Formats (1: carb value) and (2: food type) */ "%1$@: %2$@" = "%1$@: %2$@"; -/* Formats absorbed carb value */ -"%@ absorbed" = "%@ opgenomen"; - -/* The subtitle format describing total insulin. (1: localized insulin total) */ -"%@ U Total" = "%@ E totaal"; - -/* Appends a full-stop to a statement */ -"%@." = "%@."; - /* Description of the prediction input effect for glucose momentum */ "15 min glucose regression coefficient (b₁), continued with decay over 30 min" = "15 minuten glucose regressie coëficient (b1), continue zonder afbouw over 30 min"; @@ -67,15 +64,20 @@ /* The string format describing active insulin. (1: localized insulin value description) */ "Active Insulin: %@" = "Actieve insuline: %@"; +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Voeg koolhydraten toe"; + +/* Action sheet title selecting CGM + Title text for button to set up a CGM */ +"Add CGM" = "Voeg CGM toe"; + /* The label of the carb entry button */ "Add Meal" = "Voeg maaltijd toe"; -/* Title text for button to set up a new pump */ +/* Action sheet title selecting Pump + Title text for button to set up a new pump */ "Add Pump" = "Voeg pomp toe"; -/* The title of the section containing algorithm settings */ -"Algorithm Settings" = "Algoritme instellingen"; - /* The title of the Amplitude service */ "Amplitude" = "Amplitude"; @@ -88,6 +90,9 @@ /* The title of the nightscout API secret credential */ "API Secret" = "API Secret"; +/* Confirmation message for deleting a CGM */ +"Are you sure you want to delete this CGM?" = "Weet je zeker dat je de CGM wilt verwijderen"; + /* Format fragment for a specific time */ "at %@" = "bij %@"; @@ -105,9 +110,18 @@ The notification title for a bolus failure */ "Bolus" = "Bolus"; +/* The format string for bolus progress. (1: delivered volume)(2: total volume) */ +"Bolused %1$@ of %2$@" = "Bolused %1$@ van %2$@"; + +/* The format string for bolus in progress showing total volume. (1: total volume) */ +"Bolusing %1$@" = "Geef bolus %1$@"; + /* The title of the cancel action in an action sheet */ "Cancel" = "Annuleer"; +/* The title of the cell indicating a bolus is being canceled */ +"Canceling Bolus" = "Annuleer Bolus"; + /* Details for missing data error when carb effects are missing */ "Carb effects" = "Koolhydraten impact"; @@ -139,6 +153,9 @@ /* The title text for the looping enabled switch cell */ "Closed Loop" = "Gesloten loop"; +/* The title of the action used to dismiss an error alert */ +"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; + /* The title of the configuration section in settings */ "Configuration" = "Configuratie"; @@ -155,14 +172,14 @@ /* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ "Current glucose of %1$@ is below correction range." = "Huidige glucose van %1$@ is lager dan het correctie bereik"; +/* The title of the cell indicating a generic temporary override is enabled */ +"Custom Override" = "Aangepast programma"; + /* The title of the Loggly customer token credential */ "Customer Token" = "Gebruiker token"; -/* The short unit display string for decibles */ -"dB" = "dB"; - -/* The title of the button to remove the credentials for a service */ -"Delete Account" = "Verwijder account"; +/* Button title to delete CGM */ +"Delete CGM" = "Verwijder CGM"; /* Title text for delivery limits */ "Delivery Limits" = "Toediening limieten"; @@ -170,17 +187,17 @@ /* The action hint of the workout mode toggle button when enabled */ "Disables" = "Uitschakelen"; -/* Title of the switch which toggles retrospective correction effects */ -"Enable Retrospective Correction" = "Schakel ‘correctie achteraf’ in"; - /* The action hint of the workout mode toggle button when disabled */ "Enables" = "Inschakelen"; /* The placeholder text instructing users to enter a suspend treshold */ "Enter suspend threshold" = "Voer drempel voor onderbreking insuline in"; -/* The placeholder text instructing users how to enter a transmitter ID */ -"Enter the 6-digit transmitter ID" = "Voer het 6 cijferige zender ID in"; +/* The alert title for an error while canceling a bolus */ +"Error Canceling Bolus" = "Fout bij annuleren van bolus"; + +/* The alert title for a resume error */ +"Error Resuming" = "Fout bij vervolgen"; /* The subtitle format describing eventual glucose. (1: localized glucose value description) */ "Eventually %@" = "Uiteindelijk %@"; @@ -194,15 +211,6 @@ /* The format string used to describe a finite workout targets duration */ "For %1$@" = "Voor %1$@"; -/* The short unit display string for grams */ -"g" = "gr"; - -/* The title text for the G4 Share Receiver switch cell */ -"G4 Share Receiver" = "G4 Share ontvanger"; - -/* The title text for the G5/G6 Transmitter switch cell */ -"G5/G6 Transmitter" = "G5/G6 zender"; - /* The title of the glucose and prediction graph */ "Glucose" = "Glucose"; @@ -216,7 +224,7 @@ "Glucose Momentum" = "Glucose momentum"; /* The placeholder text for the nightscout site URL credential */ -"https://mysite.herokuapp.com" = "https://mijnsite.herokuapp.com"; +"https://mysite.herokuapp.com" = "https://mysite.herokuapp.com"; /* The title of a target alert action specifying an indefinitely long workout targets duration */ "Indefinitely" = "Oneindig"; @@ -262,27 +270,21 @@ /* The notification alert describing a long-lasting loop failure. The substitution parameter is the time interval since the last loop */ "Loop has not completed successfully in %@" = "Loop is niet goed afgerond in %@"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; - /* The error message for missing data. (1: missing data details) */ "Missing data: %1$@" = "Missende gegevens: %1$@"; -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; - /* Details for missing data error when momentum effects are missing */ "Momentum effects" = "Momentum impact"; -/* Sensor state description for the non-valid state */ -"Needs Attention" = "Aandacht vereist"; - /* The title of the Nightscout service */ "Nightscout" = "Nightscout"; /* The error message displayed for device connection errors. */ "No connected devices, or failure during device connection" = "Geen gekoppelde apparaten, of fout bij apparaat verbinding"; +/* The title text for the override presets */ +"Override Presets" = "Aangepast programma’s"; + /* The label of the pre-meal mode toggle button */ "Pre-Meal Targets" = "Voor de maaltijd doelen"; @@ -292,6 +294,9 @@ /* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ "Predicted glucose of %1$@ is below your suspend threshold setting." = "Verwachte glucose van %1$@ is lager dan de onderbroken insuline drempel instelling."; +/* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ +"Predicted: %1$@\nActual: %2$@ (%3$@)" = "Voorspeld: %1$@\nActueel: %2$@ (%3$@)"; + /* The title of the pump section in settings */ "Pump" = "Pomp"; @@ -310,6 +315,9 @@ /* The notification title for a low pump reservoir */ "Pump Reservoir Low" = "Pomp reservoir bijna leeg"; +/* The title of the cell indicating the pump is suspended */ +"Pump Suspended" = "Pomp tijdelijk uitgeschakeld"; + /* Title of insulin model preset */ "Rapid-Acting – Adults" = "Snelwerkende - volwassenen"; @@ -349,8 +357,8 @@ /* The title of the nightscout site URL credential */ "Site URL" = "Webpagina URL"; -/* The title text for the Medtronic sensor switch cell */ -"Sof-Sensor / Enlite" = "Sof-Sensor / Enlite"; +/* The format for the description of a temporary override start date */ +"starting at %@" = "start op %@"; /* The title of the cell indicating a bolus is being sent */ "Starting Bolus" = "Start bolus"; @@ -358,21 +366,21 @@ /* The title text in settings */ "Suspend Threshold" = "Onderbrekingsdrempel"; +/* The subtitle of the cell displaying an action to resume insulin delivery */ +"Tap to Resume" = "Klik om te herstarten"; + /* Subtitle description of Walsh insulin model setting */ "The legacy model used by Loop, allowing customization of action duration." = "Het oude model dat door Loop wordt gebruikt, waardoor de actieduur kan worden aangepast."; /* Body of the alert describing a maximum bolus validation error. (1: The localized max bolus value) */ "The maximum bolus amount is %@ Units" = "De maximale bolushoeveelheid is %@ Eenheden"; -/* Instructions on where to find the transmitter ID */ -"The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app." = "De zender-ID staat afgedrukt op de achterkant van het apparaat, aan de zijkant van de doos waarin deze is geplaatst en in de instellingenmenu's van de G5/G6-ontvanger en de mobiele app."; - -/* The description of the switch which toggles retrospective correction effects */ -"This will more aggresively increase or decrease basal delivery when glucose movement doesn't match the carbohydrate and insulin-based model." = "Dit zal agressiever de basale toediening verhogen of verlagen wanneer de glucosebeweging niet overeenkomt met het koolhydraat- en insuline-gebaseerde model."; - /* The short unit display string for international units of insulin */ "U" = "E"; +/* The format for the description of a temporary override end date */ +"until %@" = "tot %@"; + /* The title of the alert controller used to select a duration for workout targets */ "Use Workout Glucose Targets" = "Gebruik training glucose doelen"; diff --git a/Loop/nl.lproj/Main.strings b/Loop/nl.lproj/Main.strings index 4f3d11ccde..37d3ec72e6 100644 --- a/Loop/nl.lproj/Main.strings +++ b/Loop/nl.lproj/Main.strings @@ -25,9 +25,6 @@ /* Class = "UINavigationItem"; title = "Bolus"; ObjectID = "aiu-ZA-zVa"; */ "aiu-ZA-zVa.title" = "Bolus"; -/* Class = "UILabel"; text = "Title"; ObjectID = "B9F-K3-7RI"; */ -"B9F-K3-7RI.text" = "Titel"; - /* Class = "UILabel"; text = "Label"; ObjectID = "bIL-Ub-qYp"; */ "bIL-Ub-qYp.text" = "Etiket"; diff --git a/Loop/pl.lproj/InfoPlist.strings b/Loop/pl.lproj/InfoPlist.strings index 83e8caa8d8..d71717c5f8 100644 --- a/Loop/pl.lproj/InfoPlist.strings +++ b/Loop/pl.lproj/InfoPlist.strings @@ -1,9 +1,9 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "Loop"; - /* Bundle name */ "CFBundleName" = "$(PRODUCT_NAME)"; +/* Privacy - Bluetooth Always Usage Description */ +"NSBluetoothAlwaysUsageDescription" = "Bluetooth jest używany do komunikacji z pompą i urządzeniami ciągłego monitoringu glukozy."; + /* Privacy - Bluetooth Peripheral Usage Description */ "NSBluetoothPeripheralUsageDescription" = "Bluetooth jest używany do komunikacji z pompą i urządzeniami ciągłego monitoringu glukozy."; diff --git a/Loop/pl.lproj/LaunchScreen.strings b/Loop/pl.lproj/LaunchScreen.strings deleted file mode 100644 index 8b13789179..0000000000 --- a/Loop/pl.lproj/LaunchScreen.strings +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Loop/pl.lproj/Localizable.strings b/Loop/pl.lproj/Localizable.strings index 567f2aa863..4eb845782a 100644 --- a/Loop/pl.lproj/Localizable.strings +++ b/Loop/pl.lproj/Localizable.strings @@ -1,10 +1,7 @@ -/* Created with Localizable Strings Merge, the swiss-army knife for iOS and OS X localization. */ - /* The string format appended to active insulin that describes pending insulin. (1: pending insulin) */ -" (pending: %@)" = "(oczekujące: %@)"; +" (pending: %@)" = " (oczekujące: %@)"; -/* Format string for carb ratio average. (1: value)(2: carb unit) - Format string for insulin sensitivity average (1: value)(2: glucose unit) */ +/* Format string for carb ratio average. (1: value)(2: carb unit) */ "%1$@ %2$@/U" = "%1$@ %2$@/J"; /* Formats (1: carb start time) and (2: carb absorption duration) */ @@ -25,12 +22,12 @@ /* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ "%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; -/* Format string describing glucose units per minute (1: glucose unit string) */ -"%1$@/min" = "%1$@/min"; - /* Formats (1: carb value) and (2: food type) */ "%1$@: %2$@" = "%1$@: %2$@"; +/* The format for an active override preset. (1: preset symbol)(2: preset name) */ +"%@ %@" = "%1$@ %2$@"; + /* Formats absorbed carb value */ "%@ absorbed" = "%@ zaabsorbowane"; @@ -47,10 +44,10 @@ "30 min comparison of glucose prediction vs actual, continued with decay over 60 min" = "30 minutowe porównanie przewidywania stężenia glukozy w stosunku do rzeczywistego, kontynuowane z rozkładem przez 60 min."; /* Subtitle of Fiasp preset */ -"A model based on the published absorption of Fiasp insulin." = "Model oparty na opublikowanej absorpcji insulin Humalog, Novolog/Novorapid i Apidra u dorosłych."; +"A model based on the published absorption of Fiasp insulin." = "Model oparty na opublikowanej absorpcji insulin Fiasp."; /* Subtitle of Rapid-Acting – Adult preset */ -"A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults." = "A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults."; +"A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults." = "Model oparty na opublikowanej absorpcji insulin Humalog, Novolog/Novorapid i Apidra u dorosłych."; /* Action to copy the recommended Bolus value to the actual Bolus Field */ "AcceptRecommendedBolus" = "AcceptRecommendedBolus"; @@ -67,15 +64,19 @@ /* The string format describing active insulin. (1: localized insulin value description) */ "Active Insulin: %@" = "Aktywna Insulina: %@"; +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Dodaj pozycję dla węglowodanów"; + +/* Action sheet title selecting CGM + Title text for button to set up a CGM */ +"Add CGM" = "Dodaj CGM"; + /* The label of the carb entry button */ "Add Meal" = "Dodaj posiłek"; /* Title text for button to set up a new pump */ "Add Pump" = "Dodaj pompę"; -/* The title of the section containing algorithm settings */ -"Algorithm Settings" = "Ustawienia algorytmu"; - /* The title of the Amplitude service */ "Amplitude" = "Amplitude"; @@ -88,6 +89,9 @@ /* The title of the nightscout API secret credential */ "API Secret" = "API Secret"; +/* Confirmation message for deleting a CGM */ +"Are you sure you want to delete this CGM?" = "Czy na pewno chcesz usunąć ten CGM?"; + /* Format fragment for a specific time */ "at %@" = "o %@"; @@ -103,11 +107,20 @@ /* The label of the bolus entry button The notification title for a bolus failure */ -"Bolus" = "Bolus"; +"Bolus" = "Bolusa"; + +/* The format string for bolus progress. (1: delivered volume)(2: total volume) */ +"Bolused %1$@ of %2$@" = "Zbolusowano %1$@ of %2$@"; + +/* The format string for bolus in progress showing total volume. (1: total volume) */ +"Bolusing %1$@" = "Bolusowanie %1$@"; /* The title of the cancel action in an action sheet */ "Cancel" = "Anuluj"; +/* The title of the cell indicating a bolus is being canceled */ +"Canceling Bolus" = "Anulowanie bolusa"; + /* Details for missing data error when carb effects are missing */ "Carb effects" = "wpływ węglowodanów"; @@ -139,6 +152,9 @@ /* The title text for the looping enabled switch cell */ "Closed Loop" = "Zamknięta Loop"; +/* The title of the action used to dismiss an error alert */ +"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; + /* The title of the configuration section in settings */ "Configuration" = "Konfiguracja"; @@ -155,14 +171,14 @@ /* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ "Current glucose of %1$@ is below correction range." = "Poziom glukozy %1$@ jest poniżej wartości korekcji."; +/* The title of the cell indicating a generic temporary override is enabled */ +"Custom Override" = "Pominięcie niestandardowe"; + /* The title of the Loggly customer token credential */ "Customer Token" = "Token użytkownika"; -/* The short unit display string for decibles */ -"dB" = "dB"; - -/* The title of the button to remove the credentials for a service */ -"Delete Account" = "Usuń konto"; +/* Button title to delete CGM */ +"Delete CGM" = "Usuń CGM"; /* Title text for delivery limits */ "Delivery Limits" = "Limit podaży"; @@ -170,17 +186,17 @@ /* The action hint of the workout mode toggle button when enabled */ "Disables" = "Wyłącza"; -/* Title of the switch which toggles retrospective correction effects */ -"Enable Retrospective Correction" = "Włącz retrospektywną korektę"; - /* The action hint of the workout mode toggle button when disabled */ "Enables" = "Włącza"; /* The placeholder text instructing users to enter a suspend treshold */ "Enter suspend threshold" = "Wprowadź próg zawieszenia pompy"; -/* The placeholder text instructing users how to enter a transmitter ID */ -"Enter the 6-digit transmitter ID" = "Wprowadź 6-cyfrowy ID transmitera"; +/* The alert title for an error while canceling a bolus */ +"Error Canceling Bolus" = "Błąd anulowania bolusa"; + +/* The alert title for a resume error */ +"Error Resuming" = "Błąd wznawiania"; /* The subtitle format describing eventual glucose. (1: localized glucose value description) */ "Eventually %@" = "docelowo %@"; @@ -194,15 +210,6 @@ /* The format string used to describe a finite workout targets duration */ "For %1$@" = "Do %1$@"; -/* The short unit display string for grams */ -"g" = "g"; - -/* The title text for the G4 Share Receiver switch cell */ -"G4 Share Receiver" = "Odbiornik G4 Share"; - -/* The title text for the G5/G6 Transmitter switch cell */ -"G5/G6 Transmitter" = "Transmiter G5/G6"; - /* The title of the glucose and prediction graph */ "Glucose" = "Glukoza"; @@ -262,27 +269,21 @@ /* The notification alert describing a long-lasting loop failure. The substitution parameter is the time interval since the last loop */ "Loop has not completed successfully in %@" = "Loop nie działał poprawnie przez %@"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; - /* The error message for missing data. (1: missing data details) */ "Missing data: %1$@" = "Brakujące dane: %1$@"; -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; - /* Details for missing data error when momentum effects are missing */ "Momentum effects" = "wpływ pędu"; -/* Sensor state description for the non-valid state */ -"Needs Attention" = "Potrzebuje uwagi"; - /* The title of the Nightscout service */ "Nightscout" = "Nightscout"; /* The error message displayed for device connection errors. */ "No connected devices, or failure during device connection" = "Brak podłączonych urządzeń lub awaria podczas połączenia urządzenia"; +/* The title text for the override presets */ +"Override Presets" = "Wstępne ustawienia pominięcia"; + /* The label of the pre-meal mode toggle button */ "Pre-Meal Targets" = "Poziom przed posiłkiem"; @@ -292,6 +293,9 @@ /* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ "Predicted glucose of %1$@ is below your suspend threshold setting." = "Przewidywany poziom cukru %1$@ jest poniżej progu zawieszenia."; +/* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ +"Predicted: %1$@\nActual: %2$@ (%3$@)" = "Przewidywana: %1$@\Rzeczywista: %2$@ (%3$@)"; + /* The title of the pump section in settings */ "Pump" = "Pompa"; @@ -310,6 +314,9 @@ /* The notification title for a low pump reservoir */ "Pump Reservoir Low" = "Niski stan zbiornika w pompie"; +/* The title of the cell indicating the pump is suspended */ +"Pump Suspended" = "Pump Suspended"; + /* Title of insulin model preset */ "Rapid-Acting – Adults" = "Szybko-działająca - Dorośli"; @@ -349,8 +356,8 @@ /* The title of the nightscout site URL credential */ "Site URL" = "URL strony"; -/* The title text for the Medtronic sensor switch cell */ -"Sof-Sensor / Enlite" = "Sof-Sensor / Enlite"; +/* The format for the description of a temporary override start date */ +"starting at %@" = "starting at %@"; /* The title of the cell indicating a bolus is being sent */ "Starting Bolus" = "Rozpoczynam podawanie bolusa"; @@ -358,21 +365,21 @@ /* The title text in settings */ "Suspend Threshold" = "Próg zawieszenia pompy"; +/* The subtitle of the cell displaying an action to resume insulin delivery */ +"Tap to Resume" = "Dotknij, aby wznowić"; + /* Subtitle description of Walsh insulin model setting */ "The legacy model used by Loop, allowing customization of action duration." = "Model umożliwiający dostosowanie czasu działania insuliny."; /* Body of the alert describing a maximum bolus validation error. (1: The localized max bolus value) */ "The maximum bolus amount is %@ Units" = "Maksymalny bolus wynosi %@ jednostek"; -/* Instructions on where to find the transmitter ID */ -"The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app." = "ID transmitera znajduje się na odwrocie urządzenia, na boku pudełka, w którym został dostarczony oraz z poziomu menu ustawień odbiornika lub aplikacji mobilnej."; - -/* The description of the switch which toggles retrospective correction effects */ -"This will more aggresively increase or decrease basal delivery when glucose movement doesn't match the carbohydrate and insulin-based model." = "Spowoduje to bardziej agresywny wzrost lub spadek w podaży dawki podstawowej, gdy zmiany poziomu glukozy nie odpowiadają zastosowanym modelom absorpcji węglowodanów i aktywności insuliny."; - /* The short unit display string for international units of insulin */ "U" = "J"; +/* The format for the description of a temporary override end date */ +"until %@" = "do %@"; + /* The title of the alert controller used to select a duration for workout targets */ "Use Workout Glucose Targets" = "Użyj zakresu glukozy dla wysiłku fizycznego"; diff --git a/Loop/pl.lproj/Main.strings b/Loop/pl.lproj/Main.strings index f59e397a5d..8c2b800fca 100644 --- a/Loop/pl.lproj/Main.strings +++ b/Loop/pl.lproj/Main.strings @@ -78,7 +78,7 @@ "aCb-Qs-bpu.text" = "Szczegóły"; /* Class = "UINavigationItem"; title = "Bolus"; ObjectID = "aiu-ZA-zVa"; */ -"aiu-ZA-zVa.title" = "Bolus"; +"aiu-ZA-zVa.title" = "Bolusa"; /* Class = "UILabel"; text = "Label"; ObjectID = "bIL-Ub-qYp"; */ "bIL-Ub-qYp.text" = "Etykieta"; @@ -117,7 +117,7 @@ "m9c-SQ-djE.text" = "Kliknij, aby ustawić"; /* Class = "UILabel"; accessibilityLabel = "Units"; ObjectID = "mVz-dr-xLU"; */ -"mVz-dr-xLU.accessibilityLabel" = "Units"; +"mVz-dr-xLU.accessibilityLabel" = "Jednostki"; /* Class = "UILabel"; text = "U"; ObjectID = "mVz-dr-xLU"; */ "mVz-dr-xLU.text" = "J"; diff --git a/Loop/pt-BR.lproj/InfoPlist.strings b/Loop/pt-BR.lproj/InfoPlist.strings new file mode 100644 index 0000000000..96a0de7f0b --- /dev/null +++ b/Loop/pt-BR.lproj/InfoPlist.strings @@ -0,0 +1,18 @@ +/* Bundle name */ +"CFBundleName" = "$(PRODUCT_NAME)"; + +/* Privacy - Bluetooth Always Usage Description */ +"NSBluetoothAlwaysUsageDescription" = "Bluetooth é utilizado para comunicação com a bomba de insulina e os dispositivos de monitoramento de glicose."; + +/* Privacy - Bluetooth Peripheral Usage Description */ +"NSBluetoothPeripheralUsageDescription" = "Bluetooth é utilizado para comunicação com a bomba de insulina e os dispositivos de monitoramento de glicose."; + +/* Privacy - Face ID Usage Description */ +"NSFaceIDUsageDescription" = "Face ID é utilizado para autenticar o bolus de insulina."; + +/* Privacy - Health Share Usage Description */ +"NSHealthShareUsageDescription" = "Os dados de refeições do banco de dados de saúde são utilizados para definir os efeitos da glicose para a representação gráfica e cálculo da aceleração."; + +/* Privacy - Health Update Usage Description */ +"NSHealthUpdateUsageDescription" = "Dados de carboidratos inseridos no aplicativo e no Apple Watch são armazenados no banco de dados de saúde. Dados de glicemia recebidos do CGM são armazenados de modo seguro no HealthKit."; + diff --git a/Loop/pt-BR.lproj/Localizable.strings b/Loop/pt-BR.lproj/Localizable.strings new file mode 100644 index 0000000000..1b620804c6 --- /dev/null +++ b/Loop/pt-BR.lproj/Localizable.strings @@ -0,0 +1,395 @@ +/* The string format appended to active insulin that describes pending insulin. (1: pending insulin) */ +" (pending: %@)" = " (pendente: %@)"; + +/* The format for an active override preset. (1: preset symbol)(2: preset name) */ +"%@ %@" = "%1$@ %2$@"; + +/* Formats absorbed carb value */ +"%@ absorbed" = "%@ absorvida"; + +/* The subtitle format describing total insulin. (1: localized insulin total) */ +"%@ U Total" = "%@ U Total"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ +"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; + +/* Format string for carb ratio average. (1: value)(2: carb unit) */ +"%1$@ %2$@/U" = "%1$@ %2$@/U"; + +/* Formats (1: carb start time) and (2: carb absorption duration) */ +"%1$@ + %2$@" = "%1$@ + %2$@"; + +/* Low reservoir alert format string. (1: Number of units remaining) */ +"%1$@ U left" = "%1$@ U restante"; + +/* Low reservoir alert with time remaining format string. (1: Number of units remaining)(2: approximate time remaining) */ +"%1$@ U left: %2$@" = "%1$@ U restante: %2$@"; + +/* The format for recommended temp basal rate and time. (1: localized rate number)(2: localized time) */ +"%1$@ U/hour @ %2$@" = "%1$@ U/hora @ %2$@"; + +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Formats (1: carb value) and (2: food type) */ +"%1$@: %2$@" = "%1$@: %2$@"; + +/* Description of the prediction input effect for glucose momentum */ +"15 min glucose regression coefficient (b₁), continued with decay over 30 min" = "15 min coeficiente de regressão de glicose (b₁), continuada com queda em 30 min."; + +/* Description of the prediction input effect for retrospective correction */ +"30 min comparison of glucose prediction vs actual, continued with decay over 60 min" = "30 min de comparação da previsão de glicose vs atual, continuada com queda em 60 min."; + +/* Subtitle of Fiasp preset */ +"A model based on the published absorption of Fiasp insulin." = "Um modelo baseado na absorção publicada da insulina Fiasp."; + +/* Subtitle of Rapid-Acting – Adult preset */ +"A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults." = "Um modelo baseado na absorção publicada das insulinas Humalog, Novolog e Apidra em adultos."; + +/* Action to copy the recommended Bolus value to the actual Bolus Field */ +"AcceptRecommendedBolus" = "AceitarBolusRecomendado"; + +/* The title of the Carbs On-Board graph */ +"Active Carbohydrates" = "Carboidratos Ativos"; + +/* The string format describing active carbohydrates. (1: localized glucose value description) */ +"Active Carbohydrates: %@" = "Carboidratos Ativos: %@"; + +/* The title of the Insulin On-Board graph */ +"Active Insulin" = "Insulina Ativa"; + +/* The string format describing active insulin. (1: localized insulin value description) */ +"Active Insulin: %@" = "Insulina Ativa: %@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Adicionar Carb"; + +/* Action sheet title selecting CGM + Title text for button to set up a CGM */ +"Add CGM" = "Adicionar CGM"; + +/* The label of the carb entry button */ +"Add Meal" = "Adicionar Refeição"; + +/* Action sheet title selecting Pump + Title text for button to set up a new pump */ +"Add Pump" = "Adicionar Bomba"; + +/* The title of the Amplitude service */ +"Amplitude" = "Amplitude"; + +/* Subtitle of Rapid-Acting – Children preset */ +"An adjustment to the adult model based on empirical effects in children." = "Um ajuste do modelo para adultos baseado nos efeitos empíricos em crianças."; + +/* The title of the amplitude API key credential */ +"API Key" = "API Key"; + +/* The title of the nightscout API secret credential */ +"API Secret" = "API Secret"; + +/* Confirmation message for deleting a CGM */ +"Are you sure you want to delete this CGM?" = "Você está certo que quer remover este CGM?"; + +/* Format fragment for a specific time */ +"at %@" = "em %@"; + +/* The message displayed during a device authentication prompt for bolus specification */ +"Authenticate to Bolus %@ Units" = "Autentique para Bolus de %@ Unidades"; + +/* Details for configuration error when basal rate schedule is missing */ +"Basal Rate Schedule" = "Programação da Taxa Basal"; + +/* The title of the basal rate profile screen + The title text for the basal rate schedule */ +"Basal Rates" = "Taxa Basal"; + +/* The label of the bolus entry button + The notification title for a bolus failure */ +"Bolus" = "Bolus"; + +/* The format string for bolus progress. (1: delivered volume)(2: total volume) */ +"Bolused %1$@ of %2$@" = "Entregue %1$@ of %2$@"; + +/* The format string for bolus in progress showing total volume. (1: total volume) */ +"Bolusing %1$@" = "Aplicando %1$@"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Cancelar"; + +/* The title of the cell indicating a bolus is being canceled */ +"Canceling Bolus" = "Cancelando Bolus"; + +/* Details for missing data error when carb effects are missing */ +"Carb effects" = "Efeitos Carb"; + +/* The title of the carb ratios schedule screen + The title text for the carb ratio schedule */ +"Carb Ratios" = "Relação Carb"; + +/* Title of the prediction input effect for carbohydrates */ +"Carbohydrates" = "Carboidratos"; + +/* Description of the prediction input effect for carbohydrates. (1: The glucose unit string) */ +"Carbs Absorbed (g) ÷ Carb Ratio (g/U) × Insulin Sensitivity (%1$@/U)" = "Carbs Absorvidos (g) ÷ Relação Carb (g/U) × Sensibilidade a Insulina (%1$@/U)"; + +/* The notification alert describing a low pump battery */ +"Change the pump battery immediately" = "Troque a bateria da bomba imediatamente"; + +/* The notification alert describing an empty pump reservoir */ +"Change the pump reservoir now" = "Troque o reservatório da bomba agora"; + +/* Details for configuration error when one or more loop settings are missing */ +"Check settings" = "Verifique as configurações"; + +/* Recovery suggestion when reservoir data is missing */ +"Check that your pump is in range" = "Verifique se sua bomba está dentro do alcance"; + +/* Recovery suggestion when glucose data is missing */ +"Check your CGM data source" = "Verifique seu dispositivo CGM"; + +/* The title text for the looping enabled switch cell */ +"Closed Loop" = "Ciclo Fechado"; + +/* The title of the action used to dismiss an error alert */ +"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; + +/* The title of the configuration section in settings */ +"Configuration" = "Configurações"; + +/* The error message displayed for configuration errors. (1: configuration error details) */ +"Configuration Error: %1$@" = "Erro de Configuração: %1$@"; + +/* The title of the continuous glucose monitor section in settings */ +"Continuous Glucose Monitor" = "Monitoramento Contínuo de Glicose"; + +/* The title of the glucose target range schedule screen + The title text for the glucose target range schedule */ +"Correction Range" = "Zona de correção"; + +/* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ +"Current glucose of %1$@ is below correction range." = " Glicemia atual %1$@ está abaixo da zona de correção."; + +/* The title of the cell indicating a generic temporary override is enabled */ +"Custom Override" = "Sobreposições"; + +/* The title of the Loggly customer token credential */ +"Customer Token" = "Token do Cliente"; + +/* Button title to delete CGM */ +"Delete CGM" = "Remover CGM"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Limites de entrega"; + +/* The action hint of the workout mode toggle button when enabled */ +"Disables" = "Desativa"; + +/* The action hint of the workout mode toggle button when disabled */ +"Enables" = "Ativar"; + +/* The placeholder text instructing users to enter a suspend treshold */ +"Enter suspend threshold" = "Digite o limite de suspensão"; + +/* The alert title for an error while canceling a bolus */ +"Error Canceling Bolus" = "Erro Cancelando Bolus"; + +/* The alert title for a resume error */ +"Error Resuming" = "Erro ao Retomar"; + +/* The subtitle format describing eventual glucose. (1: localized glucose value description) */ +"Eventually %@" = "Eventualmente %@"; + +/* The title of the alert describing a maximum bolus validation error */ +"Exceeds Maximum Bolus" = "Excede o Bolus Máximo"; + +/* Title of insulin model preset */ +"Fiasp" = "Fiasp"; + +/* The format string used to describe a finite workout targets duration */ +"For %1$@" = "Por %1$@"; + +/* The title of the glucose and prediction graph */ +"Glucose" = "Glicose"; + +/* The error message when glucose data is too old to be used. (1: glucose data age in minutes) */ +"Glucose data is %1$@ old" = "Medida de glicose está %1$@ atrasada"; + +/* Description of error when glucose data is missing */ +"Glucose data not available" = "Medida de glicose não disponível"; + +/* Title of the prediction input effect for glucose momentum */ +"Glucose Momentum" = "Aceleração da Glicose"; + +/* The placeholder text for the nightscout site URL credential */ +"https://mysite.herokuapp.com" = "https://mysite.herokuapp.com"; + +/* The title of a target alert action specifying an indefinitely long workout targets duration */ +"Indefinitely" = "Indefinidamente"; + +/* Title of the prediction input effect for insulin */ +"Insulin" = "Insulina"; + +/* Description of the prediction input effect for insulin */ +"Insulin Absorbed (U) × Insulin Sensitivity (%1$@/U)" = "Insulina Absorvida (U) × Sensibilidade a Insulina (%1$@/U)"; + +/* The title of the insulin delivery graph */ +"Insulin Delivery" = "Insulina Entregue"; + +/* Details for missing data error when insulin effects are missing */ +"Insulin effects" = "Efeitos da Insulina"; + +/* Details for configuration error when insulin model is missing + The title text for the insulin model setting row */ +"Insulin Model" = "Modelo de Insulina"; + +/* The title of the insulin sensitivities schedule screen + The title text for the insulin sensitivity schedule */ +"Insulin Sensitivities" = "Sensibilidades a Insulina"; + +/* The error message when invalid data was encountered. (1: details of invalid data) */ +"Invalid data: %1$@" = "Dados inválidos: %1$@"; + +/* The title text for the issue report cell */ +"Issue Report" = "Gerar Relatório"; + +/* Glucose HUD accessibility hint */ +"Launches CGM app" = "Inicia app CGM"; + +/* The loading message for the diagnostic report screen */ +"Loading..." = "Carregando..."; + +/* The title of the loggly service */ +"Loggly" = "Loggly"; + +/* The notification title for a loop failure */ +"Loop Failure" = "Falha no Loop"; + +/* The notification alert describing a long-lasting loop failure. The substitution parameter is the time interval since the last loop */ +"Loop has not completed successfully in %@" = "Nenhum ciclo completo com sucesso em %@"; + +/* The error message for missing data. (1: missing data details) */ +"Missing data: %1$@" = "Dados ausentes: %1$@"; + +/* Details for missing data error when momentum effects are missing */ +"Momentum effects" = "Efeitos de aceleração"; + +/* The title of the Nightscout service */ +"Nightscout" = "Nightscout"; + +/* The error message displayed for device connection errors. */ +"No connected devices, or failure during device connection" = "Nenhum dispositivo conectado ou falha durante a conexão"; + +/* The title text for the override presets */ +"Override Presets" = "Sobreposições Predefinidas"; + +/* The label of the pre-meal mode toggle button */ +"Pre-Meal Targets" = "Meta Pré-Refeição"; + +/* Message when offering bolus recommendation even though bg is below range and minBG is in future. (1: glucose time)(2: glucose number) */ +"Predicted glucose at %1$@ is %2$@." = "Glicemia prevista em %1$@ é %2$@."; + +/* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ +"Predicted glucose of %1$@ is below your suspend threshold setting." = "Glicemia prevista de %1$@ está abaixo do limite de suspensão."; + +/* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ +"Predicted: %1$@\nActual: %2$@ (%3$@)" = "Prevista: %1$@\nAtual: %2$@ (%3$@)"; + +/* The title of the pump section in settings */ +"Pump" = "Bomba"; + +/* The notification title for a low pump battery */ +"Pump Battery Low" = "Batteria da Bomba Fraca"; + +/* The error message when pump data is too old to be used. (1: pump data age in minutes) */ +"Pump data is %1$@ old" = "Dados da bomba são de %1$@ atrás"; + +/* Details for configuration error when pump manager is missing */ +"Pump Manager" = "Gerenciamento da Bomba"; + +/* The notification title for an empty pump reservoir */ +"Pump Reservoir Empty" = "Reservatório da Bomba Vazio"; + +/* The notification title for a low pump reservoir */ +"Pump Reservoir Low" = "Reservatório da Bomba Vazio"; + +/* The title of the cell indicating the pump is suspended */ +"Pump Suspended" = "Bomba Suspensa"; + +/* Title of insulin model preset */ +"Rapid-Acting – Adults" = "Ação-Rápida – Adultos"; + +/* Title of insulin model preset */ +"Rapid-Acting – Children" = "Ação-Rapida – Crianças"; + +/* The error message when a recommendation has expired. (1: age of recommendation in minutes) */ +"Recommendation expired: %1$@ old" = "Recomendação expirou: %1$@ atrás"; + +/* The title of the cell displaying a recommended temp basal value */ +"Recommended Basal" = "Basal Recomendada"; + +/* Accessibility hint describing recommended bolus units */ +"Recommended Bolus: %@ Units" = "Bolus Recomendado: %@ Unidades"; + +/* Details for missing data error when reservoir data is missing */ +"Reservoir" = "Reservatório"; + +/* Title of the prediction input effect for retrospective correction */ +"Retrospective Correction" = "Correção Retrospectiva"; + +/* The title of the notification action to retry a bolus command */ +"Retry" = "Tentar novamente"; + +/* The title of the services section in settings */ +"Services" = "Serviços"; + +/* The label of the settings button */ +"Settings" = "Configurações"; + +/* Loop Completion HUD accessibility hint */ +"Shows last loop error" = "Mostrar último erro do ciclo"; + +/* Format fragment for a start time */ +"since %@" = "desde %@"; + +/* The title of the nightscout site URL credential */ +"Site URL" = "Site URL"; + +/* The format for the description of a temporary override start date */ +"starting at %@" = "iniciando às %@"; + +/* The title of the cell indicating a bolus is being sent */ +"Starting Bolus" = "Iniciando Bolus"; + +/* The title text in settings */ +"Suspend Threshold" = "Limite de Suspenção"; + +/* The subtitle of the cell displaying an action to resume insulin delivery */ +"Tap to Resume" = "Toque para retomar"; + +/* Subtitle description of Walsh insulin model setting */ +"The legacy model used by Loop, allowing customization of action duration." = "O modelo antigo utilizado pelo Loop permitindo personalização da duração da ação."; + +/* Body of the alert describing a maximum bolus validation error. (1: The localized max bolus value) */ +"The maximum bolus amount is %@ Units" = "O bolus máximo é %@ Unidades"; + +/* The short unit display string for international units of insulin */ +"U" = "U"; + +/* The format for the description of a temporary override end date */ +"until %@" = "até %@"; + +/* The title of the alert controller used to select a duration for workout targets */ +"Use Workout Glucose Targets" = "Usar Metas de Glicemia de Exercício"; + +/* Title of insulin model setting */ +"Walsh" = "Walsh"; + +/* Explanation of suspend threshold */ +"When current or forecasted glucose is below the suspend threshold, Loop will not recommend a bolus, and will always recommend a temporary basal rate of 0 units per hour." = "Quando a glicose atual ou prevista estiver abaixo do limite de suspensão, o Loop não recomendará um bolus e sempre recomendará uma taxa basal temporária de 0 unidades por hora"; + +/* The label of the workout mode toggle button */ +"Workout Targets" = "Metas de Exercício"; + diff --git a/Loop/pt-BR.lproj/Main.strings b/Loop/pt-BR.lproj/Main.strings new file mode 100644 index 0000000000..2ba9a13c80 --- /dev/null +++ b/Loop/pt-BR.lproj/Main.strings @@ -0,0 +1,144 @@ +/* Class = "UINavigationItem"; title = "Status"; ObjectID = "3kU-n2-fha"; */ +"3kU-n2-fha.title" = "Status"; + +/* Class = "UILabel"; text = "3.5 U/hour @ 12:12 PM"; ObjectID = "5gz-kZ-iF1"; */ +"5gz-kZ-iF1.text" = "3.5 U/hora @ 12:12 PM"; + +/* Class = "UILabel"; text = "Bolus"; ObjectID = "5oA-6d-ZTL"; */ +"5oA-6d-ZTL.text" = "Bolus"; + +/* Class = "UILabel"; text = "Pump ID"; ObjectID = "5TX-kX-nBo"; */ +"5TX-kX-nBo.text" = " ID da Bomba"; + +/* Class = "UITextField"; accessibilityLabel = "Bolus Amount"; ObjectID = "7LT-50-ZzK"; */ +"7LT-50-ZzK.accessibilityLabel" = "Quantidade de Bolus"; + +/* Class = "UITextField"; placeholder = "0.0"; ObjectID = "7LT-50-ZzK"; */ +"7LT-50-ZzK.placeholder" = "0.0"; + +/* Class = "UILabel"; text = "Predicted"; ObjectID = "87H-N1-0vJ"; */ +"87H-N1-0vJ.text" = "Prevista"; + +/* Class = "UILabel"; text = "Detail"; ObjectID = "aCb-Qs-bpu"; */ +"aCb-Qs-bpu.text" = "Detalhes"; + +/* Class = "UINavigationItem"; title = "Bolus"; ObjectID = "aiu-ZA-zVa"; */ +"aiu-ZA-zVa.title" = "Bolus"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "bIL-Ub-qYp"; */ +"bIL-Ub-qYp.text" = "Rótulo"; + +/* Class = "UILabel"; text = "Glucose Change"; ObjectID = "bq4-98-cQU"; */ +"bq4-98-cQU.text" = "Variação de Glicose"; + +/* Class = "UILabel"; accessibilityLabel = "Units"; ObjectID = "BR0-dr-Fj2"; */ +"BR0-dr-Fj2.accessibilityLabel" = "Unidades"; + +/* Class = "UILabel"; text = "U"; ObjectID = "BR0-dr-Fj2"; */ +"BR0-dr-Fj2.text" = "U"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "cpo-Po-gFM"; */ +"cpo-Po-gFM.text" = "Rótulo"; + +/* Class = "UILabel"; text = "g Total"; ObjectID = "d3X-AN-tA5"; */ +"d3X-AN-tA5.text" = "g Total"; + +/* Class = "UILabel"; text = "Future glucose is predicted by combining the effects of multiple inputs. Use this tool to toggle various inputs to see how they compare to the final prediction."; ObjectID = "D4C-I2-dhA"; */ +"D4C-I2-dhA.text" = "A glicose futura é prevista combinando os efeitos de múltiplas entradas. Use esta ferramenta para alternar várias entradas para ver como elas se comparam à previsão final."; + +/* Class = "UILabel"; text = "Label"; ObjectID = "d6m-qV-wWi"; */ +"d6m-qV-wWi.text" = "Rótulo"; + +/* Class = "UINavigationItem"; title = "Settings"; ObjectID = "dmB-PQ-B44"; */ +"dmB-PQ-B44.title" = "Configurações"; + +/* Class = "UILabel"; text = "DEVICES"; ObjectID = "DyC-Sv-qP8"; */ +"DyC-Sv-qP8.text" = "DISPOSITIVOS"; + +/* Class = "UILabel"; text = "eventually 92 mg/dL"; ObjectID = "E41-FN-nkk"; */ +"E41-FN-nkk.text" = "eventualmente 92 mg/dL"; + +/* Class = "UILabel"; text = "Observed"; ObjectID = "EAn-Ja-S1d"; */ +"EAn-Ja-S1d.text" = "Observada"; + +/* Class = "UILabel"; text = "Active Carbohydrates: 40g"; ObjectID = "hHZ-uY-aKw"; */ +"hHZ-uY-aKw.text" = "Carboidratos Ativos: 40g"; + +/* Class = "UITableViewController"; title = "Carbohydrate Effects"; ObjectID = "hZZ-2S-lrd"; */ +"hZZ-2S-lrd.title" = "Efeitos dos Carboidratos"; + +/* Class = "UILabel"; text = "Observed changes in glucose, subtracting changes modeled from insulin delivery, can be used to estimate carbohydrate absorption."; ObjectID = "IxU-As-glo"; */ +"IxU-As-glo.text" = "Alterações observadas na glicose, subtraindo alterações modeladas da administração de insulina, podem ser usadas para estimar a absorção de carboidratos."; + +/* Class = "UILabel"; text = "Detail"; ObjectID = "J7x-W5-gwo"; */ +"J7x-W5-gwo.text" = "Detalhes"; + +/* Class = "UILabel"; text = "⚠ Glucose Predicted Below Range"; ObjectID = "k0g-P7-OVN"; */ +"k0g-P7-OVN.text" = "⚠ Previsão de glicose abaixo da meta"; + +/* Class = "UILabel"; text = "Recommended Basal"; ObjectID = "k3F-Na-7mn"; */ +"k3F-Na-7mn.text" = "Basal Recomendada"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "Krd-Aa-ret"; */ +"Krd-Aa-ret.text" = "Rótulo"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "l7l-mw-Oc9"; */ +"l7l-mw-Oc9.text" = "Rótulo"; + +/* Class = "UILabel"; text = "Tap to set"; ObjectID = "m9c-SQ-djE"; */ +"m9c-SQ-djE.text" = "toque para definir"; + +/* Class = "UILabel"; accessibilityLabel = "Units"; ObjectID = "mVz-dr-xLU"; */ +"mVz-dr-xLU.accessibilityLabel" = "Unidades"; + +/* Class = "UILabel"; text = "U"; ObjectID = "mVz-dr-xLU"; */ +"mVz-dr-xLU.text" = "U"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "OFA-qT-ZAg"; */ +"OFA-qT-ZAg.text" = "Rótulo"; + +/* Class = "UITableViewController"; title = "Predicted Glucose"; ObjectID = "PA3-sP-cWY"; */ +"PA3-sP-cWY.title" = "Glicose Prevista"; + +/* Class = "UITableViewController"; title = "Insulin Model"; ObjectID = "pi6-Dh-72V"; */ +"pi6-Dh-72V.title" = "Modelo de Insulina"; + +/* Class = "UILabel"; text = "An insulin activity model is used to estimate effects of insulin on glucose levels. An accurate model can help prevent insulin stacking and safely recommend corrective treatments."; ObjectID = "PJv-p9-cFe"; */ +"PJv-p9-cFe.text" = "Um modelo de atividade da insulina é usado para estimar os efeitos da insulina nos níveis de glicose. Um modelo preciso pode ajudar a evitar o acúmulo de insulina e recomendar com segurança tratamentos corretivos."; + +/* Class = "UILabel"; text = "eventually 92 mg/dL"; ObjectID = "Rse-x8-amW"; */ +"Rse-x8-amW.text" = "eventualmente 92 mg/dL"; + +/* Class = "UILabel"; text = "g COB"; ObjectID = "SQx-au-ZcM"; */ +"SQx-au-ZcM.text" = "g COB"; + +/* Class = "UILabel"; text = "Glucose"; ObjectID = "tuw-av-A3x"; */ +"tuw-av-A3x.text" = "Glicose"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "ufi-Kj-33k"; */ +"ufi-Kj-33k.text" = "Rótulo"; + +/* Class = "UILabel"; text = "Active Insulin: 1.5U"; ObjectID = "viU-7a-kbI"; */ +"viU-7a-kbI.text" = "Insulina Ativa: 1.5U"; + +/* Class = "UINavigationItem"; title = "Carbohydrates"; ObjectID = "Vpi-5b-bY5"; */ +"Vpi-5b-bY5.title" = "Carboidratos"; + +/* Class = "UITextField"; text = "4 hour"; ObjectID = "Wk3-xv-IM5"; */ +"Wk3-xv-IM5.text" = "4 horas"; + +/* Class = "UIButton"; normalTitle = "Deliver"; ObjectID = "Ya0-9b-ZAS"; */ +"Ya0-9b-ZAS.normalTitle" = "Entregue"; + +/* Class = "UILabel"; text = "0"; ObjectID = "yn7-2M-jZz"; */ +"yn7-2M-jZz.text" = "0"; + +/* Class = "UILabel"; text = "Recommended"; ObjectID = "ywT-OR-NnU"; */ +"ywT-OR-NnU.text" = "Recomendado"; + +/* Class = "UILabel"; text = "Title"; ObjectID = "zbc-87-wxZ"; */ +"zbc-87-wxZ.text" = "Título"; + +/* Class = "UILabel"; text = "0"; ObjectID = "zvZ-uf-zMX"; */ +"zvZ-uf-zMX.text" = "0"; + diff --git a/Loop/ro.lproj/InfoPlist.strings b/Loop/ro.lproj/InfoPlist.strings new file mode 100644 index 0000000000..cdc998d2e0 --- /dev/null +++ b/Loop/ro.lproj/InfoPlist.strings @@ -0,0 +1,18 @@ +/* Bundle name */ +"CFBundleName" = "$(PRODUCT_NAME)"; + +/* Privacy - Bluetooth Always Usage Description */ +"NSBluetoothAlwaysUsageDescription" = "Bluetooth este folosit pentru a comunica cu pompa de insulină, precum și cu dispozitivele de monitorizare glicemică continuă"; + +/* Privacy - Bluetooth Peripheral Usage Description */ +"NSBluetoothPeripheralUsageDescription" = "Bluetooth este folosit pentru a comunica cu pompa de insulină, precum și cu dispozitivele de monitorizare glicemică continuă."; + +/* Privacy - Face ID Usage Description */ +"NSFaceIDUsageDescription" = "Face ID este folosit la autentificarea pentru bolus."; + +/* Privacy - Health Share Usage Description */ +"NSHealthShareUsageDescription" = "Informațiile despre nutriție din baza de date Sănătate sunt folosite pentru a determina efectele glucozei. Informațiile despre glucoză din baza de date Sănătate sunt folosite pentru construirea de grafice și calcule de trend/momentum."; + +/* Privacy - Health Update Usage Description */ +"NSHealthUpdateUsageDescription" = "Carbohidrații introduși în aplicație și pe ceas sunt stocați în baza de date Sănătate. Glicemiile din GCM sunt stocate în mod confidențial în HealthKit."; + diff --git a/Loop/ro.lproj/Localizable.strings b/Loop/ro.lproj/Localizable.strings new file mode 100644 index 0000000000..18ca58ac8a --- /dev/null +++ b/Loop/ro.lproj/Localizable.strings @@ -0,0 +1,395 @@ +/* The string format appended to active insulin that describes pending insulin. (1: pending insulin) */ +" (pending: %@)" = " (urmează a fi administrate: %@)"; + +/* The format for an active override preset. (1: preset symbol)(2: preset name) */ +"%@ %@" = "%1$@ %2$@"; + +/* Formats absorbed carb value */ +"%@ absorbed" = "%@ absorbiți"; + +/* The subtitle format describing total insulin. (1: localized insulin total) */ +"%@ U Total" = "%@ U total"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ +"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; + +/* Format string for carb ratio average. (1: value)(2: carb unit) */ +"%1$@ %2$@/U" = "%1$@ %2$@/U"; + +/* Formats (1: carb start time) and (2: carb absorption duration) */ +"%1$@ + %2$@" = "%1$@ + %2$@"; + +/* Low reservoir alert format string. (1: Number of units remaining) */ +"%1$@ U left" = "%1$@ U rămase"; + +/* Low reservoir alert with time remaining format string. (1: Number of units remaining)(2: approximate time remaining) */ +"%1$@ U left: %2$@" = "%1$@ U rămase: %2$@"; + +/* The format for recommended temp basal rate and time. (1: localized rate number)(2: localized time) */ +"%1$@ U/hour @ %2$@" = "%1$@ U/oră @ %2$@"; + +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Formats (1: carb value) and (2: food type) */ +"%1$@: %2$@" = "%1$@: %2$@"; + +/* Description of the prediction input effect for glucose momentum */ +"15 min glucose regression coefficient (b₁), continued with decay over 30 min" = "coeficient de regresie glicemică pe 15 min (b₁), continuat cu o diminuare pe 30 min."; + +/* Description of the prediction input effect for retrospective correction */ +"30 min comparison of glucose prediction vs actual, continued with decay over 60 min" = "Comparație pe 30 min a glicemiei anticipate față de cea observată, continuată cu o diminuare pe 60 min."; + +/* Subtitle of Fiasp preset */ +"A model based on the published absorption of Fiasp insulin." = "Un model bazat pe absorbția declarată a insulinei Fiasp."; + +/* Subtitle of Rapid-Acting – Adult preset */ +"A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults." = "Un model bazat pe absorbția declarată a insulinei Humalog, Novolog și Apidra la adulți."; + +/* Action to copy the recommended Bolus value to the actual Bolus Field */ +"AcceptRecommendedBolus" = "AcceptRecommendedBolus"; + +/* The title of the Carbs On-Board graph */ +"Active Carbohydrates" = "Carbohidrați activi"; + +/* The string format describing active carbohydrates. (1: localized glucose value description) */ +"Active Carbohydrates: %@" = "Carbohidrați activi: %@"; + +/* The title of the Insulin On-Board graph */ +"Active Insulin" = "Insulină activă"; + +/* The string format describing active insulin. (1: localized insulin value description) */ +"Active Insulin: %@" = "Insulină activă: %@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Adăugare carbohidrați"; + +/* Action sheet title selecting CGM + Title text for button to set up a CGM */ +"Add CGM" = "Adaugă CGM"; + +/* The label of the carb entry button */ +"Add Meal" = "Adaugă masă"; + +/* Action sheet title selecting Pump + Title text for button to set up a new pump */ +"Add Pump" = "Adaugă pompă"; + +/* The title of the Amplitude service */ +"Amplitude" = "Amplitude"; + +/* Subtitle of Rapid-Acting – Children preset */ +"An adjustment to the adult model based on empirical effects in children." = "O ajustare a modelului de adulți, bazată pe efecte empirice observate la copii."; + +/* The title of the amplitude API key credential */ +"API Key" = "API Key"; + +/* The title of the nightscout API secret credential */ +"API Secret" = "API Secret"; + +/* Confirmation message for deleting a CGM */ +"Are you sure you want to delete this CGM?" = "Sigur doriți să ștergeți acest CGM?"; + +/* Format fragment for a specific time */ +"at %@" = "la %@"; + +/* The message displayed during a device authentication prompt for bolus specification */ +"Authenticate to Bolus %@ Units" = "Autentificare pentru bolus %@ unități"; + +/* Details for configuration error when basal rate schedule is missing */ +"Basal Rate Schedule" = "Orar rate bazale"; + +/* The title of the basal rate profile screen + The title text for the basal rate schedule */ +"Basal Rates" = "Rate bazale"; + +/* The label of the bolus entry button + The notification title for a bolus failure */ +"Bolus" = "Bolus"; + +/* The format string for bolus progress. (1: delivered volume)(2: total volume) */ +"Bolused %1$@ of %2$@" = "Bolus administrat %1$@ of %2$@"; + +/* The format string for bolus in progress showing total volume. (1: total volume) */ +"Bolusing %1$@" = "Bolus în administrare %1$@"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Renunță"; + +/* The title of the cell indicating a bolus is being canceled */ +"Canceling Bolus" = "Întrerupere Bolus"; + +/* Details for missing data error when carb effects are missing */ +"Carb effects" = "Efecte carbohidrați"; + +/* The title of the carb ratios schedule screen + The title text for the carb ratio schedule */ +"Carb Ratios" = "Raport carbohidrați/insulină"; + +/* Title of the prediction input effect for carbohydrates */ +"Carbohydrates" = "Carbohidrați"; + +/* Description of the prediction input effect for carbohydrates. (1: The glucose unit string) */ +"Carbs Absorbed (g) ÷ Carb Ratio (g/U) × Insulin Sensitivity (%1$@/U)" = "Carbohidrați absorbiți (g) ÷ Raport carbohidrați (g/U) × Factor de sensibilitate la insulină (%1$@/U)"; + +/* The notification alert describing a low pump battery */ +"Change the pump battery immediately" = "Schimbați bateria pompei imediat"; + +/* The notification alert describing an empty pump reservoir */ +"Change the pump reservoir now" = "Schimbați bateria pompei acum"; + +/* Details for configuration error when one or more loop settings are missing */ +"Check settings" = "Verificați setările"; + +/* Recovery suggestion when reservoir data is missing */ +"Check that your pump is in range" = "Verificați că pompa este în apropriere"; + +/* Recovery suggestion when glucose data is missing */ +"Check your CGM data source" = "Verificați sursa de date CGM"; + +/* The title text for the looping enabled switch cell */ +"Closed Loop" = "Loop automat"; + +/* The title of the action used to dismiss an error alert */ +"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; + +/* The title of the configuration section in settings */ +"Configuration" = "Configurare"; + +/* The error message displayed for configuration errors. (1: configuration error details) */ +"Configuration Error: %1$@" = "Eroare configurare: %1$@"; + +/* The title of the continuous glucose monitor section in settings */ +"Continuous Glucose Monitor" = "Monitorizare glicemică continuă"; + +/* The title of the glucose target range schedule screen + The title text for the glucose target range schedule */ +"Correction Range" = "Interval țintă pentru corecție"; + +/* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ +"Current glucose of %1$@ is below correction range." = " Glicemia curentă de %1$@ se situează sub intervalul țintă de corecție."; + +/* The title of the cell indicating a generic temporary override is enabled */ +"Custom Override" = "Înlocuire"; + +/* The title of the Loggly customer token credential */ +"Customer Token" = "Customer Token"; + +/* Button title to delete CGM */ +"Delete CGM" = "Șterge CGM"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Limite administrare"; + +/* The action hint of the workout mode toggle button when enabled */ +"Disables" = "Dezactivează"; + +/* The action hint of the workout mode toggle button when disabled */ +"Enables" = "Activează"; + +/* The placeholder text instructing users to enter a suspend treshold */ +"Enter suspend threshold" = "Introduceți limita pentru suspendare"; + +/* The alert title for an error while canceling a bolus */ +"Error Canceling Bolus" = "Eroare la anularea bolusului"; + +/* The alert title for a resume error */ +"Error Resuming" = "Eroare în timpul reluării"; + +/* The subtitle format describing eventual glucose. (1: localized glucose value description) */ +"Eventually %@" = "Ajunge la %@"; + +/* The title of the alert describing a maximum bolus validation error */ +"Exceeds Maximum Bolus" = "Depășește bolusul maxim"; + +/* Title of insulin model preset */ +"Fiasp" = "Fiasp"; + +/* The format string used to describe a finite workout targets duration */ +"For %1$@" = "Timp de %1$@"; + +/* The title of the glucose and prediction graph */ +"Glucose" = "Glicemie"; + +/* The error message when glucose data is too old to be used. (1: glucose data age in minutes) */ +"Glucose data is %1$@ old" = "Datele despre glicemie au o vechime de %1$@"; + +/* Description of error when glucose data is missing */ +"Glucose data not available" = "Nu sunt disponibile date despre glicemie"; + +/* Title of the prediction input effect for glucose momentum */ +"Glucose Momentum" = "Momentum glicemie"; + +/* The placeholder text for the nightscout site URL credential */ +"https://mysite.herokuapp.com" = "https://mysite.herokuapp.com"; + +/* The title of a target alert action specifying an indefinitely long workout targets duration */ +"Indefinitely" = "Nedeterminat"; + +/* Title of the prediction input effect for insulin */ +"Insulin" = "Insulină"; + +/* Description of the prediction input effect for insulin */ +"Insulin Absorbed (U) × Insulin Sensitivity (%1$@/U)" = "Insulină absorbită (U) × factor de sensibilitate la insulină (%1$@/U)"; + +/* The title of the insulin delivery graph */ +"Insulin Delivery" = "Administrare insulină"; + +/* Details for missing data error when insulin effects are missing */ +"Insulin effects" = "Efecte insulină"; + +/* Details for configuration error when insulin model is missing + The title text for the insulin model setting row */ +"Insulin Model" = "Model insulină"; + +/* The title of the insulin sensitivities schedule screen + The title text for the insulin sensitivity schedule */ +"Insulin Sensitivities" = "Sensibilitate insulină"; + +/* The error message when invalid data was encountered. (1: details of invalid data) */ +"Invalid data: %1$@" = "Date invalide: %1$@"; + +/* The title text for the issue report cell */ +"Issue Report" = "Raportare probleme"; + +/* Glucose HUD accessibility hint */ +"Launches CGM app" = "Lansează aplicația CGM"; + +/* The loading message for the diagnostic report screen */ +"Loading..." = "Se încarcă..."; + +/* The title of the loggly service */ +"Loggly" = "Loggly"; + +/* The notification title for a loop failure */ +"Loop Failure" = "Eșec Loop"; + +/* The notification alert describing a long-lasting loop failure. The substitution parameter is the time interval since the last loop */ +"Loop has not completed successfully in %@" = "Loop nu a rulat cu succes timp de %@"; + +/* The error message for missing data. (1: missing data details) */ +"Missing data: %1$@" = "Date lipsă: %1$@"; + +/* Details for missing data error when momentum effects are missing */ +"Momentum effects" = "Efecte momentum"; + +/* The title of the Nightscout service */ +"Nightscout" = "Nightscout"; + +/* The error message displayed for device connection errors. */ +"No connected devices, or failure during device connection" = "Nu este conectat niciun dispozitiv sau s-a produs o eroare la conectare"; + +/* The title text for the override presets */ +"Override Presets" = "Presetări de înlocuire"; + +/* The label of the pre-meal mode toggle button */ +"Pre-Meal Targets" = "Ținte preprandiale"; + +/* Message when offering bolus recommendation even though bg is below range and minBG is in future. (1: glucose time)(2: glucose number) */ +"Predicted glucose at %1$@ is %2$@." = "Glicemia prognozată pentru %1$@ este %2$@."; + +/* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ +"Predicted glucose of %1$@ is below your suspend threshold setting." = "Glicemia prognozată de %1$@ se situează sub limita de suspendare configurată"; + +/* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ +"Predicted: %1$@\nActual: %2$@ (%3$@)" = "Prognozată: %1$@\nActuală: %2$@ (%3$@)"; + +/* The title of the pump section in settings */ +"Pump" = "Pompă"; + +/* The notification title for a low pump battery */ +"Pump Battery Low" = "Nivel scăzut baterie pompă"; + +/* The error message when pump data is too old to be used. (1: pump data age in minutes) */ +"Pump data is %1$@ old" = "Datele din pompă sunt vechi de %1$@"; + +/* Details for configuration error when pump manager is missing */ +"Pump Manager" = "Manager pompă"; + +/* The notification title for an empty pump reservoir */ +"Pump Reservoir Empty" = "Rezervor pompă gol"; + +/* The notification title for a low pump reservoir */ +"Pump Reservoir Low" = "Nivel scăzut rezervor pompă"; + +/* The title of the cell indicating the pump is suspended */ +"Pump Suspended" = "Pompă suspendată"; + +/* Title of insulin model preset */ +"Rapid-Acting – Adults" = "Rapid-Acting – Adulți"; + +/* Title of insulin model preset */ +"Rapid-Acting – Children" = "Rapid-Acting – Copii"; + +/* The error message when a recommendation has expired. (1: age of recommendation in minutes) */ +"Recommendation expired: %1$@ old" = "Recomandare expirată: acum %1$@"; + +/* The title of the cell displaying a recommended temp basal value */ +"Recommended Basal" = "Bazal recomandat"; + +/* Accessibility hint describing recommended bolus units */ +"Recommended Bolus: %@ Units" = "Bolus recomandat: %@ unități"; + +/* Details for missing data error when reservoir data is missing */ +"Reservoir" = "Rezervor"; + +/* Title of the prediction input effect for retrospective correction */ +"Retrospective Correction" = "Corecție retrospectivă"; + +/* The title of the notification action to retry a bolus command */ +"Retry" = "Reîncearcă"; + +/* The title of the services section in settings */ +"Services" = "Servicii"; + +/* The label of the settings button */ +"Settings" = "Setări"; + +/* Loop Completion HUD accessibility hint */ +"Shows last loop error" = "Afișează ultima eroare de loop"; + +/* Format fragment for a start time */ +"since %@" = "de la %@"; + +/* The title of the nightscout site URL credential */ +"Site URL" = "Site URL"; + +/* The format for the description of a temporary override start date */ +"starting at %@" = "începând de la %@"; + +/* The title of the cell indicating a bolus is being sent */ +"Starting Bolus" = "Start Bolus"; + +/* The title text in settings */ +"Suspend Threshold" = "Limită suspendare"; + +/* The subtitle of the cell displaying an action to resume insulin delivery */ +"Tap to Resume" = "Apăsați pentru a relua"; + +/* Subtitle description of Walsh insulin model setting */ +"The legacy model used by Loop, allowing customization of action duration." = "Modelul învechit utilizat de Loop, permite personalizarea duratei de acțiune."; + +/* Body of the alert describing a maximum bolus validation error. (1: The localized max bolus value) */ +"The maximum bolus amount is %@ Units" = "Cantitatea maximă de bolus este de %@ unități"; + +/* The short unit display string for international units of insulin */ +"U" = "U"; + +/* The format for the description of a temporary override end date */ +"until %@" = "până la %@"; + +/* The title of the alert controller used to select a duration for workout targets */ +"Use Workout Glucose Targets" = "Folosește țintele glicemice de activitate sportivă"; + +/* Title of insulin model setting */ +"Walsh" = "Walsh"; + +/* Explanation of suspend threshold */ +"When current or forecasted glucose is below the suspend threshold, Loop will not recommend a bolus, and will always recommend a temporary basal rate of 0 units per hour." = "În momentul în care glicemia actuală sau cea prognozată se situează sub limita de suspendare, Loop nu va recomanda un bolus și va recomanda întotdeauna o rată bazală temporară de 0 unități pe ora."; + +/* The label of the workout mode toggle button */ +"Workout Targets" = "Ținte de activitate sportivă"; + diff --git a/Loop/ro.lproj/Main.strings b/Loop/ro.lproj/Main.strings new file mode 100644 index 0000000000..c93ea00c61 --- /dev/null +++ b/Loop/ro.lproj/Main.strings @@ -0,0 +1,144 @@ +/* Class = "UINavigationItem"; title = "Status"; ObjectID = "3kU-n2-fha"; */ +"3kU-n2-fha.title" = "Status"; + +/* Class = "UILabel"; text = "3.5 U/hour @ 12:12 PM"; ObjectID = "5gz-kZ-iF1"; */ +"5gz-kZ-iF1.text" = "3.5 U/oră @ 12:12 PM"; + +/* Class = "UILabel"; text = "Bolus"; ObjectID = "5oA-6d-ZTL"; */ +"5oA-6d-ZTL.text" = "Bolus"; + +/* Class = "UILabel"; text = "Pump ID"; ObjectID = "5TX-kX-nBo"; */ +"5TX-kX-nBo.text" = "ID pompă"; + +/* Class = "UITextField"; accessibilityLabel = "Bolus Amount"; ObjectID = "7LT-50-ZzK"; */ +"7LT-50-ZzK.accessibilityLabel" = "Cantitate bolus"; + +/* Class = "UITextField"; placeholder = "0.0"; ObjectID = "7LT-50-ZzK"; */ +"7LT-50-ZzK.placeholder" = "0.0"; + +/* Class = "UILabel"; text = "Predicted"; ObjectID = "87H-N1-0vJ"; */ +"87H-N1-0vJ.text" = "Estimată"; + +/* Class = "UILabel"; text = "Detail"; ObjectID = "aCb-Qs-bpu"; */ +"aCb-Qs-bpu.text" = "Detaliu"; + +/* Class = "UINavigationItem"; title = "Bolus"; ObjectID = "aiu-ZA-zVa"; */ +"aiu-ZA-zVa.title" = "Bolus"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "bIL-Ub-qYp"; */ +"bIL-Ub-qYp.text" = "Etichetă"; + +/* Class = "UILabel"; text = "Glucose Change"; ObjectID = "bq4-98-cQU"; */ +"bq4-98-cQU.text" = "Glucose Change"; + +/* Class = "UILabel"; accessibilityLabel = "Units"; ObjectID = "BR0-dr-Fj2"; */ +"BR0-dr-Fj2.accessibilityLabel" = "unități"; + +/* Class = "UILabel"; text = "U"; ObjectID = "BR0-dr-Fj2"; */ +"BR0-dr-Fj2.text" = "U"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "cpo-Po-gFM"; */ +"cpo-Po-gFM.text" = "Etichetă"; + +/* Class = "UILabel"; text = "g Total"; ObjectID = "d3X-AN-tA5"; */ +"d3X-AN-tA5.text" = "g în total"; + +/* Class = "UILabel"; text = "Future glucose is predicted by combining the effects of multiple inputs. Use this tool to toggle various inputs to see how they compare to the final prediction."; ObjectID = "D4C-I2-dhA"; */ +"D4C-I2-dhA.text" = "Glicemia este estimată prin combinarea unui număr de date sursă. Folosiți acest instrument pentru a controla diverse surse de date și a felului în care influențează estimarea."; + +/* Class = "UILabel"; text = "Label"; ObjectID = "d6m-qV-wWi"; */ +"d6m-qV-wWi.text" = "Etichetă"; + +/* Class = "UINavigationItem"; title = "Settings"; ObjectID = "dmB-PQ-B44"; */ +"dmB-PQ-B44.title" = "Setări"; + +/* Class = "UILabel"; text = "DEVICES"; ObjectID = "DyC-Sv-qP8"; */ +"DyC-Sv-qP8.text" = "DEVICES"; + +/* Class = "UILabel"; text = "eventually 92 mg/dL"; ObjectID = "E41-FN-nkk"; */ +"E41-FN-nkk.text" = "estimată să ajungă la 92 mg/dL"; + +/* Class = "UILabel"; text = "Observed"; ObjectID = "EAn-Ja-S1d"; */ +"EAn-Ja-S1d.text" = "Observată"; + +/* Class = "UILabel"; text = "Active Carbohydrates: 40g"; ObjectID = "hHZ-uY-aKw"; */ +"hHZ-uY-aKw.text" = "Carbohidrați activi: 40g"; + +/* Class = "UITableViewController"; title = "Carbohydrate Effects"; ObjectID = "hZZ-2S-lrd"; */ +"hZZ-2S-lrd.title" = "Efecte carbohidrați"; + +/* Class = "UILabel"; text = "Observed changes in glucose, subtracting changes modeled from insulin delivery, can be used to estimate carbohydrate absorption."; ObjectID = "IxU-As-glo"; */ +"IxU-As-glo.text" = "Modificările observate ale glicemiei, eliminând modificările modelate din administrarea insulinei, pot fi folosite pentru a estima absorbția carbohidraților."; + +/* Class = "UILabel"; text = "Detail"; ObjectID = "J7x-W5-gwo"; */ +"J7x-W5-gwo.text" = "Detaliu"; + +/* Class = "UILabel"; text = "⚠ Glucose Predicted Below Range"; ObjectID = "k0g-P7-OVN"; */ +"k0g-P7-OVN.text" = "⚠ Glicemia estimată se situează sub intervalul țintă"; + +/* Class = "UILabel"; text = "Recommended Basal"; ObjectID = "k3F-Na-7mn"; */ +"k3F-Na-7mn.text" = "Bazal recomandat"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "Krd-Aa-ret"; */ +"Krd-Aa-ret.text" = "Etichetă"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "l7l-mw-Oc9"; */ +"l7l-mw-Oc9.text" = "Etichetă"; + +/* Class = "UILabel"; text = "Tap to set"; ObjectID = "m9c-SQ-djE"; */ +"m9c-SQ-djE.text" = "Apăsați pentru a seta"; + +/* Class = "UILabel"; accessibilityLabel = "Units"; ObjectID = "mVz-dr-xLU"; */ +"mVz-dr-xLU.accessibilityLabel" = "Unități"; + +/* Class = "UILabel"; text = "U"; ObjectID = "mVz-dr-xLU"; */ +"mVz-dr-xLU.text" = "U"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "OFA-qT-ZAg"; */ +"OFA-qT-ZAg.text" = "Etichetă"; + +/* Class = "UITableViewController"; title = "Predicted Glucose"; ObjectID = "PA3-sP-cWY"; */ +"PA3-sP-cWY.title" = "Glicemie estimată"; + +/* Class = "UITableViewController"; title = "Insulin Model"; ObjectID = "pi6-Dh-72V"; */ +"pi6-Dh-72V.title" = "Model insulină"; + +/* Class = "UILabel"; text = "An insulin activity model is used to estimate effects of insulin on glucose levels. An accurate model can help prevent insulin stacking and safely recommend corrective treatments."; ObjectID = "PJv-p9-cFe"; */ +"PJv-p9-cFe.text" = "Pentru a estima efectele insulinei asupra glicemiei, se folosește un model de acțiune a insulinei. Acuratețea modelului poate preveni suprapunerea insulinei și recomanda corecții într-un mod sigur"; + +/* Class = "UILabel"; text = "eventually 92 mg/dL"; ObjectID = "Rse-x8-amW"; */ +"Rse-x8-amW.text" = "estimată să ajungă la 92 mg/dL"; + +/* Class = "UILabel"; text = "g COB"; ObjectID = "SQx-au-ZcM"; */ +"SQx-au-ZcM.text" = "g CLB"; + +/* Class = "UILabel"; text = "Glucose"; ObjectID = "tuw-av-A3x"; */ +"tuw-av-A3x.text" = "Glicemie"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "ufi-Kj-33k"; */ +"ufi-Kj-33k.text" = "Eticheă"; + +/* Class = "UILabel"; text = "Active Insulin: 1.5U"; ObjectID = "viU-7a-kbI"; */ +"viU-7a-kbI.text" = "Insulină activă: 1.5U"; + +/* Class = "UINavigationItem"; title = "Carbohydrates"; ObjectID = "Vpi-5b-bY5"; */ +"Vpi-5b-bY5.title" = "Carbohidrați"; + +/* Class = "UITextField"; text = "4 hour"; ObjectID = "Wk3-xv-IM5"; */ +"Wk3-xv-IM5.text" = "4 ore"; + +/* Class = "UIButton"; normalTitle = "Deliver"; ObjectID = "Ya0-9b-ZAS"; */ +"Ya0-9b-ZAS.normalTitle" = "Administrează"; + +/* Class = "UILabel"; text = "0"; ObjectID = "yn7-2M-jZz"; */ +"yn7-2M-jZz.text" = "0"; + +/* Class = "UILabel"; text = "Recommended"; ObjectID = "ywT-OR-NnU"; */ +"ywT-OR-NnU.text" = "Recomandat"; + +/* Class = "UILabel"; text = "Title"; ObjectID = "zbc-87-wxZ"; */ +"zbc-87-wxZ.text" = "Titlu"; + +/* Class = "UILabel"; text = "0"; ObjectID = "zvZ-uf-zMX"; */ +"zvZ-uf-zMX.text" = "0"; + diff --git a/Loop/ru.lproj/InfoPlist.strings b/Loop/ru.lproj/InfoPlist.strings index fef7ac0773..0f388ab0de 100644 --- a/Loop/ru.lproj/InfoPlist.strings +++ b/Loop/ru.lproj/InfoPlist.strings @@ -1,9 +1,9 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "Loop"; - /* Bundle name */ "CFBundleName" = "$(PRODUCT_NAME)"; +/* Privacy - Bluetooth Always Usage Description */ +"NSBluetoothAlwaysUsageDescription" = "Bluetooth is used to communicate with insulin pump and continuous glucose monitor devices."; + /* Privacy - Bluetooth Peripheral Usage Description */ "NSBluetoothPeripheralUsageDescription" = "Блутус применяется для коммуникации с инсулиновой помпой и устройствами непрерывного мониторинга СК"; diff --git a/Loop/ru.lproj/LaunchScreen.strings b/Loop/ru.lproj/LaunchScreen.strings deleted file mode 100644 index 874e8a4532..0000000000 --- a/Loop/ru.lproj/LaunchScreen.strings +++ /dev/null @@ -1 +0,0 @@ -/* No Localized Strings */ diff --git a/Loop/ru.lproj/Localizable.strings b/Loop/ru.lproj/Localizable.strings index f013b962da..2e84a5fe16 100644 --- a/Loop/ru.lproj/Localizable.strings +++ b/Loop/ru.lproj/Localizable.strings @@ -1,10 +1,22 @@ -/* Created with Localizable Strings Merge, the swiss-army knife for iOS and OS X localization. */ - /* The string format appended to active insulin that describes pending insulin. (1: pending insulin) */ " (pending: %@)" = " (В ожидании: %@)"; -/* Format string for carb ratio average. (1: value)(2: carb unit) - Format string for insulin sensitivity average (1: value)(2: glucose unit) */ +/* The format for an active override preset. (1: preset symbol)(2: preset name) */ +"%@ %@" = "%1$@ %2$@"; + +/* Formats absorbed carb value */ +"%@ absorbed" = "%@ усвоено"; + +/* The subtitle format describing total insulin. (1: localized insulin total) */ +"%@ U Total" = "%@ всего ед"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ +"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; + +/* Format string for carb ratio average. (1: value)(2: carb unit) */ "%1$@ %2$@/U" = "%1$@ %2$@/ед"; /* Formats (1: carb start time) and (2: carb absorption duration) */ @@ -19,27 +31,9 @@ /* The format for recommended temp basal rate and time. (1: localized rate number)(2: localized time) */ "%1$@ U/hour @ %2$@" = "%1$@ ед/час @ %2$@"; -/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ -"%1$@ v%2$@" = "%1$@ v%2$@"; - -/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ -"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; - -/* Format string describing glucose units per minute (1: glucose unit string) */ -"%1$@/min" = "%1$@/минут"; - /* Formats (1: carb value) and (2: food type) */ "%1$@: %2$@" = "%1$@: %2$@"; -/* Formats absorbed carb value */ -"%@ absorbed" = "%@ усвоено"; - -/* The subtitle format describing total insulin. (1: localized insulin total) */ -"%@ U Total" = "%@ всего ед"; - -/* Appends a full-stop to a statement */ -"%@." = "%@."; - /* Description of the prediction input effect for glucose momentum */ "15 min glucose regression coefficient (b₁), continued with decay over 30 min" = "15-мин коэффициент регрессии гликемии (b1), продолжен с угасанием 30 мин"; @@ -67,15 +61,20 @@ /* The string format describing active insulin. (1: localized insulin value description) */ "Active Insulin: %@" = "Действующий инсулин: %@"; +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Добавить запись углеводов"; + +/* Action sheet title selecting CGM + Title text for button to set up a CGM */ +"Add CGM" = "добавить мониторинг"; + /* The label of the carb entry button */ "Add Meal" = "Добавить продукт"; -/* Title text for button to set up a new pump */ +/* Action sheet title selecting Pump + Title text for button to set up a new pump */ "Add Pump" = "Добавить помпу"; -/* The title of the section containing algorithm settings */ -"Algorithm Settings" = "Настройки алгоритма"; - /* The title of the Amplitude service */ "Amplitude" = "Amplitude"; @@ -88,6 +87,9 @@ /* The title of the nightscout API secret credential */ "API Secret" = "API Secret"; +/* Confirmation message for deleting a CGM */ +"Are you sure you want to delete this CGM?" = "Стереть этот мониторинг?"; + /* Format fragment for a specific time */ "at %@" = "В %@"; @@ -105,9 +107,18 @@ The notification title for a bolus failure */ "Bolus" = "Болюс"; +/* The format string for bolus progress. (1: delivered volume)(2: total volume) */ +"Bolused %1$@ of %2$@" = "Подан болюс%1$@ из %2$@"; + +/* The format string for bolus in progress showing total volume. (1: total volume) */ +"Bolusing %1$@" = "Подается болюс"; + /* The title of the cancel action in an action sheet */ "Cancel" = "Отмена"; +/* The title of the cell indicating a bolus is being canceled */ +"Canceling Bolus" = "Отмена болюса"; + /* Details for missing data error when carb effects are missing */ "Carb effects" = "Влияние углеводов"; @@ -139,6 +150,9 @@ /* The title text for the looping enabled switch cell */ "Closed Loop" = "Замкнутый цикл"; +/* The title of the action used to dismiss an error alert */ +"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; + /* The title of the configuration section in settings */ "Configuration" = "Конфигурация"; @@ -155,14 +169,14 @@ /* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ "Current glucose of %1$@ is below correction range." = "Гликемия %1$@ ниже диапазона коррекции"; +/* The title of the cell indicating a generic temporary override is enabled */ +"Custom Override" = "Настраиваемая перезапись"; + /* The title of the Loggly customer token credential */ "Customer Token" = "Жетон пользователя"; -/* The short unit display string for decibles */ -"dB" = "dB"; - -/* The title of the button to remove the credentials for a service */ -"Delete Account" = "Удалить аккаунт"; +/* Button title to delete CGM */ +"Delete CGM" = "Удалить мониторинг"; /* Title text for delivery limits */ "Delivery Limits" = "Предел подачи"; @@ -170,17 +184,17 @@ /* The action hint of the workout mode toggle button when enabled */ "Disables" = "Деактивирует"; -/* Title of the switch which toggles retrospective correction effects */ -"Enable Retrospective Correction" = "Активирует ретроспективную коррекцию"; - /* The action hint of the workout mode toggle button when disabled */ "Enables" = "Активирует"; /* The placeholder text instructing users to enter a suspend treshold */ "Enter suspend threshold" = "Введите рубеж приостановки"; -/* The placeholder text instructing users how to enter a transmitter ID */ -"Enter the 6-digit transmitter ID" = "Введите инд. номер трансмиттера (6 цифр)"; +/* The alert title for an error while canceling a bolus */ +"Error Canceling Bolus" = "Ошибка отмены болюса"; + +/* The alert title for a resume error */ +"Error Resuming" = "Ошибка возобновления"; /* The subtitle format describing eventual glucose. (1: localized glucose value description) */ "Eventually %@" = "В конечном итоге %@"; @@ -189,20 +203,11 @@ "Exceeds Maximum Bolus" = "Превышает макс болюс"; /* Title of insulin model preset */ -"Fiasp" = "Fiasp"; +"Fiasp" = "Fiasp"; /* The format string used to describe a finite workout targets duration */ "For %1$@" = "В течение %1$@"; -/* The short unit display string for grams */ -"g" = "г"; - -/* The title text for the G4 Share Receiver switch cell */ -"G4 Share Receiver" = "Ресивер G4 Шеар"; - -/* The title text for the G5/G6 Transmitter switch cell */ -"G5/G6 Transmitter" = "Трансмиттер G5/G6"; - /* The title of the glucose and prediction graph */ "Glucose" = "Гликемия"; @@ -216,7 +221,7 @@ "Glucose Momentum" = "Динамика гликемии"; /* The placeholder text for the nightscout site URL credential */ -"https://mysite.herokuapp.com" = "https://мойсайт. herokuapp.com"; +"https://mysite.herokuapp.com" = "https://mysite.herokuapp.com"; /* The title of a target alert action specifying an indefinitely long workout targets duration */ "Indefinitely" = "На неопределенное время"; @@ -262,27 +267,21 @@ /* The notification alert describing a long-lasting loop failure. The substitution parameter is the time interval since the last loop */ "Loop has not completed successfully in %@" = "Ну удалось успешно замкнуть цикл/контур в %@"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "мг/дл"; - /* The error message for missing data. (1: missing data details) */ "Missing data: %1$@" = "Пропущены данные: %1$@"; -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "ммоль/л"; - /* Details for missing data error when momentum effects are missing */ "Momentum effects" = "Влияние динамики СК"; -/* Sensor state description for the non-valid state */ -"Needs Attention" = "Требует внимания"; - /* The title of the Nightscout service */ "Nightscout" = "Nightscout"; /* The error message displayed for device connection errors. */ "No connected devices, or failure during device connection" = "Устройства не сопряжены или ошибка во время сопряжения"; +/* The title text for the override presets */ +"Override Presets" = "Перезапись настроек"; + /* The label of the pre-meal mode toggle button */ "Pre-Meal Targets" = "Целевые значения до приема пищи"; @@ -292,6 +291,9 @@ /* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ "Predicted glucose of %1$@ is below your suspend threshold setting." = "Предсказываемая гликемия %1$@ ниже ваших настроек порога приостановки помпы"; +/* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ +"Predicted: %1$@\nActual: %2$@ (%3$@)" = "Прогноз: %1$@\nФакт: %2$@ (%3$@)"; + /* The title of the pump section in settings */ "Pump" = "Помпа"; @@ -310,6 +312,9 @@ /* The notification title for a low pump reservoir */ "Pump Reservoir Low" = "Низкий уровень резервуара помпы"; +/* The title of the cell indicating the pump is suspended */ +"Pump Suspended" = "Помпа приостановлена"; + /* Title of insulin model preset */ "Rapid-Acting – Adults" = "Боыстродействующий - взрослые"; @@ -349,8 +354,8 @@ /* The title of the nightscout site URL credential */ "Site URL" = "URL сайта"; -/* The title text for the Medtronic sensor switch cell */ -"Sof-Sensor / Enlite" = "Sof-Sensor/Enlite"; +/* The format for the description of a temporary override start date */ +"starting at %@" = "начало с %@"; /* The title of the cell indicating a bolus is being sent */ "Starting Bolus" = "Начинаю болюс"; @@ -358,20 +363,20 @@ /* The title text in settings */ "Suspend Threshold" = "Порог приостановки"; +/* The subtitle of the cell displaying an action to resume insulin delivery */ +"Tap to Resume" = "Нажмите чтобы возобновить"; + /* Subtitle description of Walsh insulin model setting */ "The legacy model used by Loop, allowing customization of action duration." = "Модель, используемая для цикла/контура, позволяет настройку длительности действия."; /* Body of the alert describing a maximum bolus validation error. (1: The localized max bolus value) */ "The maximum bolus amount is %@ Units" = "максимальный болюс %@ ед"; -/* Instructions on where to find the transmitter ID */ -"The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app." = "Инд. номер трансмиттера находится на задней стенке устройства , сбоку упаковочной коробки, в настроечном меню ресивера и мобильного приложения"; - -/* The description of the switch which toggles retrospective correction effects */ -"This will more aggresively increase or decrease basal delivery when glucose movement doesn't match the carbohydrate and insulin-based model." = "Это более радикально понизит или увеличит подачу базала в тех случаях когда динамика гликемии не соответствует моделям основанным на углеводах и инсулине"; - /* The short unit display string for international units of insulin */ -"U" = "U"; +"U" = "U"; + +/* The format for the description of a temporary override end date */ +"until %@" = "до %@"; /* The title of the alert controller used to select a duration for workout targets */ "Use Workout Glucose Targets" = "Применить цели гликемии как для физической нагрузки"; diff --git a/Loop/ru.lproj/Main.strings b/Loop/ru.lproj/Main.strings index f0915a04c6..91e393c000 100644 --- a/Loop/ru.lproj/Main.strings +++ b/Loop/ru.lproj/Main.strings @@ -25,9 +25,6 @@ /* Class = "UINavigationItem"; title = "Bolus"; ObjectID = "aiu-ZA-zVa"; */ "aiu-ZA-zVa.title" = "Болюс"; -/* Class = "UILabel"; text = "Title"; ObjectID = "B9F-K3-7RI"; */ -"B9F-K3-7RI.text" = "Название"; - /* Class = "UILabel"; text = "Label"; ObjectID = "bIL-Ub-qYp"; */ "bIL-Ub-qYp.text" = "Пометка"; diff --git a/Loop/sv.lproj/InfoPlist.strings b/Loop/sv.lproj/InfoPlist.strings new file mode 100644 index 0000000000..6098bba476 --- /dev/null +++ b/Loop/sv.lproj/InfoPlist.strings @@ -0,0 +1,18 @@ +/* Bundle name */ +"CFBundleName" = "$(PRODUCT_NAME)"; + +/* Privacy - Bluetooth Always Usage Description */ +"NSBluetoothAlwaysUsageDescription" = "Bluetooth används för att kommunicera med insulinpumpen och kontinuerlig glukosmätare."; + +/* Privacy - Bluetooth Peripheral Usage Description */ +"NSBluetoothPeripheralUsageDescription" = "Bluetooth används för att kommunicera med insulinpumpen och kontinuerlig glukosmätare."; + +/* Privacy - Face ID Usage Description */ +"NSFaceIDUsageDescription" = "Face ID avänds för att godkänna bolus."; + +/* Privacy - Health Share Usage Description */ +"NSHealthShareUsageDescription" = "Kolhydratdata från Apple Health-databasen används för att bestämma effekten på glukosvärde. Glukosvärden från Apple Health-databasen används i diagram och för beräkning av förändring."; + +/* Privacy - Health Update Usage Description */ +"NSHealthUpdateUsageDescription" = "Kolhydratvärden inmatade i appen i klockan lagras i Apple Health-databasen. Glukosvärden mottagna från CGM lagras krypterat i HealthKit."; + diff --git a/Loop/sv.lproj/Localizable.strings b/Loop/sv.lproj/Localizable.strings new file mode 100644 index 0000000000..f338a3503e --- /dev/null +++ b/Loop/sv.lproj/Localizable.strings @@ -0,0 +1,395 @@ +/* The string format appended to active insulin that describes pending insulin. (1: pending insulin) */ +" (pending: %@)" = " (återstår: %@)"; + +/* The format for an active override preset. (1: preset symbol)(2: preset name) */ +"%@ %@" = "%1$@ %2$@"; + +/* Formats absorbed carb value */ +"%@ absorbed" = "%@ absorberat"; + +/* The subtitle format describing total insulin. (1: localized insulin total) */ +"%@ U Total" = "%@ E totalt"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ +"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; + +/* Format string for carb ratio average. (1: value)(2: carb unit) */ +"%1$@ %2$@/U" = "%1$@ %2$@/E"; + +/* Formats (1: carb start time) and (2: carb absorption duration) */ +"%1$@ + %2$@" = "%1$@ + %2$@"; + +/* Low reservoir alert format string. (1: Number of units remaining) */ +"%1$@ U left" = "%1$@ E kvar"; + +/* Low reservoir alert with time remaining format string. (1: Number of units remaining)(2: approximate time remaining) */ +"%1$@ U left: %2$@" = "%1$@ E kvar: %2$@"; + +/* The format for recommended temp basal rate and time. (1: localized rate number)(2: localized time) */ +"%1$@ U/hour @ %2$@" = "%1$@ E/timme @ %2$@"; + +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Formats (1: carb value) and (2: food type) */ +"%1$@: %2$@" = "%1$@: %2$@"; + +/* Description of the prediction input effect for glucose momentum */ +"15 min glucose regression coefficient (b₁), continued with decay over 30 min" = "15 minuters glukosregressionskoeficient (b₁), fortsatt med 30 minuters avklingande"; + +/* Description of the prediction input effect for retrospective correction */ +"30 min comparison of glucose prediction vs actual, continued with decay over 60 min" = "30 min jämförelse av glukosprediktion och faktiskt värde, fortsatt med 60 miuters avklingande."; + +/* Subtitle of Fiasp preset */ +"A model based on the published absorption of Fiasp insulin." = "Insulinmodell baserad på publicerade studier av absorption av Fiasp insulin."; + +/* Subtitle of Rapid-Acting – Adult preset */ +"A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults." = "Insulinmodell baserad på publicerade studier av absorption av Humalog, Novolog, and Apidra-insulin hos vuxna."; + +/* Action to copy the recommended Bolus value to the actual Bolus Field */ +"AcceptRecommendedBolus" = "AcceptRecommendedBolus"; + +/* The title of the Carbs On-Board graph */ +"Active Carbohydrates" = "Aktiva kolhydrater"; + +/* The string format describing active carbohydrates. (1: localized glucose value description) */ +"Active Carbohydrates: %@" = "Aktiva kolhydrater: %@"; + +/* The title of the Insulin On-Board graph */ +"Active Insulin" = "Aktivt insulin"; + +/* The string format describing active insulin. (1: localized insulin value description) */ +"Active Insulin: %@" = "Aktivt insulin: %@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Lägg till kolhydrater"; + +/* Action sheet title selecting CGM + Title text for button to set up a CGM */ +"Add CGM" = "Lägg till CGM"; + +/* The label of the carb entry button */ +"Add Meal" = "Lägg till måltid"; + +/* Action sheet title selecting Pump + Title text for button to set up a new pump */ +"Add Pump" = "Lägg till pump"; + +/* The title of the Amplitude service */ +"Amplitude" = "Amplitude"; + +/* Subtitle of Rapid-Acting – Children preset */ +"An adjustment to the adult model based on empirical effects in children." = "En justering av vuxenmodellen baserad på empirisk effekt på barn."; + +/* The title of the amplitude API key credential */ +"API Key" = "API Key"; + +/* The title of the nightscout API secret credential */ +"API Secret" = "API Secret"; + +/* Confirmation message for deleting a CGM */ +"Are you sure you want to delete this CGM?" = "Säkert att du vill radera denna CGM?"; + +/* Format fragment for a specific time */ +"at %@" = "kl %@"; + +/* The message displayed during a device authentication prompt for bolus specification */ +"Authenticate to Bolus %@ Units" = "Godkänn bolus på %@ Enheter"; + +/* Details for configuration error when basal rate schedule is missing */ +"Basal Rate Schedule" = "Basaldosschema"; + +/* The title of the basal rate profile screen + The title text for the basal rate schedule */ +"Basal Rates" = "Basaldoser"; + +/* The label of the bolus entry button + The notification title for a bolus failure */ +"Bolus" = "Bolus"; + +/* The format string for bolus progress. (1: delivered volume)(2: total volume) */ +"Bolused %1$@ of %2$@" = "Gett bolus %1$@ av %2$@"; + +/* The format string for bolus in progress showing total volume. (1: total volume) */ +"Bolusing %1$@" = "Ger bolus %1$@ "; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Avbryt"; + +/* The title of the cell indicating a bolus is being canceled */ +"Canceling Bolus" = "Avbryter bolus"; + +/* Details for missing data error when carb effects are missing */ +"Carb effects" = "Kolhydrateffekter"; + +/* The title of the carb ratios schedule screen + The title text for the carb ratio schedule */ +"Carb Ratios" = "Insulinkvoter"; + +/* Title of the prediction input effect for carbohydrates */ +"Carbohydrates" = "Kolhydrater"; + +/* Description of the prediction input effect for carbohydrates. (1: The glucose unit string) */ +"Carbs Absorbed (g) ÷ Carb Ratio (g/U) × Insulin Sensitivity (%1$@/U)" = "Kolhydrater abbsorberade (g) ÷ Insulinkvot (g/E) × Insulinkänslighet (%1$@/E)"; + +/* The notification alert describing a low pump battery */ +"Change the pump battery immediately" = "Byt pumpbatteri nu"; + +/* The notification alert describing an empty pump reservoir */ +"Change the pump reservoir now" = "Byt pumpreservoar nu"; + +/* Details for configuration error when one or more loop settings are missing */ +"Check settings" = "Kontrollera inställningar"; + +/* Recovery suggestion when reservoir data is missing */ +"Check that your pump is in range" = "Kontrollera att pump är inom räckhåll"; + +/* Recovery suggestion when glucose data is missing */ +"Check your CGM data source" = "Kontrollera din CGMs datakälla"; + +/* The title text for the looping enabled switch cell */ +"Closed Loop" = "Sluten loop"; + +/* The title of the action used to dismiss an error alert */ +"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; + +/* The title of the configuration section in settings */ +"Configuration" = "Konfiguration"; + +/* The error message displayed for configuration errors. (1: configuration error details) */ +"Configuration Error: %1$@" = "Konfigurationsfel %1$@"; + +/* The title of the continuous glucose monitor section in settings */ +"Continuous Glucose Monitor" = "Kontinuerlig glukosmätning"; + +/* The title of the glucose target range schedule screen + The title text for the glucose target range schedule */ +"Correction Range" = "Målvärde"; + +/* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ +"Current glucose of %1$@ is below correction range." = "Nuvarande glukosvärde %1$@ är under målvärde."; + +/* The title of the cell indicating a generic temporary override is enabled */ +"Custom Override" = "Anpassad override"; + +/* The title of the Loggly customer token credential */ +"Customer Token" = "Inloggningsuppgift"; + +/* Button title to delete CGM */ +"Delete CGM" = "Radera CGM"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Maxdoser"; + +/* The action hint of the workout mode toggle button when enabled */ +"Disables" = "Stänger av"; + +/* The action hint of the workout mode toggle button when disabled */ +"Enables" = "Sätter på"; + +/* The placeholder text instructing users to enter a suspend treshold */ +"Enter suspend threshold" = "Ange tröskelvärde"; + +/* The alert title for an error while canceling a bolus */ +"Error Canceling Bolus" = "Fel vid försök att avbryta bolus"; + +/* The alert title for a resume error */ +"Error Resuming" = "Fel vid försök att återgå"; + +/* The subtitle format describing eventual glucose. (1: localized glucose value description) */ +"Eventually %@" = "Eventuellt %@"; + +/* The title of the alert describing a maximum bolus validation error */ +"Exceeds Maximum Bolus" = "Överstiger maxdos"; + +/* Title of insulin model preset */ +"Fiasp" = "Fiasp"; + +/* The format string used to describe a finite workout targets duration */ +"For %1$@" = "I %1$@"; + +/* The title of the glucose and prediction graph */ +"Glucose" = "Glukos"; + +/* The error message when glucose data is too old to be used. (1: glucose data age in minutes) */ +"Glucose data is %1$@ old" = "Glukosvärde är %1$@ gammalt"; + +/* Description of error when glucose data is missing */ +"Glucose data not available" = "Glukosvärde saknas"; + +/* Title of the prediction input effect for glucose momentum */ +"Glucose Momentum" = "Glukosförändring"; + +/* The placeholder text for the nightscout site URL credential */ +"https://mysite.herokuapp.com" = "https://mysite.herokuapp.com"; + +/* The title of a target alert action specifying an indefinitely long workout targets duration */ +"Indefinitely" = "Oändligt"; + +/* Title of the prediction input effect for insulin */ +"Insulin" = "Insulin"; + +/* Description of the prediction input effect for insulin */ +"Insulin Absorbed (U) × Insulin Sensitivity (%1$@/U)" = "Insulin absorberat (E) × Insulinkänslighet (%1$@/E)"; + +/* The title of the insulin delivery graph */ +"Insulin Delivery" = "Insulin doserat"; + +/* Details for missing data error when insulin effects are missing */ +"Insulin effects" = "Insulineffekter"; + +/* Details for configuration error when insulin model is missing + The title text for the insulin model setting row */ +"Insulin Model" = "Insulinmodell"; + +/* The title of the insulin sensitivities schedule screen + The title text for the insulin sensitivity schedule */ +"Insulin Sensitivities" = "Insulinkänslighet"; + +/* The error message when invalid data was encountered. (1: details of invalid data) */ +"Invalid data: %1$@" = "Ogiltigt värde: %1$@"; + +/* The title text for the issue report cell */ +"Issue Report" = "Skapa rapport"; + +/* Glucose HUD accessibility hint */ +"Launches CGM app" = "Startar CGM-app"; + +/* The loading message for the diagnostic report screen */ +"Loading..." = "Laddar..."; + +/* The title of the loggly service */ +"Loggly" = "Loggly"; + +/* The notification title for a loop failure */ +"Loop Failure" = "Loopfel"; + +/* The notification alert describing a long-lasting loop failure. The substitution parameter is the time interval since the last loop */ +"Loop has not completed successfully in %@" = "Loop har inte körts på %@"; + +/* The error message for missing data. (1: missing data details) */ +"Missing data: %1$@" = "Saknar data: %1$@"; + +/* Details for missing data error when momentum effects are missing */ +"Momentum effects" = "Momentumeffekter"; + +/* The title of the Nightscout service */ +"Nightscout" = "Nightscout"; + +/* The error message displayed for device connection errors. */ +"No connected devices, or failure during device connection" = "Ingen ansluten enhet, eller fel vid anslutning"; + +/* The title text for the override presets */ +"Override Presets" = "Override förinställningar"; + +/* The label of the pre-meal mode toggle button */ +"Pre-Meal Targets" = "Målvärden före måltid"; + +/* Message when offering bolus recommendation even though bg is below range and minBG is in future. (1: glucose time)(2: glucose number) */ +"Predicted glucose at %1$@ is %2$@." = "Predikterat glukos at %1$@ är %2$@."; + +/* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ +"Predicted glucose of %1$@ is below your suspend threshold setting." = "Predikterat glukos på %1$@ är under ditt tröskelvärde."; + +/* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ +"Predicted: %1$@\nActual: %2$@ (%3$@)" = "Predikterat: %1$@\nFaktiskt: %2$@ (%3$@)"; + +/* The title of the pump section in settings */ +"Pump" = "Pump"; + +/* The notification title for a low pump battery */ +"Pump Battery Low" = "Lågt batteri i pump"; + +/* The error message when pump data is too old to be used. (1: pump data age in minutes) */ +"Pump data is %1$@ old" = "Pumpvärden är %1$@ gamla"; + +/* Details for configuration error when pump manager is missing */ +"Pump Manager" = "Pump Manager"; + +/* The notification title for an empty pump reservoir */ +"Pump Reservoir Empty" = "Pumpreservoar är tom"; + +/* The notification title for a low pump reservoir */ +"Pump Reservoir Low" = "Pumpreservoar är låg"; + +/* The title of the cell indicating the pump is suspended */ +"Pump Suspended" = "Pump pausad"; + +/* Title of insulin model preset */ +"Rapid-Acting – Adults" = "Snabbverkande – Vuxna"; + +/* Title of insulin model preset */ +"Rapid-Acting – Children" = "Snabbverkande – Barn"; + +/* The error message when a recommendation has expired. (1: age of recommendation in minutes) */ +"Recommendation expired: %1$@ old" = "Rekommendation utgått %1$@ gammal"; + +/* The title of the cell displaying a recommended temp basal value */ +"Recommended Basal" = "Rekommenderad basal"; + +/* Accessibility hint describing recommended bolus units */ +"Recommended Bolus: %@ Units" = "Rekommenderad bolus: %@ Enheter"; + +/* Details for missing data error when reservoir data is missing */ +"Reservoir" = "Reservoar"; + +/* Title of the prediction input effect for retrospective correction */ +"Retrospective Correction" = "Retrospektiv korrektion"; + +/* The title of the notification action to retry a bolus command */ +"Retry" = "Försök igen"; + +/* The title of the services section in settings */ +"Services" = "Tjänster"; + +/* The label of the settings button */ +"Settings" = "Inställningar"; + +/* Loop Completion HUD accessibility hint */ +"Shows last loop error" = "Visar senaste loopfel"; + +/* Format fragment for a start time */ +"since %@" = "sedan %@"; + +/* The title of the nightscout site URL credential */ +"Site URL" = "Sida URL"; + +/* The format for the description of a temporary override start date */ +"starting at %@" = "Börjar kl %@"; + +/* The title of the cell indicating a bolus is being sent */ +"Starting Bolus" = "Påbörjar bolus"; + +/* The title text in settings */ +"Suspend Threshold" = "Tröskelvärde"; + +/* The subtitle of the cell displaying an action to resume insulin delivery */ +"Tap to Resume" = "Tryck för att återuppta"; + +/* Subtitle description of Walsh insulin model setting */ +"The legacy model used by Loop, allowing customization of action duration." = "Äldre modell använd av Loop, som tillåter anpassing av effektduration."; + +/* Body of the alert describing a maximum bolus validation error. (1: The localized max bolus value) */ +"The maximum bolus amount is %@ Units" = "Maximala bolusdos är %@ Eheter"; + +/* The short unit display string for international units of insulin */ +"U" = "E"; + +/* The format for the description of a temporary override end date */ +"until %@" = "till %@"; + +/* The title of the alert controller used to select a duration for workout targets */ +"Use Workout Glucose Targets" = "Avänd målvärden för träning"; + +/* Title of insulin model setting */ +"Walsh" = "Walsh"; + +/* Explanation of suspend threshold */ +"When current or forecasted glucose is below the suspend threshold, Loop will not recommend a bolus, and will always recommend a temporary basal rate of 0 units per hour." = "När nuvarande, eller predikterat, glukosvärde är under tröskelvärde, kommer Loop inte att rekommendera en bolus och kommer dessutom alltid att föreslå en temporär basal på 0 eheter per timme."; + +/* The label of the workout mode toggle button */ +"Workout Targets" = "Målvärden för träning"; + diff --git a/Loop/sv.lproj/Main.strings b/Loop/sv.lproj/Main.strings new file mode 100644 index 0000000000..b81d79ebdf --- /dev/null +++ b/Loop/sv.lproj/Main.strings @@ -0,0 +1,144 @@ +/* Class = "UINavigationItem"; title = "Status"; ObjectID = "3kU-n2-fha"; */ +"3kU-n2-fha.title" = "Status"; + +/* Class = "UILabel"; text = "3.5 U/hour @ 12:12 PM"; ObjectID = "5gz-kZ-iF1"; */ +"5gz-kZ-iF1.text" = "3.5 E/timme kl 12:12 PM"; + +/* Class = "UILabel"; text = "Bolus"; ObjectID = "5oA-6d-ZTL"; */ +"5oA-6d-ZTL.text" = "Bolus"; + +/* Class = "UILabel"; text = "Pump ID"; ObjectID = "5TX-kX-nBo"; */ +"5TX-kX-nBo.text" = "Pump-ID"; + +/* Class = "UITextField"; accessibilityLabel = "Bolus Amount"; ObjectID = "7LT-50-ZzK"; */ +"7LT-50-ZzK.accessibilityLabel" = "Bolusvärde"; + +/* Class = "UITextField"; placeholder = "0.0"; ObjectID = "7LT-50-ZzK"; */ +"7LT-50-ZzK.placeholder" = "0.0"; + +/* Class = "UILabel"; text = "Predicted"; ObjectID = "87H-N1-0vJ"; */ +"87H-N1-0vJ.text" = "Predikterat"; + +/* Class = "UILabel"; text = "Detail"; ObjectID = "aCb-Qs-bpu"; */ +"aCb-Qs-bpu.text" = "Detail"; + +/* Class = "UINavigationItem"; title = "Bolus"; ObjectID = "aiu-ZA-zVa"; */ +"aiu-ZA-zVa.title" = "Bolus"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "bIL-Ub-qYp"; */ +"bIL-Ub-qYp.text" = "Label"; + +/* Class = "UILabel"; text = "Glucose Change"; ObjectID = "bq4-98-cQU"; */ +"bq4-98-cQU.text" = "Glukosförändring"; + +/* Class = "UILabel"; accessibilityLabel = "Units"; ObjectID = "BR0-dr-Fj2"; */ +"BR0-dr-Fj2.accessibilityLabel" = "Enheter"; + +/* Class = "UILabel"; text = "U"; ObjectID = "BR0-dr-Fj2"; */ +"BR0-dr-Fj2.text" = "E"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "cpo-Po-gFM"; */ +"cpo-Po-gFM.text" = "Titel"; + +/* Class = "UILabel"; text = "g Total"; ObjectID = "d3X-AN-tA5"; */ +"d3X-AN-tA5.text" = "g totalt"; + +/* Class = "UILabel"; text = "Future glucose is predicted by combining the effects of multiple inputs. Use this tool to toggle various inputs to see how they compare to the final prediction."; ObjectID = "D4C-I2-dhA"; */ +"D4C-I2-dhA.text" = "Framtida glukosvärde uppskattas genom kombination av flera inmatningar. Använd det här verktyget för att jämföra deras påverkan på utfall "; + +/* Class = "UILabel"; text = "Label"; ObjectID = "d6m-qV-wWi"; */ +"d6m-qV-wWi.text" = "Titel"; + +/* Class = "UINavigationItem"; title = "Settings"; ObjectID = "dmB-PQ-B44"; */ +"dmB-PQ-B44.title" = "Inställningar"; + +/* Class = "UILabel"; text = "DEVICES"; ObjectID = "DyC-Sv-qP8"; */ +"DyC-Sv-qP8.text" = "ENHETER"; + +/* Class = "UILabel"; text = "eventually 92 mg/dL"; ObjectID = "E41-FN-nkk"; */ +"E41-FN-nkk.text" = "eventuellt 92 mg/dL"; + +/* Class = "UILabel"; text = "Observed"; ObjectID = "EAn-Ja-S1d"; */ +"EAn-Ja-S1d.text" = "Observerad"; + +/* Class = "UILabel"; text = "Active Carbohydrates: 40g"; ObjectID = "hHZ-uY-aKw"; */ +"hHZ-uY-aKw.text" = "Aktiva kolhydrater: 40g"; + +/* Class = "UITableViewController"; title = "Carbohydrate Effects"; ObjectID = "hZZ-2S-lrd"; */ +"hZZ-2S-lrd.title" = "Kolhydrateffekter"; + +/* Class = "UILabel"; text = "Observed changes in glucose, subtracting changes modeled from insulin delivery, can be used to estimate carbohydrate absorption."; ObjectID = "IxU-As-glo"; */ +"IxU-As-glo.text" = "Observerad glukosförändring, med substraherad förändring modellerad från isulindoser, kan användas för att uppskatta kolhydratabsorption."; + +/* Class = "UILabel"; text = "Detail"; ObjectID = "J7x-W5-gwo"; */ +"J7x-W5-gwo.text" = "Detalj"; + +/* Class = "UILabel"; text = "⚠ Glucose Predicted Below Range"; ObjectID = "k0g-P7-OVN"; */ +"k0g-P7-OVN.text" = "⚠ Predikterat glukosvärde under målvärde"; + +/* Class = "UILabel"; text = "Recommended Basal"; ObjectID = "k3F-Na-7mn"; */ +"k3F-Na-7mn.text" = "Rekommenderad basal"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "Krd-Aa-ret"; */ +"Krd-Aa-ret.text" = "Label"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "l7l-mw-Oc9"; */ +"l7l-mw-Oc9.text" = "Titel"; + +/* Class = "UILabel"; text = "Tap to set"; ObjectID = "m9c-SQ-djE"; */ +"m9c-SQ-djE.text" = "Klicka för att ange"; + +/* Class = "UILabel"; accessibilityLabel = "Units"; ObjectID = "mVz-dr-xLU"; */ +"mVz-dr-xLU.accessibilityLabel" = "Enheter"; + +/* Class = "UILabel"; text = "U"; ObjectID = "mVz-dr-xLU"; */ +"mVz-dr-xLU.text" = "E"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "OFA-qT-ZAg"; */ +"OFA-qT-ZAg.text" = "Label"; + +/* Class = "UITableViewController"; title = "Predicted Glucose"; ObjectID = "PA3-sP-cWY"; */ +"PA3-sP-cWY.title" = "Uppskattat glukosvärde"; + +/* Class = "UITableViewController"; title = "Insulin Model"; ObjectID = "pi6-Dh-72V"; */ +"pi6-Dh-72V.title" = "Insulinmodell"; + +/* Class = "UILabel"; text = "An insulin activity model is used to estimate effects of insulin on glucose levels. An accurate model can help prevent insulin stacking and safely recommend corrective treatments."; ObjectID = "PJv-p9-cFe"; */ +"PJv-p9-cFe.text" = "En insulinaktivitetsmodell används för att uppskatta effekterna på blodglukosnivåer. En korrekt modell kan hjälpa till att förhindra att insulindoser lagras på varandra ge en mer säker korrigerande rekommenderad behandling."; + +/* Class = "UILabel"; text = "eventually 92 mg/dL"; ObjectID = "Rse-x8-amW"; */ +"Rse-x8-amW.text" = "eventuellt 92 mg/dL"; + +/* Class = "UILabel"; text = "g COB"; ObjectID = "SQx-au-ZcM"; */ +"SQx-au-ZcM.text" = "g COB"; + +/* Class = "UILabel"; text = "Glucose"; ObjectID = "tuw-av-A3x"; */ +"tuw-av-A3x.text" = "Glukos"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "ufi-Kj-33k"; */ +"ufi-Kj-33k.text" = "Titel"; + +/* Class = "UILabel"; text = "Active Insulin: 1.5U"; ObjectID = "viU-7a-kbI"; */ +"viU-7a-kbI.text" = "Aktivt insulin: 1.5E"; + +/* Class = "UINavigationItem"; title = "Carbohydrates"; ObjectID = "Vpi-5b-bY5"; */ +"Vpi-5b-bY5.title" = "Kolhydrater"; + +/* Class = "UITextField"; text = "4 hour"; ObjectID = "Wk3-xv-IM5"; */ +"Wk3-xv-IM5.text" = "4 timmar"; + +/* Class = "UIButton"; normalTitle = "Deliver"; ObjectID = "Ya0-9b-ZAS"; */ +"Ya0-9b-ZAS.normalTitle" = "Ge bolus"; + +/* Class = "UILabel"; text = "0"; ObjectID = "yn7-2M-jZz"; */ +"yn7-2M-jZz.text" = "0"; + +/* Class = "UILabel"; text = "Recommended"; ObjectID = "ywT-OR-NnU"; */ +"ywT-OR-NnU.text" = "Rekommenderad"; + +/* Class = "UILabel"; text = "Title"; ObjectID = "zbc-87-wxZ"; */ +"zbc-87-wxZ.text" = "Titel"; + +/* Class = "UILabel"; text = "0"; ObjectID = "zvZ-uf-zMX"; */ +"zvZ-uf-zMX.text" = "0"; + diff --git a/Loop/vi.lproj/InfoPlist.strings b/Loop/vi.lproj/InfoPlist.strings new file mode 100644 index 0000000000..8f2686ff2e --- /dev/null +++ b/Loop/vi.lproj/InfoPlist.strings @@ -0,0 +1,18 @@ +/* Bundle name */ +"CFBundleName" = "$(PRODUCT_NAME)"; + +/* Privacy - Bluetooth Always Usage Description */ +"NSBluetoothAlwaysUsageDescription" = "Bluetooth được sử dụng để liên lạc với máy bơm insulin và các thiết bị theo dõi đường huyết liên tục/CGM."; + +/* Privacy - Bluetooth Peripheral Usage Description */ +"NSBluetoothPeripheralUsageDescription" = "Bluetooth được sử dụng để liên lạc với máy bơm insulin và các thiết bị theo dõi đường huyết liên tục/CGM."; + +/* Privacy - Face ID Usage Description */ +"NSFaceIDUsageDescription" = "Face ID được sử dụng để xác thực liều insulin cho các bữa ăn."; + +/* Privacy - Health Share Usage Description */ +"NSHealthShareUsageDescription" = "Thông số bữa ăn từ app Health được sử dụng để xác định tác động của glucose. Thông số glucose từ app Health được sử dụng cho các tính toán vẽ đồ thị và chuyển động của đường huyết."; + +/* Privacy - Health Update Usage Description */ +"NSHealthUpdateUsageDescription" = "Dữ liệu Carbohydrate của bữa ăn được nhập trên phần mềm và trên đồng hồ thông minh sẽ được lưu trữ tại app Health. Các thông số glucose được lấy từ thiết bị theo dõi đường huyết liên tục/CGM sẽ được lưu trữ an toàn trong HealthKit."; + diff --git a/Loop/vi.lproj/Localizable.strings b/Loop/vi.lproj/Localizable.strings new file mode 100644 index 0000000000..2ac05b150c --- /dev/null +++ b/Loop/vi.lproj/Localizable.strings @@ -0,0 +1,395 @@ +/* The string format appended to active insulin that describes pending insulin. (1: pending insulin) */ +" (pending: %@)" = " (đang chờ xử lý: %@)"; + +/* The format for an active override preset. (1: preset symbol)(2: preset name) */ +"%@ %@" = "%1$@ %2$@"; + +/* Formats absorbed carb value */ +"%@ absorbed" = "%@ được hấp thụ"; + +/* The subtitle format describing total insulin. (1: localized insulin total) */ +"%@ U Total" = "%@ U Tổng cộng"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ +"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; + +/* Format string for carb ratio average. (1: value)(2: carb unit) */ +"%1$@ %2$@/U" = "%1$@ %2$@/U"; + +/* Formats (1: carb start time) and (2: carb absorption duration) */ +"%1$@ + %2$@" = "%1$@ + %2$@"; + +/* Low reservoir alert format string. (1: Number of units remaining) */ +"%1$@ U left" = "%1$@ U còn lại"; + +/* Low reservoir alert with time remaining format string. (1: Number of units remaining)(2: approximate time remaining) */ +"%1$@ U left: %2$@" = "%1$@ U còn lại: %2$@"; + +/* The format for recommended temp basal rate and time. (1: localized rate number)(2: localized time) */ +"%1$@ U/hour @ %2$@" = "%1$@ U/giờ @ %2$@"; + +/* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ +"%1$@ v%2$@" = "%1$@ v%2$@"; + +/* Formats (1: carb value) and (2: food type) */ +"%1$@: %2$@" = "%1$@: %2$@"; + +/* Description of the prediction input effect for glucose momentum */ +"15 min glucose regression coefficient (b₁), continued with decay over 30 min" = "15 phút hồi quy glucose (b₁), tiếp tục phân rã trên 30 phút."; + +/* Description of the prediction input effect for retrospective correction */ +"30 min comparison of glucose prediction vs actual, continued with decay over 60 min" = "30 phút so sánh dự đoán glucose so với thực tế, tiếp tục với sự phân rã hơn 60 phút."; + +/* Subtitle of Fiasp preset */ +"A model based on the published absorption of Fiasp insulin." = "Mô hình dựa trên sự hấp thụ được công bố đối với insulin Fiasp."; + +/* Subtitle of Rapid-Acting – Adult preset */ +"A model based on the published absorption of Humalog, Novolog, and Apidra insulin in adults." = "Mô hình dựa trên sự hấp thụ được công bố của các loại insulin Humalog, Novolog và Apidra ở người lớn."; + +/* Action to copy the recommended Bolus value to the actual Bolus Field */ +"AcceptRecommendedBolus" = "AcceptRecommendedBolus"; + +/* The title of the Carbs On-Board graph */ +"Active Carbohydrates" = "Lượng Carbohydrates còn hoạt động"; + +/* The string format describing active carbohydrates. (1: localized glucose value description) */ +"Active Carbohydrates: %@" = "Lượng Carbohydrates còn hoạt động: %@"; + +/* The title of the Insulin On-Board graph */ +"Active Insulin" = "Lượng Insulin còn hoạt động"; + +/* The string format describing active insulin. (1: localized insulin value description) */ +"Active Insulin: %@" = "Lượng Insulin còn hoạt động: %@"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Khai báo khối lượng Carb"; + +/* Action sheet title selecting CGM + Title text for button to set up a CGM */ +"Add CGM" = "Khai báo CGM"; + +/* The label of the carb entry button */ +"Add Meal" = "Khai báo bữa ăn"; + +/* Action sheet title selecting Pump + Title text for button to set up a new pump */ +"Add Pump" = "Khai báo bơm"; + +/* The title of the Amplitude service */ +"Amplitude" = "Amplitude"; + +/* Subtitle of Rapid-Acting – Children preset */ +"An adjustment to the adult model based on empirical effects in children." = "Sự điều chỉnh cho mẫu người lớn dựa trên hiệu ứng theo kinh nghiệm ở trẻ em."; + +/* The title of the amplitude API key credential */ +"API Key" = "API Key"; + +/* The title of the nightscout API secret credential */ +"API Secret" = "API Secret"; + +/* Confirmation message for deleting a CGM */ +"Are you sure you want to delete this CGM?" = "Bạn có chắc sẽ xóa CGM này?"; + +/* Format fragment for a specific time */ +"at %@" = "lúc %@"; + +/* The message displayed during a device authentication prompt for bolus specification */ +"Authenticate to Bolus %@ Units" = "Xác thực liều Bolus %@ Units"; + +/* Details for configuration error when basal rate schedule is missing */ +"Basal Rate Schedule" = "Lịch biểu tiêm liều basal"; + +/* The title of the basal rate profile screen + The title text for the basal rate schedule */ +"Basal Rates" = "Basal Rates"; + +/* The label of the bolus entry button + The notification title for a bolus failure */ +"Bolus" = "Liều Bolus"; + +/* The format string for bolus progress. (1: delivered volume)(2: total volume) */ +"Bolused %1$@ of %2$@" = "Đã thực hiện Bolus %1$@ của %2$@"; + +/* The format string for bolus in progress showing total volume. (1: total volume) */ +"Bolusing %1$@" = "Đang thực hiện bolus %1$@"; + +/* The title of the cancel action in an action sheet */ +"Cancel" = "Hủy bỏ"; + +/* The title of the cell indicating a bolus is being canceled */ +"Canceling Bolus" = "Hủy bỏ liều Bolus"; + +/* Details for missing data error when carb effects are missing */ +"Carb effects" = "các tác động của Carb"; + +/* The title of the carb ratios schedule screen + The title text for the carb ratio schedule */ +"Carb Ratios" = "Tỷ lệ Carb"; + +/* Title of the prediction input effect for carbohydrates */ +"Carbohydrates" = "Các bon hydrat"; + +/* Description of the prediction input effect for carbohydrates. (1: The glucose unit string) */ +"Carbs Absorbed (g) ÷ Carb Ratio (g/U) × Insulin Sensitivity (%1$@/U)" = "Khối lượng carb tiêu thụ (g) ÷ Tỷ lệ Carb (g/U) × Độ nhạy của Insulin (%1$@/U)"; + +/* The notification alert describing a low pump battery */ +"Change the pump battery immediately" = "Thay pin máy bơm ngay"; + +/* The notification alert describing an empty pump reservoir */ +"Change the pump reservoir now" = "Thay ngăn chứa insulin bây giờ"; + +/* Details for configuration error when one or more loop settings are missing */ +"Check settings" = "Kiểm tra các cài đặt"; + +/* Recovery suggestion when reservoir data is missing */ +"Check that your pump is in range" = "Kiểm tra máy bơm đang trong phạm vi cho phép"; + +/* Recovery suggestion when glucose data is missing */ +"Check your CGM data source" = "Kiểm tra nguồn dữ liệu CGM của bạn"; + +/* The title text for the looping enabled switch cell */ +"Closed Loop" = "Vòng lặp kín"; + +/* The title of the action used to dismiss an error alert */ +"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; + +/* The title of the configuration section in settings */ +"Configuration" = "Cấu hình"; + +/* The error message displayed for configuration errors. (1: configuration error details) */ +"Configuration Error: %1$@" = "Lỗi cấu hình: %1$@"; + +/* The title of the continuous glucose monitor section in settings */ +"Continuous Glucose Monitor" = "Kiểm soát đường huyết liên tục"; + +/* The title of the glucose target range schedule screen + The title text for the glucose target range schedule */ +"Correction Range" = "Phạm vi Điều chỉnh"; + +/* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ +"Current glucose of %1$@ is below correction range." = " Chỉ số glucose hiện tại %1$@ nằm dưới Phạm vi Điều chỉnh."; + +/* The title of the cell indicating a generic temporary override is enabled */ +"Custom Override" = "Chồng liều"; + +/* The title of the Loggly customer token credential */ +"Customer Token" = "Customer Token"; + +/* Button title to delete CGM */ +"Delete CGM" = "Xóa CGM"; + +/* Title text for delivery limits */ +"Delivery Limits" = "Giới hạn tiêm"; + +/* The action hint of the workout mode toggle button when enabled */ +"Disables" = "Vô hiệu hóa"; + +/* The action hint of the workout mode toggle button when disabled */ +"Enables" = "Cho phép"; + +/* The placeholder text instructing users to enter a suspend treshold */ +"Enter suspend threshold" = "Nhập ngưỡng tạm dừng"; + +/* The alert title for an error while canceling a bolus */ +"Error Canceling Bolus" = "Lỗi hủy liều Bolus"; + +/* The alert title for a resume error */ +"Error Resuming" = "Lỗi trong việc Bắt đầu lại"; + +/* The subtitle format describing eventual glucose. (1: localized glucose value description) */ +"Eventually %@" = "Kết quả %@"; + +/* The title of the alert describing a maximum bolus validation error */ +"Exceeds Maximum Bolus" = "Vượt quá liều Bolus tối đa"; + +/* Title of insulin model preset */ +"Fiasp" = "Fiasp"; + +/* The format string used to describe a finite workout targets duration */ +"For %1$@" = "For %1$@"; + +/* The title of the glucose and prediction graph */ +"Glucose" = "Đường huyết"; + +/* The error message when glucose data is too old to be used. (1: glucose data age in minutes) */ +"Glucose data is %1$@ old" = "Dữ liệu đường huyết %1$@ cũ"; + +/* Description of error when glucose data is missing */ +"Glucose data not available" = "Dữ liệu đường huyết không sẵn sàng"; + +/* Title of the prediction input effect for glucose momentum */ +"Glucose Momentum" = "Chuyển động của Glucose"; + +/* The placeholder text for the nightscout site URL credential */ +"https://mysite.herokuapp.com" = "https://mysite.herokuapp.com"; + +/* The title of a target alert action specifying an indefinitely long workout targets duration */ +"Indefinitely" = "Vô hạn định"; + +/* Title of the prediction input effect for insulin */ +"Insulin" = "Thuốc Insulin"; + +/* Description of the prediction input effect for insulin */ +"Insulin Absorbed (U) × Insulin Sensitivity (%1$@/U)" = "Lượng insulin tiêu hao (U) × Độ nhạy của insulin (%1$@/U)"; + +/* The title of the insulin delivery graph */ +"Insulin Delivery" = "Khối lượng tiêm insulin"; + +/* Details for missing data error when insulin effects are missing */ +"Insulin effects" = "Tác dụng của insulin"; + +/* Details for configuration error when insulin model is missing + The title text for the insulin model setting row */ +"Insulin Model" = "Chủng loại Insulin"; + +/* The title of the insulin sensitivities schedule screen + The title text for the insulin sensitivity schedule */ +"Insulin Sensitivities" = "Độ nhạy của Insulin"; + +/* The error message when invalid data was encountered. (1: details of invalid data) */ +"Invalid data: %1$@" = "Dữ liệu vô hiệu: %1$@"; + +/* The title text for the issue report cell */ +"Issue Report" = "Xuất bản Báo cáo"; + +/* Glucose HUD accessibility hint */ +"Launches CGM app" = "Phát hành ứng dụng CGM"; + +/* The loading message for the diagnostic report screen */ +"Loading..." = "Đang tải..."; + +/* The title of the loggly service */ +"Loggly" = "Loggly"; + +/* The notification title for a loop failure */ +"Loop Failure" = "Loop lỗi"; + +/* The notification alert describing a long-lasting loop failure. The substitution parameter is the time interval since the last loop */ +"Loop has not completed successfully in %@" = "Loop không hoạt động thành công trong %@"; + +/* The error message for missing data. (1: missing data details) */ +"Missing data: %1$@" = "Dữ liệu thiếu: %1$@"; + +/* Details for missing data error when momentum effects are missing */ +"Momentum effects" = "Hiệu ứng động lượng"; + +/* The title of the Nightscout service */ +"Nightscout" = "Nightscout"; + +/* The error message displayed for device connection errors. */ +"No connected devices, or failure during device connection" = "Không có thiết bị nào được kết nối, hoặc lỗi trong quá trình kết nối"; + +/* The title text for the override presets */ +"Override Presets" = "Ghi đè cài đặt trước"; + +/* The label of the pre-meal mode toggle button */ +"Pre-Meal Targets" = "Mục tiêu trước bữa ăn"; + +/* Message when offering bolus recommendation even though bg is below range and minBG is in future. (1: glucose time)(2: glucose number) */ +"Predicted glucose at %1$@ is %2$@." = "Dự đoán đường huyết vào lúc %1$@ là %2$@."; + +/* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ +"Predicted glucose of %1$@ is below your suspend threshold setting." = "Dự đoán đường huyết %1$@ là dưới ngưỡng tạm ngưng trong cài đặt của bạn."; + +/* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ +"Predicted: %1$@\nActual: %2$@ (%3$@)" = "Được dự đoán: %1$@\nThực tế: %2$@ (%3$@)"; + +/* The title of the pump section in settings */ +"Pump" = "Bơm"; + +/* The notification title for a low pump battery */ +"Pump Battery Low" = "Pin của bơm thấp"; + +/* The error message when pump data is too old to be used. (1: pump data age in minutes) */ +"Pump data is %1$@ old" = "Dữ liệu bơm %1$@ là cũ"; + +/* Details for configuration error when pump manager is missing */ +"Pump Manager" = "Trình quản lý bơm"; + +/* The notification title for an empty pump reservoir */ +"Pump Reservoir Empty" = "Ngăn chứa hết insulin"; + +/* The notification title for a low pump reservoir */ +"Pump Reservoir Low" = "Ngăn chứa insulin thấp"; + +/* The title of the cell indicating the pump is suspended */ +"Pump Suspended" = "Bơm đã tạm ngưng"; + +/* Title of insulin model preset */ +"Rapid-Acting – Adults" = "Thuốc tác động nhanh cho người lớn"; + +/* Title of insulin model preset */ +"Rapid-Acting – Children" = "Thuốc tác động nhanh cho trẻ em"; + +/* The error message when a recommendation has expired. (1: age of recommendation in minutes) */ +"Recommendation expired: %1$@ old" = "Khuyến cáo hết hạn: %1$@ phút"; + +/* The title of the cell displaying a recommended temp basal value */ +"Recommended Basal" = "Liều Basal khuyến nghị"; + +/* Accessibility hint describing recommended bolus units */ +"Recommended Bolus: %@ Units" = "Liều Bolus khuyến nghị: %@ Units"; + +/* Details for missing data error when reservoir data is missing */ +"Reservoir" = "Ngăn chứa insulin"; + +/* Title of the prediction input effect for retrospective correction */ +"Retrospective Correction" = "Liều Bổ sung còn hiệu lực"; + +/* The title of the notification action to retry a bolus command */ +"Retry" = "Thử lại"; + +/* The title of the services section in settings */ +"Services" = "Dịch vụ"; + +/* The label of the settings button */ +"Settings" = "Cài đặt"; + +/* Loop Completion HUD accessibility hint */ +"Shows last loop error" = "Hiển thị lỗi của loop trước đó"; + +/* Format fragment for a start time */ +"since %@" = "từ khi %@"; + +/* The title of the nightscout site URL credential */ +"Site URL" = "Site URL"; + +/* The format for the description of a temporary override start date */ +"starting at %@" = "bắt đầu lúc %@"; + +/* The title of the cell indicating a bolus is being sent */ +"Starting Bolus" = "Bắt đầu liều Bolus"; + +/* The title text in settings */ +"Suspend Threshold" = "Ngưỡng Tạm dừng"; + +/* The subtitle of the cell displaying an action to resume insulin delivery */ +"Tap to Resume" = "Chạm để tiếp tục"; + +/* Subtitle description of Walsh insulin model setting */ +"The legacy model used by Loop, allowing customization of action duration." = "Mô hình cũ được Loop sử dụng, cho phép tùy chỉnh thời lượng hành động."; + +/* Body of the alert describing a maximum bolus validation error. (1: The localized max bolus value) */ +"The maximum bolus amount is %@ Units" = "Số lượng bolus tối đa là %@ Units"; + +/* The short unit display string for international units of insulin */ +"U" = "U"; + +/* The format for the description of a temporary override end date */ +"until %@" = "cho đến khi %@"; + +/* The title of the alert controller used to select a duration for workout targets */ +"Use Workout Glucose Targets" = "Sử dụng Workout Glucose Targets"; + +/* Title of insulin model setting */ +"Walsh" = "Walsh"; + +/* Explanation of suspend threshold */ +"When current or forecasted glucose is below the suspend threshold, Loop will not recommend a bolus, and will always recommend a temporary basal rate of 0 units per hour." = "Khi mức glucose hiện tại hoặc được dự báo thấp hơn ngưỡng tạm dừng, Loop sẽ không khuyến nghị một liều bolus và sẽ luôn khuyến nghị liều basal là 0 unit mỗi giờ."; + +/* The label of the workout mode toggle button */ +"Workout Targets" = "Mục tiêu tập luyện"; + diff --git a/Loop/vi.lproj/Main.strings b/Loop/vi.lproj/Main.strings new file mode 100644 index 0000000000..addd1c59af --- /dev/null +++ b/Loop/vi.lproj/Main.strings @@ -0,0 +1,144 @@ +/* Class = "UINavigationItem"; title = "Status"; ObjectID = "3kU-n2-fha"; */ +"3kU-n2-fha.title" = "Tình trạng"; + +/* Class = "UILabel"; text = "3.5 U/hour @ 12:12 PM"; ObjectID = "5gz-kZ-iF1"; */ +"5gz-kZ-iF1.text" = "3.5 U/hour @ 12:12 PM"; + +/* Class = "UILabel"; text = "Bolus"; ObjectID = "5oA-6d-ZTL"; */ +"5oA-6d-ZTL.text" = "Liều Bolus"; + +/* Class = "UILabel"; text = "Pump ID"; ObjectID = "5TX-kX-nBo"; */ +"5TX-kX-nBo.text" = "Số ID của bơm"; + +/* Class = "UITextField"; accessibilityLabel = "Bolus Amount"; ObjectID = "7LT-50-ZzK"; */ +"7LT-50-ZzK.accessibilityLabel" = "Khối lượng liều Bolus"; + +/* Class = "UITextField"; placeholder = "0.0"; ObjectID = "7LT-50-ZzK"; */ +"7LT-50-ZzK.placeholder" = "0.0"; + +/* Class = "UILabel"; text = "Predicted"; ObjectID = "87H-N1-0vJ"; */ +"87H-N1-0vJ.text" = "Được dự đoán"; + +/* Class = "UILabel"; text = "Detail"; ObjectID = "aCb-Qs-bpu"; */ +"aCb-Qs-bpu.text" = "Chi tiết"; + +/* Class = "UINavigationItem"; title = "Bolus"; ObjectID = "aiu-ZA-zVa"; */ +"aiu-ZA-zVa.title" = "Liều Bolus"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "bIL-Ub-qYp"; */ +"bIL-Ub-qYp.text" = "Nhãn"; + +/* Class = "UILabel"; text = "Glucose Change"; ObjectID = "bq4-98-cQU"; */ +"bq4-98-cQU.text" = "Mức đường huyết thay đổi"; + +/* Class = "UILabel"; accessibilityLabel = "Units"; ObjectID = "BR0-dr-Fj2"; */ +"BR0-dr-Fj2.accessibilityLabel" = "Units"; + +/* Class = "UILabel"; text = "U"; ObjectID = "BR0-dr-Fj2"; */ +"BR0-dr-Fj2.text" = "U"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "cpo-Po-gFM"; */ +"cpo-Po-gFM.text" = "Nhãn"; + +/* Class = "UILabel"; text = "g Total"; ObjectID = "d3X-AN-tA5"; */ +"d3X-AN-tA5.text" = "g Tổng cộng"; + +/* Class = "UILabel"; text = "Future glucose is predicted by combining the effects of multiple inputs. Use this tool to toggle various inputs to see how they compare to the final prediction."; ObjectID = "D4C-I2-dhA"; */ +"D4C-I2-dhA.text" = "Glucose trong tương lai được dự đoán bằng cách kết hợp các tác động của nhiều dữ liệu đầu vào. Sử dụng công cụ này để chuyển đổi các đầu vào khác nhau để xem cách chúng so sánh với dự đoán cuối cùng."; + +/* Class = "UILabel"; text = "Label"; ObjectID = "d6m-qV-wWi"; */ +"d6m-qV-wWi.text" = "Nhãn"; + +/* Class = "UINavigationItem"; title = "Settings"; ObjectID = "dmB-PQ-B44"; */ +"dmB-PQ-B44.title" = "Cài đặt"; + +/* Class = "UILabel"; text = "DEVICES"; ObjectID = "DyC-Sv-qP8"; */ +"DyC-Sv-qP8.text" = "THIẾT BỊ"; + +/* Class = "UILabel"; text = "eventually 92 mg/dL"; ObjectID = "E41-FN-nkk"; */ +"E41-FN-nkk.text" = "kết quả 92 mg/dL"; + +/* Class = "UILabel"; text = "Observed"; ObjectID = "EAn-Ja-S1d"; */ +"EAn-Ja-S1d.text" = "Được quan sát"; + +/* Class = "UILabel"; text = "Active Carbohydrates: 40g"; ObjectID = "hHZ-uY-aKw"; */ +"hHZ-uY-aKw.text" = "Lượng Carbohydrates còn hoạt động: 40g"; + +/* Class = "UITableViewController"; title = "Carbohydrate Effects"; ObjectID = "hZZ-2S-lrd"; */ +"hZZ-2S-lrd.title" = "Carbohydrate Effects"; + +/* Class = "UILabel"; text = "Observed changes in glucose, subtracting changes modeled from insulin delivery, can be used to estimate carbohydrate absorption."; ObjectID = "IxU-As-glo"; */ +"IxU-As-glo.text" = "Những thay đổi quan sát được trong glucose, trừ đi những thay đổi được mô hình hóa từ việc cung cấp insulin, có thể được sử dụng để ước tính sự hấp thụ carbohydrate."; + +/* Class = "UILabel"; text = "Detail"; ObjectID = "J7x-W5-gwo"; */ +"J7x-W5-gwo.text" = "Detail"; + +/* Class = "UILabel"; text = "⚠ Glucose Predicted Below Range"; ObjectID = "k0g-P7-OVN"; */ +"k0g-P7-OVN.text" = "⚠ Đường huyết dự đoán nằm dưới phạm vi"; + +/* Class = "UILabel"; text = "Recommended Basal"; ObjectID = "k3F-Na-7mn"; */ +"k3F-Na-7mn.text" = "Khuyến nghị liều Basal"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "Krd-Aa-ret"; */ +"Krd-Aa-ret.text" = "Nhãn"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "l7l-mw-Oc9"; */ +"l7l-mw-Oc9.text" = "Nhãn"; + +/* Class = "UILabel"; text = "Tap to set"; ObjectID = "m9c-SQ-djE"; */ +"m9c-SQ-djE.text" = "Chạm để cài đặt"; + +/* Class = "UILabel"; accessibilityLabel = "Units"; ObjectID = "mVz-dr-xLU"; */ +"mVz-dr-xLU.accessibilityLabel" = "Units"; + +/* Class = "UILabel"; text = "U"; ObjectID = "mVz-dr-xLU"; */ +"mVz-dr-xLU.text" = "U"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "OFA-qT-ZAg"; */ +"OFA-qT-ZAg.text" = "Nhãn"; + +/* Class = "UITableViewController"; title = "Predicted Glucose"; ObjectID = "PA3-sP-cWY"; */ +"PA3-sP-cWY.title" = "Đường huyết được dự đoán"; + +/* Class = "UITableViewController"; title = "Insulin Model"; ObjectID = "pi6-Dh-72V"; */ +"pi6-Dh-72V.title" = "Insulin Model"; + +/* Class = "UILabel"; text = "An insulin activity model is used to estimate effects of insulin on glucose levels. An accurate model can help prevent insulin stacking and safely recommend corrective treatments."; ObjectID = "PJv-p9-cFe"; */ +"PJv-p9-cFe.text" = "Mô hình hoạt động của insulin được sử dụng để ước tính tác động của insulin lên mức glucose. Một mô hình chính xác có thể giúp ngăn việc chồng liều và đưa ra khuyến nghị một liều bổ sung an toàn."; + +/* Class = "UILabel"; text = "eventually 92 mg/dL"; ObjectID = "Rse-x8-amW"; */ +"Rse-x8-amW.text" = "kết quả là 92 mg/dL"; + +/* Class = "UILabel"; text = "g COB"; ObjectID = "SQx-au-ZcM"; */ +"SQx-au-ZcM.text" = "g COB"; + +/* Class = "UILabel"; text = "Glucose"; ObjectID = "tuw-av-A3x"; */ +"tuw-av-A3x.text" = "Đường huyết"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "ufi-Kj-33k"; */ +"ufi-Kj-33k.text" = "Nhãn"; + +/* Class = "UILabel"; text = "Active Insulin: 1.5U"; ObjectID = "viU-7a-kbI"; */ +"viU-7a-kbI.text" = "Lượng insulin còn hoạt động: 1.5U"; + +/* Class = "UINavigationItem"; title = "Carbohydrates"; ObjectID = "Vpi-5b-bY5"; */ +"Vpi-5b-bY5.title" = "Carbohydrates"; + +/* Class = "UITextField"; text = "4 hour"; ObjectID = "Wk3-xv-IM5"; */ +"Wk3-xv-IM5.text" = "4 giờ"; + +/* Class = "UIButton"; normalTitle = "Deliver"; ObjectID = "Ya0-9b-ZAS"; */ +"Ya0-9b-ZAS.normalTitle" = "Deliver"; + +/* Class = "UILabel"; text = "0"; ObjectID = "yn7-2M-jZz"; */ +"yn7-2M-jZz.text" = "0"; + +/* Class = "UILabel"; text = "Recommended"; ObjectID = "ywT-OR-NnU"; */ +"ywT-OR-NnU.text" = "Được khuyến nghị"; + +/* Class = "UILabel"; text = "Title"; ObjectID = "zbc-87-wxZ"; */ +"zbc-87-wxZ.text" = "Title"; + +/* Class = "UILabel"; text = "0"; ObjectID = "zvZ-uf-zMX"; */ +"zvZ-uf-zMX.text" = "0"; + diff --git a/Loop/zh-Hans.lproj/InfoPlist.strings b/Loop/zh-Hans.lproj/InfoPlist.strings index 03b550bc9c..09fb0ea7e5 100644 --- a/Loop/zh-Hans.lproj/InfoPlist.strings +++ b/Loop/zh-Hans.lproj/InfoPlist.strings @@ -1,9 +1,9 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "Loop"; - /* Bundle name */ "CFBundleName" = "$(PRODUCT_NAME)"; +/* Privacy - Bluetooth Always Usage Description */ +"NSBluetoothAlwaysUsageDescription" = "蓝牙用于与胰岛素泵和连续血糖监测设备进行通信"; + /* Privacy - Bluetooth Peripheral Usage Description */ "NSBluetoothPeripheralUsageDescription" = "蓝牙用于与胰岛素泵和连续血糖监测设备进行通信"; diff --git a/Loop/zh-Hans.lproj/LaunchScreen.strings b/Loop/zh-Hans.lproj/LaunchScreen.strings deleted file mode 100644 index 8b13789179..0000000000 --- a/Loop/zh-Hans.lproj/LaunchScreen.strings +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Loop/zh-Hans.lproj/Localizable.strings b/Loop/zh-Hans.lproj/Localizable.strings index d2b3b24d9d..3532625b76 100644 --- a/Loop/zh-Hans.lproj/Localizable.strings +++ b/Loop/zh-Hans.lproj/Localizable.strings @@ -1,10 +1,22 @@ -/* Created with Localizable Strings Merge, the swiss-army knife for iOS and OS X localization. */ - /* The string format appended to active insulin that describes pending insulin. (1: pending insulin) */ -" (pending: %@)" = "(待执行: %@)"; +" (pending: %@)" = " (待执行: %@)"; + +/* The format for an active override preset. (1: preset symbol)(2: preset name) */ +"%@ %@" = "%1$@ %2$@"; + +/* Formats absorbed carb value */ +"%@ absorbed" = "%@ 已吸收"; + +/* The subtitle format describing total insulin. (1: localized insulin total) */ +"%@ U Total" = "%@ 单位 总量"; + +/* Appends a full-stop to a statement */ +"%@." = "%@."; + +/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ +"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; -/* Format string for carb ratio average. (1: value)(2: carb unit) - Format string for insulin sensitivity average (1: value)(2: glucose unit) */ +/* Format string for carb ratio average. (1: value)(2: carb unit) */ "%1$@ %2$@/U" = "%1$@ %2$@/单位"; /* Formats (1: carb start time) and (2: carb absorption duration) */ @@ -22,24 +34,9 @@ /* The format string for the app name and version number. (1: bundle name)(2: bundle version) */ "%1$@ v%2$@" = "%1$@ v%2$@"; -/* Format string for glucose target range. (1: Min target)(2: Max target)(3: glucose unit) */ -"%1$@ – %2$@ %3$@" = "%1$@ – %2$@ %3$@"; - -/* Format string describing glucose units per minute (1: glucose unit string) */ -"%1$@/min" = "%1$@/分钟"; - /* Formats (1: carb value) and (2: food type) */ "%1$@: %2$@" = "%1$@: %2$@"; -/* Formats absorbed carb value */ -"%@ absorbed" = "%@ 已吸收"; - -/* The subtitle format describing total insulin. (1: localized insulin total) */ -"%@ U Total" = "%@ 单位 总量"; - -/* Appends a full-stop to a statement */ -"%@." = "%@."; - /* Description of the prediction input effect for glucose momentum */ "15 min glucose regression coefficient (b₁), continued with decay over 30 min" = "15分钟葡萄糖回归系数(b1),持续30分钟衰减"; @@ -67,15 +64,20 @@ /* The string format describing active insulin. (1: localized insulin value description) */ "Active Insulin: %@" = "活性胰岛素: %@"; +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "添加碳水化合物"; + +/* Action sheet title selecting CGM + Title text for button to set up a CGM */ +"Add CGM" = "添加CGM"; + /* The label of the carb entry button */ "Add Meal" = "添加用餐信息"; -/* Title text for button to set up a new pump */ +/* Action sheet title selecting Pump + Title text for button to set up a new pump */ "Add Pump" = "添加泵"; -/* The title of the section containing algorithm settings */ -"Algorithm Settings" = "算法设置"; - /* The title of the Amplitude service */ "Amplitude" = "Amplitude"; @@ -88,6 +90,9 @@ /* The title of the nightscout API secret credential */ "API Secret" = "API密码"; +/* Confirmation message for deleting a CGM */ +"Are you sure you want to delete this CGM?" = "确定要删除该CGM数据源?"; + /* Format fragment for a specific time */ "at %@" = "%@分钟内"; @@ -105,9 +110,18 @@ The notification title for a bolus failure */ "Bolus" = "大剂量"; +/* The format string for bolus progress. (1: delivered volume)(2: total volume) */ +"Bolused %1$@ of %2$@" = "大剂量已输注%1$@ of %2$@"; + +/* The format string for bolus in progress showing total volume. (1: total volume) */ +"Bolusing %1$@" = "%1$@大剂量注射中"; + /* The title of the cancel action in an action sheet */ "Cancel" = "取消"; +/* The title of the cell indicating a bolus is being canceled */ +"Canceling Bolus" = "取消大剂量"; + /* Details for missing data error when carb effects are missing */ "Carb effects" = "碳水效果"; @@ -139,6 +153,9 @@ /* The title text for the looping enabled switch cell */ "Closed Loop" = "开启闭环"; +/* The title of the action used to dismiss an error alert */ +"com.loudnate.LoopKit.errorAlertActionTitle" = "OK"; + /* The title of the configuration section in settings */ "Configuration" = "配置"; @@ -155,14 +172,14 @@ /* Message when offering bolus recommendation even though bg is below range. (1: glucose value) */ "Current glucose of %1$@ is below correction range." = "当前葡萄糖%1$@低于修正范围"; +/* The title of the cell indicating a generic temporary override is enabled */ +"Custom Override" = "自定义覆盖"; + /* The title of the Loggly customer token credential */ "Customer Token" = "用户Token"; -/* The short unit display string for decibles */ -"dB" = "dB"; - -/* The title of the button to remove the credentials for a service */ -"Delete Account" = "删除账户"; +/* Button title to delete CGM */ +"Delete CGM" = "删除CGM数据源"; /* Title text for delivery limits */ "Delivery Limits" = "输注限制"; @@ -170,17 +187,17 @@ /* The action hint of the workout mode toggle button when enabled */ "Disables" = "禁用"; -/* Title of the switch which toggles retrospective correction effects */ -"Enable Retrospective Correction" = "启用回溯修正算法"; - /* The action hint of the workout mode toggle button when disabled */ "Enables" = "启用"; /* The placeholder text instructing users to enter a suspend treshold */ "Enter suspend threshold" = "输入暂停阈值"; -/* The placeholder text instructing users how to enter a transmitter ID */ -"Enter the 6-digit transmitter ID" = "输入6位数的发射器编号"; +/* The alert title for an error while canceling a bolus */ +"Error Canceling Bolus" = "无法取消大剂量"; + +/* The alert title for a resume error */ +"Error Resuming" = "无法恢复输注"; /* The subtitle format describing eventual glucose. (1: localized glucose value description) */ "Eventually %@" = "最终 %@"; @@ -194,15 +211,6 @@ /* The format string used to describe a finite workout targets duration */ "For %1$@" = "对于 %1$@"; -/* The short unit display string for grams */ -"g" = "克"; - -/* The title text for the G4 Share Receiver switch cell */ -"G4 Share Receiver" = "带有蓝牙功能G4接收器"; - -/* The title text for the G5/G6 Transmitter switch cell */ -"G5/G6 Transmitter" = "G5/G6发射器"; - /* The title of the glucose and prediction graph */ "Glucose" = "葡萄糖"; @@ -262,27 +270,21 @@ /* The notification alert describing a long-lasting loop failure. The substitution parameter is the time interval since the last loop */ "Loop has not completed successfully in %@" = "Loop运行异常 %@"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "毫克/分升"; - /* The error message for missing data. (1: missing data details) */ "Missing data: %1$@" = "数据缺失: %1$@"; -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "毫摩尔/升"; - /* Details for missing data error when momentum effects are missing */ "Momentum effects" = "葡萄糖动量效应"; -/* Sensor state description for the non-valid state */ -"Needs Attention" = "请注意"; - /* The title of the Nightscout service */ "Nightscout" = "Nightscout"; /* The error message displayed for device connection errors. */ "No connected devices, or failure during device connection" = "没有连接的设备,或设备连接期间发生故障"; +/* The title text for the override presets */ +"Override Presets" = "覆盖预设置"; + /* The label of the pre-meal mode toggle button */ "Pre-Meal Targets" = "餐前目标"; @@ -292,6 +294,9 @@ /* Notice message when recommending bolus when BG is below the suspend threshold. (1: glucose value) */ "Predicted glucose of %1$@ is below your suspend threshold setting." = "预测葡萄糖%1$@低于您的暂停阈值设置"; +/* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ +"Predicted: %1$@\nActual: %2$@ (%3$@)" = "预测值: %1$@\n实际值: %2$@ (%3$@)"; + /* The title of the pump section in settings */ "Pump" = "胰岛素泵"; @@ -310,6 +315,9 @@ /* The notification title for a low pump reservoir */ "Pump Reservoir Low" = "药量低"; +/* The title of the cell indicating the pump is suspended */ +"Pump Suspended" = "泵已暂停输注"; + /* Title of insulin model preset */ "Rapid-Acting – Adults" = "速效胰岛素 - 成人模型"; @@ -349,8 +357,8 @@ /* The title of the nightscout site URL credential */ "Site URL" = "网址"; -/* The title text for the Medtronic sensor switch cell */ -"Sof-Sensor / Enlite" = "Sof-Sensor / Enlite"; +/* The format for the description of a temporary override start date */ +"starting at %@" = "开始于 %@"; /* The title of the cell indicating a bolus is being sent */ "Starting Bolus" = "开始输注大剂量"; @@ -358,20 +366,20 @@ /* The title text in settings */ "Suspend Threshold" = "暂停阈值"; +/* The subtitle of the cell displaying an action to resume insulin delivery */ +"Tap to Resume" = "点击恢复输注"; + /* Subtitle description of Walsh insulin model setting */ "The legacy model used by Loop, allowing customization of action duration." = "Loop使用的默认模型参数,您可以自行修改胰岛素代谢时间。"; /* Body of the alert describing a maximum bolus validation error. (1: The localized max bolus value) */ "The maximum bolus amount is %@ Units" = "大剂量输注最大限制为%@单位"; -/* Instructions on where to find the transmitter ID */ -"The transmitter ID can be found printed on the back of the device, on the side of the box it came in, and from within the settings menus of the receiver and mobile app." = "G5发射器编号可以在发射器背部,或者发射器包盒、以及接收器找找到"; - -/* The description of the switch which toggles retrospective correction effects */ -"This will more aggresively increase or decrease basal delivery when glucose movement doesn't match the carbohydrate and insulin-based model." = "当葡萄糖与碳水化合物和基于胰岛素的模型不匹配时,这将更积极地增加或减少基础递送。"; - /* The short unit display string for international units of insulin */ -"U" = "单位"; +"U" = "U"; + +/* The format for the description of a temporary override end date */ +"until %@" = "直到 %@"; /* The title of the alert controller used to select a duration for workout targets */ "Use Workout Glucose Targets" = "使用运动状态的血糖目标"; diff --git a/Loop/zh-Hans.lproj/Main.strings b/Loop/zh-Hans.lproj/Main.strings index 969307fa2d..1010ddee60 100644 --- a/Loop/zh-Hans.lproj/Main.strings +++ b/Loop/zh-Hans.lproj/Main.strings @@ -25,9 +25,6 @@ /* Class = "UINavigationItem"; title = "Bolus"; ObjectID = "aiu-ZA-zVa"; */ "aiu-ZA-zVa.title" = "大剂量"; -/* Class = "UILabel"; text = "Title"; ObjectID = "B9F-K3-7RI"; */ -"B9F-K3-7RI.text" = "标题"; - /* Class = "UILabel"; text = "Label"; ObjectID = "bIL-Ub-qYp"; */ "bIL-Ub-qYp.text" = "标签"; diff --git a/LoopTests/de.lproj/InfoPlist.strings b/LoopTests/de.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f9040..0000000000 --- a/LoopTests/de.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/LoopTests/es.lproj/InfoPlist.strings b/LoopTests/es.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f9040..0000000000 --- a/LoopTests/es.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/LoopTests/fr.lproj/InfoPlist.strings b/LoopTests/fr.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f9040..0000000000 --- a/LoopTests/fr.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/LoopTests/it.lproj/InfoPlist.strings b/LoopTests/it.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f9040..0000000000 --- a/LoopTests/it.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/LoopTests/nb.lproj/InfoPlist.strings b/LoopTests/nb.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f9040..0000000000 --- a/LoopTests/nb.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/LoopTests/nl.lproj/InfoPlist.strings b/LoopTests/nl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f9040..0000000000 --- a/LoopTests/nl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/LoopTests/pl.lproj/InfoPlist.strings b/LoopTests/pl.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f9040..0000000000 --- a/LoopTests/pl.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/LoopTests/ru.lproj/InfoPlist.strings b/LoopTests/ru.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f9040..0000000000 --- a/LoopTests/ru.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/LoopTests/zh-Hans.lproj/InfoPlist.strings b/LoopTests/zh-Hans.lproj/InfoPlist.strings deleted file mode 100644 index bbcf8f9040..0000000000 --- a/LoopTests/zh-Hans.lproj/InfoPlist.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/LoopUI/Base.lproj/Localizable.strings b/LoopUI/Base.lproj/Localizable.strings index e232ec6b42..0afa563329 100644 --- a/LoopUI/Base.lproj/Localizable.strings +++ b/LoopUI/Base.lproj/Localizable.strings @@ -1,51 +1,30 @@ -/* Format string describing the time interval since the last completion date. (1: The localized date components */ -"%@ ago" = "%@ ago"; - -/* The format string describing the basal rate. */ -"%@ U" = "%@ U"; - -/* Format string for reservoir volume. (1: The localized volume) */ -"%@U" = "%@U"; - /* Accessbility format value describing glucose: (1: glucose number)(2: glucose time) */ "%1$@ at %2$@" = "%1$@ at %2$@"; /* Accessibility format string describing the basal rate. (1: localized basal rate value)(2: last updated time) */ "%1$@ units per hour at %2$@" = "%1$@ units per hour at %2$@"; -/* Accessibility format string for (1: localized volume)(2: time) */ -"%1$@ units remaining at %2$@" = "%1$@ units remaining at %2$@"; +/* Format string describing glucose units per minute (1: glucose unit string) */ +"%1$@/min" = "%1$@/min"; -/* Accessibility hint describing completion HUD for a closed loop */ -"Closed loop" = "Closed loop"; +/* Format string describing the time interval since the last completion date. (1: The localized date components */ +"%@ ago" = "%@ ago"; -/* The short unit display string for decibles */ -"dB" = "dB"; +/* The format string describing the basal rate. */ +"%@ U" = "%@ U"; -/* The short unit display string for grams */ -"g" = "g"; +/* Accessibility hint describing completion HUD for a closed loop */ +"Closed loop" = "Closed loop"; /* Accessbility format label describing the time interval since the last completion date. (1: The localized date components) */ "Loop ran %@ ago" = "Loop ran %@ ago"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; - -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; - /* Accessibility label component for glucose HUD describing an invalid state */ "Needs attention" = "Needs attention"; /* Accessbility hint describing completion HUD for an open loop */ "Open loop" = "Open loop"; -/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ -"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; - -/* The short unit display string for international units of insulin */ -"U" = "U"; - /* Accessibility value for an unknown value */ "Unknown" = "Unknown"; diff --git a/LoopUI/da.lproj/Localizable.strings b/LoopUI/da.lproj/Localizable.strings new file mode 100644 index 0000000000..deda259d07 --- /dev/null +++ b/LoopUI/da.lproj/Localizable.strings @@ -0,0 +1,36 @@ +/* Format string describing the time interval since the last completion date. (1: The localized date components */ +"%@ ago" = "%@ siden"; + +/* The format string describing the basal rate. */ +"%@ U" = "%@ E"; + +/* Accessbility format value describing glucose: (1: glucose number)(2: glucose time) */ +"%1$@ at %2$@" = "%1$@ ved %2$@"; + +/* Accessibility format string describing the basal rate. (1: localized basal rate value)(2: last updated time) */ +"%1$@ units per hour at %2$@" = "%1$@ enheder per time ved %2$@"; + +/* Format string describing glucose units per minute (1: glucose unit string) */ +"%1$@/min" = "%1$@/min"; + +/* Accessibility hint describing completion HUD for a closed loop */ +"Closed loop" = "Lukket loop"; + +/* Accessbility format label describing the time interval since the last completion date. (1: The localized date components) */ +"Loop ran %@ ago" = "Loop kørte for %@ siden"; + +/* Accessibility label component for glucose HUD describing an invalid state */ +"Needs attention" = "Behøver opmærksomhed"; + +/* Accessbility hint describing completion HUD for an open loop */ +"Open loop" = "Åben loop"; + +/* The short unit display string for international units of insulin */ +"U" = "E"; + +/* Accessibility value for an unknown value */ +"Unknown" = "Ukendt"; + +/* Acessibility label describing completion HUD waiting for first run */ +"Waiting for first run" = "Venter på første kørsel"; + diff --git a/LoopUI/de.lproj/InfoPlist.strings b/LoopUI/de.lproj/InfoPlist.strings deleted file mode 100644 index 13ab538314..0000000000 --- a/LoopUI/de.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "LoopUI"; - -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/LoopUI/de.lproj/Localizable.strings b/LoopUI/de.lproj/Localizable.strings index 143d6d6806..3e9e2bd432 100644 --- a/LoopUI/de.lproj/Localizable.strings +++ b/LoopUI/de.lproj/Localizable.strings @@ -2,10 +2,7 @@ "%@ ago" = "vor %@"; /* The format string describing the basal rate. */ -"%@ U" = "%@ E"; - -/* Format string for reservoir volume. (1: The localized volume) */ -"%@U" = "%@E"; +"%@ U" = "%@ IE"; /* Accessbility format value describing glucose: (1: glucose number)(2: glucose time) */ "%1$@ at %2$@" = "%1$@ in %2$@"; @@ -13,38 +10,23 @@ /* Accessibility format string describing the basal rate. (1: localized basal rate value)(2: last updated time) */ "%1$@ units per hour at %2$@" = "%1$@ Einheiten pro Stunde in %2$@"; -/* Accessibility format string for (1: localized volume)(2: time) */ -"%1$@ units remaining at %2$@" = "%1$@ verbleibende Einheiten in %2$@"; +/* Format string describing glucose units per minute (1: glucose unit string) */ +"%1$@/min" = "%1$@/min"; /* Accessibility hint describing completion HUD for a closed loop */ "Closed loop" = "Geschlossener Loop"; -/* The short unit display string for decibles */ -"dB" = "dB"; - -/* The short unit display string for grams */ -"g" = "g"; - /* Accessbility format label describing the time interval since the last completion date. (1: The localized date components) */ "Loop ran %@ ago" = "Loop lief seit %@"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; - -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; - /* Accessibility label component for glucose HUD describing an invalid state */ "Needs attention" = "Erfordert Aufmerksamkeit"; /* Accessbility hint describing completion HUD for an open loop */ "Open loop" = "Offener Loop"; -/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ -"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; - /* The short unit display string for international units of insulin */ -"U" = "E"; +"U" = "IE"; /* Accessibility value for an unknown value */ "Unknown" = "Unbekannt"; diff --git a/LoopUI/en.lproj/Localizable.strings b/LoopUI/en.lproj/Localizable.strings new file mode 100644 index 0000000000..0afa563329 --- /dev/null +++ b/LoopUI/en.lproj/Localizable.strings @@ -0,0 +1,32 @@ +/* Accessbility format value describing glucose: (1: glucose number)(2: glucose time) */ +"%1$@ at %2$@" = "%1$@ at %2$@"; + +/* Accessibility format string describing the basal rate. (1: localized basal rate value)(2: last updated time) */ +"%1$@ units per hour at %2$@" = "%1$@ units per hour at %2$@"; + +/* Format string describing glucose units per minute (1: glucose unit string) */ +"%1$@/min" = "%1$@/min"; + +/* Format string describing the time interval since the last completion date. (1: The localized date components */ +"%@ ago" = "%@ ago"; + +/* The format string describing the basal rate. */ +"%@ U" = "%@ U"; + +/* Accessibility hint describing completion HUD for a closed loop */ +"Closed loop" = "Closed loop"; + +/* Accessbility format label describing the time interval since the last completion date. (1: The localized date components) */ +"Loop ran %@ ago" = "Loop ran %@ ago"; + +/* Accessibility label component for glucose HUD describing an invalid state */ +"Needs attention" = "Needs attention"; + +/* Accessbility hint describing completion HUD for an open loop */ +"Open loop" = "Open loop"; + +/* Accessibility value for an unknown value */ +"Unknown" = "Unknown"; + +/* Acessibility label describing completion HUD waiting for first run */ +"Waiting for first run" = "Waiting for first run"; diff --git a/LoopUI/es.lproj/InfoPlist.strings b/LoopUI/es.lproj/InfoPlist.strings deleted file mode 100644 index 13ab538314..0000000000 --- a/LoopUI/es.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "LoopUI"; - -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/LoopUI/es.lproj/Localizable.strings b/LoopUI/es.lproj/Localizable.strings index c629b57ec6..2c55677a48 100644 --- a/LoopUI/es.lproj/Localizable.strings +++ b/LoopUI/es.lproj/Localizable.strings @@ -1,54 +1,32 @@ -/* Format string describing the time interval since the last completion date. (1: The localized date components */ -"%@ ago" = "hace %@"; - -/* The format string describing the basal rate. */ -"%@ U" = "%@ U"; - -/* Format string for reservoir volume. (1: The localized volume) */ -"%@U" = "%@U"; - /* Accessbility format value describing glucose: (1: glucose number)(2: glucose time) */ "%1$@ at %2$@" = "%1$@ en %2$@"; /* Accessibility format string describing the basal rate. (1: localized basal rate value)(2: last updated time) */ "%1$@ units per hour at %2$@" = "%1$@ unidades por hora en %2$@"; -/* Accessibility format string for (1: localized volume)(2: time) */ -"%1$@ units remaining at %2$@" = "%1$@ unidades restantes a %2$@"; +/* Format string describing glucose units per minute (1: glucose unit string) */ +"%1$@/min" = "%1$@/min"; -/* Accessibility hint describing completion HUD for a closed loop */ -"Closed loop" = "Asa cerrada"; +/* Format string describing the time interval since the last completion date. (1: The localized date components */ +"%@ ago" = "hace %@"; -/* The short unit display string for decibles */ -"dB" = "dB"; +/* The format string describing the basal rate. */ +"%@ U" = "%@ U"; -/* The short unit display string for grams */ -"g" = "g"; +/* Accessibility hint describing completion HUD for a closed loop */ +"Closed loop" = "Asa cerrada"; /* Accessbility format label describing the time interval since the last completion date. (1: The localized date components) */ "Loop ran %@ ago" = "Loop corrió hace %@"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; - -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; - /* Accessibility label component for glucose HUD describing an invalid state */ "Needs attention" = "Necesita atención"; /* Accessbility hint describing completion HUD for an open loop */ "Open loop" = "Asa abierta"; -/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ -"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; - -/* The short unit display string for international units of insulin */ -"U" = "U"; - /* Accessibility value for an unknown value */ "Unknown" = "Desconocido"; /* Acessibility label describing completion HUD waiting for first run */ "Waiting for first run" = "Esperando el primer uso"; - diff --git a/LoopUI/fi.lproj/Localizable.strings b/LoopUI/fi.lproj/Localizable.strings new file mode 100644 index 0000000000..a2c595ee7b --- /dev/null +++ b/LoopUI/fi.lproj/Localizable.strings @@ -0,0 +1,33 @@ +/* Format string describing the time interval since the last completion date. (1: The localized date components */ +"%@ ago" = "%@ sitten"; + +/* The format string describing the basal rate. */ +"%@ U" = "%@ U"; + +/* Accessbility format value describing glucose: (1: glucose number)(2: glucose time) */ +"%1$@ at %2$@" = "%1$@ klo %2$@"; + +/* Accessibility format string describing the basal rate. (1: localized basal rate value)(2: last updated time) */ +"%1$@ units per hour at %2$@" = "%1$@ yksikköä tunnissa klo %2$@"; + +/* Format string describing glucose units per minute (1: glucose unit string) */ +"%1$@/min" = "%1$@/min"; + +/* Accessibility hint describing completion HUD for a closed loop */ +"Closed loop" = "Suljettu säätö"; + +/* Accessbility format label describing the time interval since the last completion date. (1: The localized date components) */ +"Loop ran %@ ago" = "Viimeisin säätökierros %@ sitten"; + +/* Accessibility label component for glucose HUD describing an invalid state */ +"Needs attention" = "Tarvitsee huomion"; + +/* Accessbility hint describing completion HUD for an open loop */ +"Open loop" = "Avoin säätö"; + +/* Accessibility value for an unknown value */ +"Unknown" = "Tuntematon"; + +/* Acessibility label describing completion HUD waiting for first run */ +"Waiting for first run" = "Odotetaan ensimmäistä ..."; + diff --git a/LoopUI/fr.lproj/InfoPlist.strings b/LoopUI/fr.lproj/InfoPlist.strings deleted file mode 100644 index 13ab538314..0000000000 --- a/LoopUI/fr.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "LoopUI"; - -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/LoopUI/fr.lproj/Localizable.strings b/LoopUI/fr.lproj/Localizable.strings index 5a8db48187..3784a86b91 100644 --- a/LoopUI/fr.lproj/Localizable.strings +++ b/LoopUI/fr.lproj/Localizable.strings @@ -4,48 +4,27 @@ /* The format string describing the basal rate. */ "%@ U" = "%@ U"; -/* Format string for reservoir volume. (1: The localized volume) */ -"%@U" = "%@U"; - /* Accessbility format value describing glucose: (1: glucose number)(2: glucose time) */ "%1$@ at %2$@" = "%1$@ à %2$@"; /* Accessibility format string describing the basal rate. (1: localized basal rate value)(2: last updated time) */ "%1$@ units per hour at %2$@" = "%1$@ unités par heure à %2$@"; -/* Accessibility format string for (1: localized volume)(2: time) */ -"%1$@ units remaining at %2$@" = "%1$@ unités restants à %2$@"; +/* Format string describing glucose units per minute (1: glucose unit string) */ +"%1$@/min" = "%1$@/min"; /* Accessibility hint describing completion HUD for a closed loop */ "Closed loop" = "Loop fermée"; -/* The short unit display string for decibles */ -"dB" = "dB"; - -/* The short unit display string for grams */ -"g" = "g"; - /* Accessbility format label describing the time interval since the last completion date. (1: The localized date components) */ "Loop ran %@ ago" = "Loop a roulé il y a %@"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; - -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; - /* Accessibility label component for glucose HUD describing an invalid state */ "Needs attention" = "Nécessite de l'attention"; /* Accessbility hint describing completion HUD for an open loop */ "Open loop" = "Loop ouverte"; -/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ -"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; - -/* The short unit display string for international units of insulin */ -"U" = "U"; - /* Accessibility value for an unknown value */ "Unknown" = "Inconnu"; diff --git a/LoopUI/it.lproj/InfoPlist.strings b/LoopUI/it.lproj/InfoPlist.strings deleted file mode 100644 index 13ab538314..0000000000 --- a/LoopUI/it.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "LoopUI"; - -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/LoopUI/it.lproj/Localizable.strings b/LoopUI/it.lproj/Localizable.strings index 00424f37e1..f0ed716021 100644 --- a/LoopUI/it.lproj/Localizable.strings +++ b/LoopUI/it.lproj/Localizable.strings @@ -4,48 +4,27 @@ /* The format string describing the basal rate. */ "%@ U" = "%@ U"; -/* Format string for reservoir volume. (1: The localized volume) */ -"%@U" = "%@U"; - /* Accessbility format value describing glucose: (1: glucose number)(2: glucose time) */ "%1$@ at %2$@" = "%1$@ a %2$@"; /* Accessibility format string describing the basal rate. (1: localized basal rate value)(2: last updated time) */ "%1$@ units per hour at %2$@" = "%1$@ unità per ora a %2$@"; -/* Accessibility format string for (1: localized volume)(2: time) */ -"%1$@ units remaining at %2$@" = "%1$@ unità residua a %2$@"; +/* Format string describing glucose units per minute (1: glucose unit string) */ +"%1$@/min" = "%1$@/min"; /* Accessibility hint describing completion HUD for a closed loop */ "Closed loop" = "Loop chiuso"; -/* The short unit display string for decibles */ -"dB" = "dB"; - -/* The short unit display string for grams */ -"g" = "g"; - /* Accessbility format label describing the time interval since the last completion date. (1: The localized date components) */ "Loop ran %@ ago" = "Loop funziona %@ fa"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; - -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; - /* Accessibility label component for glucose HUD describing an invalid state */ "Needs attention" = "Esige attenzione"; /* Accessbility hint describing completion HUD for an open loop */ "Open loop" = "Loop aperto"; -/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ -"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; - -/* The short unit display string for international units of insulin */ -"U" = "U"; - /* Accessibility value for an unknown value */ "Unknown" = "Sconosciuto"; diff --git a/LoopUI/ja.lproj/Localizable.strings b/LoopUI/ja.lproj/Localizable.strings new file mode 100644 index 0000000000..dc0fe55bca --- /dev/null +++ b/LoopUI/ja.lproj/Localizable.strings @@ -0,0 +1,33 @@ +/* Format string describing the time interval since the last completion date. (1: The localized date components */ +"%@ ago" = "%@ 前"; + +/* The format string describing the basal rate. */ +"%@ U" = "%@ U"; + +/* Accessbility format value describing glucose: (1: glucose number)(2: glucose time) */ +"%1$@ at %2$@" = "%1$@ - %2$@"; + +/* Accessibility format string describing the basal rate. (1: localized basal rate value)(2: last updated time) */ +"%1$@ units per hour at %2$@" = "%1$@ U/時 @ %2$@"; + +/* Format string describing glucose units per minute (1: glucose unit string) */ +"%1$@/min" = "%1$@/分"; + +/* Accessibility hint describing completion HUD for a closed loop */ +"Closed loop" = "クローズドループ"; + +/* Accessbility format label describing the time interval since the last completion date. (1: The localized date components) */ +"Loop ran %@ ago" = "ループは %@ 前に作動しました"; + +/* Accessibility label component for glucose HUD describing an invalid state */ +"Needs attention" = "注意が必要です"; + +/* Accessbility hint describing completion HUD for an open loop */ +"Open loop" = "オープンループ"; + +/* Accessibility value for an unknown value */ +"Unknown" = "不明"; + +/* Acessibility label describing completion HUD waiting for first run */ +"Waiting for first run" = "開始待機中"; + diff --git a/LoopUI/nb.lproj/InfoPlist.strings b/LoopUI/nb.lproj/InfoPlist.strings deleted file mode 100644 index 5c1fb13406..0000000000 --- a/LoopUI/nb.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* (No Comment) */ -"CFBundleDisplayName" = "LoopUI"; - -/* (No Comment) */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/LoopUI/nb.lproj/Localizable.strings b/LoopUI/nb.lproj/Localizable.strings index 0a8b6ffba7..e2ee3e2432 100644 --- a/LoopUI/nb.lproj/Localizable.strings +++ b/LoopUI/nb.lproj/Localizable.strings @@ -4,47 +4,29 @@ /* The format string describing the basal rate. */ "%@ U" = "%@ E"; -/* Format string for reservoir volume. (1: The localized volume) */ -"%@U" = "%@E"; - /* Accessbility format value describing glucose: (1: glucose number)(2: glucose time) */ "%1$@ at %2$@" = "%1$@ kl. %2$@"; /* Accessibility format string describing the basal rate. (1: localized basal rate value)(2: last updated time) */ "%1$@ units per hour at %2$@" = "%1$@ enheter per time kl. %2$@"; -/* Accessibility format string for (1: localized volume)(2: time) */ -"%1$@ units remaining at %2$@" = "%1$@ enheter igjen kl. %2$@"; +/* Format string describing glucose units per minute (1: glucose unit string) */ +"%1$@/min" = "%1$@/min"; /* Accessibility hint describing completion HUD for a closed loop */ "Closed loop" = "Lukket Loop"; -/* The short unit display string for decibles */ -"dB" = "dB"; - -/* The short unit display string for grams */ -"g" = "g"; - /* Accessbility format label describing the time interval since the last completion date. (1: The localized date components) */ "Loop ran %@ ago" = "Loop kjørte %@ siden"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; - -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; - /* Accessibility label component for glucose HUD describing an invalid state */ "Needs attention" = "Trenger tilsyn"; /* Accessbility hint describing completion HUD for an open loop */ "Open loop" = "Åpen Loop"; -/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ -"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; - /* The short unit display string for international units of insulin */ -"U" = "U"; +"U" = "E"; /* Accessibility value for an unknown value */ "Unknown" = "Ukjent"; diff --git a/LoopUI/nl.lproj/InfoPlist.strings b/LoopUI/nl.lproj/InfoPlist.strings deleted file mode 100644 index 5c1fb13406..0000000000 --- a/LoopUI/nl.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* (No Comment) */ -"CFBundleDisplayName" = "LoopUI"; - -/* (No Comment) */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/LoopUI/nl.lproj/Localizable.strings b/LoopUI/nl.lproj/Localizable.strings index c267cc3e69..b5ca322d7d 100644 --- a/LoopUI/nl.lproj/Localizable.strings +++ b/LoopUI/nl.lproj/Localizable.strings @@ -4,45 +4,27 @@ /* The format string describing the basal rate. */ "%@ U" = "%@ E"; -/* Format string for reservoir volume. (1: The localized volume) */ -"%@U" = "%@E"; - /* Accessbility format value describing glucose: (1: glucose number)(2: glucose time) */ -"%1$@ at %2$@" = "%1$@ op %2$@"; +"%1$@ at %2$@" = "%1$@ at %2$@"; /* Accessibility format string describing the basal rate. (1: localized basal rate value)(2: last updated time) */ "%1$@ units per hour at %2$@" = "%1$@ eenheden per uur op %2$@"; -/* Accessibility format string for (1: localized volume)(2: time) */ -"%1$@ units remaining at %2$@" = "%1$@ eenheden resterend op %2$@"; +/* Format string describing glucose units per minute (1: glucose unit string) */ +"%1$@/min" = "%1$@/min"; /* Accessibility hint describing completion HUD for a closed loop */ "Closed loop" = "Gesloten loop"; -/* The short unit display string for decibles */ -"dB" = "dB"; - -/* The short unit display string for grams */ -"g" = "gr"; - /* Accessbility format label describing the time interval since the last completion date. (1: The localized date components) */ "Loop ran %@ ago" = "Loop heeft %@ geleden gedraaid"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; - -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; - /* Accessibility label component for glucose HUD describing an invalid state */ "Needs attention" = "Aandacht vereist"; /* Accessbility hint describing completion HUD for an open loop */ "Open loop" = "Open loop"; -/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ -"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; - /* The short unit display string for international units of insulin */ "U" = "E"; diff --git a/LoopUI/pl.lproj/InfoPlist.strings b/LoopUI/pl.lproj/InfoPlist.strings deleted file mode 100644 index 5c1fb13406..0000000000 --- a/LoopUI/pl.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* (No Comment) */ -"CFBundleDisplayName" = "LoopUI"; - -/* (No Comment) */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/LoopUI/pl.lproj/Localizable.strings b/LoopUI/pl.lproj/Localizable.strings index 7f2011af46..cc07569b5c 100644 --- a/LoopUI/pl.lproj/Localizable.strings +++ b/LoopUI/pl.lproj/Localizable.strings @@ -1,51 +1,30 @@ -/* Format string describing the time interval since the last completion date. (1: The localized date components */ -"%@ ago" = "%@ temu"; - -/* The format string describing the basal rate. */ -"%@ U" = "%@ J"; - -/* Format string for reservoir volume. (1: The localized volume) */ -"%@U" = "%@J"; - /* Accessbility format value describing glucose: (1: glucose number)(2: glucose time) */ "%1$@ at %2$@" = "%1$@ o %2$@"; /* Accessibility format string describing the basal rate. (1: localized basal rate value)(2: last updated time) */ "%1$@ units per hour at %2$@" = "%1$@ jednostek na godzinę o %2$@"; -/* Accessibility format string for (1: localized volume)(2: time) */ -"%1$@ units remaining at %2$@" = "%1$@ pozostałych jednostek o %2$@"; +/* Format string describing glucose units per minute (1: glucose unit string) */ +"%1$@/min" = "%1$@/min"; -/* Accessibility hint describing completion HUD for a closed loop */ -"Closed loop" = "Zamknięta Loop"; +/* Format string describing the time interval since the last completion date. (1: The localized date components */ +"%@ ago" = "%@ temu"; -/* The short unit display string for decibles */ -"dB" = "dB"; +/* The format string describing the basal rate. */ +"%@ U" = "%@ J"; -/* The short unit display string for grams */ -"g" = "g"; +/* Accessibility hint describing completion HUD for a closed loop */ +"Closed loop" = "Zamknięta Loop"; /* Accessbility format label describing the time interval since the last completion date. (1: The localized date components) */ "Loop ran %@ ago" = "Pętla była uruchomiona %@ temu"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; - -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; - /* Accessibility label component for glucose HUD describing an invalid state */ "Needs attention" = "Wymaga uwagi"; /* Accessbility hint describing completion HUD for an open loop */ "Open loop" = "Otwarta Loop"; -/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ -"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; - -/* The short unit display string for international units of insulin */ -"U" = "J"; - /* Accessibility value for an unknown value */ "Unknown" = "Nieznany"; diff --git a/LoopUI/pt-BR.lproj/Localizable.strings b/LoopUI/pt-BR.lproj/Localizable.strings new file mode 100644 index 0000000000..78b767baeb --- /dev/null +++ b/LoopUI/pt-BR.lproj/Localizable.strings @@ -0,0 +1,33 @@ +/* Format string describing the time interval since the last completion date. (1: The localized date components */ +"%@ ago" = "%@ atrás"; + +/* The format string describing the basal rate. */ +"%@ U" = "%@ U"; + +/* Accessbility format value describing glucose: (1: glucose number)(2: glucose time) */ +"%1$@ at %2$@" = "%1$@ at %2$@"; + +/* Accessibility format string describing the basal rate. (1: localized basal rate value)(2: last updated time) */ +"%1$@ units per hour at %2$@" = "%1$@ unidades por hora em %2$@"; + +/* Format string describing glucose units per minute (1: glucose unit string) */ +"%1$@/min" = "%1$@/min"; + +/* Accessibility hint describing completion HUD for a closed loop */ +"Closed loop" = "Ciclo fechado"; + +/* Accessbility format label describing the time interval since the last completion date. (1: The localized date components) */ +"Loop ran %@ ago" = "Ciclo executado %@ atrás"; + +/* Accessibility label component for glucose HUD describing an invalid state */ +"Needs attention" = "Precisa de atenção"; + +/* Accessbility hint describing completion HUD for an open loop */ +"Open loop" = "Ciclo aberto"; + +/* Accessibility value for an unknown value */ +"Unknown" = "Desconhecido"; + +/* Acessibility label describing completion HUD waiting for first run */ +"Waiting for first run" = "Aguardando a primeira execução"; + diff --git a/LoopUI/ro.lproj/Localizable.strings b/LoopUI/ro.lproj/Localizable.strings new file mode 100644 index 0000000000..2b63178cfe --- /dev/null +++ b/LoopUI/ro.lproj/Localizable.strings @@ -0,0 +1,33 @@ +/* Format string describing the time interval since the last completion date. (1: The localized date components */ +"%@ ago" = "acum %@"; + +/* The format string describing the basal rate. */ +"%@ U" = "%@ U"; + +/* Accessbility format value describing glucose: (1: glucose number)(2: glucose time) */ +"%1$@ at %2$@" = "%1$@ la %2$@"; + +/* Accessibility format string describing the basal rate. (1: localized basal rate value)(2: last updated time) */ +"%1$@ units per hour at %2$@" = "%1$@ unități pe oră la %2$@"; + +/* Format string describing glucose units per minute (1: glucose unit string) */ +"%1$@/min" = "%1$@/min"; + +/* Accessibility hint describing completion HUD for a closed loop */ +"Closed loop" = "Loop automat"; + +/* Accessbility format label describing the time interval since the last completion date. (1: The localized date components) */ +"Loop ran %@ ago" = "Loop a rulat acum %@"; + +/* Accessibility label component for glucose HUD describing an invalid state */ +"Needs attention" = "Necesită atenție"; + +/* Accessbility hint describing completion HUD for an open loop */ +"Open loop" = "Loop manual"; + +/* Accessibility value for an unknown value */ +"Unknown" = "Necunoscută"; + +/* Acessibility label describing completion HUD waiting for first run */ +"Waiting for first run" = "Se așteaptă prima rulare"; + diff --git a/LoopUI/ru.lproj/InfoPlist.strings b/LoopUI/ru.lproj/InfoPlist.strings deleted file mode 100644 index 13ab538314..0000000000 --- a/LoopUI/ru.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "LoopUI"; - -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/LoopUI/ru.lproj/Localizable.strings b/LoopUI/ru.lproj/Localizable.strings index 62b83425d4..0d5c9a60c6 100644 --- a/LoopUI/ru.lproj/Localizable.strings +++ b/LoopUI/ru.lproj/Localizable.strings @@ -4,48 +4,27 @@ /* The format string describing the basal rate. */ "%@ U" = "%@ ед"; -/* Format string for reservoir volume. (1: The localized volume) */ -"%@U" = "%@ед"; - /* Accessbility format value describing glucose: (1: glucose number)(2: glucose time) */ "%1$@ at %2$@" = "%1$@ в %2$@ "; /* Accessibility format string describing the basal rate. (1: localized basal rate value)(2: last updated time) */ "%1$@ units per hour at %2$@" = "%1$@ ед/час в %2$@"; -/* Accessibility format string for (1: localized volume)(2: time) */ -"%1$@ units remaining at %2$@" = "%1$@ ед остающихся в %2$@"; +/* Format string describing glucose units per minute (1: glucose unit string) */ +"%1$@/min" = "%1$@/минут"; /* Accessibility hint describing completion HUD for a closed loop */ "Closed loop" = "Алгоритм замкнутого цикла"; -/* The short unit display string for decibles */ -"dB" = "dB"; - -/* The short unit display string for grams */ -"g" = "г"; - /* Accessbility format label describing the time interval since the last completion date. (1: The localized date components) */ "Loop ran %@ ago" = "Алгоритм цикла запущен %@ назад"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "мг/дл"; - -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "ммоль/л"; - /* Accessibility label component for glucose HUD describing an invalid state */ "Needs attention" = "Требует внимания"; /* Accessbility hint describing completion HUD for an open loop */ "Open loop" = "Алгоритм открытого цикла"; -/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ -"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; - -/* The short unit display string for international units of insulin */ -"U" = "ед"; - /* Accessibility value for an unknown value */ "Unknown" = "Неизвестно"; diff --git a/LoopUI/sv.lproj/Localizable.strings b/LoopUI/sv.lproj/Localizable.strings new file mode 100644 index 0000000000..01f6ebf04e --- /dev/null +++ b/LoopUI/sv.lproj/Localizable.strings @@ -0,0 +1,36 @@ +/* Format string describing the time interval since the last completion date. (1: The localized date components */ +"%@ ago" = "%@ sedan"; + +/* The format string describing the basal rate. */ +"%@ U" = "%@ E"; + +/* Accessbility format value describing glucose: (1: glucose number)(2: glucose time) */ +"%1$@ at %2$@" = "%1$@ at %2$@"; + +/* Accessibility format string describing the basal rate. (1: localized basal rate value)(2: last updated time) */ +"%1$@ units per hour at %2$@" = "%1$@ eheter per timme kl %2$@"; + +/* Format string describing glucose units per minute (1: glucose unit string) */ +"%1$@/min" = "%1$@/min"; + +/* Accessibility hint describing completion HUD for a closed loop */ +"Closed loop" = "Sluten loop"; + +/* Accessbility format label describing the time interval since the last completion date. (1: The localized date components) */ +"Loop ran %@ ago" = "Loop kördes %@ sedan"; + +/* Accessibility label component for glucose HUD describing an invalid state */ +"Needs attention" = "Kräver uppmärksamhet"; + +/* Accessbility hint describing completion HUD for an open loop */ +"Open loop" = "Öppen loop"; + +/* The short unit display string for international units of insulin */ +"U" = "E"; + +/* Accessibility value for an unknown value */ +"Unknown" = "Okänd"; + +/* Acessibility label describing completion HUD waiting for first run */ +"Waiting for first run" = "Väntar på första körning"; + diff --git a/LoopUI/vi.lproj/Localizable.strings b/LoopUI/vi.lproj/Localizable.strings new file mode 100644 index 0000000000..8461c5fb21 --- /dev/null +++ b/LoopUI/vi.lproj/Localizable.strings @@ -0,0 +1,33 @@ +/* Format string describing the time interval since the last completion date. (1: The localized date components */ +"%@ ago" = "%@ trước đó"; + +/* The format string describing the basal rate. */ +"%@ U" = "%@ U"; + +/* Accessbility format value describing glucose: (1: glucose number)(2: glucose time) */ +"%1$@ at %2$@" = "%1$@ lúc %2$@"; + +/* Accessibility format string describing the basal rate. (1: localized basal rate value)(2: last updated time) */ +"%1$@ units per hour at %2$@" = "%1$@ units một giờ lúc %2$@"; + +/* Format string describing glucose units per minute (1: glucose unit string) */ +"%1$@/min" = "%1$@/phút"; + +/* Accessibility hint describing completion HUD for a closed loop */ +"Closed loop" = "Closed loop"; + +/* Accessbility format label describing the time interval since the last completion date. (1: The localized date components) */ +"Loop ran %@ ago" = "Loop hoạt động %@ trước đó"; + +/* Accessibility label component for glucose HUD describing an invalid state */ +"Needs attention" = "Cần chú ý"; + +/* Accessbility hint describing completion HUD for an open loop */ +"Open loop" = "Open loop"; + +/* Accessibility value for an unknown value */ +"Unknown" = "Không nhận ra"; + +/* Acessibility label describing completion HUD waiting for first run */ +"Waiting for first run" = "Đang chờ cho lần chạy đầu tiên"; + diff --git a/LoopUI/zh-Hans.lproj/InfoPlist.strings b/LoopUI/zh-Hans.lproj/InfoPlist.strings deleted file mode 100644 index 13ab538314..0000000000 --- a/LoopUI/zh-Hans.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "LoopUI"; - -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/LoopUI/zh-Hans.lproj/Localizable.strings b/LoopUI/zh-Hans.lproj/Localizable.strings index 54264f6927..222ee96771 100644 --- a/LoopUI/zh-Hans.lproj/Localizable.strings +++ b/LoopUI/zh-Hans.lproj/Localizable.strings @@ -4,48 +4,27 @@ /* The format string describing the basal rate. */ "%@ U" = "%@ 单位"; -/* Format string for reservoir volume. (1: The localized volume) */ -"%@U" = "%@单位"; - /* Accessbility format value describing glucose: (1: glucose number)(2: glucose time) */ "%1$@ at %2$@" = "%2$@ 分钟时为 %1$@"; /* Accessibility format string describing the basal rate. (1: localized basal rate value)(2: last updated time) */ "%1$@ units per hour at %2$@" = "%1$@ 每小时的单位 %2$@"; -/* Accessibility format string for (1: localized volume)(2: time) */ -"%1$@ units remaining at %2$@" = "%1$@ 剩余的单位 %2$@"; +/* Format string describing glucose units per minute (1: glucose unit string) */ +"%1$@/min" = "%1$@/分钟"; /* Accessibility hint describing completion HUD for a closed loop */ "Closed loop" = "启动闭环"; -/* The short unit display string for decibles */ -"dB" = "dB"; - -/* The short unit display string for grams */ -"g" = "克"; - /* Accessbility format label describing the time interval since the last completion date. (1: The localized date components) */ "Loop ran %@ ago" = "闭环已停止运行%@"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "毫克/分升"; - -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "毫摩尔/升"; - /* Accessibility label component for glucose HUD describing an invalid state */ "Needs attention" = "请注意"; /* Accessbility hint describing completion HUD for an open loop */ "Open loop" = "关闭闭环"; -/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ -"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; - -/* The short unit display string for international units of insulin */ -"U" = "单位"; - /* Accessibility value for an unknown value */ "Unknown" = "未知"; diff --git a/WatchApp Extension/Base.lproj/Localizable.strings b/WatchApp Extension/Base.lproj/Localizable.strings index a6fae0a4f3..4cbccfb6cc 100644 --- a/WatchApp Extension/Base.lproj/Localizable.strings +++ b/WatchApp Extension/Base.lproj/Localizable.strings @@ -1,41 +1,38 @@ -/* Title of the user activity for adding carbs */ -"Add Carb Entry" = "Add Carb Entry"; +/* HUD row title for COB */ +"Active Carbs" = "Active Carbs"; + +/* HUD row title for IOB */ +"Active Insulin" = "Active Insulin"; /* The title of the alert controller displayed after a bolus attempt fails */ "Bolus Failed" = "Bolus Failed"; -/* The short unit display string for decibles */ -"dB" = "dB"; - /* The action button title to dismiss an error message */ "Dismiss" = "Dismiss"; -/* The short unit display string for grams */ -"g" = "g"; - /* The recovery message displayed after a bolus attempt fails - The recovery message displayed after a carb entry send attempt fails - The recovery message displayed after a glucose range override send attempt fails */ + The recovery message displayed after a carb entry send attempt fails + The recovery message displayed after a glucose range override send attempt fails */ "Make sure your iPhone is nearby and try again" = "Make sure your iPhone is nearby and try again"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; - -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; +/* HUD row title for Net Basal Rate */ +"Net Basal Rate" = "Net Basal Rate"; -/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ -"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; +/* The text for the Watch button for enabling a temporary override */ +"Override" = "Override"; /* The label and value showing the recommended bolus */ "Rec: %@ U" = "Rec: %@ U"; +/* HUD row title for remaining reservoir volume */ +"Reservoir Volume" = "Reservoir Volume"; + /* The title of the alert controller displayed after a carb entry send attempt fails - The title of the alert controller displayed after a glucose range override send attempt fails */ + The title of the alert controller displayed after a glucose range override send attempt fails */ "Send Failed" = "Send Failed"; -/* The short unit display string for international units of insulin */ -"U" = "U"; - /* The short unit display string for international units of insulin delivery per hour */ "U/hr" = "U/hr"; + +/* The text for the Watch button for enabling workout mode */ +"Workout" = "Workout"; diff --git a/WatchApp Extension/da.lproj/Localizable.strings b/WatchApp Extension/da.lproj/Localizable.strings new file mode 100644 index 0000000000..40a68205f9 --- /dev/null +++ b/WatchApp Extension/da.lproj/Localizable.strings @@ -0,0 +1,45 @@ +/* HUD row title for COB */ +"Active Carbs" = "Aktive Kulhydrater"; + +/* HUD row title for IOB */ +"Active Insulin" = "Activt Insulin"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Tilføj Kulhydrat indtastning"; + +/* The title of the alert controller displayed after a bolus attempt fails */ +"Bolus Failed" = "Bolus Fejlede"; + +/* The action button title to dismiss an error message */ +"Dismiss" = "Ignorer"; + +/* The recovery message displayed after a bolus attempt fails + The recovery message displayed after a carb entry send attempt fails + The recovery message displayed after a glucose range override send attempt fails */ +"Make sure your iPhone is nearby and try again" = "Sørg for at din iPhone er i nærheden, og prøv igen"; + +/* HUD row title for Net Basal Rate */ +"Net Basal Rate" = "Netto Basal Rate"; + +/* The text for the Watch button for enabling a temporary override */ +"Override" = "Overstyr"; + +/* The label and value showing the recommended bolus */ +"Rec: %@ U" = "Anb: %@ E"; + +/* HUD row title for remaining reservoir volume */ +"Reservoir Volume" = "Reservoir Mængde"; + +/* The title of the alert controller displayed after a carb entry send attempt fails + The title of the alert controller displayed after a glucose range override send attempt fails */ +"Send Failed" = "Sending Fejlede"; + +/* The short unit display string for international units of insulin */ +"U" = "E"; + +/* The short unit display string for international units of insulin delivery per hour */ +"U/hr" = "E/t"; + +/* The text for the Watch button for enabling workout mode */ +"Workout" = "Motion"; + diff --git a/WatchApp Extension/de.lproj/InfoPlist.strings b/WatchApp Extension/de.lproj/InfoPlist.strings deleted file mode 100644 index d133967074..0000000000 --- a/WatchApp Extension/de.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "WatchApp Extension"; - -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/WatchApp Extension/de.lproj/Localizable.strings b/WatchApp Extension/de.lproj/Localizable.strings index 7e701ddebe..b0b162ca88 100644 --- a/WatchApp Extension/de.lproj/Localizable.strings +++ b/WatchApp Extension/de.lproj/Localizable.strings @@ -1,36 +1,42 @@ +/* HUD row title for COB */ +"Active Carbs" = "Active Carbs"; + +/* HUD row title for IOB */ +"Active Insulin" = "Active Insulin"; + /* The title of the alert controller displayed after a bolus attempt fails */ "Bolus Failed" = "Bolus fehlgeschlagen"; -/* The short unit display string for decibles */ -"dB" = "dB"; - /* The action button title to dismiss an error message */ "Dismiss" = "Verwerfen"; -/* The short unit display string for grams */ -"g" = "g"; - /* The recovery message displayed after a bolus attempt fails The recovery message displayed after a carb entry send attempt fails The recovery message displayed after a glucose range override send attempt fails */ "Make sure your iPhone is nearby and try again" = "Stellen Sie sicher, dass Ihr iPhone in der Nähe ist, und versuchen Sie es erneut."; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; +/* HUD row title for Net Basal Rate */ +"Net Basal Rate" = "Net Basal Rate"; -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; - -/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ -"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; +/* The text for the Watch button for enabling a temporary override */ +"Override" = "Override"; /* The label and value showing the recommended bolus */ -"Rec: %@ U" = "Empfohlen: %@ E"; +"Rec: %@ U" = "Empfohlen: %@ IE"; + +/* HUD row title for remaining reservoir volume */ +"Reservoir Volume" = "Reservoir Volume"; /* The title of the alert controller displayed after a carb entry send attempt fails The title of the alert controller displayed after a glucose range override send attempt fails */ "Send Failed" = "Senden fehlgeschlagen"; /* The short unit display string for international units of insulin */ -"U" = "E"; +"U" = "IE"; + +/* The short unit display string for international units of insulin delivery per hour */ +"U/hr" = "IE/Std."; + +/* The text for the Watch button for enabling workout mode */ +"Workout" = "Training"; diff --git a/WatchApp Extension/de.lproj/ckcomplication.strings b/WatchApp Extension/de.lproj/ckcomplication.strings deleted file mode 100644 index 6839fc3047..0000000000 --- a/WatchApp Extension/de.lproj/ckcomplication.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Utilitarian large flat format string (1: Glucose & Trend symbol) (2: Eventual Glucose) (3: Time) */ -"UtilitarianLargeFlat" = "%@%@ %@"; - diff --git a/WatchApp Extension/en.lproj/Localizable.strings b/WatchApp Extension/en.lproj/Localizable.strings new file mode 100644 index 0000000000..4cbccfb6cc --- /dev/null +++ b/WatchApp Extension/en.lproj/Localizable.strings @@ -0,0 +1,38 @@ +/* HUD row title for COB */ +"Active Carbs" = "Active Carbs"; + +/* HUD row title for IOB */ +"Active Insulin" = "Active Insulin"; + +/* The title of the alert controller displayed after a bolus attempt fails */ +"Bolus Failed" = "Bolus Failed"; + +/* The action button title to dismiss an error message */ +"Dismiss" = "Dismiss"; + +/* The recovery message displayed after a bolus attempt fails + The recovery message displayed after a carb entry send attempt fails + The recovery message displayed after a glucose range override send attempt fails */ +"Make sure your iPhone is nearby and try again" = "Make sure your iPhone is nearby and try again"; + +/* HUD row title for Net Basal Rate */ +"Net Basal Rate" = "Net Basal Rate"; + +/* The text for the Watch button for enabling a temporary override */ +"Override" = "Override"; + +/* The label and value showing the recommended bolus */ +"Rec: %@ U" = "Rec: %@ U"; + +/* HUD row title for remaining reservoir volume */ +"Reservoir Volume" = "Reservoir Volume"; + +/* The title of the alert controller displayed after a carb entry send attempt fails + The title of the alert controller displayed after a glucose range override send attempt fails */ +"Send Failed" = "Send Failed"; + +/* The short unit display string for international units of insulin delivery per hour */ +"U/hr" = "U/hr"; + +/* The text for the Watch button for enabling workout mode */ +"Workout" = "Workout"; diff --git a/WatchApp Extension/es.lproj/InfoPlist.strings b/WatchApp Extension/es.lproj/InfoPlist.strings deleted file mode 100644 index d133967074..0000000000 --- a/WatchApp Extension/es.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "WatchApp Extension"; - -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/WatchApp Extension/es.lproj/Localizable.strings b/WatchApp Extension/es.lproj/Localizable.strings index 0a557ee744..9a8a0b2cf9 100644 --- a/WatchApp Extension/es.lproj/Localizable.strings +++ b/WatchApp Extension/es.lproj/Localizable.strings @@ -1,36 +1,38 @@ +/* HUD row title for COB */ +"Active Carbs" = "Carbs Activos"; + +/* HUD row title for IOB */ +"Active Insulin" = "Insulina activa"; + /* The title of the alert controller displayed after a bolus attempt fails */ "Bolus Failed" = "Bolo Falló"; -/* The short unit display string for decibles */ -"dB" = "dB"; - /* The action button title to dismiss an error message */ "Dismiss" = "Ignorar"; -/* The short unit display string for grams */ -"g" = "g"; - /* The recovery message displayed after a bolus attempt fails The recovery message displayed after a carb entry send attempt fails The recovery message displayed after a glucose range override send attempt fails */ "Make sure your iPhone is nearby and try again" = "Asegúrate que tu iPhone se encuentre cerca e inténtalo de nuevo"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; - -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; +/* HUD row title for Net Basal Rate */ +"Net Basal Rate" = "Tasa basal neta"; -/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ -"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; +/* The text for the Watch button for enabling a temporary override */ +"Override" = "Sobreescritura"; /* The label and value showing the recommended bolus */ "Rec: %@ U" = "Rec: %@ U"; +/* HUD row title for remaining reservoir volume */ +"Reservoir Volume" = "Volumen de Reservorio"; + /* The title of the alert controller displayed after a carb entry send attempt fails The title of the alert controller displayed after a glucose range override send attempt fails */ "Send Failed" = "Envío Falló"; -/* The short unit display string for international units of insulin */ -"U" = "U"; +/* The short unit display string for international units of insulin delivery per hour */ +"U/hr" = "U/hra"; +/* The text for the Watch button for enabling workout mode */ +"Workout" = "Ejercicio"; diff --git a/WatchApp Extension/es.lproj/ckcomplication.strings b/WatchApp Extension/es.lproj/ckcomplication.strings deleted file mode 100644 index 0c9dc09cf7..0000000000 --- a/WatchApp Extension/es.lproj/ckcomplication.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Utilitarian large flat format string (1: Glucose & Trend symbol) (2: Eventual Glucose) (3: Time) */ -"UtilitarianLargeFlat" = "UtilitarianLargeFlat"; - diff --git a/WatchApp Extension/fi.lproj/Localizable.strings b/WatchApp Extension/fi.lproj/Localizable.strings new file mode 100644 index 0000000000..2429e4ab1e --- /dev/null +++ b/WatchApp Extension/fi.lproj/Localizable.strings @@ -0,0 +1,42 @@ +/* HUD row title for COB */ +"Active Carbs" = "Aktiivinen hiilihydr."; + +/* HUD row title for IOB */ +"Active Insulin" = "Aktiivinen insuliini"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Lisää hiilihydraatteja"; + +/* The title of the alert controller displayed after a bolus attempt fails */ +"Bolus Failed" = "Bolus epäonnistui"; + +/* The action button title to dismiss an error message */ +"Dismiss" = "Hylkää"; + +/* The recovery message displayed after a bolus attempt fails + The recovery message displayed after a carb entry send attempt fails + The recovery message displayed after a glucose range override send attempt fails */ +"Make sure your iPhone is nearby and try again" = "Varmista, että iPhone on riittävän lähellä ja yritä uudeleen"; + +/* HUD row title for Net Basal Rate */ +"Net Basal Rate" = "Nettobasaali"; + +/* The text for the Watch button for enabling a temporary override */ +"Override" = "Tilapäisas"; + +/* The label and value showing the recommended bolus */ +"Rec: %@ U" = "Suosit: %@ U"; + +/* HUD row title for remaining reservoir volume */ +"Reservoir Volume" = "Säiliön tila"; + +/* The title of the alert controller displayed after a carb entry send attempt fails + The title of the alert controller displayed after a glucose range override send attempt fails */ +"Send Failed" = "Lähetys epäonnistui"; + +/* The short unit display string for international units of insulin delivery per hour */ +"U/hr" = "U/h"; + +/* The text for the Watch button for enabling workout mode */ +"Workout" = "Liikunta"; + diff --git a/WatchApp Extension/fr.lproj/InfoPlist.strings b/WatchApp Extension/fr.lproj/InfoPlist.strings deleted file mode 100644 index d133967074..0000000000 --- a/WatchApp Extension/fr.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "WatchApp Extension"; - -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/WatchApp Extension/fr.lproj/Localizable.strings b/WatchApp Extension/fr.lproj/Localizable.strings index ccbc8eecb8..7fe28958a0 100644 --- a/WatchApp Extension/fr.lproj/Localizable.strings +++ b/WatchApp Extension/fr.lproj/Localizable.strings @@ -1,36 +1,39 @@ +/* HUD row title for COB */ +"Active Carbs" = "Glucides actifs"; + +/* HUD row title for IOB */ +"Active Insulin" = "Insuline active"; + /* The title of the alert controller displayed after a bolus attempt fails */ "Bolus Failed" = "Bolus Échoué"; -/* The short unit display string for decibles */ -"dB" = "dB"; - /* The action button title to dismiss an error message */ "Dismiss" = "Rejeter"; -/* The short unit display string for grams */ -"g" = "g"; - /* The recovery message displayed after a bolus attempt fails The recovery message displayed after a carb entry send attempt fails The recovery message displayed after a glucose range override send attempt fails */ "Make sure your iPhone is nearby and try again" = "Assurez-vous que votre iPhone est à proximité et réessayez"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; +/* HUD row title for Net Basal Rate */ +"Net Basal Rate" = "Débit de basale net"; -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; - -/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ -"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; +/* The text for the Watch button for enabling a temporary override */ +"Override" = "Surcharge"; /* The label and value showing the recommended bolus */ "Rec: %@ U" = "Rec: %@ U"; +/* HUD row title for remaining reservoir volume */ +"Reservoir Volume" = "Volume du réservoir"; + /* The title of the alert controller displayed after a carb entry send attempt fails The title of the alert controller displayed after a glucose range override send attempt fails */ "Send Failed" = "Echec de l'envoi"; -/* The short unit display string for international units of insulin */ -"U" = "U"; +/* The short unit display string for international units of insulin delivery per hour */ +"U/hr" = "U/h"; + +/* The text for the Watch button for enabling workout mode */ +"Workout" = "Sport"; diff --git a/WatchApp Extension/fr.lproj/ckcomplication.strings b/WatchApp Extension/fr.lproj/ckcomplication.strings deleted file mode 100644 index 346c8f6ec3..0000000000 --- a/WatchApp Extension/fr.lproj/ckcomplication.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* - ckcomplication.strings - Loop - - Created by Nate Racklyeft on 9/18/16. - Copyright © 2016 Nathan Racklyeft. All rights reserved. -*/ - -/* Utilitarian large flat format string (1: Glucose & Trend symbol) (2: Eventual Glucose) (3: Time) */ -"UtilitarianLargeFlat" = "%@%@ %@"; - - diff --git a/WatchApp Extension/it.lproj/InfoPlist.strings b/WatchApp Extension/it.lproj/InfoPlist.strings deleted file mode 100644 index d133967074..0000000000 --- a/WatchApp Extension/it.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "WatchApp Extension"; - -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/WatchApp Extension/it.lproj/Localizable.strings b/WatchApp Extension/it.lproj/Localizable.strings index 7cbdcc4938..820a206a89 100644 --- a/WatchApp Extension/it.lproj/Localizable.strings +++ b/WatchApp Extension/it.lproj/Localizable.strings @@ -1,36 +1,42 @@ +/* HUD row title for COB */ +"Active Carbs" = "Carboidrati attivi"; + +/* HUD row title for IOB */ +"Active Insulin" = "Insulina attiva"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Aggiungi carboidrati assunti"; + /* The title of the alert controller displayed after a bolus attempt fails */ "Bolus Failed" = "Bolo Fallito"; -/* The short unit display string for decibles */ -"dB" = "dB"; - /* The action button title to dismiss an error message */ "Dismiss" = "Rimuovere"; -/* The short unit display string for grams */ -"g" = "g"; - /* The recovery message displayed after a bolus attempt fails The recovery message displayed after a carb entry send attempt fails The recovery message displayed after a glucose range override send attempt fails */ "Make sure your iPhone is nearby and try again" = "Assicurati che il tuo iPhone sia vicino e riprova"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; - -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; +/* HUD row title for Net Basal Rate */ +"Net Basal Rate" = "Velocità basale netta"; -/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ -"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; +/* The text for the Watch button for enabling a temporary override */ +"Override" = "Attiva regolazione manuale"; /* The label and value showing the recommended bolus */ "Rec: %@ U" = "Rec: %@ U"; +/* HUD row title for remaining reservoir volume */ +"Reservoir Volume" = "Volume serbatoio"; + /* The title of the alert controller displayed after a carb entry send attempt fails The title of the alert controller displayed after a glucose range override send attempt fails */ "Send Failed" = "Invio Fallito"; -/* The short unit display string for international units of insulin */ -"U" = "U"; +/* The short unit display string for international units of insulin delivery per hour */ +"U/hr" = "U/ora"; + +/* The text for the Watch button for enabling workout mode */ +"Workout" = "Attività fisica"; diff --git a/WatchApp Extension/it.lproj/ckcomplication.strings b/WatchApp Extension/it.lproj/ckcomplication.strings deleted file mode 100644 index 6839fc3047..0000000000 --- a/WatchApp Extension/it.lproj/ckcomplication.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Utilitarian large flat format string (1: Glucose & Trend symbol) (2: Eventual Glucose) (3: Time) */ -"UtilitarianLargeFlat" = "%@%@ %@"; - diff --git a/WatchApp Extension/ja.lproj/Localizable.strings b/WatchApp Extension/ja.lproj/Localizable.strings new file mode 100644 index 0000000000..c341eac1a8 --- /dev/null +++ b/WatchApp Extension/ja.lproj/Localizable.strings @@ -0,0 +1,42 @@ +/* HUD row title for COB */ +"Active Carbs" = "残存糖質"; + +/* HUD row title for IOB */ +"Active Insulin" = "残存インスリン"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "カーボを追加"; + +/* The title of the alert controller displayed after a bolus attempt fails */ +"Bolus Failed" = "ボーラス不成功"; + +/* The action button title to dismiss an error message */ +"Dismiss" = "閉じる"; + +/* The recovery message displayed after a bolus attempt fails + The recovery message displayed after a carb entry send attempt fails + The recovery message displayed after a glucose range override send attempt fails */ +"Make sure your iPhone is nearby and try again" = "iPhone が近くにあることを確認して、再実行してください"; + +/* HUD row title for Net Basal Rate */ +"Net Basal Rate" = "正味基礎インスリン"; + +/* The text for the Watch button for enabling a temporary override */ +"Override" = "オーバーライド"; + +/* The label and value showing the recommended bolus */ +"Rec: %@ U" = "推奨: %@ U"; + +/* HUD row title for remaining reservoir volume */ +"Reservoir Volume" = "リザーバ残量"; + +/* The title of the alert controller displayed after a carb entry send attempt fails + The title of the alert controller displayed after a glucose range override send attempt fails */ +"Send Failed" = "送信に失敗"; + +/* The short unit display string for international units of insulin delivery per hour */ +"U/hr" = "U/時"; + +/* The text for the Watch button for enabling workout mode */ +"Workout" = "運動"; + diff --git a/WatchApp Extension/nb.lproj/InfoPlist.strings b/WatchApp Extension/nb.lproj/InfoPlist.strings deleted file mode 100644 index 06077199aa..0000000000 --- a/WatchApp Extension/nb.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* (No Comment) */ -"CFBundleDisplayName" = "WatchApp Extension"; - -/* (No Comment) */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/WatchApp Extension/nb.lproj/Localizable.strings b/WatchApp Extension/nb.lproj/Localizable.strings index e2798b7973..18e9f83afe 100644 --- a/WatchApp Extension/nb.lproj/Localizable.strings +++ b/WatchApp Extension/nb.lproj/Localizable.strings @@ -1,32 +1,32 @@ +/* HUD row title for COB */ +"Active Carbs" = "Aktive karbohydrater"; + +/* HUD row title for IOB */ +"Active Insulin" = "Aktivt insulin"; + /* The title of the alert controller displayed after a bolus attempt fails */ "Bolus Failed" = "Bolus mislyktes"; -/* The short unit display string for decibles */ -"dB" = "dB"; - /* The action button title to dismiss an error message */ "Dismiss" = "Avskjedige"; -/* The short unit display string for grams */ -"g" = "g"; - /* The recovery message displayed after a bolus attempt fails The recovery message displayed after a carb entry send attempt fails The recovery message displayed after a glucose range override send attempt fails */ "Make sure your iPhone is nearby and try again" = "Pass på at iPhone er i nærheten, og prøv igjen"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; +/* HUD row title for Net Basal Rate */ +"Net Basal Rate" = "Netto Basaldose"; -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; - -/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ -"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; +/* The text for the Watch button for enabling a temporary override */ +"Override" = "Overstyr"; /* The label and value showing the recommended bolus */ "Rec: %@ U" = "Rec: %@ E"; +/* HUD row title for remaining reservoir volume */ +"Reservoir Volume" = "Reservoarstørrelse"; + /* The title of the alert controller displayed after a carb entry send attempt fails The title of the alert controller displayed after a glucose range override send attempt fails */ "Send Failed" = "Send mislyktes"; @@ -34,3 +34,9 @@ /* The short unit display string for international units of insulin */ "U" = "E"; +/* The short unit display string for international units of insulin delivery per hour */ +"U/hr" = "E/t"; + +/* The text for the Watch button for enabling workout mode */ +"Workout" = "Trening"; + diff --git a/WatchApp Extension/nb.lproj/ckcomplication.strings b/WatchApp Extension/nb.lproj/ckcomplication.strings deleted file mode 100644 index a859782678..0000000000 --- a/WatchApp Extension/nb.lproj/ckcomplication.strings +++ /dev/null @@ -1,10 +0,0 @@ -/* - ckcomplication.strings - Loop - - Created by Nate Racklyeft on 9/18/16. - Copyright © 2016 Nathan Racklyeft. All rights reserved. -*/ - -/* Utilitarian large flat format string (1: Glucose & Trend symbol) (2: Eventual Glucose) (3: Time) */ -"UtilitarianLargeFlat" = "%@%@ %@"; diff --git a/WatchApp Extension/nl.lproj/InfoPlist.strings b/WatchApp Extension/nl.lproj/InfoPlist.strings deleted file mode 100644 index 06077199aa..0000000000 --- a/WatchApp Extension/nl.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* (No Comment) */ -"CFBundleDisplayName" = "WatchApp Extension"; - -/* (No Comment) */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/WatchApp Extension/nl.lproj/Localizable.strings b/WatchApp Extension/nl.lproj/Localizable.strings index 990a8941e5..65bb8274fb 100644 --- a/WatchApp Extension/nl.lproj/Localizable.strings +++ b/WatchApp Extension/nl.lproj/Localizable.strings @@ -1,32 +1,35 @@ +/* HUD row title for COB */ +"Active Carbs" = "Actieve koolhydraten"; + +/* HUD row title for IOB */ +"Active Insulin" = "Actieve insuline"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Voeg koolhydraten toe"; + /* The title of the alert controller displayed after a bolus attempt fails */ "Bolus Failed" = "Bolus mislukt"; -/* The short unit display string for decibles */ -"dB" = "dB"; - /* The action button title to dismiss an error message */ "Dismiss" = "Verwerp"; -/* The short unit display string for grams */ -"g" = "gr"; - /* The recovery message displayed after a bolus attempt fails The recovery message displayed after a carb entry send attempt fails The recovery message displayed after a glucose range override send attempt fails */ "Make sure your iPhone is nearby and try again" = "Zorg dat je iPhone in de buurt is en probeer opnieuw"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; +/* HUD row title for Net Basal Rate */ +"Net Basal Rate" = "Netto basaalsnelheid"; -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; - -/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ -"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; +/* The text for the Watch button for enabling a temporary override */ +"Override" = "Aangepast programma"; /* The label and value showing the recommended bolus */ "Rec: %@ U" = "Voorgesteld: %@ E"; +/* HUD row title for remaining reservoir volume */ +"Reservoir Volume" = "Reservoir inhoud"; + /* The title of the alert controller displayed after a carb entry send attempt fails The title of the alert controller displayed after a glucose range override send attempt fails */ "Send Failed" = "Versturen mislukt"; @@ -34,3 +37,9 @@ /* The short unit display string for international units of insulin */ "U" = "E"; +/* The short unit display string for international units of insulin delivery per hour */ +"U/hr" = "E/uur"; + +/* The text for the Watch button for enabling workout mode */ +"Workout" = "Training"; + diff --git a/WatchApp Extension/nl.lproj/ckcomplication.strings b/WatchApp Extension/nl.lproj/ckcomplication.strings deleted file mode 100644 index a859782678..0000000000 --- a/WatchApp Extension/nl.lproj/ckcomplication.strings +++ /dev/null @@ -1,10 +0,0 @@ -/* - ckcomplication.strings - Loop - - Created by Nate Racklyeft on 9/18/16. - Copyright © 2016 Nathan Racklyeft. All rights reserved. -*/ - -/* Utilitarian large flat format string (1: Glucose & Trend symbol) (2: Eventual Glucose) (3: Time) */ -"UtilitarianLargeFlat" = "%@%@ %@"; diff --git a/WatchApp Extension/pl.lproj/InfoPlist.strings b/WatchApp Extension/pl.lproj/InfoPlist.strings deleted file mode 100644 index 06077199aa..0000000000 --- a/WatchApp Extension/pl.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* (No Comment) */ -"CFBundleDisplayName" = "WatchApp Extension"; - -/* (No Comment) */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/WatchApp Extension/pl.lproj/Localizable.strings b/WatchApp Extension/pl.lproj/Localizable.strings index 707b1a66b0..3368448e7d 100644 --- a/WatchApp Extension/pl.lproj/Localizable.strings +++ b/WatchApp Extension/pl.lproj/Localizable.strings @@ -1,37 +1,38 @@ +/* HUD row title for COB */ +"Active Carbs" = "Aktywne węglowodany"; + +/* HUD row title for IOB */ +"Active Insulin" = "Aktywna insulina"; + /* The title of the alert controller displayed after a bolus attempt fails */ "Bolus Failed" = "Podanie bolusa nie powiodło się"; -/* The short unit display string for decibles */ -"dB" = "dB"; - /* The action button title to dismiss an error message */ "Dismiss" = "Zignoruj"; -/* The short unit display string for grams */ -"g" = "g"; - /* The recovery message displayed after a bolus attempt fails - The recovery message displayed after a carb entry send attempt fails - The recovery message displayed after a glucose range override send attempt fails */ + The recovery message displayed after a carb entry send attempt fails + The recovery message displayed after a glucose range override send attempt fails */ "Make sure your iPhone is nearby and try again" = "Upewnij się, że Twój iPhone jest w pobliżu i spróbuj ponownie"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "mg/dL"; +/* HUD row title for Net Basal Rate */ +"Net Basal Rate" = "Dawka podstawowa netto"; -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "mmol/L"; - -/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ -"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; +/* The text for the Watch button for enabling a temporary override */ +"Override" = "Pominięcie"; /* The label and value showing the recommended bolus */ "Rec: %@ U" = "Rekomendowane: %@ J"; +/* HUD row title for remaining reservoir volume */ +"Reservoir Volume" = "Objętość w zbiorniku"; + /* The title of the alert controller displayed after a carb entry send attempt fails - The title of the alert controller displayed after a glucose range override send attempt fails */ + The title of the alert controller displayed after a glucose range override send attempt fails */ "Send Failed" = "Wysyłanie nie powiodło się"; -/* The short unit display string for international units of insulin */ -"U" = "J"; - +/* The short unit display string for international units of insulin delivery per hour */ +"U/hr" = "J/godz."; +/* The text for the Watch button for enabling workout mode */ +"Workout" = "Wysiłek fizyczny"; diff --git a/WatchApp Extension/pl.lproj/ckcomplication.strings b/WatchApp Extension/pl.lproj/ckcomplication.strings deleted file mode 100644 index a859782678..0000000000 --- a/WatchApp Extension/pl.lproj/ckcomplication.strings +++ /dev/null @@ -1,10 +0,0 @@ -/* - ckcomplication.strings - Loop - - Created by Nate Racklyeft on 9/18/16. - Copyright © 2016 Nathan Racklyeft. All rights reserved. -*/ - -/* Utilitarian large flat format string (1: Glucose & Trend symbol) (2: Eventual Glucose) (3: Time) */ -"UtilitarianLargeFlat" = "%@%@ %@"; diff --git a/WatchApp Extension/pt-BR.lproj/Localizable.strings b/WatchApp Extension/pt-BR.lproj/Localizable.strings new file mode 100644 index 0000000000..0c6e9b3731 --- /dev/null +++ b/WatchApp Extension/pt-BR.lproj/Localizable.strings @@ -0,0 +1,39 @@ +/* HUD row title for COB */ +"Active Carbs" = "Carboidratos Ativos"; + +/* HUD row title for IOB */ +"Active Insulin" = "Insulina Ativa"; + +/* The title of the alert controller displayed after a bolus attempt fails */ +"Bolus Failed" = "Bolus Falhou"; + +/* The action button title to dismiss an error message */ +"Dismiss" = "Dispensar"; + +/* The recovery message displayed after a bolus attempt fails + The recovery message displayed after a carb entry send attempt fails + The recovery message displayed after a glucose range override send attempt fails */ +"Make sure your iPhone is nearby and try again" = "Verifique se o iPhone está próximo e tente novamente"; + +/* HUD row title for Net Basal Rate */ +"Net Basal Rate" = "Taxa Basal Líquida"; + +/* The text for the Watch button for enabling a temporary override */ +"Override" = "Sobrepor"; + +/* The label and value showing the recommended bolus */ +"Rec: %@ U" = "Rec: %@ U"; + +/* HUD row title for remaining reservoir volume */ +"Reservoir Volume" = "Volume do Reservatório"; + +/* The title of the alert controller displayed after a carb entry send attempt fails + The title of the alert controller displayed after a glucose range override send attempt fails */ +"Send Failed" = "Envio falhou"; + +/* The short unit display string for international units of insulin delivery per hour */ +"U/hr" = "U/hr"; + +/* The text for the Watch button for enabling workout mode */ +"Workout" = "Exercício"; + diff --git a/WatchApp Extension/ro.lproj/Localizable.strings b/WatchApp Extension/ro.lproj/Localizable.strings new file mode 100644 index 0000000000..8b9c22dc37 --- /dev/null +++ b/WatchApp Extension/ro.lproj/Localizable.strings @@ -0,0 +1,42 @@ +/* HUD row title for COB */ +"Active Carbs" = "Carbohidrați activi"; + +/* HUD row title for IOB */ +"Active Insulin" = "Insulină activă"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Adaugă carbohidrați"; + +/* The title of the alert controller displayed after a bolus attempt fails */ +"Bolus Failed" = "Bolus eșuat"; + +/* The action button title to dismiss an error message */ +"Dismiss" = "Închide"; + +/* The recovery message displayed after a bolus attempt fails + The recovery message displayed after a carb entry send attempt fails + The recovery message displayed after a glucose range override send attempt fails */ +"Make sure your iPhone is nearby and try again" = "Asigurați-vă că iPhone-ul este în apropiere, după care încercați din nou"; + +/* HUD row title for Net Basal Rate */ +"Net Basal Rate" = "Rată bazală netă"; + +/* The text for the Watch button for enabling a temporary override */ +"Override" = "Înlocuire"; + +/* The label and value showing the recommended bolus */ +"Rec: %@ U" = "Rec: %@ U"; + +/* HUD row title for remaining reservoir volume */ +"Reservoir Volume" = "Volum rezervor"; + +/* The title of the alert controller displayed after a carb entry send attempt fails + The title of the alert controller displayed after a glucose range override send attempt fails */ +"Send Failed" = "Transmitere eșuată"; + +/* The short unit display string for international units of insulin delivery per hour */ +"U/hr" = "U/oră"; + +/* The text for the Watch button for enabling workout mode */ +"Workout" = "Activitate sportivă"; + diff --git a/WatchApp Extension/ru.lproj/InfoPlist.strings b/WatchApp Extension/ru.lproj/InfoPlist.strings deleted file mode 100644 index d133967074..0000000000 --- a/WatchApp Extension/ru.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "WatchApp Extension"; - -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/WatchApp Extension/ru.lproj/Localizable.strings b/WatchApp Extension/ru.lproj/Localizable.strings index 2a57835c53..0dfa7d4169 100644 --- a/WatchApp Extension/ru.lproj/Localizable.strings +++ b/WatchApp Extension/ru.lproj/Localizable.strings @@ -1,33 +1,42 @@ +/* HUD row title for COB */ +"Active Carbs" = "Активные углеводы"; + +/* HUD row title for IOB */ +"Active Insulin" = "Активный инсулин"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Добавить запись углеводов"; + /* The title of the alert controller displayed after a bolus attempt fails */ "Bolus Failed" = "Болюс не состоялся"; -/* The short unit display string for decibles */ -"dB" = "dB"; - /* The action button title to dismiss an error message */ "Dismiss" = "Пропустить"; -/* The short unit display string for grams */ -"g" = "г"; - /* The recovery message displayed after a bolus attempt fails The recovery message displayed after a carb entry send attempt fails The recovery message displayed after a glucose range override send attempt fails */ "Make sure your iPhone is nearby and try again" = "Убедитесь, что ваш iPhone поблизости и повторите попытку"; -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "ммоль/л"; +/* HUD row title for Net Basal Rate */ +"Net Basal Rate" = "Нетто скорость базала"; -/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ -"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; +/* The text for the Watch button for enabling a temporary override */ +"Override" = "Перезаписать"; /* The label and value showing the recommended bolus */ "Rec: %@ U" = "Рекомендовано: %@ ед"; +/* HUD row title for remaining reservoir volume */ +"Reservoir Volume" = "Остаток картриджа"; + /* The title of the alert controller displayed after a carb entry send attempt fails The title of the alert controller displayed after a glucose range override send attempt fails */ "Send Failed" = "Ошибка отправки"; -/* The short unit display string for international units of insulin */ -"U" = "ед"; +/* The short unit display string for international units of insulin delivery per hour */ +"U/hr" = "Ед./ч."; + +/* The text for the Watch button for enabling workout mode */ +"Workout" = "Нагрузка"; diff --git a/WatchApp Extension/ru.lproj/ckcomplication.strings b/WatchApp Extension/ru.lproj/ckcomplication.strings deleted file mode 100644 index 0c9dc09cf7..0000000000 --- a/WatchApp Extension/ru.lproj/ckcomplication.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Utilitarian large flat format string (1: Glucose & Trend symbol) (2: Eventual Glucose) (3: Time) */ -"UtilitarianLargeFlat" = "UtilitarianLargeFlat"; - diff --git a/WatchApp Extension/sv.lproj/Localizable.strings b/WatchApp Extension/sv.lproj/Localizable.strings new file mode 100644 index 0000000000..6b129d3f88 --- /dev/null +++ b/WatchApp Extension/sv.lproj/Localizable.strings @@ -0,0 +1,45 @@ +/* HUD row title for COB */ +"Active Carbs" = "Aktiva kolhydrater"; + +/* HUD row title for IOB */ +"Active Insulin" = "Aktivt insulin"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Lägg till kolhydrater"; + +/* The title of the alert controller displayed after a bolus attempt fails */ +"Bolus Failed" = "Bolus misslyckades"; + +/* The action button title to dismiss an error message */ +"Dismiss" = "Avfärda"; + +/* The recovery message displayed after a bolus attempt fails + The recovery message displayed after a carb entry send attempt fails + The recovery message displayed after a glucose range override send attempt fails */ +"Make sure your iPhone is nearby and try again" = "Säkerställ att telefon är inom räckhåll och försök igen"; + +/* HUD row title for Net Basal Rate */ +"Net Basal Rate" = "Netto basaldos"; + +/* The text for the Watch button for enabling a temporary override */ +"Override" = "Override"; + +/* The label and value showing the recommended bolus */ +"Rec: %@ U" = "Rek: %@ E"; + +/* HUD row title for remaining reservoir volume */ +"Reservoir Volume" = "Reservoarvolym"; + +/* The title of the alert controller displayed after a carb entry send attempt fails + The title of the alert controller displayed after a glucose range override send attempt fails */ +"Send Failed" = "Skicka misslyckades"; + +/* The short unit display string for international units of insulin */ +"U" = "E"; + +/* The short unit display string for international units of insulin delivery per hour */ +"U/hr" = "E/h"; + +/* The text for the Watch button for enabling workout mode */ +"Workout" = "Träning"; + diff --git a/WatchApp Extension/vi.lproj/Localizable.strings b/WatchApp Extension/vi.lproj/Localizable.strings new file mode 100644 index 0000000000..eedc89b522 --- /dev/null +++ b/WatchApp Extension/vi.lproj/Localizable.strings @@ -0,0 +1,42 @@ +/* HUD row title for COB */ +"Active Carbs" = "Lượng Carbs còn hoạt động"; + +/* HUD row title for IOB */ +"Active Insulin" = "Lượng Insulin còn hoạt động"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "Khai báo khối lượng Carb"; + +/* The title of the alert controller displayed after a bolus attempt fails */ +"Bolus Failed" = "Bolus lỗi"; + +/* The action button title to dismiss an error message */ +"Dismiss" = "Từ bỏ"; + +/* The recovery message displayed after a bolus attempt fails + The recovery message displayed after a carb entry send attempt fails + The recovery message displayed after a glucose range override send attempt fails */ +"Make sure your iPhone is nearby and try again" = "Đảm bảo rằng iPhone của bạn đang ở gần và thử lại"; + +/* HUD row title for Net Basal Rate */ +"Net Basal Rate" = "Tỷ lệ liều basal ròng"; + +/* The text for the Watch button for enabling a temporary override */ +"Override" = "Chồng liều"; + +/* The label and value showing the recommended bolus */ +"Rec: %@ U" = "Khuyến nghị: %@ U"; + +/* HUD row title for remaining reservoir volume */ +"Reservoir Volume" = "Khối lượng ngăn chứa insulin"; + +/* The title of the alert controller displayed after a carb entry send attempt fails + The title of the alert controller displayed after a glucose range override send attempt fails */ +"Send Failed" = "Chuyển bị lỗi"; + +/* The short unit display string for international units of insulin delivery per hour */ +"U/hr" = "U/giờ"; + +/* The text for the Watch button for enabling workout mode */ +"Workout" = "Tập luyện"; + diff --git a/WatchApp Extension/zh-Hans.lproj/InfoPlist.strings b/WatchApp Extension/zh-Hans.lproj/InfoPlist.strings deleted file mode 100644 index d133967074..0000000000 --- a/WatchApp Extension/zh-Hans.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* Bundle display name */ -"CFBundleDisplayName" = "WatchApp Extension"; - -/* Bundle name */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/WatchApp Extension/zh-Hans.lproj/Localizable.strings b/WatchApp Extension/zh-Hans.lproj/Localizable.strings index 2e4ecb1643..d9a27d7dbd 100644 --- a/WatchApp Extension/zh-Hans.lproj/Localizable.strings +++ b/WatchApp Extension/zh-Hans.lproj/Localizable.strings @@ -1,36 +1,42 @@ +/* HUD row title for COB */ +"Active Carbs" = "活性碳水化合物"; + +/* HUD row title for IOB */ +"Active Insulin" = "活性胰岛素"; + +/* Title of the user activity for adding carbs */ +"Add Carb Entry" = "添加碳水化合物"; + /* The title of the alert controller displayed after a bolus attempt fails */ "Bolus Failed" = "大剂量输注失败"; -/* The short unit display string for decibles */ -"dB" = "dB"; - /* The action button title to dismiss an error message */ "Dismiss" = "忽略"; -/* The short unit display string for grams */ -"g" = "克"; - /* The recovery message displayed after a bolus attempt fails The recovery message displayed after a carb entry send attempt fails The recovery message displayed after a glucose range override send attempt fails */ "Make sure your iPhone is nearby and try again" = "请确保苹果手机接近设备并重试"; -/* The short unit display string for milligrams of glucose per decilter */ -"mg/dL" = "毫克/分升"; - -/* The short unit display string for millimoles of glucose per liter */ -"mmol/L" = "毫摩尔/升"; +/* HUD row title for Net Basal Rate */ +"Net Basal Rate" = "净基础率"; -/* Format string for combining localized numeric value and unit. (1: numeric value)(2: unit) */ -"QUANTITY_VALUE_AND_UNIT" = "%1$@ %2$@"; +/* The text for the Watch button for enabling a temporary override */ +"Override" = "覆盖"; /* The label and value showing the recommended bolus */ "Rec: %@ U" = "推荐: %@ 单位"; +/* HUD row title for remaining reservoir volume */ +"Reservoir Volume" = "储药器容量"; + /* The title of the alert controller displayed after a carb entry send attempt fails The title of the alert controller displayed after a glucose range override send attempt fails */ "Send Failed" = "发送失败"; -/* The short unit display string for international units of insulin */ -"U" = "单位"; +/* The short unit display string for international units of insulin delivery per hour */ +"U/hr" = "U/小时"; + +/* The text for the Watch button for enabling workout mode */ +"Workout" = "运动"; diff --git a/WatchApp Extension/zh-Hans.lproj/ckcomplication.strings b/WatchApp Extension/zh-Hans.lproj/ckcomplication.strings deleted file mode 100644 index 6839fc3047..0000000000 --- a/WatchApp Extension/zh-Hans.lproj/ckcomplication.strings +++ /dev/null @@ -1,3 +0,0 @@ -/* Utilitarian large flat format string (1: Glucose & Trend symbol) (2: Eventual Glucose) (3: Time) */ -"UtilitarianLargeFlat" = "%@%@ %@"; - diff --git a/WatchApp/da.lproj/Interface.strings b/WatchApp/da.lproj/Interface.strings new file mode 100644 index 0000000000..0a9ce82f38 --- /dev/null +++ b/WatchApp/da.lproj/Interface.strings @@ -0,0 +1,120 @@ +/* Class = "WKInterfaceButton"; title = "🌮"; ObjectID = "0fo-Z3-hTi"; */ +"0fo-Z3-hTi.title" = "🌮"; + +/* Class = "WKInterfaceButton"; title = "Add Carbs"; ObjectID = "b6f-3I-jki"; */ +"b6f-3I-jki.title" = "Tilføj Kulhydrater"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "CsQ-fc-KLC"; */ +"CsQ-fc-KLC.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "15"; ObjectID = "CWt-7U-cnK"; */ +"CWt-7U-cnK.text" = "15"; + +/* Class = "WKInterfaceLabel"; text = "TOTAL CARBS"; ObjectID = "dea-qG-va8"; */ +"dea-qG-va8.text" = "TOTALE KULHYDRATER"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "Dh9-HV-fXy"; */ +"Dh9-HV-fXy.accessibilityLabel" = "Fratræk"; + +/* Class = "WKInterfaceButton"; title = "−"; ObjectID = "Dh9-HV-fXy"; */ +"Dh9-HV-fXy.title" = "−"; + +/* Class = "WKInterfaceButton"; title = "🍕"; ObjectID = "dPF-QZ-sh6"; */ +"dPF-QZ-sh6.title" = "🍕"; + +/* Class = "WKInterfaceMenuItem"; title = "2 hours"; ObjectID = "dPh-7b-Tfv"; */ +"dPh-7b-Tfv.title" = "2 timer"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "Dt1-kz-jMZ"; */ +"Dt1-kz-jMZ.text" = "---"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "DZc-Gn-RLu"; */ +"DZc-Gn-RLu.accessibilityLabel" = "Tilføj"; + +/* Class = "WKInterfaceButton"; title = "+"; ObjectID = "DZc-Gn-RLu"; */ +"DZc-Gn-RLu.title" = "+"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "eu3-pj-GH3"; */ +"eu3-pj-GH3.accessibilityLabel" = "Tilføj"; + +/* Class = "WKInterfaceButton"; title = "+"; ObjectID = "eu3-pj-GH3"; */ +"eu3-pj-GH3.title" = "+"; + +/* Class = "WKInterfaceLabel"; text = "Pre-Meal"; ObjectID = "f5G-bS-9pd"; */ +"f5G-bS-9pd.text" = "Før-Måltid"; + +/* Class = "WKInterfaceMenuItem"; title = "3 hours"; ObjectID = "fR1-7h-SNe"; */ +"fR1-7h-SNe.title" = "3 timer"; + +/* Class = "WKInterfaceButton"; title = "🍭"; ObjectID = "gAn-qe-OvX"; */ +"gAn-qe-OvX.title" = "🍭"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "hjF-xr-cwO"; */ +"hjF-xr-cwO.accessibilityLabel" = "Fratræk"; + +/* Class = "WKInterfaceButton"; title = "−"; ObjectID = "hjF-xr-cwO"; */ +"hjF-xr-cwO.title" = "−"; + +/* Class = "WKInterfaceLabel"; text = "Carbs"; ObjectID = "hln-CI-MRP"; */ +"hln-CI-MRP.text" = "Kulhydrater"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "IRi-4t-ESO"; */ +"IRi-4t-ESO.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "Bolus Failed"; ObjectID = "jj3-Gq-HBy"; */ +"jj3-Gq-HBy.text" = "Bolus Fejlede"; + +/* Class = "WKInterfaceLabel"; text = "Running"; ObjectID = "JXa-s1-PJx"; */ +"JXa-s1-PJx.text" = "Løber"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "Mhe-aR-kQQ"; */ +"Mhe-aR-kQQ.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "0.000"; ObjectID = "mpK-zY-UvA"; */ +"mpK-zY-UvA.text" = "0.000"; + +/* Class = "WKInterfaceLabel"; text = "TITLE"; ObjectID = "MZU-QV-PtZ"; */ +"MZU-QV-PtZ.text" = "TITEL"; + +/* Class = "WKInterfaceLabel"; text = "Override"; ObjectID = "nC0-X3-oFJ"; */ +"nC0-X3-oFJ.text" = "Overstyr"; + +/* Class = "WKInterfaceButton"; title = "Bolus"; ObjectID = "Qsq-p5-1J0"; */ +"Qsq-p5-1J0.title" = "Bolus"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "rNf-Mh-tID"; */ +"rNf-Mh-tID.title" = "Loop"; + +/* Class = "WKInterfaceLabel"; text = "UNITS"; ObjectID = "rV7-d9-n6u"; */ +"rV7-d9-n6u.text" = "ENHEDER"; + +/* Class = "WKInterfaceLabel"; text = "Bolus"; ObjectID = "smL-Rc-IZh"; */ +"smL-Rc-IZh.text" = "Bolus"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "T4U-wP-dSW"; */ +"T4U-wP-dSW.text" = "Label"; + +/* Class = "WKInterfaceLabel"; text = "10:09 AM"; ObjectID = "Ury-of-vQg"; */ +"Ury-of-vQg.text" = "10:09 AM"; + +/* Class = "WKInterfaceLabel"; text = "🏃‍♀️"; ObjectID = "UVY-pa-SUL"; */ +"UVY-pa-SUL.text" = "🏃‍♀️"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "v5b-sO-bb8"; */ +"v5b-sO-bb8.title" = "Loop"; + +/* Class = "WKInterfaceMenuItem"; title = "1 hour"; ObjectID = "vL1-NA-WZ1"; */ +"vL1-NA-WZ1.title" = "1 time"; + +/* Class = "WKInterfaceLabel"; text = ""; ObjectID = "XkS-y5-khE"; */ +"XkS-y5-khE.text" = ""; + +/* Class = "WKInterfaceLabel"; text = "ACTIVE CARBS"; ObjectID = "ycL-5X-a05"; */ +"ycL-5X-a05.text" = "AKTIVE KULHYDRATER"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "yl8-ZP-c3l"; */ +"yl8-ZP-c3l.text" = "---"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "zO8-x6-bZd"; */ +"zO8-x6-bZd.text" = "Label"; + diff --git a/WatchApp/de.lproj/InfoPlist.strings b/WatchApp/de.lproj/InfoPlist.strings deleted file mode 100644 index 9250064a26..0000000000 --- a/WatchApp/de.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* (No Comment) */ -"CFBundleDisplayName" = "Loop"; - -/* (No Comment) */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/WatchApp/de.lproj/Interface.strings b/WatchApp/de.lproj/Interface.strings index 127eb5b947..f8a3658c17 100644 --- a/WatchApp/de.lproj/Interface.strings +++ b/WatchApp/de.lproj/Interface.strings @@ -4,9 +4,15 @@ /* Class = "WKInterfaceButton"; title = "Add Carbs"; ObjectID = "b6f-3I-jki"; */ "b6f-3I-jki.title" = "Carbs zufuegen"; +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "CsQ-fc-KLC"; */ +"CsQ-fc-KLC.text" = "—"; + /* Class = "WKInterfaceLabel"; text = "15"; ObjectID = "CWt-7U-cnK"; */ "CWt-7U-cnK.text" = "15"; +/* Class = "WKInterfaceLabel"; text = "TOTAL CARBS"; ObjectID = "dea-qG-va8"; */ +"dea-qG-va8.text" = "Gesamtkohlenhydrate"; + /* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "Dh9-HV-fXy"; */ "Dh9-HV-fXy.accessibilityLabel" = "Abziehen"; @@ -16,6 +22,12 @@ /* Class = "WKInterfaceButton"; title = "🍕"; ObjectID = "dPF-QZ-sh6"; */ "dPF-QZ-sh6.title" = "🍕"; +/* Class = "WKInterfaceMenuItem"; title = "2 hours"; ObjectID = "dPh-7b-Tfv"; */ +"dPh-7b-Tfv.title" = "2 Stunden"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "Dt1-kz-jMZ"; */ +"Dt1-kz-jMZ.text" = "---"; + /* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "DZc-Gn-RLu"; */ "DZc-Gn-RLu.accessibilityLabel" = "Zufuegen"; @@ -31,6 +43,9 @@ /* Class = "WKInterfaceLabel"; text = "Pre-Meal"; ObjectID = "f5G-bS-9pd"; */ "f5G-bS-9pd.text" = "Vor dem Essen"; +/* Class = "WKInterfaceMenuItem"; title = "3 hours"; ObjectID = "fR1-7h-SNe"; */ +"fR1-7h-SNe.title" = "3 Stunden"; + /* Class = "WKInterfaceButton"; title = "🍭"; ObjectID = "gAn-qe-OvX"; */ "gAn-qe-OvX.title" = "🍭"; @@ -43,14 +58,26 @@ /* Class = "WKInterfaceLabel"; text = "Carbs"; ObjectID = "hln-CI-MRP"; */ "hln-CI-MRP.text" = "Carbs"; +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "IRi-4t-ESO"; */ +"IRi-4t-ESO.text" = "—"; + /* Class = "WKInterfaceLabel"; text = "Bolus Failed"; ObjectID = "jj3-Gq-HBy"; */ "jj3-Gq-HBy.text" = "Bolus fehlgeschlagen"; +/* Class = "WKInterfaceLabel"; text = "Running"; ObjectID = "JXa-s1-PJx"; */ +"JXa-s1-PJx.text" = "Training"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "Mhe-aR-kQQ"; */ +"Mhe-aR-kQQ.text" = "—"; + /* Class = "WKInterfaceLabel"; text = "0.000"; ObjectID = "mpK-zY-UvA"; */ "mpK-zY-UvA.text" = "0.000"; -/* Class = "WKInterfaceLabel"; text = "Workout"; ObjectID = "nC0-X3-oFJ"; */ -"nC0-X3-oFJ.text" = "Training"; +/* Class = "WKInterfaceLabel"; text = "TITLE"; ObjectID = "MZU-QV-PtZ"; */ +"MZU-QV-PtZ.text" = "TITEL"; + +/* Class = "WKInterfaceLabel"; text = "Override"; ObjectID = "nC0-X3-oFJ"; */ +"nC0-X3-oFJ.text" = "Override"; /* Class = "WKInterfaceButton"; title = "Bolus"; ObjectID = "Qsq-p5-1J0"; */ "Qsq-p5-1J0.title" = "Bolus"; @@ -64,9 +91,30 @@ /* Class = "WKInterfaceLabel"; text = "Bolus"; ObjectID = "smL-Rc-IZh"; */ "smL-Rc-IZh.text" = "Bolus"; +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "T4U-wP-dSW"; */ +"T4U-wP-dSW.text" = "Label"; + /* Class = "WKInterfaceLabel"; text = "10:09 AM"; ObjectID = "Ury-of-vQg"; */ "Ury-of-vQg.text" = "10:09 AM"; +/* Class = "WKInterfaceLabel"; text = "🏃‍♀️"; ObjectID = "UVY-pa-SUL"; */ +"UVY-pa-SUL.text" = "🏃‍♀️"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "v5b-sO-bb8"; */ +"v5b-sO-bb8.title" = "Loop"; + +/* Class = "WKInterfaceMenuItem"; title = "1 hour"; ObjectID = "vL1-NA-WZ1"; */ +"vL1-NA-WZ1.title" = "1 Stunde"; + /* Class = "WKInterfaceLabel"; text = ""; ObjectID = "XkS-y5-khE"; */ "XkS-y5-khE.text" = ""; +/* Class = "WKInterfaceLabel"; text = "ACTIVE CARBS"; ObjectID = "ycL-5X-a05"; */ +"ycL-5X-a05.text" = "AKTIVE CARBS"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "yl8-ZP-c3l"; */ +"yl8-ZP-c3l.text" = "---"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "zO8-x6-bZd"; */ +"zO8-x6-bZd.text" = "Label"; + diff --git a/WatchApp/en.lproj/Interface.strings b/WatchApp/en.lproj/Interface.strings new file mode 100644 index 0000000000..c746b40682 --- /dev/null +++ b/WatchApp/en.lproj/Interface.strings @@ -0,0 +1,120 @@ + +/* Class = "WKInterfaceButton"; title = "🌮"; ObjectID = "0fo-Z3-hTi"; */ +"0fo-Z3-hTi.title" = "🌮"; + +/* Class = "WKInterfaceLabel"; text = "15"; ObjectID = "CWt-7U-cnK"; */ +"CWt-7U-cnK.text" = "15"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "CsQ-fc-KLC"; */ +"CsQ-fc-KLC.text" = "—"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "DZc-Gn-RLu"; */ +"DZc-Gn-RLu.accessibilityLabel" = "Add"; + +/* Class = "WKInterfaceButton"; title = "+"; ObjectID = "DZc-Gn-RLu"; */ +"DZc-Gn-RLu.title" = "+"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "Dh9-HV-fXy"; */ +"Dh9-HV-fXy.accessibilityLabel" = "Subtract"; + +/* Class = "WKInterfaceButton"; title = "−"; ObjectID = "Dh9-HV-fXy"; */ +"Dh9-HV-fXy.title" = "−"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "Dt1-kz-jMZ"; */ +"Dt1-kz-jMZ.text" = "---"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "IRi-4t-ESO"; */ +"IRi-4t-ESO.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "Running"; ObjectID = "JXa-s1-PJx"; */ +"JXa-s1-PJx.text" = "Running"; + +/* Class = "WKInterfaceLabel"; text = "TITLE"; ObjectID = "MZU-QV-PtZ"; */ +"MZU-QV-PtZ.text" = "TITLE"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "Mhe-aR-kQQ"; */ +"Mhe-aR-kQQ.text" = "—"; + +/* Class = "WKInterfaceButton"; title = "Bolus"; ObjectID = "Qsq-p5-1J0"; */ +"Qsq-p5-1J0.title" = "Bolus"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "T4U-wP-dSW"; */ +"T4U-wP-dSW.text" = "Label"; + +/* Class = "WKInterfaceLabel"; text = "🏃‍♀️"; ObjectID = "UVY-pa-SUL"; */ +"UVY-pa-SUL.text" = "🏃‍♀️"; + +/* Class = "WKInterfaceLabel"; text = "10:09 AM"; ObjectID = "Ury-of-vQg"; */ +"Ury-of-vQg.text" = "10:09 AM"; + +/* Class = "WKInterfaceLabel"; text = ""; ObjectID = "XkS-y5-khE"; */ +"XkS-y5-khE.text" = ""; + +/* Class = "WKInterfaceButton"; title = "Add Carbs"; ObjectID = "b6f-3I-jki"; */ +"b6f-3I-jki.title" = "Add Carbs"; + +/* Class = "WKInterfaceButton"; title = "🍕"; ObjectID = "dPF-QZ-sh6"; */ +"dPF-QZ-sh6.title" = "🍕"; + +/* Class = "WKInterfaceMenuItem"; title = "2 hours"; ObjectID = "dPh-7b-Tfv"; */ +"dPh-7b-Tfv.title" = "2 hours"; + +/* Class = "WKInterfaceLabel"; text = "TOTAL CARBS"; ObjectID = "dea-qG-va8"; */ +"dea-qG-va8.text" = "TOTAL CARBS"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "eu3-pj-GH3"; */ +"eu3-pj-GH3.accessibilityLabel" = "Add"; + +/* Class = "WKInterfaceButton"; title = "+"; ObjectID = "eu3-pj-GH3"; */ +"eu3-pj-GH3.title" = "+"; + +/* Class = "WKInterfaceLabel"; text = "Pre-Meal"; ObjectID = "f5G-bS-9pd"; */ +"f5G-bS-9pd.text" = "Pre-Meal"; + +/* Class = "WKInterfaceMenuItem"; title = "3 hours"; ObjectID = "fR1-7h-SNe"; */ +"fR1-7h-SNe.title" = "3 hours"; + +/* Class = "WKInterfaceButton"; title = "🍭"; ObjectID = "gAn-qe-OvX"; */ +"gAn-qe-OvX.title" = "🍭"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "hjF-xr-cwO"; */ +"hjF-xr-cwO.accessibilityLabel" = "Subtract"; + +/* Class = "WKInterfaceButton"; title = "−"; ObjectID = "hjF-xr-cwO"; */ +"hjF-xr-cwO.title" = "−"; + +/* Class = "WKInterfaceLabel"; text = "Carbs"; ObjectID = "hln-CI-MRP"; */ +"hln-CI-MRP.text" = "Carbs"; + +/* Class = "WKInterfaceLabel"; text = "Bolus Failed"; ObjectID = "jj3-Gq-HBy"; */ +"jj3-Gq-HBy.text" = "Bolus Failed"; + +/* Class = "WKInterfaceLabel"; text = "0.000"; ObjectID = "mpK-zY-UvA"; */ +"mpK-zY-UvA.text" = "0.000"; + +/* Class = "WKInterfaceLabel"; text = "Override"; ObjectID = "nC0-X3-oFJ"; */ +"nC0-X3-oFJ.text" = "Override"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "rNf-Mh-tID"; */ +"rNf-Mh-tID.title" = "Loop"; + +/* Class = "WKInterfaceLabel"; text = "UNITS"; ObjectID = "rV7-d9-n6u"; */ +"rV7-d9-n6u.text" = "UNITS"; + +/* Class = "WKInterfaceLabel"; text = "Bolus"; ObjectID = "smL-Rc-IZh"; */ +"smL-Rc-IZh.text" = "Bolus"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "v5b-sO-bb8"; */ +"v5b-sO-bb8.title" = "Loop"; + +/* Class = "WKInterfaceMenuItem"; title = "1 hour"; ObjectID = "vL1-NA-WZ1"; */ +"vL1-NA-WZ1.title" = "1 hour"; + +/* Class = "WKInterfaceLabel"; text = "ACTIVE CARBS"; ObjectID = "ycL-5X-a05"; */ +"ycL-5X-a05.text" = "ACTIVE CARBS"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "yl8-ZP-c3l"; */ +"yl8-ZP-c3l.text" = "---"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "zO8-x6-bZd"; */ +"zO8-x6-bZd.text" = "Label"; diff --git a/WatchApp/es.lproj/InfoPlist.strings b/WatchApp/es.lproj/InfoPlist.strings deleted file mode 100644 index 9250064a26..0000000000 --- a/WatchApp/es.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* (No Comment) */ -"CFBundleDisplayName" = "Loop"; - -/* (No Comment) */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/WatchApp/es.lproj/Interface.strings b/WatchApp/es.lproj/Interface.strings index da577b444a..2db0c91e29 100644 --- a/WatchApp/es.lproj/Interface.strings +++ b/WatchApp/es.lproj/Interface.strings @@ -1,26 +1,66 @@ + /* Class = "WKInterfaceButton"; title = "🌮"; ObjectID = "0fo-Z3-hTi"; */ "0fo-Z3-hTi.title" = "🌮"; -/* Class = "WKInterfaceButton"; title = "Add Carbs"; ObjectID = "b6f-3I-jki"; */ -"b6f-3I-jki.title" = "Agregar Carbs"; - /* Class = "WKInterfaceLabel"; text = "15"; ObjectID = "CWt-7U-cnK"; */ "CWt-7U-cnK.text" = "15"; +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "CsQ-fc-KLC"; */ +"CsQ-fc-KLC.text" = "—"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "DZc-Gn-RLu"; */ +"DZc-Gn-RLu.accessibilityLabel" = "Agregar"; + +/* Class = "WKInterfaceButton"; title = "+"; ObjectID = "DZc-Gn-RLu"; */ +"DZc-Gn-RLu.title" = "+"; + /* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "Dh9-HV-fXy"; */ -"Dh9-HV-fXy.accessibilityLabel" = "Substraer"; +"Dh9-HV-fXy.accessibilityLabel" = "Sustraer"; /* Class = "WKInterfaceButton"; title = "−"; ObjectID = "Dh9-HV-fXy"; */ "Dh9-HV-fXy.title" = "−"; +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "Dt1-kz-jMZ"; */ +"Dt1-kz-jMZ.text" = "---"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "IRi-4t-ESO"; */ +"IRi-4t-ESO.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "Running"; ObjectID = "JXa-s1-PJx"; */ +"JXa-s1-PJx.text" = "Correr"; + +/* Class = "WKInterfaceLabel"; text = "TITLE"; ObjectID = "MZU-QV-PtZ"; */ +"MZU-QV-PtZ.text" = "TÍTULO"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "Mhe-aR-kQQ"; */ +"Mhe-aR-kQQ.text" = "—"; + +/* Class = "WKInterfaceButton"; title = "Bolus"; ObjectID = "Qsq-p5-1J0"; */ +"Qsq-p5-1J0.title" = "Bolo"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "T4U-wP-dSW"; */ +"T4U-wP-dSW.text" = "Etiqueta"; + +/* Class = "WKInterfaceLabel"; text = "🏃‍♀️"; ObjectID = "UVY-pa-SUL"; */ +"UVY-pa-SUL.text" = "🏃‍♀️"; + +/* Class = "WKInterfaceLabel"; text = "10:09 AM"; ObjectID = "Ury-of-vQg"; */ +"Ury-of-vQg.text" = "10:09 AM"; + +/* Class = "WKInterfaceLabel"; text = ""; ObjectID = "XkS-y5-khE"; */ +"XkS-y5-khE.text" = ""; + +/* Class = "WKInterfaceButton"; title = "Add Carbs"; ObjectID = "b6f-3I-jki"; */ +"b6f-3I-jki.title" = "Agregar Carbs"; + /* Class = "WKInterfaceButton"; title = "🍕"; ObjectID = "dPF-QZ-sh6"; */ "dPF-QZ-sh6.title" = "🍕"; -/* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "DZc-Gn-RLu"; */ -"DZc-Gn-RLu.accessibilityLabel" = "Agregar"; +/* Class = "WKInterfaceMenuItem"; title = "2 hours"; ObjectID = "dPh-7b-Tfv"; */ +"dPh-7b-Tfv.title" = "2 horas"; -/* Class = "WKInterfaceButton"; title = "+"; ObjectID = "DZc-Gn-RLu"; */ -"DZc-Gn-RLu.title" = "+"; +/* Class = "WKInterfaceLabel"; text = "TOTAL CARBS"; ObjectID = "dea-qG-va8"; */ +"dea-qG-va8.text" = "CARBS"; /* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "eu3-pj-GH3"; */ "eu3-pj-GH3.accessibilityLabel" = "Agregar"; @@ -31,6 +71,9 @@ /* Class = "WKInterfaceLabel"; text = "Pre-Meal"; ObjectID = "f5G-bS-9pd"; */ "f5G-bS-9pd.text" = "Pre-Comida"; +/* Class = "WKInterfaceMenuItem"; title = "3 hours"; ObjectID = "fR1-7h-SNe"; */ +"fR1-7h-SNe.title" = "3 horas"; + /* Class = "WKInterfaceButton"; title = "🍭"; ObjectID = "gAn-qe-OvX"; */ "gAn-qe-OvX.title" = "🍭"; @@ -49,11 +92,8 @@ /* Class = "WKInterfaceLabel"; text = "0.000"; ObjectID = "mpK-zY-UvA"; */ "mpK-zY-UvA.text" = "0.000"; -/* Class = "WKInterfaceLabel"; text = "Workout"; ObjectID = "nC0-X3-oFJ"; */ -"nC0-X3-oFJ.text" = "Ejercicio"; - -/* Class = "WKInterfaceButton"; title = "Bolus"; ObjectID = "Qsq-p5-1J0"; */ -"Qsq-p5-1J0.title" = "Bolo"; +/* Class = "WKInterfaceLabel"; text = "Override"; ObjectID = "nC0-X3-oFJ"; */ +"nC0-X3-oFJ.text" = "Sobreescritura"; /* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "rNf-Mh-tID"; */ "rNf-Mh-tID.title" = "Loop"; @@ -64,9 +104,17 @@ /* Class = "WKInterfaceLabel"; text = "Bolus"; ObjectID = "smL-Rc-IZh"; */ "smL-Rc-IZh.text" = "Bolo"; -/* Class = "WKInterfaceLabel"; text = "10:09 AM"; ObjectID = "Ury-of-vQg"; */ -"Ury-of-vQg.text" = "10:09 AM"; +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "v5b-sO-bb8"; */ +"v5b-sO-bb8.title" = "Loop"; -/* Class = "WKInterfaceLabel"; text = ""; ObjectID = "XkS-y5-khE"; */ -"XkS-y5-khE.text" = ""; +/* Class = "WKInterfaceMenuItem"; title = "1 hour"; ObjectID = "vL1-NA-WZ1"; */ +"vL1-NA-WZ1.title" = "1 hora"; + +/* Class = "WKInterfaceLabel"; text = "ACTIVE CARBS"; ObjectID = "ycL-5X-a05"; */ +"ycL-5X-a05.text" = "CARBS ACTIVOS"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "yl8-ZP-c3l"; */ +"yl8-ZP-c3l.text" = "---"; +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "zO8-x6-bZd"; */ +"zO8-x6-bZd.text" = "Etiqueta"; diff --git a/WatchApp/fi.lproj/Interface.strings b/WatchApp/fi.lproj/Interface.strings new file mode 100644 index 0000000000..5a25daef1b --- /dev/null +++ b/WatchApp/fi.lproj/Interface.strings @@ -0,0 +1,120 @@ +/* Class = "WKInterfaceButton"; title = "🌮"; ObjectID = "0fo-Z3-hTi"; */ +"0fo-Z3-hTi.title" = "🌮"; + +/* Class = "WKInterfaceButton"; title = "Add Carbs"; ObjectID = "b6f-3I-jki"; */ +"b6f-3I-jki.title" = "Lisää hiilihydraatteja"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "CsQ-fc-KLC"; */ +"CsQ-fc-KLC.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "15"; ObjectID = "CWt-7U-cnK"; */ +"CWt-7U-cnK.text" = "15"; + +/* Class = "WKInterfaceLabel"; text = "TOTAL CARBS"; ObjectID = "dea-qG-va8"; */ +"dea-qG-va8.text" = "HIILIHYDRAATTEJA YHT."; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "Dh9-HV-fXy"; */ +"Dh9-HV-fXy.accessibilityLabel" = "Vähennä"; + +/* Class = "WKInterfaceButton"; title = "−"; ObjectID = "Dh9-HV-fXy"; */ +"Dh9-HV-fXy.title" = "−"; + +/* Class = "WKInterfaceButton"; title = "🍕"; ObjectID = "dPF-QZ-sh6"; */ +"dPF-QZ-sh6.title" = "🍕"; + +/* Class = "WKInterfaceMenuItem"; title = "2 hours"; ObjectID = "dPh-7b-Tfv"; */ +"dPh-7b-Tfv.title" = "2 tuntia"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "Dt1-kz-jMZ"; */ +"Dt1-kz-jMZ.text" = "---"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "DZc-Gn-RLu"; */ +"DZc-Gn-RLu.accessibilityLabel" = "Lisää"; + +/* Class = "WKInterfaceButton"; title = "+"; ObjectID = "DZc-Gn-RLu"; */ +"DZc-Gn-RLu.title" = "+"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "eu3-pj-GH3"; */ +"eu3-pj-GH3.accessibilityLabel" = "Lisää"; + +/* Class = "WKInterfaceButton"; title = "+"; ObjectID = "eu3-pj-GH3"; */ +"eu3-pj-GH3.title" = "+"; + +/* Class = "WKInterfaceLabel"; text = "Pre-Meal"; ObjectID = "f5G-bS-9pd"; */ +"f5G-bS-9pd.text" = "Ennen ateriaa"; + +/* Class = "WKInterfaceMenuItem"; title = "3 hours"; ObjectID = "fR1-7h-SNe"; */ +"fR1-7h-SNe.title" = "3 tuntia"; + +/* Class = "WKInterfaceButton"; title = "🍭"; ObjectID = "gAn-qe-OvX"; */ +"gAn-qe-OvX.title" = "🍭"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "hjF-xr-cwO"; */ +"hjF-xr-cwO.accessibilityLabel" = "Vähennä"; + +/* Class = "WKInterfaceButton"; title = "−"; ObjectID = "hjF-xr-cwO"; */ +"hjF-xr-cwO.title" = "−"; + +/* Class = "WKInterfaceLabel"; text = "Carbs"; ObjectID = "hln-CI-MRP"; */ +"hln-CI-MRP.text" = "Hiilihydraatit"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "IRi-4t-ESO"; */ +"IRi-4t-ESO.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "Bolus Failed"; ObjectID = "jj3-Gq-HBy"; */ +"jj3-Gq-HBy.text" = "Bolus epäonnistui"; + +/* Class = "WKInterfaceLabel"; text = "Running"; ObjectID = "JXa-s1-PJx"; */ +"JXa-s1-PJx.text" = "Juoksu"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "Mhe-aR-kQQ"; */ +"Mhe-aR-kQQ.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "0.000"; ObjectID = "mpK-zY-UvA"; */ +"mpK-zY-UvA.text" = "0.000"; + +/* Class = "WKInterfaceLabel"; text = "TITLE"; ObjectID = "MZU-QV-PtZ"; */ +"MZU-QV-PtZ.text" = "OTSIKKO"; + +/* Class = "WKInterfaceLabel"; text = "Override"; ObjectID = "nC0-X3-oFJ"; */ +"nC0-X3-oFJ.text" = "Override"; + +/* Class = "WKInterfaceButton"; title = "Bolus"; ObjectID = "Qsq-p5-1J0"; */ +"Qsq-p5-1J0.title" = "Bolus"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "rNf-Mh-tID"; */ +"rNf-Mh-tID.title" = "Loop"; + +/* Class = "WKInterfaceLabel"; text = "UNITS"; ObjectID = "rV7-d9-n6u"; */ +"rV7-d9-n6u.text" = "YKSIKKÖÄ"; + +/* Class = "WKInterfaceLabel"; text = "Bolus"; ObjectID = "smL-Rc-IZh"; */ +"smL-Rc-IZh.text" = "Bolus"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "T4U-wP-dSW"; */ +"T4U-wP-dSW.text" = "Nimiö"; + +/* Class = "WKInterfaceLabel"; text = "10:09 AM"; ObjectID = "Ury-of-vQg"; */ +"Ury-of-vQg.text" = "10:09 AM"; + +/* Class = "WKInterfaceLabel"; text = "🏃‍♀️"; ObjectID = "UVY-pa-SUL"; */ +"UVY-pa-SUL.text" = "🏃‍♀️"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "v5b-sO-bb8"; */ +"v5b-sO-bb8.title" = "Loop"; + +/* Class = "WKInterfaceMenuItem"; title = "1 hour"; ObjectID = "vL1-NA-WZ1"; */ +"vL1-NA-WZ1.title" = "1 tunti"; + +/* Class = "WKInterfaceLabel"; text = ""; ObjectID = "XkS-y5-khE"; */ +"XkS-y5-khE.text" = ""; + +/* Class = "WKInterfaceLabel"; text = "ACTIVE CARBS"; ObjectID = "ycL-5X-a05"; */ +"ycL-5X-a05.text" = "AKTIIVINEN HIILIHYDRAATTI"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "yl8-ZP-c3l"; */ +"yl8-ZP-c3l.text" = "---"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "zO8-x6-bZd"; */ +"zO8-x6-bZd.text" = "Nimiö"; + diff --git a/WatchApp/fr.lproj/InfoPlist.strings b/WatchApp/fr.lproj/InfoPlist.strings deleted file mode 100644 index 9250064a26..0000000000 --- a/WatchApp/fr.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* (No Comment) */ -"CFBundleDisplayName" = "Loop"; - -/* (No Comment) */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/WatchApp/fr.lproj/Interface.strings b/WatchApp/fr.lproj/Interface.strings index f35c37da43..00375015c7 100644 --- a/WatchApp/fr.lproj/Interface.strings +++ b/WatchApp/fr.lproj/Interface.strings @@ -4,9 +4,15 @@ /* Class = "WKInterfaceButton"; title = "Add Carbs"; ObjectID = "b6f-3I-jki"; */ "b6f-3I-jki.title" = "Ajouter des glucides"; +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "CsQ-fc-KLC"; */ +"CsQ-fc-KLC.text" = "—"; + /* Class = "WKInterfaceLabel"; text = "15"; ObjectID = "CWt-7U-cnK"; */ "CWt-7U-cnK.text" = "15"; +/* Class = "WKInterfaceLabel"; text = "TOTAL CARBS"; ObjectID = "dea-qG-va8"; */ +"dea-qG-va8.text" = "GLUCIDES TOTAUX"; + /* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "Dh9-HV-fXy"; */ "Dh9-HV-fXy.accessibilityLabel" = "Soustraire"; @@ -16,6 +22,12 @@ /* Class = "WKInterfaceButton"; title = "🍕"; ObjectID = "dPF-QZ-sh6"; */ "dPF-QZ-sh6.title" = "🍕"; +/* Class = "WKInterfaceMenuItem"; title = "2 hours"; ObjectID = "dPh-7b-Tfv"; */ +"dPh-7b-Tfv.title" = "2 heures"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "Dt1-kz-jMZ"; */ +"Dt1-kz-jMZ.text" = "---"; + /* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "DZc-Gn-RLu"; */ "DZc-Gn-RLu.accessibilityLabel" = "Ajouter"; @@ -31,6 +43,9 @@ /* Class = "WKInterfaceLabel"; text = "Pre-Meal"; ObjectID = "f5G-bS-9pd"; */ "f5G-bS-9pd.text" = "Pré-Repas"; +/* Class = "WKInterfaceMenuItem"; title = "3 hours"; ObjectID = "fR1-7h-SNe"; */ +"fR1-7h-SNe.title" = "3 heures"; + /* Class = "WKInterfaceButton"; title = "🍭"; ObjectID = "gAn-qe-OvX"; */ "gAn-qe-OvX.title" = "🍭"; @@ -43,14 +58,26 @@ /* Class = "WKInterfaceLabel"; text = "Carbs"; ObjectID = "hln-CI-MRP"; */ "hln-CI-MRP.text" = "Glucides"; +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "IRi-4t-ESO"; */ +"IRi-4t-ESO.text" = "—"; + /* Class = "WKInterfaceLabel"; text = "Bolus Failed"; ObjectID = "jj3-Gq-HBy"; */ "jj3-Gq-HBy.text" = "Échec du bolus"; +/* Class = "WKInterfaceLabel"; text = "Running"; ObjectID = "JXa-s1-PJx"; */ +"JXa-s1-PJx.text" = "Course"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "Mhe-aR-kQQ"; */ +"Mhe-aR-kQQ.text" = "—"; + /* Class = "WKInterfaceLabel"; text = "0.000"; ObjectID = "mpK-zY-UvA"; */ "mpK-zY-UvA.text" = "0.000"; -/* Class = "WKInterfaceLabel"; text = "Workout"; ObjectID = "nC0-X3-oFJ"; */ -"nC0-X3-oFJ.text" = "Entraînement"; +/* Class = "WKInterfaceLabel"; text = "TITLE"; ObjectID = "MZU-QV-PtZ"; */ +"MZU-QV-PtZ.text" = "TITLE"; + +/* Class = "WKInterfaceLabel"; text = "Override"; ObjectID = "nC0-X3-oFJ"; */ +"nC0-X3-oFJ.text" = "Surcharge"; /* Class = "WKInterfaceButton"; title = "Bolus"; ObjectID = "Qsq-p5-1J0"; */ "Qsq-p5-1J0.title" = "Bolus"; @@ -64,9 +91,30 @@ /* Class = "WKInterfaceLabel"; text = "Bolus"; ObjectID = "smL-Rc-IZh"; */ "smL-Rc-IZh.text" = "Bolus"; +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "T4U-wP-dSW"; */ +"T4U-wP-dSW.text" = "Label"; + /* Class = "WKInterfaceLabel"; text = "10:09 AM"; ObjectID = "Ury-of-vQg"; */ "Ury-of-vQg.text" = "10:09 AM"; +/* Class = "WKInterfaceLabel"; text = "🏃‍♀️"; ObjectID = "UVY-pa-SUL"; */ +"UVY-pa-SUL.text" = "🏃‍♀️"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "v5b-sO-bb8"; */ +"v5b-sO-bb8.title" = "Loop"; + +/* Class = "WKInterfaceMenuItem"; title = "1 hour"; ObjectID = "vL1-NA-WZ1"; */ +"vL1-NA-WZ1.title" = "1 heure"; + /* Class = "WKInterfaceLabel"; text = ""; ObjectID = "XkS-y5-khE"; */ "XkS-y5-khE.text" = ""; +/* Class = "WKInterfaceLabel"; text = "ACTIVE CARBS"; ObjectID = "ycL-5X-a05"; */ +"ycL-5X-a05.text" = "GLUCIDES ACTIFS"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "yl8-ZP-c3l"; */ +"yl8-ZP-c3l.text" = "---"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "zO8-x6-bZd"; */ +"zO8-x6-bZd.text" = "Nom"; + diff --git a/WatchApp/it.lproj/InfoPlist.strings b/WatchApp/it.lproj/InfoPlist.strings deleted file mode 100644 index 9250064a26..0000000000 --- a/WatchApp/it.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* (No Comment) */ -"CFBundleDisplayName" = "Loop"; - -/* (No Comment) */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/WatchApp/it.lproj/Interface.strings b/WatchApp/it.lproj/Interface.strings index 617964b394..ef86a11670 100644 --- a/WatchApp/it.lproj/Interface.strings +++ b/WatchApp/it.lproj/Interface.strings @@ -4,9 +4,15 @@ /* Class = "WKInterfaceButton"; title = "Add Carbs"; ObjectID = "b6f-3I-jki"; */ "b6f-3I-jki.title" = "Aggiungi Carb"; +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "CsQ-fc-KLC"; */ +"CsQ-fc-KLC.text" = "—"; + /* Class = "WKInterfaceLabel"; text = "15"; ObjectID = "CWt-7U-cnK"; */ "CWt-7U-cnK.text" = "15"; +/* Class = "WKInterfaceLabel"; text = "TOTAL CARBS"; ObjectID = "dea-qG-va8"; */ +"dea-qG-va8.text" = "TOTALE CARBOIDRATI"; + /* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "Dh9-HV-fXy"; */ "Dh9-HV-fXy.accessibilityLabel" = "Sottrarre"; @@ -16,6 +22,12 @@ /* Class = "WKInterfaceButton"; title = "🍕"; ObjectID = "dPF-QZ-sh6"; */ "dPF-QZ-sh6.title" = "🍕"; +/* Class = "WKInterfaceMenuItem"; title = "2 hours"; ObjectID = "dPh-7b-Tfv"; */ +"dPh-7b-Tfv.title" = "2 ore"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "Dt1-kz-jMZ"; */ +"Dt1-kz-jMZ.text" = "---"; + /* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "DZc-Gn-RLu"; */ "DZc-Gn-RLu.accessibilityLabel" = "Aggiungi"; @@ -31,6 +43,9 @@ /* Class = "WKInterfaceLabel"; text = "Pre-Meal"; ObjectID = "f5G-bS-9pd"; */ "f5G-bS-9pd.text" = "Pre-Pasto"; +/* Class = "WKInterfaceMenuItem"; title = "3 hours"; ObjectID = "fR1-7h-SNe"; */ +"fR1-7h-SNe.title" = "3 ore"; + /* Class = "WKInterfaceButton"; title = "🍭"; ObjectID = "gAn-qe-OvX"; */ "gAn-qe-OvX.title" = "🍭"; @@ -41,16 +56,28 @@ "hjF-xr-cwO.title" = "−"; /* Class = "WKInterfaceLabel"; text = "Carbs"; ObjectID = "hln-CI-MRP"; */ -"hln-CI-MRP.text" = "Carb"; +"hln-CI-MRP.text" = "Carboidrati"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "IRi-4t-ESO"; */ +"IRi-4t-ESO.text" = "—"; /* Class = "WKInterfaceLabel"; text = "Bolus Failed"; ObjectID = "jj3-Gq-HBy"; */ "jj3-Gq-HBy.text" = "Bolo Fallito"; +/* Class = "WKInterfaceLabel"; text = "Running"; ObjectID = "JXa-s1-PJx"; */ +"JXa-s1-PJx.text" = "Attività fisica"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "Mhe-aR-kQQ"; */ +"Mhe-aR-kQQ.text" = "—"; + /* Class = "WKInterfaceLabel"; text = "0.000"; ObjectID = "mpK-zY-UvA"; */ "mpK-zY-UvA.text" = "0.000"; -/* Class = "WKInterfaceLabel"; text = "Workout"; ObjectID = "nC0-X3-oFJ"; */ -"nC0-X3-oFJ.text" = "Attività fisica"; +/* Class = "WKInterfaceLabel"; text = "TITLE"; ObjectID = "MZU-QV-PtZ"; */ +"MZU-QV-PtZ.text" = "TITOLO"; + +/* Class = "WKInterfaceLabel"; text = "Override"; ObjectID = "nC0-X3-oFJ"; */ +"nC0-X3-oFJ.text" = "Override"; /* Class = "WKInterfaceButton"; title = "Bolus"; ObjectID = "Qsq-p5-1J0"; */ "Qsq-p5-1J0.title" = "Bolo"; @@ -64,9 +91,30 @@ /* Class = "WKInterfaceLabel"; text = "Bolus"; ObjectID = "smL-Rc-IZh"; */ "smL-Rc-IZh.text" = "Bolo"; +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "T4U-wP-dSW"; */ +"T4U-wP-dSW.text" = "Etichetta"; + /* Class = "WKInterfaceLabel"; text = "10:09 AM"; ObjectID = "Ury-of-vQg"; */ "Ury-of-vQg.text" = "10:09 AM"; +/* Class = "WKInterfaceLabel"; text = "🏃‍♀️"; ObjectID = "UVY-pa-SUL"; */ +"UVY-pa-SUL.text" = "🏃‍♀️"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "v5b-sO-bb8"; */ +"v5b-sO-bb8.title" = "Loop"; + +/* Class = "WKInterfaceMenuItem"; title = "1 hour"; ObjectID = "vL1-NA-WZ1"; */ +"vL1-NA-WZ1.title" = "1 ora"; + /* Class = "WKInterfaceLabel"; text = ""; ObjectID = "XkS-y5-khE"; */ "XkS-y5-khE.text" = ""; +/* Class = "WKInterfaceLabel"; text = "ACTIVE CARBS"; ObjectID = "ycL-5X-a05"; */ +"ycL-5X-a05.text" = "CARBOIDRATI ATTIVI"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "yl8-ZP-c3l"; */ +"yl8-ZP-c3l.text" = "---"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "zO8-x6-bZd"; */ +"zO8-x6-bZd.text" = "Etichetta"; + diff --git a/WatchApp/ja.lproj/Interface.strings b/WatchApp/ja.lproj/Interface.strings new file mode 100644 index 0000000000..ae0a5e3d21 --- /dev/null +++ b/WatchApp/ja.lproj/Interface.strings @@ -0,0 +1,120 @@ +/* Class = "WKInterfaceButton"; title = "🌮"; ObjectID = "0fo-Z3-hTi"; */ +"0fo-Z3-hTi.title" = "🌮"; + +/* Class = "WKInterfaceButton"; title = "Add Carbs"; ObjectID = "b6f-3I-jki"; */ +"b6f-3I-jki.title" = "糖質を追加"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "CsQ-fc-KLC"; */ +"CsQ-fc-KLC.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "15"; ObjectID = "CWt-7U-cnK"; */ +"CWt-7U-cnK.text" = "15"; + +/* Class = "WKInterfaceLabel"; text = "TOTAL CARBS"; ObjectID = "dea-qG-va8"; */ +"dea-qG-va8.text" = "カーボ合計"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "Dh9-HV-fXy"; */ +"Dh9-HV-fXy.accessibilityLabel" = "消去"; + +/* Class = "WKInterfaceButton"; title = "−"; ObjectID = "Dh9-HV-fXy"; */ +"Dh9-HV-fXy.title" = "−"; + +/* Class = "WKInterfaceButton"; title = "🍕"; ObjectID = "dPF-QZ-sh6"; */ +"dPF-QZ-sh6.title" = "🍕"; + +/* Class = "WKInterfaceMenuItem"; title = "2 hours"; ObjectID = "dPh-7b-Tfv"; */ +"dPh-7b-Tfv.title" = "2時間"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "Dt1-kz-jMZ"; */ +"Dt1-kz-jMZ.text" = "---"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "DZc-Gn-RLu"; */ +"DZc-Gn-RLu.accessibilityLabel" = "追加"; + +/* Class = "WKInterfaceButton"; title = "+"; ObjectID = "DZc-Gn-RLu"; */ +"DZc-Gn-RLu.title" = "+"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "eu3-pj-GH3"; */ +"eu3-pj-GH3.accessibilityLabel" = "追加"; + +/* Class = "WKInterfaceButton"; title = "+"; ObjectID = "eu3-pj-GH3"; */ +"eu3-pj-GH3.title" = "+"; + +/* Class = "WKInterfaceLabel"; text = "Pre-Meal"; ObjectID = "f5G-bS-9pd"; */ +"f5G-bS-9pd.text" = "食前"; + +/* Class = "WKInterfaceMenuItem"; title = "3 hours"; ObjectID = "fR1-7h-SNe"; */ +"fR1-7h-SNe.title" = "3時間"; + +/* Class = "WKInterfaceButton"; title = "🍭"; ObjectID = "gAn-qe-OvX"; */ +"gAn-qe-OvX.title" = "🍭"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "hjF-xr-cwO"; */ +"hjF-xr-cwO.accessibilityLabel" = "消去"; + +/* Class = "WKInterfaceButton"; title = "−"; ObjectID = "hjF-xr-cwO"; */ +"hjF-xr-cwO.title" = "−"; + +/* Class = "WKInterfaceLabel"; text = "Carbs"; ObjectID = "hln-CI-MRP"; */ +"hln-CI-MRP.text" = "糖質"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "IRi-4t-ESO"; */ +"IRi-4t-ESO.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "Bolus Failed"; ObjectID = "jj3-Gq-HBy"; */ +"jj3-Gq-HBy.text" = "ボーラス不成功"; + +/* Class = "WKInterfaceLabel"; text = "Running"; ObjectID = "JXa-s1-PJx"; */ +"JXa-s1-PJx.text" = "動作中"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "Mhe-aR-kQQ"; */ +"Mhe-aR-kQQ.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "0.000"; ObjectID = "mpK-zY-UvA"; */ +"mpK-zY-UvA.text" = "0.000"; + +/* Class = "WKInterfaceLabel"; text = "TITLE"; ObjectID = "MZU-QV-PtZ"; */ +"MZU-QV-PtZ.text" = "タイトル"; + +/* Class = "WKInterfaceLabel"; text = "Override"; ObjectID = "nC0-X3-oFJ"; */ +"nC0-X3-oFJ.text" = "オーバーライド"; + +/* Class = "WKInterfaceButton"; title = "Bolus"; ObjectID = "Qsq-p5-1J0"; */ +"Qsq-p5-1J0.title" = "ボーラス"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "rNf-Mh-tID"; */ +"rNf-Mh-tID.title" = "ループ"; + +/* Class = "WKInterfaceLabel"; text = "UNITS"; ObjectID = "rV7-d9-n6u"; */ +"rV7-d9-n6u.text" = "単位"; + +/* Class = "WKInterfaceLabel"; text = "Bolus"; ObjectID = "smL-Rc-IZh"; */ +"smL-Rc-IZh.text" = "ボーラス"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "T4U-wP-dSW"; */ +"T4U-wP-dSW.text" = "ラベル"; + +/* Class = "WKInterfaceLabel"; text = "10:09 AM"; ObjectID = "Ury-of-vQg"; */ +"Ury-of-vQg.text" = "10:09 AM"; + +/* Class = "WKInterfaceLabel"; text = "🏃‍♀️"; ObjectID = "UVY-pa-SUL"; */ +"UVY-pa-SUL.text" = "🏃‍♀️"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "v5b-sO-bb8"; */ +"v5b-sO-bb8.title" = "ループ"; + +/* Class = "WKInterfaceMenuItem"; title = "1 hour"; ObjectID = "vL1-NA-WZ1"; */ +"vL1-NA-WZ1.title" = "1時間"; + +/* Class = "WKInterfaceLabel"; text = ""; ObjectID = "XkS-y5-khE"; */ +"XkS-y5-khE.text" = ""; + +/* Class = "WKInterfaceLabel"; text = "ACTIVE CARBS"; ObjectID = "ycL-5X-a05"; */ +"ycL-5X-a05.text" = "残存糖質"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "yl8-ZP-c3l"; */ +"yl8-ZP-c3l.text" = "---"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "zO8-x6-bZd"; */ +"zO8-x6-bZd.text" = "ラベル"; + diff --git a/WatchApp/nb.lproj/InfoPlist.strings b/WatchApp/nb.lproj/InfoPlist.strings deleted file mode 100644 index 9250064a26..0000000000 --- a/WatchApp/nb.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* (No Comment) */ -"CFBundleDisplayName" = "Loop"; - -/* (No Comment) */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/WatchApp/nb.lproj/Interface.strings b/WatchApp/nb.lproj/Interface.strings index 8168dbc854..b4bd40f052 100644 --- a/WatchApp/nb.lproj/Interface.strings +++ b/WatchApp/nb.lproj/Interface.strings @@ -4,9 +4,15 @@ /* Class = "WKInterfaceButton"; title = "Add Carbs"; ObjectID = "b6f-3I-jki"; */ "b6f-3I-jki.title" = "Legg til karbohydrater"; +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "CsQ-fc-KLC"; */ +"CsQ-fc-KLC.text" = "—"; + /* Class = "WKInterfaceLabel"; text = "15"; ObjectID = "CWt-7U-cnK"; */ "CWt-7U-cnK.text" = "15"; +/* Class = "WKInterfaceLabel"; text = "TOTAL CARBS"; ObjectID = "dea-qG-va8"; */ +"dea-qG-va8.text" = "KARBOHYDRATER TOTALT"; + /* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "Dh9-HV-fXy"; */ "Dh9-HV-fXy.accessibilityLabel" = "Trekk fra"; @@ -16,6 +22,12 @@ /* Class = "WKInterfaceButton"; title = "🍕"; ObjectID = "dPF-QZ-sh6"; */ "dPF-QZ-sh6.title" = "🍕"; +/* Class = "WKInterfaceMenuItem"; title = "2 hours"; ObjectID = "dPh-7b-Tfv"; */ +"dPh-7b-Tfv.title" = "2 timer"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "Dt1-kz-jMZ"; */ +"Dt1-kz-jMZ.text" = "---"; + /* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "DZc-Gn-RLu"; */ "DZc-Gn-RLu.accessibilityLabel" = "Legg til"; @@ -31,6 +43,9 @@ /* Class = "WKInterfaceLabel"; text = "Pre-Meal"; ObjectID = "f5G-bS-9pd"; */ "f5G-bS-9pd.text" = "Pre-måltid"; +/* Class = "WKInterfaceMenuItem"; title = "3 hours"; ObjectID = "fR1-7h-SNe"; */ +"fR1-7h-SNe.title" = "3 timer"; + /* Class = "WKInterfaceButton"; title = "🍭"; ObjectID = "gAn-qe-OvX"; */ "gAn-qe-OvX.title" = "🍭"; @@ -43,14 +58,26 @@ /* Class = "WKInterfaceLabel"; text = "Carbs"; ObjectID = "hln-CI-MRP"; */ "hln-CI-MRP.text" = "Karbohydrater"; +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "IRi-4t-ESO"; */ +"IRi-4t-ESO.text" = "—"; + /* Class = "WKInterfaceLabel"; text = "Bolus Failed"; ObjectID = "jj3-Gq-HBy"; */ "jj3-Gq-HBy.text" = "Bolus feilet"; +/* Class = "WKInterfaceLabel"; text = "Running"; ObjectID = "JXa-s1-PJx"; */ +"JXa-s1-PJx.text" = "Løper"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "Mhe-aR-kQQ"; */ +"Mhe-aR-kQQ.text" = "—"; + /* Class = "WKInterfaceLabel"; text = "0.000"; ObjectID = "mpK-zY-UvA"; */ "mpK-zY-UvA.text" = "0.000"; -/* Class = "WKInterfaceLabel"; text = "Workout"; ObjectID = "nC0-X3-oFJ"; */ -"nC0-X3-oFJ.text" = "Trening"; +/* Class = "WKInterfaceLabel"; text = "TITLE"; ObjectID = "MZU-QV-PtZ"; */ +"MZU-QV-PtZ.text" = "TITTEL"; + +/* Class = "WKInterfaceLabel"; text = "Override"; ObjectID = "nC0-X3-oFJ"; */ +"nC0-X3-oFJ.text" = "Overstyr"; /* Class = "WKInterfaceButton"; title = "Bolus"; ObjectID = "Qsq-p5-1J0"; */ "Qsq-p5-1J0.title" = "Bolus"; @@ -64,9 +91,30 @@ /* Class = "WKInterfaceLabel"; text = "Bolus"; ObjectID = "smL-Rc-IZh"; */ "smL-Rc-IZh.text" = "Bolus"; +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "T4U-wP-dSW"; */ +"T4U-wP-dSW.text" = "Etikett"; + /* Class = "WKInterfaceLabel"; text = "10:09 AM"; ObjectID = "Ury-of-vQg"; */ "Ury-of-vQg.text" = "10:09 AM"; +/* Class = "WKInterfaceLabel"; text = "🏃‍♀️"; ObjectID = "UVY-pa-SUL"; */ +"UVY-pa-SUL.text" = "🏃‍♀️"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "v5b-sO-bb8"; */ +"v5b-sO-bb8.title" = "Loop"; + +/* Class = "WKInterfaceMenuItem"; title = "1 hour"; ObjectID = "vL1-NA-WZ1"; */ +"vL1-NA-WZ1.title" = "1 time"; + /* Class = "WKInterfaceLabel"; text = ""; ObjectID = "XkS-y5-khE"; */ "XkS-y5-khE.text" = ""; +/* Class = "WKInterfaceLabel"; text = "ACTIVE CARBS"; ObjectID = "ycL-5X-a05"; */ +"ycL-5X-a05.text" = "AKTIVE KARBOHYDRATER"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "yl8-ZP-c3l"; */ +"yl8-ZP-c3l.text" = "---"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "zO8-x6-bZd"; */ +"zO8-x6-bZd.text" = "Etikett"; + diff --git a/WatchApp/nl.lproj/InfoPlist.strings b/WatchApp/nl.lproj/InfoPlist.strings deleted file mode 100644 index 9250064a26..0000000000 --- a/WatchApp/nl.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* (No Comment) */ -"CFBundleDisplayName" = "Loop"; - -/* (No Comment) */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/WatchApp/nl.lproj/Interface.strings b/WatchApp/nl.lproj/Interface.strings index 5937e17ec6..6726c43fa7 100644 --- a/WatchApp/nl.lproj/Interface.strings +++ b/WatchApp/nl.lproj/Interface.strings @@ -4,9 +4,15 @@ /* Class = "WKInterfaceButton"; title = "Add Carbs"; ObjectID = "b6f-3I-jki"; */ "b6f-3I-jki.title" = "Koolhydraten toevoegen"; +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "CsQ-fc-KLC"; */ +"CsQ-fc-KLC.text" = "—"; + /* Class = "WKInterfaceLabel"; text = "15"; ObjectID = "CWt-7U-cnK"; */ "CWt-7U-cnK.text" = "15"; +/* Class = "WKInterfaceLabel"; text = "TOTAL CARBS"; ObjectID = "dea-qG-va8"; */ +"dea-qG-va8.text" = "Totaal koolhydraten"; + /* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "Dh9-HV-fXy"; */ "Dh9-HV-fXy.accessibilityLabel" = "Aftrekken"; @@ -16,6 +22,12 @@ /* Class = "WKInterfaceButton"; title = "🍕"; ObjectID = "dPF-QZ-sh6"; */ "dPF-QZ-sh6.title" = "🍕"; +/* Class = "WKInterfaceMenuItem"; title = "2 hours"; ObjectID = "dPh-7b-Tfv"; */ +"dPh-7b-Tfv.title" = "2 uur"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "Dt1-kz-jMZ"; */ +"Dt1-kz-jMZ.text" = "---"; + /* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "DZc-Gn-RLu"; */ "DZc-Gn-RLu.accessibilityLabel" = "Toevoegen"; @@ -31,6 +43,9 @@ /* Class = "WKInterfaceLabel"; text = "Pre-Meal"; ObjectID = "f5G-bS-9pd"; */ "f5G-bS-9pd.text" = "Voor de maaltijd"; +/* Class = "WKInterfaceMenuItem"; title = "3 hours"; ObjectID = "fR1-7h-SNe"; */ +"fR1-7h-SNe.title" = "3 uur"; + /* Class = "WKInterfaceButton"; title = "🍭"; ObjectID = "gAn-qe-OvX"; */ "gAn-qe-OvX.title" = "🍭"; @@ -43,14 +58,26 @@ /* Class = "WKInterfaceLabel"; text = "Carbs"; ObjectID = "hln-CI-MRP"; */ "hln-CI-MRP.text" = "Koolhydraten"; +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "IRi-4t-ESO"; */ +"IRi-4t-ESO.text" = "—"; + /* Class = "WKInterfaceLabel"; text = "Bolus Failed"; ObjectID = "jj3-Gq-HBy"; */ "jj3-Gq-HBy.text" = "Bolus mislukt"; +/* Class = "WKInterfaceLabel"; text = "Running"; ObjectID = "JXa-s1-PJx"; */ +"JXa-s1-PJx.text" = "Actief"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "Mhe-aR-kQQ"; */ +"Mhe-aR-kQQ.text" = "—"; + /* Class = "WKInterfaceLabel"; text = "0.000"; ObjectID = "mpK-zY-UvA"; */ "mpK-zY-UvA.text" = "0.000"; -/* Class = "WKInterfaceLabel"; text = "Workout"; ObjectID = "nC0-X3-oFJ"; */ -"nC0-X3-oFJ.text" = "Training"; +/* Class = "WKInterfaceLabel"; text = "TITLE"; ObjectID = "MZU-QV-PtZ"; */ +"MZU-QV-PtZ.text" = "TITEL"; + +/* Class = "WKInterfaceLabel"; text = "Override"; ObjectID = "nC0-X3-oFJ"; */ +"nC0-X3-oFJ.text" = "Aangepast programma"; /* Class = "WKInterfaceButton"; title = "Bolus"; ObjectID = "Qsq-p5-1J0"; */ "Qsq-p5-1J0.title" = "Bolus"; @@ -64,9 +91,30 @@ /* Class = "WKInterfaceLabel"; text = "Bolus"; ObjectID = "smL-Rc-IZh"; */ "smL-Rc-IZh.text" = "Bolus"; +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "T4U-wP-dSW"; */ +"T4U-wP-dSW.text" = "Label"; + /* Class = "WKInterfaceLabel"; text = "10:09 AM"; ObjectID = "Ury-of-vQg"; */ "Ury-of-vQg.text" = "10:09 AM"; +/* Class = "WKInterfaceLabel"; text = "🏃‍♀️"; ObjectID = "UVY-pa-SUL"; */ +"UVY-pa-SUL.text" = "🏃‍♀️"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "v5b-sO-bb8"; */ +"v5b-sO-bb8.title" = "Loop"; + +/* Class = "WKInterfaceMenuItem"; title = "1 hour"; ObjectID = "vL1-NA-WZ1"; */ +"vL1-NA-WZ1.title" = "1 uur"; + /* Class = "WKInterfaceLabel"; text = ""; ObjectID = "XkS-y5-khE"; */ "XkS-y5-khE.text" = ""; +/* Class = "WKInterfaceLabel"; text = "ACTIVE CARBS"; ObjectID = "ycL-5X-a05"; */ +"ycL-5X-a05.text" = "Actieve koolhydraten"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "yl8-ZP-c3l"; */ +"yl8-ZP-c3l.text" = "---"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "zO8-x6-bZd"; */ +"zO8-x6-bZd.text" = "Label"; + diff --git a/WatchApp/pl.lproj/InfoPlist.strings b/WatchApp/pl.lproj/InfoPlist.strings deleted file mode 100644 index 9250064a26..0000000000 --- a/WatchApp/pl.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* (No Comment) */ -"CFBundleDisplayName" = "Loop"; - -/* (No Comment) */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/WatchApp/pl.lproj/Interface.strings b/WatchApp/pl.lproj/Interface.strings index 20e326240f..87f8350f03 100644 --- a/WatchApp/pl.lproj/Interface.strings +++ b/WatchApp/pl.lproj/Interface.strings @@ -5,6 +5,9 @@ /* Class = "WKInterfaceLabel"; text = "15"; ObjectID = "CWt-7U-cnK"; */ "CWt-7U-cnK.text" = "15"; +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "CsQ-fc-KLC"; */ +"CsQ-fc-KLC.text" = "—"; + /* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "DZc-Gn-RLu"; */ "DZc-Gn-RLu.accessibilityLabel" = "Dodaj"; @@ -17,8 +20,29 @@ /* Class = "WKInterfaceButton"; title = "−"; ObjectID = "Dh9-HV-fXy"; */ "Dh9-HV-fXy.title" = "−"; +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "Dt1-kz-jMZ"; */ +"Dt1-kz-jMZ.text" = "---"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "IRi-4t-ESO"; */ +"IRi-4t-ESO.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "Running"; ObjectID = "JXa-s1-PJx"; */ +"JXa-s1-PJx.text" = "Pracuje"; + +/* Class = "WKInterfaceLabel"; text = "TITLE"; ObjectID = "MZU-QV-PtZ"; */ +"MZU-QV-PtZ.text" = "TYTUŁ"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "Mhe-aR-kQQ"; */ +"Mhe-aR-kQQ.text" = "—"; + /* Class = "WKInterfaceButton"; title = "Bolus"; ObjectID = "Qsq-p5-1J0"; */ -"Qsq-p5-1J0.title" = "Bolus"; +"Qsq-p5-1J0.title" = "Bolusa"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "T4U-wP-dSW"; */ +"T4U-wP-dSW.text" = "Etykieta"; + +/* Class = "WKInterfaceLabel"; text = "🏃‍♀️"; ObjectID = "UVY-pa-SUL"; */ +"UVY-pa-SUL.text" = "🏃‍♀️"; /* Class = "WKInterfaceLabel"; text = "10:09 AM"; ObjectID = "Ury-of-vQg"; */ "Ury-of-vQg.text" = "10:09 AM"; @@ -32,6 +56,12 @@ /* Class = "WKInterfaceButton"; title = "🍕"; ObjectID = "dPF-QZ-sh6"; */ "dPF-QZ-sh6.title" = "🍕"; +/* Class = "WKInterfaceMenuItem"; title = "2 hours"; ObjectID = "dPh-7b-Tfv"; */ +"dPh-7b-Tfv.title" = "2 godziny"; + +/* Class = "WKInterfaceLabel"; text = "TOTAL CARBS"; ObjectID = "dea-qG-va8"; */ +"dea-qG-va8.text" = "WĘGLOWODANY OGÓŁEM"; + /* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "eu3-pj-GH3"; */ "eu3-pj-GH3.accessibilityLabel" = "Dodaj"; @@ -41,6 +71,9 @@ /* Class = "WKInterfaceLabel"; text = "Pre-Meal"; ObjectID = "f5G-bS-9pd"; */ "f5G-bS-9pd.text" = "Przed posiłkiem"; +/* Class = "WKInterfaceMenuItem"; title = "3 hours"; ObjectID = "fR1-7h-SNe"; */ +"fR1-7h-SNe.title" = "3 godziny"; + /* Class = "WKInterfaceButton"; title = "🍭"; ObjectID = "gAn-qe-OvX"; */ "gAn-qe-OvX.title" = "🍭"; @@ -54,13 +87,13 @@ "hln-CI-MRP.text" = "Węglowodany"; /* Class = "WKInterfaceLabel"; text = "Bolus Failed"; ObjectID = "jj3-Gq-HBy"; */ -"jj3-Gq-HBy.text" = "Bolus nie podany"; +"jj3-Gq-HBy.text" = "Bolusa nie podany"; /* Class = "WKInterfaceLabel"; text = "0.000"; ObjectID = "mpK-zY-UvA"; */ "mpK-zY-UvA.text" = "0.000"; -/* Class = "WKInterfaceLabel"; text = "Workout"; ObjectID = "nC0-X3-oFJ"; */ -"nC0-X3-oFJ.text" = "Wysiłek fizyczny"; +/* Class = "WKInterfaceLabel"; text = "Override"; ObjectID = "nC0-X3-oFJ"; */ +"nC0-X3-oFJ.text" = "Pominięcie"; /* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "rNf-Mh-tID"; */ "rNf-Mh-tID.title" = "Loop"; @@ -69,4 +102,19 @@ "rV7-d9-n6u.text" = "JEDNOSTKI"; /* Class = "WKInterfaceLabel"; text = "Bolus"; ObjectID = "smL-Rc-IZh"; */ -"smL-Rc-IZh.text" = "Bolus"; +"smL-Rc-IZh.text" = "Bolusa"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "v5b-sO-bb8"; */ +"v5b-sO-bb8.title" = "Loop"; + +/* Class = "WKInterfaceMenuItem"; title = "1 hour"; ObjectID = "vL1-NA-WZ1"; */ +"vL1-NA-WZ1.title" = "1 godzina"; + +/* Class = "WKInterfaceLabel"; text = "ACTIVE CARBS"; ObjectID = "ycL-5X-a05"; */ +"ycL-5X-a05.text" = "AKTYWNE WĘGLOWODANY"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "yl8-ZP-c3l"; */ +"yl8-ZP-c3l.text" = "---"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "zO8-x6-bZd"; */ +"zO8-x6-bZd.text" = "Etykieta"; diff --git a/WatchApp/pt-BR.lproj/Interface.strings b/WatchApp/pt-BR.lproj/Interface.strings new file mode 100644 index 0000000000..24fdd6bff4 --- /dev/null +++ b/WatchApp/pt-BR.lproj/Interface.strings @@ -0,0 +1,120 @@ +/* Class = "WKInterfaceButton"; title = "🌮"; ObjectID = "0fo-Z3-hTi"; */ +"0fo-Z3-hTi.title" = "🌮"; + +/* Class = "WKInterfaceButton"; title = "Add Carbs"; ObjectID = "b6f-3I-jki"; */ +"b6f-3I-jki.title" = "Adicionar Carbs"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "CsQ-fc-KLC"; */ +"CsQ-fc-KLC.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "15"; ObjectID = "CWt-7U-cnK"; */ +"CWt-7U-cnK.text" = "15"; + +/* Class = "WKInterfaceLabel"; text = "TOTAL CARBS"; ObjectID = "dea-qG-va8"; */ +"dea-qG-va8.text" = "CARBS TOTAL"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "Dh9-HV-fXy"; */ +"Dh9-HV-fXy.accessibilityLabel" = "Subtrair"; + +/* Class = "WKInterfaceButton"; title = "−"; ObjectID = "Dh9-HV-fXy"; */ +"Dh9-HV-fXy.title" = "−"; + +/* Class = "WKInterfaceButton"; title = "🍕"; ObjectID = "dPF-QZ-sh6"; */ +"dPF-QZ-sh6.title" = "🍕"; + +/* Class = "WKInterfaceMenuItem"; title = "2 hours"; ObjectID = "dPh-7b-Tfv"; */ +"dPh-7b-Tfv.title" = "2 horas"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "Dt1-kz-jMZ"; */ +"Dt1-kz-jMZ.text" = "---"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "DZc-Gn-RLu"; */ +"DZc-Gn-RLu.accessibilityLabel" = "Adicionar"; + +/* Class = "WKInterfaceButton"; title = "+"; ObjectID = "DZc-Gn-RLu"; */ +"DZc-Gn-RLu.title" = "+"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "eu3-pj-GH3"; */ +"eu3-pj-GH3.accessibilityLabel" = "Adicionar"; + +/* Class = "WKInterfaceButton"; title = "+"; ObjectID = "eu3-pj-GH3"; */ +"eu3-pj-GH3.title" = "+"; + +/* Class = "WKInterfaceLabel"; text = "Pre-Meal"; ObjectID = "f5G-bS-9pd"; */ +"f5G-bS-9pd.text" = "Pré-Refeição"; + +/* Class = "WKInterfaceMenuItem"; title = "3 hours"; ObjectID = "fR1-7h-SNe"; */ +"fR1-7h-SNe.title" = "3 horas"; + +/* Class = "WKInterfaceButton"; title = "🍭"; ObjectID = "gAn-qe-OvX"; */ +"gAn-qe-OvX.title" = "🍭"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "hjF-xr-cwO"; */ +"hjF-xr-cwO.accessibilityLabel" = "Subtrair"; + +/* Class = "WKInterfaceButton"; title = "−"; ObjectID = "hjF-xr-cwO"; */ +"hjF-xr-cwO.title" = "−"; + +/* Class = "WKInterfaceLabel"; text = "Carbs"; ObjectID = "hln-CI-MRP"; */ +"hln-CI-MRP.text" = "Carbs"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "IRi-4t-ESO"; */ +"IRi-4t-ESO.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "Bolus Failed"; ObjectID = "jj3-Gq-HBy"; */ +"jj3-Gq-HBy.text" = "Bolus Falhou"; + +/* Class = "WKInterfaceLabel"; text = "Running"; ObjectID = "JXa-s1-PJx"; */ +"JXa-s1-PJx.text" = "Executando"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "Mhe-aR-kQQ"; */ +"Mhe-aR-kQQ.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "0.000"; ObjectID = "mpK-zY-UvA"; */ +"mpK-zY-UvA.text" = "0.000"; + +/* Class = "WKInterfaceLabel"; text = "TITLE"; ObjectID = "MZU-QV-PtZ"; */ +"MZU-QV-PtZ.text" = "TÍTULO"; + +/* Class = "WKInterfaceLabel"; text = "Override"; ObjectID = "nC0-X3-oFJ"; */ +"nC0-X3-oFJ.text" = "Sobrepor"; + +/* Class = "WKInterfaceButton"; title = "Bolus"; ObjectID = "Qsq-p5-1J0"; */ +"Qsq-p5-1J0.title" = "Bolus"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "rNf-Mh-tID"; */ +"rNf-Mh-tID.title" = "Loop"; + +/* Class = "WKInterfaceLabel"; text = "UNITS"; ObjectID = "rV7-d9-n6u"; */ +"rV7-d9-n6u.text" = "UNIDADES"; + +/* Class = "WKInterfaceLabel"; text = "Bolus"; ObjectID = "smL-Rc-IZh"; */ +"smL-Rc-IZh.text" = "Bolus"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "T4U-wP-dSW"; */ +"T4U-wP-dSW.text" = "Rótulo"; + +/* Class = "WKInterfaceLabel"; text = "10:09 AM"; ObjectID = "Ury-of-vQg"; */ +"Ury-of-vQg.text" = "10:09 AM"; + +/* Class = "WKInterfaceLabel"; text = "🏃‍♀️"; ObjectID = "UVY-pa-SUL"; */ +"UVY-pa-SUL.text" = "🏃‍♀️"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "v5b-sO-bb8"; */ +"v5b-sO-bb8.title" = "Loop"; + +/* Class = "WKInterfaceMenuItem"; title = "1 hour"; ObjectID = "vL1-NA-WZ1"; */ +"vL1-NA-WZ1.title" = "1 hora"; + +/* Class = "WKInterfaceLabel"; text = ""; ObjectID = "XkS-y5-khE"; */ +"XkS-y5-khE.text" = ""; + +/* Class = "WKInterfaceLabel"; text = "ACTIVE CARBS"; ObjectID = "ycL-5X-a05"; */ +"ycL-5X-a05.text" = "CARBS ATIVOS"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "yl8-ZP-c3l"; */ +"yl8-ZP-c3l.text" = "---"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "zO8-x6-bZd"; */ +"zO8-x6-bZd.text" = "Rótulo"; + diff --git a/WatchApp/ro.lproj/Interface.strings b/WatchApp/ro.lproj/Interface.strings new file mode 100644 index 0000000000..278e385ab9 --- /dev/null +++ b/WatchApp/ro.lproj/Interface.strings @@ -0,0 +1,120 @@ +/* Class = "WKInterfaceButton"; title = "🌮"; ObjectID = "0fo-Z3-hTi"; */ +"0fo-Z3-hTi.title" = "🌮"; + +/* Class = "WKInterfaceButton"; title = "Add Carbs"; ObjectID = "b6f-3I-jki"; */ +"b6f-3I-jki.title" = "Adaugă carbohidrați"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "CsQ-fc-KLC"; */ +"CsQ-fc-KLC.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "15"; ObjectID = "CWt-7U-cnK"; */ +"CWt-7U-cnK.text" = "15"; + +/* Class = "WKInterfaceLabel"; text = "TOTAL CARBS"; ObjectID = "dea-qG-va8"; */ +"dea-qG-va8.text" = "CARBOHIDRAȚI TOTALI"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "Dh9-HV-fXy"; */ +"Dh9-HV-fXy.accessibilityLabel" = "Scade"; + +/* Class = "WKInterfaceButton"; title = "−"; ObjectID = "Dh9-HV-fXy"; */ +"Dh9-HV-fXy.title" = "−"; + +/* Class = "WKInterfaceButton"; title = "🍕"; ObjectID = "dPF-QZ-sh6"; */ +"dPF-QZ-sh6.title" = "🍕"; + +/* Class = "WKInterfaceMenuItem"; title = "2 hours"; ObjectID = "dPh-7b-Tfv"; */ +"dPh-7b-Tfv.title" = "2 ore"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "Dt1-kz-jMZ"; */ +"Dt1-kz-jMZ.text" = "---"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "DZc-Gn-RLu"; */ +"DZc-Gn-RLu.accessibilityLabel" = "Adaugă"; + +/* Class = "WKInterfaceButton"; title = "+"; ObjectID = "DZc-Gn-RLu"; */ +"DZc-Gn-RLu.title" = "+"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "eu3-pj-GH3"; */ +"eu3-pj-GH3.accessibilityLabel" = "Adaugă"; + +/* Class = "WKInterfaceButton"; title = "+"; ObjectID = "eu3-pj-GH3"; */ +"eu3-pj-GH3.title" = "+"; + +/* Class = "WKInterfaceLabel"; text = "Pre-Meal"; ObjectID = "f5G-bS-9pd"; */ +"f5G-bS-9pd.text" = "Preprandial"; + +/* Class = "WKInterfaceMenuItem"; title = "3 hours"; ObjectID = "fR1-7h-SNe"; */ +"fR1-7h-SNe.title" = "3 ore"; + +/* Class = "WKInterfaceButton"; title = "🍭"; ObjectID = "gAn-qe-OvX"; */ +"gAn-qe-OvX.title" = "🍭"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "hjF-xr-cwO"; */ +"hjF-xr-cwO.accessibilityLabel" = "Scade"; + +/* Class = "WKInterfaceButton"; title = "−"; ObjectID = "hjF-xr-cwO"; */ +"hjF-xr-cwO.title" = "−"; + +/* Class = "WKInterfaceLabel"; text = "Carbs"; ObjectID = "hln-CI-MRP"; */ +"hln-CI-MRP.text" = "Carbohidrați"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "IRi-4t-ESO"; */ +"IRi-4t-ESO.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "Bolus Failed"; ObjectID = "jj3-Gq-HBy"; */ +"jj3-Gq-HBy.text" = "Bolus eșuat"; + +/* Class = "WKInterfaceLabel"; text = "Running"; ObjectID = "JXa-s1-PJx"; */ +"JXa-s1-PJx.text" = "Rulează"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "Mhe-aR-kQQ"; */ +"Mhe-aR-kQQ.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "0.000"; ObjectID = "mpK-zY-UvA"; */ +"mpK-zY-UvA.text" = "0.000"; + +/* Class = "WKInterfaceLabel"; text = "TITLE"; ObjectID = "MZU-QV-PtZ"; */ +"MZU-QV-PtZ.text" = "TITLU"; + +/* Class = "WKInterfaceLabel"; text = "Override"; ObjectID = "nC0-X3-oFJ"; */ +"nC0-X3-oFJ.text" = "Înlocuire"; + +/* Class = "WKInterfaceButton"; title = "Bolus"; ObjectID = "Qsq-p5-1J0"; */ +"Qsq-p5-1J0.title" = "Bolus"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "rNf-Mh-tID"; */ +"rNf-Mh-tID.title" = "Loop"; + +/* Class = "WKInterfaceLabel"; text = "UNITS"; ObjectID = "rV7-d9-n6u"; */ +"rV7-d9-n6u.text" = "UNITĂȚI"; + +/* Class = "WKInterfaceLabel"; text = "Bolus"; ObjectID = "smL-Rc-IZh"; */ +"smL-Rc-IZh.text" = "Bolus"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "T4U-wP-dSW"; */ +"T4U-wP-dSW.text" = "Etichetă"; + +/* Class = "WKInterfaceLabel"; text = "10:09 AM"; ObjectID = "Ury-of-vQg"; */ +"Ury-of-vQg.text" = "10:09 AM"; + +/* Class = "WKInterfaceLabel"; text = "🏃‍♀️"; ObjectID = "UVY-pa-SUL"; */ +"UVY-pa-SUL.text" = "🏃‍♀️"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "v5b-sO-bb8"; */ +"v5b-sO-bb8.title" = "Loop"; + +/* Class = "WKInterfaceMenuItem"; title = "1 hour"; ObjectID = "vL1-NA-WZ1"; */ +"vL1-NA-WZ1.title" = "1 oră"; + +/* Class = "WKInterfaceLabel"; text = ""; ObjectID = "XkS-y5-khE"; */ +"XkS-y5-khE.text" = ""; + +/* Class = "WKInterfaceLabel"; text = "ACTIVE CARBS"; ObjectID = "ycL-5X-a05"; */ +"ycL-5X-a05.text" = "CARBOHIDRAȚI ACTIVI"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "yl8-ZP-c3l"; */ +"yl8-ZP-c3l.text" = "---"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "zO8-x6-bZd"; */ +"zO8-x6-bZd.text" = "Etichetă"; + diff --git a/WatchApp/ru.lproj/InfoPlist.strings b/WatchApp/ru.lproj/InfoPlist.strings deleted file mode 100644 index 9250064a26..0000000000 --- a/WatchApp/ru.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* (No Comment) */ -"CFBundleDisplayName" = "Loop"; - -/* (No Comment) */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/WatchApp/ru.lproj/Interface.strings b/WatchApp/ru.lproj/Interface.strings index 684ab55934..cbccbed6d0 100644 --- a/WatchApp/ru.lproj/Interface.strings +++ b/WatchApp/ru.lproj/Interface.strings @@ -4,9 +4,15 @@ /* Class = "WKInterfaceButton"; title = "Add Carbs"; ObjectID = "b6f-3I-jki"; */ "b6f-3I-jki.title" = "Добавить углеводов"; +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "CsQ-fc-KLC"; */ +"CsQ-fc-KLC.text" = "—"; + /* Class = "WKInterfaceLabel"; text = "15"; ObjectID = "CWt-7U-cnK"; */ "CWt-7U-cnK.text" = "15"; +/* Class = "WKInterfaceLabel"; text = "TOTAL CARBS"; ObjectID = "dea-qG-va8"; */ +"dea-qG-va8.text" = "ВСЕГО УГЛ"; + /* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "Dh9-HV-fXy"; */ "Dh9-HV-fXy.accessibilityLabel" = "вычитать"; @@ -16,6 +22,12 @@ /* Class = "WKInterfaceButton"; title = "🍕"; ObjectID = "dPF-QZ-sh6"; */ "dPF-QZ-sh6.title" = "🍕"; +/* Class = "WKInterfaceMenuItem"; title = "2 hours"; ObjectID = "dPh-7b-Tfv"; */ +"dPh-7b-Tfv.title" = "2 часа"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "Dt1-kz-jMZ"; */ +"Dt1-kz-jMZ.text" = "---"; + /* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "DZc-Gn-RLu"; */ "DZc-Gn-RLu.accessibilityLabel" = "Добавить"; @@ -23,7 +35,7 @@ "DZc-Gn-RLu.title" = "+"; /* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "eu3-pj-GH3"; */ -"eu3-pj-GH3.accessibilityLabel" = "Добавить\n "; +"eu3-pj-GH3.accessibilityLabel" = "Добавить"; /* Class = "WKInterfaceButton"; title = "+"; ObjectID = "eu3-pj-GH3"; */ "eu3-pj-GH3.title" = "+"; @@ -31,6 +43,9 @@ /* Class = "WKInterfaceLabel"; text = "Pre-Meal"; ObjectID = "f5G-bS-9pd"; */ "f5G-bS-9pd.text" = "До еды"; +/* Class = "WKInterfaceMenuItem"; title = "3 hours"; ObjectID = "fR1-7h-SNe"; */ +"fR1-7h-SNe.title" = "3 часа"; + /* Class = "WKInterfaceButton"; title = "🍭"; ObjectID = "gAn-qe-OvX"; */ "gAn-qe-OvX.title" = "🍭"; @@ -43,14 +58,26 @@ /* Class = "WKInterfaceLabel"; text = "Carbs"; ObjectID = "hln-CI-MRP"; */ "hln-CI-MRP.text" = "Углеводы"; +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "IRi-4t-ESO"; */ +"IRi-4t-ESO.text" = "—"; + /* Class = "WKInterfaceLabel"; text = "Bolus Failed"; ObjectID = "jj3-Gq-HBy"; */ -"jj3-Gq-HBy.text" = "Болюс не состоялся "; +"jj3-Gq-HBy.text" = "Болюс не состоялся"; + +/* Class = "WKInterfaceLabel"; text = "Running"; ObjectID = "JXa-s1-PJx"; */ +"JXa-s1-PJx.text" = "Бег"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "Mhe-aR-kQQ"; */ +"Mhe-aR-kQQ.text" = "—"; /* Class = "WKInterfaceLabel"; text = "0.000"; ObjectID = "mpK-zY-UvA"; */ "mpK-zY-UvA.text" = "0.000"; -/* Class = "WKInterfaceLabel"; text = "Workout"; ObjectID = "nC0-X3-oFJ"; */ -"nC0-X3-oFJ.text" = "Физическая нагрузка"; +/* Class = "WKInterfaceLabel"; text = "TITLE"; ObjectID = "MZU-QV-PtZ"; */ +"MZU-QV-PtZ.text" = "НАЗВАНИЕ"; + +/* Class = "WKInterfaceLabel"; text = "Override"; ObjectID = "nC0-X3-oFJ"; */ +"nC0-X3-oFJ.text" = "Перезаписать"; /* Class = "WKInterfaceButton"; title = "Bolus"; ObjectID = "Qsq-p5-1J0"; */ "Qsq-p5-1J0.title" = "Болюс"; @@ -64,9 +91,30 @@ /* Class = "WKInterfaceLabel"; text = "Bolus"; ObjectID = "smL-Rc-IZh"; */ "smL-Rc-IZh.text" = "Болюс"; +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "T4U-wP-dSW"; */ +"T4U-wP-dSW.text" = "Ярлык"; + /* Class = "WKInterfaceLabel"; text = "10:09 AM"; ObjectID = "Ury-of-vQg"; */ "Ury-of-vQg.text" = "10:09 AM"; +/* Class = "WKInterfaceLabel"; text = "🏃‍♀️"; ObjectID = "UVY-pa-SUL"; */ +"UVY-pa-SUL.text" = "🏃‍♀️"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "v5b-sO-bb8"; */ +"v5b-sO-bb8.title" = "Loop"; + +/* Class = "WKInterfaceMenuItem"; title = "1 hour"; ObjectID = "vL1-NA-WZ1"; */ +"vL1-NA-WZ1.title" = "1 час"; + /* Class = "WKInterfaceLabel"; text = ""; ObjectID = "XkS-y5-khE"; */ "XkS-y5-khE.text" = ""; +/* Class = "WKInterfaceLabel"; text = "ACTIVE CARBS"; ObjectID = "ycL-5X-a05"; */ +"ycL-5X-a05.text" = "АКТИВНЫЕ УГЛ"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "yl8-ZP-c3l"; */ +"yl8-ZP-c3l.text" = "---"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "zO8-x6-bZd"; */ +"zO8-x6-bZd.text" = "Ярлык"; + diff --git a/WatchApp/sv.lproj/Interface.strings b/WatchApp/sv.lproj/Interface.strings new file mode 100644 index 0000000000..8e87c7a290 --- /dev/null +++ b/WatchApp/sv.lproj/Interface.strings @@ -0,0 +1,120 @@ +/* Class = "WKInterfaceButton"; title = "🌮"; ObjectID = "0fo-Z3-hTi"; */ +"0fo-Z3-hTi.title" = "🌮"; + +/* Class = "WKInterfaceButton"; title = "Add Carbs"; ObjectID = "b6f-3I-jki"; */ +"b6f-3I-jki.title" = "Kolhydrater"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "CsQ-fc-KLC"; */ +"CsQ-fc-KLC.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "15"; ObjectID = "CWt-7U-cnK"; */ +"CWt-7U-cnK.text" = "15"; + +/* Class = "WKInterfaceLabel"; text = "TOTAL CARBS"; ObjectID = "dea-qG-va8"; */ +"dea-qG-va8.text" = "Kolh totalt"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "Dh9-HV-fXy"; */ +"Dh9-HV-fXy.accessibilityLabel" = "Subtrahera"; + +/* Class = "WKInterfaceButton"; title = "−"; ObjectID = "Dh9-HV-fXy"; */ +"Dh9-HV-fXy.title" = "−"; + +/* Class = "WKInterfaceButton"; title = "🍕"; ObjectID = "dPF-QZ-sh6"; */ +"dPF-QZ-sh6.title" = "🍕"; + +/* Class = "WKInterfaceMenuItem"; title = "2 hours"; ObjectID = "dPh-7b-Tfv"; */ +"dPh-7b-Tfv.title" = "2 timmar"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "Dt1-kz-jMZ"; */ +"Dt1-kz-jMZ.text" = "---"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "DZc-Gn-RLu"; */ +"DZc-Gn-RLu.accessibilityLabel" = "Lägg till"; + +/* Class = "WKInterfaceButton"; title = "+"; ObjectID = "DZc-Gn-RLu"; */ +"DZc-Gn-RLu.title" = "+"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "eu3-pj-GH3"; */ +"eu3-pj-GH3.accessibilityLabel" = "Lägg till"; + +/* Class = "WKInterfaceButton"; title = "+"; ObjectID = "eu3-pj-GH3"; */ +"eu3-pj-GH3.title" = "+"; + +/* Class = "WKInterfaceLabel"; text = "Pre-Meal"; ObjectID = "f5G-bS-9pd"; */ +"f5G-bS-9pd.text" = "Före måltid"; + +/* Class = "WKInterfaceMenuItem"; title = "3 hours"; ObjectID = "fR1-7h-SNe"; */ +"fR1-7h-SNe.title" = "3 timmar"; + +/* Class = "WKInterfaceButton"; title = "🍭"; ObjectID = "gAn-qe-OvX"; */ +"gAn-qe-OvX.title" = "🍭"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "hjF-xr-cwO"; */ +"hjF-xr-cwO.accessibilityLabel" = "Subtrahera"; + +/* Class = "WKInterfaceButton"; title = "−"; ObjectID = "hjF-xr-cwO"; */ +"hjF-xr-cwO.title" = "−"; + +/* Class = "WKInterfaceLabel"; text = "Carbs"; ObjectID = "hln-CI-MRP"; */ +"hln-CI-MRP.text" = "Kolh"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "IRi-4t-ESO"; */ +"IRi-4t-ESO.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "Bolus Failed"; ObjectID = "jj3-Gq-HBy"; */ +"jj3-Gq-HBy.text" = "Bolus misslyckades"; + +/* Class = "WKInterfaceLabel"; text = "Running"; ObjectID = "JXa-s1-PJx"; */ +"JXa-s1-PJx.text" = "Löpning"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "Mhe-aR-kQQ"; */ +"Mhe-aR-kQQ.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "0.000"; ObjectID = "mpK-zY-UvA"; */ +"mpK-zY-UvA.text" = "0.000"; + +/* Class = "WKInterfaceLabel"; text = "TITLE"; ObjectID = "MZU-QV-PtZ"; */ +"MZU-QV-PtZ.text" = "TITLE"; + +/* Class = "WKInterfaceLabel"; text = "Override"; ObjectID = "nC0-X3-oFJ"; */ +"nC0-X3-oFJ.text" = "Override"; + +/* Class = "WKInterfaceButton"; title = "Bolus"; ObjectID = "Qsq-p5-1J0"; */ +"Qsq-p5-1J0.title" = "Bolus"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "rNf-Mh-tID"; */ +"rNf-Mh-tID.title" = "Loop"; + +/* Class = "WKInterfaceLabel"; text = "UNITS"; ObjectID = "rV7-d9-n6u"; */ +"rV7-d9-n6u.text" = "ENHETER"; + +/* Class = "WKInterfaceLabel"; text = "Bolus"; ObjectID = "smL-Rc-IZh"; */ +"smL-Rc-IZh.text" = "Bolus"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "T4U-wP-dSW"; */ +"T4U-wP-dSW.text" = "Label"; + +/* Class = "WKInterfaceLabel"; text = "10:09 AM"; ObjectID = "Ury-of-vQg"; */ +"Ury-of-vQg.text" = "10:09 AM"; + +/* Class = "WKInterfaceLabel"; text = "🏃‍♀️"; ObjectID = "UVY-pa-SUL"; */ +"UVY-pa-SUL.text" = "🏃‍♀️"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "v5b-sO-bb8"; */ +"v5b-sO-bb8.title" = "Loop"; + +/* Class = "WKInterfaceMenuItem"; title = "1 hour"; ObjectID = "vL1-NA-WZ1"; */ +"vL1-NA-WZ1.title" = "1 timme"; + +/* Class = "WKInterfaceLabel"; text = ""; ObjectID = "XkS-y5-khE"; */ +"XkS-y5-khE.text" = ""; + +/* Class = "WKInterfaceLabel"; text = "ACTIVE CARBS"; ObjectID = "ycL-5X-a05"; */ +"ycL-5X-a05.text" = "AKTIVA KOLH"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "yl8-ZP-c3l"; */ +"yl8-ZP-c3l.text" = "---"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "zO8-x6-bZd"; */ +"zO8-x6-bZd.text" = "Label"; + diff --git a/WatchApp/vi.lproj/Interface.strings b/WatchApp/vi.lproj/Interface.strings new file mode 100644 index 0000000000..fbfe111916 --- /dev/null +++ b/WatchApp/vi.lproj/Interface.strings @@ -0,0 +1,120 @@ +/* Class = "WKInterfaceButton"; title = "🌮"; ObjectID = "0fo-Z3-hTi"; */ +"0fo-Z3-hTi.title" = "🌮"; + +/* Class = "WKInterfaceButton"; title = "Add Carbs"; ObjectID = "b6f-3I-jki"; */ +"b6f-3I-jki.title" = "Khai báo lượng Carbs"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "CsQ-fc-KLC"; */ +"CsQ-fc-KLC.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "15"; ObjectID = "CWt-7U-cnK"; */ +"CWt-7U-cnK.text" = "15"; + +/* Class = "WKInterfaceLabel"; text = "TOTAL CARBS"; ObjectID = "dea-qG-va8"; */ +"dea-qG-va8.text" = "TỔNG CỘNG LƯỢNG CARBS"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "Dh9-HV-fXy"; */ +"Dh9-HV-fXy.accessibilityLabel" = "Trừ đi"; + +/* Class = "WKInterfaceButton"; title = "−"; ObjectID = "Dh9-HV-fXy"; */ +"Dh9-HV-fXy.title" = "−"; + +/* Class = "WKInterfaceButton"; title = "🍕"; ObjectID = "dPF-QZ-sh6"; */ +"dPF-QZ-sh6.title" = "🍕"; + +/* Class = "WKInterfaceMenuItem"; title = "2 hours"; ObjectID = "dPh-7b-Tfv"; */ +"dPh-7b-Tfv.title" = "2 giờ"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "Dt1-kz-jMZ"; */ +"Dt1-kz-jMZ.text" = "---"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "DZc-Gn-RLu"; */ +"DZc-Gn-RLu.accessibilityLabel" = "Thêm vào"; + +/* Class = "WKInterfaceButton"; title = "+"; ObjectID = "DZc-Gn-RLu"; */ +"DZc-Gn-RLu.title" = "+"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "eu3-pj-GH3"; */ +"eu3-pj-GH3.accessibilityLabel" = "Thêm vào"; + +/* Class = "WKInterfaceButton"; title = "+"; ObjectID = "eu3-pj-GH3"; */ +"eu3-pj-GH3.title" = "+"; + +/* Class = "WKInterfaceLabel"; text = "Pre-Meal"; ObjectID = "f5G-bS-9pd"; */ +"f5G-bS-9pd.text" = "Trước bữa ăn"; + +/* Class = "WKInterfaceMenuItem"; title = "3 hours"; ObjectID = "fR1-7h-SNe"; */ +"fR1-7h-SNe.title" = "3 giờ"; + +/* Class = "WKInterfaceButton"; title = "🍭"; ObjectID = "gAn-qe-OvX"; */ +"gAn-qe-OvX.title" = "🍭"; + +/* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "hjF-xr-cwO"; */ +"hjF-xr-cwO.accessibilityLabel" = "Trừ đi"; + +/* Class = "WKInterfaceButton"; title = "−"; ObjectID = "hjF-xr-cwO"; */ +"hjF-xr-cwO.title" = "−"; + +/* Class = "WKInterfaceLabel"; text = "Carbs"; ObjectID = "hln-CI-MRP"; */ +"hln-CI-MRP.text" = "Lượng Carbs"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "IRi-4t-ESO"; */ +"IRi-4t-ESO.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "Bolus Failed"; ObjectID = "jj3-Gq-HBy"; */ +"jj3-Gq-HBy.text" = "Bolus lỗi"; + +/* Class = "WKInterfaceLabel"; text = "Running"; ObjectID = "JXa-s1-PJx"; */ +"JXa-s1-PJx.text" = "Đang chạy"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "Mhe-aR-kQQ"; */ +"Mhe-aR-kQQ.text" = "—"; + +/* Class = "WKInterfaceLabel"; text = "0.000"; ObjectID = "mpK-zY-UvA"; */ +"mpK-zY-UvA.text" = "0.000"; + +/* Class = "WKInterfaceLabel"; text = "TITLE"; ObjectID = "MZU-QV-PtZ"; */ +"MZU-QV-PtZ.text" = "TITLE"; + +/* Class = "WKInterfaceLabel"; text = "Override"; ObjectID = "nC0-X3-oFJ"; */ +"nC0-X3-oFJ.text" = "Chồng lên"; + +/* Class = "WKInterfaceButton"; title = "Bolus"; ObjectID = "Qsq-p5-1J0"; */ +"Qsq-p5-1J0.title" = "Bolus"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "rNf-Mh-tID"; */ +"rNf-Mh-tID.title" = "Loop"; + +/* Class = "WKInterfaceLabel"; text = "UNITS"; ObjectID = "rV7-d9-n6u"; */ +"rV7-d9-n6u.text" = "ĐƠN VỊ"; + +/* Class = "WKInterfaceLabel"; text = "Bolus"; ObjectID = "smL-Rc-IZh"; */ +"smL-Rc-IZh.text" = "Bolus"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "T4U-wP-dSW"; */ +"T4U-wP-dSW.text" = "Label"; + +/* Class = "WKInterfaceLabel"; text = "10:09 AM"; ObjectID = "Ury-of-vQg"; */ +"Ury-of-vQg.text" = "10:09 AM"; + +/* Class = "WKInterfaceLabel"; text = "🏃‍♀️"; ObjectID = "UVY-pa-SUL"; */ +"UVY-pa-SUL.text" = "🏃‍♀️"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "v5b-sO-bb8"; */ +"v5b-sO-bb8.title" = "Loop"; + +/* Class = "WKInterfaceMenuItem"; title = "1 hour"; ObjectID = "vL1-NA-WZ1"; */ +"vL1-NA-WZ1.title" = "1 giờ"; + +/* Class = "WKInterfaceLabel"; text = ""; ObjectID = "XkS-y5-khE"; */ +"XkS-y5-khE.text" = ""; + +/* Class = "WKInterfaceLabel"; text = "ACTIVE CARBS"; ObjectID = "ycL-5X-a05"; */ +"ycL-5X-a05.text" = "LƯỢNG CARBS CÒN HOẠT ĐỘNG"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "yl8-ZP-c3l"; */ +"yl8-ZP-c3l.text" = "---"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "zO8-x6-bZd"; */ +"zO8-x6-bZd.text" = "Label"; + diff --git a/WatchApp/zh-Hans.lproj/InfoPlist.strings b/WatchApp/zh-Hans.lproj/InfoPlist.strings deleted file mode 100644 index 9250064a26..0000000000 --- a/WatchApp/zh-Hans.lproj/InfoPlist.strings +++ /dev/null @@ -1,6 +0,0 @@ -/* (No Comment) */ -"CFBundleDisplayName" = "Loop"; - -/* (No Comment) */ -"CFBundleName" = "$(PRODUCT_NAME)"; - diff --git a/WatchApp/zh-Hans.lproj/Interface.strings b/WatchApp/zh-Hans.lproj/Interface.strings index 3e589f2423..49378f974d 100644 --- a/WatchApp/zh-Hans.lproj/Interface.strings +++ b/WatchApp/zh-Hans.lproj/Interface.strings @@ -4,9 +4,15 @@ /* Class = "WKInterfaceButton"; title = "Add Carbs"; ObjectID = "b6f-3I-jki"; */ "b6f-3I-jki.title" = "添加碳水化合物"; +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "CsQ-fc-KLC"; */ +"CsQ-fc-KLC.text" = "—"; + /* Class = "WKInterfaceLabel"; text = "15"; ObjectID = "CWt-7U-cnK"; */ "CWt-7U-cnK.text" = "15"; +/* Class = "WKInterfaceLabel"; text = "TOTAL CARBS"; ObjectID = "dea-qG-va8"; */ +"dea-qG-va8.text" = "碳水总量"; + /* Class = "WKInterfaceButton"; accessibilityLabel = "Subtract"; ObjectID = "Dh9-HV-fXy"; */ "Dh9-HV-fXy.accessibilityLabel" = "减去"; @@ -16,6 +22,12 @@ /* Class = "WKInterfaceButton"; title = "🍕"; ObjectID = "dPF-QZ-sh6"; */ "dPF-QZ-sh6.title" = "🍕"; +/* Class = "WKInterfaceMenuItem"; title = "2 hours"; ObjectID = "dPh-7b-Tfv"; */ +"dPh-7b-Tfv.title" = "2小时"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "Dt1-kz-jMZ"; */ +"Dt1-kz-jMZ.text" = "---"; + /* Class = "WKInterfaceButton"; accessibilityLabel = "Add"; ObjectID = "DZc-Gn-RLu"; */ "DZc-Gn-RLu.accessibilityLabel" = "添加"; @@ -31,6 +43,9 @@ /* Class = "WKInterfaceLabel"; text = "Pre-Meal"; ObjectID = "f5G-bS-9pd"; */ "f5G-bS-9pd.text" = "餐前模式"; +/* Class = "WKInterfaceMenuItem"; title = "3 hours"; ObjectID = "fR1-7h-SNe"; */ +"fR1-7h-SNe.title" = "3小时"; + /* Class = "WKInterfaceButton"; title = "🍭"; ObjectID = "gAn-qe-OvX"; */ "gAn-qe-OvX.title" = "🍭"; @@ -43,14 +58,26 @@ /* Class = "WKInterfaceLabel"; text = "Carbs"; ObjectID = "hln-CI-MRP"; */ "hln-CI-MRP.text" = "碳水化合物"; +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "IRi-4t-ESO"; */ +"IRi-4t-ESO.text" = "—"; + /* Class = "WKInterfaceLabel"; text = "Bolus Failed"; ObjectID = "jj3-Gq-HBy"; */ "jj3-Gq-HBy.text" = "大剂量输注失败"; +/* Class = "WKInterfaceLabel"; text = "Running"; ObjectID = "JXa-s1-PJx"; */ +"JXa-s1-PJx.text" = "运行中"; + +/* Class = "WKInterfaceLabel"; text = "—"; ObjectID = "Mhe-aR-kQQ"; */ +"Mhe-aR-kQQ.text" = "—"; + /* Class = "WKInterfaceLabel"; text = "0.000"; ObjectID = "mpK-zY-UvA"; */ "mpK-zY-UvA.text" = "0.000"; -/* Class = "WKInterfaceLabel"; text = "Workout"; ObjectID = "nC0-X3-oFJ"; */ -"nC0-X3-oFJ.text" = "锻炼/运动模式"; +/* Class = "WKInterfaceLabel"; text = "TITLE"; ObjectID = "MZU-QV-PtZ"; */ +"MZU-QV-PtZ.text" = "名称"; + +/* Class = "WKInterfaceLabel"; text = "Override"; ObjectID = "nC0-X3-oFJ"; */ +"nC0-X3-oFJ.text" = "Override"; /* Class = "WKInterfaceButton"; title = "Bolus"; ObjectID = "Qsq-p5-1J0"; */ "Qsq-p5-1J0.title" = "大剂量"; @@ -64,9 +91,30 @@ /* Class = "WKInterfaceLabel"; text = "Bolus"; ObjectID = "smL-Rc-IZh"; */ "smL-Rc-IZh.text" = "大剂量"; +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "T4U-wP-dSW"; */ +"T4U-wP-dSW.text" = "标签"; + /* Class = "WKInterfaceLabel"; text = "10:09 AM"; ObjectID = "Ury-of-vQg"; */ "Ury-of-vQg.text" = "10:09 AM"; +/* Class = "WKInterfaceLabel"; text = "🏃‍♀️"; ObjectID = "UVY-pa-SUL"; */ +"UVY-pa-SUL.text" = "🏃‍♀️"; + +/* Class = "WKInterfaceController"; title = "Loop"; ObjectID = "v5b-sO-bb8"; */ +"v5b-sO-bb8.title" = "Loop"; + +/* Class = "WKInterfaceMenuItem"; title = "1 hour"; ObjectID = "vL1-NA-WZ1"; */ +"vL1-NA-WZ1.title" = "1小时"; + /* Class = "WKInterfaceLabel"; text = ""; ObjectID = "XkS-y5-khE"; */ "XkS-y5-khE.text" = ""; +/* Class = "WKInterfaceLabel"; text = "ACTIVE CARBS"; ObjectID = "ycL-5X-a05"; */ +"ycL-5X-a05.text" = "ACTIVE CARBS"; + +/* Class = "WKInterfaceLabel"; text = "---"; ObjectID = "yl8-ZP-c3l"; */ +"yl8-ZP-c3l.text" = "---"; + +/* Class = "WKInterfaceLabel"; text = "Label"; ObjectID = "zO8-x6-bZd"; */ +"zO8-x6-bZd.text" = "标签"; + From dd692222a0e04211453aa60244a28dfa47e351a1 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 28 Dec 2019 14:39:20 -0600 Subject: [PATCH 126/132] Bump carthage revs --- Cartfile.resolved | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index aa065ab191..5e78106638 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,8 +1,8 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" github "LoopKit/CGMBLEKit" "96de871ed517630cfc2c9c68ac9698870086d6c9" -github "LoopKit/G4ShareSpy" "af36bab46d5a39e500e9eabc4bbb147b24d0f2c4" +github "LoopKit/G4ShareSpy" "e5268fd8d458ddbd722187217a638d99a4578bc3" github "LoopKit/LoopKit" "3ea3bb600b599701f4fe0196409b1249c3d4e8bc" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" -github "LoopKit/dexcom-share-client-swift" "faf2b77419b690d32e488a16625314888455ba08" +github "LoopKit/dexcom-share-client-swift" "fe47b7a8f1f0e809ab4525520c559ab0e07a9d1c" github "i-schuetz/SwiftCharts" "0.6.5" -github "ps2/rileylink_ios" "4850a9ecf656a80f241c2fcc81f228c46e400fb4" +github "ps2/rileylink_ios" "8dc55eb849a5c8860db61b372550a2899642fd4b" From be121ca17c45884372d11b3a79690a7d203b4362 Mon Sep 17 00:00:00 2001 From: katie disimone Date: Sat, 28 Dec 2019 12:54:02 -0800 Subject: [PATCH 127/132] clear development team signing (#1216) --- Loop.xcodeproj/project.pbxproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 68d4bedab4..489ad76f93 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -3529,7 +3529,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_ENTITLEMENTS = Loop/Loop.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; - DEVELOPMENT_TEAM = 83JUVX6ELC; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = YES; INFOPLIST_FILE = Loop/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -3548,7 +3548,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_ENTITLEMENTS = Loop/Loop.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; - DEVELOPMENT_TEAM = 83JUVX6ELC; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = YES; INFOPLIST_FILE = Loop/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -3566,7 +3566,7 @@ CODE_SIGN_ENTITLEMENTS = "WatchApp Extension/WatchApp Extension.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = 83JUVX6ELC; + DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; @@ -3589,7 +3589,7 @@ CODE_SIGN_ENTITLEMENTS = "WatchApp Extension/WatchApp Extension.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = 83JUVX6ELC; + DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; @@ -3610,7 +3610,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = 83JUVX6ELC; + DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; IBSC_MODULE = WatchApp_Extension; INFOPLIST_FILE = WatchApp/Info.plist; @@ -3631,7 +3631,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=watchos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = 83JUVX6ELC; + DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/Carthage/Build/watchOS"; IBSC_MODULE = WatchApp_Extension; INFOPLIST_FILE = WatchApp/Info.plist; @@ -3911,7 +3911,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 83JUVX6ELC; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Loop Status Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; @@ -3933,7 +3933,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 83JUVX6ELC; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Loop Status Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; From f0cc33ce3e2ef849bdb1124c9a399673dcfc1517 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sat, 28 Dec 2019 15:48:11 -0600 Subject: [PATCH 128/132] Bump carthage revs --- Cartfile.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 5e78106638..0980ce2bf8 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -5,4 +5,4 @@ github "LoopKit/LoopKit" "3ea3bb600b599701f4fe0196409b1249c3d4e8bc" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" github "LoopKit/dexcom-share-client-swift" "fe47b7a8f1f0e809ab4525520c559ab0e07a9d1c" github "i-schuetz/SwiftCharts" "0.6.5" -github "ps2/rileylink_ios" "8dc55eb849a5c8860db61b372550a2899642fd4b" +github "ps2/rileylink_ios" "2888970adca367922b436d5245e26b0738700c07" From caf87edabaaa5dccbb26443058ae3e17fe5af2c3 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 29 Dec 2019 09:44:42 -0600 Subject: [PATCH 129/132] Remove LoopCore's Localizable.strings from Learn app --- Loop.xcodeproj/project.pbxproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 489ad76f93..2be8f19cba 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -335,7 +335,6 @@ 7D7076591FE06EE2004AC8EA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D70765B1FE06EE2004AC8EA /* Localizable.strings */; }; 7D70765E1FE06EE3004AC8EA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D7076601FE06EE3004AC8EA /* Localizable.strings */; }; 7D7076631FE06EE4004AC8EA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D7076651FE06EE4004AC8EA /* Localizable.strings */; }; - 7D9BEEE52335A6B3005DCFD6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D9BEEE72335A6B3005DCFD6 /* Localizable.strings */; }; 7D9BEEF32335CF8D005DCFD6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D9BEEF52335CF8D005DCFD6 /* Localizable.strings */; }; 892A5D2A222EF60A008961AB /* MockKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 892A5D29222EF60A008961AB /* MockKit.framework */; }; 892A5D2C222EF60A008961AB /* MockKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 892A5D2B222EF60A008961AB /* MockKitUI.framework */; }; @@ -2329,7 +2328,6 @@ files = ( 7D9BEEF32335CF8D005DCFD6 /* Localizable.strings in Resources */, 43D9FFAF21EA9A0F00AF44BF /* LaunchScreen.storyboard in Resources */, - 7D9BEEE52335A6B3005DCFD6 /* Localizable.strings in Resources */, 43D9FFAC21EA9A0F00AF44BF /* Assets.xcassets in Resources */, 43D9FFAA21EA9A0C00AF44BF /* Main.storyboard in Resources */, ); From 6144a00a8584cc72e34647391b73e9a65ade84cf Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 31 Dec 2019 15:41:44 -0600 Subject: [PATCH 130/132] Track cgm device when uploading BG data (#1222) * Track cgm device when uploading BG data * Bump carthage revs --- Cartfile.resolved | 4 ++-- Loop/Managers/DeviceDataManager.swift | 4 ++-- Loop/Managers/NightscoutDataManager.swift | 14 ++++++++++---- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 0980ce2bf8..fb7c03d9a8 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -3,6 +3,6 @@ github "LoopKit/CGMBLEKit" "96de871ed517630cfc2c9c68ac9698870086d6c9" github "LoopKit/G4ShareSpy" "e5268fd8d458ddbd722187217a638d99a4578bc3" github "LoopKit/LoopKit" "3ea3bb600b599701f4fe0196409b1249c3d4e8bc" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" -github "LoopKit/dexcom-share-client-swift" "fe47b7a8f1f0e809ab4525520c559ab0e07a9d1c" +github "LoopKit/dexcom-share-client-swift" "83b89d5badb86219a5da400437fc3cdea12b03b3" github "i-schuetz/SwiftCharts" "0.6.5" -github "ps2/rileylink_ios" "2888970adca367922b436d5245e26b0738700c07" +github "ps2/rileylink_ios" "0dc526607ce2b06f48dadf58a3fd7b39fe11e452" diff --git a/Loop/Managers/DeviceDataManager.swift b/Loop/Managers/DeviceDataManager.swift index 67a9174a94..194382ec2b 100644 --- a/Loop/Managers/DeviceDataManager.swift +++ b/Loop/Managers/DeviceDataManager.swift @@ -160,12 +160,12 @@ final class DeviceDataManager { switch result { case .newData(let values): log.default("CGMManager:\(type(of: manager)) did update with \(values.count) values") - + loopManager.addGlucose(values) { result in if manager.shouldSyncToRemoteService { switch result { case .success(let values): - self.nightscoutDataManager.uploadGlucose(values, sensorState: manager.sensorState) + self.nightscoutDataManager.uploadGlucose(values, sensorState: manager.sensorState, fromDevice: manager.device) case .failure: break } diff --git a/Loop/Managers/NightscoutDataManager.swift b/Loop/Managers/NightscoutDataManager.swift index ea8899ea15..f276d94b3f 100644 --- a/Loop/Managers/NightscoutDataManager.swift +++ b/Loop/Managers/NightscoutDataManager.swift @@ -406,12 +406,17 @@ final class NightscoutDataManager { uploader.uploadDeviceStatus(deviceStatus) } - func uploadGlucose(_ values: [GlucoseValue], sensorState: SensorDisplayable?) { + func uploadGlucose(_ values: [GlucoseValue], sensorState: SensorDisplayable?, fromDevice device: HKDevice?) { guard let uploader = deviceManager.remoteDataManager.nightscoutService.uploader else { return } - - let device = "loop://\(UIDevice.current.name)" + + var deviceStr: String + if let device = device { + deviceStr = [device.name, device.manufacturer, device.model, device.firmwareVersion, device.softwareVersion].compactMap { $0 }.joined(separator: " ") + } else { + deviceStr = "loop://unknowndevice" + } let direction: String? = { switch sensorState?.trendType { case .up?: @@ -434,9 +439,10 @@ final class NightscoutDataManager { glucoseMGDL: Int(value.quantity.doubleValue(for: .milligramsPerDeciliter)), at: value.startDate, direction: direction, - device: device + device: deviceStr ) } + uploader.flushAll(); } } From db9b05a92a5830f5859ac32cd934b177433f4096 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 31 Dec 2019 15:44:51 -0600 Subject: [PATCH 131/132] Use release versions of LoopKit, CGMBLEKit, rileylink_ios --- Cartfile | 10 +++++----- Cartfile.resolved | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cartfile b/Cartfile index ca66f13586..439e66e555 100644 --- a/Cartfile +++ b/Cartfile @@ -1,7 +1,7 @@ -github "LoopKit/LoopKit" "dev" -github "LoopKit/CGMBLEKit" "dev" +github "LoopKit/LoopKit" ~> 3.0 +github "LoopKit/CGMBLEKit" ~> 3.2 github "i-schuetz/SwiftCharts" == 0.6.5 -github "LoopKit/dexcom-share-client-swift" "dev" -github "LoopKit/G4ShareSpy" "dev" -github "ps2/rileylink_ios" "dev" +github "LoopKit/dexcom-share-client-swift" ~> 1.2 +github "LoopKit/G4ShareSpy" ~> 1.1 +github "ps2/rileylink_ios" ~> 3.0 github "LoopKit/Amplitude-iOS" "decreepify" diff --git a/Cartfile.resolved b/Cartfile.resolved index fb7c03d9a8..1e048677ca 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,8 +1,8 @@ github "LoopKit/Amplitude-iOS" "2137d5fd44bf630ed33e1e72d7af6d8f8612f270" -github "LoopKit/CGMBLEKit" "96de871ed517630cfc2c9c68ac9698870086d6c9" -github "LoopKit/G4ShareSpy" "e5268fd8d458ddbd722187217a638d99a4578bc3" -github "LoopKit/LoopKit" "3ea3bb600b599701f4fe0196409b1249c3d4e8bc" +github "LoopKit/CGMBLEKit" "v3.2" +github "LoopKit/G4ShareSpy" "v1.1" +github "LoopKit/LoopKit" "v3.0" github "LoopKit/MKRingProgressView" "f548a5c64832be2d37d7c91b5800e284887a2a0a" -github "LoopKit/dexcom-share-client-swift" "83b89d5badb86219a5da400437fc3cdea12b03b3" +github "LoopKit/dexcom-share-client-swift" "v1.2" github "i-schuetz/SwiftCharts" "0.6.5" -github "ps2/rileylink_ios" "0dc526607ce2b06f48dadf58a3fd7b39fe11e452" +github "ps2/rileylink_ios" "v3.0" From e623b4bfbdb42d0e275ac54b78c4b1b944b3cfe3 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 31 Dec 2019 15:51:20 -0600 Subject: [PATCH 132/132] Remove extra NSBluetoothAlwaysUsageDescription key --- Loop/Info.plist | 2 -- 1 file changed, 2 deletions(-) diff --git a/Loop/Info.plist b/Loop/Info.plist index ca065e1b23..f8907e07d0 100644 --- a/Loop/Info.plist +++ b/Loop/Info.plist @@ -47,8 +47,6 @@ Bluetooth is used to communicate with insulin pump and continuous glucose monitor devices NSBluetoothPeripheralUsageDescription Bluetooth is used to communicate with insulin pump and continuous glucose monitor devices - NSBluetoothAlwaysUsageDescription - Bluetooth is used to communicate with insulin pump and continuous glucose monitor devices NSFaceIDUsageDescription Face ID is used to authenticate insulin bolus. NSHealthShareUsageDescription