Jon Fawcett 6 лет назад
Родитель
Сommit
1d27fe8206
42 измененных файлов с 1535 добавлено и 111 удалено
  1. 164 22
      LoopFollow.xcodeproj/project.pbxproj
  2. 3 0
      LoopFollow.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  3. 1 1
      LoopFollow.xcodeproj/xcuserdata/jon.xcuserdatad/xcschemes/xcschememanagement.plist
  4. 52 0
      LoopFollow/AlarmViewController.swift
  5. BIN
      LoopFollow/Assets.xcassets/AppIcon.appiconset/1024.png
  6. BIN
      LoopFollow/Assets.xcassets/AppIcon.appiconset/120-1.png
  7. BIN
      LoopFollow/Assets.xcassets/AppIcon.appiconset/120.png
  8. BIN
      LoopFollow/Assets.xcassets/AppIcon.appiconset/152.png
  9. BIN
      LoopFollow/Assets.xcassets/AppIcon.appiconset/167.png
  10. BIN
      LoopFollow/Assets.xcassets/AppIcon.appiconset/180.png
  11. BIN
      LoopFollow/Assets.xcassets/AppIcon.appiconset/20.png
  12. BIN
      LoopFollow/Assets.xcassets/AppIcon.appiconset/29.png
  13. BIN
      LoopFollow/Assets.xcassets/AppIcon.appiconset/40-1.png
  14. BIN
      LoopFollow/Assets.xcassets/AppIcon.appiconset/40-2.png
  15. BIN
      LoopFollow/Assets.xcassets/AppIcon.appiconset/40.png
  16. BIN
      LoopFollow/Assets.xcassets/AppIcon.appiconset/58-1.png
  17. BIN
      LoopFollow/Assets.xcassets/AppIcon.appiconset/58.png
  18. BIN
      LoopFollow/Assets.xcassets/AppIcon.appiconset/60.png
  19. BIN
      LoopFollow/Assets.xcassets/AppIcon.appiconset/76.png
  20. BIN
      LoopFollow/Assets.xcassets/AppIcon.appiconset/80-1.png
  21. BIN
      LoopFollow/Assets.xcassets/AppIcon.appiconset/80.png
  22. BIN
      LoopFollow/Assets.xcassets/AppIcon.appiconset/87.png
  23. 18 0
      LoopFollow/Assets.xcassets/AppIcon.appiconset/Contents.json
  24. 161 48
      LoopFollow/Base.lproj/Main.storyboard
  25. 0 20
      LoopFollow/FirstViewController.swift
  26. 5 0
      LoopFollow/Info.plist
  27. 10 0
      LoopFollow/Loop Follow.entitlements
  28. 552 0
      LoopFollow/MainViewController.swift
  29. 33 0
      LoopFollow/NightScoutViewController.swift
  30. 0 20
      LoopFollow/SecondViewController.swift
  31. 55 0
      LoopFollow/SettingsViewController.swift
  32. 136 0
      LoopFollow/helpers/AnyConvertible.swift
  33. 14 0
      LoopFollow/helpers/AppConstants.swift
  34. 52 0
      LoopFollow/helpers/BackgroundTask.swift
  35. 26 0
      LoopFollow/helpers/Chart.swift
  36. 23 0
      LoopFollow/helpers/ObservationToken.swift
  37. 126 0
      LoopFollow/helpers/UserDefaultsValue.swift
  38. 75 0
      LoopFollow/helpers/UserDefaultsValueGroups.swift
  39. BIN
      LoopFollow/helpers/blank.wav
  40. BIN
      LoopFollow/helpers/silence.mp3
  41. 9 0
      LoopFollow/repository/UserData.xcdatamodeld/UserData.xcdatamodel/contents
  42. 20 0
      LoopFollow/repository/UserDefaults.swift

+ 164 - 22
LoopFollow.xcodeproj/project.pbxproj

@@ -3,31 +3,62 @@
 	archiveVersion = 1;
 	archiveVersion = 1;
 	classes = {
 	classes = {
 	};
 	};
-	objectVersion = 50;
+	objectVersion = 51;
 	objects = {
 	objects = {
 
 
 /* Begin PBXBuildFile section */
 /* Begin PBXBuildFile section */
+		3F1335F351590E573D8E6962 /* Pods_LoopFollow.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D55B42A22051DAD69E89D0 /* Pods_LoopFollow.framework */; };
 		FC9788182485969B00A7906C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9788172485969B00A7906C /* AppDelegate.swift */; };
 		FC9788182485969B00A7906C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9788172485969B00A7906C /* AppDelegate.swift */; };
 		FC97881A2485969B00A7906C /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9788192485969B00A7906C /* SceneDelegate.swift */; };
 		FC97881A2485969B00A7906C /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9788192485969B00A7906C /* SceneDelegate.swift */; };
-		FC97881C2485969B00A7906C /* FirstViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC97881B2485969B00A7906C /* FirstViewController.swift */; };
-		FC97881E2485969B00A7906C /* SecondViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC97881D2485969B00A7906C /* SecondViewController.swift */; };
+		FC97881C2485969B00A7906C /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC97881B2485969B00A7906C /* MainViewController.swift */; };
+		FC97881E2485969B00A7906C /* NightScoutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC97881D2485969B00A7906C /* NightScoutViewController.swift */; };
 		FC9788212485969B00A7906C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FC97881F2485969B00A7906C /* Main.storyboard */; };
 		FC9788212485969B00A7906C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FC97881F2485969B00A7906C /* Main.storyboard */; };
 		FC9788242485969B00A7906C /* LoopFollow.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = FC9788222485969B00A7906C /* LoopFollow.xcdatamodeld */; };
 		FC9788242485969B00A7906C /* LoopFollow.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = FC9788222485969B00A7906C /* LoopFollow.xcdatamodeld */; };
 		FC9788262485969C00A7906C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FC9788252485969C00A7906C /* Assets.xcassets */; };
 		FC9788262485969C00A7906C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FC9788252485969C00A7906C /* Assets.xcassets */; };
 		FC9788292485969C00A7906C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FC9788272485969C00A7906C /* LaunchScreen.storyboard */; };
 		FC9788292485969C00A7906C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FC9788272485969C00A7906C /* LaunchScreen.storyboard */; };
+		FCC68850248935D800A0279D /* AlarmViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC6884F248935D800A0279D /* AlarmViewController.swift */; };
+		FCC6885A2489554800A0279D /* BackgroundTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC688592489554800A0279D /* BackgroundTask.swift */; };
+		FCC6885C2489559400A0279D /* blank.wav in Resources */ = {isa = PBXBuildFile; fileRef = FCC6885B2489559400A0279D /* blank.wav */; };
+		FCC6885E24896A6C00A0279D /* silence.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = FCC6885D24896A6C00A0279D /* silence.mp3 */; };
+		FCC6886524898EEE00A0279D /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC6886424898EEE00A0279D /* UserDefaults.swift */; };
+		FCC6886724898F8000A0279D /* UserDefaultsValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC6886624898F8000A0279D /* UserDefaultsValue.swift */; };
+		FCC6886924898FB100A0279D /* UserDefaultsValueGroups.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC6886824898FB100A0279D /* UserDefaultsValueGroups.swift */; };
+		FCC6886B24898FD800A0279D /* ObservationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC6886A24898FD800A0279D /* ObservationToken.swift */; };
+		FCC6886D2489909D00A0279D /* AnyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC6886C2489909D00A0279D /* AnyConvertible.swift */; };
+		FCC6886F2489A53800A0279D /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC6886E2489A53800A0279D /* AppConstants.swift */; };
+		FCFEEC9E2486E68E00402A7F /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCFEEC9D2486E68E00402A7F /* WebKit.framework */; };
+		FCFEECA02488157B00402A7F /* Chart.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCFEEC9F2488157B00402A7F /* Chart.swift */; };
+		FCFEECA2248857A600402A7F /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCFEECA1248857A600402A7F /* SettingsViewController.swift */; };
 /* End PBXBuildFile section */
 /* End PBXBuildFile section */
 
 
 /* Begin PBXFileReference section */
 /* Begin PBXFileReference section */
-		FC9788142485969B00A7906C /* LoopFollow.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LoopFollow.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		059B0FA59AABFE72FE13DDDA /* Pods-LoopFollow.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LoopFollow.release.xcconfig"; path = "Target Support Files/Pods-LoopFollow/Pods-LoopFollow.release.xcconfig"; sourceTree = "<group>"; };
+		A7D55B42A22051DAD69E89D0 /* Pods_LoopFollow.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LoopFollow.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		ECA3EFB4037410B4973BB632 /* Pods-LoopFollow.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LoopFollow.debug.xcconfig"; path = "Target Support Files/Pods-LoopFollow/Pods-LoopFollow.debug.xcconfig"; sourceTree = "<group>"; };
+		FC8DEEE62485D1ED0075863F /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		FC9788142485969B00A7906C /* Loop Follow.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Loop Follow.app"; sourceTree = BUILT_PRODUCTS_DIR; };
 		FC9788172485969B00A7906C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 		FC9788172485969B00A7906C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 		FC9788192485969B00A7906C /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
 		FC9788192485969B00A7906C /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
-		FC97881B2485969B00A7906C /* FirstViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstViewController.swift; sourceTree = "<group>"; };
-		FC97881D2485969B00A7906C /* SecondViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondViewController.swift; sourceTree = "<group>"; };
+		FC97881B2485969B00A7906C /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
+		FC97881D2485969B00A7906C /* NightScoutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightScoutViewController.swift; sourceTree = "<group>"; };
 		FC9788202485969B00A7906C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
 		FC9788202485969B00A7906C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
 		FC9788232485969B00A7906C /* LoopFollow.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LoopFollow.xcdatamodel; sourceTree = "<group>"; };
 		FC9788232485969B00A7906C /* LoopFollow.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LoopFollow.xcdatamodel; sourceTree = "<group>"; };
 		FC9788252485969C00A7906C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		FC9788252485969C00A7906C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		FC9788282485969C00A7906C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
 		FC9788282485969C00A7906C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
-		FC97882A2485969C00A7906C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		FCC6884F248935D800A0279D /* AlarmViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmViewController.swift; sourceTree = "<group>"; };
+		FCC688592489554800A0279D /* BackgroundTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackgroundTask.swift; sourceTree = "<group>"; };
+		FCC6885B2489559400A0279D /* blank.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = blank.wav; sourceTree = "<group>"; };
+		FCC6885D24896A6C00A0279D /* silence.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = silence.mp3; sourceTree = "<group>"; };
+		FCC6886424898EEE00A0279D /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = "<group>"; };
+		FCC6886624898F8000A0279D /* UserDefaultsValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsValue.swift; sourceTree = "<group>"; };
+		FCC6886824898FB100A0279D /* UserDefaultsValueGroups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsValueGroups.swift; sourceTree = "<group>"; };
+		FCC6886A24898FD800A0279D /* ObservationToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservationToken.swift; sourceTree = "<group>"; };
+		FCC6886C2489909D00A0279D /* AnyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyConvertible.swift; sourceTree = "<group>"; };
+		FCC6886E2489A53800A0279D /* AppConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = "<group>"; };
+		FCC688702489A57C00A0279D /* Loop Follow.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Loop Follow.entitlements"; sourceTree = "<group>"; };
+		FCFEEC9D2486E68E00402A7F /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };
+		FCFEEC9F2488157B00402A7F /* Chart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Chart.swift; sourceTree = "<group>"; };
+		FCFEECA1248857A600402A7F /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 /* End PBXFileReference section */
 
 
 /* Begin PBXFrameworksBuildPhase section */
 /* Begin PBXFrameworksBuildPhase section */
@@ -35,44 +66,95 @@
 			isa = PBXFrameworksBuildPhase;
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			buildActionMask = 2147483647;
 			files = (
 			files = (
+				FCFEEC9E2486E68E00402A7F /* WebKit.framework in Frameworks */,
+				3F1335F351590E573D8E6962 /* Pods_LoopFollow.framework in Frameworks */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 		};
 /* End PBXFrameworksBuildPhase section */
 /* End PBXFrameworksBuildPhase section */
 
 
 /* Begin PBXGroup section */
 /* Begin PBXGroup section */
-		FC97880B2485969B00A7906C = {
+		6A5880E0B811AF443B05AB02 /* Frameworks */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
-				FC9788162485969B00A7906C /* LoopFollow */,
-				FC9788152485969B00A7906C /* Products */,
+				FCFEEC9D2486E68E00402A7F /* WebKit.framework */,
+				A7D55B42A22051DAD69E89D0 /* Pods_LoopFollow.framework */,
 			);
 			);
+			name = Frameworks;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
-		FC9788152485969B00A7906C /* Products */ = {
+		8E32230C453C93FDCE59C2B9 /* Pods */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
-				FC9788142485969B00A7906C /* LoopFollow.app */,
+				ECA3EFB4037410B4973BB632 /* Pods-LoopFollow.debug.xcconfig */,
+				059B0FA59AABFE72FE13DDDA /* Pods-LoopFollow.release.xcconfig */,
 			);
 			);
-			name = Products;
+			path = Pods;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
-		FC9788162485969B00A7906C /* LoopFollow */ = {
+		FC8DEEE32485D1680075863F /* LoopFollow */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				FCC688702489A57C00A0279D /* Loop Follow.entitlements */,
+				FCC688542489367300A0279D /* helpers */,
+				FCC688512489363F00A0279D /* repository */,
+				FC8DEEE62485D1ED0075863F /* Info.plist */,
 				FC9788172485969B00A7906C /* AppDelegate.swift */,
 				FC9788172485969B00A7906C /* AppDelegate.swift */,
 				FC9788192485969B00A7906C /* SceneDelegate.swift */,
 				FC9788192485969B00A7906C /* SceneDelegate.swift */,
-				FC97881B2485969B00A7906C /* FirstViewController.swift */,
-				FC97881D2485969B00A7906C /* SecondViewController.swift */,
+				FC97881B2485969B00A7906C /* MainViewController.swift */,
+				FC97881D2485969B00A7906C /* NightScoutViewController.swift */,
 				FC97881F2485969B00A7906C /* Main.storyboard */,
 				FC97881F2485969B00A7906C /* Main.storyboard */,
 				FC9788252485969C00A7906C /* Assets.xcassets */,
 				FC9788252485969C00A7906C /* Assets.xcassets */,
 				FC9788272485969C00A7906C /* LaunchScreen.storyboard */,
 				FC9788272485969C00A7906C /* LaunchScreen.storyboard */,
-				FC97882A2485969C00A7906C /* Info.plist */,
 				FC9788222485969B00A7906C /* LoopFollow.xcdatamodeld */,
 				FC9788222485969B00A7906C /* LoopFollow.xcdatamodeld */,
+				FCFEECA1248857A600402A7F /* SettingsViewController.swift */,
+				FCC6884F248935D800A0279D /* AlarmViewController.swift */,
 			);
 			);
 			path = LoopFollow;
 			path = LoopFollow;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
+		FC97880B2485969B00A7906C = {
+			isa = PBXGroup;
+			children = (
+				FC8DEEE32485D1680075863F /* LoopFollow */,
+				FC9788152485969B00A7906C /* Products */,
+				8E32230C453C93FDCE59C2B9 /* Pods */,
+				6A5880E0B811AF443B05AB02 /* Frameworks */,
+			);
+			sourceTree = "<group>";
+		};
+		FC9788152485969B00A7906C /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				FC9788142485969B00A7906C /* Loop Follow.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		FCC688512489363F00A0279D /* repository */ = {
+			isa = PBXGroup;
+			children = (
+				FCC6886424898EEE00A0279D /* UserDefaults.swift */,
+			);
+			path = repository;
+			sourceTree = "<group>";
+		};
+		FCC688542489367300A0279D /* helpers */ = {
+			isa = PBXGroup;
+			children = (
+				FCC6886E2489A53800A0279D /* AppConstants.swift */,
+				FCC6886624898F8000A0279D /* UserDefaultsValue.swift */,
+				FCC6886824898FB100A0279D /* UserDefaultsValueGroups.swift */,
+				FCC6886A24898FD800A0279D /* ObservationToken.swift */,
+				FCC6886C2489909D00A0279D /* AnyConvertible.swift */,
+				FCC6885D24896A6C00A0279D /* silence.mp3 */,
+				FCC6885B2489559400A0279D /* blank.wav */,
+				FCC688592489554800A0279D /* BackgroundTask.swift */,
+				FCFEEC9F2488157B00402A7F /* Chart.swift */,
+			);
+			path = helpers;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 /* End PBXGroup section */
 
 
 /* Begin PBXNativeTarget section */
 /* Begin PBXNativeTarget section */
@@ -80,9 +162,11 @@
 			isa = PBXNativeTarget;
 			isa = PBXNativeTarget;
 			buildConfigurationList = FC97882D2485969C00A7906C /* Build configuration list for PBXNativeTarget "LoopFollow" */;
 			buildConfigurationList = FC97882D2485969C00A7906C /* Build configuration list for PBXNativeTarget "LoopFollow" */;
 			buildPhases = (
 			buildPhases = (
+				B038D39450A1F9A97D2B8BA4 /* [CP] Check Pods Manifest.lock */,
 				FC9788102485969B00A7906C /* Sources */,
 				FC9788102485969B00A7906C /* Sources */,
 				FC9788112485969B00A7906C /* Frameworks */,
 				FC9788112485969B00A7906C /* Frameworks */,
 				FC9788122485969B00A7906C /* Resources */,
 				FC9788122485969B00A7906C /* Resources */,
+				04DA71CCA0280FA5FA2DF7A6 /* [CP] Embed Pods Frameworks */,
 			);
 			);
 			buildRules = (
 			buildRules = (
 			);
 			);
@@ -90,7 +174,7 @@
 			);
 			);
 			name = LoopFollow;
 			name = LoopFollow;
 			productName = LoopFollow;
 			productName = LoopFollow;
-			productReference = FC9788142485969B00A7906C /* LoopFollow.app */;
+			productReference = FC9788142485969B00A7906C /* Loop Follow.app */;
 			productType = "com.apple.product-type.application";
 			productType = "com.apple.product-type.application";
 		};
 		};
 /* End PBXNativeTarget section */
 /* End PBXNativeTarget section */
@@ -131,6 +215,8 @@
 			isa = PBXResourcesBuildPhase;
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			buildActionMask = 2147483647;
 			files = (
 			files = (
+				FCC6885E24896A6C00A0279D /* silence.mp3 in Resources */,
+				FCC6885C2489559400A0279D /* blank.wav in Resources */,
 				FC9788292485969C00A7906C /* LaunchScreen.storyboard in Resources */,
 				FC9788292485969C00A7906C /* LaunchScreen.storyboard in Resources */,
 				FC9788262485969C00A7906C /* Assets.xcassets in Resources */,
 				FC9788262485969C00A7906C /* Assets.xcassets in Resources */,
 				FC9788212485969B00A7906C /* Main.storyboard in Resources */,
 				FC9788212485969B00A7906C /* Main.storyboard in Resources */,
@@ -139,15 +225,67 @@
 		};
 		};
 /* End PBXResourcesBuildPhase section */
 /* End PBXResourcesBuildPhase section */
 
 
+/* Begin PBXShellScriptBuildPhase section */
+		04DA71CCA0280FA5FA2DF7A6 /* [CP] Embed Pods Frameworks */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+				"${PODS_ROOT}/Target Support Files/Pods-LoopFollow/Pods-LoopFollow-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+			);
+			name = "[CP] Embed Pods Frameworks";
+			outputFileListPaths = (
+				"${PODS_ROOT}/Target Support Files/Pods-LoopFollow/Pods-LoopFollow-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-LoopFollow/Pods-LoopFollow-frameworks.sh\"\n";
+			showEnvVarsInLog = 0;
+		};
+		B038D39450A1F9A97D2B8BA4 /* [CP] Check Pods Manifest.lock */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+				"${PODS_ROOT}/Manifest.lock",
+			);
+			name = "[CP] Check Pods Manifest.lock";
+			outputFileListPaths = (
+			);
+			outputPaths = (
+				"$(DERIVED_FILE_DIR)/Pods-LoopFollow-checkManifestLockResult.txt",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+			showEnvVarsInLog = 0;
+		};
+/* End PBXShellScriptBuildPhase section */
+
 /* Begin PBXSourcesBuildPhase section */
 /* Begin PBXSourcesBuildPhase section */
 		FC9788102485969B00A7906C /* Sources */ = {
 		FC9788102485969B00A7906C /* Sources */ = {
 			isa = PBXSourcesBuildPhase;
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			buildActionMask = 2147483647;
 			files = (
 			files = (
-				FC97881E2485969B00A7906C /* SecondViewController.swift in Sources */,
+				FCC6885A2489554800A0279D /* BackgroundTask.swift in Sources */,
+				FCC68850248935D800A0279D /* AlarmViewController.swift in Sources */,
+				FCC6886724898F8000A0279D /* UserDefaultsValue.swift in Sources */,
+				FC97881E2485969B00A7906C /* NightScoutViewController.swift in Sources */,
+				FCC6886F2489A53800A0279D /* AppConstants.swift in Sources */,
+				FCC6886924898FB100A0279D /* UserDefaultsValueGroups.swift in Sources */,
 				FC9788242485969B00A7906C /* LoopFollow.xcdatamodeld in Sources */,
 				FC9788242485969B00A7906C /* LoopFollow.xcdatamodeld in Sources */,
-				FC97881C2485969B00A7906C /* FirstViewController.swift in Sources */,
+				FCFEECA2248857A600402A7F /* SettingsViewController.swift in Sources */,
+				FCFEECA02488157B00402A7F /* Chart.swift in Sources */,
+				FCC6886524898EEE00A0279D /* UserDefaults.swift in Sources */,
+				FC97881C2485969B00A7906C /* MainViewController.swift in Sources */,
 				FC9788182485969B00A7906C /* AppDelegate.swift in Sources */,
 				FC9788182485969B00A7906C /* AppDelegate.swift in Sources */,
+				FCC6886B24898FD800A0279D /* ObservationToken.swift in Sources */,
+				FCC6886D2489909D00A0279D /* AnyConvertible.swift in Sources */,
 				FC97881A2485969B00A7906C /* SceneDelegate.swift in Sources */,
 				FC97881A2485969B00A7906C /* SceneDelegate.swift in Sources */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
@@ -290,8 +428,10 @@
 		};
 		};
 		FC97882E2485969C00A7906C /* Debug */ = {
 		FC97882E2485969C00A7906C /* Debug */ = {
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
+			baseConfigurationReference = ECA3EFB4037410B4973BB632 /* Pods-LoopFollow.debug.xcconfig */;
 			buildSettings = {
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CODE_SIGN_ENTITLEMENTS = "LoopFollow/Loop Follow.entitlements";
 				CODE_SIGN_STYLE = Automatic;
 				CODE_SIGN_STYLE = Automatic;
 				DEVELOPMENT_TEAM = 96ZGUPW5TE;
 				DEVELOPMENT_TEAM = 96ZGUPW5TE;
 				INFOPLIST_FILE = LoopFollow/Info.plist;
 				INFOPLIST_FILE = LoopFollow/Info.plist;
@@ -300,7 +440,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/Frameworks",
 				);
 				);
 				PRODUCT_BUNDLE_IDENTIFIER = com.fawcett.LoopFollow;
 				PRODUCT_BUNDLE_IDENTIFIER = com.fawcett.LoopFollow;
-				PRODUCT_NAME = "$(TARGET_NAME)";
+				PRODUCT_NAME = "Loop Follow";
 				SWIFT_VERSION = 5.0;
 				SWIFT_VERSION = 5.0;
 				TARGETED_DEVICE_FAMILY = "1,2";
 				TARGETED_DEVICE_FAMILY = "1,2";
 			};
 			};
@@ -308,8 +448,10 @@
 		};
 		};
 		FC97882F2485969C00A7906C /* Release */ = {
 		FC97882F2485969C00A7906C /* Release */ = {
 			isa = XCBuildConfiguration;
 			isa = XCBuildConfiguration;
+			baseConfigurationReference = 059B0FA59AABFE72FE13DDDA /* Pods-LoopFollow.release.xcconfig */;
 			buildSettings = {
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CODE_SIGN_ENTITLEMENTS = "LoopFollow/Loop Follow.entitlements";
 				CODE_SIGN_STYLE = Automatic;
 				CODE_SIGN_STYLE = Automatic;
 				DEVELOPMENT_TEAM = 96ZGUPW5TE;
 				DEVELOPMENT_TEAM = 96ZGUPW5TE;
 				INFOPLIST_FILE = LoopFollow/Info.plist;
 				INFOPLIST_FILE = LoopFollow/Info.plist;
@@ -318,7 +460,7 @@
 					"@executable_path/Frameworks",
 					"@executable_path/Frameworks",
 				);
 				);
 				PRODUCT_BUNDLE_IDENTIFIER = com.fawcett.LoopFollow;
 				PRODUCT_BUNDLE_IDENTIFIER = com.fawcett.LoopFollow;
-				PRODUCT_NAME = "$(TARGET_NAME)";
+				PRODUCT_NAME = "Loop Follow";
 				SWIFT_VERSION = 5.0;
 				SWIFT_VERSION = 5.0;
 				TARGETED_DEVICE_FAMILY = "1,2";
 				TARGETED_DEVICE_FAMILY = "1,2";
 			};
 			};

+ 3 - 0
LoopFollow.xcodeproj/project.xcworkspace/contents.xcworkspacedata

@@ -2,6 +2,9 @@
 <Workspace
 <Workspace
    version = "1.0">
    version = "1.0">
    <FileRef
    <FileRef
+      location = "group:../../../NightscoutKit/NightscoutKit.xcodeproj">
+   </FileRef>
+   <FileRef
       location = "self:LoopFollow.xcodeproj">
       location = "self:LoopFollow.xcodeproj">
    </FileRef>
    </FileRef>
 </Workspace>
 </Workspace>

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

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

+ 52 - 0
LoopFollow/AlarmViewController.swift

@@ -0,0 +1,52 @@
+//
+//  AlarmViewController.swift
+//  LoopFollow
+//
+//  Created by Jon Fawcett on 6/3/20.
+//  Copyright © 2020 Jon Fawcett. All rights reserved.
+//
+
+import UIKit
+import Eureka
+
+class AlarmViewController: FormViewController {
+
+
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        form +++ Section(header: "High BG Alert", footer: "Alerts when blood glucose raises above this value")
+            <<< SliderRow.glucoseLevelSlider(initialValue: 180, minimumValue: 100, maximumValue: 400)
+
+        
+    }
+
+
+}
+
+extension SliderRow {
+    
+    class func glucoseLevelSlider(initialValue: Float, minimumValue: Float, maximumValue: Float, snapIncrementForMgDl: Float = 1.0) -> SliderRow {
+        
+        return SliderRow() { row in
+            row.value = 180
+            }.cellSetup { cell, row in
+                
+                let minimumValue: Float = 40
+                let maximumValue: Float  = 400
+                
+                cell.slider.minimumValue = minimumValue
+                cell.slider.maximumValue = maximumValue
+                row.displayValueFor = { value in
+                    guard let value = value else { return "" }
+                    let units = "mg/dl"
+                    return units
+                }
+                
+                // fixed width for value label
+                let widthConstraint = NSLayoutConstraint(item: cell.valueLabel!, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 96)
+                cell.valueLabel.addConstraints([widthConstraint])
+        }
+    }
+}

BIN
LoopFollow/Assets.xcassets/AppIcon.appiconset/1024.png


BIN
LoopFollow/Assets.xcassets/AppIcon.appiconset/120-1.png


BIN
LoopFollow/Assets.xcassets/AppIcon.appiconset/120.png


BIN
LoopFollow/Assets.xcassets/AppIcon.appiconset/152.png


BIN
LoopFollow/Assets.xcassets/AppIcon.appiconset/167.png


BIN
LoopFollow/Assets.xcassets/AppIcon.appiconset/180.png


BIN
LoopFollow/Assets.xcassets/AppIcon.appiconset/20.png


BIN
LoopFollow/Assets.xcassets/AppIcon.appiconset/29.png


BIN
LoopFollow/Assets.xcassets/AppIcon.appiconset/40-1.png


BIN
LoopFollow/Assets.xcassets/AppIcon.appiconset/40-2.png


BIN
LoopFollow/Assets.xcassets/AppIcon.appiconset/40.png


BIN
LoopFollow/Assets.xcassets/AppIcon.appiconset/58-1.png


BIN
LoopFollow/Assets.xcassets/AppIcon.appiconset/58.png


BIN
LoopFollow/Assets.xcassets/AppIcon.appiconset/60.png


BIN
LoopFollow/Assets.xcassets/AppIcon.appiconset/76.png


BIN
LoopFollow/Assets.xcassets/AppIcon.appiconset/80-1.png


BIN
LoopFollow/Assets.xcassets/AppIcon.appiconset/80.png


BIN
LoopFollow/Assets.xcassets/AppIcon.appiconset/87.png


+ 18 - 0
LoopFollow/Assets.xcassets/AppIcon.appiconset/Contents.json

@@ -1,91 +1,109 @@
 {
 {
   "images" : [
   "images" : [
     {
     {
+      "filename" : "40.png",
       "idiom" : "iphone",
       "idiom" : "iphone",
       "scale" : "2x",
       "scale" : "2x",
       "size" : "20x20"
       "size" : "20x20"
     },
     },
     {
     {
+      "filename" : "60.png",
       "idiom" : "iphone",
       "idiom" : "iphone",
       "scale" : "3x",
       "scale" : "3x",
       "size" : "20x20"
       "size" : "20x20"
     },
     },
     {
     {
+      "filename" : "58.png",
       "idiom" : "iphone",
       "idiom" : "iphone",
       "scale" : "2x",
       "scale" : "2x",
       "size" : "29x29"
       "size" : "29x29"
     },
     },
     {
     {
+      "filename" : "87.png",
       "idiom" : "iphone",
       "idiom" : "iphone",
       "scale" : "3x",
       "scale" : "3x",
       "size" : "29x29"
       "size" : "29x29"
     },
     },
     {
     {
+      "filename" : "80.png",
       "idiom" : "iphone",
       "idiom" : "iphone",
       "scale" : "2x",
       "scale" : "2x",
       "size" : "40x40"
       "size" : "40x40"
     },
     },
     {
     {
+      "filename" : "120.png",
       "idiom" : "iphone",
       "idiom" : "iphone",
       "scale" : "3x",
       "scale" : "3x",
       "size" : "40x40"
       "size" : "40x40"
     },
     },
     {
     {
+      "filename" : "120-1.png",
       "idiom" : "iphone",
       "idiom" : "iphone",
       "scale" : "2x",
       "scale" : "2x",
       "size" : "60x60"
       "size" : "60x60"
     },
     },
     {
     {
+      "filename" : "180.png",
       "idiom" : "iphone",
       "idiom" : "iphone",
       "scale" : "3x",
       "scale" : "3x",
       "size" : "60x60"
       "size" : "60x60"
     },
     },
     {
     {
+      "filename" : "20.png",
       "idiom" : "ipad",
       "idiom" : "ipad",
       "scale" : "1x",
       "scale" : "1x",
       "size" : "20x20"
       "size" : "20x20"
     },
     },
     {
     {
+      "filename" : "40-1.png",
       "idiom" : "ipad",
       "idiom" : "ipad",
       "scale" : "2x",
       "scale" : "2x",
       "size" : "20x20"
       "size" : "20x20"
     },
     },
     {
     {
+      "filename" : "29.png",
       "idiom" : "ipad",
       "idiom" : "ipad",
       "scale" : "1x",
       "scale" : "1x",
       "size" : "29x29"
       "size" : "29x29"
     },
     },
     {
     {
+      "filename" : "58-1.png",
       "idiom" : "ipad",
       "idiom" : "ipad",
       "scale" : "2x",
       "scale" : "2x",
       "size" : "29x29"
       "size" : "29x29"
     },
     },
     {
     {
+      "filename" : "40-2.png",
       "idiom" : "ipad",
       "idiom" : "ipad",
       "scale" : "1x",
       "scale" : "1x",
       "size" : "40x40"
       "size" : "40x40"
     },
     },
     {
     {
+      "filename" : "80-1.png",
       "idiom" : "ipad",
       "idiom" : "ipad",
       "scale" : "2x",
       "scale" : "2x",
       "size" : "40x40"
       "size" : "40x40"
     },
     },
     {
     {
+      "filename" : "76.png",
       "idiom" : "ipad",
       "idiom" : "ipad",
       "scale" : "1x",
       "scale" : "1x",
       "size" : "76x76"
       "size" : "76x76"
     },
     },
     {
     {
+      "filename" : "152.png",
       "idiom" : "ipad",
       "idiom" : "ipad",
       "scale" : "2x",
       "scale" : "2x",
       "size" : "76x76"
       "size" : "76x76"
     },
     },
     {
     {
+      "filename" : "167.png",
       "idiom" : "ipad",
       "idiom" : "ipad",
       "scale" : "2x",
       "scale" : "2x",
       "size" : "83.5x83.5"
       "size" : "83.5x83.5"
     },
     },
     {
     {
+      "filename" : "1024.png",
       "idiom" : "ios-marketing",
       "idiom" : "ios-marketing",
       "scale" : "1x",
       "scale" : "1x",
       "size" : "1024x1024"
       "size" : "1024x1024"

+ 161 - 48
LoopFollow/Base.lproj/Main.storyboard

@@ -1,80 +1,189 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" 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" 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>
     <dependencies>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     </dependencies>
     <scenes>
     <scenes>
-        <!--First-->
+        <!--Home-->
         <scene sceneID="hNz-n2-bh7">
         <scene sceneID="hNz-n2-bh7">
             <objects>
             <objects>
-                <viewController id="9pv-A4-QxB" customClass="FirstViewController" customModuleProvider="target" sceneMemberID="viewController">
+                <viewController id="9pv-A4-QxB" userLabel="Home" customClass="MainViewController" customModule="Loop_Follow" customModuleProvider="target" sceneMemberID="viewController">
                     <view key="view" contentMode="scaleToFill" id="tsR-hK-woN">
                     <view key="view" contentMode="scaleToFill" id="tsR-hK-woN">
-                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+                        <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <subviews>
                         <subviews>
-                            <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" text="First View" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="KQZ-1w-vlD">
-                                <color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
-                                <fontDescription key="fontDescription" type="system" pointSize="36"/>
-                                <color key="textColor" xcode11CocoaTouchSystemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
-                                <nil key="highlightedColor"/>
-                            </label>
-                            <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Loaded by FirstViewController" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="A5M-7J-77L">
-                                <fontDescription key="fontDescription" type="system" pointSize="14"/>
-                                <color key="textColor" xcode11CocoaTouchSystemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
-                                <nil key="highlightedColor"/>
-                            </label>
+                            <view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Nml-lr-dgQ" userLabel="BGChartFull" customClass="LineChartView" customModule="Charts">
+                                <rect key="frame" x="0.0" y="691" width="414" height="113"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                            </view>
+                            <view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="51B-JU-npv" customClass="LineChartView" customModule="Charts">
+                                <rect key="frame" x="0.0" y="302" width="414" height="370"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                            </view>
+                            <view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="gp3-ES-mVi">
+                                <rect key="frame" x="0.0" y="44" width="203" height="239"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                <subviews>
+                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Delta" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sb7-g9-JNv" userLabel="Delta">
+                                        <rect key="frame" x="47" y="169" width="115" height="39"/>
+                                        <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                        <fontDescription key="fontDescription" type="system" pointSize="32"/>
+                                        <nil key="textColor"/>
+                                        <color key="highlightedColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                    </label>
+                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="BG" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vXU-IW-GOL">
+                                        <rect key="frame" x="19" y="18" width="176" height="102"/>
+                                        <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                        <fontDescription key="fontDescription" type="system" weight="black" pointSize="85"/>
+                                        <nil key="textColor"/>
+                                        <nil key="highlightedColor"/>
+                                    </label>
+                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="MinAgo" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="UkG-hw-DVo">
+                                        <rect key="frame" x="54" y="216" width="108" height="15"/>
+                                        <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                        <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                                        <nil key="textColor"/>
+                                        <nil key="highlightedColor"/>
+                                    </label>
+                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="--" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4sd-6h-LSj" userLabel="Direction">
+                                        <rect key="frame" x="47" y="97" width="115" height="72"/>
+                                        <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                        <fontDescription key="fontDescription" type="system" weight="black" pointSize="60"/>
+                                        <nil key="textColor"/>
+                                        <nil key="highlightedColor"/>
+                                    </label>
+                                </subviews>
+                                <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                            </view>
+                            <tableView clipsSubviews="YES" contentMode="scaleToFill" fixedFrame="YES" 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="211" y="44" width="203" height="250"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                                <prototypes>
+                                    <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="LabelCell" textLabel="gif-qe-9mv" detailTextLabel="hfT-xF-eNt" style="IBUITableViewCellStyleValue1" id="GJo-f1-ajA">
+                                        <rect key="frame" x="0.0" y="28" width="203" height="43.666667938232422"/>
+                                        <autoresizingMask key="autoresizingMask"/>
+                                        <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="GJo-f1-ajA" id="2H1-eI-xS5">
+                                            <rect key="frame" x="0.0" y="0.0" width="203" height="43.666667938232422"/>
+                                            <autoresizingMask key="autoresizingMask"/>
+                                            <subviews>
+                                                <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="gif-qe-9mv">
+                                                    <rect key="frame" x="16.000000000000004" y="11.999999999999998" width="33.333333333333336" height="20.333333333333332"/>
+                                                    <autoresizingMask key="autoresizingMask"/>
+                                                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
+                                                    <nil key="textColor"/>
+                                                    <nil key="highlightedColor"/>
+                                                </label>
+                                                <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Detail" textAlignment="right" lineBreakMode="characterWrap" minimumFontSize="8" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" id="hfT-xF-eNt">
+                                                    <rect key="frame" x="143" y="11.999999999999998" width="44" height="20.333333333333332"/>
+                                                    <autoresizingMask key="autoresizingMask"/>
+                                                    <fontDescription key="fontDescription" type="system" pointSize="17"/>
+                                                    <nil key="textColor"/>
+                                                    <nil key="highlightedColor"/>
+                                                </label>
+                                            </subviews>
+                                        </tableViewCellContentView>
+                                    </tableViewCell>
+                                </prototypes>
+                            </tableView>
                         </subviews>
                         </subviews>
-                        <color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
-                        <constraints>
-                            <constraint firstAttribute="centerX" secondItem="KQZ-1w-vlD" secondAttribute="centerX" id="6BV-lF-sBN"/>
-                            <constraint firstItem="A5M-7J-77L" firstAttribute="top" secondItem="KQZ-1w-vlD" secondAttribute="bottom" constant="8" symbolic="YES" id="cfb-er-3JN"/>
-                            <constraint firstItem="A5M-7J-77L" firstAttribute="centerX" secondItem="KQZ-1w-vlD" secondAttribute="centerX" id="e1l-AV-tCB"/>
-                            <constraint firstAttribute="centerY" secondItem="KQZ-1w-vlD" secondAttribute="centerY" id="exm-UA-ej4"/>
-                        </constraints>
+                        <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
                         <viewLayoutGuide key="safeArea" id="PQr-Ze-W5v"/>
                         <viewLayoutGuide key="safeArea" id="PQr-Ze-W5v"/>
                     </view>
                     </view>
-                    <tabBarItem key="tabBarItem" title="First" image="first" id="acW-dT-cKf"/>
+                    <tabBarItem key="tabBarItem" title="Home" image="house" catalog="system" id="acW-dT-cKf" userLabel="Home"/>
+                    <connections>
+                        <outlet property="BGChart" destination="51B-JU-npv" id="oF1-UA-KHE"/>
+                        <outlet property="BGChartFull" destination="Nml-lr-dgQ" id="Z21-fG-GK9"/>
+                        <outlet property="BGText" destination="vXU-IW-GOL" id="2aC-pv-Qbg"/>
+                        <outlet property="DeltaText" destination="sb7-g9-JNv" id="h7s-Yj-i4M"/>
+                        <outlet property="DirectionText" destination="4sd-6h-LSj" id="lB5-Z0-p25"/>
+                        <outlet property="MinAgoText" destination="UkG-hw-DVo" id="Xd2-fL-qYC"/>
+                        <outlet property="infoTable" destination="kTs-7g-lp9" id="Khq-sV-EZ1"/>
+                    </connections>
                 </viewController>
                 </viewController>
                 <placeholder placeholderIdentifier="IBFirstResponder" id="W5J-7L-Pyd" sceneMemberID="firstResponder"/>
                 <placeholder placeholderIdentifier="IBFirstResponder" id="W5J-7L-Pyd" sceneMemberID="firstResponder"/>
             </objects>
             </objects>
-            <point key="canvasLocation" x="750" y="-320"/>
+            <point key="canvasLocation" x="-1496" y="746"/>
         </scene>
         </scene>
-        <!--Second-->
+        <!--Nightscout-->
         <scene sceneID="wg7-f3-ORb">
         <scene sceneID="wg7-f3-ORb">
             <objects>
             <objects>
-                <viewController id="8rJ-Kc-sve" customClass="SecondViewController" customModuleProvider="target" sceneMemberID="viewController">
+                <viewController id="8rJ-Kc-sve" customClass="NightscoutViewController" customModule="Loop_Follow" customModuleProvider="target" sceneMemberID="viewController">
                     <view key="view" contentMode="scaleToFill" id="QS5-Rx-YEW">
                     <view key="view" contentMode="scaleToFill" id="QS5-Rx-YEW">
-                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+                        <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <subviews>
                         <subviews>
-                            <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" text="Second View" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="zEq-FU-wV5">
-                                <color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
-                                <fontDescription key="fontDescription" type="system" pointSize="36"/>
-                                <color key="textColor" xcode11CocoaTouchSystemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
-                                <nil key="highlightedColor"/>
-                            </label>
-                            <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Loaded by SecondViewController" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="NDk-cv-Gan">
-                                <fontDescription key="fontDescription" type="system" pointSize="14"/>
-                                <color key="textColor" xcode11CocoaTouchSystemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
-                                <nil key="highlightedColor"/>
-                            </label>
+                            <wkWebView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5Xw-bG-4Kg">
+                                <rect key="frame" x="0.0" y="44" width="414" height="769"/>
+                                <color key="backgroundColor" red="0.36078431370000003" green="0.38823529410000002" blue="0.4039215686" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                                <wkWebViewConfiguration key="configuration">
+                                    <audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
+                                    <wkPreferences key="preferences"/>
+                                </wkWebViewConfiguration>
+                            </wkWebView>
                         </subviews>
                         </subviews>
-                        <color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                        <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
                         <constraints>
                         <constraints>
-                            <constraint firstItem="NDk-cv-Gan" firstAttribute="top" secondItem="zEq-FU-wV5" secondAttribute="bottom" constant="8" symbolic="YES" id="Day-4N-Vmt"/>
-                            <constraint firstItem="NDk-cv-Gan" firstAttribute="centerX" secondItem="zEq-FU-wV5" secondAttribute="centerX" id="JgO-Fn-dHn"/>
-                            <constraint firstAttribute="centerX" secondItem="zEq-FU-wV5" secondAttribute="centerX" id="qqM-NS-xev"/>
-                            <constraint firstAttribute="centerY" secondItem="zEq-FU-wV5" secondAttribute="centerY" id="qzY-Ky-pLD"/>
+                            <constraint firstItem="5Xw-bG-4Kg" firstAttribute="top" secondItem="O1u-W8-tvY" secondAttribute="top" id="5V4-Ga-6op"/>
+                            <constraint firstItem="5Xw-bG-4Kg" firstAttribute="leading" secondItem="QS5-Rx-YEW" secondAttribute="leading" id="FaP-vT-thb"/>
+                            <constraint firstItem="O1u-W8-tvY" firstAttribute="bottom" secondItem="5Xw-bG-4Kg" secondAttribute="bottom" id="QgW-jN-ThE"/>
+                            <constraint firstAttribute="trailing" secondItem="5Xw-bG-4Kg" secondAttribute="trailing" id="hoj-B8-6r3"/>
                         </constraints>
                         </constraints>
                         <viewLayoutGuide key="safeArea" id="O1u-W8-tvY"/>
                         <viewLayoutGuide key="safeArea" id="O1u-W8-tvY"/>
                     </view>
                     </view>
-                    <tabBarItem key="tabBarItem" title="Second" image="second" id="cPa-gy-q4n"/>
+                    <tabBarItem key="tabBarItem" title="Nightscout" image="safari" catalog="system" id="cPa-gy-q4n"/>
+                    <connections>
+                        <outlet property="WebView" destination="5Xw-bG-4Kg" id="WKZ-Pf-oAY"/>
+                    </connections>
                 </viewController>
                 </viewController>
                 <placeholder placeholderIdentifier="IBFirstResponder" id="4Nw-L8-lE0" sceneMemberID="firstResponder"/>
                 <placeholder placeholderIdentifier="IBFirstResponder" id="4Nw-L8-lE0" sceneMemberID="firstResponder"/>
             </objects>
             </objects>
-            <point key="canvasLocation" x="750" y="360"/>
+            <point key="canvasLocation" x="-703" y="746"/>
+        </scene>
+        <!--Alarms-->
+        <scene sceneID="sEH-4N-YEn">
+            <objects>
+                <viewController id="7an-Fz-r9u" customClass="AlarmViewController" customModule="Loop_Follow" customModuleProvider="target" sceneMemberID="viewController">
+                    <view key="view" contentMode="scaleToFill" id="53u-XU-GCk">
+                        <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                        <viewLayoutGuide key="safeArea" id="SlM-Sc-PIr"/>
+                    </view>
+                    <tabBarItem key="tabBarItem" title="Alarms" image="alarm" catalog="system" id="c7L-5b-Di9"/>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="Rld-0Y-q4t" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="146" y="746"/>
+        </scene>
+        <!--Settings-->
+        <scene sceneID="ORV-wr-Fd3">
+            <objects>
+                <viewController id="hDv-qK-Udb" customClass="SettingsViewController" customModule="Loop_Follow" customModuleProvider="target" sceneMemberID="viewController">
+                    <view key="view" contentMode="scaleToFill" id="ljg-dW-5uR">
+                        <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6gS-Ff-qPd">
+                                <rect key="frame" x="0.0" y="44" width="414" height="769"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                <viewLayoutGuide key="contentLayoutGuide" id="1wh-Gg-Auv"/>
+                                <viewLayoutGuide key="frameLayoutGuide" id="lqA-iJ-wdZ"/>
+                            </scrollView>
+                        </subviews>
+                        <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                        <viewLayoutGuide key="safeArea" id="aVb-ap-Pwt"/>
+                    </view>
+                    <tabBarItem key="tabBarItem" title="Settings" image="gear" catalog="system" id="CBE-5V-OzJ"/>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="mou-xo-Vm3" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="1037.68115942029" y="745.98214285714278"/>
         </scene>
         </scene>
         <!--Tab Bar Controller-->
         <!--Tab Bar Controller-->
         <scene sceneID="yl2-sM-qoP">
         <scene sceneID="yl2-sM-qoP">
@@ -88,6 +197,8 @@
                     <connections>
                     <connections>
                         <segue destination="9pv-A4-QxB" kind="relationship" relationship="viewControllers" id="u7Y-xg-7CH"/>
                         <segue destination="9pv-A4-QxB" kind="relationship" relationship="viewControllers" id="u7Y-xg-7CH"/>
                         <segue destination="8rJ-Kc-sve" kind="relationship" relationship="viewControllers" id="lzU-1b-eKA"/>
                         <segue destination="8rJ-Kc-sve" kind="relationship" relationship="viewControllers" id="lzU-1b-eKA"/>
+                        <segue destination="7an-Fz-r9u" kind="relationship" relationship="viewControllers" id="o3X-OO-dh3"/>
+                        <segue destination="hDv-qK-Udb" kind="relationship" relationship="viewControllers" id="maf-Gu-a58"/>
                     </connections>
                     </connections>
                 </tabBarController>
                 </tabBarController>
                 <placeholder placeholderIdentifier="IBFirstResponder" id="HuB-VB-40B" sceneMemberID="firstResponder"/>
                 <placeholder placeholderIdentifier="IBFirstResponder" id="HuB-VB-40B" sceneMemberID="firstResponder"/>
@@ -96,7 +207,9 @@
         </scene>
         </scene>
     </scenes>
     </scenes>
     <resources>
     <resources>
-        <image name="first" width="30" height="30"/>
-        <image name="second" width="30" height="30"/>
+        <image name="alarm" catalog="system" width="124" height="128"/>
+        <image name="gear" catalog="system" width="128" height="119"/>
+        <image name="house" catalog="system" width="128" height="110"/>
+        <image name="safari" catalog="system" width="128" height="121"/>
     </resources>
     </resources>
 </document>
 </document>

+ 0 - 20
LoopFollow/FirstViewController.swift

@@ -1,20 +0,0 @@
-//
-//  FirstViewController.swift
-//  LoopFollow
-//
-//  Created by Jon Fawcett on 6/1/20.
-//  Copyright © 2020 Jon Fawcett. All rights reserved.
-//
-
-import UIKit
-
-class FirstViewController: UIViewController {
-
-    override func viewDidLoad() {
-        super.viewDidLoad()
-        // Do any additional setup after loading the view.
-    }
-
-
-}
-

+ 5 - 0
LoopFollow/Info.plist

@@ -39,6 +39,11 @@
 			</array>
 			</array>
 		</dict>
 		</dict>
 	</dict>
 	</dict>
+	<key>UIBackgroundModes</key>
+	<array>
+		<string>audio</string>
+		<string>processing</string>
+	</array>
 	<key>UILaunchStoryboardName</key>
 	<key>UILaunchStoryboardName</key>
 	<string>LaunchScreen</string>
 	<string>LaunchScreen</string>
 	<key>UIMainStoryboardFile</key>
 	<key>UIMainStoryboardFile</key>

+ 10 - 0
LoopFollow/Loop Follow.entitlements

@@ -0,0 +1,10 @@
+<?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>com.apple.security.application-groups</key>
+	<array>
+		<string>group.com.fawcett.LoopFollow</string>
+	</array>
+</dict>
+</plist>

+ 552 - 0
LoopFollow/MainViewController.swift

@@ -0,0 +1,552 @@
+//
+//  FirstViewController.swift
+//  LoopFollow
+//
+//  Created by Jon Fawcett on 6/1/20.
+//  Copyright © 2020 Jon Fawcett. All rights reserved.
+//
+
+import UIKit
+import Charts
+
+class MainViewController: UIViewController, UITableViewDataSource {
+    
+    @IBOutlet weak var BGText: UILabel!
+    @IBOutlet weak var DeltaText: UILabel!
+    @IBOutlet weak var DirectionText: UILabel!
+    @IBOutlet weak var BGChart: LineChartView!
+    @IBOutlet weak var BGChartFull: LineChartView!
+    @IBOutlet weak var MinAgoText: UILabel!
+    @IBOutlet weak var infoTable: UITableView!
+    
+    //NS BG Struct
+    struct sgvData: Codable {
+        var sgv: Int
+        var date: TimeInterval
+        var direction: String?
+    }
+    
+    // Data Table Struct
+    struct infoData {
+        var name: String
+        var value: String
+    }
+    
+    // Variables for BG Charts
+    public var numPoints: Int = 13
+    public var linePlotData: [Double] = []
+    public var linePlotDataTime: [Double] = []
+    var firstStart: Bool = true
+    
+    // Vars for NS Pull
+    var graphHours:Int=24
+    var mmol = false as Bool
+    var urlUser = UserDefaultsRepository.url.value as String
+    var token = "" as String
+    var defaults : UserDefaults?
+    let consoleLogging = true
+    var timeofLastBGUpdate = 0 as TimeInterval
+    
+    var backgroundTask = BackgroundTask()
+    
+    // Refresh NS Data
+    var timer = Timer()
+    // check every 30 Seconds whether new bgvalues should be retrieved
+    let timeInterval: TimeInterval = 30.0
+    
+    // Info Table Setup
+    var tableData = [
+        infoData(name: "IOB", value: ""), //0
+        infoData(name: "COB", value: ""), //1
+        infoData(name: "Basal", value: ""), //2
+        infoData(name: "Override", value: ""), //3
+        infoData(name: "Battery", value: ""), //4
+        infoData(name: "Pump", value: ""), //5
+        infoData(name: "Loop", value: ""), //6
+        infoData(name: "SAGE", value: ""), //7
+        infoData(name: "CAGE", value: "") //8
+    ]
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        
+        UNUserNotificationCenter.current().requestAuthorization(options: .badge) { (granted, error) in
+            if error != nil {
+                // success!
+            }
+        }
+        
+        // ToDo: Should continue running in background
+        // stop timer when app enters in background, start is again when becomes active
+        let notificationCenter = NotificationCenter.default
+            notificationCenter.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
+            notificationCenter.addObserver(self, selector: #selector(appCameToForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
+        
+        //Bind info data
+        infoTable.rowHeight = 25
+        infoTable.dataSource = self
+
+    }
+    
+    override func viewWillAppear(_ animated: Bool) {
+        Reload()
+    }
+    
+    
+    // Info Table Functions
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return tableData.count
+    }
+    
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell = tableView.dequeueReusableCell(withIdentifier: "LabelCell", for: indexPath)
+        let values = tableData[indexPath.row]
+        cell.textLabel?.text = values.name
+        cell.detailTextLabel?.text = values.value
+        return cell
+    }
+    
+    // Timer
+    fileprivate func startTimer() {
+        timer = Timer.scheduledTimer(timeInterval: timeInterval,
+                                     target: self,
+                                     selector: #selector(MainViewController.timerDidEnd(_:)),
+                                     userInfo: nil,
+                                     repeats: true)
+    }
+    
+    @objc func appMovedToBackground() {
+          // timer.invalidate()
+        backgroundTask.startBackgroundTask()
+    }
+
+   @objc func appCameToForeground() {
+        backgroundTask.stopBackgroundTask()
+        //timer.invalidate()
+        startTimer()
+        loadBGData(urlUser: urlUser)
+        loadDeviceStatus(urlUser: urlUser)
+    
+   }
+    
+    // check whether new Values should be retrieved
+    @objc func timerDidEnd(_ timer:Timer) {
+        print("timer ended")
+        Reload()
+    }
+    
+    func Reload() {
+        loadBGData(urlUser: urlUser)
+        clearLastInfoData()
+        loadDeviceStatus(urlUser: urlUser)
+        loadTempBasals(urlUser: urlUser)
+    }
+    
+
+    // Main NS Data Pull
+    func loadBGData(urlUser: String) {
+        let points = String(self.graphHours * 12 + 1)
+        var urlBGDataPath: String = urlUser + "/api/v1/entries/sgv.json?"
+        if token == "" {
+            urlBGDataPath = urlBGDataPath + "count=" + points
+        }
+        else
+        {
+            urlBGDataPath = urlBGDataPath + "token=" + token + "&count=" + points
+        }
+        guard let urlBGData = URL(string: urlBGDataPath) else {
+            return
+        }
+        var request = URLRequest(url: urlBGData)
+        request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
+        
+        let getBGTask = URLSession.shared.dataTask(with: request) { data, response, error in
+            if self.consoleLogging == true {print("start bg url")}
+            guard error == nil else {
+                return
+            }
+            guard let data = data else {
+                return
+            }
+            
+            let decoder = JSONDecoder()
+            let entriesResponse = try? decoder.decode([sgvData].self, from: data)
+            if let entriesResponse = entriesResponse {
+                DispatchQueue.main.async {
+                    if self.backgroundTask.player.isPlaying {
+                        self.updateBadge(entries: entriesResponse)
+                    } else {
+                        self.updateBG(entries: entriesResponse)
+                        self.createGraph(entries: entriesResponse)
+                    }
+                   
+                    
+                }
+            }
+            else
+            {
+                
+                return
+            }
+        }
+        getBGTask.resume()
+    }
+    
+    //Clear the info data before next pull
+    func clearLastInfoData(){
+       for i in 0..<tableData.count{
+        tableData[i].value = ""
+        }
+    }
+    
+    
+    
+    // NS Device Status Pull
+    func loadDeviceStatus(urlUser: String) {
+        var urlStringDeviceStatus = urlUser + "/api/v1/devicestatus.json?count=1"
+        if token != "" {
+            urlStringDeviceStatus = urlUser + "/api/v1/devicestatus.json?token=" + token + "&count=1"
+        }
+        
+        let escapedAddress = urlStringDeviceStatus.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
+        guard let urlDeviceStatus = URL(string: escapedAddress!) else {
+            return
+        }
+        
+        if consoleLogging == true {print("entered 2nd task.")}
+        var requestDeviceStatus = URLRequest(url: urlDeviceStatus)
+        requestDeviceStatus.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
+        let deviceStatusTask = URLSession.shared.dataTask(with: requestDeviceStatus) { data, response, error in
+            if self.consoleLogging == true {print("in update loop.")}
+            guard error == nil else {
+                return
+            }
+            guard let data = data else {
+                
+                return
+            }
+            
+            let json = try? JSONSerialization.jsonObject(with: data) as! [[String:AnyObject]]
+            
+            if let json = json {
+                DispatchQueue.main.async {
+                    self.updateDeviceStatusDisplay(jsonDeviceStatus: json)
+                }
+            }
+            else
+            {
+               
+                return
+            }
+            if self.consoleLogging == true {print("finish pump update")}
+        }
+        deviceStatusTask.resume()
+    }
+    
+    // Need to figure out the date to pull only last 24 hours
+    func loadTempBasals(urlUser: String) {
+        var dayComponent    = DateComponents()
+        dayComponent.day    = -1 // For removing one day (yesterday): -1
+        let theCalendar     = Calendar.current
+    
+        let yesterday       = theCalendar.date(byAdding: dayComponent, to: Date())
+        let dateFormatter = DateFormatter()
+        dateFormatter.dateFormat = "yyyy-mm-ddTHH:mm:ss"
+        //dateFormatter.timeZone = NSTimeZone(name: "UTC")
+        //let date: NSDate? = dateFormatter.dateFromString("2016-02-29 12:24:26")
+        //print(date)
+        
+        var urlStringBasal = urlUser + "/api/v1/treatments.json?find[eventType][$eq]=Temp%20Basal&find[created_at][$gte]=2020-06-02T22:46:26"
+        if token != "" {
+            urlStringBasal = urlUser + "/api/v1/treatments.json?token=" + token + "&find[eventType][$eq]=Temp%20Basal&find[created_at][$gte]=2020-06-02T22:46:26"
+        }
+        
+        let escapedAddress = urlStringBasal.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
+        guard let urlBasal = URL(string: escapedAddress!) else {
+            return
+        }
+        
+        if consoleLogging == true {print("entered 2nd task.")}
+        var requestBasal = URLRequest(url: urlBasal)
+        requestBasal.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
+        let basalTask = URLSession.shared.dataTask(with: requestBasal) { data, response, error in
+            if self.consoleLogging == true {print("in update loop.")}
+            guard error == nil else {
+                return
+            }
+            guard let data = data else {
+                
+                return
+            }
+            
+            let json = try? JSONSerialization.jsonObject(with: data) as! [[String:AnyObject]]
+            
+            if let json = json {
+                DispatchQueue.main.async {
+                    self.updateDeviceStatusDisplay(jsonDeviceStatus: json)
+                }
+            }
+            else
+            {
+               
+                return
+            }
+            if self.consoleLogging == true {print("finish pump update")}
+        }
+        basalTask.resume()
+    }
+    
+    // Parse Device Status Data
+    func updateDeviceStatusDisplay(jsonDeviceStatus: [[String:AnyObject]]) {
+        if consoleLogging == true {print("in updatePump")}
+        if jsonDeviceStatus.count == 0 {
+            return
+        }
+        
+        //only grabbing one record since ns sorts by {created_at : -1}
+        let lastDeviceStatus = jsonDeviceStatus[0] as [String : AnyObject]?
+        
+        //pump and uploader
+        let formatter = ISO8601DateFormatter()
+        formatter.formatOptions = [.withFullDate,
+                                   .withTime,
+                                   .withDashSeparatorInDate,
+                                   .withColonSeparatorInTime]
+        if let lastPumpRecord = lastDeviceStatus?["pump"] as! [String : AnyObject]? {
+            if let lastPumpTime = formatter.date(from: (lastPumpRecord["clock"] as! String))?.timeIntervalSince1970  {
+                if let reservoirData = lastPumpRecord["reservoir"] as? Double
+                {
+                    tableData[5].value = String(format:"%.0f", reservoirData) + "U"
+                } else {
+                    tableData[5].value = "50+U"
+                   
+                }
+                
+                if let uploader = lastDeviceStatus?["uploader"] as? [String:AnyObject] {
+                    let upbat = uploader["battery"] as! Double
+                  //  BatteryText.text! += " " + String(format:"%.0f", upbat) + "%"
+                    tableData[4].value = String(format:"%.0f", upbat) + "%"
+                }
+                
+            }
+        }
+                
+        if let lastLoopRecord = lastDeviceStatus?["loop"] as! [String : AnyObject]? {
+            if let lastLoopTime = formatter.date(from: (lastLoopRecord["timestamp"] as! String))?.timeIntervalSince1970  {
+                if let failure = lastLoopRecord["failureReason"] {
+                    
+                    //LoopStatusText.text! += " Failure "
+                }
+                else
+                {
+                    if let enacted = lastLoopRecord["enacted"] as? [String:AnyObject] {
+                        if let lastTempBasal = enacted["rate"] as? Double {
+                            tableData[2].value = String(format:"%.1f", lastTempBasal)
+                        }
+                    }
+                    if let iobdata = lastLoopRecord["iob"] as? [String:AnyObject] {
+                        tableData[0].value = String(format:"%.1f", (iobdata["iob"] as! Double))
+                    }
+                    if let cobdata = lastLoopRecord["cob"] as? [String:AnyObject] {
+                        tableData[1].value = String(format:"%.0f", cobdata["cob"] as! Double)
+                    }
+                    if let predictdata = lastLoopRecord["predicted"] as? [String:AnyObject] {
+                       // let prediction = predictdata["values"] as! [Double]
+                       // loopStatusText += " EBG " + bgOutputFormat(bg: prediction.last!, mmol: mmol)
+                    }
+                    
+                }
+            }
+            
+        }
+        
+        var oText = "" as String
+               
+               if let lastOverride = lastDeviceStatus?["override"] as! [String : AnyObject]? {
+                   if let lastOverrideTime = formatter.date(from: (lastOverride["timestamp"] as! String))?.timeIntervalSince1970  {
+                   }
+                   if lastOverride["active"] as! Bool {
+                       
+                       let lastCorrection  = lastOverride["currentCorrectionRange"] as! [String: AnyObject]
+                       if let multiplier = lastOverride["multiplier"] as? Double {
+                                              oText += String(format:"%.1f", multiplier*100)
+                                          }
+                                          else
+                                          {
+                                              oText += String(format:"%.1f", 100)
+                                          }
+                       oText += "% ("
+                       let minValue = lastCorrection["minValue"] as! Double
+                       let maxValue = lastCorrection["maxValue"] as! Double
+                       oText += bgOutputFormat(bg: minValue, mmol: mmol) + "-" + bgOutputFormat(bg: maxValue, mmol: mmol) + ")"
+                      
+                    tableData[3].value =  oText
+                   }
+               }
+        
+        infoTable.reloadData()
+        }
+        
+    func stringFromTimeInterval(interval: TimeInterval) -> String {
+        let interval = Int(interval)
+        let minutes = (interval / 60) % 60
+        let hours = (interval / 3600)
+        return String(format: "%02d:%02d", hours, minutes)
+    }
+
+    func createGraph(entries: [sgvData]){
+        var bgChartEntry = [ChartDataEntry]()
+        var colors = [NSUIColor]()
+        for i in 0..<entries.count{
+            var dateString = String(entries[entries.count - 1 - i].date).prefix(10)
+            let dateSecondsOnly = Double(String(dateString))!
+            
+            let value = ChartDataEntry(x: Double(dateSecondsOnly), y: Double(entries[entries.count - 1 - i].sgv))
+            bgChartEntry.append(value)
+            
+            if Double(entries[entries.count - 1 - i].sgv) > 180 {
+                colors.append(NSUIColor.yellow)
+            } else if Double(entries[entries.count - 1 - i].sgv) < 70 {
+                colors.append(NSUIColor.red)
+            } else {
+                colors.append(NSUIColor.green)
+            }
+        }
+        let line1 = LineChartDataSet(entries:bgChartEntry, label: "")
+        line1.circleRadius = 3
+        line1.circleColors = [NSUIColor.green]
+        line1.drawCircleHoleEnabled = false
+        if UserDefaultsRepository.showLines.value {
+            line1.lineWidth = 2
+        } else {
+            line1.lineWidth = 0
+        }
+        if UserDefaultsRepository.showDots.value {
+            line1.drawCirclesEnabled = true
+        } else {
+            line1.drawCirclesEnabled = false
+        }
+        line1.setDrawHighlightIndicators(false)
+        line1.valueFont.withSize(50)
+        
+        for i in 1..<colors.count{
+            line1.addColor(colors[i])
+            line1.circleColors.append(colors[i])
+        }
+        
+        let data = LineChartData()
+        data.addDataSet(line1)
+        data.setValueFont(UIFont(name: UIFont.systemFont(ofSize: 10).fontName, size: 10)!)
+        data.setDrawValues(false)
+        BGChart.xAxis.valueFormatter = ChartXValueFormatter()
+        BGChart.xAxis.granularity = 1800
+        BGChart.xAxis.labelTextColor = NSUIColor.white
+        BGChart.rightAxis.labelTextColor = NSUIColor.white
+        BGChart.leftAxis.enabled = false
+        BGChart.legend.enabled = false
+        BGChart.scaleYEnabled = false
+        BGChart.data = data
+        BGChart.setExtraOffsets(left: 10, top: 10, right: 10, bottom: 10)
+        BGChart.setVisibleXRangeMinimum(10)
+        if firstStart {
+            BGChart.zoom(scaleX: 20, scaleY: 1, x: 1, y: 1)
+            firstStart = false
+        }
+        BGChart.moveViewToX(BGChart.chartXMax)
+        
+        //24 Hour Small Graph
+        let line2 = LineChartDataSet(entries:bgChartEntry, label: "Number")
+        line2.drawCirclesEnabled = false
+        line2.lineWidth = 1
+        for i in 1..<colors.count{
+            line2.addColor(colors[i])
+            line2.circleColors.append(colors[i])
+        }
+        
+        let data2 = LineChartData()
+        data2.addDataSet(line2)
+        BGChartFull.leftAxis.enabled = false
+        BGChartFull.rightAxis.enabled = false
+        BGChartFull.xAxis.enabled = false
+        BGChartFull.legend.enabled = false
+        BGChartFull.scaleYEnabled = false
+        BGChartFull.scaleXEnabled = false
+        BGChartFull.drawGridBackgroundEnabled = false
+        BGChartFull.data = data2
+        
+      
+    }
+    
+    func updateBadge(entries: [sgvData]) {
+        UIApplication.shared.applicationIconBadgeNumber = 0
+        if entries.count > 0 {
+            let latestBG = entries[0].sgv
+            UIApplication.shared.applicationIconBadgeNumber = latestBG
+        }
+        print("updated badge")
+    }
+    
+    func updateBG (entries: [sgvData]) {
+        if consoleLogging == true {print("in update BG")}
+        UIApplication.shared.applicationIconBadgeNumber = 0
+        if entries.count > 0 {
+            let latestBG = entries[0].sgv
+            let priorBG = entries[1].sgv
+            let deltaBG = latestBG - priorBG as Int
+            let lastBGTime = entries[0].date / 1000 //NS has different units
+            let deltaTime = (TimeInterval(Date().timeIntervalSince1970)-lastBGTime) / 60
+            var userUnit = " mg/dL"
+            if mmol {
+                userUnit = " mmol/L"
+            }
+            UIApplication.shared.applicationIconBadgeNumber = latestBG
+            BGText.text = bgOutputFormat(bg: Double(latestBG), mmol: mmol)
+            MinAgoText.text = String(Int(deltaTime)) + " min ago"
+            print(String(Int(deltaTime)) + " min ago")
+            if let directionBG = entries[0].direction {
+                DirectionText.text = bgDirectionGraphic(directionBG)
+            }
+            else
+            {
+                DirectionText.text = ""
+            }
+            
+          if deltaBG < 0 {
+            self.DeltaText.text = String(deltaBG)
+            }
+            else
+            {
+                self.DeltaText.text = "+" + String(deltaBG)
+            }
+        
+        }
+        else
+        {
+            
+            return
+        }
+        
+        
+        if consoleLogging == true {print("end update bg")}
+    }
+
+    func bgOutputFormat(bg: Double, mmol: Bool) -> String {
+        if !mmol {
+            return String(format:"%.0f", bg)
+        }
+        else
+        {
+            return String(format:"%.1f", bg / 18.0)
+        }
+    }
+    
+    func bgDirectionGraphic(_ value:String)->String {
+        let graphics:[String:String]=["Flat":"\u{2192}","DoubleUp":"\u{21C8}","SingleUp":"\u{2191}","FortyFiveUp":"\u{2197}\u{FE0E}","FortyFiveDown":"\u{2198}\u{FE0E}","SingleDown":"\u{2193}","DoubleDown":"\u{21CA}","None":"-","NOT COMPUTABLE":"-","RATE OUT OF RANGE":"-"]
+        
+        
+        return graphics[value]!
+    }
+    
+}
+

+ 33 - 0
LoopFollow/NightScoutViewController.swift

@@ -0,0 +1,33 @@
+//
+//  SecondViewController.swift
+//  LoopFollow
+//
+//  Created by Jon Fawcett on 6/1/20.
+//  Copyright © 2020 Jon Fawcett. All rights reserved.
+//
+
+import UIKit
+import WebKit
+
+class NightscoutViewController: UIViewController {
+
+    @IBOutlet weak var webView: WKWebView!
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        guard let myUrl = URL(string: UserDefaultsRepository.url.value) else { return  }
+        webView.load(URLRequest(url: myUrl))
+        
+        let refreshControl = UIRefreshControl()
+        refreshControl.addTarget(self, action: #selector(reloadWebView(_:)), for: .valueChanged)
+        webView.scrollView.addSubview(refreshControl)
+        
+    }
+
+    @objc func reloadWebView(_ sender: UIRefreshControl) {
+        webView.reload()
+        sender.endRefreshing()
+    }
+
+}
+

+ 0 - 20
LoopFollow/SecondViewController.swift

@@ -1,20 +0,0 @@
-//
-//  SecondViewController.swift
-//  LoopFollow
-//
-//  Created by Jon Fawcett on 6/1/20.
-//  Copyright © 2020 Jon Fawcett. All rights reserved.
-//
-
-import UIKit
-
-class SecondViewController: UIViewController {
-
-    override func viewDidLoad() {
-        super.viewDidLoad()
-        // Do any additional setup after loading the view.
-    }
-
-
-}
-

+ 55 - 0
LoopFollow/SettingsViewController.swift

@@ -0,0 +1,55 @@
+//
+//  SettingsViewController.swift
+//  LoopFollow
+//
+//  Created by Jon Fawcett on 6/3/20.
+//  Copyright © 2020 Jon Fawcett. All rights reserved.
+//
+
+import UIKit
+import Eureka
+
+class SettingsViewController: FormViewController {
+
+
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        
+
+        form +++ Section("Nightscout Settings")
+            <<< TextRow(){ row in
+                row.title = "URL"
+                row.placeholder = "https://mycgm.herokuapp.com"
+                row.value = UserDefaultsRepository.url.value
+            }.onChange { row in
+                guard let value = row.value else { return }
+                UserDefaultsRepository.url.value = value.lowercased()
+                }
+        +++ Section("General Settings")
+            
+        +++ Section("Graph Settings")
+            <<< SwitchRow("switchRowDots"){ row in
+                row.title = "Display Dots"
+                row.value = UserDefaultsRepository.showDots.value
+            }.onChange { [weak self] row in
+                        guard let value = row.value else { return }
+                        UserDefaultsRepository.showDots.value = value
+                }
+            <<< SwitchRow("switchRowLines"){ row in
+                row.title = "Display Lines"
+                row.value = UserDefaultsRepository.showLines.value
+            }.onChange { [weak self] row in
+                        guard let value = row.value else { return }
+                        UserDefaultsRepository.showLines.value = value
+                        
+                }
+        
+         }
+        
+        
+        
+ 
+
+
+}

+ 136 - 0
LoopFollow/helpers/AnyConvertible.swift

@@ -0,0 +1,136 @@
+//
+//  AnyConvertible.swift
+//  nightguard
+//
+//  Created by Florian Preknya on 1/27/19.
+//  Copyright © 2019 private. All rights reserved.
+//
+
+import Foundation
+
+/// A type that can be converted to/from Any 
+protocol AnyConvertible {
+    func toAny() -> Any
+    static func fromAny(_ anyValue: Any) -> Self?
+}
+
+// common type implementations
+extension Bool: AnyConvertible {
+    func toAny() -> Any {
+        return self
+    }
+    
+    static func fromAny(_ anyValue: Any) -> Bool? {
+        return anyValue as? Bool
+    }
+}
+
+extension String: AnyConvertible {
+    func toAny() -> Any {
+        return self
+    }
+    
+    static func fromAny(_ anyValue: Any) -> String? {
+        return anyValue as? String
+    }
+}
+
+extension Int: AnyConvertible {
+    func toAny() -> Any {
+        return self
+    }
+    
+    static func fromAny(_ anyValue: Any) -> Int? {
+        return anyValue as? Int
+    }
+}
+
+extension Float: AnyConvertible {
+    func toAny() -> Any {
+        return self
+    }
+    
+    static func fromAny(_ anyValue: Any) -> Float? {
+        return anyValue as? Float
+    }
+}
+
+extension Double: AnyConvertible {
+    func toAny() -> Any {
+        return self
+    }
+    
+    static func fromAny(_ anyValue: Any) -> Double? {
+        return anyValue as? Double
+    }
+}
+
+extension Date: AnyConvertible {
+    func toAny() -> Any {
+        return self
+    }
+    
+    static func fromAny(_ anyValue: Any) -> Date? {
+        return anyValue as? Date
+    }
+}
+
+extension Data: AnyConvertible {
+    func toAny() -> Any {
+        return self
+    }
+    
+    static func fromAny(_ anyValue: Any) -> Data? {
+        return anyValue as? Data
+    }
+}
+
+extension UUID: AnyConvertible {
+    func toAny() -> Any {
+        return self.uuidString
+    }
+    
+    static func fromAny(_ anyValue: Any) -> UUID? {
+        guard let uuidString = anyValue as? String else {
+            return nil
+        }
+        
+        return UUID(uuidString: uuidString)
+    }
+}
+
+//extension Array: AnyConvertible {
+//    func toAny() -> Any {
+//        return self
+//    }
+//
+//    static func fromAny(_ anyValue: Any) -> Array? {
+//        return anyValue as? Array
+//    }
+//}
+
+extension Array: AnyConvertible where Element: AnyConvertible {
+    func toAny() -> Any {
+        return self.map { $0.toAny() }
+    }
+    
+    static func fromAny(_ anyValue: Any) -> Array? {
+        return (anyValue as? Array)?.compactMap { Element.fromAny($0) }
+    }
+}
+
+extension Optional: AnyConvertible where Wrapped: AnyConvertible {
+    func toAny() -> Any {
+        switch self {
+        case .some(let value):
+            return value.toAny()
+        case .none:
+            return self as Any
+        }
+    }
+    
+    static func fromAny(_ anyValue: Any) -> Optional<Wrapped>? {
+        return Wrapped.fromAny(anyValue)
+    }
+}
+

+ 14 - 0
LoopFollow/helpers/AppConstants.swift

@@ -0,0 +1,14 @@
+//
+//  AppConstants.swift
+//  scoutwatch
+//
+//  Created by Dirk Hermanns on 26.12.15.
+//  Copyright © 2015 private. All rights reserved.
+//
+
+import Foundation
+
+// Class that contains general constants used in different classes
+class AppConstants {
+    internal static let APP_GROUP_ID = "group.com.fawcett.LoopFollow"
+}

+ 52 - 0
LoopFollow/helpers/BackgroundTask.swift

@@ -0,0 +1,52 @@
+//
+//  BackgroundTask.swift
+//
+//  Created by Yaro on 8/27/16.
+//  Copyright © 2016 Yaro. All rights reserved.
+//
+
+import AVFoundation
+
+class BackgroundTask {
+    
+    // MARK: - Vars
+    var player = AVAudioPlayer()
+    var timer = Timer()
+    
+    // MARK: - Methods
+    func startBackgroundTask() {
+        NotificationCenter.default.addObserver(self, selector: #selector(interruptedAudio), name: AVAudioSession.interruptionNotification, object: AVAudioSession.sharedInstance())
+        self.playAudio()
+    }
+    
+    func stopBackgroundTask() {
+        NotificationCenter.default.removeObserver(self, name: AVAudioSession.interruptionNotification, object: nil)
+        player.stop()
+    }
+    
+    @objc fileprivate func interruptedAudio(_ notification: Notification) {
+        if notification.name == AVAudioSession.interruptionNotification && notification.userInfo != nil {
+            var info = notification.userInfo!
+            var intValue = 0
+            (info[AVAudioSessionInterruptionTypeKey]! as AnyObject).getValue(&intValue)
+            if intValue == 1 { playAudio() }
+        }
+    }
+    
+    fileprivate func playAudio() {
+        do {
+            let bundle = Bundle.main.path(forResource: "blank", ofType: "wav")
+            let alertSound = URL(fileURLWithPath: bundle!)
+            try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
+            try AVAudioSession.sharedInstance().setActive(true)
+            try self.player = AVAudioPlayer(contentsOf: alertSound)
+            // Play audio forever by setting num of loops to -1
+            self.player.numberOfLoops = -1
+            self.player.volume = 0.01
+            self.player.prepareToPlay()
+            self.player.play()
+            print("silent audio playing")
+        } catch { print(error)
+        }
+    }
+}

+ 26 - 0
LoopFollow/helpers/Chart.swift

@@ -0,0 +1,26 @@
+//
+//  Chart.swift
+//  LoopFollow
+//
+//  Created by Jon Fawcett on 6/3/20.
+//  Copyright © 2020 Jon Fawcett. All rights reserved.
+//
+
+import Foundation
+import Charts
+
+final class ChartXValueFormatter: IAxisValueFormatter {
+
+    func stringForValue(_ value: Double, axis: AxisBase?) -> String {
+        
+        let dateFormatter = DateFormatter()
+        //let timezoneOffset = TimeZone.current.secondsFromGMT()
+        //let epochTimezoneOffset = value + Double(timezoneOffset)
+        dateFormatter.setLocalizedDateFormatFromTemplate("hh:mm")
+        //let date = Date(timeIntervalSince1970: epochTimezoneOffset)
+        let date = Date(timeIntervalSince1970: value)
+        let formattedDate = dateFormatter.string(from: date)
+
+        return formattedDate
+    }
+}

+ 23 - 0
LoopFollow/helpers/ObservationToken.swift

@@ -0,0 +1,23 @@
+//
+//  ObservationToken.swift
+//  nightguard
+//
+//  Created by Florian Preknya on 1/30/19.
+//  Copyright © 2019 private. All rights reserved.
+//
+
+import Foundation
+
+/// The token received by an observe when subscribes to its subject. The observer can cancel observation, so the subject will remove it from its observers list.
+class ObservationToken {
+    
+    private let cancellationClosure: () -> Void
+    
+    init(cancellationClosure: @escaping () -> Void) {
+        self.cancellationClosure = cancellationClosure
+    }
+    
+    func cancel() {
+        cancellationClosure()
+    }
+}

Разница между файлами не показана из-за своего большого размера
+ 126 - 0
LoopFollow/helpers/UserDefaultsValue.swift


+ 75 - 0
LoopFollow/helpers/UserDefaultsValueGroups.swift

@@ -0,0 +1,75 @@
+//
+//  UserDefaultsValueGroups.swift
+//  nightguard
+//
+//  Created by Florian Preknya on 1/29/19.
+//  Copyright © 2019 private. All rights reserved.
+//
+
+import Foundation
+
+/// UserDefaultValue groups manager class, providing change observation capabilities (keep clients informed when a change occured in a given group).
+class UserDefaultsValueGroups {
+    
+    class func values(from groupName: String) -> [UserDefaultsAnyValue]? {
+        return groupNameToValues[groupName]
+    }
+    
+    class func add(_ value: UserDefaultsAnyValue, to groupName: String) {
+        
+        // add to "value-key to groupNames" dictionary
+        var groupNames = valueKeyToGroupNames[value.key] ?? []
+        guard !groupNames.contains(groupName) else {
+            
+            // already added value to this group!
+            return
+        }
+        
+        groupNames.append(groupName)
+        valueKeyToGroupNames[value.key] = groupNames
+        
+        // add to "groupName to value" dictionary
+        var values = groupNameToValues[groupName] ?? []
+        values.append(value)
+        groupNameToValues[groupName] = values
+    }
+    
+    @discardableResult
+    class func observeChanges(in groupName: String, using closure: @escaping(UserDefaultsAnyValue, String) -> Void) -> ObservationToken {
+        
+        let id = UUID()
+        
+        var observers = groupNameToObservers[groupName] ?? [:]
+        observers[id] = closure
+        groupNameToObservers[groupName] = observers
+        
+        return ObservationToken {
+            groupNameToObservers[groupName]?.removeValue(forKey: id)
+        }
+    }
+    
+    // called by UserDefaultsValue instances when value changes
+    class func valueChanged(_ value: UserDefaultsAnyValue) {
+        valueKeyToGroupNames[value.key]?.forEach() { groupName in
+            notifyValueChanged(value, in: groupName)
+        }
+    }
+    
+    private class func notifyValueChanged(_ value: UserDefaultsAnyValue, in groupName: String) {
+        groupNameToObservers[groupName]?.values.forEach { closure in
+            closure(value, groupName)
+        }
+    }
+    
+    static private var groupNameToValues: [String: [UserDefaultsAnyValue]] = [:]
+    static private var valueKeyToGroupNames: [String: [String]] = [:]
+    static private var groupNameToObservers: [String: [UUID : (UserDefaultsAnyValue, String) -> Void]] = [:]
+}
+
+// user default values group definitions
+extension UserDefaultsValueGroups {
+    struct GroupNames {
+        static let watchSync = "watchSync"
+        static let alarm = "alarm"
+    }
+}

BIN
LoopFollow/helpers/blank.wav


BIN
LoopFollow/helpers/silence.mp3


+ 9 - 0
LoopFollow/repository/UserData.xcdatamodeld/UserData.xcdatamodel/contents

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19E287" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
+    <entity name="Nightscout" representedClassName="Nightscout" syncable="YES" codeGenerationType="class">
+        <attribute name="url" optional="YES" attributeType="String"/>
+    </entity>
+    <elements>
+        <element name="Nightscout" positionX="-63" positionY="-18" width="128" height="58"/>
+    </elements>
+</model>

+ 20 - 0
LoopFollow/repository/UserDefaults.swift

@@ -0,0 +1,20 @@
+//
+//  UserDefaults.swift
+//  LoopFollow
+//
+//  Created by Jon Fawcett on 6/4/20.
+//  Copyright © 2020 Jon Fawcett. All rights reserved.
+//
+
+import Foundation
+
+class UserDefaultsRepository {
+    
+    static let url = UserDefaultsValue<String>(key: "url", default: "")
+    
+    
+    // Graph Settings
+    static let showDots = UserDefaultsValue<Bool>(key: "showDots", default: true)
+    static let showLines = UserDefaultsValue<Bool>(key: "showLines", default: true)
+    
+}