Skip to content

Basal picker#4

Merged
ps2 merged 30 commits intodevfrom
basal-picker
Mar 5, 2019
Merged

Basal picker#4
ps2 merged 30 commits intodevfrom
basal-picker

Conversation

@ps2
Copy link

@ps2 ps2 commented Feb 27, 2019

A new basal schedule editor using a UIPickerView for modifying entries. Constrains user input to specified value and time increments, and prevents creation of invalid schedules. Also expected to help prevent decimal place errors. Back button is renamed "cancel" when changes are unsynced.

https://trello.com/c/IkRMs6mB/345-basal-schedule-entry-constraints

Copy link

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

A number of comments. Overall I like the design. Not sure I agree with the separation of the whole number from fractional number in the picker though. It is very odd to go from 2.00 to 1.90. Was using a single number component too clunky going from 0.00 to 5.00 in 0.05 steps?

Oh, and there are some issues with leaving the picker open when tapping Edit or +.


func validate() {
if delegate?.validateBasalScheduleEntryTableViewCell(self) == true
{

Choose a reason for hiding this comment

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

{ on preceding line? Plus, no need for == true.

Choose a reason for hiding this comment

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

[nit] ping

Copy link
Author

Choose a reason for hiding this comment

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

The result of this expression is an optional. If there is no delegate for validation, we don't consider the value invalid.

let time = startTimeForTimeComponent(row: row)
return stringForStartTime(time)
case .whole:
return valueNumberFormatter.string(from: Double(row))

Choose a reason for hiding this comment

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

wholeNumberFormatter?

Copy link
Author

Choose a reason for hiding this comment

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

OBE

case .whole:
return valueNumberFormatter.string(from: Double(row))
case .fractional:
return valueNumberFormatter.string(from: Double(row) * minimumRateIncrement)

Choose a reason for hiding this comment

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

fractionalNumberFormatter?

Copy link
Author

Choose a reason for hiding this comment

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

OBE

}

func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
let w = pickerView.frame.size.width

Choose a reason for hiding this comment

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

w -> width?

Copy link
Author

Choose a reason for hiding this comment

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

OBE

case .time:
return Int(round((maximumStartTime - minimumStartTime) / pickerInterval) + 1)
case .whole:
return maximumWholeValue + 1

Choose a reason for hiding this comment

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

(maximumWholeValue - minimumWholeValue) + 1?

Copy link
Author

Choose a reason for hiding this comment

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

OBE

@darinkrauss
Copy link

FYI - There are a couple of comments that are stale due to some commits you added recently.

@ps2
Copy link
Author

ps2 commented Feb 28, 2019

A number of comments. Overall I like the design. Not sure I agree with the separation of the whole number from fractional number in the picker though. It is very odd to go from 2.00 to 1.90. Was using a single number component too clunky going from 0.00 to 5.00 in 0.05 steps?

The issue is that max basal rate can be 35 on mm pumps, and 30 on omnipod, and MM can do 0.025 rates as well. Though I do think it could be smart to switch to a single spinner column if the user's max basal is low enough to make it work.

Copy link

@mpangburn mpangburn left a comment

Choose a reason for hiding this comment

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

Bug: it's possible to add more than the maximum allowed number of rates.
To reproduce:

  • Add to maximum number of rates. (add button will disable)
  • Sync with pump. (add button will reenable)
  • Tap add again, and the max number of rates has been bypassed.

@mpangburn
Copy link

And a UI bug: if there exist basal rates at both the first second possible times, the date picker of the first cell shows no date.
screen shot 2019-02-28 at 11 50 55 am

@darinkrauss
Copy link

Going to review it again now. One more minor bug:

  1. Add a couple basal rate schedules.
  2. Edit a basal rate schedule and spin the basal rate spinner.
  3. While it is slowing down, tap the Edit button. The basal rate schedule closes.
  4. Tap the Done button.
  5. Open the basal rate schedule you were editing in Mock managers update #2.
  6. Note that the basal rate spinner has a different value than the basal rate label.

Copy link

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

Thanks for making all of the changes. Looking good!

Copy link

@mpangburn mpangburn left a comment

Choose a reason for hiding this comment

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

Looks good!

The potentially misnamed isCellEditingPossible is my only concern, though the controller behavior seems right.. Otherwise, just nits.

}

private var isCellEditingPossible: Bool {
return isReadOnly || isSyncInProgress

Choose a reason for hiding this comment

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

I think this property returns the negation of its name.

tableView.endEditing(false)

var startTime = TimeInterval(0)
var value = allowedBasalRates.count > 0 ? allowedBasalRates[0] : 0

Choose a reason for hiding this comment

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

allowedBasalRates.first ?? 0

updateSyncButton()
}

var preferredValueFractionDigits: Int = 1

Choose a reason for hiding this comment

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

Why a stored property here?

}

public var isScheduleValid: Bool {
return scheduleItems.endIndex > 0 &&

Choose a reason for hiding this comment

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

!scheduleItems.isEmpty


public var isScheduleValid: Bool {
return scheduleItems.endIndex > 0 &&
scheduleItems.endIndex <= maximumScheduleItemCount &&

Choose a reason for hiding this comment

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

Nit: This one I think is clearer as scheduleItems.count, since it's being used to refer to the number of items rather than an index.

}

private func updateTimeLimitsFor(itemAt index: Int) {
guard index > 0 && index < scheduleItems.endIndex else {

Choose a reason for hiding this comment

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

scheduleItems.indices.contains(index)


super.tableView(tableView, commit: editingStyle, forRowAt: indexPath)

if scheduleItems.endIndex == 1 {

Choose a reason for hiding this comment

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

Nit: scheduleItems.count

super.setSelected(selected, animated: animated)

if selected && !isReadOnly {
isPickerHidden = !isPickerHidden

Choose a reason for hiding this comment

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

Nit: isPickerHidden.toggle()

Copy link
Author

Choose a reason for hiding this comment

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

Nice! Didn't know about that.

case .short, .medium:
return LocalizedString("U/hr", comment: "The short unit display string for international units of insulin per hour")
case .long:
return LocalizedString("Units/Hour", comment: "The long unit display string for international units of insulin per hour")

Choose a reason for hiding this comment

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

I don't think "Hour" should be capitalized.

Do we need to have a pluralized version? So, "1 Unit/hour" and "2 Units/hour"?

@darinkrauss
Copy link

This is all good from my perspective. 🚀

@ps2 ps2 merged commit 35b6897 into dev Mar 5, 2019
@ps2 ps2 deleted the basal-picker branch March 5, 2019 17:47
ps2 pushed a commit that referenced this pull request Apr 21, 2023
* Add unannounced meal func to CarbStoreProtocol

* V1 of UAM algo

* Fix issues with time intervals not lining up

* Fix issue with ICE not being cumulative

* Make carb threshold a static const

* Handle errors in fetching glucose effects

* Add date range unit test

* Style improvement

* Update build settings so watch can access LoopNotificationUserInfoKey

* Add debug info for feature to carbstore

* Remove unneeded variable set

* Save last UAM notification time in UserDefaults

* Add debug info

* Update naming to make constant transition easier

* Use constants instead of variables

* Only restrict notification delivery so we don't detect the same meal twice

* Retract UAM notifications after the carbs have expired

* Simplify change & meal checks into a single threshold

* Fix effects from outside of the search window being included & triggering false-positive UAM detections

* Improve function naming

* Add enhanced debug info

* Move notification logic from LoopKit to Loop

* Improve debug logs

* Make current date configurable

* Add preliminary tests

* Add test

* Fix carb entries being purged bc of current date not being set during unit testing

* Update UAM tests

* Update status enum naming for point-of-use clarity

* Fix observation start for unit testing

* Pull UAM constants into separate struct

* Fix unexpected effect not including all needed effects

* Fix carb effects not being carb _counteraction_ effects

* Update tests for algo changes

* Add noisy cgm testcase

* Add test for non-unannounced meal with COB

* Add realistic unannounced meal test

* Improve algo commenting

* Update UAM algo to detect more-aggressively

* Remove TODO

* Update debug information

* Schedule missed meal notifications to avoid notification during an microbolus (#2)

* Add ability to estimate delivery duration of bolus

* Add tests for UAM notification delay

* Update `estimatedDuration` function headers in response to PR feedback

* Add UAM banner to carb entry screen (#4)

* UAM algo updates: only use directly observed carb absorption (#5)

* Add ability to calculate the number of carbs in a missed meal (#3)

* Plumb a customizable carb amount through UAM notification architecture

* Refactor carb effect threshold computation into helper function

* Add first draft of dynamic carb selection for meal

* Bump max carb autofill limit from 100 -> 150

* Add tests for carb autofill

* Bump max carb threshold limit because of dynamic autofill clamping

* Lower the UAM max autofill

* Require that unannounced meals have ICE onboard that is >= the meal threshold, and only autofill observed ICE

* Lower ICE threshold for a UAM notification

* Update tests

* Improve debug info

* Update commenting

* Remove unused test

* Update test

* `dateRange` -> `simulationDateRange`

* Create `MealDetectionManager` from old UAM functions in `CarbStore`

* Add fixme

* Updates based on feedback for UAM PR (#6)

* `dateRange` -> `simulationDateRange`

* Create `MealDetectionManager` from old UAM functions in `CarbStore`

* Add fixme

* Move UAM test fixtures from LoopKit to Loop

* Unannounced meal / UAM -> missed meal
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.

3 participants