Jon Fawcett пре 6 година
родитељ
комит
59ecc6e39a

+ 2 - 13
LoopFollow/Application/Base.lproj/Main.storyboard

@@ -106,16 +106,9 @@
                                 </subviews>
                             </stackView>
                             <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="51B-JU-npv" customClass="LineChartView" customModule="Charts">
-                                <rect key="frame" x="8" y="312" width="398" height="297"/>
+                                <rect key="frame" x="8" y="312" width="398" height="425"/>
                                 <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
                             </view>
-                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="AOf-kb-6CI" userLabel="BasalChart" customClass="LineChartView" customModule="Charts">
-                                <rect key="frame" x="8" y="617" width="398" height="120"/>
-                                <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
-                                <constraints>
-                                    <constraint firstAttribute="height" priority="750" constant="120" id="qWe-ZM-8HL"/>
-                                </constraints>
-                            </view>
                             <view autoresizesSubviews="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Nml-lr-dgQ" userLabel="BGChartFull" customClass="LineChartView" customModule="Charts">
                                 <rect key="frame" x="8" y="745" width="398" height="60"/>
                                 <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
@@ -128,16 +121,13 @@
                         <constraints>
                             <constraint firstItem="51B-JU-npv" firstAttribute="top" secondItem="mJy-Ly-Jze" secondAttribute="bottom" constant="8" id="2Qj-m5-4Yj"/>
                             <constraint firstItem="PQr-Ze-W5v" firstAttribute="trailing" secondItem="Nml-lr-dgQ" secondAttribute="trailing" constant="8" id="5dn-w0-XWn"/>
-                            <constraint firstItem="AOf-kb-6CI" firstAttribute="top" secondItem="51B-JU-npv" secondAttribute="bottom" constant="8" id="8aC-1I-XGc"/>
                             <constraint firstItem="Nml-lr-dgQ" firstAttribute="leading" secondItem="PQr-Ze-W5v" secondAttribute="leading" constant="8" id="D6D-3r-YIy"/>
-                            <constraint firstItem="AOf-kb-6CI" firstAttribute="leading" secondItem="PQr-Ze-W5v" secondAttribute="leading" constant="8" id="KGd-6a-HA1"/>
                             <constraint firstItem="PQr-Ze-W5v" firstAttribute="trailing" secondItem="mJy-Ly-Jze" secondAttribute="trailing" constant="8" id="KcX-yW-Vnd"/>
                             <constraint firstItem="PQr-Ze-W5v" firstAttribute="bottom" secondItem="Nml-lr-dgQ" secondAttribute="bottom" constant="8" id="Sb3-4z-Qup"/>
                             <constraint firstItem="mJy-Ly-Jze" firstAttribute="leading" secondItem="PQr-Ze-W5v" secondAttribute="leading" constant="8" id="b30-Y0-qsu"/>
                             <constraint firstItem="51B-JU-npv" firstAttribute="leading" secondItem="PQr-Ze-W5v" secondAttribute="leading" constant="8" id="dfE-Q6-SML"/>
-                            <constraint firstItem="Nml-lr-dgQ" firstAttribute="top" secondItem="AOf-kb-6CI" secondAttribute="bottom" constant="8" id="oLY-NQ-TFm"/>
                             <constraint firstItem="PQr-Ze-W5v" firstAttribute="trailing" secondItem="51B-JU-npv" secondAttribute="trailing" constant="8" id="oqO-3J-e8a"/>
-                            <constraint firstItem="PQr-Ze-W5v" firstAttribute="trailing" secondItem="AOf-kb-6CI" secondAttribute="trailing" constant="8" id="xBV-Eb-pEs"/>
+                            <constraint firstItem="Nml-lr-dgQ" firstAttribute="top" secondItem="51B-JU-npv" secondAttribute="bottom" constant="8" id="woM-1z-QyB"/>
                             <constraint firstItem="mJy-Ly-Jze" firstAttribute="top" secondItem="PQr-Ze-W5v" secondAttribute="top" constant="8" id="xem-Lc-bdU"/>
                         </constraints>
                         <viewLayoutGuide key="safeArea" id="PQr-Ze-W5v"/>
@@ -147,7 +137,6 @@
                         <outlet property="BGChart" destination="51B-JU-npv" id="oF1-UA-KHE"/>
                         <outlet property="BGChartFull" destination="Nml-lr-dgQ" id="Z21-fG-GK9"/>
                         <outlet property="BGText" destination="vXU-IW-GOL" id="2aC-pv-Qbg"/>
-                        <outlet property="BasalChart" destination="AOf-kb-6CI" id="Qsl-jz-Qfj"/>
                         <outlet property="DeltaText" destination="sb7-g9-JNv" id="h7s-Yj-i4M"/>
                         <outlet property="DirectionText" destination="4sd-6h-LSj" id="lB5-Z0-p25"/>
                         <outlet property="LoopStatusLabel" destination="XEm-uK-Xui" id="EBm-Ta-tZ1"/>

+ 89 - 116
LoopFollow/Controllers/Graphs.swift

@@ -13,22 +13,14 @@ import UIKit
 
 extension MainViewController {
     
-    // rewrite this func
-    func chartTranslated(_ chartView: ChartViewBase, dX: CGFloat, dY: CGFloat) {
-        
-        
-        if  chartView == BGChart {
-            let currentMatrix = chartView.viewPortHandler.touchMatrix
-            BasalChart.viewPortHandler.refresh(newMatrix: currentMatrix, chart: BasalChart, invalidate: true)
-        }else {
-            let currentMatrix = BasalChart.viewPortHandler.touchMatrix
-            BGChart.viewPortHandler.refresh(newMatrix: currentMatrix, chart: BGChart, invalidate: true)
-        }
-    }
+
     
 
     
-    func createGraph(newBGLoaded: Bool = false){
+    func createGraph(){
+        self.BGChart.clear()
+        
+        // Create the BG Graph Data
         let entries = bgData
         var bgChartEntry = [ChartDataEntry]()
         var colors = [NSUIColor]()
@@ -73,36 +65,33 @@ extension MainViewController {
             }
         }
         
-        let line1 = LineChartDataSet(entries:bgChartEntry, label: "")
-        line1.circleRadius = 3
-        line1.circleColors = [NSUIColor.systemGreen]
-        line1.drawCircleHoleEnabled = false
+        // Setup BG line details
+        let lineBG = LineChartDataSet(entries:bgChartEntry, label: "")
+        lineBG.circleRadius = 3
+        lineBG.circleColors = [NSUIColor.systemGreen]
+        lineBG.drawCircleHoleEnabled = false
+        lineBG.axisDependency = YAxis.AxisDependency.right
+        lineBG.highlightEnabled = false
         if UserDefaultsRepository.showLines.value {
-            line1.lineWidth = 2
+            lineBG.lineWidth = 2
         } else {
-            line1.lineWidth = 0
+            lineBG.lineWidth = 0
         }
         if UserDefaultsRepository.showDots.value {
-            line1.drawCirclesEnabled = true
+            lineBG.drawCirclesEnabled = true
         } else {
-            line1.drawCirclesEnabled = false
+            lineBG.drawCirclesEnabled = false
         }
-        line1.setDrawHighlightIndicators(false)
-        line1.valueFont.withSize(50)
+        lineBG.setDrawHighlightIndicators(false)
+        lineBG.valueFont.withSize(50)
         
         for i in 1..<colors.count{
-            line1.addColor(colors[i])
-            line1.circleColors.append(colors[i])
+            lineBG.addColor(colors[i])
+            lineBG.circleColors.append(colors[i])
         }
         
-        let data = LineChartData()
-        data.addDataSet(line1)
-        data.setValueFont(UIFont(name: UIFont.systemFont(ofSize: 10).fontName, size: 10)!)
-        data.setDrawValues(false)
-        line1.axisDependency = YAxis.AxisDependency.right
-        //chartData.addDataSet(line1)
-        
-        // Basal graph
+
+        // create Basal graph data
         var chartEntry = [ChartDataEntry]()
         var maxBasal = 1.0
         for i in 0..<basalData.count{
@@ -112,29 +101,62 @@ extension MainViewController {
                 maxBasal = basalData[i].basalRate
             }
         }
-        let lineB = LineChartDataSet(entries:chartEntry, label: "")
-        lineB.circleRadius = 3
-        lineB.circleColors = [NSUIColor.systemBlue]
-        lineB.drawCircleHoleEnabled = false
-        lineB.setDrawHighlightIndicators(false)
-        lineB.setColor(NSUIColor.systemBlue, alpha: 0.5)
-        lineB.lineWidth = 0
-        lineB.drawFilledEnabled = true
-        lineB.fillColor = NSUIColor.systemBlue.withAlphaComponent(0.8)
-        lineB.drawCirclesEnabled = false
-        data.addDataSet(lineB)
+        // Setup Basal line details
+        let lineBasal = LineChartDataSet(entries:chartEntry, label: "")
+        lineBasal.setDrawHighlightIndicators(false)
+        lineBasal.setColor(NSUIColor.systemBlue, alpha: 0.5)
+        lineBasal.lineWidth = 0
+        lineBasal.drawFilledEnabled = true
+        lineBasal.fillColor = NSUIColor.systemBlue.withAlphaComponent(0.8)
+        lineBasal.drawCirclesEnabled = false
+        lineBasal.axisDependency = YAxis.AxisDependency.left
+        lineBasal.highlightEnabled = false
+        
+        // Boluses
+        var chartEntryBolus = [ChartDataEntry]()
+        for i in 0..<bolusData.count{
+            let value = ChartDataEntry(x: Double(bolusData[i].date), y: Double(bolusData[i].sgv), data: String(bolusData[i].value))
+            chartEntryBolus.append(value)
+        }
+        let lineBolus = LineChartDataSet(entries:chartEntryBolus, label: "")
+        lineBolus.circleRadius = 8
+        lineBolus.circleColors = [NSUIColor.systemBlue.withAlphaComponent(0.75)]
+        lineBolus.drawCircleHoleEnabled = false
+        lineBolus.setDrawHighlightIndicators(false)
+        lineBolus.setColor(NSUIColor.systemBlue, alpha: 1.0)
+        lineBolus.drawCirclesEnabled = true
+        lineBolus.lineWidth = 0
+        lineBolus.axisDependency = YAxis.AxisDependency.right
+        
+        // Carbs
+        var chartEntryCarbs = [ChartDataEntry]()
+        for i in 0..<carbData.count{
+            let value = ChartDataEntry(x: Double(carbData[i].date), y: Double(carbData[i].sgv), data: String(carbData[i].value))
+            chartEntryCarbs.append(value)
+        }
+        let lineCarbs = LineChartDataSet(entries:chartEntryCarbs, label: "")
+        lineCarbs.circleRadius = 8
+        lineCarbs.circleColors = [NSUIColor.systemOrange.withAlphaComponent(0.75)]
+        lineCarbs.drawCircleHoleEnabled = false
+        lineCarbs.setDrawHighlightIndicators(false)
+        lineCarbs.setColor(NSUIColor.systemBlue, alpha: 1.0)
+        lineCarbs.drawCirclesEnabled = true
+        lineCarbs.lineWidth = 0
+        lineCarbs.axisDependency = YAxis.AxisDependency.right
+        
+        // Setup the chart data of all lines
+        let data = LineChartData()
+        data.addDataSet(lineBG)
+        data.addDataSet(lineBasal)
+        data.addDataSet(lineBolus)
+        data.addDataSet(lineCarbs)
         data.setValueFont(UIFont(name: UIFont.systemFont(ofSize: 10).fontName, size: 10)!)
         data.setDrawValues(false)
-        //chartData.addDataSet(line1)
-        
-        lineB.axisDependency = YAxis.AxisDependency.left
-        BGChart.leftAxis.enabled = true
-        BGChart.leftAxis.labelPosition = YAxis.LabelPosition.insideChart
-        BGChart.leftAxis.axisMaximum = maxBasal
-        BGChart.leftAxis.axisMinimum = 0.0
-        BGChart.leftAxis.drawGridLinesEnabled = false
         
         
+        // Add marker popups for bolus and carbs
+        let marker = PillMarker(color: .secondarySystemBackground, font: UIFont.boldSystemFont(ofSize: 14), textColor: .label)
+        BGChart.marker = marker
         
         // Clear limit lines so they don't add multiples when changing the settings
         BGChart.rightAxis.removeAllLimitLines()
@@ -151,32 +173,38 @@ extension MainViewController {
         ul.lineColor = NSUIColor.systemYellow.withAlphaComponent(0.5)
         BGChart.rightAxis.addLimitLine(ul)
         
+        // Setup the main graph overall details
         BGChart.xAxis.valueFormatter = ChartXValueFormatter()
         BGChart.xAxis.granularity = 1800
         BGChart.xAxis.labelTextColor = NSUIColor.label
         BGChart.xAxis.labelPosition = XAxis.LabelPosition.bottom
+        
+        BGChart.leftAxis.enabled = true
+        BGChart.leftAxis.labelPosition = YAxis.LabelPosition.insideChart
+        BGChart.leftAxis.axisMaximum = maxBasal
+        BGChart.leftAxis.axisMinimum = 0.0
+        BGChart.leftAxis.drawGridLinesEnabled = false
+        
         BGChart.rightAxis.labelTextColor = NSUIColor.label
         BGChart.rightAxis.labelPosition = YAxis.LabelPosition.insideChart
         BGChart.rightAxis.axisMinimum = 40
-        //BGChart.leftAxis.axisMinimum = 40
         BGChart.rightAxis.axisMaximum = Double(maxBG)
-        //BGChart.leftAxis.axisMaximum = Double(maxBG)
-        //BGChart.leftAxis.enabled = false
+        
         BGChart.legend.enabled = false
         BGChart.scaleYEnabled = false
-        BGChart.data = data
+        BGChart.drawGridBackgroundEnabled = true
+        BGChart.gridBackgroundColor = NSUIColor.secondarySystemBackground
         
+        BGChart.data = data
         
+        // This must be called after the data is loaded
         BGChart.setExtraOffsets(left: 10, top: 10, right: 10, bottom: 10)
         BGChart.setVisibleXRangeMinimum(10)
-        BGChart.drawGridBackgroundEnabled = true
-        BGChart.gridBackgroundColor = NSUIColor.secondarySystemBackground
         if firstGraphLoad {
             BGChart.zoom(scaleX: 18, scaleY: 1, x: 1, y: 1)
             firstGraphLoad = false
         }
-        if newBGLoaded {
-            //BGChart.moveViewToX(dateTimeUtils.getNowTimeIntervalUTC() - (BGChart.visibleXRange * 0.7))
+        if minAgoBG < 1 {
             BGChart.moveViewToAnimated(xValue: dateTimeUtils.getNowTimeIntervalUTC() - (BGChart.visibleXRange * 0.7), yValue: 0.0, axis: .right, duration: 1, easingOption: .easeInBack)
         }
         
@@ -210,60 +238,5 @@ extension MainViewController {
         BGChartFull.data = data2
     }
     
-    func createBasalGraph(entries: [basalGraphStruct]){
-        
-        var chartEntry = [ChartDataEntry]()
-        var maxBasal = 1.0
-        for i in 0..<entries.count{
-            let value = ChartDataEntry(x: Double(entries[i].date), y: Double(entries[i].basalRate))
-            chartEntry.append(value)
-            if entries[i].basalRate  > maxBasal {
-                maxBasal = entries[i].basalRate
-            }
-        }
-        let line1 = LineChartDataSet(entries:chartEntry, label: "")
-        line1.circleRadius = 3
-        line1.circleColors = [NSUIColor.systemBlue]
-        line1.drawCircleHoleEnabled = false
-        line1.setDrawHighlightIndicators(false)
-        line1.setColor(NSUIColor.systemBlue, alpha: 0.5)
-        line1.lineWidth = 0
-        line1.drawFilledEnabled = true
-        line1.fillColor = NSUIColor.systemBlue.withAlphaComponent(0.8)
-        line1.drawCirclesEnabled = false
-        let data = LineChartData()
-        data.addDataSet(line1)
-        data.setValueFont(UIFont(name: UIFont.systemFont(ofSize: 10).fontName, size: 10)!)
-        data.setDrawValues(false)
-        chartData.addDataSet(line1)
-        
-        line1.axisDependency = YAxis.AxisDependency.left
-        BGChart.leftAxis.enabled = true
-        BGChart.leftAxis.labelPosition = YAxis.LabelPosition.insideChart
-        BGChart.leftAxis.axisMaximum = maxBasal
-        BGChart.leftAxis.axisMinimum = 0.0
-        BGChart.leftAxis.drawGridLinesEnabled = false
-        
-        
-        BasalChart.xAxis.valueFormatter = ChartXValueFormatter()
-        BasalChart.xAxis.granularity = 1800
-        BasalChart.xAxis.labelTextColor = NSUIColor.label
-        BasalChart.xAxis.labelPosition = XAxis.LabelPosition.bottom
-        BasalChart.rightAxis.labelTextColor = NSUIColor.label
-        BasalChart.rightAxis.labelPosition = YAxis.LabelPosition.insideChart
-        BasalChart.leftAxis.enabled = false
-        BasalChart.legend.enabled = false
-        BasalChart.scaleYEnabled = false
-        BasalChart.data = data
-        BasalChart.setExtraOffsets(left: 10, top: 10, right: 10, bottom: 10)
-        BasalChart.setVisibleXRangeMinimum(10)
-        BasalChart.drawGridBackgroundEnabled = true
-        BasalChart.gridBackgroundColor = NSUIColor.secondarySystemBackground
-        if firstBasalGraphLoad {
-            BasalChart.zoom(scaleX: 18, scaleY: 1, x: 1, y: 1)
-            firstBasalGraphLoad = false
-        }
-        BasalChart.moveViewToX(dateTimeUtils.getNowTimeIntervalUTC() - (BGChart.visibleXRange * 0.7))
-       
-    }
+ 
 }

+ 204 - 142
LoopFollow/Controllers/NightScout.swift

@@ -16,11 +16,10 @@ extension MainViewController {
     // Main loader for all data
     func nightscoutLoader(forceLoad: Bool = false) {
         
-        
         var needsLoaded: Bool = false
         var onlyPullLastRecord = false
         
-        // If we have existing data and it's within 5 minutes, we aren't going to do a network call
+        // If we have existing data and it's within 5 minutes, we aren't going to do a BG network call
         if bgData.count > 0 {
             let now = NSDate().timeIntervalSince1970
             let lastReadingTime = bgData[bgData.count - 1].date
@@ -35,45 +34,51 @@ extension MainViewController {
             needsLoaded = true
         }
         
-        // reset the chart data before loading
-        chartData.clearValues()
-
-        
         if forceLoad { needsLoaded = true}
         // Only do the network calls if we don't have a current reading
         if needsLoaded {
-            loadProfile(urlUser: urlUser)
-            loadTempBasals(urlUser: urlUser)
-            loadDeviceStatus(urlUser: urlUser)
-            loadBGData(urlUser: urlUser, onlyPullLastRecord: onlyPullLastRecord)
-            clearLastInfoData()
-            loadCage(urlUser: urlUser)
-            loadSage(urlUser: urlUser)
+            self.clearLastInfoData()
+            // Dispatch and process json for all web calls before proceeeding
+            webLoadNSProfile()
+            WebLoadNSTempBasals()
+            webLoadNSDeviceStatus()
+            webLoadNSBGData(onlyPullLastRecord: onlyPullLastRecord)
+            webLoadNSBoluses()
+            webLoadNSCarbs()
+            
+            webLoadNSCage()
+            webLoadNSSage()
             
             chartDispatch.notify(queue: .main){
-                self.createGraph(newBGLoaded: needsLoaded)
+                self.updateBadge()
+                self.viewUpdateNSBG()
+                if UserDefaultsRepository.writeCalendarEvent.value {
+                    self.writeCalendar()
+                }
+                self.createGraph()
             }
             
-           // loadBoluses(urlUser: urlUser)
            
         } else {
-            loadProfile(urlUser: urlUser)
-            loadTempBasals(urlUser: urlUser)
-            loadDeviceStatus(urlUser: urlUser)
+            // Dispatch and process json for all web calls before proceeeding
+            webLoadNSProfile()
+            WebLoadNSTempBasals()
+            webLoadNSDeviceStatus()
+            webLoadNSBoluses()
+            webLoadNSCarbs()
            
-            updateMinAgo()
-            clearOldSnoozes()
-            checkAlarms(bgs: bgData)
-            loadProfile(urlUser: urlUser)
             chartDispatch.notify(queue: .main){
-                self.createGraph(newBGLoaded: needsLoaded)
+                self.createGraph()
+                self.updateMinAgo()
+                self.clearOldSnoozes()
+                self.checkAlarms(bgs: self.bgData)
             }
         }
         
     }
     
-    // Main NS BG Data Pull
-    func loadBGData(urlUser: String, onlyPullLastRecord: Bool = false) {
+    // NS BG Data Web call
+    func webLoadNSBGData(onlyPullLastRecord: Bool = false) {
         chartDispatch.enter()
         // Set the count= in the url either to pull 24 hours or only the last record
         var points = "1"
@@ -82,7 +87,7 @@ extension MainViewController {
         }
 
         // URL processor
-        var urlBGDataPath: String = urlUser + "/api/v1/entries/sgv.json?"
+        var urlBGDataPath: String = UserDefaultsRepository.url.value + "/api/v1/entries/sgv.json?"
         if token == "" {
             urlBGDataPath = urlBGDataPath + "count=" + points
         } else {
@@ -116,7 +121,7 @@ extension MainViewController {
         getBGTask.resume()
     }
        
-    // Primary processor for what to do with the downloaded NS BG data
+    // NS BG Data Response processor
     func ProcessNSBGData(data: [sgvData], onlyPullLastRecord: Bool){
         var pullDate = data[data.count - 1].date / 1000
         pullDate.round(FloatingPointRoundingRule.toNearestOrEven)
@@ -128,8 +133,8 @@ extension MainViewController {
             bgData.removeFirst()
         } else {
             // Update the badge, bg, graph settings even if we don't have a new reading.
-            self.updateBadge(entries: bgData)
-            self.updateBG(entries: bgData)
+            self.updateBadge()
+            self.viewUpdateNSBG()
             return
         }
         
@@ -140,23 +145,18 @@ extension MainViewController {
             let reading = sgvData(sgv: data[data.count - 1 - i].sgv, date: dateString, direction: data[data.count - 1 - i].direction)
             bgData.append(reading)
         }
-        self.updateBadge(entries: bgData)
-        self.updateBG(entries: bgData)
-        //self.createGraph(entries: bgData)
-        if UserDefaultsRepository.writeCalendarEvent.value {
-            self.writeCalendar()
-        }
         chartDispatch.leave()
        }
     
-    func updateBG (entries: [sgvData]) {
-        if consoleLogging == true {print("in update BG")}
+    // NS BG Data Front end updater
+    func viewUpdateNSBG () {
+        let entries = bgData
         if entries.count > 0 {
             let latestEntryi = entries.count - 1
             let latestBG = entries[latestEntryi].sgv
             let priorBG = entries[latestEntryi - 1].sgv
             let deltaBG = latestBG - priorBG as Int
-            let lastBGTime = entries[latestEntryi].date //NS has different units
+            let lastBGTime = entries[latestEntryi].date
             let deltaTime = (TimeInterval(Date().timeIntervalSince1970)-lastBGTime) / 60
             var userUnit = " mg/dL"
             if mmol {
@@ -199,8 +199,9 @@ extension MainViewController {
         checkAlarms(bgs: entries)
     }
     
-     // NS Device Status Pull from NS
-      func loadDeviceStatus(urlUser: String) {
+     // NS Device Status Web Call
+      func webLoadNSDeviceStatus() {
+        let urlUser = UserDefaultsRepository.url.value
           var urlStringDeviceStatus = urlUser + "/api/v1/devicestatus.json?count=1"
           if token != "" {
               urlStringDeviceStatus = urlUser + "/api/v1/devicestatus.json?token=" + token + "&count=1"
@@ -233,7 +234,7 @@ extension MainViewController {
           deviceStatusTask.resume()
       }
       
-      // Parse Device Status Data
+      // NS Device Status Response Processor
       func updateDeviceStatusDisplay(jsonDeviceStatus: [[String:AnyObject]]) {
           if consoleLogging == true {print("in updatePump")}
           if jsonDeviceStatus.count == 0 {
@@ -278,7 +279,7 @@ extension MainViewController {
                   {
                       if let enacted = lastLoopRecord["enacted"] as? [String:AnyObject] {
                           if let lastTempBasal = enacted["rate"] as? Double {
-                              tableData[2].value = String(format:"%.1f", lastTempBasal)
+                         //     tableData[2].value = String(format:"%.1f", lastTempBasal)
                           }
                       }
                       if let iobdata = lastLoopRecord["iob"] as? [String:AnyObject] {
@@ -352,8 +353,9 @@ extension MainViewController {
           infoTable.reloadData()
           }
       
-      // Get last CAGE entry
-      func loadCage(urlUser: String) {
+      // NS Cage Web Call
+      func webLoadNSCage() {
+        let urlUser = UserDefaultsRepository.url.value
           var urlString = urlUser + "/api/v1/treatments.json?find[eventType]=Site%20Change&count=1"
           if token != "" {
               urlString = urlUser + "/api/v1/treatments.json?token=" + token + "&find[eventType]=Site%20Change&count=1"
@@ -387,7 +389,7 @@ extension MainViewController {
           task.resume()
       }
        
-      // Parse Cage Data
+      // NS Cage Response Processor
       func updateCage(data: [cageData]) {
           if consoleLogging == true {print("in updateCage")}
           if data.count == 0 {
@@ -418,8 +420,8 @@ extension MainViewController {
           infoTable.reloadData()
       }
        
-      // Get last SAGE entry
-      func loadSage(urlUser: String) {
+      // NS Sage Web Call
+      func webLoadNSSage() {
           var dayComponent    = DateComponents()
           dayComponent.day    = -10 // For removing 10 days
           let theCalendar     = Calendar.current
@@ -429,7 +431,7 @@ extension MainViewController {
           dateFormatter.dateFormat = "yyyy-MM-dd"
           var startDateString = dateFormatter.string(from: startDate)
 
-
+        let urlUser = UserDefaultsRepository.url.value
           var urlString = urlUser + "/api/v1/treatments.json?find[eventType]=Sensor%20Start&find[created_at][$gte]=2020-05-31&count=1"
           if token != "" {
               urlString = urlUser + "/api/v1/treatments.json?token=" + token + "&find[eventType]=Sensor%20Start&find[created_at][$gte]=2020-05-31&count=1"
@@ -463,7 +465,7 @@ extension MainViewController {
           task.resume()
       }
        
-      // Parse Sage Data
+      // NS Sage Response Processor
       func updateSage(data: [cageData]) {
           if consoleLogging == true {print("in updateSage")}
           if data.count == 0 {
@@ -494,10 +496,10 @@ extension MainViewController {
           infoTable.reloadData()
       }
        
-      // Load Current Profile
-      func loadProfile(urlUser: String) {
+      // NS Profile Web Call
+      func webLoadNSProfile() {
           chartDispatch.enter()
-          let urlString = urlUser + "/api/v1/profile/current.json"
+        let urlString = UserDefaultsRepository.url.value + "/api/v1/profile/current.json"
           let escapedAddress = urlString.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
           guard let url = URL(string: escapedAddress!) else {
               return
@@ -527,7 +529,7 @@ extension MainViewController {
           task.resume()
       }
       
-      // Parse Basal schedule from the profile
+      // NS Profile Response Processor
       func updateProfile(jsonDeviceStatus: Dictionary<String, Any>) {
         
           if jsonDeviceStatus.count == 0 {
@@ -546,20 +548,14 @@ extension MainViewController {
           
       }
       
-
-      func loadTempBasals(urlUser: String) {
+        // NS Temp Basal Web Call
+      func WebLoadNSTempBasals() {
         chartDispatch.enter()
-          let today = Date()
-            let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: today)!
-            
-            let dateFormatter = DateFormatter()
-            dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
-            dateFormatter.timeZone = TimeZone.init(secondsFromGMT: 0)
-            let yesterdayString = dateFormatter.string(from: yesterday)
-            
-            var urlString = urlUser + "/api/v1/treatments.json?find[eventType][$eq]=Temp%20Basal&find[created_at][$gte]=" + yesterdayString
+        let yesterdayString = dateTimeUtils.nowMinus24HoursTimeInterval()
+
+        var urlString = UserDefaultsRepository.url.value + "/api/v1/treatments.json?find[eventType][$eq]=Temp%20Basal&find[created_at][$gte]=" + yesterdayString
             if token != "" {
-                urlString = urlUser + "/api/v1/treatments.json?token=" + token + "&find[eventType][$eq]=Temp%20Basal&find[created_at][$gte]=" + yesterdayString
+                urlString = UserDefaultsRepository.url.value + "/api/v1/treatments.json?token=" + token + "&find[eventType][$eq]=Temp%20Basal&find[created_at][$gte]=" + yesterdayString
             }
             
             guard let urlData = URL(string: urlString) else {
@@ -571,25 +567,26 @@ extension MainViewController {
             request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
             let task = URLSession.shared.dataTask(with: request) { data, response, error in
            
-            guard error == nil else {
-                return
-            }
-            guard let data = data else {
-                return
-            }
-                
+                guard error == nil else {
+                    return
+                }
+                guard let data = data else {
+                    return
+                }
+                    
                 let json = try? (JSONSerialization.jsonObject(with: data) as! [[String:AnyObject]])
-            if let json = json {
-                DispatchQueue.main.async {
-                    self.updateBasals(entries: json)
+                if let json = json {
+                    DispatchQueue.main.async {
+                        self.updateBasals(entries: json)
+                    }
+                } else {
+                    return
                 }
-            } else {
-                return
-            }
             }
             task.resume()
       }
       
+    // NS Temp Basal Response Processor
       func updateBasals(entries: [[String:AnyObject]]) {
         
           // due to temp basal durations, we're going to destroy the array and load everything each cycle for the time being.
@@ -668,6 +665,7 @@ extension MainViewController {
             // If it's the last one and not ended yet, extend it for 1 hour to match the prediction length. Otherwise let it end
             if i == entries.count - 1 && dateTimeStamp + duration <= dateTimeUtils.getNowTimeIntervalUTC() {
                 lastEndDot = Date().timeIntervalSince1970 + (55 * 60)
+                tableData[2].value = String(format:"%.1f", basalRate)
             } else {
                 lastEndDot = dateTimeStamp + (duration * 60)
             }
@@ -695,6 +693,8 @@ extension MainViewController {
                       
                     
                   }
+            
+                  tableData[2].value = String(format:"%.1f", scheduled)
                   // Make the starting dot at the last ending dot
                   let startDot = basalGraphStruct(basalRate: scheduled, date: Double(lastEndDot))
                   basalData.append(startDot)
@@ -707,82 +707,144 @@ extension MainViewController {
              }
             
         
-        
-        /*
-        // Add 1 hour to end to match prediction time length
-        var scheduled = 0.0
-        // cycle through basal profiles to get current schedule for end.
-        // TODO figure out how to deal with profile changes that happen mid-gap
-        
-          for b in 0..<self.basalProfile.count {
-            let scheduleTimeToday = self.basalProfile[b].timeAsSeconds + dateTimeUtils.getTimeIntervalMidnightToday()
-            // check the last end time to the profile seconds from midnight
-            if lastEndDot >= scheduleTimeToday {
-                scheduled = basalProfile[b].value
-            }
-          }
-          // Make the starting dot at the last ending dot
-          let startDot = basalGraphStruct(basalRate: scheduled, date: Double(lastEndDot))
-          basalData.append(startDot)
-        
-        // Make the ending dot 1 hour from the latest BG reading
-        let endDot = basalGraphStruct(basalRate: scheduled, date: Double(Date().timeIntervalSince1970 + (60 * 60)))
-        basalData.append(endDot)
-        */
-        createBasalGraph(entries: basalData)
+ 
         chartDispatch.leave()
       }
     
-      // NOT IMPLEMENTED YET
-      func loadBoluses(urlUser: String){
-          var calendar = Calendar.current
-          let today = Date()
-          let midnight = calendar.startOfDay(for: today)
-          let yesterday = calendar.date(byAdding: .day, value: -1, to: today)!
-          let dateFormatter = DateFormatter()
-          dateFormatter.timeZone = TimeZone(abbreviation: "GMT")
-          dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
-          let formattedDate = dateFormatter.string(from: yesterday)
-          
-          var searchString = "find[eventType]=Meal%20Bolus&find[created_at][$gte]=" + formattedDate
-          var urlBGDataPath: String = urlUser + "/api/v1/treatments.json?"
+    // NS Bolus Web Call
+      func webLoadNSBoluses(){
+        chartDispatch.enter()
+        let yesterdayString = dateTimeUtils.nowMinus24HoursTimeInterval()
+        let urlUser = UserDefaultsRepository.url.value
+          var searchString = "find[eventType]=Correction%20Bolus&find[created_at][$gte]=" + yesterdayString
+          var urlDataPath: String = urlUser + "/api/v1/treatments.json?"
           if token == "" {
-              urlBGDataPath = urlBGDataPath + searchString
+              urlDataPath = urlDataPath + searchString
           }
           else
           {
-              urlBGDataPath = urlBGDataPath + "token=" + token + searchString
+              urlDataPath = urlDataPath + "token=" + token + "&" + searchString
           }
-          guard let urlBGData = URL(string: urlBGDataPath) else {
+          guard let urlData = URL(string: urlDataPath) else {
               return
           }
-          var request = URLRequest(url: urlBGData)
-          request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
+          var request = URLRequest(url: urlData)
+           request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
+           let task = URLSession.shared.dataTask(with: request) { data, response, error in
           
-          let getTask = URLSession.shared.dataTask(with: request) { data, response, error in
-              if self.consoleLogging == true {print("start meal bolus url")}
-              guard error == nil else {
-                  return
-              }
-              guard let data = data else {
-                  return
-              }
-              
-              let decoder = JSONDecoder()
-              let entriesResponse = try? decoder.decode([sgvData].self, from: data)
-              if let entriesResponse = entriesResponse {
-                  DispatchQueue.main.async {
-                     // self.ProcessNSData(data: entriesResponse, onlyPullLastRecord: onlyPullLastRecord)
-                  }
-              }
-              else
-              {
-                  
-                  return
-              }
-          }
-          getTask.resume()
+               guard error == nil else {
+                   return
+               }
+               guard let data = data else {
+                   return
+               }
+                   
+               let json = try? (JSONSerialization.jsonObject(with: data) as! [[String:AnyObject]])
+               if let json = json {
+                   DispatchQueue.main.async {
+                       self.processNSBolus(entries: json)
+                   }
+               } else {
+                   return
+               }
+           }
+           task.resume()
       }
     
+    // NS Meal Bolus Response Processor
+         func processNSBolus(entries: [[String:AnyObject]]) {
+           
+             // because it's a small array, we're going to destroy and reload every time.
+             bolusData.removeAll()
+             var lastFoundIndex = 0
+             for i in 0..<entries.count {
+                 let currentEntry = entries[entries.count - 1 - i] as [String : AnyObject]?
+                 let bolusDate = currentEntry?["timestamp"] as! String
+                 let strippedZone = String(bolusDate.dropLast())
+                 let dateFormatter = DateFormatter()
+                 dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
+                 dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
+                 let dateString = dateFormatter.date(from: strippedZone)
+                 let dateTimeStamp = dateString!.timeIntervalSince1970
+                 let bolus = currentEntry?["insulin"] as! Double
+                
+                let sgv = findNearestBGbyTime(needle: dateTimeStamp, haystack: bgData, startingIndex: lastFoundIndex)
+                lastFoundIndex = sgv.foundIndex
+                
+                 // Make the dot
+                let dot = bolusCarbGraphStruct(value: bolus, date: Double(dateTimeStamp), sgv: Int(sgv.sgv))
+                 bolusData.append(dot)
+            }
+
+           chartDispatch.leave()
+         }
+    
     
+    // NS Carb Web Call
+      func webLoadNSCarbs(){
+        chartDispatch.enter()
+        let yesterdayString = dateTimeUtils.nowMinus24HoursTimeInterval()
+        let urlUser = UserDefaultsRepository.url.value
+          var searchString = "find[eventType]=Meal%20Bolus&find[created_at][$gte]=" + yesterdayString
+          var urlDataPath: String = urlUser + "/api/v1/treatments.json?"
+          if token == "" {
+              urlDataPath = urlDataPath + searchString
+          }
+          else
+          {
+              urlDataPath = urlDataPath + "token=" + token + "&" + searchString
+          }
+          guard let urlData = URL(string: urlDataPath) else {
+              return
+          }
+          var request = URLRequest(url: urlData)
+           request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
+           let task = URLSession.shared.dataTask(with: request) { data, response, error in
+          
+               guard error == nil else {
+                   return
+               }
+               guard let data = data else {
+                   return
+               }
+                   
+               let json = try? (JSONSerialization.jsonObject(with: data) as! [[String:AnyObject]])
+               if let json = json {
+                   DispatchQueue.main.async {
+                       self.processNSCarbs(entries: json)
+                   }
+               } else {
+                   return
+               }
+           }
+           task.resume()
+      }
+    
+    // NS Carb Bolus Response Processor
+         func processNSCarbs(entries: [[String:AnyObject]]) {
+           
+             // because it's a small array, we're going to destroy and reload every time.
+             carbData.removeAll()
+             var lastFoundIndex = 0
+             for i in 0..<entries.count {
+                 let currentEntry = entries[entries.count - 1 - i] as [String : AnyObject]?
+                 let bolusDate = currentEntry?["timestamp"] as! String
+                 let strippedZone = String(bolusDate.dropLast())
+                 let dateFormatter = DateFormatter()
+                 dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
+                 dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
+                 let dateString = dateFormatter.date(from: strippedZone)
+                 let dateTimeStamp = dateString!.timeIntervalSince1970
+                 let carbs = currentEntry?["carbs"] as! Double
+                
+                let sgv = findNearestBGbyTime(needle: dateTimeStamp, haystack: bgData, startingIndex: lastFoundIndex)
+                lastFoundIndex = sgv.foundIndex
+                
+                 // Make the dot
+                let dot = bolusCarbGraphStruct(value: carbs, date: Double(dateTimeStamp), sgv: Int(sgv.sgv))
+                 carbData.append(dot)
+            }
+
+           chartDispatch.leave()
+         }
 }

+ 13 - 2
LoopFollow/ViewControllers/MainViewController.swift

@@ -24,7 +24,6 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
     @IBOutlet weak var DragBar: UIImageView!
     @IBOutlet weak var PredictionLabel: UILabel!
     @IBOutlet weak var LoopStatusLabel: UILabel!
-    @IBOutlet weak var BasalChart: LineChartView!
     
     //NS BG Struct
     struct sgvData: Codable {
@@ -51,6 +50,13 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
         var date: TimeInterval
     }
     
+    //NS Bolus Data  Struct
+    struct bolusCarbGraphStruct: Codable {
+        var value: Double
+        var date: TimeInterval
+        var sgv: Int
+    }
+    
     // Data Table Struct
     struct infoData {
         var name: String
@@ -63,6 +69,7 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
     public var linePlotDataTime: [Double] = []
     var firstGraphLoad: Bool = true
     var firstBasalGraphLoad: Bool = true
+    var minAgoBG: Double = 0.0
     
     // Vars for NS Pull
     var graphHours:Int=24
@@ -100,6 +107,8 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
     var bgData: [sgvData] = []
     var basalProfile: [basalProfileStruct] = []
     var basalData: [basalGraphStruct] = []
+    var bolusData: [bolusCarbGraphStruct] = []
+    var carbData: [bolusCarbGraphStruct] = []
     var predictionData: [Double] = []
     var chartData = LineChartData()
     let chartDispatch = DispatchGroup()
@@ -228,6 +237,7 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
     //update Min Ago Text. We need to call this separately because it updates between readings
     func updateMinAgo(){
         let deltaTime = (TimeInterval(Date().timeIntervalSince1970)-bgData[bgData.count - 1].date) / 60
+        minAgoBG = Double(TimeInterval(Date().timeIntervalSince1970)-bgData[bgData.count - 1].date)
         MinAgoText.text = String(Int(deltaTime)) + " min ago"
     }
     
@@ -247,7 +257,8 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
 
     
     
-    func updateBadge(entries: [sgvData]) {
+    func updateBadge() {
+        let entries = bgData
         if entries.count > 0 && UserDefaultsRepository.appBadge.value {
             let latestBG = entries[entries.count - 1].sgv
             UIApplication.shared.applicationIconBadgeNumber = latestBG

+ 110 - 0
LoopFollow/helpers/Chart.swift

@@ -24,3 +24,113 @@ final class ChartXValueFormatter: IAxisValueFormatter {
         return formattedDate
     }
 }
+
+
+class ChartMarker: MarkerView {
+    private var text = String()
+
+    private let drawAttributes: [NSAttributedString.Key: Any] = [
+        .font: UIFont.systemFont(ofSize: 15),
+        //.foregroundColor: UIColor.white,
+        //.backgroundColor: UIColor.darkGray
+        .foregroundColor: UIColor.label,
+        .backgroundColor: UIColor.secondarySystemBackground
+    ]
+
+    override func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
+        if entry.data != nil {
+            text = entry.data as? String ?? ""
+        } else {
+            text = String(entry.y)
+        }
+    }
+
+    override func draw(context: CGContext, point: CGPoint) {
+        super.draw(context: context, point: point)
+
+        let sizeForDrawing = text.size(withAttributes: drawAttributes)
+        bounds.size = sizeForDrawing
+        offset = CGPoint(x: -sizeForDrawing.width / 2, y: -sizeForDrawing.height - 4)
+
+        let offset = offsetForDrawing(atPoint: point)
+        let originPoint = CGPoint(x: point.x + offset.x, y: point.y + offset.y)
+        let rectForText = CGRect(origin: originPoint, size: sizeForDrawing)
+        drawText(text: text, rect: rectForText, withAttributes: drawAttributes)
+    }
+
+    private func drawText(text: String, rect: CGRect, withAttributes attributes: [NSAttributedString.Key: Any]? = nil) {
+        let size = bounds.size
+        let centeredRect = CGRect(
+            x: rect.origin.x + (rect.size.width - size.width) / 2,
+            y: rect.origin.y + (rect.size.height - size.height) / 2,
+            width: size.width,
+            height: size.height
+        )
+        text.draw(in: centeredRect, withAttributes: attributes)
+    }
+}
+
+class PillMarker: MarkerImage {
+
+    private (set) var color: UIColor
+    private (set) var font: UIFont
+    private (set) var textColor: UIColor
+    private var labelText: String = ""
+    private var attrs: [NSAttributedString.Key: AnyObject]!
+
+    static let formatter: DateComponentsFormatter = {
+        let f = DateComponentsFormatter()
+        f.allowedUnits = [.minute, .second]
+        f.unitsStyle = .short
+        return f
+    }()
+
+    init(color: UIColor, font: UIFont, textColor: UIColor) {
+        self.color = color
+        self.font = font
+        self.textColor = textColor
+
+        let paragraphStyle = NSMutableParagraphStyle()
+        paragraphStyle.alignment = .center
+        attrs = [.font: font, .paragraphStyle: paragraphStyle, .foregroundColor: textColor, .baselineOffset: NSNumber(value: -4)]
+        super.init()
+    }
+
+    override func draw(context: CGContext, point: CGPoint) {
+        // custom padding around text
+        let labelWidth = labelText.size(withAttributes: attrs).width + 10
+        // if you modify labelHeigh you will have to tweak baselineOffset in attrs
+        let labelHeight = labelText.size(withAttributes: attrs).height + 4
+
+        // place pill above the marker, centered along x
+        var rectangle = CGRect(x: point.x, y: point.y, width: labelWidth, height: labelHeight)
+        rectangle.origin.x -= rectangle.width / 2.0
+        let spacing: CGFloat = 20
+        rectangle.origin.y -= rectangle.height + spacing
+
+        // rounded rect
+        let clipPath = UIBezierPath(roundedRect: rectangle, cornerRadius: 6.0).cgPath
+        context.addPath(clipPath)
+        context.setFillColor(UIColor.secondarySystemBackground.cgColor)
+        context.setStrokeColor(UIColor.label.cgColor)
+        context.closePath()
+        context.drawPath(using: .fillStroke)
+
+        // add the text
+        labelText.draw(with: rectangle, options: .usesLineFragmentOrigin, attributes: attrs, context: nil)
+    }
+
+    override func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
+         if entry.data != nil {
+                   labelText = entry.data as? String ?? ""
+               } else {
+                   labelText = String(entry.y)
+               }
+    }
+
+    private func customString(_ value: Double) -> String {
+        let formattedString = PillMarker.formatter.string(from: TimeInterval(value))!
+        // using this to convert the left axis values formatting, ie 2 min
+        return "\(formattedString)"
+    }
+}

+ 10 - 0
LoopFollow/helpers/DateTime.swift

@@ -48,4 +48,14 @@ class dateTimeUtils {
         return utcTime
     }
     
+    static func nowMinus24HoursTimeInterval() -> String {
+        let today = Date()
+        let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: today)!
+        let dateFormatter = DateFormatter()
+        dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
+        dateFormatter.timeZone = TimeZone.init(secondsFromGMT: 0)
+        let yesterdayString = dateFormatter.string(from: yesterday)
+        return yesterdayString
+    }
+    
 }

+ 29 - 0
LoopFollow/helpers/carbBolusArrays.swift

@@ -0,0 +1,29 @@
+//
+//  carbBolusArrays.swift
+//  LoopFollow
+//
+//  Created by Jon Fawcett on 6/17/20.
+//  Copyright © 2020 Jon Fawcett. All rights reserved.
+//
+
+import Foundation
+
+
+extension MainViewController {
+    
+    func findNearestBGbyTime(needle: TimeInterval, haystack: [sgvData], startingIndex: Int) -> (sgv: Double, foundIndex: Int) {
+        
+        // If we can't find a match or things fail, put it at 100 BG
+        for i in startingIndex..<haystack.count {
+            // i has reached the end without a result. Put the dot at 100
+            if i == haystack.count - 1 { return (100.00, 0) }
+            
+            if needle >= haystack[i].date && needle < haystack[i + 1].date {
+                return (Double(haystack[i].sgv), i)
+            }
+        }
+        
+        return (100.00, 0)
+    }
+    
+}