implement BG Check treatments

- BG Checks (finger prick) can be entered as a treatment
- will be converted and stored in coredata as mg/dl (to avoid having to add units to the treatment coredata model)
- will be converted and shown locally in the correct unit
- will be synced with Nightscout in the original unit
- Nightscout uploads will add the "GlucoseType"="Finger" and "units" attributes to align with BG checks entered via Care Portal.
- BG checks displayed on the main chart as a red circle with gray border as per Nightscout style
- observer added to refresh treatment table (values/units) if the bg unit is changed by the user
This commit is contained in:
Paul Plant 2022-05-20 13:22:33 +02:00
parent 24a7e2b1f4
commit 87d2ae296f
15 changed files with 224 additions and 67 deletions

View File

@ -55,10 +55,8 @@
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "32">
<RemoteRunnable
runnableDebuggingMode = "2"
BundleIdentifier = "com.apple.Carousel"
RemotePath = "/(null)">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "470B6185270C448000561E56"
@ -66,7 +64,7 @@
BlueprintName = "Watch App"
ReferencedContainer = "container:xdrip.xcodeproj">
</BuildableReference>
</RemoteRunnable>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@ -75,10 +73,8 @@
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
launchAutomaticallySubstyle = "32">
<RemoteRunnable
runnableDebuggingMode = "2"
BundleIdentifier = "com.apple.Carousel"
RemotePath = "/(null)">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "470B6185270C448000561E56"
@ -86,16 +82,7 @@
BlueprintName = "Watch App"
ReferencedContainer = "container:xdrip.xcodeproj">
</BuildableReference>
</RemoteRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "470B6185270C448000561E56"
BuildableName = "xDrip4iO5.app"
BlueprintName = "Watch App"
ReferencedContainer = "container:xdrip.xcodeproj">
</BuildableReference>
</MacroExpansion>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">

View File

@ -54,10 +54,8 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<RemoteRunnable
runnableDebuggingMode = "2"
BundleIdentifier = "com.apple.Carousel"
RemotePath = "/(null)">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "470B6185270C448000561E56"
@ -65,7 +63,7 @@
BlueprintName = "Watch App"
ReferencedContainer = "container:xdrip.xcodeproj">
</BuildableReference>
</RemoteRunnable>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@ -73,10 +71,8 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<RemoteRunnable
runnableDebuggingMode = "2"
BundleIdentifier = "com.apple.Carousel"
RemotePath = "/(null)">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "470B6185270C448000561E56"
@ -84,16 +80,7 @@
BlueprintName = "Watch App"
ReferencedContainer = "container:xdrip.xcodeproj">
</BuildableReference>
</RemoteRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "470B6185270C448000561E56"
BuildableName = "xDrip4iO5.app"
BlueprintName = "Watch App"
ReferencedContainer = "container:xdrip.xcodeproj">
</BuildableReference>
</MacroExpansion>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">

View File

@ -68,6 +68,8 @@ enum ConstantsGlucoseChart {
/// color for target line
static let guidelineTargetColor = UIColor.green.withAlphaComponent(0.3)
// glucose circle/dot color and sizes
/// glucose colors - for values in range
static let glucoseInRangeColor = UIColor.green
@ -89,6 +91,8 @@ enum ConstantsGlucoseChart {
/// diameter of the circle for blood glucose readings with a 24h chart width. The more hours on the chart, the smaller the circles should be
static let glucoseCircleDiameter24h: CGFloat = 4
// calibration circle fill/border color/sizes
/// calibration inner circle color
static let calibrationCircleColorInner = UIColor.red
@ -101,6 +105,8 @@ enum ConstantsGlucoseChart {
/// calibration inner circle scale factor compared to the chart glucose circle size
static let calibrationCircleScaleInner: CGFloat = 1.4
// bolus treatment marker color/sizes
/// bolus treament marker colour
static let bolusTreatmentColor = UIColor.systemBlue
@ -131,6 +137,8 @@ enum ConstantsGlucoseChart {
/// make the triangle height slightly less than the width to prevent it looking too "pointy"
static let bolusTriangleHeightScale: CGFloat = 0.9
// carb treatment marker color/sizes
/// carbs treament marker colour
static let carbsTreatmentColor = UIColor.systemOrange
@ -154,6 +162,22 @@ enum ConstantsGlucoseChart {
/// The scale will determine how big the veryLargeCarbs circle is scaled compared to the glucose point size)
static let veryLargeCarbsTreamentScale: CGFloat = 6.6
// bg check circle fill/border color/sizes
/// bg check outer circle color
static let bgCheckTreatmentColorOuter = UIColor.gray
/// bg check inner circle color
static let bgCheckTreatmentColorInner = UIColor.red
/// bg check outer circle scale factor compared to the chart glucose circle size
static let bgCheckTreatmentScaleOuter: CGFloat = 1.9
/// bg check inner circle scale factor compared to the chart glucose circle size
static let bgCheckTreatmentScaleInner: CGFloat = 1.4
// treatment label font size/color/background
/// default label settings for the treatments labels. These are set for 6hr chart width - they will be scaled accordingly as needed
static let treatmentLabelFontSize: Double = 12
@ -182,6 +206,8 @@ enum ConstantsGlucoseChart {
/// how far should the label be separated from the veryLargeCarbs marker by default
static let veryLargeCarbsLabelSeparation: Double = 12
// chart format parameters
/// labels width for vertical axis
static let yAxisLabelsWidth: CGFloat = 30

View File

@ -18,5 +18,11 @@ enum ConstantsNightScout {
/// download treatments from nightscout, how manyhours
static let maxHoursTreatmentsToDownload = 24.0
/// the text used by Nightscout for the "unit" json attribute for BG Checks stored in mg/dl
static let mgDlNightscoutUnitString = "mg/dl"
/// the text used by Nightscout for the "unit" json attribute for BG Checks stored in mmol/l
static let mmolNightscoutUnitString = "mmol"
}

View File

@ -19,6 +19,7 @@ import CoreData
case Insulin
case Carbs
case Exercise
case BgCheck
/// String representation.
public func asString() -> String {
@ -29,6 +30,8 @@ import CoreData
return Texts_TreatmentsView.carbs
case .Exercise:
return Texts_TreatmentsView.exercise
case .BgCheck:
return Texts_TreatmentsView.bgCheck
default:
return Texts_TreatmentsView.questionMark
}
@ -43,6 +46,8 @@ import CoreData
return Texts_TreatmentsView.carbsUnit
case .Exercise:
return Texts_TreatmentsView.exerciseUnit
case .BgCheck:
return UserDefaults.standard.bloodGlucoseUnitIsMgDl ? Texts_Common.mgdl : Texts_Common.mmol
default:
return Texts_TreatmentsView.questionMark
}
@ -69,6 +74,9 @@ import CoreData
case .Exercise:
return "exericse"
case .BgCheck:
return "glucose"
}
}
@ -157,7 +165,25 @@ public class TreatmentEntry: NSManagedObject, Comparable {
/// Returns the displayValue: the .value with the proper unit.
public func displayValue() -> String {
return self.value.stringWithoutTrailingZeroes + " " + self.treatmentType.unit()
var displayValueString: String
// if the treatmentType is a BG Check then convert the value to mmol/l if that is what the user is using. All BG checks are stored in coredata as mg/dl
if self.treatmentType == .BgCheck {
// save typing
let isMgDl: Bool = UserDefaults.standard.bloodGlucoseUnitIsMgDl
// convert to mmol/l if needed, round accordingly and add the correct units
displayValueString = self.value.mgdlToMmol(mgdl: isMgDl).bgValueRounded(mgdl: isMgDl).stringWithoutTrailingZeroes + " " + String(isMgDl ? Texts_Common.mgdl : Texts_Common.mmol)
} else {
displayValueString = self.value.stringWithoutTrailingZeroes + " " + self.treatmentType.unit()
}
return displayValueString
}
/// - get the dictionary representation required for creating a new treatment @ NighScout using POST or updating an existing treatment @ NightScout using PUT
@ -188,6 +214,11 @@ public class TreatmentEntry: NSManagedObject, Comparable {
case .Exercise:
dict["eventType"] = "Exercise" // maybe overwritten in next statement
dict["duration"] = self.value
case .BgCheck:
dict["eventType"] = "BG Check" // maybe overwritten in next statement
dict["glucose"] = self.value
dict["glucoseType"] = "Finger"
dict["units"] = String(UserDefaults.standard.bloodGlucoseUnitIsMgDl ? ConstantsNightScout.mgDlNightscoutUnitString : ConstantsNightScout.mmolNightscoutUnitString)
default:
break
}

View File

@ -23,7 +23,7 @@ extension TreatmentEntry {
/// Date represents the date of the treatment, not the date of creation.
@NSManaged public var date: Date
/// Value represents the amount (e.g. insulin units or carbs grams).
/// Value represents the amount (e.g. insulin units, carbs grams, BG check glucose value).
@NSManaged public var value: Double
/// Enum TreatmentType defines which treatment this instance is.

View File

@ -33,6 +33,16 @@ extension ChartPoint {
}
/// the bg check treatment value is always stored in mg/dl so needs to be converted/rounded as required to show correctly on the chart
convenience init(bgCheck: TreatmentEntry, formatter: DateFormatter, unitIsMgDl: Bool) {
self.init(
x: ChartAxisValueDate(date: bgCheck.date, formatter: formatter),
y: ChartAxisValueDouble(bgCheck.value.mgdlToMmol(mgdl: unitIsMgDl).bgValueRounded(mgdl: unitIsMgDl))
)
}
/// the chartpoints defined for certain treatment entries (such as carbs) are positioned relative to other elements and need to be re-scaled to fit the y-axis values of the glucose chart points (and therefore avoid needing a secondary axis)
convenience init(treatmentEntry: TreatmentEntry, formatter: DateFormatter, newYAxisValue: Double? = 0) {

View File

@ -20,7 +20,8 @@ public class GlucoseChartManager {
/// - smallBolus = bolus values below the micro-bolus threshold (usually around 1.0U or less)
/// - mediumBolus = all boluses over the micro-bolus threshold ("normal" boluses and will be shown with a label)
/// - smallCarbs / mediumCarbs / largeCarbs / veryLargeCarbs = groups of each aproximate size to be represented by a different size chart point. The exact carb size context is given using a label
typealias TreatmentChartPointsType = (smallBolus: [ChartPoint], mediumBolus: [ChartPoint], smallCarbs: [ChartPoint], mediumCarbs: [ChartPoint], largeCarbs: [ChartPoint], veryLargeCarbs: [ChartPoint])
/// - bgCheck = blood glucose (finger) checks
typealias TreatmentChartPointsType = (smallBolus: [ChartPoint], mediumBolus: [ChartPoint], smallCarbs: [ChartPoint], mediumCarbs: [ChartPoint], largeCarbs: [ChartPoint], veryLargeCarbs: [ChartPoint], bgChecks: [ChartPoint])
// MARK: - private properties
@ -37,7 +38,7 @@ public class GlucoseChartManager {
private var calibrationChartPoints = [ChartPoint]()
/// treatmentChartPoints to be shown on chart
private var treatmentChartPoints: TreatmentChartPointsType = ([ChartPoint](), [ChartPoint](), [ChartPoint](), [ChartPoint](), [ChartPoint](), [ChartPoint]())
private var treatmentChartPoints: TreatmentChartPointsType = ([ChartPoint](), [ChartPoint](), [ChartPoint](), [ChartPoint](), [ChartPoint](), [ChartPoint](), [ChartPoint]())
/// smallBolusTreatmentChartPoints to be shown on chart
private var smallBolusTreatmentChartPoints = [ChartPoint]()
@ -56,6 +57,9 @@ public class GlucoseChartManager {
/// veryLargeCarbsTreatmentChartPoints to be shown on chart
private var veryLargeCarbsTreatmentChartPoints = [ChartPoint]()
/// bgCheckTreatmentChartPoints to be shown on chart
private var bgCheckTreatmentChartPoints = [ChartPoint]()
/// ChartPoints to be shown on chart, processed only in main thread - urgent Range
private var urgentRangeGlucoseChartPoints = [ChartPoint]()
@ -314,6 +318,7 @@ public class GlucoseChartManager {
self.treatmentChartPoints.mediumCarbs = treatmentChartPoints.mediumCarbs
self.treatmentChartPoints.largeCarbs = treatmentChartPoints.largeCarbs
self.treatmentChartPoints.veryLargeCarbs = treatmentChartPoints.veryLargeCarbs
self.treatmentChartPoints.bgChecks = treatmentChartPoints.bgChecks
}
@ -340,6 +345,9 @@ public class GlucoseChartManager {
self.mediumCarbsTreatmentChartPoints = self.treatmentChartPoints.mediumCarbs
self.largeCarbsTreatmentChartPoints = self.treatmentChartPoints.largeCarbs
self.veryLargeCarbsTreatmentChartPoints = self.treatmentChartPoints.veryLargeCarbs
// assign the BG check treatment chart points
self.bgCheckTreatmentChartPoints = self.treatmentChartPoints.bgChecks
// update the chart outlet
chartOutlet.reloadChart()
@ -775,6 +783,11 @@ public class GlucoseChartManager {
let calibrationCirclesInnerLayer = ChartPointsScatterCirclesLayer(xAxis: xAxisLayer.axis, yAxis: yAxisLayer.axis, chartPoints: calibrationChartPoints, displayDelay: 0, itemSize: CGSize(width: glucoseCircleDiameter * ConstantsGlucoseChart.calibrationCircleScaleInner, height: glucoseCircleDiameter * ConstantsGlucoseChart.calibrationCircleScaleInner), itemFillColor: ConstantsGlucoseChart.calibrationCircleColorInner, optimized: true)
// bg check treatment circle layers - we'll create two circles, one on top of the other to give a gray border as per Nightscout BG Checks. We'll make the inner circle UIColor.red to make it slightly different to the UIColor.systemRed used by the glucoseChartPoints. Both circles will be scaled as per the current glucoseCircleDiameter but bigger so that they stand out
let bgCheckCirclesOuterLayer = ChartPointsScatterCirclesLayer(xAxis: xAxisLayer.axis, yAxis: yAxisLayer.axis, chartPoints: bgCheckTreatmentChartPoints, displayDelay: 0, itemSize: CGSize(width: glucoseCircleDiameter * ConstantsGlucoseChart.bgCheckTreatmentScaleOuter , height: glucoseCircleDiameter * ConstantsGlucoseChart.bgCheckTreatmentScaleOuter), itemFillColor: ConstantsGlucoseChart.bgCheckTreatmentColorOuter, optimized: true)
let bgCheckCirclesInnerLayer = ChartPointsScatterCirclesLayer(xAxis: xAxisLayer.axis, yAxis: yAxisLayer.axis, chartPoints: bgCheckTreatmentChartPoints, displayDelay: 0, itemSize: CGSize(width: glucoseCircleDiameter * ConstantsGlucoseChart.bgCheckTreatmentScaleInner, height: glucoseCircleDiameter * ConstantsGlucoseChart.bgCheckTreatmentScaleInner), itemFillColor: ConstantsGlucoseChart.bgCheckTreatmentColorInner, optimized: true)
// bolus triangle layers
let mediumBolusTriangleLayer = ChartPointsScatterDownTrianglesWithDropdownLineLayer(xAxis: xAxisLayer.axis, yAxis: yAxisLayer.axis, chartPoints: mediumBolusTreatmentChartPoints, displayDelay: 0, itemSize: CGSize(width: bolusTriangleSize, height: bolusTriangleSize * ConstantsGlucoseChart.bolusTriangleHeightScale), itemFillColor: ConstantsGlucoseChart.bolusTreatmentColor)
@ -840,13 +853,13 @@ public class GlucoseChartManager {
}
let layersGlucoseCircles: [ChartLayer?] = [
// calibrationPoint layers
calibrationCirclesOuterLayer,
calibrationCirclesInnerLayer,
// glucosePoint layers
inRangeGlucoseCirclesLayer,
notUrgentRangeGlucoseCirclesLayer,
urgentRangeGlucoseCirclesLayer
urgentRangeGlucoseCirclesLayer,
// calibrationPoint layers
calibrationCirclesOuterLayer,
calibrationCirclesInnerLayer
]
layers.append(contentsOf: layersGlucoseCircles)
@ -854,6 +867,9 @@ public class GlucoseChartManager {
if UserDefaults.standard.showTreatmentsOnChart {
let layersTreatmentLabels: [ChartLayer?] = [
// bg check treatment layers
bgCheckCirclesOuterLayer,
bgCheckCirclesInnerLayer,
// treatment label layers
smallCarbsLabelsLayer,
mediumCarbsLabelsLayer,
@ -1032,7 +1048,7 @@ public class GlucoseChartManager {
/// - bgReadingsAccessor : bg readings accessor object
/// - managedObjectContext : the ManagedObjectContext to use
/// - returns: a tuple with chart point arrays for each classification of treatment type + size
private func getTreatmentEntryChartPoints(startDate: Date, endDate: Date, treatmentEntryAccessor: TreatmentEntryAccessor, bgReadingsAccessor: BgReadingsAccessor, on managedObjectContext: NSManagedObjectContext) -> ([ChartPoint], [ChartPoint], [ChartPoint], [ChartPoint], [ChartPoint], [ChartPoint]) {
private func getTreatmentEntryChartPoints(startDate: Date, endDate: Date, treatmentEntryAccessor: TreatmentEntryAccessor, bgReadingsAccessor: BgReadingsAccessor, on managedObjectContext: NSManagedObjectContext) -> ([ChartPoint], [ChartPoint], [ChartPoint], [ChartPoint], [ChartPoint], [ChartPoint], [ChartPoint]) {
// get treaments between the two timestamps from coredata
let treatmentEntries = treatmentEntryAccessor.getTreatments(fromDate: startDate, toDate: endDate, on: managedObjectContext)
@ -1040,11 +1056,14 @@ public class GlucoseChartManager {
// intialize the treatment chart point arrays
var smallBolusTreatmentEntryChartPoints = [ChartPoint]()
var mediumBolusTreatmentEntryChartPoints = [ChartPoint]()
var smallCarbsTreatmentEntryChartPoints = [ChartPoint]()
var mediumCarbsTreatmentEntryChartPoints = [ChartPoint]()
var largeCarbsTreatmentEntryChartPoints = [ChartPoint]()
var veryLargeCarbsTreatmentEntryChartPoints = [ChartPoint]()
var bgCheckTreatmentEntryChartPoints = [ChartPoint]()
managedObjectContext.performAndWait {
// filter the treatment entries that have not been marked as deleted and append them to the relevant chart point array
@ -1085,6 +1104,10 @@ public class GlucoseChartManager {
}
case .BgCheck:
bgCheckTreatmentEntryChartPoints.append(ChartPoint(bgCheck: treatmentEntry, formatter: data().chartPointDateFormatter, unitIsMgDl: UserDefaults.standard.bloodGlucoseUnitIsMgDl))
default:
break
@ -1095,7 +1118,7 @@ public class GlucoseChartManager {
}
// return all treatment arrays based upon treatment type and size (as defined by the threshold values)
return (smallBolusTreatmentEntryChartPoints, mediumBolusTreatmentEntryChartPoints, smallCarbsTreatmentEntryChartPoints, mediumCarbsTreatmentEntryChartPoints, largeCarbsTreatmentEntryChartPoints, veryLargeCarbsTreatmentEntryChartPoints)
return (smallBolusTreatmentEntryChartPoints, mediumBolusTreatmentEntryChartPoints, smallCarbsTreatmentEntryChartPoints, mediumCarbsTreatmentEntryChartPoints, largeCarbsTreatmentEntryChartPoints, veryLargeCarbsTreatmentEntryChartPoints, bgCheckTreatmentEntryChartPoints)
}
@ -1285,7 +1308,7 @@ public class GlucoseChartManager {
stopDeceleration()
glucoseChartPoints = ([ChartPoint](), [ChartPoint](), [ChartPoint](), nil, nil, nil)
treatmentChartPoints = ([ChartPoint](), [ChartPoint](), [ChartPoint](), [ChartPoint](), [ChartPoint](), [ChartPoint]())
treatmentChartPoints = ([ChartPoint](), [ChartPoint](), [ChartPoint](), [ChartPoint](), [ChartPoint](), [ChartPoint](), [ChartPoint]())
calibrationChartPoints = [ChartPoint]()
@ -1296,6 +1319,8 @@ public class GlucoseChartManager {
mediumCarbsTreatmentChartPoints = [ChartPoint]()
largeCarbsTreatmentChartPoints = [ChartPoint]()
bgCheckTreatmentChartPoints = [ChartPoint]()
chartSettings = nil
chartPointDateFormatter = nil

View File

@ -601,6 +601,10 @@ public class NightScoutUploadManager: NSObject {
case .Exercise:
treatmentToUploadToNightscoutAsDictionary["duration"] = otherTreatmentEntry.value
case .BgCheck:
treatmentToUploadToNightscoutAsDictionary["glucose"] = otherTreatmentEntry.value.mgdlToMmol(mgdl: UserDefaults.standard.bloodGlucoseUnitIsMgDl).bgValueRounded(mgdl: UserDefaults.standard.bloodGlucoseUnitIsMgDl)
treatmentToUploadToNightscoutAsDictionary["units"] = String(UserDefaults.standard.bloodGlucoseUnitIsMgDl ? ConstantsNightScout.mgDlNightscoutUnitString : ConstantsNightScout.mmolNightscoutUnitString)
default:
break

View File

@ -1834,16 +1834,16 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="40" translatesAutoresizingMaskIntoConstraints="NO" id="Ltu-UO-Ypa">
<rect key="frame" x="35" y="168" width="320" height="378"/>
<rect key="frame" x="35" y="168" width="320" height="422"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="lph-Ca-ZpH">
<rect key="frame" x="36" y="0.0" width="248" height="122"/>
<rect key="frame" x="30.333333333333343" y="0.0" width="259.33333333333326" height="166"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="ZDj-72-yiq" userLabel="Carbs Stack">
<rect key="frame" x="0.0" y="0.0" width="248" height="34"/>
<rect key="frame" x="0.0" y="0.0" width="259.33333333333331" height="34"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Carbs (g):" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fvP-G8-Gxk">
<rect key="frame" x="0.0" y="5.6666666666666572" width="140" height="23"/>
<rect key="frame" x="0.0" y="5.6666666666666572" width="151.33333333333334" height="23"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="140" id="XcQ-AC-RqE"/>
</constraints>
@ -1852,7 +1852,7 @@
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="0" textAlignment="center" minimumFontSize="16" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="saY-fP-EEW">
<rect key="frame" x="148" y="0.0" width="100" height="34"/>
<rect key="frame" x="159.33333333333331" y="0.0" width="100" height="34"/>
<constraints>
<constraint firstAttribute="width" constant="100" id="o92-Cd-7bP"/>
</constraints>
@ -1862,10 +1862,10 @@
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="4G8-c0-IhJ" userLabel="Insulin Stack">
<rect key="frame" x="0.0" y="44" width="248" height="34"/>
<rect key="frame" x="0.0" y="44" width="259.33333333333331" height="34"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Insulin (U):" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="agq-m8-HZ9">
<rect key="frame" x="0.0" y="0.0" width="140" height="34"/>
<rect key="frame" x="0.0" y="0.0" width="151.33333333333334" height="34"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="140" id="PIM-iN-dt1"/>
</constraints>
@ -1874,7 +1874,7 @@
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="0.0" textAlignment="center" minimumFontSize="16" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="ggq-1D-Pj6">
<rect key="frame" x="148" y="0.0" width="100" height="34"/>
<rect key="frame" x="159.33333333333331" y="0.0" width="100" height="34"/>
<constraints>
<constraint firstAttribute="width" constant="100" id="KId-Tf-1aj"/>
</constraints>
@ -1884,10 +1884,10 @@
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="OJv-t7-QLZ" userLabel="Exercise Stack">
<rect key="frame" x="0.0" y="88" width="248" height="34"/>
<rect key="frame" x="0.0" y="88" width="259.33333333333331" height="34"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Exercise (min):" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9tx-02-GXf">
<rect key="frame" x="0.0" y="0.0" width="140" height="34"/>
<rect key="frame" x="0.0" y="0.0" width="151.33333333333334" height="34"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="140" id="c4f-TH-tJq"/>
</constraints>
@ -1896,7 +1896,7 @@
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="0" textAlignment="center" minimumFontSize="16" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="kl9-iM-MlH">
<rect key="frame" x="148" y="0.0" width="100" height="34"/>
<rect key="frame" x="159.33333333333331" y="0.0" width="100" height="34"/>
<constraints>
<constraint firstAttribute="width" constant="100" id="1kf-sW-mbf"/>
</constraints>
@ -1905,10 +1905,29 @@
</textField>
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6xP-xO-cse">
<rect key="frame" x="0.0" y="132" width="259.33333333333331" height="34"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="BG Check (mg/dl):" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Gju-y6-ier">
<rect key="frame" x="0.0" y="0.0" width="159.33333333333334" height="34"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="19"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="0" textAlignment="center" minimumFontSize="16" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="3jm-to-PhE">
<rect key="frame" x="159.33333333333331" y="0.0" width="100" height="34"/>
<constraints>
<constraint firstAttribute="width" constant="100" id="xoy-M1-gJY"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" keyboardType="decimalPad" smartInsertDeleteType="no" smartQuotesType="no"/>
</textField>
</subviews>
</stackView>
</subviews>
</stackView>
<datePicker contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" datePickerMode="dateAndTime" minuteInterval="1" style="wheels" translatesAutoresizingMaskIntoConstraints="NO" id="M5l-qV-xjH">
<rect key="frame" x="0.0" y="162" width="320" height="216"/>
<rect key="frame" x="0.0" y="206" width="320" height="216"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="color" keyPath="textColor">
<color key="value" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@ -1938,6 +1957,9 @@
</navigationItem>
<simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
<connections>
<outlet property="bgCheckLabel" destination="Gju-y6-ier" id="IIt-rx-jiD"/>
<outlet property="bgCheckStackView" destination="6xP-xO-cse" id="B9Q-UA-Ant"/>
<outlet property="bgCheckTextField" destination="3jm-to-PhE" id="gBA-zV-4F2"/>
<outlet property="carbsLabel" destination="fvP-G8-Gxk" id="d2b-CN-IK4"/>
<outlet property="carbsStackView" destination="ZDj-72-yiq" id="HEu-wA-BlI"/>
<outlet property="carbsTextField" destination="saY-fP-EEW" id="4DV-cC-8cz"/>

View File

@ -21,7 +21,7 @@ class Texts_Common {
return NSLocalizedString("common_mmol", tableName: filename, bundle: Bundle.main, value: "mmol/l", comment: "mmol/l")
}()
static let bloodGLucoseUnit: String = {
static let bloodGlucoseUnit: String = {
return NSLocalizedString("common_bloodglucoseunit", tableName: filename, bundle: Bundle.main, value: "Blood Glucose Unit", comment: "can be used in several screens, just the words Bloodglucose unit")
}()

View File

@ -51,6 +51,10 @@ enum Texts_TreatmentsView {
static let exercise:String = {
return NSLocalizedString("treatments_exercise", tableName: filename, bundle: Bundle.main, value: "Exercise", comment: "Exercise.")
}()
static let bgCheck:String = {
return NSLocalizedString("treatments_bgcheck", tableName: filename, bundle: Bundle.main, value: "BG Check", comment: "Blood Glucose Check")
}()
static let questionMark:String = {
return NSLocalizedString("treatments_question_mark", tableName: filename, bundle: Bundle.main, value: "?", comment: "Literally a question mark, used as unknown abbreviation.")

View File

@ -26,13 +26,19 @@ public struct TreatmentNSResponse {
/// - eventType received from NightScout (for downloaded treatments) or uploaded to NightScout (for treatments created in xdrip4ios)
public let nightscoutEventType: String?
/// - eventType received from NightScout (for downloaded treatments) or uploaded to NightScout (for treatments created in xdrip4ios)
// public let nightscoutGlucoseType: String?
/// - eventType received from NightScout (for downloaded treatments) or uploaded to NightScout (for treatments created in xdrip4ios)
// public let nightscoutUnits: String?
public let value: Double
/// Takes a NSDictionary from nightscout response and returns an array TreatmentNSResponse. Can be more than one, eg NightScout treatment of type 'Snack Bolus' could contain an insulin value and a carbs value
///
/// id will be the id retrieved from nightscout + "-insulin", "-carbs", "-exercise", according to treatment type
public static func fromNighscout(dictionary: NSDictionary) -> [TreatmentNSResponse] {
public static func fromNightscout(dictionary: NSDictionary) -> [TreatmentNSResponse] {
var treatmentNSResponses: [TreatmentNSResponse] = []
@ -90,6 +96,12 @@ public struct TreatmentNSResponse {
treatmentNSResponses.append(TreatmentNSResponse(id: id + TreatmentType.Carbs.idExtension(), createdAt: date, eventType: .Exercise, nightscoutEventType: nightScoutEventType, value: duration))
}
if let glucose = dictionary["glucose"] as? Double, let units = dictionary["units"] as? String {
treatmentNSResponses.append(TreatmentNSResponse(id: id + TreatmentType.BgCheck.idExtension(), createdAt: date, eventType: .BgCheck, nightscoutEventType: nightScoutEventType, value: units == "mg/dl" ? glucose : glucose.mmolToMgdl()))
}
}
@ -106,7 +118,7 @@ public struct TreatmentNSResponse {
for element in array {
if let dictionary = element as? NSDictionary {
responses = responses + TreatmentNSResponse.fromNighscout(dictionary: dictionary)
responses = responses + TreatmentNSResponse.fromNightscout(dictionary: dictionary)
}
}

View File

@ -12,18 +12,26 @@ import Foundation
class TreatmentsInsertViewController : UIViewController {
@IBOutlet weak var titleNavigation: UINavigationItem!
@IBOutlet weak var carbsLabel: UILabel!
@IBOutlet weak var insulinLabel: UILabel!
@IBOutlet weak var exerciseLabel: UILabel!
@IBOutlet weak var bgCheckLabel: UILabel!
@IBOutlet weak var doneButton: UIBarButtonItem!
@IBOutlet weak var datePicker: UIDatePicker!
@IBOutlet weak var carbsTextField: UITextField!
@IBOutlet weak var insulinTextField: UITextField!
@IBOutlet weak var exerciseTextField: UITextField!
@IBOutlet weak var bgCheckTextField: UITextField!
@IBOutlet weak var carbsStackView: UIStackView!
@IBOutlet weak var insulinStackView: UIStackView!
@IBOutlet weak var exerciseStackView: UIStackView!
@IBOutlet weak var bgCheckStackView: UIStackView!
// MARK: - private properties
/// reference to coreDataManager
@ -67,15 +75,20 @@ class TreatmentsInsertViewController : UIViewController {
// Title
self.titleNavigation.title = Texts_TreatmentsView.newEntryTitle
// update the BG Check placeholder text depending on BG unit being used
self.bgCheckTextField.placeholder = Double(0).mgdlToMmolAndToString(mgdl: UserDefaults.standard.bloodGlucoseUnitIsMgDl)
// Labels for each TextField
self.carbsLabel.text = Texts_TreatmentsView.carbsWithUnit
self.insulinLabel.text = Texts_TreatmentsView.insulinWithUnit
self.exerciseLabel.text = Texts_TreatmentsView.exerciseWithUnit
self.bgCheckLabel.text = Texts_TreatmentsView.bgCheck + " (" + String(UserDefaults.standard.bloodGlucoseUnitIsMgDl ? Texts_Common.mgdl : Texts_Common.mmol) + ")"
// Done button
self.addDoneButtonOnNumpad(textField: self.carbsTextField)
self.addDoneButtonOnNumpad(textField: self.insulinTextField)
self.addDoneButtonOnNumpad(textField: self.exerciseTextField)
self.addDoneButtonOnNumpad(textField: self.bgCheckTextField)
self.setDismissKeyboard()
@ -94,6 +107,9 @@ class TreatmentsInsertViewController : UIViewController {
exerciseTextField.isHidden = true
exerciseLabel.isHidden = true
exerciseStackView.isHidden = true
bgCheckTextField.isHidden = true
bgCheckLabel.isHidden = true
bgCheckStackView.isHidden = true
case .Exercise:
// set text to value of treatMentEntryToUpdate
@ -106,6 +122,9 @@ class TreatmentsInsertViewController : UIViewController {
insulinTextField.isHidden = true
insulinLabel.isHidden = true
insulinStackView.isHidden = true
bgCheckTextField.isHidden = true
bgCheckLabel.isHidden = true
bgCheckStackView.isHidden = true
case .Insulin:
// set text to value of treatMentEntryToUpdate
@ -118,6 +137,25 @@ class TreatmentsInsertViewController : UIViewController {
exerciseTextField.isHidden = true
exerciseLabel.isHidden = true
exerciseStackView.isHidden = true
bgCheckTextField.isHidden = true
bgCheckLabel.isHidden = true
bgCheckStackView.isHidden = true
case .BgCheck:
// set text to value of treatMentEntryToUpdate
// as the BG Check values are always stored in coredata as mg/dl, the number must be converted and rounded as needed
bgCheckTextField.text = treatMentEntryToUpdate.value.mgdlToMmol(mgdl: UserDefaults.standard.bloodGlucoseUnitIsMgDl).bgValueRounded(mgdl: UserDefaults.standard.bloodGlucoseUnitIsMgDl).stringWithoutTrailingZeroes
// hide the other fields
insulinTextField.isHidden = true
insulinLabel.isHidden = true
insulinStackView.isHidden = true
carbsTextField.isHidden = true
carbsLabel.isHidden = true
carbsStackView.isHidden = true
exerciseTextField.isHidden = true
exerciseLabel.isHidden = true
exerciseStackView.isHidden = true
}
@ -209,6 +247,9 @@ class TreatmentsInsertViewController : UIViewController {
case .Exercise:
updateFunction(exerciseTextField)
case .BgCheck:
updateFunction(bgCheckTextField)
}
} else {
@ -248,6 +289,7 @@ class TreatmentsInsertViewController : UIViewController {
createFunction(carbsTextField.text, .Carbs)
createFunction(insulinTextField.text, .Insulin)
createFunction(exerciseTextField.text, .Exercise)
createFunction(bgCheckTextField.text, .BgCheck)
}

View File

@ -46,7 +46,8 @@ class TreatmentsViewController : UIViewController {
// add observer for nightScoutTreatmentsUpdateCounter, to reload the screen whenever the value changes
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.nightScoutTreatmentsUpdateCounter.rawValue, options: .new, context: nil)
// add observer for bloodGlucoseUnitIsMgDl, to reload the screen whenever the bg unit changes
UserDefaults.standard.addObserver(self, forKeyPath: UserDefaults.Key.bloodGlucoseUnitIsMgDl.rawValue, options: .new, context: nil)
}
@ -109,7 +110,7 @@ class TreatmentsViewController : UIViewController {
switch keyPathEnum {
case UserDefaults.Key.nightScoutTreatmentsUpdateCounter :
case UserDefaults.Key.nightScoutTreatmentsUpdateCounter, UserDefaults.Key.bloodGlucoseUnitIsMgDl :
// Reloads data and table.
self.reload()