From 04323bb8c92e40f147e0b32648c5ee287f9c1177 Mon Sep 17 00:00:00 2001 From: Anna Date: Thu, 18 Jun 2020 09:47:46 -0700 Subject: [PATCH 01/29] LOOP 1269 - add prescription data model --- .../Extensions/Prescription.swift | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 TidepoolServiceKit/Extensions/Prescription.swift diff --git a/TidepoolServiceKit/Extensions/Prescription.swift b/TidepoolServiceKit/Extensions/Prescription.swift new file mode 100644 index 0000000..a71f651 --- /dev/null +++ b/TidepoolServiceKit/Extensions/Prescription.swift @@ -0,0 +1,148 @@ +// +// Prescription.swift +// TidepoolServiceKit +// +// Created by Anna Quinlan on 6/18/20. +// Copyright © 2020 LoopKit Authors. All rights reserved. +// + +import Foundation +import HealthKit +import LoopKit + + +public enum CGMType: String, Codable { + case g6 +} + +public enum PumpType: String, Codable { + case dash + case coastal +} + +public enum TrainingType: String, Codable { + case inPerson // Patient must have hands-on training with clinician/CDE + case inModule // Patient can train in-app + +} + +public struct Prescription { + public let datePrescribed: Date // Date prescription was prescribed + public let providerName: String // Name of clinician prescribing + public let cgm: CGMType // CGM type (manufacturer & model) + public let pump: PumpType // Pump type (manufacturer & model) + public let bloodGlucoseUnit: HKUnit // CGM type (manufacturer & model) + public let basalRateSchedule: BasalRateSchedule + public let glucoseTargetRangeSchedule: GlucoseRangeSchedule + public let carbRatioSchedule: CarbRatioSchedule + public let insulinSensitivitySchedule: InsulinSensitivitySchedule + public let maximumBasalRatePerHour: Double + public let maximumBolus: Double + public let suspendThreshold: GlucoseThreshold + public let insulinModel: InsulinModel + + public init(datePrescribed: Date, + providerName: String, + cgmType: CGMType, + pumpType: PumpType, + bloodGlucoseUnit: HKUnit, + basalRateSchedule: BasalRateSchedule, + glucoseTargetRangeSchedule: GlucoseRangeSchedule, + carbRatioSchedule: CarbRatioSchedule, + insulinSensitivitySchedule: InsulinSensitivitySchedule, + maximumBasalRatePerHour: Double, + maximumBolus: Double, + suspendThreshold: GlucoseThreshold, + insulinModel: InsulinModel) { + self.datePrescribed = datePrescribed + self.providerName = providerName + self.cgm = cgmType + self.pump = pumpType + self.bloodGlucoseUnit = bloodGlucoseUnit + self.basalRateSchedule = basalRateSchedule + self.glucoseTargetRangeSchedule = glucoseTargetRangeSchedule + self.carbRatioSchedule = carbRatioSchedule + self.insulinSensitivitySchedule = insulinSensitivitySchedule + self.maximumBasalRatePerHour = maximumBasalRatePerHour + self.maximumBolus = maximumBolus + self.suspendThreshold = suspendThreshold + self.insulinModel = insulinModel + } + + public struct InsulinModel: Codable, Equatable { + public enum ModelType: String, Codable { + case fiasp + case rapidAdult + case rapidChild + case walsh + } + + public let modelType: ModelType + public let actionDuration: TimeInterval + public let peakActivity: TimeInterval? + + public init(modelType: ModelType, actionDuration: TimeInterval, peakActivity: TimeInterval? = nil) { + self.modelType = modelType + self.actionDuration = actionDuration + self.peakActivity = peakActivity + } + } +} + +extension Prescription: Codable { + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + var bloodGlucoseUnit: HKUnit? + if let bloodGlucoseUnitString = try container.decodeIfPresent(String.self, forKey: .bloodGlucoseUnit) { + bloodGlucoseUnit = HKUnit(from: bloodGlucoseUnitString) + } + self.init(datePrescribed: try container.decode(Date.self, forKey: .datePrescribed), + providerName: try container.decode(String.self, forKey: .providerName), + cgmType: try container.decode(CGMType.self, forKey: .cgm), + pumpType: try container.decode(PumpType.self, forKey: .pump), + bloodGlucoseUnit: bloodGlucoseUnit ?? .milligramsPerDeciliter, + basalRateSchedule: try container.decode(BasalRateSchedule.self, forKey: .basalRateSchedule), + glucoseTargetRangeSchedule: try container.decode(GlucoseRangeSchedule.self, forKey: .glucoseTargetRangeSchedule), + carbRatioSchedule: try container.decode(CarbRatioSchedule.self, forKey: .carbRatioSchedule), + insulinSensitivitySchedule: try container.decode(InsulinSensitivitySchedule.self, forKey: .insulinSensitivitySchedule), + maximumBasalRatePerHour: try container.decode(Double.self, forKey: .maximumBasalRatePerHour), + maximumBolus: try container.decode(Double.self, forKey: .maximumBolus), + suspendThreshold: try container.decode(GlucoseThreshold.self, forKey: .suspendThreshold), + insulinModel: try container.decode(InsulinModel.self, forKey: .insulinModel)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(datePrescribed, forKey: .datePrescribed) + try container.encode(providerName, forKey: .providerName) + try container.encode(cgm, forKey: .cgm) + try container.encode(pump, forKey: .pump) + try container.encode(bloodGlucoseUnit.unitString, forKey: .bloodGlucoseUnit) + try container.encode(basalRateSchedule, forKey: .basalRateSchedule) + try container.encode(glucoseTargetRangeSchedule, forKey: .glucoseTargetRangeSchedule) + try container.encode(carbRatioSchedule, forKey: .carbRatioSchedule) + try container.encode(insulinSensitivitySchedule, forKey: .insulinSensitivitySchedule) + try container.encode(maximumBasalRatePerHour, forKey: .maximumBasalRatePerHour) + try container.encode(maximumBolus, forKey: .maximumBolus) + try container.encode(suspendThreshold, forKey: .suspendThreshold) + try container.encode(insulinModel, forKey: .insulinModel) + } + + private enum CodingKeys: String, CodingKey { + case datePrescribed + case providerName + case cgm + case pump + case bloodGlucoseUnit + case basalRateSchedule + case glucoseTargetRangeSchedule + case carbRatioSchedule + case insulinSensitivitySchedule + case maximumBasalRatePerHour + case maximumBolus + case suspendThreshold + case insulinModel + + } +} + From 2d1bab62f05a8564b34e71e52f15c84775afa0c0 Mon Sep 17 00:00:00 2001 From: Anna Date: Thu, 18 Jun 2020 10:55:35 -0700 Subject: [PATCH 02/29] Undo commit to add project.pbx file changes --- .../Extensions/Prescription.swift | 148 ------------------ 1 file changed, 148 deletions(-) delete mode 100644 TidepoolServiceKit/Extensions/Prescription.swift diff --git a/TidepoolServiceKit/Extensions/Prescription.swift b/TidepoolServiceKit/Extensions/Prescription.swift deleted file mode 100644 index a71f651..0000000 --- a/TidepoolServiceKit/Extensions/Prescription.swift +++ /dev/null @@ -1,148 +0,0 @@ -// -// Prescription.swift -// TidepoolServiceKit -// -// Created by Anna Quinlan on 6/18/20. -// Copyright © 2020 LoopKit Authors. All rights reserved. -// - -import Foundation -import HealthKit -import LoopKit - - -public enum CGMType: String, Codable { - case g6 -} - -public enum PumpType: String, Codable { - case dash - case coastal -} - -public enum TrainingType: String, Codable { - case inPerson // Patient must have hands-on training with clinician/CDE - case inModule // Patient can train in-app - -} - -public struct Prescription { - public let datePrescribed: Date // Date prescription was prescribed - public let providerName: String // Name of clinician prescribing - public let cgm: CGMType // CGM type (manufacturer & model) - public let pump: PumpType // Pump type (manufacturer & model) - public let bloodGlucoseUnit: HKUnit // CGM type (manufacturer & model) - public let basalRateSchedule: BasalRateSchedule - public let glucoseTargetRangeSchedule: GlucoseRangeSchedule - public let carbRatioSchedule: CarbRatioSchedule - public let insulinSensitivitySchedule: InsulinSensitivitySchedule - public let maximumBasalRatePerHour: Double - public let maximumBolus: Double - public let suspendThreshold: GlucoseThreshold - public let insulinModel: InsulinModel - - public init(datePrescribed: Date, - providerName: String, - cgmType: CGMType, - pumpType: PumpType, - bloodGlucoseUnit: HKUnit, - basalRateSchedule: BasalRateSchedule, - glucoseTargetRangeSchedule: GlucoseRangeSchedule, - carbRatioSchedule: CarbRatioSchedule, - insulinSensitivitySchedule: InsulinSensitivitySchedule, - maximumBasalRatePerHour: Double, - maximumBolus: Double, - suspendThreshold: GlucoseThreshold, - insulinModel: InsulinModel) { - self.datePrescribed = datePrescribed - self.providerName = providerName - self.cgm = cgmType - self.pump = pumpType - self.bloodGlucoseUnit = bloodGlucoseUnit - self.basalRateSchedule = basalRateSchedule - self.glucoseTargetRangeSchedule = glucoseTargetRangeSchedule - self.carbRatioSchedule = carbRatioSchedule - self.insulinSensitivitySchedule = insulinSensitivitySchedule - self.maximumBasalRatePerHour = maximumBasalRatePerHour - self.maximumBolus = maximumBolus - self.suspendThreshold = suspendThreshold - self.insulinModel = insulinModel - } - - public struct InsulinModel: Codable, Equatable { - public enum ModelType: String, Codable { - case fiasp - case rapidAdult - case rapidChild - case walsh - } - - public let modelType: ModelType - public let actionDuration: TimeInterval - public let peakActivity: TimeInterval? - - public init(modelType: ModelType, actionDuration: TimeInterval, peakActivity: TimeInterval? = nil) { - self.modelType = modelType - self.actionDuration = actionDuration - self.peakActivity = peakActivity - } - } -} - -extension Prescription: Codable { - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - var bloodGlucoseUnit: HKUnit? - if let bloodGlucoseUnitString = try container.decodeIfPresent(String.self, forKey: .bloodGlucoseUnit) { - bloodGlucoseUnit = HKUnit(from: bloodGlucoseUnitString) - } - self.init(datePrescribed: try container.decode(Date.self, forKey: .datePrescribed), - providerName: try container.decode(String.self, forKey: .providerName), - cgmType: try container.decode(CGMType.self, forKey: .cgm), - pumpType: try container.decode(PumpType.self, forKey: .pump), - bloodGlucoseUnit: bloodGlucoseUnit ?? .milligramsPerDeciliter, - basalRateSchedule: try container.decode(BasalRateSchedule.self, forKey: .basalRateSchedule), - glucoseTargetRangeSchedule: try container.decode(GlucoseRangeSchedule.self, forKey: .glucoseTargetRangeSchedule), - carbRatioSchedule: try container.decode(CarbRatioSchedule.self, forKey: .carbRatioSchedule), - insulinSensitivitySchedule: try container.decode(InsulinSensitivitySchedule.self, forKey: .insulinSensitivitySchedule), - maximumBasalRatePerHour: try container.decode(Double.self, forKey: .maximumBasalRatePerHour), - maximumBolus: try container.decode(Double.self, forKey: .maximumBolus), - suspendThreshold: try container.decode(GlucoseThreshold.self, forKey: .suspendThreshold), - insulinModel: try container.decode(InsulinModel.self, forKey: .insulinModel)) - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(datePrescribed, forKey: .datePrescribed) - try container.encode(providerName, forKey: .providerName) - try container.encode(cgm, forKey: .cgm) - try container.encode(pump, forKey: .pump) - try container.encode(bloodGlucoseUnit.unitString, forKey: .bloodGlucoseUnit) - try container.encode(basalRateSchedule, forKey: .basalRateSchedule) - try container.encode(glucoseTargetRangeSchedule, forKey: .glucoseTargetRangeSchedule) - try container.encode(carbRatioSchedule, forKey: .carbRatioSchedule) - try container.encode(insulinSensitivitySchedule, forKey: .insulinSensitivitySchedule) - try container.encode(maximumBasalRatePerHour, forKey: .maximumBasalRatePerHour) - try container.encode(maximumBolus, forKey: .maximumBolus) - try container.encode(suspendThreshold, forKey: .suspendThreshold) - try container.encode(insulinModel, forKey: .insulinModel) - } - - private enum CodingKeys: String, CodingKey { - case datePrescribed - case providerName - case cgm - case pump - case bloodGlucoseUnit - case basalRateSchedule - case glucoseTargetRangeSchedule - case carbRatioSchedule - case insulinSensitivitySchedule - case maximumBasalRatePerHour - case maximumBolus - case suspendThreshold - case insulinModel - - } -} - From 9af14884c588ee09c12edade2cec39f23b322dca Mon Sep 17 00:00:00 2001 From: Anna Date: Thu, 18 Jun 2020 10:56:20 -0700 Subject: [PATCH 03/29] Auto stash before revert of "LOOP 1269 - add prescription data model" --- .../Mocks/MockPrescriptionManager.swift | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 TidepoolServiceKit/Mocks/MockPrescriptionManager.swift diff --git a/TidepoolServiceKit/Mocks/MockPrescriptionManager.swift b/TidepoolServiceKit/Mocks/MockPrescriptionManager.swift new file mode 100644 index 0000000..78efa08 --- /dev/null +++ b/TidepoolServiceKit/Mocks/MockPrescriptionManager.swift @@ -0,0 +1,93 @@ +// +// MockPrescriptionManager.swift +// TidepoolServiceKit +// +// Created by Anna Quinlan on 6/18/20. +// Copyright © 2020 LoopKit Authors. All rights reserved. +// + +import Foundation +import LoopKit + + +extension TimeInterval { + static func minutes(_ minutes: Double) -> TimeInterval { + return TimeInterval(minutes * 60 /* seconds in minute */) + } + + static func hours(_ hours: Double) -> TimeInterval { + return TimeInterval(hours * 60 /* minutes in hr */ * 60 /* seconds in minute */) + } +} + +public class MockPrescriptionManager { + private var prescription: Prescription + + public init(prescription: Prescription? = nil) { + if let prescription = prescription { + self.prescription = prescription + } else { + let timeZone = TimeZone(identifier: "America/Los_Angeles")! + let glucoseTargetRangeSchedule = GlucoseRangeSchedule( + rangeSchedule: DailyQuantitySchedule(unit: .milligramsPerDeciliter, + dailyItems: [RepeatingScheduleValue(startTime: .hours(0), value: DoubleRange(minValue: 100.0, maxValue: 110.0)), RepeatingScheduleValue(startTime: .hours(8), value: DoubleRange(minValue: 95.0, maxValue: 105.0)), RepeatingScheduleValue(startTime: .hours(14), value: DoubleRange(minValue: 95.0, maxValue: 105.0)), RepeatingScheduleValue(startTime: .hours(16), value: DoubleRange(minValue: 100.0, maxValue: 110.0)), RepeatingScheduleValue(startTime: .hours(18), value: DoubleRange(minValue: 90.0, maxValue: 100.0)), RepeatingScheduleValue(startTime: .hours(21), value: DoubleRange(minValue: 110.0, maxValue: 120.0))], + timeZone: timeZone)!, + override: GlucoseRangeSchedule.Override(value: DoubleRange(minValue: 80.0, maxValue: 90.0), + start: Date().addingTimeInterval(-.minutes(30)), + end: Date().addingTimeInterval(.minutes(30))) + ) + let basalRateSchedule = BasalRateSchedule( + dailyItems: [RepeatingScheduleValue(startTime: .hours(0), value: 1.0), + RepeatingScheduleValue(startTime: .hours(8), value: 1.125), + RepeatingScheduleValue(startTime: .hours(10), value: 1.25), + RepeatingScheduleValue(startTime: .hours(12), value: 1.5), + RepeatingScheduleValue(startTime: .hours(14), value: 1.25), + RepeatingScheduleValue(startTime: .hours(16), value: 1.5), + RepeatingScheduleValue(startTime: .hours(18), value: 1.25), + RepeatingScheduleValue(startTime: .hours(21), value: 1.0)], + timeZone: timeZone)! + let insulinSensitivitySchedule = InsulinSensitivitySchedule( + unit: .milligramsPerDeciliter, + dailyItems: [RepeatingScheduleValue(startTime: .hours(0), value: 45.0), + RepeatingScheduleValue(startTime: .hours(8), value: 40.0), + RepeatingScheduleValue(startTime: .hours(10), value: 35.0), + RepeatingScheduleValue(startTime: .hours(12), value: 30.0), + RepeatingScheduleValue(startTime: .hours(14), value: 35.0), + RepeatingScheduleValue(startTime: .hours(16), value: 40.0)], + timeZone: timeZone)! + let carbRatioSchedule = CarbRatioSchedule( + unit: .gram(), + + dailyItems: [RepeatingScheduleValue(startTime: .hours(0), value: 10.0), + RepeatingScheduleValue(startTime: .hours(8), value: 12.0), + RepeatingScheduleValue(startTime: .hours(10), value: 9.0), + RepeatingScheduleValue(startTime: .hours(12), value: 10.0), + RepeatingScheduleValue(startTime: .hours(14), value: 11.0), + RepeatingScheduleValue(startTime: .hours(16), value: 12.0), + RepeatingScheduleValue(startTime: .hours(18), value: 8.0), + RepeatingScheduleValue(startTime: .hours(21), value: 10.0)], + timeZone: timeZone)! + + + + self.prescription = Prescription( + datePrescribed: Date(), + providerName: "Sally Seastar", + cgmType: CGMType.g6, + pumpType: PumpType.dash, + bloodGlucoseUnit: .milligramsPerDeciliter, + basalRateSchedule: basalRateSchedule, + glucoseTargetRangeSchedule: glucoseTargetRangeSchedule, + carbRatioSchedule: carbRatioSchedule, + insulinSensitivitySchedule: insulinSensitivitySchedule, + maximumBasalRatePerHour: 3.0, + maximumBolus: 5.0, + suspendThreshold: GlucoseThreshold(unit: .milligramsPerDeciliter, value: 70), + insulinModel: Prescription.InsulinModel(modelType: .rapidAdult, actionDuration: .hours(6), peakActivity: .hours(3))) + } + } + + public func getPrescriptionData(completion: @escaping (Result) -> Void) { + completion(.success(self.prescription)) + } +} From e0740c4fa233523df87dd2c48f3e684450fb486f Mon Sep 17 00:00:00 2001 From: Anna Date: Thu, 18 Jun 2020 12:08:01 -0700 Subject: [PATCH 04/29] LOOP-1269 Add prescription storage model --- TidepoolService.xcodeproj/project.pbxproj | 4 + .../Extensions/Prescription.swift | 147 ++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 TidepoolServiceKit/Extensions/Prescription.swift diff --git a/TidepoolService.xcodeproj/project.pbxproj b/TidepoolService.xcodeproj/project.pbxproj index 96b7481..65100a4 100644 --- a/TidepoolService.xcodeproj/project.pbxproj +++ b/TidepoolService.xcodeproj/project.pbxproj @@ -62,6 +62,7 @@ A9DAAD6A22E7E81E00E76C9F /* LoopKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9DAAD6922E7E81E00E76C9F /* LoopKitUI.framework */; }; A9DAAD6D22E7EA8F00E76C9F /* IdentifiableClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DAAD6C22E7EA8F00E76C9F /* IdentifiableClass.swift */; }; A9DAAD6F22E7EA9700E76C9F /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DAAD6E22E7EA9700E76C9F /* NibLoadable.swift */; }; + E9692174249BF2A100D9BE3B /* Prescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9692173249BF2A100D9BE3B /* Prescription.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -168,6 +169,7 @@ A9DAAD6922E7E81E00E76C9F /* LoopKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LoopKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A9DAAD6C22E7EA8F00E76C9F /* IdentifiableClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentifiableClass.swift; sourceTree = ""; }; A9DAAD6E22E7EA9700E76C9F /* NibLoadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NibLoadable.swift; sourceTree = ""; }; + E9692173249BF2A100D9BE3B /* Prescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Prescription.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -235,6 +237,7 @@ A9309CAE2436C52900E02268 /* StoredGlucoseSample.swift */, A97A60FA243818C900AD69A5 /* TDatum.swift */, A9309CA2243563CD00E02268 /* TOrigin.swift */, + E9692173249BF2A100D9BE3B /* Prescription.swift */, ); path = Extensions; sourceTree = ""; @@ -693,6 +696,7 @@ A9309CA52435986300E02268 /* DeletedCarbEntry.swift in Sources */, A9309CAF2436C52900E02268 /* StoredGlucoseSample.swift in Sources */, A9D1107C242289720091C620 /* HKUnit.swift in Sources */, + E9692174249BF2A100D9BE3B /* Prescription.swift in Sources */, A9309CA72435987000E02268 /* StoredCarbEntry.swift in Sources */, A97A60FB243818C900AD69A5 /* TDatum.swift in Sources */, A9DAAD3322E7CA1A00E76C9F /* LocalizedString.swift in Sources */, diff --git a/TidepoolServiceKit/Extensions/Prescription.swift b/TidepoolServiceKit/Extensions/Prescription.swift new file mode 100644 index 0000000..25922af --- /dev/null +++ b/TidepoolServiceKit/Extensions/Prescription.swift @@ -0,0 +1,147 @@ +// +// Prescription.swift +// TidepoolServiceKit +// +// Created by Anna Quinlan on 6/18/20. +// Copyright © 2020 LoopKit Authors. All rights reserved. +// + +import Foundation +import HealthKit +import LoopKit + + +public enum CGMType: String, Codable { + case g6 +} + +public enum PumpType: String, Codable { + case dash + case coastal +} + +public enum TrainingType: String, Codable { + case inPerson // Patient must have hands-on training with clinician/CDE + case inModule // Patient can train in-app + +} + +public struct Prescription { + public let datePrescribed: Date // Date prescription was prescribed + public let providerName: String // Name of clinician prescribing + public let cgm: CGMType // CGM type (manufacturer & model) + public let pump: PumpType // Pump type (manufacturer & model) + public let bloodGlucoseUnit: HKUnit // CGM type (manufacturer & model) + public let basalRateSchedule: BasalRateSchedule + public let glucoseTargetRangeSchedule: GlucoseRangeSchedule + public let carbRatioSchedule: CarbRatioSchedule + public let insulinSensitivitySchedule: InsulinSensitivitySchedule + public let maximumBasalRatePerHour: Double + public let maximumBolus: Double + public let suspendThreshold: GlucoseThreshold + public let insulinModel: InsulinModel + + public init(datePrescribed: Date, + providerName: String, + cgmType: CGMType, + pumpType: PumpType, + bloodGlucoseUnit: HKUnit, + basalRateSchedule: BasalRateSchedule, + glucoseTargetRangeSchedule: GlucoseRangeSchedule, + carbRatioSchedule: CarbRatioSchedule, + insulinSensitivitySchedule: InsulinSensitivitySchedule, + maximumBasalRatePerHour: Double, + maximumBolus: Double, + suspendThreshold: GlucoseThreshold, + insulinModel: InsulinModel) { + self.datePrescribed = datePrescribed + self.providerName = providerName + self.cgm = cgmType + self.pump = pumpType + self.bloodGlucoseUnit = bloodGlucoseUnit + self.basalRateSchedule = basalRateSchedule + self.glucoseTargetRangeSchedule = glucoseTargetRangeSchedule + self.carbRatioSchedule = carbRatioSchedule + self.insulinSensitivitySchedule = insulinSensitivitySchedule + self.maximumBasalRatePerHour = maximumBasalRatePerHour + self.maximumBolus = maximumBolus + self.suspendThreshold = suspendThreshold + self.insulinModel = insulinModel + } + + public struct InsulinModel: Codable, Equatable { + public enum ModelType: String, Codable { + case fiasp + case rapidAdult + case rapidChild + case walsh + } + + public let modelType: ModelType + public let actionDuration: TimeInterval + public let peakActivity: TimeInterval? + + public init(modelType: ModelType, actionDuration: TimeInterval, peakActivity: TimeInterval? = nil) { + self.modelType = modelType + self.actionDuration = actionDuration + self.peakActivity = peakActivity + } + } +} + +extension Prescription: Codable { + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + var bloodGlucoseUnit: HKUnit? + if let bloodGlucoseUnitString = try container.decodeIfPresent(String.self, forKey: .bloodGlucoseUnit) { + bloodGlucoseUnit = HKUnit(from: bloodGlucoseUnitString) + } + self.init(datePrescribed: try container.decode(Date.self, forKey: .datePrescribed), + providerName: try container.decode(String.self, forKey: .providerName), + cgmType: try container.decode(CGMType.self, forKey: .cgm), + pumpType: try container.decode(PumpType.self, forKey: .pump), + bloodGlucoseUnit: bloodGlucoseUnit ?? .milligramsPerDeciliter, + basalRateSchedule: try container.decode(BasalRateSchedule.self, forKey: .basalRateSchedule), + glucoseTargetRangeSchedule: try container.decode(GlucoseRangeSchedule.self, forKey: .glucoseTargetRangeSchedule), + carbRatioSchedule: try container.decode(CarbRatioSchedule.self, forKey: .carbRatioSchedule), + insulinSensitivitySchedule: try container.decode(InsulinSensitivitySchedule.self, forKey: .insulinSensitivitySchedule), + maximumBasalRatePerHour: try container.decode(Double.self, forKey: .maximumBasalRatePerHour), + maximumBolus: try container.decode(Double.self, forKey: .maximumBolus), + suspendThreshold: try container.decode(GlucoseThreshold.self, forKey: .suspendThreshold), + insulinModel: try container.decode(InsulinModel.self, forKey: .insulinModel)) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(datePrescribed, forKey: .datePrescribed) + try container.encode(providerName, forKey: .providerName) + try container.encode(cgm, forKey: .cgm) + try container.encode(pump, forKey: .pump) + try container.encode(bloodGlucoseUnit.unitString, forKey: .bloodGlucoseUnit) + try container.encode(basalRateSchedule, forKey: .basalRateSchedule) + try container.encode(glucoseTargetRangeSchedule, forKey: .glucoseTargetRangeSchedule) + try container.encode(carbRatioSchedule, forKey: .carbRatioSchedule) + try container.encode(insulinSensitivitySchedule, forKey: .insulinSensitivitySchedule) + try container.encode(maximumBasalRatePerHour, forKey: .maximumBasalRatePerHour) + try container.encode(maximumBolus, forKey: .maximumBolus) + try container.encode(suspendThreshold, forKey: .suspendThreshold) + try container.encode(insulinModel, forKey: .insulinModel) + } + + private enum CodingKeys: String, CodingKey { + case datePrescribed + case providerName + case cgm + case pump + case bloodGlucoseUnit + case basalRateSchedule + case glucoseTargetRangeSchedule + case carbRatioSchedule + case insulinSensitivitySchedule + case maximumBasalRatePerHour + case maximumBolus + case suspendThreshold + case insulinModel + + } +} From 0cc3b7637eed5dce2ab47cc84c6a11ec2ee66632 Mon Sep 17 00:00:00 2001 From: Anna Date: Mon, 22 Jun 2020 18:01:31 -0700 Subject: [PATCH 05/29] LOOP-1269 - add UI controller + first screen Add underlying infrastructure into Tidepool Service, a UI controller to coordinate prescription flow, & the prescription code entry screen --- TidepoolService.xcodeproj/project.pbxproj | 48 +++++++ TidepoolServiceKit/TidepoolService.swift | 17 +++ .../TidepoolServiceSetupViewController.swift | 8 ++ .../PrescriptionReviewUICoordinator.swift | 96 +++++++++++++ .../PrescriptionCodeEntryModel.swift | 32 +++++ .../PrescriptionCodeEntryViewModel.swift | 48 +++++++ .../Views/PrescriptionCodeEntryView.swift | 128 ++++++++++++++++++ 7 files changed, 377 insertions(+) create mode 100644 TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift create mode 100644 TidepoolServiceKitUI/View Models/PrescriptionCodeEntryModel.swift create mode 100644 TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift create mode 100644 TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift diff --git a/TidepoolService.xcodeproj/project.pbxproj b/TidepoolService.xcodeproj/project.pbxproj index 65100a4..1a1b4ba 100644 --- a/TidepoolService.xcodeproj/project.pbxproj +++ b/TidepoolService.xcodeproj/project.pbxproj @@ -62,7 +62,11 @@ A9DAAD6A22E7E81E00E76C9F /* LoopKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9DAAD6922E7E81E00E76C9F /* LoopKitUI.framework */; }; A9DAAD6D22E7EA8F00E76C9F /* IdentifiableClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DAAD6C22E7EA8F00E76C9F /* IdentifiableClass.swift */; }; A9DAAD6F22E7EA9700E76C9F /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DAAD6E22E7EA9700E76C9F /* NibLoadable.swift */; }; + E93BA05E24A14CBA00C5D7E6 /* PrescriptionCodeEntryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E93BA05D24A14CBA00C5D7E6 /* PrescriptionCodeEntryViewModel.swift */; }; + E9692172249BC73600D9BE3B /* MockPrescriptionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9692171249BC73600D9BE3B /* MockPrescriptionManager.swift */; }; E9692174249BF2A100D9BE3B /* Prescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9692173249BF2A100D9BE3B /* Prescription.swift */; }; + E9692176249C1AE700D9BE3B /* PrescriptionReviewUICoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9692175249C1AE700D9BE3B /* PrescriptionReviewUICoordinator.swift */; }; + E96AEB98249C2FF1003797B4 /* PrescriptionCodeEntryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E96AEB97249C2FF1003797B4 /* PrescriptionCodeEntryView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -169,7 +173,11 @@ A9DAAD6922E7E81E00E76C9F /* LoopKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LoopKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A9DAAD6C22E7EA8F00E76C9F /* IdentifiableClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentifiableClass.swift; sourceTree = ""; }; A9DAAD6E22E7EA9700E76C9F /* NibLoadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NibLoadable.swift; sourceTree = ""; }; + E93BA05D24A14CBA00C5D7E6 /* PrescriptionCodeEntryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrescriptionCodeEntryViewModel.swift; sourceTree = ""; }; + E9692171249BC73600D9BE3B /* MockPrescriptionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPrescriptionManager.swift; sourceTree = ""; }; E9692173249BF2A100D9BE3B /* Prescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Prescription.swift; sourceTree = ""; }; + E9692175249C1AE700D9BE3B /* PrescriptionReviewUICoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrescriptionReviewUICoordinator.swift; sourceTree = ""; }; + E96AEB97249C2FF1003797B4 /* PrescriptionCodeEntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrescriptionCodeEntryView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -306,6 +314,7 @@ A9DAAD0022E7987800E76C9F /* TidepoolServiceKit */ = { isa = PBXGroup; children = ( + E9692170249BC69B00D9BE3B /* Mocks */, A9DAAD0122E7987800E76C9F /* TidepoolServiceKit.h */, A9DAAD0222E7987800E76C9F /* Info.plist */, A9DAAD3522E7CAC100E76C9F /* TidepoolService.swift */, @@ -326,7 +335,10 @@ A9DAAD1C22E7988900E76C9F /* TidepoolServiceKitUI */ = { isa = PBXGroup; children = ( + E93BA05C24A126E000C5D7E6 /* View Models */, + E9692179249C1B9400D9BE3B /* Views */, A9DAAD1D22E7988900E76C9F /* TidepoolServiceKitUI.h */, + E9692177249C1B0F00D9BE3B /* View Controllers */, A9DAAD1E22E7988900E76C9F /* Info.plist */, A9DAAD6C22E7EA8F00E76C9F /* IdentifiableClass.swift */, A9DAAD6E22E7EA9700E76C9F /* NibLoadable.swift */, @@ -362,6 +374,38 @@ name = Frameworks; sourceTree = ""; }; + E93BA05C24A126E000C5D7E6 /* View Models */ = { + isa = PBXGroup; + children = ( + E93BA05D24A14CBA00C5D7E6 /* PrescriptionCodeEntryViewModel.swift */, + ); + path = "View Models"; + sourceTree = ""; + }; + E9692170249BC69B00D9BE3B /* Mocks */ = { + isa = PBXGroup; + children = ( + E9692171249BC73600D9BE3B /* MockPrescriptionManager.swift */, + ); + path = Mocks; + sourceTree = ""; + }; + E9692177249C1B0F00D9BE3B /* View Controllers */ = { + isa = PBXGroup; + children = ( + E9692175249C1AE700D9BE3B /* PrescriptionReviewUICoordinator.swift */, + ); + path = "View Controllers"; + sourceTree = ""; + }; + E9692179249C1B9400D9BE3B /* Views */ = { + isa = PBXGroup; + children = ( + E96AEB97249C2FF1003797B4 /* PrescriptionCodeEntryView.swift */, + ); + path = Views; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -702,6 +746,7 @@ A9DAAD3322E7CA1A00E76C9F /* LocalizedString.swift in Sources */, A913B37D24200C97000805C4 /* Bundle.swift in Sources */, A9309CA3243563CD00E02268 /* TOrigin.swift in Sources */, + E9692172249BC73600D9BE3B /* MockPrescriptionManager.swift in Sources */, A97651752421AA10002EB5D4 /* OSLog.swift in Sources */, A9DAAD3622E7CAC100E76C9F /* TidepoolService.swift in Sources */, ); @@ -720,10 +765,13 @@ files = ( A9DAAD6D22E7EA8F00E76C9F /* IdentifiableClass.swift in Sources */, A94AE4F8235A909A005CA320 /* UIColor.swift in Sources */, + E96AEB98249C2FF1003797B4 /* PrescriptionCodeEntryView.swift in Sources */, A97651762421AA11002EB5D4 /* OSLog.swift in Sources */, + E9692176249C1AE700D9BE3B /* PrescriptionReviewUICoordinator.swift in Sources */, A9DAAD3422E7CA1A00E76C9F /* LocalizedString.swift in Sources */, A9DAAD3922E7DEE000E76C9F /* TidepoolService+UI.swift in Sources */, A9DAAD6F22E7EA9700E76C9F /* NibLoadable.swift in Sources */, + E93BA05E24A14CBA00C5D7E6 /* PrescriptionCodeEntryViewModel.swift in Sources */, A92E770122E9181500591027 /* TidepoolServiceSetupViewController.swift in Sources */, A9DAAD3B22E7DEF100E76C9F /* TidepoolServiceSettingsViewController.swift in Sources */, ); diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index 6fec8b4..2bf79c3 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -12,6 +12,7 @@ import TidepoolKit public enum TidepoolServiceError: Error { case configuration + case noPrescriptionDataAvailable } public protocol SessionStorage { @@ -30,6 +31,8 @@ public final class TidepoolService: Service { public lazy var sessionStorage: SessionStorage? = KeychainManager() public let tapi = TAPI() + + public let onboardingNeeded: Bool public private (set) var error: Error? @@ -58,6 +61,7 @@ public final class TidepoolService: Service { public init() { self.id = UUID().uuidString + self.onboardingNeeded = true } public init?(rawState: RawStateValue) { @@ -67,6 +71,7 @@ public final class TidepoolService: Service { do { self.id = id self.dataSetId = rawState["dataSetId"] as? String + self.onboardingNeeded = rawState["onboardingNeeded"] as? Bool ?? false self.session = try sessionStorage?.getSession(for: sessionService) } catch let error { self.error = error @@ -77,6 +82,7 @@ public final class TidepoolService: Service { var rawValue: RawStateValue = [:] rawValue["id"] = id rawValue["dataSetId"] = dataSetId + rawValue["onboardingNeeded"] = onboardingNeeded return rawValue } @@ -145,6 +151,17 @@ public final class TidepoolService: Service { } } } + + public func getPrescriptionData(completion: @escaping (Result) -> Void) { + #if targetEnvironment(simulator) + MockPrescriptionManager().getPrescriptionData { result in + completion(result) + } + #else + // TODO: add in proper query to backend + completion(.failure(TidepoolServiceError.noPrescriptionDataAvailable)) + #endif + } private var sessionService: String { "org.tidepool.TidepoolService.\(id)" } } diff --git a/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift b/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift index 17cd38f..2aa4931 100644 --- a/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift +++ b/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift @@ -7,6 +7,7 @@ // import LoopKitUI +import SwiftUI import TidepoolKit import TidepoolKitUI import TidepoolServiceKit @@ -61,6 +62,13 @@ final class TidepoolServiceSetupViewController: UIViewController, TLoginSignupDe } } } + + private func onboardIfNeeded() { + if service.onboardingNeeded { + let setupViewController = PrescriptionReviewUICoordinator() + self.present(setupViewController, animated: true, completion: nil) + } + } private func notifyComplete() { if let serviceViewController = navigationController as? ServiceViewController { diff --git a/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift b/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift new file mode 100644 index 0000000..d90b019 --- /dev/null +++ b/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift @@ -0,0 +1,96 @@ +// +// PrescriptionReviewUICoordinator.swift +// TidepoolServiceKitUI +// +// Created by Anna Quinlan on 6/18/20. +// Copyright © 2020 LoopKit Authors. All rights reserved. +// + +import Foundation +import SwiftUI +import LoopKitUI + +enum PrescriptionReviewScreen { + case enterCode + case reviewDevices + + func next() -> PrescriptionReviewScreen? { + switch self { + case .enterCode: + return .reviewDevices + case .reviewDevices: + return nil + } + } +} + +class PrescriptionReviewUICoordinator: UINavigationController, CompletionNotifying { + var screenStack = [PrescriptionReviewScreen]() + var completionDelegate: CompletionDelegate? + + let viewModel = PrescriptionCodeEntryViewModel() + + var currentScreen: PrescriptionReviewScreen { + return screenStack.last! + } + + // TODO: create delegate so we can add settings to LoopDataManager + init() { + super.init(navigationBarClass: UINavigationBar.self, toolbarClass: UIToolbar.self) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func viewControllerForScreen(_ screen: PrescriptionReviewScreen) -> UIViewController { + switch screen { + case .enterCode: + viewModel.didCancel = { [weak self] in + self?.setupCanceled() + } + viewModel.didFinishStep = { [weak self] in + self?.stepFinished() + } + let view = PrescriptionCodeEntryView(viewModel: viewModel) + return UIHostingController(rootView: view) + case .reviewDevices: + viewModel.didCancel = { [weak self] in + self?.setupCanceled() + } + viewModel.didFinishStep = { [weak self] in + self?.stepFinished() + } + let view = PrescriptionDeviceView(viewModel: viewModel) + return UIHostingController(rootView: view) + } + } + + private func determineFirstScreen() -> PrescriptionReviewScreen { + return .enterCode + } + + override func viewWillAppear(_ animated: Bool) { + screenStack = [determineFirstScreen()] + let viewController = viewControllerForScreen(currentScreen) + setViewControllers([viewController], animated: false) + } + + private func setupCanceled() { + completionDelegate?.completionNotifyingDidComplete(self) + } + + private func stepFinished() { + if let nextStep = currentScreen.next() { + navigate(to: nextStep) + } else { + completionDelegate?.completionNotifyingDidComplete(self) + } + } + + func navigate(to screen: PrescriptionReviewScreen) { + screenStack.append(screen) + let viewController = viewControllerForScreen(screen) + self.pushViewController(viewController, animated: true) + } +} diff --git a/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryModel.swift b/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryModel.swift new file mode 100644 index 0000000..68ca55e --- /dev/null +++ b/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryModel.swift @@ -0,0 +1,32 @@ +// +// PrescriptionCodeEntryModel.swift +// TidepoolServiceKitUI +// +// Created by Anna Quinlan on 6/22/20. +// Copyright © 2020 LoopKit Authors. All rights reserved. +// + +import Foundation +import TidepoolServiceKit + +class PrescriptionCodeEntryViewModel: ObservableObject { + var didFinish: (() -> Void)? + var didCancel: (() -> Void)? + + var prescription: Prescription? + + func loadPrescriptionFromCode(prescriptionCode: String) { + #if targetEnvironment(simulator) + MockPrescriptionManager().getPrescriptionData { result in + switch result { + case .failure: + fatalError("Mock prescription manager should always return a prescription") + case .success(let prescription): + self.prescription = prescription + } + } + #else + // TODO: add in proper query to backend + #endif + } +} diff --git a/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift b/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift new file mode 100644 index 0000000..3059d77 --- /dev/null +++ b/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift @@ -0,0 +1,48 @@ +// +// PrescriptionCodeEntryModel.swift +// TidepoolServiceKitUI +// +// Created by Anna Quinlan on 6/22/20. +// Copyright © 2020 LoopKit Authors. All rights reserved. +// + +import Foundation +import TidepoolServiceKit + +class PrescriptionCodeEntryViewModel: ObservableObject { + var didFinishStep: (() -> Void) + var didCancel: (() -> Void)? + + var prescription: Prescription? + + init(finishedStepHandler: @escaping () -> Void = { }) { + self.didFinishStep = finishedStepHandler + } + + func entryNavigation(success: Bool) { + if success { + didFinishStep() + } else { + // Handle error + } + } + + func loadPrescriptionFromCode(prescriptionCode: String) { + // TODO: validate prescription code and check if it works; if not, raise invalidCode error + + #if targetEnvironment(simulator) + MockPrescriptionManager().getPrescriptionData { result in + switch result { + case .failure: + fatalError("Mock prescription manager should always return a prescription") + case .success(let prescription): + self.prescription = prescription + self.entryNavigation(success: true) + } + } + #else + // TODO: call function to properly query the backend + // TODO: if prescription couldn't be retrieved from backend, raise unableToRetreivePrescription error + #endif + } +} diff --git a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift new file mode 100644 index 0000000..4abdc3e --- /dev/null +++ b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift @@ -0,0 +1,128 @@ +// +// PrescriptionCodeEntryView.swift +// TidepoolServiceKitUI +// +// Created by Anna Quinlan on 6/18/20. +// Copyright © 2020 Tidepool Project. All rights reserved. +// + +import SwiftUI +import LoopKitUI + +struct CodeEntry: TextFieldStyle { + func _body(configuration: TextField) -> some View { + configuration + .keyboardType(.numberPad) + .font(.body) + .multilineTextAlignment(.leading) + .padding() + } +} + +struct PrescriptionCodeEntryView: View { + + @State private var prescriptionCode: String = "" + @ObservedObject var viewModel: PrescriptionCodeEntryViewModel + + let blueGray = Color(#colorLiteral(red: 0.4156862745, green: 0.4705882353, blue: 0.5529411765, alpha: 1)) + let lightGray = Color(#colorLiteral(red: 0.7019607843, green: 0.6980392157, blue: 0.6980392157, alpha: 1)) + let purple = Color(#colorLiteral(red: 0.3647058824, green: 0.4745098039, blue: 1, alpha: 1)) + + var body: some View { + VStack { + VStack(alignment: .leading, spacing: 25) { + self.itemsNeededDescription + self.itemsNeededList + } + .padding() + VStack(alignment: .leading, spacing: 10) { + self.codeEntryRequest + self.prescriptionCodeInput + } + .padding() + VStack(alignment: .leading, spacing: 15) { + self.submitCodeButton + self.requestPrescriptionButton + } + .padding() + } + + .navigationBarTitle(Text(LocalizedString("Your Settings", comment: "Navigation view title"))) + .navigationBarItems(trailing: cancelButton) + } + + private var cancelButton: some View { + Button(action: { + self.viewModel.didCancel?() + }) { + Text(LocalizedString("Cancel", comment: "Button text to exit the prescription code entry screen")) + .foregroundColor(purple) + } + } + + private var itemsNeededDescription: some View { + VStack (alignment: .leading, spacing: 10) { + Text(LocalizedString("What you'll need", comment: "Title for section describing items needed to review settings")) + .font(.headline) + Text(LocalizedString("For the next section, you'll want to have the following:", comment: "Subheader for items-needed section")) + .foregroundColor(blueGray) + .fixedSize(horizontal: false, vertical: true) // prevent text from being cut off + } + + } + + private var itemsNeededList: some View { + InstructionList(instructions: [ + LocalizedString("Prescription activation code", comment: "Label text for the first needed prescription activation item"), + LocalizedString("Configuration settings for glucose targets and insulin delivery from your healthcare provider", comment: "Label text for the second needed prescription activation item") + ], + stepsColor: blueGray + ) + .foregroundColor(blueGray) + } + + private var codeEntryRequest: some View { + VStack (alignment: .leading, spacing: 15) { + Text(LocalizedString("Enter your prescription code", comment: "Title for section to enter your prescription code")) + .font(.headline) + Text(LocalizedString("If you have a prescription activation code, please enter it now.", comment: "Text requesting entry of activation code")) + .foregroundColor(blueGray) + } + + } + + private var prescriptionCodeInput: some View { + TextField(LocalizedString("Activation code", comment: "Placeholder text before entering prescription code in text field"), text: $prescriptionCode) + .textFieldStyle(CodeEntry()) + .overlay( + RoundedRectangle(cornerRadius: 10) + .stroke(lightGray, lineWidth: 1) + ) + } + + private var submitCodeButton: some View { + Button(action: { + self.viewModel.loadPrescriptionFromCode(prescriptionCode: self.prescriptionCode) + }) { + Text(LocalizedString("Submit activation code", comment: "Button title for submitting the prescription activation code to Tidepool")) + .actionButtonStyle(.tidepoolPrimary) + } + } + + private var requestPrescriptionButton: some View { + Button(action: { + // TODO: contact prescriber window + print("TODO") + }) { + Text(LocalizedString("Request activation code", comment:"Button title for requesting a prescription activation code from the prescriber")) + .actionButtonStyle(.tidepoolSecondary) + } + } +} + +struct PrescriptionCodeEntryView_Previews: PreviewProvider { + static var previews: some View { + PrescriptionCodeEntryView(viewModel: PrescriptionCodeEntryViewModel()) + } +} + From a2ce00e5c6c9b3303c1a0bf47ffac267c4f17e77 Mon Sep 17 00:00:00 2001 From: Anna Date: Tue, 23 Jun 2020 09:57:55 -0700 Subject: [PATCH 06/29] Add draft of 2nd screen --- TidepoolService.xcodeproj/project.pbxproj | 4 + ...idepoolServiceSettingsViewController.swift | 5 +- .../TidepoolServiceSetupViewController.swift | 4 +- .../Views/PrescriptionCodeEntryView.swift | 43 +++++--- .../Views/PrescriptionDeviceView.swift | 104 ++++++++++++++++++ 5 files changed, 140 insertions(+), 20 deletions(-) create mode 100644 TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift diff --git a/TidepoolService.xcodeproj/project.pbxproj b/TidepoolService.xcodeproj/project.pbxproj index 1a1b4ba..5f5e293 100644 --- a/TidepoolService.xcodeproj/project.pbxproj +++ b/TidepoolService.xcodeproj/project.pbxproj @@ -63,6 +63,7 @@ A9DAAD6D22E7EA8F00E76C9F /* IdentifiableClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DAAD6C22E7EA8F00E76C9F /* IdentifiableClass.swift */; }; A9DAAD6F22E7EA9700E76C9F /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DAAD6E22E7EA9700E76C9F /* NibLoadable.swift */; }; E93BA05E24A14CBA00C5D7E6 /* PrescriptionCodeEntryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E93BA05D24A14CBA00C5D7E6 /* PrescriptionCodeEntryViewModel.swift */; }; + E93BA06024A15FA800C5D7E6 /* PrescriptionDeviceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E93BA05F24A15FA800C5D7E6 /* PrescriptionDeviceView.swift */; }; E9692172249BC73600D9BE3B /* MockPrescriptionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9692171249BC73600D9BE3B /* MockPrescriptionManager.swift */; }; E9692174249BF2A100D9BE3B /* Prescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9692173249BF2A100D9BE3B /* Prescription.swift */; }; E9692176249C1AE700D9BE3B /* PrescriptionReviewUICoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9692175249C1AE700D9BE3B /* PrescriptionReviewUICoordinator.swift */; }; @@ -174,6 +175,7 @@ A9DAAD6C22E7EA8F00E76C9F /* IdentifiableClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentifiableClass.swift; sourceTree = ""; }; A9DAAD6E22E7EA9700E76C9F /* NibLoadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NibLoadable.swift; sourceTree = ""; }; E93BA05D24A14CBA00C5D7E6 /* PrescriptionCodeEntryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrescriptionCodeEntryViewModel.swift; sourceTree = ""; }; + E93BA05F24A15FA800C5D7E6 /* PrescriptionDeviceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrescriptionDeviceView.swift; sourceTree = ""; }; E9692171249BC73600D9BE3B /* MockPrescriptionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPrescriptionManager.swift; sourceTree = ""; }; E9692173249BF2A100D9BE3B /* Prescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Prescription.swift; sourceTree = ""; }; E9692175249C1AE700D9BE3B /* PrescriptionReviewUICoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrescriptionReviewUICoordinator.swift; sourceTree = ""; }; @@ -402,6 +404,7 @@ isa = PBXGroup; children = ( E96AEB97249C2FF1003797B4 /* PrescriptionCodeEntryView.swift */, + E93BA05F24A15FA800C5D7E6 /* PrescriptionDeviceView.swift */, ); path = Views; sourceTree = ""; @@ -767,6 +770,7 @@ A94AE4F8235A909A005CA320 /* UIColor.swift in Sources */, E96AEB98249C2FF1003797B4 /* PrescriptionCodeEntryView.swift in Sources */, A97651762421AA11002EB5D4 /* OSLog.swift in Sources */, + E93BA06024A15FA800C5D7E6 /* PrescriptionDeviceView.swift in Sources */, E9692176249C1AE700D9BE3B /* PrescriptionReviewUICoordinator.swift in Sources */, A9DAAD3422E7CA1A00E76C9F /* LocalizedString.swift in Sources */, A9DAAD3922E7DEE000E76C9F /* TidepoolService+UI.swift in Sources */, diff --git a/TidepoolServiceKitUI/TidepoolServiceSettingsViewController.swift b/TidepoolServiceKitUI/TidepoolServiceSettingsViewController.swift index 4a89fe2..e306f9f 100644 --- a/TidepoolServiceKitUI/TidepoolServiceSettingsViewController.swift +++ b/TidepoolServiceKitUI/TidepoolServiceSettingsViewController.swift @@ -32,8 +32,11 @@ final class TidepoolServiceSettingsViewController: UITableViewController { } @objc private func done() { + // ANNA TODO: revert + let setupViewController = PrescriptionReviewUICoordinator() + self.present(setupViewController, animated: true, completion: nil) service.completeUpdate() - notifyComplete() + //notifyComplete() } private func confirmDeletion(completion: (() -> Void)? = nil) { diff --git a/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift b/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift index 2aa4931..fc1f5eb 100644 --- a/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift +++ b/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift @@ -72,7 +72,9 @@ final class TidepoolServiceSetupViewController: UIViewController, TLoginSignupDe private func notifyComplete() { if let serviceViewController = navigationController as? ServiceViewController { - serviceViewController.notifyComplete() + onboardIfNeeded() + // ANNA TODO: revert, this will make the screen be dismissed + //serviceViewController.notifyComplete() } } } diff --git a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift index 4abdc3e..4df8432 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift @@ -19,7 +19,7 @@ struct CodeEntry: TextFieldStyle { } } -struct PrescriptionCodeEntryView: View { +struct PrescriptionCodeEntryView: View, HorizontalSizeClassOverride { @State private var prescriptionCode: String = "" @ObservedObject var viewModel: PrescriptionCodeEntryViewModel @@ -29,26 +29,33 @@ struct PrescriptionCodeEntryView: View { let purple = Color(#colorLiteral(red: 0.3647058824, green: 0.4745098039, blue: 1, alpha: 1)) var body: some View { - VStack { - VStack(alignment: .leading, spacing: 25) { - self.itemsNeededDescription - self.itemsNeededList + NavigationView { + VStack { + VStack(alignment: .leading, spacing: 25) { + self.itemsNeededDescription + self.itemsNeededList + } + .padding() + VStack(alignment: .leading, spacing: 10) { + self.codeEntryRequest + self.prescriptionCodeInput + } + .padding() + VStack(alignment: .leading, spacing: 15) { + self.submitCodeButton + self.requestPrescriptionButton + } + .padding() } - .padding() - VStack(alignment: .leading, spacing: 10) { - self.codeEntryRequest - self.prescriptionCodeInput - } - .padding() - VStack(alignment: .leading, spacing: 15) { - self.submitCodeButton - self.requestPrescriptionButton - } - .padding() + .listStyle(GroupedListStyle()) + .navigationBarBackButtonHidden(false) + .navigationBarHidden(false) + .navigationBarItems(trailing: cancelButton) + .navigationBarTitle(Text(LocalizedString("Your Settings", comment: "Navigation view title"))) + .environment(\.horizontalSizeClass, horizontalOverride) } - .navigationBarTitle(Text(LocalizedString("Your Settings", comment: "Navigation view title"))) - .navigationBarItems(trailing: cancelButton) + } private var cancelButton: some View { diff --git a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift new file mode 100644 index 0000000..1423f1b --- /dev/null +++ b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift @@ -0,0 +1,104 @@ +// +// PrescriptionDeviceView.swift +// TidepoolServiceKitUI +// +// Created by Anna Quinlan on 6/22/20. +// Copyright © 2020 LoopKit Authors. All rights reserved. +// + +import Foundation +import SwiftUI +import LoopKitUI + +struct PrescriptionDeviceView: View { + @State private var prescriptionCode: String = "" + @ObservedObject var viewModel: PrescriptionCodeEntryViewModel + + let blueGray = Color(#colorLiteral(red: 0.4156862745, green: 0.4705882353, blue: 0.5529411765, alpha: 1)) + let lightGray = Color(#colorLiteral(red: 0.7019607843, green: 0.6980392157, blue: 0.6980392157, alpha: 1)) + let purple = Color(#colorLiteral(red: 0.3647058824, green: 0.4745098039, blue: 1, alpha: 1)) + + var body: some View { + NavigationView { + VStack { + VStack(alignment: .leading, spacing: 20) { + self.prescribedDeviceInfo + } + .padding() + VStack(alignment: .leading, spacing: 10) { + self.itemsNeededList + //self.prescribedDevices + } + .padding() + VStack(alignment: .leading, spacing: 15) { + self.approveDevicesButton + self.editDevicesButton + } + .padding() + } + .listStyle(GroupedListStyle()) + .navigationBarBackButtonHidden(false) + .navigationBarTitle(Text(LocalizedString("Review your settings", comment: "Navigation view title"))) + .navigationBarItems(trailing: cancelButton) + } + } + + private var cancelButton: some View { + Button(action: { + self.viewModel.didCancel?() + }) { + Text(LocalizedString("Cancel", comment: "Button text to exit the device review screen")) + .foregroundColor(purple) + } + } + + private var prescribedDeviceInfo: some View { + VStack (alignment: .leading, spacing: 10) { + Text(LocalizedString("Since your provider included your recommended settings with your prescription, you'll have the chance to review and accept each of these settings now.", comment: "Text describing purpose of settings walk-through")) + //.fixedSize(horizontal: false, vertical: true) // prevent text from being cut off + Text(LocalizedString("Your prescription contains recommended settings for the following devices:", comment: "Title for devices prescribed section")) + + .fixedSize(horizontal: false, vertical: true) // prevent text from being cut off + } + .foregroundColor(blueGray) + } + + private var itemsNeededList: some View { + InstructionList(instructions: [ + LocalizedString("Prescription activation code", comment: "Label text for the first needed prescription activation item"), + LocalizedString("Configuration settings for glucose targets and insulin delivery from your healthcare provider", comment: "Label text for the second needed prescription activation item") + ], + stepsColor: blueGray + ) + .foregroundColor(blueGray) + } + + private var codeEntryRequest: some View { + VStack (alignment: .leading, spacing: 15) { + Text(LocalizedString("Enter your prescription code", comment: "Title for section to enter your prescription code")) + .font(.headline) + Text(LocalizedString("If you have a prescription activation code, please enter it now.", comment: "Text requesting entry of activation code")) + .foregroundColor(blueGray) + } + + } + + private var approveDevicesButton: some View { + Button(action: { + self.viewModel.loadPrescriptionFromCode(prescriptionCode: self.prescriptionCode) + }) { + Text(LocalizedString("Next: review your settings", comment: "Button title for approving devices")) + .actionButtonStyle(.tidepoolPrimary) + } + } + + private var editDevicesButton: some View { + Button(action: { + // TODO: contact prescriber window + print("TODO") + }) { + Text(LocalizedString("Edit devices", comment:"Button title for editing the prescribed devices")) + .actionButtonStyle(.tidepoolSecondary) + } + } +} From 4b3fe92a2b887cd80cf16a14fde1beaf2f4c68e4 Mon Sep 17 00:00:00 2001 From: Anna Date: Wed, 24 Jun 2020 07:53:02 -0700 Subject: [PATCH 07/29] LOOP-1269: complete first 2 screens --- TidepoolService.xcodeproj/project.pbxproj | 6 + .../Assets.xcassets/Contents.json | 6 + .../dash.imageset/Contents.json | 21 +++ .../Assets.xcassets/dash.imageset/dash.pdf | Bin 0 -> 5537 bytes .../dexcom.imageset/Contents.json | 21 +++ .../dexcom.imageset/dexcom.pdf | Bin 0 -> 4878 bytes .../PrescriptionReviewUICoordinator.swift | 9 +- .../Views/PrescriptionCodeEntryView.swift | 92 ++++++----- .../Views/PrescriptionDeviceView.swift | 148 ++++++++++++------ 9 files changed, 218 insertions(+), 85 deletions(-) create mode 100644 TidepoolServiceKitUI/Assets.xcassets/Contents.json create mode 100644 TidepoolServiceKitUI/Assets.xcassets/dash.imageset/Contents.json create mode 100644 TidepoolServiceKitUI/Assets.xcassets/dash.imageset/dash.pdf create mode 100644 TidepoolServiceKitUI/Assets.xcassets/dexcom.imageset/Contents.json create mode 100644 TidepoolServiceKitUI/Assets.xcassets/dexcom.imageset/dexcom.pdf diff --git a/TidepoolService.xcodeproj/project.pbxproj b/TidepoolService.xcodeproj/project.pbxproj index 5f5e293..974d9d1 100644 --- a/TidepoolService.xcodeproj/project.pbxproj +++ b/TidepoolService.xcodeproj/project.pbxproj @@ -64,6 +64,7 @@ A9DAAD6F22E7EA9700E76C9F /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DAAD6E22E7EA9700E76C9F /* NibLoadable.swift */; }; E93BA05E24A14CBA00C5D7E6 /* PrescriptionCodeEntryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E93BA05D24A14CBA00C5D7E6 /* PrescriptionCodeEntryViewModel.swift */; }; E93BA06024A15FA800C5D7E6 /* PrescriptionDeviceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E93BA05F24A15FA800C5D7E6 /* PrescriptionDeviceView.swift */; }; + E93BA06224A29C9C00C5D7E6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E93BA06124A29C9C00C5D7E6 /* Assets.xcassets */; }; E9692172249BC73600D9BE3B /* MockPrescriptionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9692171249BC73600D9BE3B /* MockPrescriptionManager.swift */; }; E9692174249BF2A100D9BE3B /* Prescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9692173249BF2A100D9BE3B /* Prescription.swift */; }; E9692176249C1AE700D9BE3B /* PrescriptionReviewUICoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9692175249C1AE700D9BE3B /* PrescriptionReviewUICoordinator.swift */; }; @@ -176,6 +177,7 @@ A9DAAD6E22E7EA9700E76C9F /* NibLoadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NibLoadable.swift; sourceTree = ""; }; E93BA05D24A14CBA00C5D7E6 /* PrescriptionCodeEntryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrescriptionCodeEntryViewModel.swift; sourceTree = ""; }; E93BA05F24A15FA800C5D7E6 /* PrescriptionDeviceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrescriptionDeviceView.swift; sourceTree = ""; }; + E93BA06124A29C9C00C5D7E6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; E9692171249BC73600D9BE3B /* MockPrescriptionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPrescriptionManager.swift; sourceTree = ""; }; E9692173249BF2A100D9BE3B /* Prescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Prescription.swift; sourceTree = ""; }; E9692175249C1AE700D9BE3B /* PrescriptionReviewUICoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrescriptionReviewUICoordinator.swift; sourceTree = ""; }; @@ -349,6 +351,7 @@ A92E770022E9181500591027 /* TidepoolServiceSetupViewController.swift */, A94AE4F6235A907C005CA320 /* Extensions */, A9DAAD4F22E7DFD400E76C9F /* Localizable.strings */, + E93BA06124A29C9C00C5D7E6 /* Assets.xcassets */, ); path = TidepoolServiceKitUI; sourceTree = ""; @@ -628,6 +631,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + E93BA06224A29C9C00C5D7E6 /* Assets.xcassets in Resources */, A9DAAD4D22E7DFD400E76C9F /* Localizable.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1244,6 +1248,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_PREVIEWS = YES; INFOPLIST_FILE = TidepoolServiceKitUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( @@ -1270,6 +1275,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_PREVIEWS = YES; INFOPLIST_FILE = TidepoolServiceKitUI/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/TidepoolServiceKitUI/Assets.xcassets/Contents.json b/TidepoolServiceKitUI/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/TidepoolServiceKitUI/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TidepoolServiceKitUI/Assets.xcassets/dash.imageset/Contents.json b/TidepoolServiceKitUI/Assets.xcassets/dash.imageset/Contents.json new file mode 100644 index 0000000..07a24cb --- /dev/null +++ b/TidepoolServiceKitUI/Assets.xcassets/dash.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "dash.pdf", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TidepoolServiceKitUI/Assets.xcassets/dash.imageset/dash.pdf b/TidepoolServiceKitUI/Assets.xcassets/dash.imageset/dash.pdf new file mode 100644 index 0000000000000000000000000000000000000000..04076939773d5b05d5212779f437dd2b22ea346a GIT binary patch literal 5537 zcmai&cT`i$7RIU46h!Hu1SB9uNFV_sy%!5r1SCKp0jWWHF(62hZYa{E7wJV%danu+ zdQpmW=~4t~@`6|2^}hSZJ1Z+^^39%|ea_5U-){z}si=GdEGR+=Y+Kk~SbepV*4NfS z2?2lrXfta{NlAdvZIr#G^Fsg>FKGjWl&x%?QI7b#Ey5Y4h%!ebQ2=RaN+)MW6vB?u zoiH|DE@EEvywbjOz$jscYK)@VDohR(%9e%&5DF7=mv?>JQ5`Q{SrNQ{5B)Lc7DUM# z6McGgxO#~BkRI|D8mVc%f;o9>-8bTS+hP?)1J^m9k0}He=LK4yPU{78EX7nAWcdql z-sjH>7;t~Q)$K=XR?>c3lG*%&doIwiUqJw)TRx=kW$-#Ms(M~(%(Hl4Q~)v0b<9`v zeQ8o_{U)s^fA%=KdQOdD)@<@iqB93Fw6~t-WhAt0S$k}4*H~rvO#noDQ67?Y{aOjH z^R{L+Q+hb<7M5M_YVfNp{`+haZ!9|;Ka>C{W?)($ccf6GE?TI zV;orVhyFeFu+?cvz*qU#=oHP^s}IVrWZNtCp-A4j(23C7J#vrm`jYKRG>_OUDn@cf zDlcmH30FKb7}FDLWjdEunH}`eDYT8%a8@apUF)fU#dBI-eHzqxQYC%$@LAIK7oj1| zkbUJJ&mz)1>5bIW*y;GH&<>d*t6uEe;uzj+UzQkQDfr=3h6tr~DZ6b3=+x%Lv+IEE% zOjIgaB)woa`m`pti87UGMEcDiG2iCd`w}#~FGLhj`-D`I`YIQQl=?R9#uh^%k@$mt6q>{a`@LsJSxLACDQo|5feSzfk z$;+v^nUJJ7%W4mdUs7H()kIz9p^m-r*v2E%uyCPQ?>FqL$4ec8k|^HCpBmT)Lhe?1 zc*G{({va+B+q zus5d8m1_U+LrZ*aalymBW0crF=Vh;#hC{jUzJJmMw1%UIq-)2A5h^L%hqbMkq-7f8 z8H>pEt73DZo4w@|YZSHJAG)vE|Ck%^9hFRWX~3288%&H1s~%}BdA%S5tE-{gBI5*(R3Vhlvmue-~@IA4}iBkT@ zUX?o-Ke&u*!`@cy^g+sN{@4?L`2G!_FON0GzHM`yb`|#sRwkD;eh-6d+?-x`JLu6> zT#h@=+5x^!>fI--?Y+An-5Y1TowI{!D*k~zt=oZ1yEe?P#=z^BkLz~YKLz#f$H2E- zBH`l;c1vEp#g@rJRoOcy%QfHNl_PQKg2L(FzR7&`aBenfaC93W6*6f|)8f)(8BJYJ zRil~S>ll+A(^>hJFVbP69iGtO!-L*z;3M8vs(8k@IOT(bRD?1lnHaH6*vZ*MViTxl z=N0J5UU?7E*)~K%fS$ppOtF^+}cKHEsKw(dVC(-!0um(U2&3|Xoa1#;;{yx0-C630p#gpmI*xU11E}cc z>C-aFVY4~1Sk_YOV-}fmiVkg0<+5F`%hyYv>u_6ircu!8Ger!6tElJ_Lc_>KW!Uf` zBiF2FY=`coH4PlQ^3i#BOVebe;7TANR^luO`z>-qYAjKA*9RQTG?_D?b5uO@K4<#` z>GmhNVSV*D#HB87ZBvdL52?A`ZTSK$@6@iZYVu0H8P6xzd))lu2!1QEQhCJ0D!}K2 zy{i8XKPAc@`OER}tuv!MbH}qgSWHyxtoN)f{D+C^dN`l}LYfH6pWlusduM>iUxuoU zazeW}nxmWmqJIerXnSXTzZ2lhgtgDi^;f>L{@4-MhK{*2+;FBsU1KN zQO5ycjsl#ywt^EFAoeT4A34PT$nneAVLzjU?&1p)k~=eZd%Uv)gcMP(R^}*e75V?~ z`N=QbbaloVf^hX>^?jE(0l-}rPYS?*gepCWR^ zxe^`KQ$Mp&itUS<9nD&bN(+-=HWZsh(4DEolTL%_@ukYqam!82Xbpkpu^&l`vp(Sy zNV-1V#%iKOC==as&NT`-^Z@oSg}^<>az2D4Sj{Ylb~N5<;Y*jr;a0iMff<-T zruELuGxyuL-HP)OAjT&p=od(8cx;&5WZE>&R zQslx?45n?@3We$!2==%PldhCE#NH#QusbKaFjJ_p2_V=eC8(HsxpOtp);Owd_B;<& z=-QgC6Je^2ApaM!qf|fi`^G@nenR)gk)A{jqm1u0HF^=0(n8LwoLh=LJ?E+8aLAMW zi>Gr7lPC`vvv;x$-9r}> z0!#o*vr3Ju3oS*UaE+MDUdEFMv-;p7n6j&(?qLzO#3@qP^T*O5JC#w0{mgASPMyWQ z^N~nLV1@v5=Cr?UGts5zWX@@n5MMzMk$~w1wkIME#8UtQ|0k*!iC+*4suEerlDw9a z9wVTX1CJ59KDBlr81nV(Bf0A*>Tu48s*@HgrTqeG!`RNLpQoGljS!&4PPY=_``T&q~ zCxjS(+IXTt#Dy_}w@=1PCD_PSTDm_gy3@ZTV{G~~qh7@zOJT zV#G`xG#qapuXmeqQPkq1i0^@4w%Kr5jy_MJj4OpGZBqD+Ha*iw(+8z-WyE8MH3>HA z&L-wrHV5x2CL>{cXfa78&1hKdlhxJ>Gu&J5b$nBFn|?ISPG8L)b2L!;u=!k_j%4p> z8Z$gEyu9`I`oLh>!o+J9*O{9t=lYy+UMln|^zywGWC8HU0otsWWolQx8{4Pwr zTGm6>Lm^xHLazOgetvvSvY|%3B1Rn}q!KL>w-Yz_t|+3 zT0o}XMYNf-!=l4#!y4uZ@ z?eaB-K-S#W2O0+i9rL|IvQnMOW8Xx$)Z|VblG!2zY zYR*j$NpD1?ATnpnujOM4`uQ>qGbK92&EGx>*^-`nz+D=gtQMsf{_c*>*wB=&X$`Id zciv|4AvdG|THBdXI$>Z?s63JeYnAMJoavEtz)vL=svnxnFv(ydur8o;V^x6NfY*Rj z->zb%^sx0~TcQoz9Mc(SJ!f9tn=`rirYP^mv}@x!Mj{bjI>}Ibw-&cYy~laPeFP?x z3UUvsr`{iPcD-Kfj~S+2{W4S4yA?J^#^c3PHmLS$I@zF?xA#JCCe?YyuZ%-Cm>l~b z%~Y{DVg-?cVJ7Yl=|lHE#m*V79`@WB4PSC?vum3ilNvv%I^w6fMT1od?TmR9y~Ki} zGJY0UavyuYq%vyXY}0!4mGn*NVCit_%MGp#1sI|4h!dg{*Tc!3(XUH~qemJf;v|I> zUnsLklu7i+q^X1{E|I#Bv^Enq*ZQ#w4VZ8l4KWbOA1k;9NK!!0!|4y5m;?nNh1_c% zRrlPz-Q7rTm?VDeVb+}bSV+=ift(Tno6kB)B^^15-w&rO;Q9`RjeWxq0g@ih2y zOWwlm;$4$!w*`{X=-Qy8sHG16rSL8IchoVKB+mb}f4^c}8$8%ttyqm-ZH`Do?#*YH z*CyM(^La7PV={R~^7`{WSov3cuNagEB0@^};UZEz^+K(XOprp&d#mA%dzUk0`?0sL zI~iEi#z*|P{Y^EDD+&12fU4a3^^~OwD)qfq5AEXaeB!))_my`$?~c5o7Em`A%LdmU zfyd3z4IApBfRSROPt|wb;LSBN-x^jU?ErSF<6pa#yTehe)Ak`#Lt1HCr46PJ%QqK0 z+-+iVxn@T{!OXyTys!8(ylA0kJ7c@D_WoLf9^%0xQyQyCWMqYK?(2H>5^I&= zS1Va7zATZ6i}fpBmFvkLJ3oy!PTTnm5{47ElSyBtW2^J&+SjxxlCx_^Ow1Iv;#Ml>n9O&X!1*VTqwpv0Skj(U$P2jO#-xRzq7c6UvWLAGC z5$Awy&31lg-%L3#IqngH$zSFff4?GDUxmug@!eq_cYWdBkUH!5{`N@m9qKF1k%+ewJNN8%W z2D1=xn#@WZdhT#Tu)*upctUC!Gj-BWmPcbO)8i?A^6WaCG(r;Hmv5=({`f{(ctl?+wFs#hbz)us9ebEDRC` zL!lyuAP^7!^LOMQHR5*yNEdVbk=)NZ@F#Q%_>(wid;yAQ82>XTUc7@w{`vdw>fIet z7L*_WScDSv-wPm$|4%5u0`NU|3`y}LH-p75f{hLtZY$^ zKhHgNtUU4W55FnWMx*g-=4@TyU!@Y%kN7BRq literal 0 HcmV?d00001 diff --git a/TidepoolServiceKitUI/Assets.xcassets/dexcom.imageset/Contents.json b/TidepoolServiceKitUI/Assets.xcassets/dexcom.imageset/Contents.json new file mode 100644 index 0000000..f989182 --- /dev/null +++ b/TidepoolServiceKitUI/Assets.xcassets/dexcom.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "dexcom.pdf", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TidepoolServiceKitUI/Assets.xcassets/dexcom.imageset/dexcom.pdf b/TidepoolServiceKitUI/Assets.xcassets/dexcom.imageset/dexcom.pdf new file mode 100644 index 0000000000000000000000000000000000000000..81968bc7a42edf10f1a8325f8c4aa69d918aef37 GIT binary patch literal 4878 zcmZvgTW=i45ryC9SM-Yj+kl&WzW{~-TZ)4q38K(>GyFiW)-nxpH|ElTlV6|j^vuqT zDDBhmu&cVRr)uWa+c)2R>1~}RZ=5;*^y_5IS6`X0zn+e#clvV*d;I2a$J6`gUrYqN z)v3$#)A8+kn}9WK*vlecdCjJrO3xjudTzx7{-$I01L_*Eoh zN!1k|YA>!fb2*%*YF&-JcjLQ_F7*=3;}nARwM2x1x8C`ZlZnpu9+HcO|1QSFXmbc2 zof~$TVz(}(p2H4J>q%cF(^4}#Y}I&}HniAOt4GaF;U{jsLR^fA+576ust@~HkFE*o zq2iS(Hm2HQG0E13`ewM)mR^hVV4eSQJaWoA7dY-~_OVGp?5zb;k*hs<!}Cy-T8B@)V){qfK1{z*BQ7K<_p61EQ&OTp!w!Aeq1i^P zZP6sECHoTHR-7?kWw%nC0DX6j;I3fA#*8oN18BK+Jw;}tt&z~|&k>tcdR9GSeedMV z+gxJsT~G_DWugTbA`zCTcPWrkoeQo`sY3On`w?t?E|l zhfX1Do@~V=vILY;ErG<^l+h-G z0lBFr1t8W^9S_-tfD3**2SmANQs`~*9#bSr(?l9xrgZKg!azAkS5?8Gh0249nwWDz zu(A2xi$tR>EkGfFG<$Y99g%2-e~W}j9nR2tlnt%N1-T|)B})Dgm5Vrr6I_8&z4;=8 zmve$#OIf$N-eXkP0ur1wNP#BLvYh-^GN4;)M*wZ4sd8xsY6WIY5+%olB+IcLeIovy z*2fUI(&m|?JL(&p3$l7j3sS7q4FMrtbZ@gcd2S!4YC~nCKWeWckpgp&pu^NC?(W8Y zm0S?W%Ed0gRRpS}MnJ2D*$V9&Z6cPKa+nG#bPOz0A6!F%rAc!1OCl1PD78Ez6gq7n zUZM&L$C1lQiP4~`bdabp6RW72hGEdE7`Z~%sL=*E=w<~U1bPxS=&&?TDkQv+4V6Y? zpm>8;{u%l-TM#82mD$vE&gUqef}VZrel$NfsJtU_8J5qc_(E@P;KMZh$S{_ACnZ;k zI31FJJ4|3q!k>grK_RRpk7+WOzoIEi;S+&`s(OM!#5jr556bZn&D12P4h+~H z;NAh&NZ)tz?4*rbX?v70f|UB)WD!AWUL>j9=hz*Y1}vUfG>Xj;3mP1#LKry%;Y%(| zIGt(~Hliv@i3nV(a+EGn9I~#o9EcA!4vD~+obxd=CJ-LYAWwu+X@hvM3zDNAq>*YB zL8!S(gnGa*v**>(xP}x zj3LbyqoJTlK*ERx+~Nrf45*P29D@oBmQHA-Yl#r7Rj^Wv3j+c@8bKjOywT)=s^UDW zD$OJewkIV>kD*2}6JJnL%gonQ%P~osy96QNjq1%Zg*@7*Hv@HxLf-J1NwuL8HNT;D z1_<0+$KJsFU|&!LUl(c!i4iJRSuwn100l=m?@(fdo~3iI((pf<{@nEohBS1;0>Way z=}hWZ)-3vnEssj36Nmy^HDEY^PM7CUOr$57FE5XV48A5#vro@s}{`>hbtlqC<^Ek)u#nN zWY#O~KXRkszE@GsAT%|DaLR)~Or+WXYhgF1=rWC<@goS57q<{GZE0ZTz~zJf0~Nwb35LInGi4=Y7PhhHD-iHH#LS>zyVW@3Zk9`J>zvf8 zI|8NzXe4k83lkcL+e=y&luhwEGYvX#GHQ^Vj=#m}=y~t-0Rw`usbx{v-P@ zAUjPgo;yvmo3HPl?;g(Y7g0HV`IHfVJRN_Y=#gnI!4U#TgeC`GepKFgGN%=a*s0$N z@#e}$ofi<>zzZltj4vU+1YDu#r@Q-yPAsnAw&X32>&1d)TKA(efb9+8N i8#z1zeDnSN|BhHczFgfsJr8#h3+VLf)t~ Date: Wed, 24 Jun 2020 11:30:30 -0700 Subject: [PATCH 08/29] Fix back button --- ...idepoolServiceSettingsViewController.swift | 4 +++- .../TidepoolServiceSetupViewController.swift | 12 +++++++++- .../PrescriptionReviewUICoordinator.swift | 22 ++++++++++++++++++- .../Views/PrescriptionCodeEntryView.swift | 1 + .../Views/PrescriptionDeviceView.swift | 3 ++- 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/TidepoolServiceKitUI/TidepoolServiceSettingsViewController.swift b/TidepoolServiceKitUI/TidepoolServiceSettingsViewController.swift index e306f9f..3abfb23 100644 --- a/TidepoolServiceKitUI/TidepoolServiceSettingsViewController.swift +++ b/TidepoolServiceKitUI/TidepoolServiceSettingsViewController.swift @@ -31,11 +31,13 @@ final class TidepoolServiceSettingsViewController: UITableViewController { navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(done)) } + let setupViewController = PrescriptionReviewUICoordinator() @objc private func done() { // ANNA TODO: revert - let setupViewController = PrescriptionReviewUICoordinator() + setupViewController.completionDelegate = self self.present(setupViewController, animated: true, completion: nil) service.completeUpdate() + // ANNA TODO: revert once testing is done //notifyComplete() } diff --git a/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift b/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift index fc1f5eb..b15dfaf 100644 --- a/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift +++ b/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift @@ -63,9 +63,10 @@ final class TidepoolServiceSetupViewController: UIViewController, TLoginSignupDe } } + let setupViewController = PrescriptionReviewUICoordinator() private func onboardIfNeeded() { if service.onboardingNeeded { - let setupViewController = PrescriptionReviewUICoordinator() + setupViewController.completionDelegate = self self.present(setupViewController, animated: true, completion: nil) } } @@ -78,3 +79,12 @@ final class TidepoolServiceSetupViewController: UIViewController, TLoginSignupDe } } } + +// ANNA TODO: remove once done with testing +extension TidepoolServiceSetupViewController: CompletionDelegate { + func completionNotifyingDidComplete(_ object: CompletionNotifying) { + if let vc = object as? UIViewController, presentedViewController === vc { + dismiss(animated: true, completion: nil) + } + } +} diff --git a/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift b/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift index 4cec07d..6f3f7c6 100644 --- a/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift +++ b/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift @@ -24,7 +24,7 @@ enum PrescriptionReviewScreen { } } -class PrescriptionReviewUICoordinator: UINavigationController, CompletionNotifying { +class PrescriptionReviewUICoordinator: UINavigationController, CompletionNotifying, UINavigationControllerDelegate { var screenStack = [PrescriptionReviewScreen]() var completionDelegate: CompletionDelegate? @@ -41,6 +41,7 @@ class PrescriptionReviewUICoordinator: UINavigationController, CompletionNotifyi override func viewDidLoad() { super.viewDidLoad() + delegate = self self.navigationBar.prefersLargeTitles = true // ensure nav bar text is displayed correctly } @@ -71,6 +72,16 @@ class PrescriptionReviewUICoordinator: UINavigationController, CompletionNotifyi } } + public func navigationController(_ navigationController: UINavigationController, + willShow viewController: UIViewController, + animated: Bool) { + // Pop the current screen from the stack if we're navigating back + if viewControllers.count < screenStack.count { + // Navigation back + let _ = screenStack.popLast() + } + } + private func determineFirstScreen() -> PrescriptionReviewScreen { return .enterCode } @@ -99,3 +110,12 @@ class PrescriptionReviewUICoordinator: UINavigationController, CompletionNotifyi self.pushViewController(viewController, animated: true) } } + +// ANNA TODO: remove this once done testing +extension TidepoolServiceSettingsViewController: CompletionDelegate { + func completionNotifyingDidComplete(_ object: CompletionNotifying) { + if let vc = object as? UIViewController, presentedViewController === vc { + dismiss(animated: true, completion: nil) + } + } +} diff --git a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift index 787573d..b123264 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift @@ -41,6 +41,7 @@ struct PrescriptionCodeEntryView: View, HorizontalSizeClassOverride { self.requestPrescriptionButton } .padding() + Spacer() } .environment(\.horizontalSizeClass, horizontalOverride) .navigationBarItems(trailing: cancelButton) diff --git a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift index be0956d..c8e2ef3 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift @@ -20,7 +20,7 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { var body: some View { // Option 1 - List { + VStack { VStack(alignment: .leading, spacing: 25) { self.prescribedDeviceInfo self.devicesList @@ -30,6 +30,7 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { VStack(alignment: .leading, spacing: 15) { self.approveDevicesButton self.editDevicesButton + Spacer() } .padding() } From 21081ad1b1385fda7fa06c05584d4a90206b4434 Mon Sep 17 00:00:00 2001 From: Anna Date: Wed, 24 Jun 2020 12:01:45 -0700 Subject: [PATCH 09/29] Fix scrolling issue --- .../Views/PrescriptionCodeEntryView.swift | 16 +++++++-------- .../Views/PrescriptionDeviceView.swift | 20 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift index b123264..c6e7fbc 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift @@ -30,22 +30,22 @@ struct PrescriptionCodeEntryView: View, HorizontalSizeClassOverride { var body: some View { /// option 1 - VStack { + List { VStack(alignment: .leading, spacing: 25) { - self.itemsNeededList - self.codeEntryRequest - } - .padding() - VStack(alignment: .leading, spacing: 15) { - self.submitCodeButton - self.requestPrescriptionButton + itemsNeededList + codeEntryRequest } .padding() + submitCodeButton + requestPrescriptionButton Spacer() } .environment(\.horizontalSizeClass, horizontalOverride) .navigationBarItems(trailing: cancelButton) .navigationBarTitle(Text(LocalizedString("Your Settings", comment: "Navigation view title"))) + .onAppear() { + UITableView.appearance().separatorStyle = .none + } /// option 2 diff --git a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift index c8e2ef3..a90b89b 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift @@ -20,22 +20,22 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { var body: some View { // Option 1 - VStack { + List { VStack(alignment: .leading, spacing: 25) { - self.prescribedDeviceInfo - self.devicesList - self.disclaimer - } - .padding() - VStack(alignment: .leading, spacing: 15) { - self.approveDevicesButton - self.editDevicesButton - Spacer() + prescribedDeviceInfo + devicesList + disclaimer } .padding() + approveDevicesButton + editDevicesButton + Spacer() } .environment(\.horizontalSizeClass, horizontalOverride) .navigationBarTitle(Text(LocalizedString("Review your settings", comment: "Navigation view title"))) + .onAppear() { + UITableView.appearance().separatorStyle = .none + } // Option 2 /*GuidePage(content: { From 6e1f2d26927d59b1b5be0ba2499658c187f99524 Mon Sep 17 00:00:00 2001 From: Anna Date: Wed, 24 Jun 2020 12:10:56 -0700 Subject: [PATCH 10/29] Widen text presentation area --- TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift | 3 ++- TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift index c6e7fbc..e94eb3c 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift @@ -35,11 +35,12 @@ struct PrescriptionCodeEntryView: View, HorizontalSizeClassOverride { itemsNeededList codeEntryRequest } - .padding() + .padding(.vertical) submitCodeButton requestPrescriptionButton Spacer() } + .buttonStyle(BorderlessButtonStyle()) // Fix for button click highlighting the whole cell .environment(\.horizontalSizeClass, horizontalOverride) .navigationBarItems(trailing: cancelButton) .navigationBarTitle(Text(LocalizedString("Your Settings", comment: "Navigation view title"))) diff --git a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift index a90b89b..a9c86e5 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift @@ -26,11 +26,12 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { devicesList disclaimer } - .padding() + .padding(.vertical) approveDevicesButton editDevicesButton Spacer() } + .buttonStyle(BorderlessButtonStyle()) // Fix for button click highlighting the whole cell .environment(\.horizontalSizeClass, horizontalOverride) .navigationBarTitle(Text(LocalizedString("Review your settings", comment: "Navigation view title"))) .onAppear() { From a1332ff5bafa20e995892de065a6d9819959c53a Mon Sep 17 00:00:00 2001 From: Anna Date: Wed, 24 Jun 2020 13:08:00 -0700 Subject: [PATCH 11/29] Use mock prescription manager (for now) & make keyboard have letters --- .../View Models/PrescriptionCodeEntryViewModel.swift | 5 +---- TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift b/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift index 3059d77..62ee4cf 100644 --- a/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift +++ b/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift @@ -29,8 +29,7 @@ class PrescriptionCodeEntryViewModel: ObservableObject { func loadPrescriptionFromCode(prescriptionCode: String) { // TODO: validate prescription code and check if it works; if not, raise invalidCode error - - #if targetEnvironment(simulator) + MockPrescriptionManager().getPrescriptionData { result in switch result { case .failure: @@ -40,9 +39,7 @@ class PrescriptionCodeEntryViewModel: ObservableObject { self.entryNavigation(success: true) } } - #else // TODO: call function to properly query the backend // TODO: if prescription couldn't be retrieved from backend, raise unableToRetreivePrescription error - #endif } } diff --git a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift index e94eb3c..ee841aa 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift @@ -12,7 +12,7 @@ import LoopKitUI struct CodeEntry: TextFieldStyle { func _body(configuration: TextField) -> some View { configuration - .keyboardType(.numberPad) + .keyboardType(.default) .font(.body) .multilineTextAlignment(.leading) .padding() From 94136dd3ede239c13bd9359032fe54d9d538775e Mon Sep 17 00:00:00 2001 From: Anna Date: Wed, 24 Jun 2020 13:11:35 -0700 Subject: [PATCH 12/29] Remove second design from views --- .../Views/PrescriptionCodeEntryView.swift | 24 +------------------ .../Views/PrescriptionDeviceView.swift | 21 +--------------- 2 files changed, 2 insertions(+), 43 deletions(-) diff --git a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift index ee841aa..25427b5 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift @@ -29,7 +29,6 @@ struct PrescriptionCodeEntryView: View, HorizontalSizeClassOverride { let purple = Color(#colorLiteral(red: 0.3647058824, green: 0.4745098039, blue: 1, alpha: 1)) var body: some View { - /// option 1 List { VStack(alignment: .leading, spacing: 25) { itemsNeededList @@ -45,29 +44,8 @@ struct PrescriptionCodeEntryView: View, HorizontalSizeClassOverride { .navigationBarItems(trailing: cancelButton) .navigationBarTitle(Text(LocalizedString("Your Settings", comment: "Navigation view title"))) .onAppear() { - UITableView.appearance().separatorStyle = .none + UITableView.appearance().separatorStyle = .none // Remove lines between sections } - - /// option 2 - - /*GuidePage(content: { - self.itemsNeededList - .padding(.vertical) - self.codeEntryRequest - .padding(.vertical) - - }) { - VStack(alignment: .leading, spacing: 15) { - self.submitCodeButton - self.requestPrescriptionButton - } - .padding() - } - .environment(\.horizontalSizeClass, horizontalOverride) - .navigationBarItems(trailing: cancelButton) - .navigationBarTitle(Text(LocalizedString("Your Settings", comment: "Navigation view title")), displayMode: .large) - */ - } private var cancelButton: some View { diff --git a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift index a9c86e5..ca76727 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift @@ -19,7 +19,6 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { let purple = Color(#colorLiteral(red: 0.3647058824, green: 0.4745098039, blue: 1, alpha: 1)) var body: some View { - // Option 1 List { VStack(alignment: .leading, spacing: 25) { prescribedDeviceInfo @@ -35,26 +34,8 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { .environment(\.horizontalSizeClass, horizontalOverride) .navigationBarTitle(Text(LocalizedString("Review your settings", comment: "Navigation view title"))) .onAppear() { - UITableView.appearance().separatorStyle = .none + UITableView.appearance().separatorStyle = .none // Remove lines between sections } - - // Option 2 - /*GuidePage(content: { - self.prescribedDeviceInfo - .padding(.vertical) - self.devicesList - .padding(.vertical) - self.disclaimer - .padding(.vertical) - }) { - VStack(alignment: .leading, spacing: 15) { - self.approveDevicesButton - self.editDevicesButton - } - .padding() - } - .environment(\.horizontalSizeClass, horizontalOverride) - .navigationBarTitle(Text(LocalizedString("Review your settings", comment: "Navigation view title")))*/ } private var prescribedDeviceInfo: some View { From 17a07f64edf356e80a88fd9c9392628b316519ed Mon Sep 17 00:00:00 2001 From: Anna Date: Wed, 24 Jun 2020 13:26:28 -0700 Subject: [PATCH 13/29] Add colors to assets file --- .../blue gray.colorset/Contents.json | 20 +++++++++++++++++++ .../tidepool indigo.colorset/Contents.json | 20 +++++++++++++++++++ .../Views/PrescriptionCodeEntryView.swift | 8 ++++---- .../Views/PrescriptionDeviceView.swift | 6 +++--- 4 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 TidepoolServiceKitUI/Assets.xcassets/blue gray.colorset/Contents.json create mode 100644 TidepoolServiceKitUI/Assets.xcassets/tidepool indigo.colorset/Contents.json diff --git a/TidepoolServiceKitUI/Assets.xcassets/blue gray.colorset/Contents.json b/TidepoolServiceKitUI/Assets.xcassets/blue gray.colorset/Contents.json new file mode 100644 index 0000000..6ae1fa9 --- /dev/null +++ b/TidepoolServiceKitUI/Assets.xcassets/blue gray.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x8D", + "green" : "0x78", + "red" : "0x6A" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TidepoolServiceKitUI/Assets.xcassets/tidepool indigo.colorset/Contents.json b/TidepoolServiceKitUI/Assets.xcassets/tidepool indigo.colorset/Contents.json new file mode 100644 index 0000000..9fb60e0 --- /dev/null +++ b/TidepoolServiceKitUI/Assets.xcassets/tidepool indigo.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0x79", + "red" : "0x5D" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift index 25427b5..b4c544b 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift @@ -24,9 +24,8 @@ struct PrescriptionCodeEntryView: View, HorizontalSizeClassOverride { @State private var prescriptionCode: String = "" @ObservedObject var viewModel: PrescriptionCodeEntryViewModel - let blueGray = Color(#colorLiteral(red: 0.4156862745, green: 0.4705882353, blue: 0.5529411765, alpha: 1)) - let lightGray = Color(#colorLiteral(red: 0.7019607843, green: 0.6980392157, blue: 0.6980392157, alpha: 1)) - let purple = Color(#colorLiteral(red: 0.3647058824, green: 0.4745098039, blue: 1, alpha: 1)) + let blueGray = Color("blue gray", bundle: Bundle(for: PrescriptionReviewUICoordinator.self)) + let purple = Color("tidepool indigo", bundle: Bundle(for: PrescriptionReviewUICoordinator.self)) var body: some View { List { @@ -101,7 +100,8 @@ struct PrescriptionCodeEntryView: View, HorizontalSizeClassOverride { .textFieldStyle(CodeEntry()) .overlay( RoundedRectangle(cornerRadius: 10) - .stroke(lightGray, lineWidth: 1) + .stroke(Color.gray, lineWidth: 1) + ) } diff --git a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift index ca76727..74d04d3 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift @@ -14,9 +14,8 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { @State private var prescriptionCode: String = "" @ObservedObject var viewModel: PrescriptionCodeEntryViewModel - let blueGray = Color(#colorLiteral(red: 0.4156862745, green: 0.4705882353, blue: 0.5529411765, alpha: 1)) - let lightGray = Color(#colorLiteral(red: 0.7019607843, green: 0.6980392157, blue: 0.6980392157, alpha: 1)) - let purple = Color(#colorLiteral(red: 0.3647058824, green: 0.4745098039, blue: 1, alpha: 1)) + let blueGray = Color("blue gray", bundle: Bundle(for: PrescriptionReviewUICoordinator.self)) + let purple = Color("tidepool indigo", bundle: Bundle(for: PrescriptionReviewUICoordinator.self)) var body: some View { List { @@ -111,6 +110,7 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { VStack (alignment: .leading, spacing: 10) { Text(LocalizedString("Tidepool Loop does NOT automatically adjust or recommend changes to your settings", comment: "Text describing that Tidepool Loop doesn't automatically change settings")) .italic() + .fixedSize(horizontal: false, vertical: true) // prevent text from being cut off .padding(.vertical) Text(LocalizedString("Work with your healthcare provider to find the right settings for you", comment: "Text describing determining settings with your doctor")) .italic() From 74cafcc4cefd71c1b74d1814716939e1c53e3788 Mon Sep 17 00:00:00 2001 From: Anna Date: Wed, 24 Jun 2020 14:27:27 -0700 Subject: [PATCH 14/29] Add temporary correction ranges to prescription --- .../Extensions/Prescription.swift | 17 ++++++++++++++--- .../Mocks/MockPrescriptionManager.swift | 4 +++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/TidepoolServiceKit/Extensions/Prescription.swift b/TidepoolServiceKit/Extensions/Prescription.swift index 25922af..04787fa 100644 --- a/TidepoolServiceKit/Extensions/Prescription.swift +++ b/TidepoolServiceKit/Extensions/Prescription.swift @@ -40,6 +40,8 @@ public struct Prescription { public let maximumBolus: Double public let suspendThreshold: GlucoseThreshold public let insulinModel: InsulinModel + public let preMealTargetRange: DoubleRange + public let workoutTargetRange: DoubleRange public init(datePrescribed: Date, providerName: String, @@ -53,7 +55,9 @@ public struct Prescription { maximumBasalRatePerHour: Double, maximumBolus: Double, suspendThreshold: GlucoseThreshold, - insulinModel: InsulinModel) { + insulinModel: InsulinModel, + preMealTargetRange: DoubleRange, + workoutTargetRange: DoubleRange) { self.datePrescribed = datePrescribed self.providerName = providerName self.cgm = cgmType @@ -67,6 +71,8 @@ public struct Prescription { self.maximumBolus = maximumBolus self.suspendThreshold = suspendThreshold self.insulinModel = insulinModel + self.preMealTargetRange = preMealTargetRange + self.workoutTargetRange = workoutTargetRange } public struct InsulinModel: Codable, Equatable { @@ -108,7 +114,9 @@ extension Prescription: Codable { maximumBasalRatePerHour: try container.decode(Double.self, forKey: .maximumBasalRatePerHour), maximumBolus: try container.decode(Double.self, forKey: .maximumBolus), suspendThreshold: try container.decode(GlucoseThreshold.self, forKey: .suspendThreshold), - insulinModel: try container.decode(InsulinModel.self, forKey: .insulinModel)) + insulinModel: try container.decode(InsulinModel.self, forKey: .insulinModel), + preMealTargetRange: try container.decode(DoubleRange.self, forKey: .preMealTargetRange), + workoutTargetRange: try container.decode(DoubleRange.self, forKey: .workoutTargetRange)) } public func encode(to encoder: Encoder) throws { @@ -126,6 +134,8 @@ extension Prescription: Codable { try container.encode(maximumBolus, forKey: .maximumBolus) try container.encode(suspendThreshold, forKey: .suspendThreshold) try container.encode(insulinModel, forKey: .insulinModel) + try container.encode(preMealTargetRange, forKey: .preMealTargetRange) + try container.encode(workoutTargetRange, forKey: .workoutTargetRange) } private enum CodingKeys: String, CodingKey { @@ -142,6 +152,7 @@ extension Prescription: Codable { case maximumBolus case suspendThreshold case insulinModel - + case preMealTargetRange + case workoutTargetRange } } diff --git a/TidepoolServiceKit/Mocks/MockPrescriptionManager.swift b/TidepoolServiceKit/Mocks/MockPrescriptionManager.swift index 78efa08..09c3872 100644 --- a/TidepoolServiceKit/Mocks/MockPrescriptionManager.swift +++ b/TidepoolServiceKit/Mocks/MockPrescriptionManager.swift @@ -83,7 +83,9 @@ public class MockPrescriptionManager { maximumBasalRatePerHour: 3.0, maximumBolus: 5.0, suspendThreshold: GlucoseThreshold(unit: .milligramsPerDeciliter, value: 70), - insulinModel: Prescription.InsulinModel(modelType: .rapidAdult, actionDuration: .hours(6), peakActivity: .hours(3))) + insulinModel: Prescription.InsulinModel(modelType: .rapidAdult, actionDuration: .hours(6), peakActivity: .hours(3)), + preMealTargetRange: DoubleRange(minValue: 80.0, maxValue: 90.0), + workoutTargetRange: DoubleRange(minValue: 80.0, maxValue: 90.0)) } } From 1067f7cb8523a7e828184802363adea4a781aa7f Mon Sep 17 00:00:00 2001 From: Anna Date: Wed, 24 Jun 2020 14:27:42 -0700 Subject: [PATCH 15/29] Color dexcom icon & move delegates to right file --- .../TidepoolServiceSettingsViewController.swift | 9 +++++++++ .../TidepoolServiceSetupViewController.swift | 1 - .../PrescriptionReviewUICoordinator.swift | 9 --------- .../Views/PrescriptionCodeEntryView.swift | 2 +- TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift | 7 ++++--- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/TidepoolServiceKitUI/TidepoolServiceSettingsViewController.swift b/TidepoolServiceKitUI/TidepoolServiceSettingsViewController.swift index 3abfb23..79a4c4a 100644 --- a/TidepoolServiceKitUI/TidepoolServiceSettingsViewController.swift +++ b/TidepoolServiceKitUI/TidepoolServiceSettingsViewController.swift @@ -133,3 +133,12 @@ fileprivate extension UIAlertController { } } + +extension TidepoolServiceSettingsViewController: CompletionDelegate { + func completionNotifyingDidComplete(_ object: CompletionNotifying) { + if let vc = object as? UIViewController, presentedViewController === vc { + dismiss(animated: true, completion: nil) + } + } +} + diff --git a/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift b/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift index b15dfaf..3d58094 100644 --- a/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift +++ b/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift @@ -80,7 +80,6 @@ final class TidepoolServiceSetupViewController: UIViewController, TLoginSignupDe } } -// ANNA TODO: remove once done with testing extension TidepoolServiceSetupViewController: CompletionDelegate { func completionNotifyingDidComplete(_ object: CompletionNotifying) { if let vc = object as? UIViewController, presentedViewController === vc { diff --git a/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift b/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift index 6f3f7c6..b28d321 100644 --- a/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift +++ b/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift @@ -110,12 +110,3 @@ class PrescriptionReviewUICoordinator: UINavigationController, CompletionNotifyi self.pushViewController(viewController, animated: true) } } - -// ANNA TODO: remove this once done testing -extension TidepoolServiceSettingsViewController: CompletionDelegate { - func completionNotifyingDidComplete(_ object: CompletionNotifying) { - if let vc = object as? UIViewController, presentedViewController === vc { - dismiss(animated: true, completion: nil) - } - } -} diff --git a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift index b4c544b..3466425 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift @@ -116,7 +116,7 @@ struct PrescriptionCodeEntryView: View, HorizontalSizeClassOverride { private var requestPrescriptionButton: some View { Button(action: { - // TODO: contact prescriber window + // TODO: open contact prescriber window print("TODO") }) { Text(LocalizedString("Request activation code", comment:"Button title for requesting a prescription activation code from the prescriber")) diff --git a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift index 74d04d3..9221c8d 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift @@ -51,7 +51,6 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { Text(LocalizedString("Your prescription contains recommended settings for the following devices:", comment: "Title for devices prescribed section")) .foregroundColor(blueGray) .fixedSize(horizontal: false, vertical: true) // prevent text from being cut off - // TODO: support multiple devices pumpStack cgmStack } @@ -75,10 +74,12 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { private var dashIcon: some View { Image("dash", bundle: Bundle(for: PrescriptionReviewUICoordinator.self)) + .renderingMode(.template) .resizable() .aspectRatio(contentMode: ContentMode.fit) .frame(height: 50) - .padding(5) // ANNA TODO: figure out better way to align + .padding(5) // align with Dexcom + .foregroundColor(purple) } private var cgmStack: some View { @@ -133,7 +134,7 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { private var editDevicesButton: some View { Button(action: { - // TODO: contact prescriber window + // TODO: open window to edit the devices print("TODO") }) { Text(LocalizedString("Edit devices", comment:"Button title for editing the prescribed devices")) From f34e32e001a79e8d538a965d1b97100edeaa26f9 Mon Sep 17 00:00:00 2001 From: Anna Date: Thu, 25 Jun 2020 12:03:29 -0700 Subject: [PATCH 16/29] Add button to Tidepool setup controller to trigger flow --- ...idepoolServiceSettingsViewController.swift | 16 +------------- .../TidepoolServiceSetupViewController.swift | 21 +++++++++---------- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/TidepoolServiceKitUI/TidepoolServiceSettingsViewController.swift b/TidepoolServiceKitUI/TidepoolServiceSettingsViewController.swift index 79a4c4a..4a89fe2 100644 --- a/TidepoolServiceKitUI/TidepoolServiceSettingsViewController.swift +++ b/TidepoolServiceKitUI/TidepoolServiceSettingsViewController.swift @@ -31,14 +31,9 @@ final class TidepoolServiceSettingsViewController: UITableViewController { navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(done)) } - let setupViewController = PrescriptionReviewUICoordinator() @objc private func done() { - // ANNA TODO: revert - setupViewController.completionDelegate = self - self.present(setupViewController, animated: true, completion: nil) service.completeUpdate() - // ANNA TODO: revert once testing is done - //notifyComplete() + notifyComplete() } private func confirmDeletion(completion: (() -> Void)? = nil) { @@ -133,12 +128,3 @@ fileprivate extension UIAlertController { } } - -extension TidepoolServiceSettingsViewController: CompletionDelegate { - func completionNotifyingDidComplete(_ object: CompletionNotifying) { - if let vc = object as? UIViewController, presentedViewController === vc { - dismiss(animated: true, completion: nil) - } - } -} - diff --git a/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift b/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift index 3d58094..7323f05 100644 --- a/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift +++ b/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift @@ -32,6 +32,7 @@ final class TidepoolServiceSetupViewController: UIViewController, TLoginSignupDe title = service.localizedTitle navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancel)) + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(startFlow)) var loginSignupViewController = service.tapi.loginSignupViewController() loginSignupViewController.delegate = self @@ -46,6 +47,14 @@ final class TidepoolServiceSetupViewController: UIViewController, TLoginSignupDe @objc private func cancel() { notifyComplete() } + + let setupViewController = PrescriptionReviewUICoordinator() + @objc private func startFlow() { + if service.onboardingNeeded { + setupViewController.completionDelegate = self + self.present(setupViewController, animated: true, completion: nil) + } + } func loginSignup(_ loginSignup: TLoginSignup, didCreateSession session: TSession, completion: @escaping (Error?) -> Void) { service.completeCreate(withSession: session) { error in @@ -62,20 +71,10 @@ final class TidepoolServiceSetupViewController: UIViewController, TLoginSignupDe } } } - - let setupViewController = PrescriptionReviewUICoordinator() - private func onboardIfNeeded() { - if service.onboardingNeeded { - setupViewController.completionDelegate = self - self.present(setupViewController, animated: true, completion: nil) - } - } private func notifyComplete() { if let serviceViewController = navigationController as? ServiceViewController { - onboardIfNeeded() - // ANNA TODO: revert, this will make the screen be dismissed - //serviceViewController.notifyComplete() + serviceViewController.notifyComplete() } } } From 8cd0b687d0363f62d85a1782bcd2a4d1448632c0 Mon Sep 17 00:00:00 2001 From: Anna Date: Thu, 25 Jun 2020 12:25:46 -0700 Subject: [PATCH 17/29] Use device specified in prescription --- .../Extensions/Prescription.swift | 1 - .../Views/PrescriptionDeviceView.swift | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/TidepoolServiceKit/Extensions/Prescription.swift b/TidepoolServiceKit/Extensions/Prescription.swift index 04787fa..0364a5d 100644 --- a/TidepoolServiceKit/Extensions/Prescription.swift +++ b/TidepoolServiceKit/Extensions/Prescription.swift @@ -17,7 +17,6 @@ public enum CGMType: String, Codable { public enum PumpType: String, Codable { case dash - case coastal } public enum TrainingType: String, Codable { diff --git a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift index 9221c8d..156bc73 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift @@ -58,6 +58,15 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { } private var pumpStack: some View { + switch self.viewModel.prescription?.pump { + case .dash: + return dashStack + case .none: + return dashStack // TODO: ask design about a default 'empty' pump card + } + } + + private var dashStack: some View { HStack { dashIcon .padding(.horizontal) @@ -83,6 +92,15 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { } private var cgmStack: some View { + switch self.viewModel.prescription?.cgm { + case .g6: + return dexcomStack + case .none: + return dexcomStack // TODO: ask design about a default 'empty' cgm card + } + } + + private var dexcomStack: some View { HStack { dexcomIcon .padding(.horizontal) From 0aecd3d6c7ce9575c2e44d70c4360b39b7dd8887 Mon Sep 17 00:00:00 2001 From: Anna Date: Thu, 25 Jun 2020 12:43:41 -0700 Subject: [PATCH 18/29] Enforce a prescription code length --- .../PrescriptionCodeEntryViewModel.swift | 13 ++++++++++--- .../Views/PrescriptionCodeEntryView.swift | 10 +++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift b/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift index 62ee4cf..b357786 100644 --- a/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift +++ b/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift @@ -14,6 +14,7 @@ class PrescriptionCodeEntryViewModel: ObservableObject { var didCancel: (() -> Void)? var prescription: Prescription? + let prescriptionCodeLength = 4 init(finishedStepHandler: @escaping () -> Void = { }) { self.didFinishStep = finishedStepHandler @@ -27,9 +28,17 @@ class PrescriptionCodeEntryViewModel: ObservableObject { } } + func validateCode(prescriptionCode: String) -> Bool { + return prescriptionCode.count == prescriptionCodeLength + } + func loadPrescriptionFromCode(prescriptionCode: String) { - // TODO: validate prescription code and check if it works; if not, raise invalidCode error + guard validateCode(prescriptionCode: prescriptionCode) else { + // TODO: handle error + return + } + // TODO: call function to properly query the backend; if prescription couldn't be retrieved, raise unableToRetreivePrescription error MockPrescriptionManager().getPrescriptionData { result in switch result { case .failure: @@ -39,7 +48,5 @@ class PrescriptionCodeEntryViewModel: ObservableObject { self.entryNavigation(success: true) } } - // TODO: call function to properly query the backend - // TODO: if prescription couldn't be retrieved from backend, raise unableToRetreivePrescription error } } diff --git a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift index 3466425..a269949 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift @@ -110,9 +110,17 @@ struct PrescriptionCodeEntryView: View, HorizontalSizeClassOverride { self.viewModel.loadPrescriptionFromCode(prescriptionCode: self.prescriptionCode) }) { Text(LocalizedString("Submit activation code", comment: "Button title for submitting the prescription activation code to Tidepool")) - .actionButtonStyle(.tidepoolPrimary) + .actionButtonStyle(submitButtonStyle(enabled: prescriptionCode.count == self.viewModel.prescriptionCodeLength)) + .disabled(prescriptionCode.count != viewModel.prescriptionCodeLength) } } + + private func submitButtonStyle(enabled: Bool) -> ActionButton.ButtonType { + if enabled { + return .tidepoolPrimary + } + return .primary + } private var requestPrescriptionButton: some View { Button(action: { From 0f9faaaaed69b236dd6268caf371b2a1ea75c724 Mon Sep 17 00:00:00 2001 From: Anna Date: Thu, 25 Jun 2020 13:29:43 -0700 Subject: [PATCH 19/29] Change copyright -> Tidepool Project --- TidepoolServiceKit/Extensions/Prescription.swift | 2 +- TidepoolServiceKit/Mocks/MockPrescriptionManager.swift | 2 +- .../View Controllers/PrescriptionReviewUICoordinator.swift | 2 +- .../View Models/PrescriptionCodeEntryViewModel.swift | 2 +- TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/TidepoolServiceKit/Extensions/Prescription.swift b/TidepoolServiceKit/Extensions/Prescription.swift index 0364a5d..6d05267 100644 --- a/TidepoolServiceKit/Extensions/Prescription.swift +++ b/TidepoolServiceKit/Extensions/Prescription.swift @@ -3,7 +3,7 @@ // TidepoolServiceKit // // Created by Anna Quinlan on 6/18/20. -// Copyright © 2020 LoopKit Authors. All rights reserved. +// Copyright © 2020 Tidepool Project. All rights reserved. // import Foundation diff --git a/TidepoolServiceKit/Mocks/MockPrescriptionManager.swift b/TidepoolServiceKit/Mocks/MockPrescriptionManager.swift index 09c3872..b9b9eea 100644 --- a/TidepoolServiceKit/Mocks/MockPrescriptionManager.swift +++ b/TidepoolServiceKit/Mocks/MockPrescriptionManager.swift @@ -3,7 +3,7 @@ // TidepoolServiceKit // // Created by Anna Quinlan on 6/18/20. -// Copyright © 2020 LoopKit Authors. All rights reserved. +// Copyright © 2020 Tidepool Project. All rights reserved. // import Foundation diff --git a/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift b/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift index b28d321..8cd1645 100644 --- a/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift +++ b/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift @@ -3,7 +3,7 @@ // TidepoolServiceKitUI // // Created by Anna Quinlan on 6/18/20. -// Copyright © 2020 LoopKit Authors. All rights reserved. +// Copyright © 2020 Tidepool Project. All rights reserved. // import Foundation diff --git a/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift b/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift index b357786..0e4cb23 100644 --- a/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift +++ b/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift @@ -3,7 +3,7 @@ // TidepoolServiceKitUI // // Created by Anna Quinlan on 6/22/20. -// Copyright © 2020 LoopKit Authors. All rights reserved. +// Copyright © 2020 Tidepool Project. All rights reserved. // import Foundation diff --git a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift index 156bc73..b4cf163 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift @@ -3,7 +3,7 @@ // TidepoolServiceKitUI // // Created by Anna Quinlan on 6/22/20. -// Copyright © 2020 LoopKit Authors. All rights reserved. +// Copyright © 2020 Tidepool Project. All rights reserved. // import Foundation From 30b176fb92b3508f3156ffbcc710e473b820f45b Mon Sep 17 00:00:00 2001 From: Anna Date: Thu, 25 Jun 2020 14:11:37 -0700 Subject: [PATCH 20/29] Fix keyboard obstructing text field --- TidepoolService.xcodeproj/project.pbxproj | 4 ++ .../Views/AdaptiveKeyboardPadding.swift | 37 +++++++++++++++++++ .../Views/PrescriptionCodeEntryView.swift | 2 + 3 files changed, 43 insertions(+) create mode 100644 TidepoolServiceKitUI/Views/AdaptiveKeyboardPadding.swift diff --git a/TidepoolService.xcodeproj/project.pbxproj b/TidepoolService.xcodeproj/project.pbxproj index 974d9d1..e191483 100644 --- a/TidepoolService.xcodeproj/project.pbxproj +++ b/TidepoolService.xcodeproj/project.pbxproj @@ -69,6 +69,7 @@ E9692174249BF2A100D9BE3B /* Prescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9692173249BF2A100D9BE3B /* Prescription.swift */; }; E9692176249C1AE700D9BE3B /* PrescriptionReviewUICoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9692175249C1AE700D9BE3B /* PrescriptionReviewUICoordinator.swift */; }; E96AEB98249C2FF1003797B4 /* PrescriptionCodeEntryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E96AEB97249C2FF1003797B4 /* PrescriptionCodeEntryView.swift */; }; + E991F1B724A548580059281B /* AdaptiveKeyboardPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = E991F1B624A548580059281B /* AdaptiveKeyboardPadding.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -182,6 +183,7 @@ E9692173249BF2A100D9BE3B /* Prescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Prescription.swift; sourceTree = ""; }; E9692175249C1AE700D9BE3B /* PrescriptionReviewUICoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrescriptionReviewUICoordinator.swift; sourceTree = ""; }; E96AEB97249C2FF1003797B4 /* PrescriptionCodeEntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrescriptionCodeEntryView.swift; sourceTree = ""; }; + E991F1B624A548580059281B /* AdaptiveKeyboardPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveKeyboardPadding.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -408,6 +410,7 @@ children = ( E96AEB97249C2FF1003797B4 /* PrescriptionCodeEntryView.swift */, E93BA05F24A15FA800C5D7E6 /* PrescriptionDeviceView.swift */, + E991F1B624A548580059281B /* AdaptiveKeyboardPadding.swift */, ); path = Views; sourceTree = ""; @@ -772,6 +775,7 @@ files = ( A9DAAD6D22E7EA8F00E76C9F /* IdentifiableClass.swift in Sources */, A94AE4F8235A909A005CA320 /* UIColor.swift in Sources */, + E991F1B724A548580059281B /* AdaptiveKeyboardPadding.swift in Sources */, E96AEB98249C2FF1003797B4 /* PrescriptionCodeEntryView.swift in Sources */, A97651762421AA11002EB5D4 /* OSLog.swift in Sources */, E93BA06024A15FA800C5D7E6 /* PrescriptionDeviceView.swift in Sources */, diff --git a/TidepoolServiceKitUI/Views/AdaptiveKeyboardPadding.swift b/TidepoolServiceKitUI/Views/AdaptiveKeyboardPadding.swift new file mode 100644 index 0000000..3ac28a4 --- /dev/null +++ b/TidepoolServiceKitUI/Views/AdaptiveKeyboardPadding.swift @@ -0,0 +1,37 @@ +// +// AdaptiveKeyboardPadding.swift +// TidepoolServiceKitUI +// +// Created by Anna Quinlan on 6/25/20. +// Copyright © 2020 Tidepool Project. All rights reserved. +// +import Combine +import SwiftUI + +public struct AdaptiveKeyboardPadding: ViewModifier { + @State private var keyboardHeight: CGFloat = 0 + + private var keyboardHeightPublisher: AnyPublisher { + Publishers.Merge( + NotificationCenter.default + .publisher(for: UIResponder.keyboardWillShowNotification) + .compactMap { $0.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue } + .map { $0.cgRectValue.height }, + NotificationCenter.default + .publisher(for: UIResponder.keyboardWillHideNotification) + .map { _ in CGFloat(0) } + ).eraseToAnyPublisher() + } + + public func body(content: Content) -> some View { + content + .padding(.bottom, keyboardHeight) + .onReceive(keyboardHeightPublisher) { self.keyboardHeight = $0 } + } +} + +extension View { + func adaptiveKeyboardPadding() -> some View { + ModifiedContent(content: self, modifier: AdaptiveKeyboardPadding()) + } +} diff --git a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift index a269949..b6cddb4 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift @@ -13,6 +13,7 @@ struct CodeEntry: TextFieldStyle { func _body(configuration: TextField) -> some View { configuration .keyboardType(.default) + .disableAutocorrection(true) .font(.body) .multilineTextAlignment(.leading) .padding() @@ -38,6 +39,7 @@ struct PrescriptionCodeEntryView: View, HorizontalSizeClassOverride { requestPrescriptionButton Spacer() } + .adaptiveKeyboardPadding() // To ensure the keyboard doesn't obstruct the TextField .buttonStyle(BorderlessButtonStyle()) // Fix for button click highlighting the whole cell .environment(\.horizontalSizeClass, horizontalOverride) .navigationBarItems(trailing: cancelButton) From 98b51101b3ccb7d73d9f2466466ee926f4494af7 Mon Sep 17 00:00:00 2001 From: Anna Date: Thu, 25 Jun 2020 14:37:30 -0700 Subject: [PATCH 21/29] Fix keyboard swiping --- .../PrescriptionReviewUICoordinator.swift | 2 +- .../Views/PrescriptionCodeEntryView.swift | 26 +++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift b/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift index 8cd1645..babb3f4 100644 --- a/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift +++ b/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift @@ -59,7 +59,7 @@ class PrescriptionReviewUICoordinator: UINavigationController, CompletionNotifyi self?.stepFinished() } let view = PrescriptionCodeEntryView(viewModel: viewModel) - return DismissibleHostingController(rootView: view) + return UIHostingController(rootView: view) case .reviewDevices: viewModel.didCancel = { [weak self] in self?.setupCanceled() diff --git a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift index b6cddb4..a107db4 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift @@ -9,16 +9,16 @@ import SwiftUI import LoopKitUI -struct CodeEntry: TextFieldStyle { - func _body(configuration: TextField) -> some View { - configuration - .keyboardType(.default) - .disableAutocorrection(true) - .font(.body) - .multilineTextAlignment(.leading) - .padding() - } -} +//struct CodeEntry: TextFieldStyle { +// func _body(configuration: TextField) -> some View { +// configuration +// .keyboardType(.default) +// .disableAutocorrection(true) +// .font(.body) +// .multilineTextAlignment(.leading) +// .padding() +// } +//} struct PrescriptionCodeEntryView: View, HorizontalSizeClassOverride { @@ -99,7 +99,11 @@ struct PrescriptionCodeEntryView: View, HorizontalSizeClassOverride { private var prescriptionCodeInputField: some View { TextField(LocalizedString("Activation code", comment: "Placeholder text before entering prescription code in text field"), text: $prescriptionCode) - .textFieldStyle(CodeEntry()) + .keyboardType(.default) + .disableAutocorrection(true) + .font(.body) + .multilineTextAlignment(.leading) + .padding() .overlay( RoundedRectangle(cornerRadius: 10) .stroke(Color.gray, lineWidth: 1) From 19a4734bd5b5d20c7a0e73643e200cb44a867e3b Mon Sep 17 00:00:00 2001 From: Anna Date: Thu, 25 Jun 2020 15:32:23 -0700 Subject: [PATCH 22/29] Fix animation issue and use return to dismiss --- .../View Controllers/PrescriptionReviewUICoordinator.swift | 2 +- TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift b/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift index babb3f4..8cd1645 100644 --- a/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift +++ b/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift @@ -59,7 +59,7 @@ class PrescriptionReviewUICoordinator: UINavigationController, CompletionNotifyi self?.stepFinished() } let view = PrescriptionCodeEntryView(viewModel: viewModel) - return UIHostingController(rootView: view) + return DismissibleHostingController(rootView: view) case .reviewDevices: viewModel.didCancel = { [weak self] in self?.setupCanceled() diff --git a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift index a107db4..290a1d4 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift @@ -107,7 +107,7 @@ struct PrescriptionCodeEntryView: View, HorizontalSizeClassOverride { .overlay( RoundedRectangle(cornerRadius: 10) .stroke(Color.gray, lineWidth: 1) - + ) } From 1fec6b3c828d6832859ca1c6d2ff97b44a440a80 Mon Sep 17 00:00:00 2001 From: Anna Date: Thu, 25 Jun 2020 15:40:43 -0700 Subject: [PATCH 23/29] Remove onboardingNeeded --- TidepoolServiceKit/TidepoolService.swift | 5 ----- .../TidepoolServiceSetupViewController.swift | 6 ++---- .../View Controllers/PrescriptionReviewUICoordinator.swift | 3 --- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index 2bf79c3..a6692d5 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -31,8 +31,6 @@ public final class TidepoolService: Service { public lazy var sessionStorage: SessionStorage? = KeychainManager() public let tapi = TAPI() - - public let onboardingNeeded: Bool public private (set) var error: Error? @@ -61,7 +59,6 @@ public final class TidepoolService: Service { public init() { self.id = UUID().uuidString - self.onboardingNeeded = true } public init?(rawState: RawStateValue) { @@ -71,7 +68,6 @@ public final class TidepoolService: Service { do { self.id = id self.dataSetId = rawState["dataSetId"] as? String - self.onboardingNeeded = rawState["onboardingNeeded"] as? Bool ?? false self.session = try sessionStorage?.getSession(for: sessionService) } catch let error { self.error = error @@ -82,7 +78,6 @@ public final class TidepoolService: Service { var rawValue: RawStateValue = [:] rawValue["id"] = id rawValue["dataSetId"] = dataSetId - rawValue["onboardingNeeded"] = onboardingNeeded return rawValue } diff --git a/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift b/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift index 7323f05..dc575e4 100644 --- a/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift +++ b/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift @@ -50,10 +50,8 @@ final class TidepoolServiceSetupViewController: UIViewController, TLoginSignupDe let setupViewController = PrescriptionReviewUICoordinator() @objc private func startFlow() { - if service.onboardingNeeded { - setupViewController.completionDelegate = self - self.present(setupViewController, animated: true, completion: nil) - } + setupViewController.completionDelegate = self + self.present(setupViewController, animated: true, completion: nil) } func loginSignup(_ loginSignup: TLoginSignup, didCreateSession session: TSession, completion: @escaping (Error?) -> Void) { diff --git a/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift b/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift index 8cd1645..45eccd3 100644 --- a/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift +++ b/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift @@ -61,9 +61,6 @@ class PrescriptionReviewUICoordinator: UINavigationController, CompletionNotifyi let view = PrescriptionCodeEntryView(viewModel: viewModel) return DismissibleHostingController(rootView: view) case .reviewDevices: - viewModel.didCancel = { [weak self] in - self?.setupCanceled() - } viewModel.didFinishStep = { [weak self] in self?.stepFinished() } From 5bd36171aa4eb417428c7c5c81bdb4fcb1fb61cd Mon Sep 17 00:00:00 2001 From: Anna Date: Thu, 25 Jun 2020 15:48:59 -0700 Subject: [PATCH 24/29] Remove commented out code --- .../Views/PrescriptionCodeEntryView.swift | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift index 290a1d4..78c5518 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift @@ -9,17 +9,6 @@ import SwiftUI import LoopKitUI -//struct CodeEntry: TextFieldStyle { -// func _body(configuration: TextField) -> some View { -// configuration -// .keyboardType(.default) -// .disableAutocorrection(true) -// .font(.body) -// .multilineTextAlignment(.leading) -// .padding() -// } -//} - struct PrescriptionCodeEntryView: View, HorizontalSizeClassOverride { @State private var prescriptionCode: String = "" From d2b7de980d3334a4a7b4b8fb8762dfe5003095c6 Mon Sep 17 00:00:00 2001 From: Anna Date: Fri, 26 Jun 2020 09:30:00 -0700 Subject: [PATCH 25/29] PR-feedback changes --- TidepoolService.xcodeproj/project.pbxproj | 12 +++++++---- .../Extensions/TimeInterval.swift | 19 +++++++++++++++++ .../MockPrescription.swift} | 4 ++-- .../Mocks/MockPrescriptionManager.swift | 21 +++++-------------- TidepoolServiceKit/TidepoolService.swift | 2 +- .../PrescriptionReviewUICoordinator.swift | 7 ++++++- .../PrescriptionCodeEntryViewModel.swift | 2 +- .../Views/PrescriptionCodeEntryView.swift | 9 ++++---- .../Views/PrescriptionDeviceView.swift | 18 +++++++--------- 9 files changed, 53 insertions(+), 41 deletions(-) create mode 100644 TidepoolServiceKit/Extensions/TimeInterval.swift rename TidepoolServiceKit/{Extensions/Prescription.swift => Mocks/MockPrescription.swift} (99%) diff --git a/TidepoolService.xcodeproj/project.pbxproj b/TidepoolService.xcodeproj/project.pbxproj index e191483..2b9bca8 100644 --- a/TidepoolService.xcodeproj/project.pbxproj +++ b/TidepoolService.xcodeproj/project.pbxproj @@ -66,10 +66,11 @@ E93BA06024A15FA800C5D7E6 /* PrescriptionDeviceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E93BA05F24A15FA800C5D7E6 /* PrescriptionDeviceView.swift */; }; E93BA06224A29C9C00C5D7E6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E93BA06124A29C9C00C5D7E6 /* Assets.xcassets */; }; E9692172249BC73600D9BE3B /* MockPrescriptionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9692171249BC73600D9BE3B /* MockPrescriptionManager.swift */; }; - E9692174249BF2A100D9BE3B /* Prescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9692173249BF2A100D9BE3B /* Prescription.swift */; }; + E9692174249BF2A100D9BE3B /* MockPrescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9692173249BF2A100D9BE3B /* MockPrescription.swift */; }; E9692176249C1AE700D9BE3B /* PrescriptionReviewUICoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9692175249C1AE700D9BE3B /* PrescriptionReviewUICoordinator.swift */; }; E96AEB98249C2FF1003797B4 /* PrescriptionCodeEntryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E96AEB97249C2FF1003797B4 /* PrescriptionCodeEntryView.swift */; }; E991F1B724A548580059281B /* AdaptiveKeyboardPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = E991F1B624A548580059281B /* AdaptiveKeyboardPadding.swift */; }; + E991F1BB24A654CC0059281B /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = E991F1BA24A654CC0059281B /* TimeInterval.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -180,10 +181,11 @@ E93BA05F24A15FA800C5D7E6 /* PrescriptionDeviceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrescriptionDeviceView.swift; sourceTree = ""; }; E93BA06124A29C9C00C5D7E6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; E9692171249BC73600D9BE3B /* MockPrescriptionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPrescriptionManager.swift; sourceTree = ""; }; - E9692173249BF2A100D9BE3B /* Prescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Prescription.swift; sourceTree = ""; }; + E9692173249BF2A100D9BE3B /* MockPrescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPrescription.swift; sourceTree = ""; }; E9692175249C1AE700D9BE3B /* PrescriptionReviewUICoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrescriptionReviewUICoordinator.swift; sourceTree = ""; }; E96AEB97249C2FF1003797B4 /* PrescriptionCodeEntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrescriptionCodeEntryView.swift; sourceTree = ""; }; E991F1B624A548580059281B /* AdaptiveKeyboardPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveKeyboardPadding.swift; sourceTree = ""; }; + E991F1BA24A654CC0059281B /* TimeInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeInterval.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -251,7 +253,7 @@ A9309CAE2436C52900E02268 /* StoredGlucoseSample.swift */, A97A60FA243818C900AD69A5 /* TDatum.swift */, A9309CA2243563CD00E02268 /* TOrigin.swift */, - E9692173249BF2A100D9BE3B /* Prescription.swift */, + E991F1BA24A654CC0059281B /* TimeInterval.swift */, ); path = Extensions; sourceTree = ""; @@ -392,6 +394,7 @@ E9692170249BC69B00D9BE3B /* Mocks */ = { isa = PBXGroup; children = ( + E9692173249BF2A100D9BE3B /* MockPrescription.swift */, E9692171249BC73600D9BE3B /* MockPrescriptionManager.swift */, ); path = Mocks; @@ -750,11 +753,12 @@ A9309CA52435986300E02268 /* DeletedCarbEntry.swift in Sources */, A9309CAF2436C52900E02268 /* StoredGlucoseSample.swift in Sources */, A9D1107C242289720091C620 /* HKUnit.swift in Sources */, - E9692174249BF2A100D9BE3B /* Prescription.swift in Sources */, + E9692174249BF2A100D9BE3B /* MockPrescription.swift in Sources */, A9309CA72435987000E02268 /* StoredCarbEntry.swift in Sources */, A97A60FB243818C900AD69A5 /* TDatum.swift in Sources */, A9DAAD3322E7CA1A00E76C9F /* LocalizedString.swift in Sources */, A913B37D24200C97000805C4 /* Bundle.swift in Sources */, + E991F1BB24A654CC0059281B /* TimeInterval.swift in Sources */, A9309CA3243563CD00E02268 /* TOrigin.swift in Sources */, E9692172249BC73600D9BE3B /* MockPrescriptionManager.swift in Sources */, A97651752421AA10002EB5D4 /* OSLog.swift in Sources */, diff --git a/TidepoolServiceKit/Extensions/TimeInterval.swift b/TidepoolServiceKit/Extensions/TimeInterval.swift new file mode 100644 index 0000000..6b2656e --- /dev/null +++ b/TidepoolServiceKit/Extensions/TimeInterval.swift @@ -0,0 +1,19 @@ +// +// TimeInterval.swift +// TidepoolServiceKit +// +// Created by Anna Quinlan on 6/26/20. +// Copyright © 2020 Tidepool Project. All rights reserved. +// + +import Foundation + +extension TimeInterval { + public static func minutes(_ minutes: Double) -> TimeInterval { + return TimeInterval(minutes * 60 /* seconds in minute */) + } + + public static func hours(_ hours: Double) -> TimeInterval { + return TimeInterval(hours * 60 /* minutes in hr */ * 60 /* seconds in minute */) + } +} diff --git a/TidepoolServiceKit/Extensions/Prescription.swift b/TidepoolServiceKit/Mocks/MockPrescription.swift similarity index 99% rename from TidepoolServiceKit/Extensions/Prescription.swift rename to TidepoolServiceKit/Mocks/MockPrescription.swift index 6d05267..876761f 100644 --- a/TidepoolServiceKit/Extensions/Prescription.swift +++ b/TidepoolServiceKit/Mocks/MockPrescription.swift @@ -25,7 +25,7 @@ public enum TrainingType: String, Codable { } -public struct Prescription { +public struct MockPrescription { public let datePrescribed: Date // Date prescription was prescribed public let providerName: String // Name of clinician prescribing public let cgm: CGMType // CGM type (manufacturer & model) @@ -94,7 +94,7 @@ public struct Prescription { } } -extension Prescription: Codable { +extension MockPrescription: Codable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) var bloodGlucoseUnit: HKUnit? diff --git a/TidepoolServiceKit/Mocks/MockPrescriptionManager.swift b/TidepoolServiceKit/Mocks/MockPrescriptionManager.swift index b9b9eea..85bff90 100644 --- a/TidepoolServiceKit/Mocks/MockPrescriptionManager.swift +++ b/TidepoolServiceKit/Mocks/MockPrescriptionManager.swift @@ -9,21 +9,10 @@ import Foundation import LoopKit - -extension TimeInterval { - static func minutes(_ minutes: Double) -> TimeInterval { - return TimeInterval(minutes * 60 /* seconds in minute */) - } - - static func hours(_ hours: Double) -> TimeInterval { - return TimeInterval(hours * 60 /* minutes in hr */ * 60 /* seconds in minute */) - } -} - public class MockPrescriptionManager { - private var prescription: Prescription + private var prescription: MockPrescription - public init(prescription: Prescription? = nil) { + public init(prescription: MockPrescription? = nil) { if let prescription = prescription { self.prescription = prescription } else { @@ -70,7 +59,7 @@ public class MockPrescriptionManager { - self.prescription = Prescription( + self.prescription = MockPrescription( datePrescribed: Date(), providerName: "Sally Seastar", cgmType: CGMType.g6, @@ -83,13 +72,13 @@ public class MockPrescriptionManager { maximumBasalRatePerHour: 3.0, maximumBolus: 5.0, suspendThreshold: GlucoseThreshold(unit: .milligramsPerDeciliter, value: 70), - insulinModel: Prescription.InsulinModel(modelType: .rapidAdult, actionDuration: .hours(6), peakActivity: .hours(3)), + insulinModel: MockPrescription.InsulinModel(modelType: .rapidAdult, actionDuration: .hours(6), peakActivity: .hours(3)), preMealTargetRange: DoubleRange(minValue: 80.0, maxValue: 90.0), workoutTargetRange: DoubleRange(minValue: 80.0, maxValue: 90.0)) } } - public func getPrescriptionData(completion: @escaping (Result) -> Void) { + public func getPrescriptionData(completion: @escaping (Result) -> Void) { completion(.success(self.prescription)) } } diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index a6692d5..300c325 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -147,7 +147,7 @@ public final class TidepoolService: Service { } } - public func getPrescriptionData(completion: @escaping (Result) -> Void) { + public func getPrescriptionData(completion: @escaping (Result) -> Void) { #if targetEnvironment(simulator) MockPrescriptionManager().getPrescriptionData { result in completion(result) diff --git a/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift b/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift index 45eccd3..de47ddc 100644 --- a/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift +++ b/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift @@ -64,7 +64,12 @@ class PrescriptionReviewUICoordinator: UINavigationController, CompletionNotifyi viewModel.didFinishStep = { [weak self] in self?.stepFinished() } - let view = PrescriptionDeviceView(viewModel: viewModel) + guard let prescription = viewModel.prescription else { + // Go back to code entry step if we don't have prescription + let view = PrescriptionCodeEntryView(viewModel: viewModel) + return DismissibleHostingController(rootView: view) + } + let view = PrescriptionDeviceView(viewModel: viewModel, prescription: prescription) return DismissibleHostingController(rootView: view) } } diff --git a/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift b/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift index 0e4cb23..5731411 100644 --- a/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift +++ b/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift @@ -13,7 +13,7 @@ class PrescriptionCodeEntryViewModel: ObservableObject { var didFinishStep: (() -> Void) var didCancel: (() -> Void)? - var prescription: Prescription? + var prescription: MockPrescription? let prescriptionCodeLength = 4 init(finishedStepHandler: @escaping () -> Void = { }) { diff --git a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift index 78c5518..2dc0911 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift @@ -15,7 +15,6 @@ struct PrescriptionCodeEntryView: View, HorizontalSizeClassOverride { @ObservedObject var viewModel: PrescriptionCodeEntryViewModel let blueGray = Color("blue gray", bundle: Bundle(for: PrescriptionReviewUICoordinator.self)) - let purple = Color("tidepool indigo", bundle: Bundle(for: PrescriptionReviewUICoordinator.self)) var body: some View { List { @@ -43,7 +42,7 @@ struct PrescriptionCodeEntryView: View, HorizontalSizeClassOverride { self.viewModel.didCancel?() }) { Text(LocalizedString("Cancel", comment: "Button text to exit the prescription code entry screen")) - .foregroundColor(purple) + .foregroundColor(.accentColor) } } @@ -112,9 +111,9 @@ struct PrescriptionCodeEntryView: View, HorizontalSizeClassOverride { private func submitButtonStyle(enabled: Bool) -> ActionButton.ButtonType { if enabled { - return .tidepoolPrimary + return .primary } - return .primary + return .deactivated } private var requestPrescriptionButton: some View { @@ -123,7 +122,7 @@ struct PrescriptionCodeEntryView: View, HorizontalSizeClassOverride { print("TODO") }) { Text(LocalizedString("Request activation code", comment:"Button title for requesting a prescription activation code from the prescriber")) - .actionButtonStyle(.tidepoolSecondary) + .actionButtonStyle(.secondary) } } } diff --git a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift index b4cf163..e850ecd 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift @@ -6,16 +6,16 @@ // Copyright © 2020 Tidepool Project. All rights reserved. // -import Foundation import SwiftUI import LoopKitUI +import TidepoolServiceKit struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { @State private var prescriptionCode: String = "" @ObservedObject var viewModel: PrescriptionCodeEntryViewModel + var prescription: MockPrescription let blueGray = Color("blue gray", bundle: Bundle(for: PrescriptionReviewUICoordinator.self)) - let purple = Color("tidepool indigo", bundle: Bundle(for: PrescriptionReviewUICoordinator.self)) var body: some View { List { @@ -58,11 +58,9 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { } private var pumpStack: some View { - switch self.viewModel.prescription?.pump { + switch prescription.pump { case .dash: return dashStack - case .none: - return dashStack // TODO: ask design about a default 'empty' pump card } } @@ -88,15 +86,13 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { .aspectRatio(contentMode: ContentMode.fit) .frame(height: 50) .padding(5) // align with Dexcom - .foregroundColor(purple) + .foregroundColor(.accentColor) } private var cgmStack: some View { - switch self.viewModel.prescription?.cgm { + switch prescription.cgm { case .g6: return dexcomStack - case .none: - return dexcomStack // TODO: ask design about a default 'empty' cgm card } } @@ -146,7 +142,7 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { self.viewModel.didFinishStep() }) { Text(LocalizedString("Next: Review Settings", comment: "Button title for approving devices")) - .actionButtonStyle(.tidepoolPrimary) + .actionButtonStyle(.primary) } } @@ -156,7 +152,7 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { print("TODO") }) { Text(LocalizedString("Edit devices", comment:"Button title for editing the prescribed devices")) - .actionButtonStyle(.tidepoolSecondary) + .actionButtonStyle(.secondary) } } } From 423e36f00ced907114e49a04704acaad372a95eb Mon Sep 17 00:00:00 2001 From: Anna Date: Fri, 26 Jun 2020 09:30:49 -0700 Subject: [PATCH 26/29] Remove purple from assets --- .../tidepool indigo.colorset/Contents.json | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 TidepoolServiceKitUI/Assets.xcassets/tidepool indigo.colorset/Contents.json diff --git a/TidepoolServiceKitUI/Assets.xcassets/tidepool indigo.colorset/Contents.json b/TidepoolServiceKitUI/Assets.xcassets/tidepool indigo.colorset/Contents.json deleted file mode 100644 index 9fb60e0..0000000 --- a/TidepoolServiceKitUI/Assets.xcassets/tidepool indigo.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0xFF", - "green" : "0x79", - "red" : "0x5D" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} From 2d6d53be6739080e0dd22fe577618779cafe9acc Mon Sep 17 00:00:00 2001 From: Anna Date: Fri, 26 Jun 2020 12:16:20 -0700 Subject: [PATCH 27/29] Feedback from Darin's review --- .../Mocks/MockPrescription.swift | 82 ++++--------------- .../Mocks/MockPrescriptionManager.swift | 15 ++-- TidepoolServiceKit/TidepoolService.swift | 4 - .../TidepoolServiceSetupViewController.swift | 3 +- .../PrescriptionReviewUICoordinator.swift | 2 +- .../PrescriptionCodeEntryViewModel.swift | 8 +- .../Views/PrescriptionCodeEntryView.swift | 5 +- .../Views/PrescriptionDeviceView.swift | 20 ++++- 8 files changed, 51 insertions(+), 88 deletions(-) diff --git a/TidepoolServiceKit/Mocks/MockPrescription.swift b/TidepoolServiceKit/Mocks/MockPrescription.swift index 876761f..f8f7b70 100644 --- a/TidepoolServiceKit/Mocks/MockPrescription.swift +++ b/TidepoolServiceKit/Mocks/MockPrescription.swift @@ -25,12 +25,26 @@ public enum TrainingType: String, Codable { } -public struct MockPrescription { +public enum BGUnit: String, Codable { + case mgdl + case mmol + + var hkUnit: HKUnit { + switch self { + case .mgdl: + return .milligramsPerDeciliter + case .mmol: + return .millimolesPerLiter + } + } +} + +public struct MockPrescription: Codable { public let datePrescribed: Date // Date prescription was prescribed public let providerName: String // Name of clinician prescribing public let cgm: CGMType // CGM type (manufacturer & model) public let pump: PumpType // Pump type (manufacturer & model) - public let bloodGlucoseUnit: HKUnit // CGM type (manufacturer & model) + public let bloodGlucoseUnit: BGUnit public let basalRateSchedule: BasalRateSchedule public let glucoseTargetRangeSchedule: GlucoseRangeSchedule public let carbRatioSchedule: CarbRatioSchedule @@ -46,7 +60,7 @@ public struct MockPrescription { providerName: String, cgmType: CGMType, pumpType: PumpType, - bloodGlucoseUnit: HKUnit, + bloodGlucoseUnit: BGUnit, basalRateSchedule: BasalRateSchedule, glucoseTargetRangeSchedule: GlucoseRangeSchedule, carbRatioSchedule: CarbRatioSchedule, @@ -93,65 +107,3 @@ public struct MockPrescription { } } } - -extension MockPrescription: Codable { - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - var bloodGlucoseUnit: HKUnit? - if let bloodGlucoseUnitString = try container.decodeIfPresent(String.self, forKey: .bloodGlucoseUnit) { - bloodGlucoseUnit = HKUnit(from: bloodGlucoseUnitString) - } - self.init(datePrescribed: try container.decode(Date.self, forKey: .datePrescribed), - providerName: try container.decode(String.self, forKey: .providerName), - cgmType: try container.decode(CGMType.self, forKey: .cgm), - pumpType: try container.decode(PumpType.self, forKey: .pump), - bloodGlucoseUnit: bloodGlucoseUnit ?? .milligramsPerDeciliter, - basalRateSchedule: try container.decode(BasalRateSchedule.self, forKey: .basalRateSchedule), - glucoseTargetRangeSchedule: try container.decode(GlucoseRangeSchedule.self, forKey: .glucoseTargetRangeSchedule), - carbRatioSchedule: try container.decode(CarbRatioSchedule.self, forKey: .carbRatioSchedule), - insulinSensitivitySchedule: try container.decode(InsulinSensitivitySchedule.self, forKey: .insulinSensitivitySchedule), - maximumBasalRatePerHour: try container.decode(Double.self, forKey: .maximumBasalRatePerHour), - maximumBolus: try container.decode(Double.self, forKey: .maximumBolus), - suspendThreshold: try container.decode(GlucoseThreshold.self, forKey: .suspendThreshold), - insulinModel: try container.decode(InsulinModel.self, forKey: .insulinModel), - preMealTargetRange: try container.decode(DoubleRange.self, forKey: .preMealTargetRange), - workoutTargetRange: try container.decode(DoubleRange.self, forKey: .workoutTargetRange)) - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(datePrescribed, forKey: .datePrescribed) - try container.encode(providerName, forKey: .providerName) - try container.encode(cgm, forKey: .cgm) - try container.encode(pump, forKey: .pump) - try container.encode(bloodGlucoseUnit.unitString, forKey: .bloodGlucoseUnit) - try container.encode(basalRateSchedule, forKey: .basalRateSchedule) - try container.encode(glucoseTargetRangeSchedule, forKey: .glucoseTargetRangeSchedule) - try container.encode(carbRatioSchedule, forKey: .carbRatioSchedule) - try container.encode(insulinSensitivitySchedule, forKey: .insulinSensitivitySchedule) - try container.encode(maximumBasalRatePerHour, forKey: .maximumBasalRatePerHour) - try container.encode(maximumBolus, forKey: .maximumBolus) - try container.encode(suspendThreshold, forKey: .suspendThreshold) - try container.encode(insulinModel, forKey: .insulinModel) - try container.encode(preMealTargetRange, forKey: .preMealTargetRange) - try container.encode(workoutTargetRange, forKey: .workoutTargetRange) - } - - private enum CodingKeys: String, CodingKey { - case datePrescribed - case providerName - case cgm - case pump - case bloodGlucoseUnit - case basalRateSchedule - case glucoseTargetRangeSchedule - case carbRatioSchedule - case insulinSensitivitySchedule - case maximumBasalRatePerHour - case maximumBolus - case suspendThreshold - case insulinModel - case preMealTargetRange - case workoutTargetRange - } -} diff --git a/TidepoolServiceKit/Mocks/MockPrescriptionManager.swift b/TidepoolServiceKit/Mocks/MockPrescriptionManager.swift index 85bff90..3c8969b 100644 --- a/TidepoolServiceKit/Mocks/MockPrescriptionManager.swift +++ b/TidepoolServiceKit/Mocks/MockPrescriptionManager.swift @@ -19,10 +19,15 @@ public class MockPrescriptionManager { let timeZone = TimeZone(identifier: "America/Los_Angeles")! let glucoseTargetRangeSchedule = GlucoseRangeSchedule( rangeSchedule: DailyQuantitySchedule(unit: .milligramsPerDeciliter, - dailyItems: [RepeatingScheduleValue(startTime: .hours(0), value: DoubleRange(minValue: 100.0, maxValue: 110.0)), RepeatingScheduleValue(startTime: .hours(8), value: DoubleRange(minValue: 95.0, maxValue: 105.0)), RepeatingScheduleValue(startTime: .hours(14), value: DoubleRange(minValue: 95.0, maxValue: 105.0)), RepeatingScheduleValue(startTime: .hours(16), value: DoubleRange(minValue: 100.0, maxValue: 110.0)), RepeatingScheduleValue(startTime: .hours(18), value: DoubleRange(minValue: 90.0, maxValue: 100.0)), RepeatingScheduleValue(startTime: .hours(21), value: DoubleRange(minValue: 110.0, maxValue: 120.0))], + dailyItems: [RepeatingScheduleValue(startTime: .hours(0), value: DoubleRange(minValue: 100.0, maxValue: 110.0)), + RepeatingScheduleValue(startTime: .hours(8), value: DoubleRange(minValue: 95.0, maxValue: 105.0)), + RepeatingScheduleValue(startTime: .hours(14), value: DoubleRange(minValue: 95.0, maxValue: 105.0)), + RepeatingScheduleValue(startTime: .hours(16), value: DoubleRange(minValue: 100.0, maxValue: 110.0)), + RepeatingScheduleValue(startTime: .hours(18), value: DoubleRange(minValue: 90.0, maxValue: 100.0)), + RepeatingScheduleValue(startTime: .hours(21), value: DoubleRange(minValue: 110.0, maxValue: 120.0))], timeZone: timeZone)!, override: GlucoseRangeSchedule.Override(value: DoubleRange(minValue: 80.0, maxValue: 90.0), - start: Date().addingTimeInterval(-.minutes(30)), + start: Date().addingTimeInterval(.minutes(-30)), end: Date().addingTimeInterval(.minutes(30))) ) let basalRateSchedule = BasalRateSchedule( @@ -55,16 +60,14 @@ public class MockPrescriptionManager { RepeatingScheduleValue(startTime: .hours(16), value: 12.0), RepeatingScheduleValue(startTime: .hours(18), value: 8.0), RepeatingScheduleValue(startTime: .hours(21), value: 10.0)], - timeZone: timeZone)! - - + timeZone: timeZone)! self.prescription = MockPrescription( datePrescribed: Date(), providerName: "Sally Seastar", cgmType: CGMType.g6, pumpType: PumpType.dash, - bloodGlucoseUnit: .milligramsPerDeciliter, + bloodGlucoseUnit: .mgdl, basalRateSchedule: basalRateSchedule, glucoseTargetRangeSchedule: glucoseTargetRangeSchedule, carbRatioSchedule: carbRatioSchedule, diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index 300c325..8d607fd 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -148,14 +148,10 @@ public final class TidepoolService: Service { } public func getPrescriptionData(completion: @escaping (Result) -> Void) { - #if targetEnvironment(simulator) MockPrescriptionManager().getPrescriptionData { result in completion(result) } - #else // TODO: add in proper query to backend - completion(.failure(TidepoolServiceError.noPrescriptionDataAvailable)) - #endif } private var sessionService: String { "org.tidepool.TidepoolService.\(id)" } diff --git a/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift b/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift index dc575e4..340d153 100644 --- a/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift +++ b/TidepoolServiceKitUI/TidepoolServiceSetupViewController.swift @@ -48,8 +48,9 @@ final class TidepoolServiceSetupViewController: UIViewController, TLoginSignupDe notifyComplete() } - let setupViewController = PrescriptionReviewUICoordinator() + @objc private func startFlow() { + let setupViewController = PrescriptionReviewUICoordinator() setupViewController.completionDelegate = self self.present(setupViewController, animated: true, completion: nil) } diff --git a/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift b/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift index de47ddc..967f48a 100644 --- a/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift +++ b/TidepoolServiceKitUI/View Controllers/PrescriptionReviewUICoordinator.swift @@ -26,7 +26,7 @@ enum PrescriptionReviewScreen { class PrescriptionReviewUICoordinator: UINavigationController, CompletionNotifying, UINavigationControllerDelegate { var screenStack = [PrescriptionReviewScreen]() - var completionDelegate: CompletionDelegate? + weak var completionDelegate: CompletionDelegate? let viewModel = PrescriptionCodeEntryViewModel() diff --git a/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift b/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift index 5731411..f6dc406 100644 --- a/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift +++ b/TidepoolServiceKitUI/View Models/PrescriptionCodeEntryViewModel.swift @@ -1,5 +1,5 @@ // -// PrescriptionCodeEntryModel.swift +// PrescriptionCodeEntryViewModel.swift // TidepoolServiceKitUI // // Created by Anna Quinlan on 6/22/20. @@ -24,16 +24,16 @@ class PrescriptionCodeEntryViewModel: ObservableObject { if success { didFinishStep() } else { - // Handle error + // TODO: handle error } } - func validateCode(prescriptionCode: String) -> Bool { + func validatePrescriptionCode(prescriptionCode: String) -> Bool { return prescriptionCode.count == prescriptionCodeLength } func loadPrescriptionFromCode(prescriptionCode: String) { - guard validateCode(prescriptionCode: prescriptionCode) else { + guard validatePrescriptionCode(prescriptionCode: prescriptionCode) else { // TODO: handle error return } diff --git a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift index 2dc0911..fe2f8ee 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift @@ -110,10 +110,7 @@ struct PrescriptionCodeEntryView: View, HorizontalSizeClassOverride { } private func submitButtonStyle(enabled: Bool) -> ActionButton.ButtonType { - if enabled { - return .primary - } - return .deactivated + return enabled ? .primary : .secondary } private var requestPrescriptionButton: some View { diff --git a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift index e850ecd..1a15599 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift @@ -10,6 +10,13 @@ import SwiftUI import LoopKitUI import TidepoolServiceKit +struct WidthPreferenceKey: PreferenceKey { + static var defaultValue: [CGFloat] = [] + static func reduce(value: inout [CGFloat], nextValue: () -> [CGFloat]) { + value.append(contentsOf: nextValue()) + } +} + struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { @State private var prescriptionCode: String = "" @ObservedObject var viewModel: PrescriptionCodeEntryViewModel @@ -17,6 +24,8 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { let blueGray = Color("blue gray", bundle: Bundle(for: PrescriptionReviewUICoordinator.self)) + @State private var width: CGFloat? = nil + var body: some View { List { VStack(alignment: .leading, spacing: 25) { @@ -35,6 +44,11 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { .onAppear() { UITableView.appearance().separatorStyle = .none // Remove lines between sections } + .onPreferenceChange(WidthPreferenceKey.self) { widths in + if let width = widths.max() { + self.width = width + } + } } private var prescribedDeviceInfo: some View { @@ -52,7 +66,9 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { .foregroundColor(blueGray) .fixedSize(horizontal: false, vertical: true) // prevent text from being cut off pumpStack + .frame(width: width, alignment: .leading) cgmStack + .frame(width: width, alignment: .leading) } } } @@ -75,7 +91,6 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { .foregroundColor(blueGray) } Spacer() - } } @@ -134,7 +149,6 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { .foregroundColor(blueGray) } } - } private var approveDevicesButton: some View { @@ -151,7 +165,7 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { // TODO: open window to edit the devices print("TODO") }) { - Text(LocalizedString("Edit devices", comment:"Button title for editing the prescribed devices")) + Text(LocalizedString("Edit devices", comment: "Button title for editing the prescribed devices")) .actionButtonStyle(.secondary) } } From d1faeddf4ce01dcae4cc6f28bd340a7a778c729a Mon Sep 17 00:00:00 2001 From: Anna Date: Mon, 29 Jun 2020 12:47:54 -0700 Subject: [PATCH 28/29] Set width size --- .../Views/PrescriptionDeviceView.swift | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift index 1a15599..edc7bf6 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift @@ -10,21 +10,13 @@ import SwiftUI import LoopKitUI import TidepoolServiceKit -struct WidthPreferenceKey: PreferenceKey { - static var defaultValue: [CGFloat] = [] - static func reduce(value: inout [CGFloat], nextValue: () -> [CGFloat]) { - value.append(contentsOf: nextValue()) - } -} - struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { @State private var prescriptionCode: String = "" @ObservedObject var viewModel: PrescriptionCodeEntryViewModel var prescription: MockPrescription let blueGray = Color("blue gray", bundle: Bundle(for: PrescriptionReviewUICoordinator.self)) - - @State private var width: CGFloat? = nil + static let imageWidth: CGFloat = 48 var body: some View { List { @@ -44,11 +36,6 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { .onAppear() { UITableView.appearance().separatorStyle = .none // Remove lines between sections } - .onPreferenceChange(WidthPreferenceKey.self) { widths in - if let width = widths.max() { - self.width = width - } - } } private var prescribedDeviceInfo: some View { @@ -66,9 +53,7 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { .foregroundColor(blueGray) .fixedSize(horizontal: false, vertical: true) // prevent text from being cut off pumpStack - .frame(width: width, alignment: .leading) cgmStack - .frame(width: width, alignment: .leading) } } } @@ -99,8 +84,7 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { .renderingMode(.template) .resizable() .aspectRatio(contentMode: ContentMode.fit) - .frame(height: 50) - .padding(5) // align with Dexcom + .frame(width: Self.imageWidth, height: 50) .foregroundColor(.accentColor) } @@ -129,7 +113,7 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { Image("dexcom", bundle: Bundle(for: PrescriptionReviewUICoordinator.self)) .resizable() .aspectRatio(contentMode: ContentMode.fit) - .frame(height: 25) + .frame(width: Self.imageWidth) } private var disclaimer: some View { From ba933c572bb56ceb57bcad8151f787a15723792c Mon Sep 17 00:00:00 2001 From: Anna Date: Mon, 29 Jun 2020 12:54:04 -0700 Subject: [PATCH 29/29] Add todo --- TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift index edc7bf6..312f163 100644 --- a/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift +++ b/TidepoolServiceKitUI/Views/PrescriptionDeviceView.swift @@ -52,6 +52,7 @@ struct PrescriptionDeviceView: View, HorizontalSizeClassOverride { Text(LocalizedString("Your prescription contains recommended settings for the following devices:", comment: "Title for devices prescribed section")) .foregroundColor(blueGray) .fixedSize(horizontal: false, vertical: true) // prevent text from being cut off + // TODO: get images and descriptions from pump manager pumpStack cgmStack }