Skip to content
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

Merged
merged 29 commits into from
Jun 29, 2020

Conversation

novalegra
Copy link
Contributor

@novalegra novalegra commented Jun 25, 2020

  • 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)

    • These are hosted in a DismissibleHostingController to prevent accidental dismissal, per discussions with design
    • Because the TextField 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

@novalegra novalegra requested a review from ps2 June 25, 2020 22:48
Copy link
Contributor

@ps2 ps2 left a 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.

TidepoolServiceKit/Extensions/Prescription.swift Outdated Show resolved Hide resolved
TidepoolServiceKit/Mocks/MockPrescriptionManager.swift Outdated Show resolved Hide resolved
TidepoolServiceKitUI/Views/PrescriptionCodeEntryView.swift Outdated Show resolved Hide resolved
@novalegra novalegra requested a review from ps2 June 26, 2020 16:32
Copy link
Contributor

@ps2 ps2 left a 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.

@rickpasetto
Copy link
Contributor

@novalegra @ps2 I will need to utilize the Dexcom and Dash icons for my work. Would it make sense to put them into LoopKitUI?

@ps2
Copy link
Contributor

ps2 commented Jun 26, 2020

@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.

Copy link
Contributor

@darinkrauss darinkrauss left a 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! 🏎️

TidepoolServiceKit/Extensions/Prescription.swift Outdated Show resolved Hide resolved
TidepoolServiceKit/Extensions/Prescription.swift Outdated Show resolved Hide resolved
public static func hours(_ hours: Double) -> TimeInterval {
return TimeInterval(hours * 60 /* minutes in hr */ * 60 /* seconds in minute */)
}
}
Copy link
Contributor

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)
    }

Copy link
Contributor

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)
Copy link
Contributor

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.

Copy link
Contributor Author

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))],
Copy link
Contributor

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
Copy link
Contributor

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 {
Copy link
Contributor

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)
Copy link
Contributor

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?
Copy link
Contributor

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.

Copy link
Contributor Author

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()]
Copy link
Contributor

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?

Copy link
Contributor Author

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 */)
}
}
Copy link
Contributor

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 {
Copy link
Contributor

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?

Copy link
Contributor Author

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?

Copy link
Contributor

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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

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.

Copy link
Contributor

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() {
Copy link
Contributor

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?

Copy link
Contributor Author

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
Copy link
Contributor

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))
    }
}

Looks like this:
Screen Shot 2020-06-26 at 1 08 06 PM

import Foundation

extension TimeInterval {
public static func minutes(_ minutes: Double) -> TimeInterval {
Copy link
Contributor

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?

Copy link
Contributor

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?".

Copy link
Contributor

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.

@novalegra novalegra merged commit 0471b79 into dev Jun 29, 2020
@novalegra novalegra deleted the novalegra/LOOP-1479 branch June 29, 2020 23:00
ps2 added a commit that referenced this pull request Apr 21, 2023
* 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
ps2 added a commit that referenced this pull request May 3, 2023
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants