Parcourir la source

Merge branch 'dexcom' into dev

josep1972 il y a 6 ans
Parent
commit
96e0d62a27
28 fichiers modifiés avec 1303 ajouts et 612 suppressions
  1. 1 1
      LoopFollow.xcodeproj/xcuserdata/jon.xcuserdatad/xcschemes/xcschememanagement.plist
  2. 20 13
      LoopFollow/Application/Base.lproj/Main.storyboard
  3. 0 1
      LoopFollow/Controllers/AlarmSound.swift
  4. 308 244
      LoopFollow/Controllers/Alarms.swift
  5. 4 4
      LoopFollow/Controllers/Graphs.swift
  6. 173 101
      LoopFollow/Controllers/NightScout.swift
  7. 1 1
      LoopFollow/Controllers/Stats.swift
  8. 177 101
      LoopFollow/ViewControllers/AlarmViewController.swift
  9. 31 30
      LoopFollow/ViewControllers/MainViewController.swift
  10. 5 1
      LoopFollow/ViewControllers/SettingsViewController.swift
  11. 41 0
      LoopFollow/ViewControllers/SnoozeViewController.swift
  12. 0 7
      LoopFollow/helpers/DataStructs.swift
  13. 1 1
      LoopFollow/helpers/carbBolusArrays.swift
  14. 12 14
      LoopFollow/repository/UserDefaults.swift
  15. 21 0
      Pods/Local Podspecs/ShareClient.podspec.json
  16. 93 93
      Pods/Pods.xcodeproj/project.pbxproj
  17. 60 0
      Pods/Pods.xcodeproj/xcuserdata/jon.xcuserdatad/xcschemes/ShareClient.xcscheme
  18. 7 0
      Pods/Pods.xcodeproj/xcuserdata/jon.xcuserdatad/xcschemes/xcschememanagement.plist
  19. 21 0
      Pods/ShareClient/LICENSE
  20. 19 0
      Pods/ShareClient/ShareClient/ShareClient.h
  21. 222 0
      Pods/ShareClient/ShareClient/ShareClient.swift
  22. 26 0
      Pods/Target Support Files/ShareClient/ShareClient-Info.plist
  23. 5 0
      Pods/Target Support Files/ShareClient/ShareClient-dummy.m
  24. 12 0
      Pods/Target Support Files/ShareClient/ShareClient-prefix.pch
  25. 17 0
      Pods/Target Support Files/ShareClient/ShareClient-umbrella.h
  26. 10 0
      Pods/Target Support Files/ShareClient/ShareClient.debug.xcconfig
  27. 6 0
      Pods/Target Support Files/ShareClient/ShareClient.modulemap
  28. 10 0
      Pods/Target Support Files/ShareClient/ShareClient.release.xcconfig

+ 1 - 1
LoopFollow.xcodeproj/xcuserdata/jon.xcuserdatad/xcschemes/xcschememanagement.plist

@@ -7,7 +7,7 @@
 		<key>LoopFollow.xcscheme_^#shared#^_</key>
 		<dict>
 			<key>orderHint</key>
-			<integer>3</integer>
+			<integer>4</integer>
 		</dict>
 	</dict>
 </dict>

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

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="49e-Tb-3d3">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="49e-Tb-3d3">
     <device id="retina6_5" orientation="portrait" appearance="light"/>
     <dependencies>
         <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
@@ -17,19 +17,25 @@
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <subviews>
                             <stackView opaque="NO" contentMode="scaleToFill" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="mJy-Ly-Jze">
-                                <rect key="frame" x="8" y="52" width="398" height="214"/>
+                                <rect key="frame" x="8" y="52" width="398" height="259.33333333333331"/>
                                 <subviews>
                                     <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="Ptf-fu-Ue4" userLabel="BGview">
-                                        <rect key="frame" x="0.0" y="0.0" width="144" height="214"/>
+                                        <rect key="frame" x="0.0" y="0.0" width="144" height="259.33333333333331"/>
                                         <subviews>
+                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Server" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2mX-T9-SAm">
+                                                <rect key="frame" x="0.0" y="0.0" width="144" height="15.666666666666666"/>
+                                                <fontDescription key="fontDescription" type="system" pointSize="13"/>
+                                                <nil key="textColor"/>
+                                                <nil key="highlightedColor"/>
+                                            </label>
                                             <label opaque="NO" userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="1000" text="BG" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vXU-IW-GOL">
-                                                <rect key="frame" x="0.0" y="0.0" width="144" height="101.66666666666667"/>
+                                                <rect key="frame" x="0.0" y="15.666666666666664" width="144" height="101.66666666666669"/>
                                                 <fontDescription key="fontDescription" type="system" weight="black" pointSize="85"/>
                                                 <nil key="textColor"/>
                                                 <nil key="highlightedColor"/>
                                             </label>
                                             <stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" translatesAutoresizingMaskIntoConstraints="NO" id="KZc-fa-qmt">
-                                                <rect key="frame" x="0.0" y="101.66666666666666" width="144" height="71.666666666666657"/>
+                                                <rect key="frame" x="0.0" y="117.33333333333334" width="144" height="71.666666666666657"/>
                                                 <subviews>
                                                     <label opaque="NO" userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="1000" text="--" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4sd-6h-LSj" userLabel="Direction">
                                                         <rect key="frame" x="0.0" y="0.0" width="72" height="71.666666666666671"/>
@@ -46,22 +52,22 @@
                                                 </subviews>
                                             </stackView>
                                             <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="MinAgo" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="UkG-hw-DVo">
-                                                <rect key="frame" x="0.0" y="173.33333333333334" width="144" height="20.333333333333343"/>
+                                                <rect key="frame" x="0.0" y="189" width="144" height="20.333333333333343"/>
                                                 <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                                 <nil key="textColor"/>
                                                 <nil key="highlightedColor"/>
                                             </label>
                                             <stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="TDG-yw-5fS">
-                                                <rect key="frame" x="0.0" y="193.66666666666666" width="144" height="20.333333333333343"/>
+                                                <rect key="frame" x="0.0" y="209.33333333333331" width="144" height="50"/>
                                                 <subviews>
-                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Loop" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XEm-uK-Xui">
-                                                        <rect key="frame" x="0.0" y="0.0" width="68" height="20.333333333333332"/>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XEm-uK-Xui">
+                                                        <rect key="frame" x="0.0" y="0.0" width="68" height="50"/>
                                                         <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                                         <nil key="textColor"/>
                                                         <nil key="highlightedColor"/>
                                                     </label>
-                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Prediction" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oiR-SM-6sg">
-                                                        <rect key="frame" x="76" y="0.0" width="68" height="20.333333333333332"/>
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oiR-SM-6sg">
+                                                        <rect key="frame" x="76" y="0.0" width="68" height="50"/>
                                                         <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                                         <nil key="textColor"/>
                                                         <nil key="highlightedColor"/>
@@ -71,7 +77,7 @@
                                         </subviews>
                                     </stackView>
                                     <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" allowsSelection="NO" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="kTs-7g-lp9">
-                                        <rect key="frame" x="154" y="0.0" width="244" height="214"/>
+                                        <rect key="frame" x="154" y="0.0" width="244" height="259.33333333333331"/>
                                         <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
                                         <constraints>
                                             <constraint firstAttribute="width" priority="750" constant="250" id="Gxe-cX-1oe"/>
@@ -106,7 +112,7 @@
                                 </subviews>
                             </stackView>
                             <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="51B-JU-npv" customClass="LineChartView" customModule="Charts">
-                                <rect key="frame" x="8" y="274" width="398" height="355"/>
+                                <rect key="frame" x="8" y="319.33333333333326" width="398" height="309.66666666666674"/>
                                 <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
                             </view>
                             <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ikj-at-auF">
@@ -287,6 +293,7 @@
                         <outlet property="MinAgoText" destination="UkG-hw-DVo" id="Xd2-fL-qYC"/>
                         <outlet property="PredictionLabel" destination="oiR-SM-6sg" id="2Sw-TR-SFo"/>
                         <outlet property="infoTable" destination="kTs-7g-lp9" id="Khq-sV-EZ1"/>
+                        <outlet property="serverText" destination="2mX-T9-SAm" id="Jot-3l-Wim"/>
                         <outlet property="statsAvgBG" destination="jpA-Nb-pU7" id="Uo8-a4-Aus"/>
                         <outlet property="statsEstA1C" destination="7Jx-XF-1vS" id="4RD-nm-JxO"/>
                         <outlet property="statsHighPercent" destination="HON-rt-8pC" id="283-3S-PCR"/>

+ 0 - 1
LoopFollow/Controllers/AlarmSound.swift

@@ -116,7 +116,6 @@ class AlarmSound {
     
     
     static func play(overrideVolume: Bool, numLoops: Int) {
-        
         guard !self.isPlaying else {
             return
         }

+ 308 - 244
LoopFollow/Controllers/Alarms.swift

@@ -12,267 +12,276 @@ import AVFoundation
 extension MainViewController {
     
     
-    func checkAlarms(bgs: [DataStructs.sgvData]) {
-           if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Checking Alarms") }
-           // Don't check or fire alarms within 1 minute of prior alarm
-           if checkAlarmTimer.isValid {  return }
-           
-           let date = Date()
-           let now = date.timeIntervalSince1970
-           let currentBG = bgs[bgs.count - 1].sgv
-           let lastBG = bgs[bgs.count - 2].sgv
-           guard let deltas: [Int] = [
-               bgs[bgs.count - 1].sgv - bgs[bgs.count - 2].sgv,
-               bgs[bgs.count - 2].sgv - bgs[bgs.count - 3].sgv,
-               bgs[bgs.count - 3].sgv - bgs[bgs.count - 4].sgv
-               ] else {}
-           let currentBGTime = bgs[bgs.count - 1].date
-           var alarmTriggered = false
-           var numLoops = 0
-           clearOldSnoozes()
-           
-           // Exit if all is snoozed
-           // still send persistent notification with all snoozed
-           if UserDefaultsRepository.alertSnoozeAllIsSnoozed.value {
-               persistentNotification(bgTime: currentBGTime)
-               return
-           }
-           
-           
-           // BG Based Alarms
-           // Check to make sure it is a current reading and has not already triggered alarm from this reading
-           if now - currentBGTime <= (5*60) && currentBGTime > UserDefaultsRepository.snoozedBGReadingTime.value as! TimeInterval {
-               
-               // trigger temporary alert first
-               if UserDefaultsRepository.alertTemporaryActive.value {
-                   if UserDefaultsRepository.alertTemporaryBelow.value {
-                       if Float(currentBG) < UserDefaultsRepository.alertTemporaryBG.value {
-                           UserDefaultsRepository.alertTemporaryActive.value = false
-                           AlarmSound.whichAlarm = "Temporary Alert"
+    func checkAlarms(bgs: [ShareGlucoseData]) {
+        if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Checking Alarms") }
+        // Don't check or fire alarms within 1 minute of prior alarm
+        if checkAlarmTimer.isValid {  return }
+        
+        let date = Date()
+        let now = date.timeIntervalSince1970
+        let currentBG = bgs[bgs.count - 1].sgv
+        let lastBG = bgs[bgs.count - 2].sgv
+        guard let deltas: [Int] = [
+            bgs[bgs.count - 1].sgv - bgs[bgs.count - 2].sgv,
+            bgs[bgs.count - 2].sgv - bgs[bgs.count - 3].sgv,
+            bgs[bgs.count - 3].sgv - bgs[bgs.count - 4].sgv
+            ] else {}
+        let currentBGTime = bgs[bgs.count - 1].date
+        var alarmTriggered = false
+        var numLoops = 0
+        checkQuietHours()
+        clearOldSnoozes()
+        
+        // Exit if all is snoozed
+        // still send persistent notification with all snoozed
+        if UserDefaultsRepository.alertSnoozeAllIsSnoozed.value {
+            persistentNotification(bgTime: currentBGTime)
+            return
+        }
+        
+        
+        // BG Based Alarms
+        // Check to make sure it is a current reading and has not already triggered alarm from this reading
+        if now - currentBGTime <= (5*60) && currentBGTime > UserDefaultsRepository.snoozedBGReadingTime.value as! TimeInterval {
+            
+            // trigger temporary alert first
+            if UserDefaultsRepository.alertTemporaryActive.value {
+                if UserDefaultsRepository.alertTemporaryBelow.value {
+                    if Float(currentBG) < UserDefaultsRepository.alertTemporaryBG.value {
+                        UserDefaultsRepository.alertTemporaryActive.value = false
+                        AlarmSound.whichAlarm = "Temporary Alert"
                         if UserDefaultsRepository.alertTemporaryBGRepeat.value { numLoops = -1 }
-                           triggerAlarm(sound: UserDefaultsRepository.alertTemporarySound.value, snooozedBGReadingTime: currentBGTime, overrideVolume: UserDefaultsRepository.alertTemporaryBGDND.value, numLoops: numLoops)
-                           return
-                       }
-                   } else{
-                       if Float(currentBG) > UserDefaultsRepository.alertTemporaryBG.value {
-                         tabBarController?.selectedIndex = 2
-                           AlarmSound.whichAlarm = "Temporary Alert"
+                        triggerAlarm(sound: UserDefaultsRepository.alertTemporarySound.value, snooozedBGReadingTime: currentBGTime, overrideVolume: UserDefaultsRepository.overrideSystemOutputVolume.value, numLoops: numLoops)
+                        return
+                    }
+                } else{
+                    if Float(currentBG) > UserDefaultsRepository.alertTemporaryBG.value {
+                        tabBarController?.selectedIndex = 2
+                        AlarmSound.whichAlarm = "Temporary Alert"
                         if UserDefaultsRepository.alertTemporaryBGRepeat.value { numLoops = -1 }
-                           triggerAlarm(sound: UserDefaultsRepository.alertTemporarySound.value, snooozedBGReadingTime: currentBGTime, overrideVolume: UserDefaultsRepository.alertTemporaryBGDND.value, numLoops: numLoops)
-                           return
-                      }
-                   }
-               }
-               
-               // Check Urgent Low
-               if UserDefaultsRepository.alertUrgentLowActive.value && !UserDefaultsRepository.alertUrgentLowIsSnoozed.value &&
-               Float(currentBG) <= UserDefaultsRepository.alertUrgentLowBG.value {
-                   // Separating this makes it so the low or drop alerts won't trigger if they already snoozed the urgent low
-                   if !UserDefaultsRepository.alertUrgentLowIsSnoozed.value {
-                       AlarmSound.whichAlarm = "Urgent Low Alert"
+                        triggerAlarm(sound: UserDefaultsRepository.alertTemporarySound.value, snooozedBGReadingTime: currentBGTime, overrideVolume: UserDefaultsRepository.overrideSystemOutputVolume.value, numLoops: numLoops)
+                        return
+                    }
+                }
+            }
+            
+            // Check Urgent Low
+            if UserDefaultsRepository.alertUrgentLowActive.value && !UserDefaultsRepository.alertUrgentLowIsSnoozed.value &&
+                Float(currentBG) <= UserDefaultsRepository.alertUrgentLowBG.value {
+                // Separating this makes it so the low or drop alerts won't trigger if they already snoozed the urgent low
+                if !UserDefaultsRepository.alertUrgentLowIsSnoozed.value {
+                    AlarmSound.whichAlarm = "Urgent Low Alert"
                     if UserDefaultsRepository.alertUrgentLowRepeat.value { numLoops = -1 }
-                       triggerAlarm(sound: UserDefaultsRepository.alertUrgentLowSound.value, snooozedBGReadingTime: currentBGTime, overrideVolume: UserDefaultsRepository.alertUrgentLowDND.value, numLoops: numLoops)
-                       return
-                   } else {
-                       return
-                   }
-               }
-               
-               // Check Low
-               if UserDefaultsRepository.alertLowActive.value && !UserDefaultsRepository.alertUrgentLowIsSnoozed.value &&
-               Float(currentBG) <= UserDefaultsRepository.alertLowBG.value && !UserDefaultsRepository.alertLowIsSnoozed.value {
-                   AlarmSound.whichAlarm = "Low Alert"
+                    triggerAlarm(sound: UserDefaultsRepository.alertUrgentLowSound.value, snooozedBGReadingTime: currentBGTime, overrideVolume: UserDefaultsRepository.overrideSystemOutputVolume.value, numLoops: numLoops)
+                    return
+                } else {
+                    return
+                }
+            }
+            
+            // Check Low
+            if UserDefaultsRepository.alertLowActive.value && !UserDefaultsRepository.alertUrgentLowIsSnoozed.value &&
+                Float(currentBG) <= UserDefaultsRepository.alertLowBG.value && !UserDefaultsRepository.alertLowIsSnoozed.value {
+                AlarmSound.whichAlarm = "Low Alert"
                 if UserDefaultsRepository.alertLowRepeat.value { numLoops = -1 }
-                   triggerAlarm(sound: UserDefaultsRepository.alertLowSound.value, snooozedBGReadingTime: currentBGTime, overrideVolume: UserDefaultsRepository.alertLowDND.value, numLoops: numLoops)
-                   return
-               }
-               
-               // Check Urgent High
-               if UserDefaultsRepository.alertUrgentHighActive.value && !UserDefaultsRepository.alertUrgentHighIsSnoozed.value &&
-               Float(currentBG) >= UserDefaultsRepository.alertUrgentHighBG.value {
-                   // Separating this makes it so the high or rise alerts won't trigger if they already snoozed the urgent high
-                   if !UserDefaultsRepository.alertUrgentHighIsSnoozed.value {
-                           AlarmSound.whichAlarm = "Urgent High Alert"
+                triggerAlarm(sound: UserDefaultsRepository.alertLowSound.value, snooozedBGReadingTime: currentBGTime, overrideVolume: UserDefaultsRepository.overrideSystemOutputVolume.value, numLoops: numLoops)
+                return
+            }
+            
+            // Check Urgent High
+            if UserDefaultsRepository.alertUrgentHighActive.value && !UserDefaultsRepository.alertUrgentHighIsSnoozed.value &&
+                Float(currentBG) >= UserDefaultsRepository.alertUrgentHighBG.value {
+                // Separating this makes it so the high or rise alerts won't trigger if they already snoozed the urgent high
+                if !UserDefaultsRepository.alertUrgentHighIsSnoozed.value {
+                    AlarmSound.whichAlarm = "Urgent High Alert"
                     if UserDefaultsRepository.alertUrgentHighRepeat.value { numLoops = -1 }
-                           triggerAlarm(sound: UserDefaultsRepository.alertUrgentHighSound.value, snooozedBGReadingTime: currentBGTime, overrideVolume: UserDefaultsRepository.alertUrgentHighDND.value, numLoops: numLoops)
-                           return
-                   } else {
-                       return
-                   }
-                   
-               }
-               
-               // Check High
-               let persistentReadings = Int(UserDefaultsRepository.alertHighPersistent.value / 5)
-               let persistentBG = bgData[bgData.count - 1 - persistentReadings].sgv
-               if UserDefaultsRepository.alertHighActive.value &&
-                   !UserDefaultsRepository.alertHighIsSnoozed.value &&
-                   Float(currentBG) >= UserDefaultsRepository.alertHighBG.value &&
-                   Float(persistentBG) >= UserDefaultsRepository.alertHighBG.value &&
-                   !UserDefaultsRepository.alertHighIsSnoozed.value {
-                       AlarmSound.whichAlarm = "High Alert"
+                    triggerAlarm(sound: UserDefaultsRepository.alertUrgentHighSound.value, snooozedBGReadingTime: currentBGTime, overrideVolume: UserDefaultsRepository.overrideSystemOutputVolume.value, numLoops: numLoops)
+                    return
+                } else {
+                    return
+                }
+                
+            }
+            
+            // Check High
+            let persistentReadings = Int(UserDefaultsRepository.alertHighPersistent.value / 5)
+            let persistentBG = bgData[bgData.count - 1 - persistentReadings].sgv
+            if UserDefaultsRepository.alertHighActive.value &&
+                !UserDefaultsRepository.alertHighIsSnoozed.value &&
+                Float(currentBG) >= UserDefaultsRepository.alertHighBG.value &&
+                Float(persistentBG) >= UserDefaultsRepository.alertHighBG.value &&
+                !UserDefaultsRepository.alertHighIsSnoozed.value {
+                AlarmSound.whichAlarm = "High Alert"
                 if UserDefaultsRepository.alertHighRepeat.value { numLoops = -1 }
-                       triggerAlarm(sound: UserDefaultsRepository.alertHighSound.value, snooozedBGReadingTime: currentBGTime, overrideVolume: UserDefaultsRepository.alertHighDND.value, numLoops: numLoops)
-                       return
-               }
-               
-               
-               
-               // Check Fast Drop
-               if UserDefaultsRepository.alertFastDropActive.value && !UserDefaultsRepository.alertFastDropIsSnoozed.value {
-                   // make sure limit is off or BG is below value
-                   if (!UserDefaultsRepository.alertFastDropUseLimit.value) || (UserDefaultsRepository.alertFastDropUseLimit.value && Float(currentBG) < UserDefaultsRepository.alertFastDropBelowBG.value) {
-                       let compare = 0 - UserDefaultsRepository.alertFastDropDelta.value
-                       
-                       //check last 2/3/4 readings
-                       if (UserDefaultsRepository.alertFastDropReadings.value == 2 && Float(deltas[0]) <= compare)
-                       || (UserDefaultsRepository.alertFastDropReadings.value == 3 && Float(deltas[0]) <= compare && Float(deltas[1]) <= compare)
-                       || (UserDefaultsRepository.alertFastDropReadings.value == 4 && Float(deltas[0]) <= compare && Float(deltas[1]) <= compare && Float(deltas[2]) <= compare) {
-                           AlarmSound.whichAlarm = "Fast Drop Alert"
+                triggerAlarm(sound: UserDefaultsRepository.alertHighSound.value, snooozedBGReadingTime: currentBGTime, overrideVolume: UserDefaultsRepository.overrideSystemOutputVolume.value, numLoops: numLoops)
+                return
+            }
+            
+            
+            
+            // Check Fast Drop
+            if UserDefaultsRepository.alertFastDropActive.value && !UserDefaultsRepository.alertFastDropIsSnoozed.value {
+                // make sure limit is off or BG is below value
+                if (!UserDefaultsRepository.alertFastDropUseLimit.value) || (UserDefaultsRepository.alertFastDropUseLimit.value && Float(currentBG) < UserDefaultsRepository.alertFastDropBelowBG.value) {
+                    let compare = 0 - UserDefaultsRepository.alertFastDropDelta.value
+                    
+                    //check last 2/3/4 readings
+                    if (UserDefaultsRepository.alertFastDropReadings.value == 2 && Float(deltas[0]) <= compare)
+                        || (UserDefaultsRepository.alertFastDropReadings.value == 3 && Float(deltas[0]) <= compare && Float(deltas[1]) <= compare)
+                        || (UserDefaultsRepository.alertFastDropReadings.value == 4 && Float(deltas[0]) <= compare && Float(deltas[1]) <= compare && Float(deltas[2]) <= compare) {
+                        AlarmSound.whichAlarm = "Fast Drop Alert"
                         if UserDefaultsRepository.alertFastDropRepeat.value { numLoops = -1 }
-                           triggerAlarm(sound: UserDefaultsRepository.alertFastDropSound.value, snooozedBGReadingTime: currentBGTime, overrideVolume: UserDefaultsRepository.alertFastDropDND.value, numLoops: numLoops)
-                           return
-                       }
-                   }
-               }
-               
-               // Check Fast Rise
-               if UserDefaultsRepository.alertFastRiseActive.value && !UserDefaultsRepository.alertFastRiseIsSnoozed.value {
-                   // make sure limit is off or BG is above value
-                   if (!UserDefaultsRepository.alertFastRiseUseLimit.value) || (UserDefaultsRepository.alertFastRiseUseLimit.value && Float(currentBG) > UserDefaultsRepository.alertFastRiseAboveBG.value) {
-                       let compare = UserDefaultsRepository.alertFastDropDelta.value
-                       
-                       //check last 2/3/4 readings
-                       if (UserDefaultsRepository.alertFastRiseReadings.value == 2 && Float(deltas[0]) >= compare)
-                       || (UserDefaultsRepository.alertFastRiseReadings.value == 3 && Float(deltas[0]) >= compare && Float(deltas[1]) >= compare)
-                       || (UserDefaultsRepository.alertFastRiseReadings.value == 4 && Float(deltas[0]) >= compare && Float(deltas[1]) >= compare && Float(deltas[2]) >= compare) {
-                           AlarmSound.whichAlarm = "Fast Rise Alert"
+                        triggerAlarm(sound: UserDefaultsRepository.alertFastDropSound.value, snooozedBGReadingTime: currentBGTime, overrideVolume: UserDefaultsRepository.overrideSystemOutputVolume.value, numLoops: numLoops)
+                        return
+                    }
+                }
+            }
+            
+            // Check Fast Rise
+            if UserDefaultsRepository.alertFastRiseActive.value && !UserDefaultsRepository.alertFastRiseIsSnoozed.value {
+                // make sure limit is off or BG is above value
+                if (!UserDefaultsRepository.alertFastRiseUseLimit.value) || (UserDefaultsRepository.alertFastRiseUseLimit.value && Float(currentBG) > UserDefaultsRepository.alertFastRiseAboveBG.value) {
+                    let compare = UserDefaultsRepository.alertFastDropDelta.value
+                    
+                    //check last 2/3/4 readings
+                    if (UserDefaultsRepository.alertFastRiseReadings.value == 2 && Float(deltas[0]) >= compare)
+                        || (UserDefaultsRepository.alertFastRiseReadings.value == 3 && Float(deltas[0]) >= compare && Float(deltas[1]) >= compare)
+                        || (UserDefaultsRepository.alertFastRiseReadings.value == 4 && Float(deltas[0]) >= compare && Float(deltas[1]) >= compare && Float(deltas[2]) >= compare) {
+                        AlarmSound.whichAlarm = "Fast Rise Alert"
                         if UserDefaultsRepository.alertFastRiseRepeat.value { numLoops = -1 }
-                           triggerAlarm(sound: UserDefaultsRepository.alertFastRiseSound.value, snooozedBGReadingTime: currentBGTime, overrideVolume: UserDefaultsRepository.alertFastRiseDND.value, numLoops: numLoops)
-                           return
-                       }
-                   }
-               }
-               
-
-               
-           }
-           
-           // These only get checked and fire if a BG reading doesn't fire
-           if UserDefaultsRepository.alertNotLoopingActive.value
-               && !UserDefaultsRepository.alertNotLoopingIsSnoozed.value
-            && (Double(dateTimeUtils.getNowTimeIntervalUTC() - UserDefaultsRepository.alertLastLoopTime.value) >= Double(UserDefaultsRepository.alertNotLooping.value * 60))
-               && UserDefaultsRepository.alertLastLoopTime.value > 0 {
-               
-               var trigger = true
-               if (UserDefaultsRepository.alertNotLoopingUseLimits.value
-                   && (
-                       (Float(currentBG) <= UserDefaultsRepository.alertNotLoopingUpperLimit.value
-                       && Float(currentBG) >= UserDefaultsRepository.alertNotLoopingLowerLimit.value) ||
-                       // Ignore Limits if BG reading is older than non looping time
-                       (Double(now - currentBGTime) >= Double(UserDefaultsRepository.alertNotLooping.value * 60))
-                   ) ||
-                   !UserDefaultsRepository.alertNotLoopingUseLimits.value) {
-                       AlarmSound.whichAlarm = "Not Looping Alert"
-                if UserDefaultsRepository.alertNotLoopingRepeat.value { numLoops = -1 }
-                       triggerAlarm(sound: UserDefaultsRepository.alertNotLoopingSound.value, snooozedBGReadingTime: nil, overrideVolume: UserDefaultsRepository.alertNotLoopingDND.value, numLoops: numLoops)
-                       return
-               }
-           }
-        
-        // check for missed bolus - Only checks within 1 hour of carb entry
-        // Only continue if alert is active, not snooozed, we have carb data, and bg is over the ignore limit
-        if UserDefaultsRepository.alertMissedBolusActive.value
-        && !UserDefaultsRepository.alertMissedBolusIsSnoozed.value
-        && carbData.count > 0
-        && Float(currentBG) > UserDefaultsRepository.alertMissedBolusLowGramsBG.value {
+                        triggerAlarm(sound: UserDefaultsRepository.alertFastRiseSound.value, snooozedBGReadingTime: currentBGTime, overrideVolume: UserDefaultsRepository.overrideSystemOutputVolume.value, numLoops: numLoops)
+                        return
+                    }
+                }
+            }
+            
             
-            // Grab the latest carb entry
-            let lastCarb = carbData[carbData.count - 1].value
-            let lastCarbTime = carbData[carbData.count - 1].date
-            let now = dateTimeUtils.getNowTimeIntervalUTC()
             
-            //Make sure carb entry is newer than 1 hour, has reached the time length, and is over the ignore limit
-            if lastCarbTime > (now - (60 * 60))
-            && lastCarbTime < (now - Double((UserDefaultsRepository.alertMissedBolus.value * 60)))
-            && lastCarb > Double(UserDefaultsRepository.alertMissedBolusLowGrams.value) {
+        }
+        
+        // These only get checked and fire if a BG reading doesn't fire
+        
+        //check for missed reading alert
+        if UserDefaultsRepository.alertMissedReadingActive.value && !UserDefaultsRepository.alertMissedReadingIsSnoozed.value && (Double(now - currentBGTime) >= Double(UserDefaultsRepository.alertMissedReading.value * 60)) {
+            AlarmSound.whichAlarm = "Missed Reading Alert"
+            if UserDefaultsRepository.alertMissedReadingRepeat.value { numLoops = -1 }
+            triggerAlarm(sound: UserDefaultsRepository.alertMissedReadingSound.value, snooozedBGReadingTime: nil, overrideVolume: UserDefaultsRepository.overrideSystemOutputVolume.value, numLoops: numLoops)
+            return
+        }
+        
+        
+        if UserDefaultsRepository.url.value != "" {
+            
+            if UserDefaultsRepository.alertNotLoopingActive.value
+                && !UserDefaultsRepository.alertNotLoopingIsSnoozed.value
+                && (Double(dateTimeUtils.getNowTimeIntervalUTC() - UserDefaultsRepository.alertLastLoopTime.value) >= Double(UserDefaultsRepository.alertNotLooping.value * 60))
+                && UserDefaultsRepository.alertLastLoopTime.value > 0 {
                 
-                // There is a current carb but no boluses data at all
-                if bolusData.count < 1 {
-                    AlarmSound.whichAlarm = "Missed Bolus Alert"
-                    if UserDefaultsRepository.alertMissedBolusRepeat.value { numLoops = -1 }
-                    triggerAlarm(sound: UserDefaultsRepository.alertMissedBolusSound.value, snooozedBGReadingTime: nil, overrideVolume: UserDefaultsRepository.alertMissedBolusDND.value, numLoops: numLoops)
+                var trigger = true
+                if (UserDefaultsRepository.alertNotLoopingUseLimits.value
+                    && (
+                        (Float(currentBG) >= UserDefaultsRepository.alertNotLoopingUpperLimit.value
+                            && Float(currentBG) <= UserDefaultsRepository.alertNotLoopingLowerLimit.value) ||
+                            // Ignore Limits if BG reading is older than non looping time
+                            (Double(now - currentBGTime) >= Double(UserDefaultsRepository.alertNotLooping.value * 60))
+                    ) ||
+                    !UserDefaultsRepository.alertNotLoopingUseLimits.value) {
+                    AlarmSound.whichAlarm = "Not Looping Alert"
+                    if UserDefaultsRepository.alertNotLoopingRepeat.value { numLoops = -1 }
+                    triggerAlarm(sound: UserDefaultsRepository.alertNotLoopingSound.value, snooozedBGReadingTime: nil, overrideVolume: UserDefaultsRepository.overrideSystemOutputVolume.value, numLoops: numLoops)
                     return
                 }
+            }
+            
+            // check for missed bolus - Only checks within 1 hour of carb entry
+            // Only continue if alert is active, not snooozed, we have carb data, and bg is over the ignore limit
+            if UserDefaultsRepository.alertMissedBolusActive.value
+                && !UserDefaultsRepository.alertMissedBolusIsSnoozed.value
+                && carbData.count > 0
+                && Float(currentBG) > UserDefaultsRepository.alertMissedBolusLowGramsBG.value {
+                
+                // Grab the latest carb entry
+                let lastCarb = carbData[carbData.count - 1].value
+                let lastCarbTime = carbData[carbData.count - 1].date
+                let now = dateTimeUtils.getNowTimeIntervalUTC()
                 
-                // Get the latest bolus over the small bolus exclusion
-                // Start with 0.0 bolus assuming there isn't one to cause a trigger and only add one if found
-                var lastBolus = 0.0
-                var lastBolusTime = 0.0
-                var i = 1
-                // check the boluses in reverse order setting it only if the time is after the carb time minus prebolus time.
-                // This will make the loop stop at the most recent bolus that is over the minimum value or continue through all boluses
-                while lastBolus < UserDefaultsRepository.alertMissedBolusIgnoreBolus.value && i <= bolusData.count {
-                    // Set the bolus if it's after the carb time minus prebolus time
-                    if (bolusData[bolusData.count - i].date >= lastCarbTime - Double(UserDefaultsRepository.alertMissedBolusPrebolus.value * 60)) {
-                        lastBolus = bolusData[bolusData.count - i].value
-                        lastBolusTime = bolusData[bolusData.count - i].date
+                //Make sure carb entry is newer than 1 hour, has reached the time length, and is over the ignore limit
+                if lastCarbTime > (now - (60 * 60))
+                    && lastCarbTime < (now - Double((UserDefaultsRepository.alertMissedBolus.value * 60)))
+                    && lastCarb > Double(UserDefaultsRepository.alertMissedBolusLowGrams.value) {
+                    
+                    // There is a current carb but no boluses data at all
+                    if bolusData.count < 1 {
+                        AlarmSound.whichAlarm = "Missed Bolus Alert"
+                        if UserDefaultsRepository.alertMissedBolusRepeat.value { numLoops = -1 }
+                        triggerAlarm(sound: UserDefaultsRepository.alertMissedBolusSound.value, snooozedBGReadingTime: nil, overrideVolume: UserDefaultsRepository.overrideSystemOutputVolume.value, numLoops: numLoops)
+                        return
+                    }
+                    
+                    // Get the latest bolus over the small bolus exclusion
+                    // Start with 0.0 bolus assuming there isn't one to cause a trigger and only add one if found
+                    var lastBolus = 0.0
+                    var lastBolusTime = 0.0
+                    var i = 1
+                    // check the boluses in reverse order setting it only if the time is after the carb time minus prebolus time.
+                    // This will make the loop stop at the most recent bolus that is over the minimum value or continue through all boluses
+                    while lastBolus < UserDefaultsRepository.alertMissedBolusIgnoreBolus.value && i <= bolusData.count {
+                        // Set the bolus if it's after the carb time minus prebolus time
+                        if (bolusData[bolusData.count - i].date >= lastCarbTime - Double(UserDefaultsRepository.alertMissedBolusPrebolus.value * 60)) {
+                            lastBolus = bolusData[bolusData.count - i].value
+                            lastBolusTime = bolusData[bolusData.count - i].date
+                        }
+                        i += 1
+                    }
+                    
+                    // This will trigger is no boluses were set above
+                    if (lastBolus == 0.0) {
+                        AlarmSound.whichAlarm = "Missed Bolus Alert"
+                        if UserDefaultsRepository.alertMissedBolusRepeat.value { numLoops = -1 }
+                        triggerAlarm(sound: UserDefaultsRepository.alertMissedBolusSound.value, snooozedBGReadingTime: nil, overrideVolume: UserDefaultsRepository.overrideSystemOutputVolume.value, numLoops: numLoops)
+                        return
                     }
-                    i += 1
+                    
                 }
                 
-                // This will trigger is no boluses were set above
-                if (lastBolus == 0.0) {
-                    AlarmSound.whichAlarm = "Missed Bolus Alert"
-                    if UserDefaultsRepository.alertMissedBolusRepeat.value { numLoops = -1 }
-                    triggerAlarm(sound: UserDefaultsRepository.alertMissedBolusSound.value, snooozedBGReadingTime: nil, overrideVolume: UserDefaultsRepository.alertMissedBolusDND.value, numLoops: numLoops)
+            }
+            
+            // Check Sage
+            if UserDefaultsRepository.alertSAGEActive.value && !UserDefaultsRepository.alertSAGEIsSnoozed.value {
+                let insertTime = Double(UserDefaultsRepository.alertSageInsertTime.value)
+                let alertDistance = Double(UserDefaultsRepository.alertSAGE.value * 60 * 60)
+                let delta = now - insertTime
+                let tenDays = 10 * 24 * 60 * 60
+                if Double(tenDays) - Double(delta) <= alertDistance {
+                    AlarmSound.whichAlarm = "Sensor Change Alert"
+                    if UserDefaultsRepository.alertSAGERepeat.value { numLoops = -1 }
+                    triggerAlarm(sound: UserDefaultsRepository.alertSAGESound.value, snooozedBGReadingTime: nil, overrideVolume: UserDefaultsRepository.overrideSystemOutputVolume.value, numLoops: numLoops)
+                    return
+                }
+            }
+            
+            // Check Cage
+            if UserDefaultsRepository.alertCAGEActive.value && !UserDefaultsRepository.alertCAGEIsSnoozed.value {
+                let insertTime = Double(UserDefaultsRepository.alertCageInsertTime.value)
+                let alertDistance = Double(UserDefaultsRepository.alertCAGE.value * 60 * 60)
+                let delta = now - insertTime
+                let tenDays = 3 * 24 * 60 * 60
+                if Double(tenDays) - Double(delta) <= alertDistance {
+                    AlarmSound.whichAlarm = "Pump Change Alert"
+                    if UserDefaultsRepository.alertCAGERepeat.value { numLoops = -1 }
+                    triggerAlarm(sound: UserDefaultsRepository.alertCAGESound.value, snooozedBGReadingTime: nil, overrideVolume: UserDefaultsRepository.overrideSystemOutputVolume.value, numLoops: numLoops)
                     return
                 }
-                    
             }
-                
         }
-           
-           //check for missed reading alert
-           if UserDefaultsRepository.alertMissedReadingActive.value && !UserDefaultsRepository.alertMissedReadingIsSnoozed.value && (Double(now - currentBGTime) >= Double(UserDefaultsRepository.alertMissedReading.value * 60)) {
-               AlarmSound.whichAlarm = "Missed Reading Alert"
-            if UserDefaultsRepository.alertMissedReadingRepeat.value { numLoops = -1 }
-                   triggerAlarm(sound: UserDefaultsRepository.alertMissedReadingSound.value, snooozedBGReadingTime: nil, overrideVolume: UserDefaultsRepository.alertMissedReadingDND.value, numLoops: numLoops)
-                   return
-           }
-           
-           // Check Sage
-           if UserDefaultsRepository.alertSAGEActive.value && !UserDefaultsRepository.alertSAGEIsSnoozed.value {
-               let insertTime = Double(UserDefaultsRepository.alertSageInsertTime.value)
-               let alertDistance = Double(UserDefaultsRepository.alertSAGE.value * 60 * 60)
-               let delta = now - insertTime
-               let tenDays = 10 * 24 * 60 * 60
-               if Double(tenDays) - Double(delta) <= alertDistance {
-                   AlarmSound.whichAlarm = "Sensor Change Alert"
-                if UserDefaultsRepository.alertSAGERepeat.value { numLoops = -1 }
-                   triggerAlarm(sound: UserDefaultsRepository.alertSAGESound.value, snooozedBGReadingTime: nil, overrideVolume: UserDefaultsRepository.alertSAGEDND.value, numLoops: numLoops)
-                   return
-               }
-           }
-           
-           // Check Cage
-           if UserDefaultsRepository.alertCAGEActive.value && !UserDefaultsRepository.alertCAGEIsSnoozed.value {
-               let insertTime = Double(UserDefaultsRepository.alertCageInsertTime.value)
-               let alertDistance = Double(UserDefaultsRepository.alertCAGE.value * 60 * 60)
-               let delta = now - insertTime
-               let tenDays = 3 * 24 * 60 * 60
-               if Double(tenDays) - Double(delta) <= alertDistance {
-                   AlarmSound.whichAlarm = "Pump Change Alert"
-                if UserDefaultsRepository.alertCAGERepeat.value { numLoops = -1 }
-                triggerAlarm(sound: UserDefaultsRepository.alertCAGESound.value, snooozedBGReadingTime: nil, overrideVolume: UserDefaultsRepository.alertCAGEDND.value, numLoops: numLoops)
-                   return
-               }
-           }
-           
-           // still send persistent notification if no alarms trigger and persistent notification is on
-           persistentNotification(bgTime: currentBGTime)
-           
-       }
+        
+        
+        
+        
+        // still send persistent notification if no alarms trigger and persistent notification is on
+        persistentNotification(bgTime: currentBGTime)
+        
+    }
        
     func checkOverrideAlarms()
     {
@@ -295,18 +304,18 @@ extension MainViewController {
         if latest.value == prior.value { return }
         
         var numLoops = 0
-        if UserDefaultsRepository.alertOverrideStart.value {
+        if UserDefaultsRepository.alertOverrideStart.value && !UserDefaultsRepository.alertOverrideStartIsSnoozed.value {
             if latest.value != 1.0 && lastOverrideStartTime != latest.date {
                 AlarmSound.whichAlarm = String(format: "%.0f%%", (latest.value * 100)) + " Override Started"
                 if UserDefaultsRepository.alertOverrideStartRepeat.value { numLoops = -1 }
-                triggerOneTimeAlarm(sound: UserDefaultsRepository.alertOverrideEndSound.value, overrideVolume: UserDefaultsRepository.alertOverrideStartDND.value, numLoops: numLoops)
+                triggerOneTimeAlarm(sound: UserDefaultsRepository.alertOverrideEndSound.value, overrideVolume: UserDefaultsRepository.overrideSystemOutputVolume.value, numLoops: numLoops)
                 lastOverrideStartTime = latest.date
             }
-        } else if UserDefaultsRepository.alertOverrideEnd.value {
+        } else if UserDefaultsRepository.alertOverrideEnd.value && !UserDefaultsRepository.alertOverrideEndIsSnoozed.value {
             if latest.value == 1.0 && lastOverrideEndTime != latest.date {
                 AlarmSound.whichAlarm = "Override Ended"
                 if UserDefaultsRepository.alertOverrideEndRepeat.value { numLoops = -1 }
-                triggerOneTimeAlarm(sound: UserDefaultsRepository.alertOverrideEndSound.value, overrideVolume: UserDefaultsRepository.alertOverrideEndDND.value, numLoops: numLoops)
+                triggerOneTimeAlarm(sound: UserDefaultsRepository.alertOverrideEndSound.value, overrideVolume: UserDefaultsRepository.overrideSystemOutputVolume.value, numLoops: numLoops)
                 lastOverrideEndTime = latest.date
             }
         }
@@ -427,9 +436,64 @@ extension MainViewController {
               alarms.reloadIsSnoozed(key: "alertCAGEIsSnoozed", value: false)
 
           }
+        if date > UserDefaultsRepository.alertOverrideStartSnoozedTime.value ?? date {
+            UserDefaultsRepository.alertOverrideStartSnoozedTime.setNil(key: "alertOverrideStartSnoozedTime")
+            UserDefaultsRepository.alertOverrideStartIsSnoozed.value = false
+            alarms.reloadSnoozeTime(key: "alertOverrideStartSnoozedTime", setNil: true)
+            alarms.reloadIsSnoozed(key: "alertOverrideStartIsSnoozed", value: false)
+        }
+        if date > UserDefaultsRepository.alertOverrideEndSnoozedTime.value ?? date {
+            UserDefaultsRepository.alertOverrideEndSnoozedTime.setNil(key: "alertOverrideEndSnoozedTime")
+            UserDefaultsRepository.alertOverrideEndIsSnoozed.value = false
+            alarms.reloadSnoozeTime(key: "alertOverrideEndSnoozedTime", setNil: true)
+            alarms.reloadIsSnoozed(key: "alertOverrideEndIsSnoozed", value: false)
+
+        }
         
       }
     
+    func checkQuietHours() {
+        if UserDefaultsRepository.quietHourStart.value == nil || UserDefaultsRepository.quietHourEnd.value == nil { return }
+        
+        var startDateComponents = DateComponents()
+        
+        let today = Date()
+        let todayCalendar = Calendar.current
+        let month = todayCalendar.component(.month, from: today)
+        let day = todayCalendar.component(.day, from: today)
+        let year = todayCalendar.component(.year, from: today)
+        let hour = todayCalendar.component(.hour, from: today)
+        let minute = todayCalendar.component(.minute, from: today)
+        let todayMinutes = (60 * hour) + minute
+        
+        let start = UserDefaultsRepository.quietHourStart.value
+        let startCalendar = Calendar.current
+        let startHour = startCalendar.component(.hour, from: start!)
+        let startMinute = startCalendar.component(.minute, from: start!)
+        let startMinutes = (60 * startHour) + startMinute
+        
+        if todayMinutes >= startMinutes {
+            let tomorrow = Date().addingTimeInterval(86400)
+            let tomorrowCalendar = Calendar.current
+            let end = UserDefaultsRepository.quietHourEnd.value
+            let endCalendar = Calendar.current
+            
+            var components = DateComponents()
+            components.month = tomorrowCalendar.component(.month, from: tomorrow)
+            components.day = tomorrowCalendar.component(.day, from: tomorrow)
+            components.year = tomorrowCalendar.component(.year, from: tomorrow)
+            components.hour = endCalendar.component(.hour, from: end!)
+            components.minute = endCalendar.component(.minute, from: end!)
+            components.second = endCalendar.component(.second, from: end!)
+            let snoozeCalendar = Calendar.current
+            let snoozeTime = snoozeCalendar.date(from: components)
+            
+            guard let snoozer = self.tabBarController!.viewControllers?[2] as? SnoozeViewController else { return }
+            snoozer.setQuietHours(snoozeTime: snoozeTime!)
+        }
+        
+    }
+    
     func speakBG(sgv: Int) {
            var speechSynthesizer = AVSpeechSynthesizer()
            var speechUtterance: AVSpeechUtterance = AVSpeechUtterance(string: "Current BG is " + bgUnits.toDisplayUnits(String(sgv)))

+ 4 - 4
LoopFollow/Controllers/Graphs.swift

@@ -227,8 +227,6 @@ extension MainViewController {
         
         BGChart.data = data
         BGChart.setExtraOffsets(left: 10, top: 10, right: 10, bottom: 10)
-        BGChart.setVisibleXRangeMinimum(10)
-        //BGChart.setVisibleXRangeMaximum(18)
         
     }
     
@@ -326,9 +324,7 @@ extension MainViewController {
         }
         
         BGChart.rightAxis.axisMaximum = Double(maxBG)
-       
         BGChart.setVisibleXRangeMinimum(600)
-       
         BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
         BGChart.data?.notifyDataChanged()
         BGChart.notifyDataSetChanged()
@@ -378,6 +374,10 @@ extension MainViewController {
             smallChart.addEntry(value)
         }
         
+        smallChart.circleColors.removeAll()
+        smallChart.colors.removeAll()
+        mainChart.colors.removeAll()
+        mainChart.circleColors.removeAll()
         if colors.count > 0 {
             if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Graph: prediction colors") }
             for i in 0..<colors.count{

+ 173 - 101
LoopFollow/Controllers/NightScout.swift

@@ -11,13 +11,7 @@ import UIKit
 
 
 extension MainViewController {
-//
-//    //NS BG Struct
-//    struct sgvData: Codable {
-//        var sgv: Int
-//        var date: TimeInterval
-//        var direction: String?
-//    }
+
     
     //NS Cage Struct
     struct cageData: Codable {
@@ -76,11 +70,19 @@ extension MainViewController {
         if forceLoad { needsLoaded = true}
         // Only update if we don't have a current reading or forced to load
         if needsLoaded {
-            self.clearLastInfoData()
-            webLoadNSDeviceStatus()
-            webLoadNSBGData(onlyPullLastRecord: onlyPullLastRecord)
             
-            if !staleData {
+            if UserDefaultsRepository.url.value != "" {
+                webLoadNSDeviceStatus()
+            }
+            
+            if UserDefaultsRepository.shareUserName.value != "" && UserDefaultsRepository.sharePassword.value != "" {
+                webLoadDexShare(onlyPullLastRecord: onlyPullLastRecord)
+            } else {
+                webLoadNSBGData(onlyPullLastRecord: onlyPullLastRecord)
+            }
+
+            
+            if !staleData && UserDefaultsRepository.url.value != "" {
                 webLoadNSProfile()
                 if UserDefaultsRepository.downloadBasal.value {
                     WebLoadNSTempBasals()
@@ -94,27 +96,53 @@ extension MainViewController {
                 webLoadNSCage()
                 webLoadNSSage()
             }
-            
-            
+
             // Give the alarms and calendar 15 seconds delay to allow time for data to compile
-//            if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Start view timer") }
             self.startViewTimer(time: viewTimeInterval)
         } else {
-            // Things to do if we already have data and don't need a network call
-            // Leaving all downloads off for right now.
-            /*
-             webLoadNSDeviceStatus()
-             
-             if UserDefaultsRepository.downloadBolus.value {
-                webLoadNSBoluses()
-             }
-             if UserDefaultsRepository.downloadCarbs.value {
-                webLoadNSCarbs()
-             }*/
+
             if bgData.count > 0 {
                 self.checkAlarms(bgs: bgData)
             }
             
+            if UserDefaultsRepository.url.value != ""  {
+                if latestLoopTime == 0 {
+                    webLoadNSDeviceStatus()
+               
+                    webLoadNSBGData(onlyPullLastRecord: onlyPullLastRecord)
+           
+                    webLoadNSProfile()
+                    if UserDefaultsRepository.downloadBasal.value {
+                        WebLoadNSTempBasals()
+                    }
+                    if UserDefaultsRepository.downloadBolus.value {
+                        webLoadNSBoluses()
+                    }
+                    if UserDefaultsRepository.downloadCarbs.value {
+                        webLoadNSCarbs()
+                    }
+                    webLoadNSCage()
+                    webLoadNSSage()
+                }
+            }
+            
+        }
+    }
+    
+    // Dex Share Web Call
+    func webLoadDexShare(onlyPullLastRecord: Bool = false) {
+        var count = 288
+        if onlyPullLastRecord { count = 1 }
+        dexShare?.fetchData(count) { (err, result) -> () in
+            
+            // TODO: add error checking
+            if(err == nil) {
+                var data = result!
+                self.ProcessNSBGData(data: data, onlyPullLastRecord: onlyPullLastRecord)
+            } else {
+                // If we get an error, immediately try to pull NS BG Data
+                self.webLoadNSBGData(onlyPullLastRecord: onlyPullLastRecord)
+            }
         }
     }
     
@@ -154,11 +182,11 @@ extension MainViewController {
             }
             
             let decoder = JSONDecoder()
-            let entriesResponse = try? decoder.decode([DataStructs.sgvData].self, from: data)
+            let entriesResponse = try? decoder.decode([ShareGlucoseData].self, from: data)
             if let entriesResponse = entriesResponse {
                 DispatchQueue.main.async {
                     // trigger the processor for the data after downloading.
-                    self.ProcessNSBGData(data: entriesResponse, onlyPullLastRecord: onlyPullLastRecord)
+                    self.ProcessNSBGData(data: entriesResponse, onlyPullLastRecord: onlyPullLastRecord, isNS: true)
                     
                 }
             } else {
@@ -170,11 +198,26 @@ extension MainViewController {
     }
     
     // NS BG Data Response processor
-    func ProcessNSBGData(data: [DataStructs.sgvData], onlyPullLastRecord: Bool){
+    func ProcessNSBGData(data: [ShareGlucoseData], onlyPullLastRecord: Bool, isNS: Bool = false){
         if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: BG") }
         
-        var pullDate = data[data.count - 1].date / 1000
-        pullDate.round(FloatingPointRoundingRule.toNearestOrEven)
+        var pullDate = data[data.count - 1].date
+        if isNS {
+            pullDate = data[data.count - 1].date / 1000
+            pullDate.round(FloatingPointRoundingRule.toNearestOrEven)
+        }
+        
+        var latestDate = data[0].date
+        if isNS {
+            latestDate = data[0].date / 1000
+            latestDate.round(FloatingPointRoundingRule.toNearestOrEven)
+        }
+        
+        let now = dateTimeUtils.getNowTimeIntervalUTC()
+        if !isNS && (latestDate + 330) < now {
+            webLoadNSBGData(onlyPullLastRecord: onlyPullLastRecord)
+            return
+        }
         
         // If we already have data, we're going to pop it to the end and remove the first. If we have old or no data, we'll destroy the whole array and start over. This is simpler than determining how far back we need to get new data from in case Dex back-filled readings
         if !onlyPullLastRecord {
@@ -195,73 +238,86 @@ extension MainViewController {
         
         // loop through the data so we can reverse the order to oldest first for the graph and convert the NS timestamp to seconds instead of milliseconds. Makes date comparisons easier for everything else.
         for i in 0..<data.count{
-            var dateString = data[data.count - 1 - i].date / 1000
-            dateString.round(FloatingPointRoundingRule.toNearestOrEven)
+            var dateString = data[data.count - 1 - i].date
+            if isNS {
+                dateString = data[data.count - 1 - i].date / 1000
+                dateString.round(FloatingPointRoundingRule.toNearestOrEven)
+            }
             if dateString >= dateTimeUtils.getTimeInterval24HoursAgo() {
-                let reading = DataStructs.sgvData(sgv: data[data.count - 1 - i].sgv, date: dateString, direction: data[data.count - 1 - i].direction)
+                let reading = ShareGlucoseData(sgv: data[data.count - 1 - i].sgv, date: dateString, direction: data[data.count - 1 - i].direction)
                 bgData.append(reading)
             }
             
         }
         
-        viewUpdateNSBG()
+        viewUpdateNSBG(isNS: isNS)
     }
     
     // NS BG Data Front end updater
-    func viewUpdateNSBG () {
-        if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Display: BG") }
-        guard let snoozer = self.tabBarController!.viewControllers?[2] as? SnoozeViewController else { return }
-        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
-            let deltaTime = (TimeInterval(Date().timeIntervalSince1970)-lastBGTime) / 60
-            var userUnit = " mg/dL"
-            if mmol {
-                userUnit = " mmol/L"
-            }
-            
-            BGText.text = bgUnits.toDisplayUnits(String(latestBG))
-            snoozer.BGLabel.text = bgUnits.toDisplayUnits(String(latestBG))
-            setBGTextColor()
-            
-            if let directionBG = entries[latestEntryi].direction {
-                DirectionText.text = bgDirectionGraphic(directionBG)
-                snoozer.DirectionLabel.text = bgDirectionGraphic(directionBG)
-                latestDirectionString = bgDirectionGraphic(directionBG)
-            }
-            else
-            {
-                DirectionText.text = ""
-                snoozer.DirectionLabel.text = ""
-                latestDirectionString = ""
-            }
-            
-            if deltaBG < 0 {
-                self.DeltaText.text = bgUnits.toDisplayUnits(String(deltaBG))
-                snoozer.DeltaLabel.text = bgUnits.toDisplayUnits(String(deltaBG))
-                latestDeltaString = String(deltaBG)
+    func viewUpdateNSBG (isNS: Bool) {
+        DispatchQueue.main.async {
+            if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Display: BG") }
+            guard let snoozer = self.tabBarController!.viewControllers?[2] as? SnoozeViewController else { return }
+            let entries = self.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
+                let deltaTime = (TimeInterval(Date().timeIntervalSince1970)-lastBGTime) / 60
+                var userUnit = " mg/dL"
+                if self.mmol {
+                    userUnit = " mmol/L"
+                }
+                
+                // TODO: remove testing feature to color code arrow based on NS vs Dex
+                if isNS {
+                    self.serverText.text = "Nightscout"
+                } else {
+                    self.serverText.text = "Dexcom"
+                }
+                
+                self.BGText.text = bgUnits.toDisplayUnits(String(latestBG))
+                snoozer.BGLabel.text = bgUnits.toDisplayUnits(String(latestBG))
+                self.setBGTextColor()
+                
+                if let directionBG = entries[latestEntryi].direction {
+                    self.DirectionText.text = self.bgDirectionGraphic(directionBG)
+                    snoozer.DirectionLabel.text = self.bgDirectionGraphic(directionBG)
+                    self.latestDirectionString = self.bgDirectionGraphic(directionBG)
+                }
+                else
+                {
+                    self.DirectionText.text = ""
+                    snoozer.DirectionLabel.text = ""
+                    self.latestDirectionString = ""
+                }
+                
+                if deltaBG < 0 {
+                    self.DeltaText.text = bgUnits.toDisplayUnits(String(deltaBG))
+                    snoozer.DeltaLabel.text = bgUnits.toDisplayUnits(String(deltaBG))
+                    self.latestDeltaString = String(deltaBG)
+                }
+                else
+                {
+                    self.DeltaText.text = "+" + bgUnits.toDisplayUnits(String(deltaBG))
+                    snoozer.DeltaLabel.text = "+" + bgUnits.toDisplayUnits(String(deltaBG))
+                    self.latestDeltaString = "+" + String(deltaBG)
+                }
+                self.updateBadge(val: latestBG)
+                
             }
             else
             {
-                self.DeltaText.text = "+" + bgUnits.toDisplayUnits(String(deltaBG))
-                snoozer.DeltaLabel.text = "+" + bgUnits.toDisplayUnits(String(deltaBG))
-                latestDeltaString = "+" + String(deltaBG)
+                
+                return
             }
-            self.updateBadge(val: latestBG)
-            
+            self.updateBGGraph()
+            self.updateMinAgo()
+            self.updateStats()
         }
-        else
-        {
-            
-            return
-        }
-        updateBGGraph()
-        updateMinAgo()
-        updateStats()
+        
     }
     
     // NS Device Status Web Call
@@ -308,6 +364,11 @@ extension MainViewController {
     
     // NS Device Status Response Processor
     func updateDeviceStatusDisplay(jsonDeviceStatus: [[String:AnyObject]]) {
+        self.clearLastInfoData(index: 0)
+        self.clearLastInfoData(index: 1)
+        self.clearLastInfoData(index: 3)
+        self.clearLastInfoData(index: 4)
+        self.clearLastInfoData(index: 5)
         if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: device status") }
         if jsonDeviceStatus.count == 0 {
             return
@@ -353,7 +414,7 @@ extension MainViewController {
                         if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Loop: Was Enacted") }
                         wasEnacted = true
                         if let lastTempBasal = enacted["rate"] as? Double {
-                            //tableData[2].value = String(format:"%.1f", lastTempBasal)
+
                         }
                     }
                     if let iobdata = lastLoopRecord["iob"] as? [String:AnyObject] {
@@ -374,9 +435,11 @@ extension MainViewController {
                             let toLoad = Int(UserDefaultsRepository.predictionToLoad.value * 12)
                             var i = 1
                             while i <= toLoad {
-                                let prediction = DataStructs.sgvData(sgv: prediction[i], date: predictionTime, direction: "flat")
-                                predictionData.append(prediction)
-                                predictionTime += 300
+                                if i < prediction.count {
+                                    let prediction = ShareGlucoseData(sgv: prediction[i], date: predictionTime, direction: "flat")
+                                    predictionData.append(prediction)
+                                    predictionTime += 300
+                                }
                                 i += 1
                             }
                         }
@@ -523,6 +586,7 @@ extension MainViewController {
     
     // NS Cage Response Processor
     func updateCage(data: [cageData]) {
+        self.clearLastInfoData(index: 7)
         if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: CAGE") }
         if data.count == 0 {
             return
@@ -592,6 +656,7 @@ extension MainViewController {
     
     // NS Sage Response Processor
     func updateSage(data: [cageData]) {
+        self.clearLastInfoData(index: 6)
         if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process/Display: SAGE") }
         if data.count == 0 {
             return
@@ -810,6 +875,7 @@ extension MainViewController {
     
     // NS Temp Basal Response Processor
     func updateBasals(entries: [[String:AnyObject]]) {
+        self.clearLastInfoData(index: 2)
         if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: Basal") }
         // due to temp basal durations, we're going to destroy the array and load everything each cycle for the time being.
         basalData.removeAll()
@@ -954,6 +1020,7 @@ extension MainViewController {
             
         }
         tableData[2].value = latestBasal
+        infoTable.reloadData()
         if UserDefaultsRepository.graphBasal.value {
             updateBasalGraph()
         }
@@ -1018,7 +1085,9 @@ extension MainViewController {
                 return
             }
             
-            let strippedZone = String(bolusDate.dropLast())
+            // fix to remove millisecond (after period in timestamp) for FreeAPS users
+            var strippedZone = String(bolusDate.dropLast())
+            strippedZone = strippedZone.components(separatedBy: ".")[0]
             let dateFormatter = DateFormatter()
             dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
             dateFormatter.locale = Locale(identifier: "en_US")
@@ -1107,7 +1176,10 @@ extension MainViewController {
             } else {
                 return
             }
-            let strippedZone = String(carbDate.dropLast())
+            // Fix for FreeAPS milliseconds in timestamp
+            var strippedZone = String(carbDate.dropLast())
+            strippedZone = strippedZone.components(separatedBy: ".")[0]
+            
             let dateFormatter = DateFormatter()
             dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
             dateFormatter.locale = Locale(identifier: "en_US")
@@ -1115,19 +1187,19 @@ extension MainViewController {
             let dateString = dateFormatter.date(from: strippedZone)
             let dateTimeStamp = dateString!.timeIntervalSince1970
             
-            do {
-                let carbs = try currentEntry?["carbs"] as! Double
-                let sgv = findNearestBGbyTime(needle: dateTimeStamp, haystack: bgData, startingIndex: lastFoundIndex)
-                lastFoundIndex = sgv.foundIndex
-                
-                if dateTimeStamp < (dateTimeUtils.getNowTimeIntervalUTC() + (60 * 60)) {
-                    // Make the dot
-                    let dot = bolusCarbGraphStruct(value: carbs, date: Double(dateTimeStamp), sgv: Int(sgv.sgv))
-                    carbData.append(dot)
-                }
-            } catch {
-                if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "ERROR: Null Carb entry") }
+            guard let carbs = currentEntry?["carbs"] as? Double else {
+                if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "ERROR: Null Carb entry")}
+                break
             }
+            let sgv = findNearestBGbyTime(needle: dateTimeStamp, haystack: bgData, startingIndex: lastFoundIndex)
+            lastFoundIndex = sgv.foundIndex
+            
+            if dateTimeStamp < (dateTimeUtils.getNowTimeIntervalUTC() + (60 * 60)) {
+                // Make the dot
+                let dot = bolusCarbGraphStruct(value: Double(carbs), date: Double(dateTimeStamp), sgv: Int(sgv.sgv))
+                carbData.append(dot)
+            }
+            
             
             
         }

+ 1 - 1
LoopFollow/Controllers/Stats.swift

@@ -23,7 +23,7 @@ class StatsData {
     var stdDev: Float
     var pie: [DataStructs.pieData]
     
-    init(bgData: [DataStructs.sgvData]) {
+    init(bgData: [ShareGlucoseData]) {
         
         self.countLow = 0
         self.countRange = 0

+ 177 - 101
LoopFollow/ViewControllers/AlarmViewController.swift

@@ -144,6 +144,8 @@ class AlarmViewController: FormViewController {
             }
     }
     
+    
+    
    // static let shared = AlarmViewController()
 
     @IBAction func unwindToAlarms(sender: UIStoryboardSegue)
@@ -160,7 +162,7 @@ class AlarmViewController: FormViewController {
         form
             +++ Section("Select Alert")
           <<< SegmentedRow<String>("bgAlerts"){ row in
-                row.title = "Main BG Alerts"
+                row.title = ""
                 row.options = ["Urgent Low", "Low", "High", "Urgent High"]
                    // row.value = "Urgent Low"
         }.onChange { [weak self] row in
@@ -177,7 +179,7 @@ class AlarmViewController: FormViewController {
             row.value = value
         }
             <<< SegmentedRow<String>("bgExtraAlerts"){ row in
-                row.title = "Extra BG Alerts"
+                row.title = ""
                 row.options = ["No Readings", "Fast Drop", "Fast Rise", "Temporary"]
                     //row.value = "Missed Readings"
         }.onChange { [weak self] row in
@@ -194,8 +196,12 @@ class AlarmViewController: FormViewController {
             row.value = value
         }
             <<< SegmentedRow<String>("otherAlerts"){ row in
-                row.title = "Other Alerts"
+                row.title = ""
                 row.options = ["Not Looping", "Missed Bolus", "SAGE", "CAGE"]
+                if UserDefaultsRepository.url.value == "" {
+                    row.hidden = true
+                }
+                
                 //row.value = "Not Looping"
         }.onChange { [weak self] row in
              guard let value = row.value else { return }
@@ -213,6 +219,9 @@ class AlarmViewController: FormViewController {
         <<< SegmentedRow<String>("overrideAlerts"){ row in
                 row.title = ""
                 row.options = ["Override Start", "Override End"]
+                if UserDefaultsRepository.url.value == "" {
+                    row.hidden = true
+                }
                 //row.value = "Not Looping"
         }.onChange { [weak self] row in
              guard let value = row.value else { return }
@@ -258,6 +267,42 @@ class AlarmViewController: FormViewController {
         buildAlarmSettings()
         
     }
+    
+    override func viewDidAppear(_ animated: Bool) {
+        showHideNSDetails()
+    }
+    
+    func showHideNSDetails() {
+        var isHidden = false
+        var isEnabled = true
+        if UserDefaultsRepository.url.value == "" {
+            isHidden = true
+            isEnabled = false
+        }
+        
+        if let row1 = form.rowBy(tag: "otherAlerts") as? SegmentedRow<String> {
+            row1.hidden = .function(["hide"],  {form in
+                return isHidden
+            })
+            row1.evaluateHidden()
+        }
+        if let row2 = form.rowBy(tag: "overrideAlerts") as? SegmentedRow<String> {
+            row2.hidden = .function(["hide"],  {form in
+                return isHidden
+            })
+            row2.evaluateHidden()
+        }
+        if let row3 = form.sectionBy(tag: "quietHourSection") as? Section {
+            row3.hidden = .function(["hide"],  {form in
+                return isHidden
+            })
+            row3.evaluateHidden()
+        }
+        
+        guard let nightscoutTab = self.tabBarController?.tabBar.items![3] else { return }
+        nightscoutTab.isEnabled = isEnabled
+        
+    }
 
     func buildSnoozeAll(){
         form
@@ -356,13 +401,6 @@ class AlarmViewController: FormViewController {
                 AlarmSound.stop()
                 AlarmSound.playTest()
         }
-        <<< SwitchRow("alertTempoaryDND"){ row in
-        row.title = "Override System Volume"
-        row.value = UserDefaultsRepository.alertTemporaryBGDND.value
-        }.onChange { [weak self] row in
-                guard let value = row.value else { return }
-                UserDefaultsRepository.alertTemporaryBGDND.value = value
-        }
         <<< SwitchRow("alertTemporaryRepeat"){ row in
         row.title = "Repeat Sound"
         row.value = UserDefaultsRepository.alertTemporaryBGRepeat.value
@@ -427,13 +465,6 @@ class AlarmViewController: FormViewController {
                 AlarmSound.stop()
                 AlarmSound.playTest()
             }
-            <<< SwitchRow("alertUrgentLowDND"){ row in
-            row.title = "Override System Volume"
-            row.value = UserDefaultsRepository.alertUrgentLowDND.value
-            }.onChange { [weak self] row in
-                    guard let value = row.value else { return }
-                    UserDefaultsRepository.alertUrgentLowDND.value = value
-            }
             <<< SwitchRow("alertUrgentLowRepeat"){ row in
             row.title = "Repeat Sound"
             row.value = UserDefaultsRepository.alertUrgentLowRepeat.value
@@ -540,13 +571,6 @@ class AlarmViewController: FormViewController {
                 AlarmSound.stop()
                 AlarmSound.playTest()
             }
-            <<< SwitchRow("alertLowDND"){ row in
-            row.title = "Override System Volume"
-            row.value = UserDefaultsRepository.alertLowDND.value
-            }.onChange { [weak self] row in
-                    guard let value = row.value else { return }
-                    UserDefaultsRepository.alertLowDND.value = value
-            }
             <<< SwitchRow("alertLowRepeat"){ row in
             row.title = "Repeat Sound"
             row.value = UserDefaultsRepository.alertLowRepeat.value
@@ -667,13 +691,6 @@ class AlarmViewController: FormViewController {
                 AlarmSound.stop()
                 AlarmSound.playTest()
             }
-            <<< SwitchRow("alertHighDND"){ row in
-            row.title = "Override System Volume"
-            row.value = UserDefaultsRepository.alertHighDND.value
-            }.onChange { [weak self] row in
-                    guard let value = row.value else { return }
-                    UserDefaultsRepository.alertHighDND.value = value
-            }
             <<< SwitchRow("alertHighRepeat"){ row in
             row.title = "Repeat Sound"
             row.value = UserDefaultsRepository.alertHighRepeat.value
@@ -778,13 +795,6 @@ class AlarmViewController: FormViewController {
                 AlarmSound.stop()
                 AlarmSound.playTest()
             }
-            <<< SwitchRow("alertUrgentHighDND"){ row in
-            row.title = "Override System Volume"
-            row.value = UserDefaultsRepository.alertUrgentHighDND.value
-            }.onChange { [weak self] row in
-                    guard let value = row.value else { return }
-                    UserDefaultsRepository.alertUrgentHighDND.value = value
-            }
             <<< SwitchRow("alertUrgentHighRepeat"){ row in
             row.title = "Repeat Sound"
             row.value = UserDefaultsRepository.alertUrgentHighRepeat.value
@@ -926,13 +936,6 @@ class AlarmViewController: FormViewController {
                 AlarmSound.stop()
                 AlarmSound.playTest()
             }
-            <<< SwitchRow("alertFastDropDND"){ row in
-            row.title = "Override System Volume"
-            row.value = UserDefaultsRepository.alertFastDropDND.value
-            }.onChange { [weak self] row in
-                    guard let value = row.value else { return }
-                    UserDefaultsRepository.alertFastDropDND.value = value
-            }
             <<< SwitchRow("alertFastDropRepeat"){ row in
             row.title = "Repeat Sound"
             row.value = UserDefaultsRepository.alertFastDropRepeat.value
@@ -1074,13 +1077,6 @@ class AlarmViewController: FormViewController {
                 AlarmSound.stop()
                 AlarmSound.playTest()
             }
-            <<< SwitchRow("alertFastRiseDND"){ row in
-            row.title = "Override System Volume"
-            row.value = UserDefaultsRepository.alertFastRiseDND.value
-            }.onChange { [weak self] row in
-                    guard let value = row.value else { return }
-                    UserDefaultsRepository.alertFastRiseDND.value = value
-            }
             <<< SwitchRow("alertFastRiseRepeat"){ row in
             row.title = "Repeat Sound"
             row.value = UserDefaultsRepository.alertFastRiseRepeat.value
@@ -1187,13 +1183,6 @@ class AlarmViewController: FormViewController {
                 AlarmSound.stop()
                 AlarmSound.playTest()
             }
-            <<< SwitchRow("alertMissedReadingDND"){ row in
-            row.title = "Override System Volume"
-            row.value = UserDefaultsRepository.alertMissedReadingDND.value
-            }.onChange { [weak self] row in
-                    guard let value = row.value else { return }
-                    UserDefaultsRepository.alertMissedReadingDND.value = value
-            }
             <<< SwitchRow("alertMissedReadingRepeat"){ row in
             row.title = "Repeat Sound"
             row.value = UserDefaultsRepository.alertMissedReadingRepeat.value
@@ -1336,13 +1325,6 @@ class AlarmViewController: FormViewController {
                 AlarmSound.stop()
                 AlarmSound.playTest()
             }
-            <<< SwitchRow("alertNotLoopingDND"){ row in
-            row.title = "Override System Volume"
-            row.value = UserDefaultsRepository.alertNotLoopingDND.value
-            }.onChange { [weak self] row in
-                    guard let value = row.value else { return }
-                    UserDefaultsRepository.alertNotLoopingDND.value = value
-            }
             <<< SwitchRow("alertNotLoopingRepeat"){ row in
             row.title = "Repeat Sound"
             row.value = UserDefaultsRepository.alertNotLoopingRepeat.value
@@ -1504,12 +1486,12 @@ class AlarmViewController: FormViewController {
                 AlarmSound.stop()
                 AlarmSound.playTest()
             }
-            <<< SwitchRow("alertMissedBolusDND"){ row in
-            row.title = "Override System Volume"
-            row.value = UserDefaultsRepository.alertMissedBolusDND.value
+            <<< SwitchRow("alertMissedBolusQuiet"){ row in
+            row.title = "Use Quiet Hours"
+            row.value = UserDefaultsRepository.alertMissedBolusQuiet.value
             }.onChange { [weak self] row in
                     guard let value = row.value else { return }
-                    UserDefaultsRepository.alertMissedBolusDND.value = value
+                    UserDefaultsRepository.alertMissedBolusQuiet.value = value
             }
             <<< SwitchRow("alertMissedBolusRepeat"){ row in
             row.title = "Repeat Sound"
@@ -1630,12 +1612,12 @@ class AlarmViewController: FormViewController {
                 AlarmSound.stop()
                 AlarmSound.playTest()
             }
-            <<< SwitchRow("alertSAGEDND"){ row in
-            row.title = "Override System Volume"
-            row.value = UserDefaultsRepository.alertSAGEDND.value
+            <<< SwitchRow("alertSAGEQuiet"){ row in
+            row.title = "Use Quiet Hours"
+            row.value = UserDefaultsRepository.alertSAGEQuiet.value
             }.onChange { [weak self] row in
                     guard let value = row.value else { return }
-                    UserDefaultsRepository.alertSAGEDND.value = value
+                    UserDefaultsRepository.alertSAGEQuiet.value = value
             }
             <<< SwitchRow("alertSAGERepeat"){ row in
             row.title = "Repeat Sound"
@@ -1742,12 +1724,12 @@ class AlarmViewController: FormViewController {
                 AlarmSound.stop()
                 AlarmSound.playTest()
             }
-            <<< SwitchRow("alertCAGEDND"){ row in
-            row.title = "Override System Volume"
-            row.value = UserDefaultsRepository.alertCAGEDND.value
+            <<< SwitchRow("alertCAGEQuiet"){ row in
+            row.title = "Use Quiet Hours"
+            row.value = UserDefaultsRepository.alertCAGEQuiet.value
             }.onChange { [weak self] row in
                     guard let value = row.value else { return }
-                    UserDefaultsRepository.alertCAGEDND.value = value
+                    UserDefaultsRepository.alertCAGEQuiet.value = value
             }
             <<< SwitchRow("alertCAGERepeat"){ row in
             row.title = "Repeat Sound"
@@ -1826,12 +1808,12 @@ class AlarmViewController: FormViewController {
                 AlarmSound.stop()
                 AlarmSound.playTest()
         }
-        <<< SwitchRow("alertOverrideStartDND"){ row in
-        row.title = "Override System Volume"
-        row.value = UserDefaultsRepository.alertOverrideStartDND.value
+        <<< SwitchRow("alertOverrideStartQuiet"){ row in
+        row.title = "Use Quiet Hours"
+        row.value = UserDefaultsRepository.alertOverrideStartQuiet.value
         }.onChange { [weak self] row in
                 guard let value = row.value else { return }
-                UserDefaultsRepository.alertOverrideStartDND.value = value
+                UserDefaultsRepository.alertOverrideStartQuiet.value = value
         }
         <<< SwitchRow("alertOverrideStartRepeat"){ row in
         row.title = "Repeat Sound"
@@ -1840,6 +1822,46 @@ class AlarmViewController: FormViewController {
                 guard let value = row.value else { return }
                 UserDefaultsRepository.alertOverrideStartRepeat.value = value
         }
+        <<< DateTimeInlineRow("alertOverrideStartSnoozedTime") { row in
+                row.title = "Snoozed Until"
+                if (UserDefaultsRepository.alertOverrideStartSnoozedTime.value != nil) {
+                    row.value = UserDefaultsRepository.alertOverrideStartSnoozedTime.value
+                }
+                row.minuteInterval = 5
+                row.noValueDisplayText = "Not Snoozed"
+            }
+            .onChange { [weak self] row in
+                guard let value = row.value else { return }
+                UserDefaultsRepository.alertOverrideStartSnoozedTime.value = value
+                UserDefaultsRepository.alertOverrideStartIsSnoozed.value = true
+                let otherRow = self?.form.rowBy(tag: "alertOverrideStartIsSnoozed") as! SwitchRow
+                otherRow.value = true
+                otherRow.reload()
+            }
+            .onExpandInlineRow { [weak self] cell, row, inlineRow in
+                inlineRow.cellUpdate() { cell, row in
+                    cell.datePicker.datePickerMode = .dateAndTime
+                }
+                let color = cell.detailTextLabel?.textColor
+                row.onCollapseInlineRow { cell, _, _ in
+                    cell.detailTextLabel?.textColor = color
+                }
+                cell.detailTextLabel?.textColor = cell.tintColor
+            }
+            <<< SwitchRow("alertOverrideStartIsSnoozed"){ row in
+                row.title = "Is Snoozed"
+                row.value = UserDefaultsRepository.alertOverrideStartIsSnoozed.value
+                row.hidden = "$alertOverrideStartSnoozedTime == nil"
+            }.onChange { [weak self] row in
+                guard let value = row.value else { return }
+                UserDefaultsRepository.alertOverrideStartIsSnoozed.value = value
+                if !value {
+                    UserDefaultsRepository.alertOverrideStartSnoozedTime.setNil(key: "alertOverrideStartSnoozedTime")
+                    let otherRow = self?.form.rowBy(tag: "alertOverrideStartSnoozedTime") as! DateTimeInlineRow
+                    otherRow.value = nil
+                    otherRow.reload()
+                }
+        }
         
     }
     
@@ -1871,12 +1893,12 @@ class AlarmViewController: FormViewController {
                 AlarmSound.stop()
                 AlarmSound.playTest()
         }
-        <<< SwitchRow("alertOverrideEndDND"){ row in
-        row.title = "Override System Volume"
-        row.value = UserDefaultsRepository.alertOverrideEndDND.value
+        <<< SwitchRow("alertOverrideEndQuiet"){ row in
+        row.title = "Use Quiet Hours"
+        row.value = UserDefaultsRepository.alertOverrideEndQuiet.value
         }.onChange { [weak self] row in
                 guard let value = row.value else { return }
-                UserDefaultsRepository.alertOverrideEndDND.value = value
+                UserDefaultsRepository.alertOverrideEndQuiet.value = value
         }
         <<< SwitchRow("alertOverrideEndRepeat"){ row in
         row.title = "Repeat Sound"
@@ -1885,19 +1907,67 @@ class AlarmViewController: FormViewController {
                 guard let value = row.value else { return }
                 UserDefaultsRepository.alertOverrideEndRepeat.value = value
         }
+        <<< DateTimeInlineRow("alertOverrideEndSnoozedTime") { row in
+                row.title = "Snoozed Until"
+                if (UserDefaultsRepository.alertOverrideEndSnoozedTime.value != nil) {
+                    row.value = UserDefaultsRepository.alertOverrideEndSnoozedTime.value
+                }
+                row.minuteInterval = 5
+                row.noValueDisplayText = "Not Snoozed"
+            }
+            .onChange { [weak self] row in
+                guard let value = row.value else { return }
+                UserDefaultsRepository.alertOverrideEndSnoozedTime.value = value
+                UserDefaultsRepository.alertOverrideEndIsSnoozed.value = true
+                let otherRow = self?.form.rowBy(tag: "alertOverrideEndIsSnoozed") as! SwitchRow
+                otherRow.value = true
+                otherRow.reload()
+            }
+            .onExpandInlineRow { [weak self] cell, row, inlineRow in
+                inlineRow.cellUpdate() { cell, row in
+                    cell.datePicker.datePickerMode = .dateAndTime
+                }
+                let color = cell.detailTextLabel?.textColor
+                row.onCollapseInlineRow { cell, _, _ in
+                    cell.detailTextLabel?.textColor = color
+                }
+                cell.detailTextLabel?.textColor = cell.tintColor
+            }
+            <<< SwitchRow("alertOverrideEndIsSnoozed"){ row in
+                row.title = "Is Snoozed"
+                row.value = UserDefaultsRepository.alertOverrideEndIsSnoozed.value
+                row.hidden = "$alertOverrideEndSnoozedTime == nil"
+            }.onChange { [weak self] row in
+                guard let value = row.value else { return }
+                UserDefaultsRepository.alertOverrideEndIsSnoozed.value = value
+                if !value {
+                    UserDefaultsRepository.alertOverrideEndSnoozedTime.setNil(key: "alertOverrideEndSnoozedTime")
+                    let otherRow = self?.form.rowBy(tag: "alertOverrideEndSnoozedTime") as! DateTimeInlineRow
+                    otherRow.value = nil
+                    otherRow.reload()
+                }
+        }
         
     }
 
     func buildAlarmSettings() {
            form
-            +++ Section(header: "Alarm Sound Settings", footer: "Use this volume level for alarms that override system volume")
+            +++ Section(header: "Alarm Sound Settings", footer: "")
            
+            <<< SwitchRow("overrideSystemOutputVolume"){ row in
+                row.title = "Override System Volume"
+                row.value = UserDefaultsRepository.overrideSystemOutputVolume.value
+            }.onChange { [weak self] row in
+                        guard let value = row.value else { return }
+                        UserDefaultsRepository.overrideSystemOutputVolume.value = value
+                }
             <<< StepperRow("forcedOutputVolume") { row in
                   row.title = "Volume Level"
                 row.cell.stepper.stepValue = 0.05
                   row.cell.stepper.minimumValue = 0
                   row.cell.stepper.maximumValue = 1
                   row.value = Double(UserDefaultsRepository.forcedOutputVolume.value)
+                  row.hidden = "$overrideSystemOutputVolume == false"
                     row.displayValueFor = { value in
                     guard let value = value else { return nil }
                     return "\(Int(value*100))%"
@@ -1906,20 +1976,26 @@ class AlarmViewController: FormViewController {
                       guard let value = row.value else { return }
                       UserDefaultsRepository.forcedOutputVolume.value = Float(value)
               }
-           /* <<< StepperRow("fadeInTimeInterval") { row in
-                row.title = "Fade-in Seconds"
-                row.cell.stepper.stepValue = 5
-                row.cell.stepper.minimumValue = 0
-                row.cell.stepper.maximumValue = 60
-                row.value = Double(UserDefaultsRepository.fadeInTimeInterval.value)
-                row.displayValueFor = { value in
-                guard let value = value else { return nil }
-                return "\(Int(value))"
-                }
-            }.onChange { [weak self] row in
-                    guard let value = row.value else { return }
-                    UserDefaultsRepository.fadeInTimeInterval.value = TimeInterval(value)
-            }*/
+            
+             +++ Section(header: "Quiet Hour Settings", footer: "Quiet hours can be used to automatically snooze non-critical alerts that you do not wish to be awakened for such as a sensor change pre-alert that may happen during the night.")  { row in
+                row.tag = "quietHourSection"
+                        }
+            <<< TimeInlineRow("quietHourStart") { row in
+                row.title = "Quiet Hours Start Today"
+                row.value = UserDefaultsRepository.quietHourStart.value
+                
+        }.onChange { [weak self] row in
+                guard let value = row.value else { return }
+                UserDefaultsRepository.quietHourStart.value = value
+        }
+        <<< TimeInlineRow("quietHourEnd") { row in
+                row.title = "Quiet Hours End Tomorrow"
+                row.value = UserDefaultsRepository.quietHourEnd.value
+                
+        }.onChange { [weak self] row in
+                guard let value = row.value else { return }
+                UserDefaultsRepository.quietHourEnd.value = value
+        }
        }
 
 }

+ 31 - 30
LoopFollow/ViewControllers/MainViewController.swift

@@ -46,6 +46,7 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
     @IBOutlet weak var statsAvgBG: UILabel!
     @IBOutlet weak var statsEstA1C: UILabel!
     @IBOutlet weak var statsStdDev: UILabel!
+
         
     // Data Table class
     class infoData {
@@ -55,6 +56,9 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
             self.name = name
             self.value = value
         }
+
+    @IBOutlet weak var serverText: UILabel!
+
     }
 
     // Variables for BG Charts
@@ -100,14 +104,14 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
     var tableData : [infoData] = []
     var derivedTableData: [infoData] = []
     
-    var bgData: [DataStructs.sgvData] = []
+    var bgData: [ShareGlucoseData] = []
     var basalProfile: [basalProfileStruct] = []
     var basalData: [basalGraphStruct] = []
     var basalScheduleData: [basalGraphStruct] = []
     var bolusData: [bolusCarbGraphStruct] = []
     var carbData: [bolusCarbGraphStruct] = []
     var overrideData: [DataStructs.overrideGraphStruct] = []
-    var predictionData: [DataStructs.sgvData] = []
+    var predictionData: [ShareGlucoseData] = []
     var chartData = LineChartData()
     var newBGPulled = false
     var lastCalDate: Double = 0
@@ -208,19 +212,12 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
             createSmallBGGraph()
         }
         
+        // setup display for NS vs Dex
+        showHideNSDetails()
+        
         // Load Data
-        if UserDefaultsRepository.url.value != "" && firstGraphLoad {
+        if (UserDefaultsRepository.url.value != "" || (UserDefaultsRepository.shareUserName.value != "" && UserDefaultsRepository.sharePassword.value != "")) && firstGraphLoad {
             nightscoutLoader()
-          
-            // TODO: move this to MainViewController extension ?
-            dexShare?.fetchData(262) { (err, result) -> () in
-                
-                // TODO: add error checking
-                if(err == nil) {
-                   self.bgDataShare = result!
-                }
-                // print("\(self.bgDataShare)")
-            }
         }
     }
     
@@ -370,21 +367,15 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
         
     }
     
+    @objc override func viewDidAppear(_ animated: Bool) {
+        showHideNSDetails()
+    }
+    
     // Check for new data when timer ends
     @objc func timerDidEnd(_ timer:Timer) {
 //        if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Main timer ended") }
         updateMinAgo()
         nightscoutLoader()
-        
-        // TODO: move this to MainViewController extension ?
-        dexShare?.fetchData(262) { (err, result) -> () in
-            
-            // TODO: add error checking
-            if(err == nil) {
-              self.bgDataShare = result!
-            }
-            // print("\(self.bgDataShare)")
-        }
     }
 
     //update Min Ago Text. We need to call this separately because it updates between readings
@@ -406,12 +397,8 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
     }
     
     //Clear the info data before next pull. This ensures we aren't displaying old data if something fails.
-    func clearLastInfoData(){
-        // var tmpData = tableData[2]
-        for i in 0..<tableData.count{
-            tableData[i].value = ""
-        }
-        // tableData[2] = tmpData
+    func clearLastInfoData(index: Int){
+        tableData[index].value = ""
     }
 
     func stringFromTimeInterval(interval: TimeInterval) -> String {
@@ -421,7 +408,21 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
         return String(format: "%02d:%02d", hours, minutes)
     }
 
-    
+    func showHideNSDetails() {
+        var isHidden = false
+        var isEnabled = true
+        if UserDefaultsRepository.url.value == "" {
+            isHidden = true
+            isEnabled = false
+        }
+        
+        LoopStatusLabel.isHidden = isHidden
+        PredictionLabel.isHidden = isHidden
+        infoTable.isHidden = isHidden
+        guard let nightscoutTab = self.tabBarController?.tabBar.items![3] else { return }
+        nightscoutTab.isEnabled = isEnabled
+        
+    }
     
     func updateBadge(val: Int) {
         if UserDefaultsRepository.appBadge.value {

+ 5 - 1
LoopFollow/ViewControllers/SettingsViewController.swift

@@ -28,7 +28,9 @@ class SettingsViewController: FormViewController {
         }.cellSetup { (cell, row) in
             cell.textField.autocorrectionType = .no
         }.onChange { row in
-            guard let value = row.value else { return }
+            guard let value = row.value else {
+                UserDefaultsRepository.url.value = ""
+                return }
             // check the format of the URL entered by the user and trim away any spaces or "/" at the end
             var urlNSInput = value.replacingOccurrences(of: "\\s+$", with: "", options: .regularExpression)
             if urlNSInput.last == "/" {
@@ -105,8 +107,10 @@ class SettingsViewController: FormViewController {
                   return controller
                }
            ), onDismiss: nil)
+
             
         }
+
         // +++ Section("Graph Settings")
         <<< ButtonRow() {
            $0.title = "Graph Settings"

+ 41 - 0
LoopFollow/ViewControllers/SnoozeViewController.swift

@@ -156,6 +156,47 @@ class SnoozeViewController: UIViewController, UNUserNotificationCenterDelegate {
         }
     }
     
+    func setQuietHours(snoozeTime: Date)
+    {
+        
+        if UserDefaultsRepository.alertMissedBolusQuiet.value {
+            UserDefaultsRepository.alertMissedBolusIsSnoozed.value = true
+            UserDefaultsRepository.alertMissedBolusSnoozedTime.value = snoozeTime
+            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            alarms.reloadIsSnoozed(key: "alertMissedBolusIsSnoozed", value: true)
+            alarms.reloadSnoozeTime(key: "alertMissedBolusSnoozedTime", setNil: false, value: snoozeTime)
+        }
+        if UserDefaultsRepository.alertOverrideStartQuiet.value {
+            UserDefaultsRepository.alertOverrideStartIsSnoozed.value = true
+            UserDefaultsRepository.alertOverrideStartSnoozedTime.value = snoozeTime
+            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            alarms.reloadIsSnoozed(key: "alertOverrideStartIsSnoozed", value: true)
+            alarms.reloadSnoozeTime(key: "alertOverrideStartSnoozedTime", setNil: false, value: snoozeTime)
+        }
+        if UserDefaultsRepository.alertOverrideEndQuiet.value {
+            UserDefaultsRepository.alertOverrideEndIsSnoozed.value = true
+            UserDefaultsRepository.alertOverrideEndSnoozedTime.value = snoozeTime
+            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            alarms.reloadIsSnoozed(key: "alertOverrideEndIsSnoozed", value: true)
+            alarms.reloadSnoozeTime(key: "alertOverrideEndSnoozedTime", setNil: false, value: snoozeTime)
+        }
+        if UserDefaultsRepository.alertCAGEQuiet.value {
+            UserDefaultsRepository.alertCAGEIsSnoozed.value = true
+            UserDefaultsRepository.alertCAGESnoozedTime.value = snoozeTime
+            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            alarms.reloadIsSnoozed(key: "alertCAGEIsSnoozed", value: true)
+            alarms.reloadSnoozeTime(key: "alertCAGESnoozedTime", setNil: false, value: snoozeTime)
+        }
+        if UserDefaultsRepository.alertSAGEQuiet.value {
+            UserDefaultsRepository.alertSAGEIsSnoozed.value = true
+            UserDefaultsRepository.alertSAGESnoozedTime.value = snoozeTime
+            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            alarms.reloadIsSnoozed(key: "alertSAGEIsSnoozed", value: true)
+            alarms.reloadSnoozeTime(key: "alertSAGESnoozedTime", setNil: false, value: snoozeTime)
+        }
+        
+    }
+    
     override func viewDidLoad() {
         super.viewDidLoad()
         if UserDefaultsRepository.forceDarkMode.value {

+ 0 - 7
LoopFollow/helpers/DataStructs.swift

@@ -10,13 +10,6 @@ import Foundation
 
 class DataStructs {
     
-    //NS BG Struct
-    struct sgvData: Codable {
-        var sgv: Int
-        var date: TimeInterval
-        var direction: String?
-    }
-    
     // Pie Chart Data
     struct pieData: Codable {
         var name: String

+ 1 - 1
LoopFollow/helpers/carbBolusArrays.swift

@@ -11,7 +11,7 @@ import Foundation
 
 extension MainViewController {
     
-    func findNearestBGbyTime(needle: TimeInterval, haystack: [DataStructs.sgvData], startingIndex: Int) -> (sgv: Double, foundIndex: Int) {
+    func findNearestBGbyTime(needle: TimeInterval, haystack: [ShareGlucoseData], 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 {

+ 12 - 14
LoopFollow/repository/UserDefaults.swift

@@ -87,6 +87,9 @@ class UserDefaultsRepository {
     
     // Alerts
     
+    static let quietHourStart = UserDefaultsValue<Date?>(key: "quietHourStart", default: nil)
+    static let quietHourEnd = UserDefaultsValue<Date?>(key: "quietHourEnd", default: nil)
+    
     static let snoozedBGReadingTime = UserDefaultsValue<TimeInterval?>(key: "snoozedBGReadingTime", default: 0)
     
     static let alertCageInsertTime = UserDefaultsValue<TimeInterval>(key: "alertCageInsertTime", default: 0)
@@ -100,7 +103,6 @@ class UserDefaultsRepository {
     static let alertUrgentLowSnooze = UserDefaultsValue<Int>(key: "alertUrgentLowSnooze", default: 5)
     static let alertUrgentLowSnoozedTime = UserDefaultsValue<Date?>(key: "alertUrgentLowSnoozedTime", default: nil)
     static let alertUrgentLowIsSnoozed = UserDefaultsValue<Bool>(key: "alertUrgentLowIsSnoozed", default: false)
-    static let alertUrgentLowDND = UserDefaultsValue<Bool>(key: "alertUrgentLowDND", default: true)
     static let alertUrgentLowRepeat = UserDefaultsValue<Bool>(key: "alertUrgentLowRepeat", default: true)
     static let alertUrgentLowSound = UserDefaultsValue<String>(key: "alertUrgentLowSound", default: "Indeed")
     
@@ -109,7 +111,6 @@ class UserDefaultsRepository {
     static let alertLowSnooze = UserDefaultsValue<Int>(key: "alertLowSnooze", default: 5)
     static let alertLowSnoozedTime = UserDefaultsValue<Date?>(key: "alertLowSnoozedTime", default: nil)
     static let alertLowIsSnoozed = UserDefaultsValue<Bool>(key: "alertLowIsSnoozed", default: false)
-    static let alertLowDND = UserDefaultsValue<Bool>(key: "alertLowDND", default: true)
     static let alertLowRepeat = UserDefaultsValue<Bool>(key: "alertLowRepeat", default: true)
     static let alertLowSound = UserDefaultsValue<String>(key: "alertLowSound", default: "Indeed")
     
@@ -119,7 +120,6 @@ class UserDefaultsRepository {
     static let alertHighSnooze = UserDefaultsValue<Int>(key: "alertHighSnooze", default: 60)
     static let alertHighSnoozedTime = UserDefaultsValue<Date?>(key: "alertHighSnoozedTime", default: nil)
     static let alertHighIsSnoozed = UserDefaultsValue<Bool>(key: "alertHighIsSnoozed", default: false)
-    static let alertHighDND = UserDefaultsValue<Bool>(key: "alertHighDND", default: true)
     static let alertHighRepeat = UserDefaultsValue<Bool>(key: "alertHighRepeat", default: true)
     static let alertHighSound = UserDefaultsValue<String>(key: "alertHighSound", default: "Indeed")
     
@@ -128,7 +128,6 @@ class UserDefaultsRepository {
     static let alertUrgentHighSnooze = UserDefaultsValue<Int>(key: "alertUrgentHighSnooze", default: 30)
     static let alertUrgentHighSnoozedTime = UserDefaultsValue<Date?>(key: "alertUrgentHighSnoozedTime", default: nil)
     static let alertUrgentHighIsSnoozed = UserDefaultsValue<Bool>(key: "alertUrgentHighIsSnoozed", default: false)
-    static let alertUrgentHighDND = UserDefaultsValue<Bool>(key: "alertUrgentHighDND", default: true)
     static let alertUrgentHighRepeat = UserDefaultsValue<Bool>(key: "alertUrgentHighRepeat", default: true)
     static let alertUrgentHighSound = UserDefaultsValue<String>(key: "alertUrgentHighSound", default: "Indeed")
 
@@ -141,7 +140,6 @@ class UserDefaultsRepository {
     static let alertFastDropBelowBG = UserDefaultsValue<Float>(key: "alertFastDropBelowBG", default: 120.0)
     static let alertFastDropSnoozedTime = UserDefaultsValue<Date?>(key: "alertFastDropSnoozedTime", default: nil)
     static let alertFastDropIsSnoozed = UserDefaultsValue<Bool>(key: "alertFastDropIsSnoozed", default: false)
-    static let alertFastDropDND = UserDefaultsValue<Bool>(key: "alertFastDropDND", default: true)
     static let alertFastDropRepeat = UserDefaultsValue<Bool>(key: "alertFastDropRepeat", default: true)
     static let alertFastDropSound = UserDefaultsValue<String>(key: "alertFastDropSound", default: "Indeed")
     
@@ -153,7 +151,6 @@ class UserDefaultsRepository {
     static let alertFastRiseAboveBG = UserDefaultsValue<Float>(key: "alertFastRiseAboveBG", default: 200.0)
     static let alertFastRiseSnoozedTime = UserDefaultsValue<Date?>(key: "alertFastRiseSnoozedTime", default: nil)
     static let alertFastRiseIsSnoozed = UserDefaultsValue<Bool>(key: "alertFastRiseIsSnoozed", default: false)
-    static let alertFastRiseDND = UserDefaultsValue<Bool>(key: "alertFastRiseDND", default: true)
     static let alertFastRiseRepeat = UserDefaultsValue<Bool>(key: "alertFastRiseRepeat", default: true)
     static let alertFastRiseSound = UserDefaultsValue<String>(key: "alertFastRiseSound", default: "Indeed")
     
@@ -163,7 +160,6 @@ class UserDefaultsRepository {
     static let alertMissedReadingSnooze = UserDefaultsValue<Int>(key: "alertMissedReadingSnooze", default: 30)
     static let alertMissedReadingSnoozedTime = UserDefaultsValue<Date?>(key: "alertMissedReadingSnoozedTime", default: nil)
     static let alertMissedReadingIsSnoozed = UserDefaultsValue<Bool>(key: "alertMissedReadingIsSnoozed", default: false)
-    static let alertMissedReadingDND = UserDefaultsValue<Bool>(key: "alertMissedReadingDND", default: true)
     static let alertMissedReadingRepeat = UserDefaultsValue<Bool>(key: "alertMissedReadingRepeat", default: true)
     static let alertMissedReadingSound = UserDefaultsValue<String>(key: "alertMissedReadingSound", default: "Indeed")
     
@@ -176,7 +172,6 @@ class UserDefaultsRepository {
     static let alertNotLoopingUpperLimit = UserDefaultsValue<Float>(key: "alertNotLoopingAboveBG", default: 160.0)
     static let alertNotLoopingSnoozedTime = UserDefaultsValue<Date?>(key: "alertNotLoopingSnoozedTime", default: nil)
     static let alertNotLoopingIsSnoozed = UserDefaultsValue<Bool>(key: "alertNotLoopingIsSnoozed", default: false)
-    static let alertNotLoopingDND = UserDefaultsValue<Bool>(key: "alertNotLoopingDND", default: true)
     static let alertNotLoopingRepeat = UserDefaultsValue<Bool>(key: "alertNotLoopingRepeat", default: true)
     static let alertNotLoopingSound = UserDefaultsValue<String>(key: "alertNotLoopingSound", default: "Indeed")
     static let alertLastLoopTime = UserDefaultsValue<TimeInterval>(key: "alertLastLoopTime", default: 0)
@@ -190,13 +185,13 @@ class UserDefaultsRepository {
     static let alertMissedBolusLowGramsBG = UserDefaultsValue<Float>(key: "alertMissedBolusLowGramsBG", default: 70.0)
     static let alertMissedBolusSnoozedTime = UserDefaultsValue<Date?>(key: "alertMissedBolusSnoozedTime", default: nil)
     static let alertMissedBolusIsSnoozed = UserDefaultsValue<Bool>(key: "alertMissedBolusIsSnoozed", default: false)
-    static let alertMissedBolusDND = UserDefaultsValue<Bool>(key: "alertMissedBolusDND", default: false)
+    static let alertMissedBolusQuiet = UserDefaultsValue<Bool>(key: "alertMissedBolusQuiet", default: false)
     static let alertMissedBolusRepeat = UserDefaultsValue<Bool>(key: "alertMissedBolusRepeat", default: false)
     static let alertMissedBolusSound = UserDefaultsValue<String>(key: "alertMissedBolusSound", default: "Indeed")
     
     static let alertSAGEActive = UserDefaultsValue<Bool>(key: "alertSAGEActive", default: false)
     static let alertSAGE = UserDefaultsValue<Int>(key: "alertSAGE", default: 8) //Hours
-    static let alertSAGEDND = UserDefaultsValue<Bool>(key: "alertSAGEDND", default: false)
+    static let alertSAGEQuiet = UserDefaultsValue<Bool>(key: "alertSAGEQuiet", default: false)
     static let alertSAGERepeat = UserDefaultsValue<Bool>(key: "alertSAGERepeat", default: false)
     static let alertSAGESnooze = UserDefaultsValue<Int>(key: "alertSAGESnooze", default: 2) //Hours
     static let alertSAGESnoozedTime = UserDefaultsValue<Date?>(key: "alertSAGESnoozedTime", default: nil)
@@ -205,7 +200,7 @@ class UserDefaultsRepository {
     
     static let alertCAGEActive = UserDefaultsValue<Bool>(key: "alertCAGEActive", default: false)
     static let alertCAGE = UserDefaultsValue<Int>(key: "alertCAGE", default: 4) //Hours
-    static let alertCAGEDND = UserDefaultsValue<Bool>(key: "alertCAGEDND", default: false)
+    static let alertCAGEQuiet = UserDefaultsValue<Bool>(key: "alertCAGEQuiet", default: false)
     static let alertCAGERepeat = UserDefaultsValue<Bool>(key: "alertCAGERepeat", default: false)
     static let alertCAGESnooze = UserDefaultsValue<Int>(key: "alertCAGESnooze", default: 2) //Hours
     static let alertCAGESnoozedTime = UserDefaultsValue<Date?>(key: "alertCAGESnoozedTime", default: nil)
@@ -217,18 +212,21 @@ class UserDefaultsRepository {
     static let alertTemporaryActive = UserDefaultsValue<Bool>(key: "alertTemporaryActive", default: false)
     static let alertTemporaryBelow = UserDefaultsValue<Bool>(key: "alertTemporaryBelow", default: true)
     static let alertTemporaryBG = UserDefaultsValue<Float>(key: "alertTemporaryBG", default: 90.0)
-    static let alertTemporaryBGDND = UserDefaultsValue<Bool>(key: "alertTemporaryBGDND", default: true)
     static let alertTemporaryBGRepeat = UserDefaultsValue<Bool>(key: "alertTemporaryBGRepeat", default: true)
     static let alertTemporarySound = UserDefaultsValue<String>(key: "alertTemporarySound", default: "Indeed")
     
     static let alertOverrideStart = UserDefaultsValue<Bool>(key: "alertOverrideStart", default: false)
-    static let alertOverrideStartDND = UserDefaultsValue<Bool>(key: "alertOverrideStartDND", default: false)
+    static let alertOverrideStartQuiet = UserDefaultsValue<Bool>(key: "alertOverrideStartQuiet", default: false)
     static let alertOverrideStartRepeat = UserDefaultsValue<Bool>(key: "alertOverrideStartRepeat", default: false)
     static let alertOverrideStartSound = UserDefaultsValue<String>(key: "alertOverrideStartSound", default: "Alert_Tone_Busy")
+    static let alertOverrideStartSnoozedTime = UserDefaultsValue<Date?>(key: "alertOverrideStartSnoozedTime", default: nil)
+    static let alertOverrideStartIsSnoozed = UserDefaultsValue<Bool>(key: "alertOverrideStartIsSnoozed", default: false)
     
     static let alertOverrideEnd = UserDefaultsValue<Bool>(key: "alertOverrideEnd", default: false)
-    static let alertOverrideEndDND = UserDefaultsValue<Bool>(key: "alertOverrideEndDND", default: false)
+    static let alertOverrideEndQuiet = UserDefaultsValue<Bool>(key: "alertOverrideEndQuiet", default: false)
     static let alertOverrideEndRepeat = UserDefaultsValue<Bool>(key: "alertOverrideEndRepeat", default: false)
     static let alertOverrideEndSound = UserDefaultsValue<String>(key: "alertOverrideEndSound", default: "Ending_Reached")
+    static let alertOverrideEndSnoozedTime = UserDefaultsValue<Date?>(key: "alertOverrideEndSnoozedTime", default: nil)
+    static let alertOverrideEndIsSnoozed = UserDefaultsValue<Bool>(key: "alertOverrideEndIsSnoozed", default: false)
     
 }

+ 21 - 0
Pods/Local Podspecs/ShareClient.podspec.json

@@ -0,0 +1,21 @@
+{
+  "name": "ShareClient",
+  "version": "1.2",
+  "summary": "Dexcom Client Pod",
+  "swift_versions": "5.0",
+  "homepage": "https://github.com/josep1972/dexcom-share-client-swift",
+  "license": "MIT",
+  "authors": "josep1972",
+  "platforms": {
+    "ios": "13"
+  },
+  "source": {
+    "git": "https://github.com/josep1972/dexcom-share-client-swift.git",
+    "tag": "v1.2"
+  },
+  "source_files": [
+    "ShareClient/ShareClient.h",
+    "ShareClient/ShareClient.swift"
+  ],
+  "swift_version": "5.0"
+}

+ 93 - 93
Pods/Pods.xcodeproj/project.pbxproj

@@ -15,7 +15,6 @@
 		03D8F8D16FAF2B28113DB82186DBC66F /* BarLineChartViewBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E9C61C3E7EBE33601F29273FD0610F /* BarLineChartViewBase.swift */; };
 		052CDA13B29437A2FDCD9D19230AD39F /* FieldsRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E304C127B722E54D8A82C9CAD421A740 /* FieldsRow.swift */; };
 		05C88AAB95E98E3296996797706F19AF /* RuleRequired.swift in Sources */ = {isa = PBXBuildFile; fileRef = 358D210738B60758170EEAB4EBFD35F7 /* RuleRequired.swift */; };
-		0651EDC4A371F639FD2EEAD9B5C2B26D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 436BAA54A31999B53B3CC7115C55FE50 /* Foundation.framework */; };
 		07572F1826C958646848D4EF5DBF377E /* RuleRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F6A8E2C6ECCAF59C045894E1F3DF8C5 /* RuleRange.swift */; };
 		0769C281810E13C0985B2A414F20ADDE /* XAxisRendererHorizontalBarChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F34C60E8ABE81F99BFB0915A1517A0 /* XAxisRendererHorizontalBarChart.swift */; };
 		09D2EA12CED518BAA4BCB6A707FA3125 /* TriangleShapeRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 733827F65F5022C471A1447F04440570 /* TriangleShapeRenderer.swift */; };
@@ -52,6 +51,7 @@
 		35C57ED01BE2D7FD6F4DACD96F5EDFE1 /* IFillFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 805B8ACDB7AF64D4B1EFF9695D06D6E7 /* IFillFormatter.swift */; };
 		39E13C8ED0C32E9EC0B353CA45C70CF2 /* AnimatedMoveViewJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9F3AC74EA385AD1FB48BB312B9A8D01 /* AnimatedMoveViewJob.swift */; };
 		3A5078F021BB004E2F4463F360D41391 /* HorizontalBarChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBB8BBD29DF85A11464A5ADA946CB94D /* HorizontalBarChartView.swift */; };
+		3A81072EB144BB6F03C7F5360EEEE03D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 436BAA54A31999B53B3CC7115C55FE50 /* Foundation.framework */; };
 		3B86A25C5362FD2905831A10C4806500 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 436BAA54A31999B53B3CC7115C55FE50 /* Foundation.framework */; };
 		3BF29BEA4C1FF7360E93882C9D6C2A7C /* AlertRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F50F1D5643D6EBCA09E428806984608 /* AlertRow.swift */; };
 		3D57709B53538839313AA6A6DC6E322C /* ILineRadarChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBF8B918AB264444686976350703223A /* ILineRadarChartDataSet.swift */; };
@@ -161,13 +161,12 @@
 		B466D5B2650B7521FBE653FC9B7A793D /* DateInlineRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082D9D903940DEE55728285DC66D61EB /* DateInlineRow.swift */; };
 		B490953386257F2C8E03C1D632AC361E /* OptionsRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8F18420183616B27CA1A9863B96A94 /* OptionsRow.swift */; };
 		B65DAD4280582DBED3A5A2C62185B389 /* YAxisRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A1B6FFE86F41186B23437AC8B0AD94 /* YAxisRenderer.swift */; };
+		B66CAEA692B43131362E25A49DDF71EF /* Pods-LoopFollow-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 7E41188828C3ADE7A96C4DA6ED1CCDA2 /* Pods-LoopFollow-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		B6DF9275361EA04A959E38F4133C33B7 /* SwipeActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F8A26F11880F80D57A284B6094FF410 /* SwipeActions.swift */; };
 		B87DABFAA95CDA5DFDDD2F409FDD5DF3 /* YAxisRendererHorizontalBarChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = F52B736816C4134465DD07E7A3C6EE82 /* YAxisRendererHorizontalBarChart.swift */; };
 		BB75859BDCF578D67C08E6E6741701EB /* PieChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E89110FD8C719CD0408D733C769EECC7 /* PieChartRenderer.swift */; };
 		BCD42880A54D247B7A68AC06335D0AF5 /* YAxis.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D9C9FC3F24D3132834031FBA0497B6 /* YAxis.swift */; };
-		BDD0A32FDF662207FD5E5391C04E31FC /* Pods-LoopFollow-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 7E41188828C3ADE7A96C4DA6ED1CCDA2 /* Pods-LoopFollow-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		BDD367ED4BB5510C86658115EE6BE71E /* CombinedHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE34CABB424024A41851C39678BA5EF3 /* CombinedHighlighter.swift */; };
-		BE11B033F02B91FA038526546C8E57B6 /* Pods-LoopFollow-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 28609CC9D3828C150821F3AA18752A77 /* Pods-LoopFollow-dummy.m */; };
 		BE93E023215A8B4A05A2AEC8B7FAE170 /* InlineRowType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7967D1617EF3687E7EA393DDFEBC8B31 /* InlineRowType.swift */; };
 		C031EFD3013B5803C3B7A6D07E4721C0 /* CombinedChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 919647803888A5B15DB3222FCDA56E0A /* CombinedChartData.swift */; };
 		C08713DF3DF904E55D8A3E3775A6F3EB /* TriplePickerRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D8EB04398FDB94F46498AD99A989CA8 /* TriplePickerRow.swift */; };
@@ -222,6 +221,7 @@
 		ECC149B136D4D63AF251912E051A142F /* IShapeRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB219299A8A67D5EC1C43C6BF5A8EE4 /* IShapeRenderer.swift */; };
 		ED58A23EB5289726B5D74EFEEA590BDD /* LineChartData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D365274AEF519C7E50E8567DEBC9CEF3 /* LineChartData.swift */; };
 		F1B3693550BD159B7E910C182326A44B /* ChartAnimationEasing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 254111998274AAAA1452EBD98043BD22 /* ChartAnimationEasing.swift */; };
+		F1B37767979921C4758B54144610815D /* Pods-LoopFollow-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 28609CC9D3828C150821F3AA18752A77 /* Pods-LoopFollow-dummy.m */; };
 		F2279E1339CC1B6326AD6FF389D24F96 /* DateInlineFieldRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFAF86922EDD701C440B78809BA7858 /* DateInlineFieldRow.swift */; };
 		F2C1EF0010084FA2B769B019EBD6D909 /* CombinedChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B27115EBCC224F7D67778C26B1275B8 /* CombinedChartView.swift */; };
 		F47D1993C006C374C634B04421484A4D /* AlertOptionsRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6153DD0B9FA40F02377E5F8A060E378 /* AlertOptionsRow.swift */; };
@@ -231,26 +231,26 @@
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
-		70AC7791B3B1114DB847777CFED5BBDA /* PBXContainerItemProxy */ = {
+		7272ABD8A5C4DE0FAA10777D4FE249DD /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
 			containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
 			proxyType = 1;
-			remoteGlobalIDString = 32507FDB9BAD6EF17DCB14A888ECA5D9;
-			remoteInfo = ShareClient;
+			remoteGlobalIDString = BF03B0B2B8B052092CB5F90CC5FB3757;
+			remoteInfo = Charts;
 		};
-		80929478E859790595DCC4B6934EEA68 /* PBXContainerItemProxy */ = {
+		768CF338A2BC484E9597CD398DFA4136 /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
 			containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
 			proxyType = 1;
-			remoteGlobalIDString = 88BE8BE7A2FE6CED4193CCD8FF3CBEF7;
-			remoteInfo = Eureka;
+			remoteGlobalIDString = 32507FDB9BAD6EF17DCB14A888ECA5D9;
+			remoteInfo = ShareClient;
 		};
-		B4EAD249F74DCCC9C759EEEBC10E9BE0 /* PBXContainerItemProxy */ = {
+		87D6DD57B95016F1515B8CA873C52B51 /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
 			containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */;
 			proxyType = 1;
-			remoteGlobalIDString = BF03B0B2B8B052092CB5F90CC5FB3757;
-			remoteInfo = Charts;
+			remoteGlobalIDString = 88BE8BE7A2FE6CED4193CCD8FF3CBEF7;
+			remoteInfo = Eureka;
 		};
 /* End PBXContainerItemProxy section */
 
@@ -503,28 +503,28 @@
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
-		7C739679CAE74D90F140F5EDC0EE4EC0 /* Frameworks */ = {
+		1DC97817961139E7E35F2AE2C60B6E3C /* Frameworks */ = {
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				624CD096DCC639825FDB7372D5E5FF43 /* Foundation.framework in Frameworks */,
-				7EEDCE629FA937973E081443AAEEF514 /* UIKit.framework in Frameworks */,
+				3A81072EB144BB6F03C7F5360EEEE03D /* Foundation.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
-		7E4835E1A446981AFA38D55A1D9E0142 /* Frameworks */ = {
+		7C739679CAE74D90F140F5EDC0EE4EC0 /* Frameworks */ = {
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				3B86A25C5362FD2905831A10C4806500 /* Foundation.framework in Frameworks */,
+				624CD096DCC639825FDB7372D5E5FF43 /* Foundation.framework in Frameworks */,
+				7EEDCE629FA937973E081443AAEEF514 /* UIKit.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
-		A09217DFEBD5550C0EFA50C1D3F01DE9 /* Frameworks */ = {
+		7E4835E1A446981AFA38D55A1D9E0142 /* Frameworks */ = {
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				0651EDC4A371F639FD2EEAD9B5C2B26D /* Foundation.framework in Frameworks */,
+				3B86A25C5362FD2905831A10C4806500 /* Foundation.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -912,19 +912,19 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
-		5774E71EFFCC7CB457E7335C552D05B1 /* Headers */ = {
+		7DD3157FBDD386B4916158C875050C47 /* Headers */ = {
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				BDD0A32FDF662207FD5E5391C04E31FC /* Pods-LoopFollow-umbrella.h in Headers */,
+				C4EB1DE2CCEE60A99EB5AA718DD67105 /* Eureka-umbrella.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
-		7DD3157FBDD386B4916158C875050C47 /* Headers */ = {
+		9B9F97AA1CF9FB0787D2291A888A6C93 /* Headers */ = {
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				C4EB1DE2CCEE60A99EB5AA718DD67105 /* Eureka-umbrella.h in Headers */,
+				B66CAEA692B43131362E25A49DDF71EF /* Pods-LoopFollow-umbrella.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -996,19 +996,19 @@
 		};
 		E187D4B9C2B76F773546AF1E39D2ECCD /* Pods-LoopFollow */ = {
 			isa = PBXNativeTarget;
-			buildConfigurationList = 33267BE99E13D5D253525BA1D49CA2DA /* Build configuration list for PBXNativeTarget "Pods-LoopFollow" */;
+			buildConfigurationList = C296AD097A8FA560D79E90D3C22C96E9 /* Build configuration list for PBXNativeTarget "Pods-LoopFollow" */;
 			buildPhases = (
-				5774E71EFFCC7CB457E7335C552D05B1 /* Headers */,
-				3EC2C540F4F22A21FD47A7EF7C6E6262 /* Sources */,
-				A09217DFEBD5550C0EFA50C1D3F01DE9 /* Frameworks */,
-				C8C3B267820FCCB290B983FC6A267748 /* Resources */,
+				9B9F97AA1CF9FB0787D2291A888A6C93 /* Headers */,
+				5BEF58C702FDC74DAA08B265E0143D5B /* Sources */,
+				1DC97817961139E7E35F2AE2C60B6E3C /* Frameworks */,
+				A35DC61B22F4A23E51FB4B97627ABCE9 /* Resources */,
 			);
 			buildRules = (
 			);
 			dependencies = (
-				E1503B327771444C7450809115CCB16D /* PBXTargetDependency */,
-				2804205A31A90804A05C34053D7862EA /* PBXTargetDependency */,
-				543EEF6428571D0759E399E754D798AF /* PBXTargetDependency */,
+				AA3C18940D595C8ABAA899EE95469C91 /* PBXTargetDependency */,
+				8B0A5134AC4ECFA08B720B20790FFA19 /* PBXTargetDependency */,
+				94AF0CD3D1D2B1992DFCD0273D0F2387 /* PBXTargetDependency */,
 			);
 			name = "Pods-LoopFollow";
 			productName = "Pods-LoopFollow";
@@ -1053,21 +1053,21 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
-		C0AFB00D995D51D37A221C832E39671F /* Resources */ = {
+		A35DC61B22F4A23E51FB4B97627ABCE9 /* Resources */ = {
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
-		C6AFE590176E3A846DAE473492B8ADE1 /* Resources */ = {
+		C0AFB00D995D51D37A221C832E39671F /* Resources */ = {
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
-		C8C3B267820FCCB290B983FC6A267748 /* Resources */ = {
+		C6AFE590176E3A846DAE473492B8ADE1 /* Resources */ = {
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
@@ -1151,11 +1151,11 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
-		3EC2C540F4F22A21FD47A7EF7C6E6262 /* Sources */ = {
+		5BEF58C702FDC74DAA08B265E0143D5B /* Sources */ = {
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				BE11B033F02B91FA038526546C8E57B6 /* Pods-LoopFollow-dummy.m in Sources */,
+				F1B37767979921C4758B54144610815D /* Pods-LoopFollow-dummy.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1319,23 +1319,23 @@
 /* End PBXSourcesBuildPhase section */
 
 /* Begin PBXTargetDependency section */
-		2804205A31A90804A05C34053D7862EA /* PBXTargetDependency */ = {
+		8B0A5134AC4ECFA08B720B20790FFA19 /* PBXTargetDependency */ = {
 			isa = PBXTargetDependency;
 			name = Eureka;
 			target = 88BE8BE7A2FE6CED4193CCD8FF3CBEF7 /* Eureka */;
-			targetProxy = 80929478E859790595DCC4B6934EEA68 /* PBXContainerItemProxy */;
+			targetProxy = 87D6DD57B95016F1515B8CA873C52B51 /* PBXContainerItemProxy */;
 		};
-		543EEF6428571D0759E399E754D798AF /* PBXTargetDependency */ = {
+		94AF0CD3D1D2B1992DFCD0273D0F2387 /* PBXTargetDependency */ = {
 			isa = PBXTargetDependency;
 			name = ShareClient;
 			target = 32507FDB9BAD6EF17DCB14A888ECA5D9 /* ShareClient */;
-			targetProxy = 70AC7791B3B1114DB847777CFED5BBDA /* PBXContainerItemProxy */;
+			targetProxy = 768CF338A2BC484E9597CD398DFA4136 /* PBXContainerItemProxy */;
 		};
-		E1503B327771444C7450809115CCB16D /* PBXTargetDependency */ = {
+		AA3C18940D595C8ABAA899EE95469C91 /* PBXTargetDependency */ = {
 			isa = PBXTargetDependency;
 			name = Charts;
 			target = BF03B0B2B8B052092CB5F90CC5FB3757 /* Charts */;
-			targetProxy = B4EAD249F74DCCC9C759EEEBC10E9BE0 /* PBXContainerItemProxy */;
+			targetProxy = 7272ABD8A5C4DE0FAA10777D4FE249DD /* PBXContainerItemProxy */;
 		};
 /* End PBXTargetDependency section */
 
@@ -1376,44 +1376,6 @@
 			};
 			name = Release;
 		};
-		08DC96BE55468FDC92D2E0A16F0D4FD1 /* Debug */ = {
-			isa = XCBuildConfiguration;
-			baseConfigurationReference = B1DF43F4B1524F385F6090756FA3016A /* Pods-LoopFollow.debug.xcconfig */;
-			buildSettings = {
-				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
-				CLANG_ENABLE_OBJC_WEAK = NO;
-				CODE_SIGN_IDENTITY = "";
-				"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
-				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
-				"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
-				CURRENT_PROJECT_VERSION = 1;
-				DEFINES_MODULE = YES;
-				DYLIB_COMPATIBILITY_VERSION = 1;
-				DYLIB_CURRENT_VERSION = 1;
-				DYLIB_INSTALL_NAME_BASE = "@rpath";
-				INFOPLIST_FILE = "Target Support Files/Pods-LoopFollow/Pods-LoopFollow-Info.plist";
-				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
-				IPHONEOS_DEPLOYMENT_TARGET = 13.4;
-				LD_RUNPATH_SEARCH_PATHS = (
-					"$(inherited)",
-					"@executable_path/Frameworks",
-					"@loader_path/Frameworks",
-				);
-				MACH_O_TYPE = staticlib;
-				MODULEMAP_FILE = "Target Support Files/Pods-LoopFollow/Pods-LoopFollow.modulemap";
-				OTHER_LDFLAGS = "";
-				OTHER_LIBTOOLFLAGS = "";
-				PODS_ROOT = "$(SRCROOT)";
-				PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}";
-				PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
-				SDKROOT = iphoneos;
-				SKIP_INSTALL = YES;
-				TARGETED_DEVICE_FAMILY = "1,2";
-				VERSIONING_SYSTEM = "apple-generic";
-				VERSION_INFO_PREFIX = "";
-			};
-			name = Debug;
-		};
 		13DF91FECD4EB2AAAF95405099441E29 /* Debug */ = {
 			isa = XCBuildConfiguration;
 			baseConfigurationReference = 017205CE9DD9404E68FC3A813A5BC6A4 /* Charts.debug.xcconfig */;
@@ -1682,6 +1644,45 @@
 			};
 			name = Release;
 		};
+		A57D7F46620CEA8E6010E60F37AA1C40 /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = EBE2A8C176CC9C76279BE195BDA90B7F /* Pods-LoopFollow.release.xcconfig */;
+			buildSettings = {
+				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
+				CLANG_ENABLE_OBJC_WEAK = NO;
+				CODE_SIGN_IDENTITY = "";
+				"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
+				"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
+				CURRENT_PROJECT_VERSION = 1;
+				DEFINES_MODULE = YES;
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				INFOPLIST_FILE = "Target Support Files/Pods-LoopFollow/Pods-LoopFollow-Info.plist";
+				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+				IPHONEOS_DEPLOYMENT_TARGET = 13.4;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+					"@loader_path/Frameworks",
+				);
+				MACH_O_TYPE = staticlib;
+				MODULEMAP_FILE = "Target Support Files/Pods-LoopFollow/Pods-LoopFollow.modulemap";
+				OTHER_LDFLAGS = "";
+				OTHER_LIBTOOLFLAGS = "";
+				PODS_ROOT = "$(SRCROOT)";
+				PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}";
+				PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+				SDKROOT = iphoneos;
+				SKIP_INSTALL = YES;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VALIDATE_PRODUCT = YES;
+				VERSIONING_SYSTEM = "apple-generic";
+				VERSION_INFO_PREFIX = "";
+			};
+			name = Release;
+		};
 		D27C976E5A2F75B799103220B6FECE18 /* Debug */ = {
 			isa = XCBuildConfiguration;
 			baseConfigurationReference = B28A2F7F1E6F57132EDC348E349719CA /* Eureka.debug.xcconfig */;
@@ -1717,9 +1718,9 @@
 			};
 			name = Debug;
 		};
-		D3990B88691574EDD3463D46E2AC8C83 /* Release */ = {
+		E56FA54D604FEC313768CAB1F44761B8 /* Debug */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = EBE2A8C176CC9C76279BE195BDA90B7F /* Pods-LoopFollow.release.xcconfig */;
+			baseConfigurationReference = B1DF43F4B1524F385F6090756FA3016A /* Pods-LoopFollow.debug.xcconfig */;
 			buildSettings = {
 				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
 				CLANG_ENABLE_OBJC_WEAK = NO;
@@ -1750,24 +1751,14 @@
 				SDKROOT = iphoneos;
 				SKIP_INSTALL = YES;
 				TARGETED_DEVICE_FAMILY = "1,2";
-				VALIDATE_PRODUCT = YES;
 				VERSIONING_SYSTEM = "apple-generic";
 				VERSION_INFO_PREFIX = "";
 			};
-			name = Release;
+			name = Debug;
 		};
 /* End XCBuildConfiguration section */
 
 /* Begin XCConfigurationList section */
-		33267BE99E13D5D253525BA1D49CA2DA /* Build configuration list for PBXNativeTarget "Pods-LoopFollow" */ = {
-			isa = XCConfigurationList;
-			buildConfigurations = (
-				08DC96BE55468FDC92D2E0A16F0D4FD1 /* Debug */,
-				D3990B88691574EDD3463D46E2AC8C83 /* Release */,
-			);
-			defaultConfigurationIsVisible = 0;
-			defaultConfigurationName = Release;
-		};
 		36778B42AB96755AAFA8AF892C40E50E /* Build configuration list for PBXNativeTarget "Eureka" */ = {
 			isa = XCConfigurationList;
 			buildConfigurations = (
@@ -1795,6 +1786,15 @@
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
 		};
+		C296AD097A8FA560D79E90D3C22C96E9 /* Build configuration list for PBXNativeTarget "Pods-LoopFollow" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				E56FA54D604FEC313768CAB1F44761B8 /* Debug */,
+				A57D7F46620CEA8E6010E60F37AA1C40 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
 		EACB1516A3F84CD99E9B870444AB37F1 /* Build configuration list for PBXNativeTarget "Charts" */ = {
 			isa = XCConfigurationList;
 			buildConfigurations = (

+ 60 - 0
Pods/Pods.xcodeproj/xcuserdata/jon.xcuserdatad/xcschemes/ShareClient.xcscheme

@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1100"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForAnalyzing = "YES"
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "32507FDB9BAD6EF17DCB14A888ECA5D9"
+               BuildableName = "ShareClient.framework"
+               BlueprintName = "ShareClient"
+               ReferencedContainer = "container:Pods.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      buildConfiguration = "Debug">
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </TestAction>
+   <LaunchAction
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      buildConfiguration = "Debug"
+      allowLocationSimulation = "YES">
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES"
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 7 - 0
Pods/Pods.xcodeproj/xcuserdata/jon.xcuserdatad/xcschemes/xcschememanagement.plist

@@ -25,6 +25,13 @@
 			<key>orderHint</key>
 			<integer>2</integer>
 		</dict>
+		<key>ShareClient.xcscheme</key>
+		<dict>
+			<key>isShown</key>
+			<false/>
+			<key>orderHint</key>
+			<integer>3</integer>
+		</dict>
 	</dict>
 	<key>SuppressBuildableAutocreation</key>
 	<dict/>

+ 21 - 0
Pods/ShareClient/LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Mark Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 19 - 0
Pods/ShareClient/ShareClient/ShareClient.h

@@ -0,0 +1,19 @@
+//
+//  ShareClient.h
+//  ShareClient
+//
+//  Created by Nathan Racklyeft on 5/8/16.
+//  Copyright © 2016 Mark Wilson. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+//! Project version number for ShareClient.
+FOUNDATION_EXPORT double ShareClientVersionNumber;
+
+//! Project version string for ShareClient.
+FOUNDATION_EXPORT const unsigned char ShareClientVersionString[];
+
+// In this header, you should import all the public headers of your framework using statements like #import <ShareClient/PublicHeader.h>
+
+

+ 222 - 0
Pods/ShareClient/ShareClient/ShareClient.swift

@@ -0,0 +1,222 @@
+//
+//  ShareClient.h
+//  ShareClient
+//
+//  Created by Mark Wilson on 5/7/16.
+//  Copyright © 2016 Mark Wilson. All rights reserved.
+//
+
+import Foundation
+
+public struct ShareGlucose {
+    public let glucose: UInt16
+    public let trend: UInt8
+    public let timestamp: Date
+}
+
+public enum ShareError: Error {
+    case httpError(Error)
+    // some possible values of errorCode:
+    // SSO_AuthenticateAccountNotFound
+    // SSO_AuthenticatePasswordInvalid
+    // SSO_AuthenticateMaxAttemptsExceeed
+    case loginError(errorCode: String)
+    case fetchError
+    case dataError(reason: String)
+    case dateError
+}
+
+
+public enum KnownShareServers: String {
+    case US="https://share1.dexcom.com"
+    case NON_US="https://shareous1.dexcom.com"
+
+}
+
+// From the Dexcom Share iOS app, via @bewest and @shanselman:
+// https://github.com/bewest/share2nightscout-bridge
+private let dexcomUserAgent = "Dexcom Share/3.0.2.11 CFNetwork/711.2.23 Darwin/14.0.0"
+private let dexcomApplicationId = "d89443d2-327c-4a6f-89e5-496bbb0317db"
+private let dexcomLoginPath = "/ShareWebServices/Services/General/LoginPublisherAccountByName"
+private let dexcomLatestGlucosePath = "/ShareWebServices/Services/Publisher/ReadPublisherLatestGlucoseValues"
+private let maxReauthAttempts = 2
+
+// TODO use an HTTP library which supports JSON and futures instead of callbacks.
+// using cocoapods in a playground appears complicated
+// ¯\_(ツ)_/¯
+private func dexcomPOST(_ url: URL, JSONData: [String: AnyObject]? = nil, callback: @escaping (Error?, String?) -> Void) {
+    var data: Data?
+
+    if let JSONData = JSONData {
+        guard let encoded = try? JSONSerialization.data(withJSONObject: JSONData, options:[]) else {
+            return callback(ShareError.dataError(reason: "Failed to encode JSON for POST to " + url.absoluteString), nil)
+        }
+
+        data = encoded
+    }
+
+    var request = URLRequest(url: url)
+    request.httpMethod = "POST"
+    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
+    request.addValue("application/json", forHTTPHeaderField: "Accept")
+    request.addValue(dexcomUserAgent, forHTTPHeaderField: "User-Agent")
+    request.httpBody = data
+
+    URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
+        if error != nil {
+            callback(error, nil)
+        } else {
+            callback(nil, String(data: data!, encoding: .utf8))
+        }
+    }).resume()
+}
+
+public class ShareClient {
+    public let username: String
+    public let password: String
+
+    private let shareServer:String
+    private var token: String?
+
+    public init(username: String, password: String, shareServer:String=KnownShareServers.US.rawValue) {
+        self.username = username
+        self.password = password
+        self.shareServer = shareServer
+    }
+    public convenience init(username: String, password: String, shareServer:KnownShareServers=KnownShareServers.US) {
+
+        self.init(username: username, password: password, shareServer:shareServer.rawValue)
+
+    }
+
+    public func fetchLast(_ n: Int, callback: @escaping (ShareError?, [ShareGlucose]?) -> Void) {
+        fetchLastWithRetries(n, remaining: maxReauthAttempts, callback: callback)
+    }
+
+    private func ensureToken(_ callback: @escaping (ShareError?) -> Void) {
+        if token != nil {
+            callback(nil)
+        } else {
+            fetchToken() { (error, token) in
+                if error != nil {
+                    callback(error)
+                } else {
+                    self.token = token
+                    callback(nil)
+                }
+            }
+        }
+    }
+
+    private func fetchToken(_ callback: @escaping (ShareError?, String?) -> Void) {
+        let data = [
+            "accountName": username,
+            "password": password,
+            "applicationId": dexcomApplicationId
+        ]
+
+        guard let url = URL(string: shareServer + dexcomLoginPath) else {
+            return callback(ShareError.fetchError, nil)
+        }
+
+        dexcomPOST(url, JSONData: data as [String : AnyObject]?) { (error, response) in
+            if let error = error {
+                return callback(.httpError(error), nil)
+            }
+
+            guard let   response = response,
+                let data = response.data(using: .utf8),
+                let decoded = try? JSONSerialization.jsonObject(with: data, options: .allowFragments)
+                else {
+                    return callback(.loginError(errorCode: "unknown"), nil)
+            }
+
+            if let token = decoded as? String {
+                // success is a JSON-encoded string containing the token
+                callback(nil, token)
+            } else {
+                // failure is a JSON object containing the error reason
+                let errorCode = (decoded as? [String: String])?["Code"] ?? "unknown"
+                callback(.loginError(errorCode: errorCode), nil)
+            }
+        }
+    }
+
+    private func fetchLastWithRetries(_ n: Int, remaining: Int, callback: @escaping (ShareError?, [ShareGlucose]?) -> Void) {
+        ensureToken() { (error) in
+            guard error == nil else {
+                return callback(error, nil)
+            }
+
+            guard var components = URLComponents(string: self.shareServer + dexcomLatestGlucosePath) else {
+                return callback(.fetchError, nil)
+            }
+
+            components.queryItems = [
+                URLQueryItem(name: "sessionId", value: self.token),
+                URLQueryItem(name: "minutes", value: String(1440)),
+                URLQueryItem(name: "maxCount", value: String(n))
+            ]
+
+            guard let url = components.url else {
+                return callback(.fetchError, nil)
+            }
+
+            dexcomPOST(url) { (error, response) in
+                if let error = error {
+                    return callback(.httpError(error), nil)
+                }
+
+                do {
+                    guard let response = response else {
+                        throw ShareError.fetchError
+                    }
+
+                    let decoded = try? JSONSerialization.jsonObject(with: response.data(using: .utf8)!, options: [])
+                    guard let sgvs = decoded as? Array<AnyObject> else {
+                        if remaining > 0 {
+                            self.token = nil
+                            return self.fetchLastWithRetries(n, remaining: remaining - 1, callback: callback)
+                        } else {
+                            throw ShareError.dataError(reason: "Failed to decode SGVs as array after trying to reauth: " + response)
+                        }
+                    }
+
+                    var transformed: Array<ShareGlucose> = []
+                    for sgv in sgvs {
+                        if let glucose = sgv["Value"] as? Int, let trend = sgv["Trend"] as? Int, let wt = sgv["WT"] as? String {
+                            transformed.append(ShareGlucose(
+                                glucose: UInt16(glucose),
+                                trend: UInt8(trend),
+                                timestamp: try self.parseDate(wt)
+                            ))
+                        } else {
+                            throw ShareError.dataError(reason: "Failed to decode an SGV record: " + response)
+                        }
+                    }
+                    callback(nil, transformed)
+                } catch let error as ShareError {
+                    callback(error, nil)
+                } catch {
+                    callback(.fetchError, nil)
+                }
+            }
+        }
+    }
+
+    private func parseDate(_ wt: String) throws -> Date {
+        // wt looks like "/Date(1462404576000)/"
+        let re = try NSRegularExpression(pattern: "\\((.*)\\)")
+        if let match = re.firstMatch(in: wt, range: NSMakeRange(0, wt.count)) {
+            #if swift(>=4)
+                let matchRange = match.range(at: 1)
+            #else
+                let matchRange = match.rangeAt(1)
+            #endif
+            let epoch = Double((wt as NSString).substring(with: matchRange))! / 1000
+            return Date(timeIntervalSince1970: epoch)
+        } else {
+            throw ShareError.dateError
+        }
+    }
+}

+ 26 - 0
Pods/Target Support Files/ShareClient/ShareClient-Info.plist

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>CFBundleDevelopmentRegion</key>
+  <string>en</string>
+  <key>CFBundleExecutable</key>
+  <string>${EXECUTABLE_NAME}</string>
+  <key>CFBundleIdentifier</key>
+  <string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
+  <key>CFBundleInfoDictionaryVersion</key>
+  <string>6.0</string>
+  <key>CFBundleName</key>
+  <string>${PRODUCT_NAME}</string>
+  <key>CFBundlePackageType</key>
+  <string>FMWK</string>
+  <key>CFBundleShortVersionString</key>
+  <string>1.2.0</string>
+  <key>CFBundleSignature</key>
+  <string>????</string>
+  <key>CFBundleVersion</key>
+  <string>${CURRENT_PROJECT_VERSION}</string>
+  <key>NSPrincipalClass</key>
+  <string></string>
+</dict>
+</plist>

+ 5 - 0
Pods/Target Support Files/ShareClient/ShareClient-dummy.m

@@ -0,0 +1,5 @@
+#import <Foundation/Foundation.h>
+@interface PodsDummy_ShareClient : NSObject
+@end
+@implementation PodsDummy_ShareClient
+@end

+ 12 - 0
Pods/Target Support Files/ShareClient/ShareClient-prefix.pch

@@ -0,0 +1,12 @@
+#ifdef __OBJC__
+#import <UIKit/UIKit.h>
+#else
+#ifndef FOUNDATION_EXPORT
+#if defined(__cplusplus)
+#define FOUNDATION_EXPORT extern "C"
+#else
+#define FOUNDATION_EXPORT extern
+#endif
+#endif
+#endif
+

+ 17 - 0
Pods/Target Support Files/ShareClient/ShareClient-umbrella.h

@@ -0,0 +1,17 @@
+#ifdef __OBJC__
+#import <UIKit/UIKit.h>
+#else
+#ifndef FOUNDATION_EXPORT
+#if defined(__cplusplus)
+#define FOUNDATION_EXPORT extern "C"
+#else
+#define FOUNDATION_EXPORT extern
+#endif
+#endif
+#endif
+
+#import "ShareClient.h"
+
+FOUNDATION_EXPORT double ShareClientVersionNumber;
+FOUNDATION_EXPORT const unsigned char ShareClientVersionString[];
+

+ 10 - 0
Pods/Target Support Files/ShareClient/ShareClient.debug.xcconfig

@@ -0,0 +1,10 @@
+CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/ShareClient
+GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
+OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
+PODS_BUILD_DIR = ${BUILD_DIR}
+PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
+PODS_ROOT = ${SRCROOT}
+PODS_TARGET_SRCROOT = ${PODS_ROOT}/ShareClient
+PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
+SKIP_INSTALL = YES
+USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES

+ 6 - 0
Pods/Target Support Files/ShareClient/ShareClient.modulemap

@@ -0,0 +1,6 @@
+framework module ShareClient {
+  umbrella header "ShareClient-umbrella.h"
+
+  export *
+  module * { export * }
+}

+ 10 - 0
Pods/Target Support Files/ShareClient/ShareClient.release.xcconfig

@@ -0,0 +1,10 @@
+CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/ShareClient
+GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
+OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
+PODS_BUILD_DIR = ${BUILD_DIR}
+PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
+PODS_ROOT = ${SRCROOT}
+PODS_TARGET_SRCROOT = ${PODS_ROOT}/ShareClient
+PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
+SKIP_INSTALL = YES
+USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES