-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
LOOP-1479: Implement first 2 screens of prescription flow + mock manager #11
Conversation
Add underlying infrastructure into Tidepool Service, a UI controller to coordinate prescription flow, & the prescription code entry screen
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks really good! I think there are a couple of cleanup items that should be tackled, but otherwise this looks like a good first stab at implementing the prescription flow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Would appreciate @darinkrauss taking a look before merge, though.
@novalegra @ps2 I will need to utilize the Dexcom and Dash icons for my work. Would it make sense to put them into LoopKitUI? |
@rickpasetto Yours should come from the actual PumpManagers. Ideally Anna's should too, but we're not implementing device choosing in setup flow for v1, so I think just hardcoding them for now is fine. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking great! 🚀
Please don't be alarmed at the number of nits as they are more a reflection of my particular-ness than anything else (and I add them to most all PRs 😄). Just consider them suggestions for your consideration. Feel free to ignore any or all of the nits.
Otherwise, I don't see a thing other than the nits, so LGTM! 🏎️
public static func hours(_ hours: Double) -> TimeInterval { | ||
return TimeInterval(hours * 60 /* minutes in hr */ * 60 /* seconds in minute */) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] Consider copy/paste this implementation from LoopKit:
static func minutes(_ minutes: Double) -> TimeInterval {
return self.init(minutes: minutes)
}
static func hours(_ hours: Double) -> TimeInterval {
return self.init(hours: hours)
}
init(minutes: Double) {
self.init(minutes * 60)
}
init(hours: Double) {
self.init(minutes: hours * 60)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@darinkrauss doesn't TidepoolService
have LoopKit
as a dependency? Just curious...should she just make those LoopKit
functions public and then use them?
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this object is a mock, but something similar will end up in TidepoolKit (using many of the types already implemented in TidepoolKit). Consider dropping use of HKUnit
and just use another enum. This would allow you to drop the custom Codable
implementation below. The enum could have a func/var that converts it into an HKUnit
if you'd like.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great suggestion, thanks!
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))], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] Consider breaking up into multiple lines as you did for the rest of this initializer.
if success { | ||
didFinishStep() | ||
} else { | ||
// Handle error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] TODO
so it doesn't get missed?
} | ||
} | ||
|
||
func validateCode(prescriptionCode: String) -> Bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] Consider how this reads "validate code prescription code". Perhaps validatePrescriptionCode(_ prescriptionCode: String)
? Same for function below.
var prescription: Prescription? | ||
|
||
func loadPrescriptionFromCode(prescriptionCode: String) { | ||
#if targetEnvironment(simulator) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] Consider just using Mock for simulator and non-simulator. After all, once TidepoolKit implements the backend API call we won't want the simulator to return a mock anymore.
|
||
class PrescriptionReviewUICoordinator: UINavigationController, CompletionNotifying, UINavigationControllerDelegate { | ||
var screenStack = [PrescriptionReviewScreen]() | ||
var completionDelegate: CompletionDelegate? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] weak
? Since the delegate is assigned to self
below, won't this cause a retain cycle? Maybe not, but I generally assume all delegates should be weak
to avoid the issue altogether.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
} | ||
|
||
override func viewWillAppear(_ animated: Bool) { | ||
screenStack = [determineFirstScreen()] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] Will the first screen every vary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we'll eventually be keeping track of progress within the flow in the case the screen quit
public static func hours(_ hours: Double) -> TimeInterval { | ||
return TimeInterval(hours * 60 /* minutes in hr */ * 60 /* seconds in minute */) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@darinkrauss doesn't TidepoolService
have LoopKit
as a dependency? Just curious...should she just make those LoopKit
functions public and then use them?
} | ||
} | ||
|
||
private var dashStack: some View { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't these icons and descriptions be vended from the device managers? Or is this just placeholder until that's in place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a placeholder. The icons/descriptions would need to be static in order for this screen to be able to access them - @rickpasetto do you know if that's the case at the moment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am working on the ability to vend these images from the device managers. My recommendation is to put TODO
comments in the code for both the device "buttons" (image, title, and description) and say that they are static placeholders for now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The device managers already do vend images for this use: https://github.com/tidepool-org/LoopKit/blob/dev/LoopKitUI/PumpManagerUI.swift#L18
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But this PR should hardcode them for now. We have no mechanism for a Service plugin to access currently available device managers, and we don't need to as initially we are going to be not allowing device selection during onboarding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ps2 yes, but not before the devices are set up (in other words: as a static var instead of a member var). I'm working on changing that now.
UITableView.appearance().separatorStyle = .none // Remove lines between sections | ||
} | ||
.onPreferenceChange(WidthPreferenceKey.self) { widths in | ||
if let width = widths.max() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious...what does this do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It aligns the images based on the max width
.resizable() | ||
.aspectRatio(contentMode: ContentMode.fit) | ||
.frame(height: 50) | ||
.padding(5) // align with Dexcom |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm...the way I did this in my screen was to fix the width of the image:
fileprivate struct LargeButton: View {
let image: String
let label: String
let description: String
static let spacing: CGFloat = 15
static let imageWidth: CGFloat = 48
static let topBottomPadding: CGFloat = 20
public var body: some View {
return HStack(spacing: Self.spacing) {
Image(frameworkImage: image, decorative: true)
.frame(width: Self.imageWidth)
VStack(alignment: .leading) {
Text(label)
DescriptiveText(label: description)
}
}
.padding(EdgeInsets(top: Self.topBottomPadding, leading: 0, bottom: Self.topBottomPadding, trailing: 0))
}
}
import Foundation | ||
|
||
extension TimeInterval { | ||
public static func minutes(_ minutes: Double) -> TimeInterval { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@novalegra why not use the existing LoopKit functions for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rickpasetto We typically don't publicly export extensions to system types from our Frameworks. There are exceptions, but these are likely conflict sources; imagine if another framework did the same. I could easily see another framework thinking "wouldn't it be nice to implement minute/hour/day extensions for TimeInterval and export them for everyone?".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ps2 I see. @novalegra in that case, it is fine to leave these as-is.
* Updating to new TidepoolKit with keycloak based auth * SettingsView using TidepoolService as ObservedObject * Tweak logo size * Fix issues with state restoration and re-logging in. Add alert when session loss is detected * Improve DataSetId caching * Do not allow environment switching when logged in * Tweak wording
* Embed TidepoolKit * Default to production environment * Use app provided client id * Tidepool backend does not support automated as bolus type * Fix upload of automatic boluses, and invalid pumpSettings. Updates for host identifier and version * Roundtrip localizations * Updates from Lokalise * Show login email on settings page * Updated translations from Lokalise on Thu Feb 9 13:30:18 CST 2023 * Do not upload Loop temp basals as automatic, to clean up Tidepool Web rendering * Move remote command parsing and validation to RemoteDataService * Revert "Remote PR Set #2: Introduce RemoteCommands" * Re-enable tracking of automatic flag for temp basal, and add for scheduled basal * Updated translations from Lokalise on Sat Mar 18 14:01:08 CDT 2023 * Updated translations from Lokalise on Sat Mar 18 15:11:44 CDT 2023 * Updated translations * Remove TidepoolKitUI reference from TidepoolServiceKitUITests * Tests compiling. Basal rate fixes. Not all tests passing yet * hostIdentifier/version updates for tests * Remove TidepoolKitUI references * Ensure food entries have name set * Nil names allow for meals, but not empty strings * Log errors during initialization * Mark automated boluses as such, and show environment in settings if not production * Update README.md * Update README.md * Updating to new TidepoolKit with keycloak based auth (#11) * Updating to new TidepoolKit with keycloak based auth * SettingsView using TidepoolService as ObservedObject * Tweak logo size * Fix issues with state restoration and re-logging in. Add alert when session loss is detected * Improve DataSetId caching * Do not allow environment switching when logged in * Tweak wording * Update tests * Remote PR Set 2: Introduce RemoteCommands * Support client-specific clientIds for auth * Remove unused code * Cleanup from review * Xcode project updates --------- Co-authored-by: Bill Gestrich <3207996+gestrich@users.noreply.github.com>
https://tidepool.atlassian.net/browse/LOOP-1479
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)
DismissibleHostingController
to prevent accidental dismissal, per discussions with designTextField
keyboard in the code entry screen may cover the field on smaller devices, I added a struct to keep track of the keyboard height and provide appropriate amounts of padding to keep the field in view