Skip to content

Commit

Permalink
LOOP-1479: Implement first 2 screens of prescription flow + mock mana…
Browse files Browse the repository at this point in the history
…ger (#11)

Create Prescription data model

Create a mock prescription manager

Create a UICoordinator to navigate between views in the flow

Add button to TidepoolService setup view controller to trigger the onboarding flow

Create assets folder with Dexcom/Dash images

Create view model for initial part of flow

Create the first 2 screens of the onboarding flow (prescription code entry & device review)
  • Loading branch information
novalegra authored Jun 29, 2020
1 parent c7aac3c commit 0471b79
Show file tree
Hide file tree
Showing 18 changed files with 902 additions and 0 deletions.
70 changes: 70 additions & 0 deletions TidepoolService.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@
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 */; };
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 /* 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 */
Expand Down Expand Up @@ -168,6 +177,15 @@
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 = "<group>"; };
A9DAAD6E22E7EA9700E76C9F /* NibLoadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NibLoadable.swift; sourceTree = "<group>"; };
E93BA05D24A14CBA00C5D7E6 /* PrescriptionCodeEntryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrescriptionCodeEntryViewModel.swift; sourceTree = "<group>"; };
E93BA05F24A15FA800C5D7E6 /* PrescriptionDeviceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrescriptionDeviceView.swift; sourceTree = "<group>"; };
E93BA06124A29C9C00C5D7E6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
E9692171249BC73600D9BE3B /* MockPrescriptionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPrescriptionManager.swift; sourceTree = "<group>"; };
E9692173249BF2A100D9BE3B /* MockPrescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPrescription.swift; sourceTree = "<group>"; };
E9692175249C1AE700D9BE3B /* PrescriptionReviewUICoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrescriptionReviewUICoordinator.swift; sourceTree = "<group>"; };
E96AEB97249C2FF1003797B4 /* PrescriptionCodeEntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrescriptionCodeEntryView.swift; sourceTree = "<group>"; };
E991F1B624A548580059281B /* AdaptiveKeyboardPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveKeyboardPadding.swift; sourceTree = "<group>"; };
E991F1BA24A654CC0059281B /* TimeInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeInterval.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -235,6 +253,7 @@
A9309CAE2436C52900E02268 /* StoredGlucoseSample.swift */,
A97A60FA243818C900AD69A5 /* TDatum.swift */,
A9309CA2243563CD00E02268 /* TOrigin.swift */,
E991F1BA24A654CC0059281B /* TimeInterval.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -303,6 +322,7 @@
A9DAAD0022E7987800E76C9F /* TidepoolServiceKit */ = {
isa = PBXGroup;
children = (
E9692170249BC69B00D9BE3B /* Mocks */,
A9DAAD0122E7987800E76C9F /* TidepoolServiceKit.h */,
A9DAAD0222E7987800E76C9F /* Info.plist */,
A9DAAD3522E7CAC100E76C9F /* TidepoolService.swift */,
Expand All @@ -323,7 +343,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 */,
Expand All @@ -332,6 +355,7 @@
A92E770022E9181500591027 /* TidepoolServiceSetupViewController.swift */,
A94AE4F6235A907C005CA320 /* Extensions */,
A9DAAD4F22E7DFD400E76C9F /* Localizable.strings */,
E93BA06124A29C9C00C5D7E6 /* Assets.xcassets */,
);
path = TidepoolServiceKitUI;
sourceTree = "<group>";
Expand Down Expand Up @@ -359,6 +383,41 @@
name = Frameworks;
sourceTree = "<group>";
};
E93BA05C24A126E000C5D7E6 /* View Models */ = {
isa = PBXGroup;
children = (
E93BA05D24A14CBA00C5D7E6 /* PrescriptionCodeEntryViewModel.swift */,
);
path = "View Models";
sourceTree = "<group>";
};
E9692170249BC69B00D9BE3B /* Mocks */ = {
isa = PBXGroup;
children = (
E9692173249BF2A100D9BE3B /* MockPrescription.swift */,
E9692171249BC73600D9BE3B /* MockPrescriptionManager.swift */,
);
path = Mocks;
sourceTree = "<group>";
};
E9692177249C1B0F00D9BE3B /* View Controllers */ = {
isa = PBXGroup;
children = (
E9692175249C1AE700D9BE3B /* PrescriptionReviewUICoordinator.swift */,
);
path = "View Controllers";
sourceTree = "<group>";
};
E9692179249C1B9400D9BE3B /* Views */ = {
isa = PBXGroup;
children = (
E96AEB97249C2FF1003797B4 /* PrescriptionCodeEntryView.swift */,
E93BA05F24A15FA800C5D7E6 /* PrescriptionDeviceView.swift */,
E991F1B624A548580059281B /* AdaptiveKeyboardPadding.swift */,
);
path = Views;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXHeadersBuildPhase section */
Expand Down Expand Up @@ -578,6 +637,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E93BA06224A29C9C00C5D7E6 /* Assets.xcassets in Resources */,
A9DAAD4D22E7DFD400E76C9F /* Localizable.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -693,11 +753,14 @@
A9309CA52435986300E02268 /* DeletedCarbEntry.swift in Sources */,
A9309CAF2436C52900E02268 /* StoredGlucoseSample.swift in Sources */,
A9D1107C242289720091C620 /* HKUnit.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 */,
A9DAAD3622E7CAC100E76C9F /* TidepoolService.swift in Sources */,
);
Expand All @@ -716,10 +779,15 @@
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 */,
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 */,
);
Expand Down Expand Up @@ -1188,6 +1256,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 = (
Expand All @@ -1214,6 +1283,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 = (
Expand Down
19 changes: 19 additions & 0 deletions TidepoolServiceKit/Extensions/TimeInterval.swift
Original file line number Diff line number Diff line change
@@ -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 */)
}
}
109 changes: 109 additions & 0 deletions TidepoolServiceKit/Mocks/MockPrescription.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//
// Prescription.swift
// TidepoolServiceKit
//
// Created by Anna Quinlan on 6/18/20.
// Copyright © 2020 Tidepool Project. All rights reserved.
//

import Foundation
import HealthKit
import LoopKit


public enum CGMType: String, Codable {
case g6
}

public enum PumpType: String, Codable {
case dash
}

public enum TrainingType: String, Codable {
case inPerson // Patient must have hands-on training with clinician/CDE
case inModule // Patient can train in-app

}

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: BGUnit
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 let preMealTargetRange: DoubleRange
public let workoutTargetRange: DoubleRange

public init(datePrescribed: Date,
providerName: String,
cgmType: CGMType,
pumpType: PumpType,
bloodGlucoseUnit: BGUnit,
basalRateSchedule: BasalRateSchedule,
glucoseTargetRangeSchedule: GlucoseRangeSchedule,
carbRatioSchedule: CarbRatioSchedule,
insulinSensitivitySchedule: InsulinSensitivitySchedule,
maximumBasalRatePerHour: Double,
maximumBolus: Double,
suspendThreshold: GlucoseThreshold,
insulinModel: InsulinModel,
preMealTargetRange: DoubleRange,
workoutTargetRange: DoubleRange) {
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
self.preMealTargetRange = preMealTargetRange
self.workoutTargetRange = workoutTargetRange
}

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
}
}
}
87 changes: 87 additions & 0 deletions TidepoolServiceKit/Mocks/MockPrescriptionManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//
// MockPrescriptionManager.swift
// TidepoolServiceKit
//
// Created by Anna Quinlan on 6/18/20.
// Copyright © 2020 Tidepool Project. All rights reserved.
//

import Foundation
import LoopKit

public class MockPrescriptionManager {
private var prescription: MockPrescription

public init(prescription: MockPrescription? = 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 = MockPrescription(
datePrescribed: Date(),
providerName: "Sally Seastar",
cgmType: CGMType.g6,
pumpType: PumpType.dash,
bloodGlucoseUnit: .mgdl,
basalRateSchedule: basalRateSchedule,
glucoseTargetRangeSchedule: glucoseTargetRangeSchedule,
carbRatioSchedule: carbRatioSchedule,
insulinSensitivitySchedule: insulinSensitivitySchedule,
maximumBasalRatePerHour: 3.0,
maximumBolus: 5.0,
suspendThreshold: GlucoseThreshold(unit: .milligramsPerDeciliter, value: 70),
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<MockPrescription, Error>) -> Void) {
completion(.success(self.prescription))
}
}
Loading

0 comments on commit 0471b79

Please sign in to comment.