Procházet zdrojové kódy

RemoteView and many other changes

Jonas Björkert před 2 roky
rodič
revize
0eacc40fe8
34 změnil soubory, kde provedl 1556 přidání a 532 odebrání
  1. 37 5
      LoopFollow.xcodeproj/project.pbxproj
  2. 3 0
      LoopFollow/Application/AppDelegate.swift
  3. 94 98
      LoopFollow/Controllers/Alarms.swift
  4. 4 4
      LoopFollow/Controllers/Nightscout/BGData.swift
  5. 24 24
      LoopFollow/Controllers/Nightscout/DeviceStatus.swift
  6. 1 1
      LoopFollow/Controllers/Nightscout/DeviceStatusLoop.swift
  7. 3 1
      LoopFollow/Controllers/Nightscout/DeviceStatusOpenAPS.swift
  8. 3 3
      LoopFollow/Controllers/Nightscout/SAge.swift
  9. 11 1
      LoopFollow/Controllers/Nightscout/Treatments.swift
  10. 81 0
      LoopFollow/Controllers/Nightscout/Treatments/TemporaryTarget.swift
  11. 10 15
      LoopFollow/Controllers/Timers.swift
  12. 26 0
      LoopFollow/Controllers/ViewControllerManager.swift
  13. 34 0
      LoopFollow/Extensions/HKQuantity+AnyConvertible.swift
  14. 22 0
      LoopFollow/Extensions/HKUnit+Extensions.swift
  15. 154 10
      LoopFollow/Remote/RemoteView.swift
  16. 52 1
      LoopFollow/Remote/RemoteViewController.swift
  17. 33 0
      LoopFollow/Remote/TreatmentResponse.swift
  18. 19 0
      LoopFollow/Storage/Observable.swift
  19. 20 0
      LoopFollow/Storage/ObservableUserDefaults.swift
  20. 112 0
      LoopFollow/Storage/ObservableUserDefaultsValue.swift
  21. 26 0
      LoopFollow/Storage/ObservableValue.swift
  22. 0 0
      LoopFollow/Storage/UserData.xcdatamodeld/UserData.xcdatamodel/contents
  23. 21 2
      LoopFollow/repository/UserDefaults.swift
  24. 0 0
      LoopFollow/Storage/UserDefaultsValue.swift
  25. 0 0
      LoopFollow/Storage/UserDefaultsValueGroups.swift
  26. 6 6
      LoopFollow/ViewControllers/AlarmViewController.swift
  27. 1 1
      LoopFollow/ViewControllers/GraphSettingsViewController.swift
  28. 7 4
      LoopFollow/ViewControllers/MainViewController.swift
  29. 1 1
      LoopFollow/ViewControllers/NightScoutViewController.swift
  30. 220 218
      LoopFollow/ViewControllers/SettingsViewController.swift
  31. 91 117
      LoopFollow/ViewControllers/SnoozeViewController.swift
  32. 1 1
      LoopFollow/ViewControllers/WatchSettingsViewController.swift
  33. 107 19
      LoopFollow/helpers/NightscoutUtils.swift
  34. 332 0
      LoopFollow/helpers/TextFieldWithToolBar.swift

+ 37 - 5
LoopFollow.xcodeproj/project.pbxproj

@@ -53,9 +53,17 @@
 		DDCF979C24C14EFB002C9752 /* AdvancedSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCF979B24C14EFB002C9752 /* AdvancedSettingsViewController.swift */; };
 		DDCF979E24C2382A002C9752 /* AppStateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCF979D24C2382A002C9752 /* AppStateController.swift */; };
 		DDCFCAF22B17273200BE5751 /* LoopFollowDisplayNameConfig.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = DDCFCAF12B17273200BE5751 /* LoopFollowDisplayNameConfig.xcconfig */; };
+		DDD10EFF2C510C3C00D76A8E /* ObservableUserDefaultsValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD10EFE2C510C3C00D76A8E /* ObservableUserDefaultsValue.swift */; };
+		DDD10F012C510C6B00D76A8E /* ObservableUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD10F002C510C6B00D76A8E /* ObservableUserDefaults.swift */; };
+		DDD10F032C518A6500D76A8E /* TreatmentResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD10F022C518A6500D76A8E /* TreatmentResponse.swift */; };
+		DDD10F052C529DA200D76A8E /* ObservableValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD10F042C529DA200D76A8E /* ObservableValue.swift */; };
+		DDD10F072C529DE800D76A8E /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD10F062C529DE800D76A8E /* Observable.swift */; };
+		DDD10F0B2C54192A00D76A8E /* TemporaryTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD10F0A2C54192A00D76A8E /* TemporaryTarget.swift */; };
 		DDF2C0102BEFA991007A20E6 /* GitHubService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF2C00F2BEFA991007A20E6 /* GitHubService.swift */; };
 		DDF2C0122BEFB733007A20E6 /* AppVersionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF2C0112BEFB733007A20E6 /* AppVersionManager.swift */; };
 		DDF2C0142BEFD468007A20E6 /* blacklisted-versions.json in Resources */ = {isa = PBXBuildFile; fileRef = DDF2C0132BEFD468007A20E6 /* blacklisted-versions.json */; };
+		DDF699942C555B310058A8D9 /* ViewControllerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF699932C555B310058A8D9 /* ViewControllerManager.swift */; };
+		DDF699962C5582290058A8D9 /* TextFieldWithToolBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF699952C5582290058A8D9 /* TextFieldWithToolBar.swift */; };
 		DDF9676E2AD08C6E00C5EB95 /* SiteChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF9676D2AD08C6E00C5EB95 /* SiteChange.swift */; };
 		FC16A97A24996673003D6245 /* NightScout.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC16A97924996673003D6245 /* NightScout.swift */; };
 		FC16A97B249966A3003D6245 /* AlarmSound.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC7CE589248ABEA3001F83B8 /* AlarmSound.swift */; };
@@ -260,9 +268,17 @@
 		DDCF979B24C14EFB002C9752 /* AdvancedSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsViewController.swift; sourceTree = "<group>"; };
 		DDCF979D24C2382A002C9752 /* AppStateController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateController.swift; sourceTree = "<group>"; };
 		DDCFCAF12B17273200BE5751 /* LoopFollowDisplayNameConfig.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = LoopFollowDisplayNameConfig.xcconfig; sourceTree = "<group>"; };
+		DDD10EFE2C510C3C00D76A8E /* ObservableUserDefaultsValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservableUserDefaultsValue.swift; sourceTree = "<group>"; };
+		DDD10F002C510C6B00D76A8E /* ObservableUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservableUserDefaults.swift; sourceTree = "<group>"; };
+		DDD10F022C518A6500D76A8E /* TreatmentResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreatmentResponse.swift; sourceTree = "<group>"; };
+		DDD10F042C529DA200D76A8E /* ObservableValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservableValue.swift; sourceTree = "<group>"; };
+		DDD10F062C529DE800D76A8E /* Observable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = "<group>"; };
+		DDD10F0A2C54192A00D76A8E /* TemporaryTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemporaryTarget.swift; sourceTree = "<group>"; };
 		DDF2C00F2BEFA991007A20E6 /* GitHubService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitHubService.swift; sourceTree = "<group>"; };
 		DDF2C0112BEFB733007A20E6 /* AppVersionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionManager.swift; sourceTree = "<group>"; };
 		DDF2C0132BEFD468007A20E6 /* blacklisted-versions.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "blacklisted-versions.json"; sourceTree = "<group>"; };
+		DDF699932C555B310058A8D9 /* ViewControllerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerManager.swift; sourceTree = "<group>"; };
+		DDF699952C5582290058A8D9 /* TextFieldWithToolBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldWithToolBar.swift; sourceTree = "<group>"; };
 		DDF9676D2AD08C6E00C5EB95 /* SiteChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteChange.swift; sourceTree = "<group>"; };
 		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>"; };
 		FC16A97924996673003D6245 /* NightScout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightScout.swift; sourceTree = "<group>"; };
@@ -470,6 +486,7 @@
 			children = (
 				DD0C0C6F2C4AFFE800DBADDF /* RemoteViewController.swift */,
 				DD0C0C712C4B000800DBADDF /* RemoteView.swift */,
+				DDD10F022C518A6500D76A8E /* TreatmentResponse.swift */,
 			);
 			path = Remote;
 			sourceTree = "<group>";
@@ -499,6 +516,7 @@
 				DD493AD42ACF2109009A6922 /* ResumePump.swift */,
 				DDF9676D2AD08C6E00C5EB95 /* SiteChange.swift */,
 				DD608A092C23593900F91132 /* SMB.swift */,
+				DDD10F0A2C54192A00D76A8E /* TemporaryTarget.swift */,
 			);
 			path = Treatments;
 			sourceTree = "<group>";
@@ -564,6 +582,7 @@
 				FC1BDD2C24A23204001B652C /* StatsView.swift */,
 				FCA2DDE52501095000254A8C /* Timers.swift */,
 				DD608A0B2C27415C00F91132 /* BackgroundAlertManager.swift */,
+				DDF699932C555B310058A8D9 /* ViewControllerManager.swift */,
 			);
 			path = Controllers;
 			sourceTree = "<group>";
@@ -713,7 +732,7 @@
 				DD98F54224BCEF190007425A /* Extensions */,
 				FC16A9782499657E003D6245 /* Controllers */,
 				FCC688542489367300A0279D /* helpers */,
-				FCC688512489363F00A0279D /* repository */,
+				FCC688512489363F00A0279D /* Storage */,
 				FC16A97624995FEE003D6245 /* Application */,
 			);
 			path = LoopFollow;
@@ -743,14 +762,18 @@
 			name = Products;
 			sourceTree = "<group>";
 		};
-		FCC688512489363F00A0279D /* repository */ = {
+		FCC688512489363F00A0279D /* Storage */ = {
 			isa = PBXGroup;
 			children = (
 				FCC6886624898F8000A0279D /* UserDefaultsValue.swift */,
 				FCC6886824898FB100A0279D /* UserDefaultsValueGroups.swift */,
 				FCC6886424898EEE00A0279D /* UserDefaults.swift */,
+				DDD10EFE2C510C3C00D76A8E /* ObservableUserDefaultsValue.swift */,
+				DDD10F002C510C6B00D76A8E /* ObservableUserDefaults.swift */,
+				DDD10F042C529DA200D76A8E /* ObservableValue.swift */,
+				DDD10F062C529DE800D76A8E /* Observable.swift */,
 			);
-			path = repository;
+			path = Storage;
 			sourceTree = "<group>";
 		};
 		FCC688542489367300A0279D /* helpers */ = {
@@ -773,6 +796,7 @@
 				DD91E4DC2BDEC3F8002D9E97 /* GlucoseConversion.swift */,
 				DDF2C00F2BEFA991007A20E6 /* GitHubService.swift */,
 				DDF2C0112BEFB733007A20E6 /* AppVersionManager.swift */,
+				DDF699952C5582290058A8D9 /* TextFieldWithToolBar.swift */,
 			);
 			path = helpers;
 			sourceTree = "<group>";
@@ -1053,6 +1077,7 @@
 				FC7CE59F248D8D23001F83B8 /* SnoozeViewController.swift in Sources */,
 				DD7FFAFD2A72953000C3A304 /* EKEventStore+Extensions.swift in Sources */,
 				FCC6886724898F8000A0279D /* UserDefaultsValue.swift in Sources */,
+				DDF699942C555B310058A8D9 /* ViewControllerManager.swift in Sources */,
 				DDCF979E24C2382A002C9752 /* AppStateController.swift in Sources */,
 				FC97881E2485969B00A7906C /* NightScoutViewController.swift in Sources */,
 				DD608A0A2C23593900F91132 /* SMB.swift in Sources */,
@@ -1070,6 +1095,8 @@
 				FC16A97B249966A3003D6245 /* AlarmSound.swift in Sources */,
 				DDB0AF522BB1A8BE00AFA48B /* BuildDetails.swift in Sources */,
 				DD0C0C622C4175FD00DBADDF /* NSProfile.swift in Sources */,
+				DDD10F032C518A6500D76A8E /* TreatmentResponse.swift in Sources */,
+				DDD10F0B2C54192A00D76A8E /* TemporaryTarget.swift in Sources */,
 				DD493AE72ACF23CF009A6922 /* DeviceStatus.swift in Sources */,
 				FCFEECA2248857A600402A7F /* SettingsViewController.swift in Sources */,
 				DD13BC792C3FE63A0062313B /* InfoManager.swift in Sources */,
@@ -1092,6 +1119,7 @@
 				DD13BC772C3FD64E0062313B /* InfoData.swift in Sources */,
 				DD13BC752C3FD6210062313B /* InfoType.swift in Sources */,
 				DDCF979C24C14EFB002C9752 /* AdvancedSettingsViewController.swift in Sources */,
+				DDD10EFF2C510C3C00D76A8E /* ObservableUserDefaultsValue.swift in Sources */,
 				FC97881C2485969B00A7906C /* MainViewController.swift in Sources */,
 				DD6A935E2BFA6FA2003FFB8E /* DeviceStatusOpenAPS.swift in Sources */,
 				DD493AD52ACF2109009A6922 /* ResumePump.swift in Sources */,
@@ -1099,6 +1127,7 @@
 				FCC0FAC224922A22003E610E /* DictionaryKeyPath.swift in Sources */,
 				DD493AD72ACF2139009A6922 /* SuspendPump.swift in Sources */,
 				FC9788182485969B00A7906C /* AppDelegate.swift in Sources */,
+				DDD10F072C529DE800D76A8E /* Observable.swift in Sources */,
 				DD608A0C2C27415C00F91132 /* BackgroundAlertManager.swift in Sources */,
 				DD0C0C6B2C48562000DBADDF /* InsulinMetric.swift in Sources */,
 				DD493AD92ACF2171009A6922 /* Carbs.swift in Sources */,
@@ -1107,6 +1136,7 @@
 				DD608A082C1F584900F91132 /* DeviceStatusLoop.swift in Sources */,
 				DD98F54424BCEFEE0007425A /* ShareClientExtension.swift in Sources */,
 				DD0C0C602C415B9D00DBADDF /* ProfileManager.swift in Sources */,
+				DDF699962C5582290058A8D9 /* TextFieldWithToolBar.swift in Sources */,
 				DD91E4DD2BDEC3F8002D9E97 /* GlucoseConversion.swift in Sources */,
 				DD0C0C682C48529400DBADDF /* Metric.swift in Sources */,
 				FCC6886D2489909D00A0279D /* AnyConvertible.swift in Sources */,
@@ -1115,12 +1145,14 @@
 				DD7E198A2ACDA62600DBD158 /* SensorStart.swift in Sources */,
 				FCD49B6C24AA536E007879DC /* DebugViewController.swift in Sources */,
 				FC1BDD2B24A22650001B652C /* Stats.swift in Sources */,
+				DDD10F052C529DA200D76A8E /* ObservableValue.swift in Sources */,
 				FC1BDD2D24A23204001B652C /* StatsView.swift in Sources */,
 				FCE537BC249A4D7D00F80BF8 /* carbBolusArrays.swift in Sources */,
 				FCD2A27D24C9D044009F7B7B /* Globals.swift in Sources */,
 				DD0C0C642C45A59400DBADDF /* HKUnit+Extensions.swift in Sources */,
 				DDCF979A24C14DB4002C9752 /* WatchSettingsViewController.swift in Sources */,
 				DD493AE52ACF2383009A6922 /* Treatments.swift in Sources */,
+				DDD10F012C510C6B00D76A8E /* ObservableUserDefaults.swift in Sources */,
 				FCA2DDE62501095000254A8C /* Timers.swift in Sources */,
 				DD493AE32ACF2358009A6922 /* CAge.swift in Sources */,
 				DD493ADD2ACF21E0009A6922 /* Basals.swift in Sources */,
@@ -1202,7 +1234,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 13.4;
+				IPHONEOS_DEPLOYMENT_TARGET = 15.6;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_FAST_MATH = YES;
 				ONLY_ACTIVE_ARCH = YES;
@@ -1257,7 +1289,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 13.4;
+				IPHONEOS_DEPLOYMENT_TARGET = 15.6;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				MTL_FAST_MATH = YES;
 				SDKROOT = iphoneos;

+ 3 - 0
LoopFollow/Application/AppDelegate.swift

@@ -42,6 +42,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
 
         UNUserNotificationCenter.current().delegate = self
 
+        // Ensure ViewControllerManager is initialized
+        _ = ViewControllerManager.shared
+        
         return true
     }
 

+ 94 - 98
LoopFollow/Controllers/Alarms.swift

@@ -338,8 +338,7 @@ extension MainViewController {
         }
         
         //check for not looping alert
-        if UserDefaultsRepository.url.value != "" {
-            
+        if IsNightscoutEnabled() {
             if UserDefaultsRepository.alertNotLoopingActive.value
                 && !UserDefaultsRepository.alertNotLoopingIsSnoozed.value
                 && (Double(dateTimeUtils.getNowTimeIntervalUTC() - UserDefaultsRepository.alertLastLoopTime.value) >= Double(UserDefaultsRepository.alertNotLooping.value * 60))
@@ -642,105 +641,102 @@ extension MainViewController {
     }
     
     func clearOldSnoozes(){
-          let date = Date()
-          let now = date.timeIntervalSince1970
-          var needsReload: Bool = false
-          guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
-          
-          if date > UserDefaultsRepository.alertSnoozeAllTime.value ?? date {
-              UserDefaultsRepository.alertSnoozeAllTime.setNil(key: "alertSnoozeAllTime")
-              UserDefaultsRepository.alertSnoozeAllIsSnoozed.value = false
-              alarms.reloadSnoozeTime(key: "alertSnoozeAllTime", setNil: true)
-              alarms.reloadIsSnoozed(key: "alertSnoozeAllIsSnoozed", value: false)
-            
-          }
-          
+        let date = Date()
+        let now = date.timeIntervalSince1970
+        guard let alarms = ViewControllerManager.shared.alarmViewController else { return }
+
+        if date > UserDefaultsRepository.alertSnoozeAllTime.value ?? date {
+            UserDefaultsRepository.alertSnoozeAllTime.setNil(key: "alertSnoozeAllTime")
+            UserDefaultsRepository.alertSnoozeAllIsSnoozed.value = false
+            alarms.reloadSnoozeTime(key: "alertSnoozeAllTime", setNil: true)
+            alarms.reloadIsSnoozed(key: "alertSnoozeAllIsSnoozed", value: false)
+        }
+
         if date > UserDefaultsRepository.alertMuteAllTime.value ?? date {
             UserDefaultsRepository.alertMuteAllTime.setNil(key: "alertMuteAllTime")
             UserDefaultsRepository.alertMuteAllIsMuted.value = false
             alarms.reloadMuteTime(key: "alertMuteAllTime", setNil: true)
             alarms.reloadIsMuted(key: "alertMuteAllIsMuted", value: false)
-          
         }
-        
-          if date > UserDefaultsRepository.alertUrgentLowSnoozedTime.value ?? date {
-              UserDefaultsRepository.alertUrgentLowSnoozedTime.setNil(key: "alertUrgentLowSnoozedTime")
-              UserDefaultsRepository.alertUrgentLowIsSnoozed.value = false
-              alarms.reloadSnoozeTime(key: "alertUrgentLowSnoozedTime", setNil: true)
-              alarms.reloadIsSnoozed(key: "alertUrgentLowIsSnoozed", value: false)
-            
-          }
-          if date > UserDefaultsRepository.alertLowSnoozedTime.value ?? date {
-              UserDefaultsRepository.alertLowSnoozedTime.setNil(key: "alertLowSnoozedTime")
-              UserDefaultsRepository.alertLowIsSnoozed.value = false
-              alarms.reloadSnoozeTime(key: "alertLowSnoozedTime", setNil: true)
-              alarms.reloadIsSnoozed(key: "alertLowIsSnoozed", value: false)
-         
-          }
-          if date > UserDefaultsRepository.alertHighSnoozedTime.value ?? date {
-              UserDefaultsRepository.alertHighSnoozedTime.setNil(key: "alertHighSnoozedTime")
-              UserDefaultsRepository.alertHighIsSnoozed.value = false
-              alarms.reloadSnoozeTime(key: "alertHighSnoozedTime", setNil: true)
-              alarms.reloadIsSnoozed(key: "alertHighIsSnoozed", value: false)
-            
-          }
-          if date > UserDefaultsRepository.alertUrgentHighSnoozedTime.value ?? date {
-              UserDefaultsRepository.alertUrgentHighSnoozedTime.setNil(key: "alertUrgentHighSnoozedTime")
-              UserDefaultsRepository.alertUrgentHighIsSnoozed.value = false
-              alarms.reloadSnoozeTime(key: "alertUrgentHighSnoozedTime", setNil: true)
-              alarms.reloadIsSnoozed(key: "alertUrgentHighIsSnoozed", value: false)
-            
-          }
-          if date > UserDefaultsRepository.alertFastDropSnoozedTime.value ?? date {
-              UserDefaultsRepository.alertFastDropSnoozedTime.setNil(key: "alertFastDropSnoozedTime")
-              UserDefaultsRepository.alertFastDropIsSnoozed.value = false
-              alarms.reloadSnoozeTime(key: "alertFastDropSnoozedTime", setNil: true)
-              alarms.reloadIsSnoozed(key: "alertFastDropIsSnoozed", value: false)
-             
-          }
-          if date > UserDefaultsRepository.alertFastRiseSnoozedTime.value ?? date {
-              UserDefaultsRepository.alertFastRiseSnoozedTime.setNil(key: "alertFastRiseSnoozedTime")
-              UserDefaultsRepository.alertFastRiseIsSnoozed.value = false
-              alarms.reloadSnoozeTime(key: "alertFastRiseSnoozedTime", setNil: true)
-              alarms.reloadIsSnoozed(key: "alertFastRiseIsSnoozed", value: false)
-             
-          }
-          if date > UserDefaultsRepository.alertMissedReadingSnoozedTime.value ?? date {
-              UserDefaultsRepository.alertMissedReadingSnoozedTime.setNil(key: "alertMissedReadingSnoozedTime")
-              UserDefaultsRepository.alertMissedReadingIsSnoozed.value = false
-              alarms.reloadSnoozeTime(key: "alertMissedReadingSnoozedTime", setNil: true)
-              alarms.reloadIsSnoozed(key: "alertMissedReadingIsSnoozed", value: false)
-            
-          }
-          if date > UserDefaultsRepository.alertNotLoopingSnoozedTime.value ?? date {
-              UserDefaultsRepository.alertNotLoopingSnoozedTime.setNil(key: "alertNotLoopingSnoozedTime")
-              UserDefaultsRepository.alertNotLoopingIsSnoozed.value = false
-              alarms.reloadSnoozeTime(key: "alertNotLoopingSnoozedTime", setNil: true)
-              alarms.reloadIsSnoozed(key: "alertNotLoopingIsSnoozed", value: false)
-           
-              
-          }
-          if date > UserDefaultsRepository.alertMissedBolusSnoozedTime.value ?? date {
-              UserDefaultsRepository.alertMissedBolusSnoozedTime.setNil(key: "alertMissedBolusSnoozedTime")
-              UserDefaultsRepository.alertMissedBolusIsSnoozed.value = false
-              alarms.reloadSnoozeTime(key: "alertMissedBolusSnoozedTime", setNil: true)
-              alarms.reloadIsSnoozed(key: "alertMissedBolusIsSnoozed", value: false)
 
-          }
-          if date > UserDefaultsRepository.alertSAGESnoozedTime.value ?? date {
-              UserDefaultsRepository.alertSAGESnoozedTime.setNil(key: "alertSAGESnoozedTime")
-              UserDefaultsRepository.alertSAGEIsSnoozed.value = false
-              alarms.reloadSnoozeTime(key: "alertSAGESnoozedTime", setNil: true)
-              alarms.reloadIsSnoozed(key: "alertSAGEIsSnoozed", value: false)
-    
-          }
-          if date > UserDefaultsRepository.alertCAGESnoozedTime.value ?? date {
-              UserDefaultsRepository.alertCAGESnoozedTime.setNil(key: "alertCAGESnoozedTime")
-              UserDefaultsRepository.alertCAGEIsSnoozed.value = false
-              alarms.reloadSnoozeTime(key: "alertCAGESnoozedTime", setNil: true)
-              alarms.reloadIsSnoozed(key: "alertCAGEIsSnoozed", value: false)
+        if date > UserDefaultsRepository.alertUrgentLowSnoozedTime.value ?? date {
+            UserDefaultsRepository.alertUrgentLowSnoozedTime.setNil(key: "alertUrgentLowSnoozedTime")
+            UserDefaultsRepository.alertUrgentLowIsSnoozed.value = false
+            alarms.reloadSnoozeTime(key: "alertUrgentLowSnoozedTime", setNil: true)
+            alarms.reloadIsSnoozed(key: "alertUrgentLowIsSnoozed", value: false)
+
+        }
+        if date > UserDefaultsRepository.alertLowSnoozedTime.value ?? date {
+            UserDefaultsRepository.alertLowSnoozedTime.setNil(key: "alertLowSnoozedTime")
+            UserDefaultsRepository.alertLowIsSnoozed.value = false
+            alarms.reloadSnoozeTime(key: "alertLowSnoozedTime", setNil: true)
+            alarms.reloadIsSnoozed(key: "alertLowIsSnoozed", value: false)
+
+        }
+        if date > UserDefaultsRepository.alertHighSnoozedTime.value ?? date {
+            UserDefaultsRepository.alertHighSnoozedTime.setNil(key: "alertHighSnoozedTime")
+            UserDefaultsRepository.alertHighIsSnoozed.value = false
+            alarms.reloadSnoozeTime(key: "alertHighSnoozedTime", setNil: true)
+            alarms.reloadIsSnoozed(key: "alertHighIsSnoozed", value: false)
+
+        }
+        if date > UserDefaultsRepository.alertUrgentHighSnoozedTime.value ?? date {
+            UserDefaultsRepository.alertUrgentHighSnoozedTime.setNil(key: "alertUrgentHighSnoozedTime")
+            UserDefaultsRepository.alertUrgentHighIsSnoozed.value = false
+            alarms.reloadSnoozeTime(key: "alertUrgentHighSnoozedTime", setNil: true)
+            alarms.reloadIsSnoozed(key: "alertUrgentHighIsSnoozed", value: false)
+
+        }
+        if date > UserDefaultsRepository.alertFastDropSnoozedTime.value ?? date {
+            UserDefaultsRepository.alertFastDropSnoozedTime.setNil(key: "alertFastDropSnoozedTime")
+            UserDefaultsRepository.alertFastDropIsSnoozed.value = false
+            alarms.reloadSnoozeTime(key: "alertFastDropSnoozedTime", setNil: true)
+            alarms.reloadIsSnoozed(key: "alertFastDropIsSnoozed", value: false)
+
+        }
+        if date > UserDefaultsRepository.alertFastRiseSnoozedTime.value ?? date {
+            UserDefaultsRepository.alertFastRiseSnoozedTime.setNil(key: "alertFastRiseSnoozedTime")
+            UserDefaultsRepository.alertFastRiseIsSnoozed.value = false
+            alarms.reloadSnoozeTime(key: "alertFastRiseSnoozedTime", setNil: true)
+            alarms.reloadIsSnoozed(key: "alertFastRiseIsSnoozed", value: false)
+
+        }
+        if date > UserDefaultsRepository.alertMissedReadingSnoozedTime.value ?? date {
+            UserDefaultsRepository.alertMissedReadingSnoozedTime.setNil(key: "alertMissedReadingSnoozedTime")
+            UserDefaultsRepository.alertMissedReadingIsSnoozed.value = false
+            alarms.reloadSnoozeTime(key: "alertMissedReadingSnoozedTime", setNil: true)
+            alarms.reloadIsSnoozed(key: "alertMissedReadingIsSnoozed", value: false)
+
+        }
+        if date > UserDefaultsRepository.alertNotLoopingSnoozedTime.value ?? date {
+            UserDefaultsRepository.alertNotLoopingSnoozedTime.setNil(key: "alertNotLoopingSnoozedTime")
+            UserDefaultsRepository.alertNotLoopingIsSnoozed.value = false
+            alarms.reloadSnoozeTime(key: "alertNotLoopingSnoozedTime", setNil: true)
+            alarms.reloadIsSnoozed(key: "alertNotLoopingIsSnoozed", value: false)
+
+
+        }
+        if date > UserDefaultsRepository.alertMissedBolusSnoozedTime.value ?? date {
+            UserDefaultsRepository.alertMissedBolusSnoozedTime.setNil(key: "alertMissedBolusSnoozedTime")
+            UserDefaultsRepository.alertMissedBolusIsSnoozed.value = false
+            alarms.reloadSnoozeTime(key: "alertMissedBolusSnoozedTime", setNil: true)
+            alarms.reloadIsSnoozed(key: "alertMissedBolusIsSnoozed", value: false)
+
+        }
+        if date > UserDefaultsRepository.alertSAGESnoozedTime.value ?? date {
+            UserDefaultsRepository.alertSAGESnoozedTime.setNil(key: "alertSAGESnoozedTime")
+            UserDefaultsRepository.alertSAGEIsSnoozed.value = false
+            alarms.reloadSnoozeTime(key: "alertSAGESnoozedTime", setNil: true)
+            alarms.reloadIsSnoozed(key: "alertSAGEIsSnoozed", value: false)
+
+        }
+        if date > UserDefaultsRepository.alertCAGESnoozedTime.value ?? date {
+            UserDefaultsRepository.alertCAGESnoozedTime.setNil(key: "alertCAGESnoozedTime")
+            UserDefaultsRepository.alertCAGEIsSnoozed.value = false
+            alarms.reloadSnoozeTime(key: "alertCAGESnoozedTime", setNil: true)
+            alarms.reloadIsSnoozed(key: "alertCAGEIsSnoozed", value: false)
 
-          }
+        }
         if date > UserDefaultsRepository.alertOverrideStartSnoozedTime.value ?? date {
             UserDefaultsRepository.alertOverrideStartSnoozedTime.setNil(key: "alertOverrideStartSnoozedTime")
             UserDefaultsRepository.alertOverrideStartIsSnoozed.value = false
@@ -754,7 +750,7 @@ extension MainViewController {
             alarms.reloadIsSnoozed(key: "alertOverrideEndIsSnoozed", value: false)
 
         }
-        
+
         if date > UserDefaultsRepository.alertPumpSnoozedTime.value ?? date {
             UserDefaultsRepository.alertPumpSnoozedTime.setNil(key: "alertPumpSnoozedTime")
             UserDefaultsRepository.alertPumpIsSnoozed.value = false
@@ -762,7 +758,7 @@ extension MainViewController {
             alarms.reloadIsSnoozed(key: "alertPumpIsSnoozed", value: false)
 
         }
-        
+
         if date > UserDefaultsRepository.alertIOBSnoozedTime.value ?? date {
             UserDefaultsRepository.alertIOBSnoozedTime.setNil(key: "alertIOBSnoozedTime")
             UserDefaultsRepository.alertIOBIsSnoozed.value = false
@@ -770,7 +766,7 @@ extension MainViewController {
             alarms.reloadIsSnoozed(key: "alertIOBIsSnoozed", value: false)
 
         }
-        
+
         if date > UserDefaultsRepository.alertCOBSnoozedTime.value ?? date {
             UserDefaultsRepository.alertCOBSnoozedTime.setNil(key: "alertCOBSnoozedTime")
             UserDefaultsRepository.alertCOBIsSnoozed.value = false
@@ -778,7 +774,7 @@ extension MainViewController {
             alarms.reloadIsSnoozed(key: "alertCOBIsSnoozed", value: false)
 
         }
-        
+
         if date > UserDefaultsRepository.alertBatterySnoozedTime.value ?? date {
             UserDefaultsRepository.alertBatterySnoozedTime.setNil(key: "alertBatterySnoozedTime")
             UserDefaultsRepository.alertBatteryIsSnoozed.value = false
@@ -793,7 +789,7 @@ extension MainViewController {
             alarms.reloadIsSnoozed(key: "alertRecBolusIsSnoozed", value: false)
         }
     }
-    
+
     func checkQuietHours() {
         if UserDefaultsRepository.quietHourStart.value == nil || UserDefaultsRepository.quietHourEnd.value == nil { return }
         

+ 4 - 4
LoopFollow/Controllers/Nightscout/BGData.swift

@@ -21,7 +21,7 @@ extension MainViewController {
                 print("Error fetching Dexcom data: \(error.localizedDescription)")
                 
                 // If we get an error, immediately try to pull NS BG Data
-                if UserDefaultsRepository.url.value != "" {
+                if IsNightscoutEnabled() {
                     self.webLoadNSBGData()
                 }
                 return
@@ -35,14 +35,14 @@ extension MainViewController {
             // If Dex data is old, load from NS instead
             let latestDate = data[0].date
             let now = dateTimeUtils.getNowTimeIntervalUTC()
-            if (latestDate + 330) < now && UserDefaultsRepository.url.value != "" {
+            if (latestDate + 330) < now && IsNightscoutEnabled() {
                 self.webLoadNSBGData()
                 print("Dex data is old, loading from NS instead")
                 return
             }
             
             // Dexcom only returns 24 hrs of data. If we need more, call NS.
-            if graphHours > 24 && UserDefaultsRepository.url.value != "" {
+            if graphHours > 24 && IsNightscoutEnabled() {
                 self.webLoadNSBGData(dexData: data)
             } else {
                 self.ProcessDexBGData(data: data, sourceName: "Dexcom")
@@ -55,7 +55,7 @@ extension MainViewController {
         if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Download: BG") }
         
         // This kicks it out in the instance where dexcom fails but they aren't using NS &&
-        if UserDefaultsRepository.url.value == "" {
+        if !IsNightscoutEnabled() {
             self.startBGTimer(time: 10)
             return
         }

+ 24 - 24
LoopFollow/Controllers/Nightscout/DeviceStatus.swift

@@ -120,36 +120,36 @@ extension MainViewController {
         // Loop - handle new data
         if let lastLoopRecord = lastDeviceStatus?["loop"] as! [String : AnyObject]? {
             DeviceStatusLoop(formatter: formatter, lastLoopRecord: lastLoopRecord)
+
+            var oText = ""
+            currentOverride = 1.0
+            if let lastOverride = lastDeviceStatus?["override"] as? [String: AnyObject],
+               let isActive = lastOverride["active"] as? Bool, isActive {
+                if let lastCorrection = lastOverride["currentCorrectionRange"] as? [String: AnyObject],
+                   let minValue = lastCorrection["minValue"] as? Double,
+                   let maxValue = lastCorrection["maxValue"] as? Double {
+
+                    if let multiplier = lastOverride["multiplier"] as? Double {
+                        currentOverride = multiplier
+                        oText += String(format: "%.0f%%", (multiplier * 100))
+                    } else {
+                        oText += "100%"
+                    }
+
+                    oText += " ("
+                    oText += Localizer.toDisplayUnits(String(minValue)) + "-" + Localizer.toDisplayUnits(String(maxValue)) + ")"
+                }
+
+                infoManager.updateInfoData(type: .override, value: oText)
+            } else {
+                infoManager.clearInfoData(type: .override)
+            }
         }
 
         // OpenAPS - handle new data
         if let lastLoopRecord = lastDeviceStatus?["openaps"] as! [String : AnyObject]? {
             DeviceStatusOpenAPS(formatter: formatter, lastDeviceStatus: lastDeviceStatus, lastLoopRecord: lastLoopRecord)
         }
-        
-        var oText = ""
-        currentOverride = 1.0
-        if let lastOverride = lastDeviceStatus?["override"] as? [String: AnyObject],
-           let isActive = lastOverride["active"] as? Bool, isActive {
-            if let lastCorrection = lastOverride["currentCorrectionRange"] as? [String: AnyObject],
-               let minValue = lastCorrection["minValue"] as? Double,
-               let maxValue = lastCorrection["maxValue"] as? Double {
-                
-                if let multiplier = lastOverride["multiplier"] as? Double {
-                    currentOverride = multiplier
-                    oText += String(format: "%.0f%%", (multiplier * 100))
-                } else {
-                    oText += "100%"
-                }
-                
-                oText += " ("
-                oText += Localizer.toDisplayUnits(String(minValue)) + "-" + Localizer.toDisplayUnits(String(maxValue)) + ")"
-            }
-            
-            infoManager.updateInfoData(type: .override, value: oText)
-        } else {
-            infoManager.clearInfoData(type: .override)
-        }
 
         // Start the timer based on the timestamp
         let now = dateTimeUtils.getNowTimeIntervalUTC()

+ 1 - 1
LoopFollow/Controllers/Nightscout/DeviceStatusLoop.swift

@@ -13,7 +13,7 @@ import HealthKit
 
 extension MainViewController {
     func DeviceStatusLoop(formatter: ISO8601DateFormatter, lastLoopRecord: [String: AnyObject]) {
-        
+        ObservableUserDefaults.shared.device.value = "Loop"
         if let lastLoopTime = formatter.date(from: (lastLoopRecord["timestamp"] as! String))?.timeIntervalSince1970  {
             UserDefaultsRepository.alertLastLoopTime.value = lastLoopTime
             if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "lastLoopTime: " + String(lastLoopTime)) }

+ 3 - 1
LoopFollow/Controllers/Nightscout/DeviceStatusOpenAPS.swift

@@ -9,8 +9,10 @@ import HealthKit
 
 extension MainViewController {
     func DeviceStatusOpenAPS(formatter: ISO8601DateFormatter, lastDeviceStatus: [String: AnyObject]?, lastLoopRecord: [String: AnyObject]) {
-        if let lastLoopTime = formatter.date(from: (lastDeviceStatus?["created_at"] as! String))?.timeIntervalSince1970 {
+        if let createdAtString = lastDeviceStatus?["created_at"] as? String,
+           let lastLoopTime = formatter.date(from: createdAtString)?.timeIntervalSince1970 {
             UserDefaultsRepository.alertLastLoopTime.value = lastLoopTime
+            ObservableUserDefaults.shared.device.value = lastDeviceStatus?["device"] as? String ?? ""
             if lastLoopRecord["failureReason"] != nil {
                 LoopStatusLabel.text = "X"
                 latestLoopStatusString = "X"

+ 3 - 3
LoopFollow/Controllers/Nightscout/SAge.swift

@@ -50,15 +50,15 @@ extension MainViewController {
                                    .withColonSeparatorInTime]
         UserDefaultsRepository.alertSageInsertTime.value = formatter.date(from: (lastSageString))?.timeIntervalSince1970 as! TimeInterval
         
-        if UserDefaultsRepository.alertAutoSnoozeCGMStart.value && (dateTimeUtils.getNowTimeIntervalUTC() - UserDefaultsRepository.alertSageInsertTime.value < 7200){
+        if UserDefaultsRepository.alertAutoSnoozeCGMStart.value && (dateTimeUtils.getNowTimeIntervalUTC() - UserDefaultsRepository.alertSageInsertTime.value < 7200) {
             let snoozeTime = Date(timeIntervalSince1970: UserDefaultsRepository.alertSageInsertTime.value + 7200)
             UserDefaultsRepository.alertSnoozeAllTime.value = snoozeTime
             UserDefaultsRepository.alertSnoozeAllIsSnoozed.value = true
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            guard let alarms = ViewControllerManager.shared.alarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertSnoozeAllIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertSnoozeAllTime", setNil: false, value: snoozeTime)
         }
-        
+
         if let sageTime = formatter.date(from: (lastSageString as! String))?.timeIntervalSince1970 {
             let now = dateTimeUtils.getNowTimeIntervalUTC()
             let secondsAgo = now - sageTime

+ 11 - 1
LoopFollow/Controllers/Nightscout/Treatments.swift

@@ -44,6 +44,7 @@ extension MainViewController {
         var smb: [[String:AnyObject]] = []
         var carbs: [[String:AnyObject]] = []
         var temporaryOverride: [[String:AnyObject]] = []
+        var temporaryTarget: [[String:AnyObject]] = []
         var note: [[String:AnyObject]] = []
         var bgCheck: [[String:AnyObject]] = []
         var suspendPump: [[String:AnyObject]] = []
@@ -72,8 +73,10 @@ extension MainViewController {
                 bolus.append(entry)
             case "Carb Correction":
                 carbs.append(entry)
-            case "Temporary Override", "Temporary Target":
+            case "Temporary Override":
                 temporaryOverride.append(entry)
+            case "Temporary Target":
+                temporaryTarget.append(entry)
             case "Note":
                 note.append(entry)
                 print("Note: \(String(describing: entry))")
@@ -141,6 +144,13 @@ extension MainViewController {
                 clearOldOverride()
             }
         }
+        if temporaryTarget.count > 0 {
+            processNSTemporaryTarget(entries: temporaryTarget)
+        } else {
+            if overrideGraphData.count > 0 {
+                clearOldOverride() //Temporary Targets are plotted as override for now
+            }
+        }
         if suspendPump.count > 0 {
             processSuspendPump(entries: suspendPump)
         } else {

+ 81 - 0
LoopFollow/Controllers/Nightscout/Treatments/TemporaryTarget.swift

@@ -0,0 +1,81 @@
+//
+//  TemporaryTarget.swift
+//  LoopFollow
+//
+//  Created by Jonas Björkert on 2024-07-26.
+//  Copyright © 2024 Jon Fawcett. All rights reserved.
+//
+
+import Foundation
+import UIKit
+import HealthKit
+
+extension MainViewController {
+    // NS Temporary Target Response Processor
+    func processNSTemporaryTarget(entries: [[String: AnyObject]]) {
+        overrideGraphData.removeAll()
+        var activeTempTarget: Int? = nil
+
+        entries.reversed().enumerated().forEach { (index, currentEntry) in
+            print("\(currentEntry)")
+            guard let dateStr = currentEntry["timestamp"] as? String ?? currentEntry["created_at"] as? String else { return }
+            guard let parsedDate = NightscoutUtils.parseDate(dateStr) else { return }
+
+            var dateTimeStamp = parsedDate.timeIntervalSince1970
+            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            if dateTimeStamp < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) {
+                dateTimeStamp = dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours)
+            }
+
+            var duration: Double = 5.0
+            duration = (currentEntry["duration"] as? Double ?? 5.0) * 60
+
+            // If duration is 0, this marks the end of the last temp target
+            if duration == 0 {
+                // Check for an active temporary target
+                if let activeIndex = overrideGraphData.lastIndex(where: { $0.endDate > dateTimeStamp }) {
+                    // Update the end date of the active temporary target
+                    overrideGraphData[activeIndex].endDate = dateTimeStamp
+                    activeTempTarget = nil
+                }
+                return
+            }
+
+            // Ignore very short durations
+            if duration < 300 { return }
+
+            guard let enteredBy = currentEntry["enteredBy"] as? String, let reason = currentEntry["reason"] as? String else { return }
+
+            var range: [Int] = []
+            let low = currentEntry["targetBottom"] as? Int
+            let high = currentEntry["targetTop"] as? Int
+            if (low == nil && high != nil) || (low != nil && high == nil) { return }
+            range = [low ?? 0, high ?? 0]
+
+            let endDate = dateTimeStamp + duration
+
+            let dot = DataStructs.overrideStruct(insulNeedsScaleFactor: 1.0, date: dateTimeStamp, endDate: endDate, duration: duration, correctionRange: range, enteredBy: enteredBy, reason: reason, sgv: -20)
+            overrideGraphData.append(dot)
+
+            // Set activeTempTarget only if it is still active
+            if Date().timeIntervalSince1970 < endDate {
+                activeTempTarget = low
+            }
+        }
+
+        if UserDefaultsRepository.graphOtherTreatments.value {
+            updateOverrideGraph()
+        }
+
+        if let target = activeTempTarget {
+            let unit = HKUnit.milligramsPerDeciliter
+            let quantity = HKQuantity(unit: unit, doubleValue: Double(target))
+            Observable.shared.tempTarget.value = quantity
+        } else {
+            Observable.shared.tempTarget.value = nil
+        }
+
+        let isActiveTempTarget = activeTempTarget != nil
+        print("Is there an active temporary target? \(isActiveTempTarget)")
+    }
+}

+ 10 - 15
LoopFollow/Controllers/Timers.swift

@@ -144,7 +144,7 @@ extension MainViewController {
     
     @objc func bgTimerDidEnd(_ timer:Timer) {
         // reset timer to 1 minute if settings aren't entered
-        if UserDefaultsRepository.shareUserName.value == "" && UserDefaultsRepository.sharePassword.value == "" && UserDefaultsRepository.url.value == "" {
+        if UserDefaultsRepository.shareUserName.value == "" && UserDefaultsRepository.sharePassword.value == "" && !IsNightscoutEnabled() {
             startBGTimer(time: 60)
             return
         }
@@ -174,12 +174,10 @@ extension MainViewController {
     @objc func deviceStatusTimerDidEnd(_ timer:Timer) {
         
         // reset timer to 1 minute if settings aren't entered
-        if UserDefaultsRepository.url.value == "" {
+        if !IsNightscoutEnabled() {
             startDeviceStatusTimer(time: 60)
             return
-        }
-        
-        if UserDefaultsRepository.url.value != "" {
+        } else {
             webLoadNSDeviceStatus()
         }
     }
@@ -196,18 +194,16 @@ extension MainViewController {
     }
     
     @objc func treatmentsTimerDidEnd(_ timer:Timer) {
-        
-        
         // reset timer to 1 minute if settings aren't entered
-        if UserDefaultsRepository.url.value == "" {
+        if !IsNightscoutEnabled() {
             startTreatmentsTimer(time: 60)
             return
         }
         
-        if UserDefaultsRepository.url.value != "" && UserDefaultsRepository.downloadTreatments.value {
-                WebLoadNSTreatments()
-            }
-            startTreatmentsTimer()
+        if IsNightscoutEnabled() && UserDefaultsRepository.downloadTreatments.value {
+            WebLoadNSTreatments()
+        }
+        startTreatmentsTimer()
     }
     
     // Profile Timer
@@ -222,14 +218,13 @@ extension MainViewController {
     }
     
     @objc func profileTimerDidEnd(_ timer:Timer) {
-        
         // reset timer to 1 minute if settings aren't entered
-        if UserDefaultsRepository.url.value == "" {
+        if !IsNightscoutEnabled() {
             startProfileTimer(time: 60)
             return
         }
         
-        if !isStaleData() && UserDefaultsRepository.url.value != "" {
+        if !isStaleData() && IsNightscoutEnabled() {
             webLoadNSProfile()
             startProfileTimer()
         }

+ 26 - 0
LoopFollow/Controllers/ViewControllerManager.swift

@@ -0,0 +1,26 @@
+//
+//  ViewControllerManager.swift
+//  LoopFollow
+//
+//  Created by Jonas Björkert on 2024-07-27.
+//  Copyright © 2024 Jon Fawcett. All rights reserved.
+//
+
+import Foundation
+import UIKit
+
+class ViewControllerManager {
+
+    static let shared = ViewControllerManager()
+
+    var alarmViewController: AlarmViewController?
+
+    private init() {
+        instantiateAlarmViewController()
+    }
+
+    private func instantiateAlarmViewController() {
+        let storyboard = UIStoryboard(name: "Main", bundle: nil)
+        self.alarmViewController = storyboard.instantiateViewController(withIdentifier: "AlarmViewController") as? AlarmViewController
+    }
+}

+ 34 - 0
LoopFollow/Extensions/HKQuantity+AnyConvertible.swift

@@ -0,0 +1,34 @@
+//
+//  HKQuantity+AnyConvertible.swift
+//  nightguard
+//
+//  Created by Jonas Björkert on 2024-07-24.
+//
+
+import HealthKit
+
+class HKQuantityWrapper: AnyConvertible {
+    let quantity: HKQuantity
+
+    init(quantity: HKQuantity) {
+        self.quantity = quantity
+    }
+
+    func toAny() -> Any {
+        return ["unit": UserDefaultsRepository.getPreferredUnit().unitString,
+                "value": quantity.doubleValue(for: UserDefaultsRepository.getPreferredUnit())]
+    }
+
+    static func fromAny(_ anyValue: Any) -> HKQuantityWrapper? {
+        // Convert dictionary back to HKQuantity
+        guard let dict = anyValue as? [String: Any],
+              let unitString = dict["unit"] as? String,
+              let value = dict["value"] as? Double else {
+            return nil
+        }
+
+        let unit = HKUnit(from: unitString)
+        let quantity = HKQuantity(unit: unit, doubleValue: value)
+        return HKQuantityWrapper(quantity: quantity)
+    }
+}

+ 22 - 0
LoopFollow/Extensions/HKUnit+Extensions.swift

@@ -17,4 +17,26 @@ extension HKUnit {
     public static let millimolesPerLiter: HKUnit = {
         return HKUnit.moleUnit(with: .milli, molarMass: HKUnitMolarMassBloodGlucose).unitDivided(by: .liter())
     }()
+
+    var preferredFractionDigits: Int {
+        if self == .milligramsPerDeciliter {
+            return 0
+        } else {
+            return 1
+        }
+    }
+
+    var localizedShortUnitString: String {
+        if self == HKUnit.millimolesPerLiter {
+            return NSLocalizedString("mmol/L", comment: "The short unit display string for millimoles of glucose per liter")
+        } else if self == .milligramsPerDeciliter {
+            return NSLocalizedString("mg/dL", comment: "The short unit display string for milligrams of glucose per decilter")
+        } else if self == .internationalUnit() {
+            return NSLocalizedString("U", comment: "The short unit display string for international units of insulin")
+        } else if self == .gram() {
+            return NSLocalizedString("g", comment: "The short unit display string for grams")
+        } else {
+            return String(describing: self)
+        }
+    }
 }

+ 154 - 10
LoopFollow/Remote/RemoteView.swift

@@ -6,24 +6,168 @@
 //  Copyright © 2024 Jon Fawcett. All rights reserved.
 //
 
-import Foundation
 import SwiftUI
+import HealthKit
 
 struct RemoteView: View {
+    @ObservedObject var nightscoutURL = ObservableUserDefaults.shared.url
+    @ObservedObject var device = ObservableUserDefaults.shared.device
+    @ObservedObject var nsWriteAuth = ObservableUserDefaults.shared.nsWriteAuth
+    @ObservedObject var tempTarget = Observable.shared.tempTarget
+    @ObservedObject var statusMessage = Observable.shared.statusMessage
+
+    @State private var newTarget: Double = 0
+    @State private var newHKTarget = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 0.0)
+    @State private var duration: Double = 0
+    @State private var intDuration: Int = 0
+    @State private var showConfirmation: Bool = false
+    @State private var showCheckmark: Bool = false
+
+    var onRefreshStatus: () -> Void
+    var onCancelExistingTarget: () -> Void
+    var sendTempTarget: (HKQuantity, Int) -> Void
+
     var body: some View {
-        VStack {
-            Text("Remote Control")
-                .font(.largeTitle)
-                .padding()
+        NavigationView {
+            VStack {
+                if nightscoutURL.value.isEmpty {
+                    ErrorMessageView(
+                        message: "Remote commands are currently only available for Trio. It requires you to enter your Nightscout address and a token with the careportal role in the settings."
+                    )
+                } else if device.value != "Trio" {
+                    ErrorMessageView(
+                        message: "Remote commands are currently only available for Trio."
+                    )
+                } else if !nsWriteAuth.value {
+                    ErrorMessageView(
+                        message: "Please enter a valid token with appropriate permissions."
+                    )
+                } else {
+                    Form {
+                        if let tempTargetValue = tempTarget.value {
+                            Section(header: Text("Existing Temp Target")) {
+                                HStack {
+                                    Text("Current Target: \(Localizer.formatQuantity(tempTargetValue)) mg/dL")
+                                    Spacer()
+                                    Button(action: onCancelExistingTarget) {
+                                        Text("Cancel")
+                                            .foregroundColor(.red)
+                                    }
+                                }
+                            }
+                        }
+
+                        Section(header: Text("Temporary Target")) {
+                            HStack {
+                                Text("Target")
+                                Spacer()
+                                TextFieldWithToolBar(text: $newTarget, placeholder: "0", numberFormatter: glucoseFormatter)
+                                Text(UserDefaultsRepository.getPreferredUnit().localizedShortUnitString).foregroundColor(.secondary)
+                            }
+                            HStack {
+                                Text("Duration")
+                                Spacer()
+                                TextFieldWithToolBar(text: $duration, placeholder: "0", numberFormatter: formatter)
+                                Text("minutes").foregroundColor(.secondary)
+                            }
+                            HStack {
+                                Button {
+                                    newHKTarget = HKQuantity(unit: UserDefaultsRepository.getPreferredUnit(), doubleValue: newTarget)
+                                    intDuration = Int(duration)
+                                    showConfirmation = true
+                                }
+                            label: { Text("Enact") }
+                                    .disabled(newTarget == 0 || duration == 0)
+                                    .buttonStyle(BorderlessButtonStyle())
+                                    .font(.callout)
+                                    .controlSize(.mini)
+                            }
+                        }
+
+                        .alert(isPresented: $showConfirmation) {
+                            Alert(
+                                title: Text("Confirm Command"),
+                                message: Text("New Target: \(Localizer.formatQuantity(newHKTarget)) \(UserDefaultsRepository.getPreferredUnit().localizedShortUnitString)\nDuration: \(intDuration) minutes"),
+                                primaryButton: .default(Text("Confirm"), action: {
+                                    sendTempTarget(newHKTarget , intDuration)
+                                }),
+                                secondaryButton: .cancel()
+                            )
+                        }
+                    }
+                    .navigationBarItems(trailing: Button(action: onRefreshStatus) {
+                        Image(systemName: "arrow.clockwise")
+                    })
+                    .padding()
+
+                    if !statusMessage.value.isEmpty {
+                        Text(statusMessage.value)
+                            .foregroundColor(.green)
+                            .padding()
+                    }
+                }
+            }
+            .padding()
+            .navigationTitle("Remote")
+            .navigationBarTitleDisplayMode(.inline)
+        }
+    }
 
-            // Your remote control UI components go here
-            Spacer()
+    private var formatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 0
+        return formatter
+    }
+
+    private var glucoseFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 0
+        if UserDefaultsRepository.getPreferredUnit() == .millimolesPerLiter {
+            formatter.maximumFractionDigits = 1
         }
+        formatter.roundingMode = .halfUp
+        return formatter
     }
+
 }
 
-struct RemoteView_Previews: PreviewProvider {
-    static var previews: some View {
-        RemoteView()
+struct ErrorMessageView: View {
+    var message: String
+    var buttonTitle: String?
+    var buttonAction: (() -> Void)?
+
+    var body: some View {
+        VStack(spacing: 20) {
+            Text(message)
+                .foregroundColor(.red)
+                .multilineTextAlignment(.center)
+                .padding()
+                .background(
+                    RoundedRectangle(cornerRadius: 10)
+                        .fill(Color.white)
+                        .shadow(color: .gray, radius: 5, x: 0, y: 2)
+                )
+                .padding()
+
+            if let buttonTitle = buttonTitle, let buttonAction = buttonAction {
+                Button(action: buttonAction) {
+                    Text(buttonTitle)
+                        .frame(maxWidth: .infinity)
+                        .padding()
+                        .background(Color.blue)
+                        .foregroundColor(.white)
+                        .cornerRadius(8)
+                }
+            }
+        }
+        .padding()
+        .background(
+            RoundedRectangle(cornerRadius: 10)
+                .fill(Color(.systemBackground))
+                .shadow(color: .gray, radius: 5, x: 0, y: 2)
+        )
+        .padding()
     }
 }

+ 52 - 1
LoopFollow/Remote/RemoteViewController.swift

@@ -9,12 +9,21 @@
 import Foundation
 import UIKit
 import SwiftUI
+import HealthKit
 
 class RemoteViewController: UIViewController {
+    private var statusMessage: ObservableValue<String> {
+        return Observable.shared.statusMessage
+    }
+
     override func viewDidLoad() {
         super.viewDidLoad()
 
-        let remoteView = RemoteView()
+        let remoteView = RemoteView(
+            onRefreshStatus: refreshStatus,
+            onCancelExistingTarget: cancelExistingTarget,
+            sendTempTarget: sendTempTarget
+        )
         let hostingController = UIHostingController(rootView: remoteView)
 
         addChild(hostingController)
@@ -29,5 +38,47 @@ class RemoteViewController: UIViewController {
         ])
 
         hostingController.didMove(toParent: self)
+
+        initialSetup()
+    }
+
+    private func initialSetup() {
+        // Perform initial setup checks here
+        // For example, load the Nightscout URL and token from user defaults or another source
+    }
+
+    private func refreshStatus() {
+        // Refresh the status to check current temp targets and other relevant info
+    }
+
+    private func cancelExistingTarget() {
+        // Cancel the existing temp target
+    }
+
+    private func sendTempTarget(newTarget: HKQuantity, duration: Int) {
+        let tempTargetBody: [String: Any] = [
+            "enteredBy": "LoopFollow",
+            "eventType": "Temporary Target",
+            "reason": "Manual",
+            "targetTop": newTarget.doubleValue(for: .milligramsPerDeciliter),
+            "targetBottom": newTarget.doubleValue(for: .milligramsPerDeciliter),
+            "duration": duration,
+            "created_at": ISO8601DateFormatter().string(from: Date())
+        ]
+
+        NightscoutUtils.executePostRequest(eventType: .treatments, body: tempTargetBody) { (result: Result<[TreatmentResponse], Error>) in
+            switch result {
+            case .success(let response):
+                print("Success: \(response)")
+                DispatchQueue.main.async {
+                    self.statusMessage.set("Temp target sent successfully.")
+                }
+            case .failure(let error):
+                print("Error: \(error)")
+                DispatchQueue.main.async {
+                    self.statusMessage.set("Failed to send temp target: \(error.localizedDescription)")
+                }
+            }
+        }
     }
 }

+ 33 - 0
LoopFollow/Remote/TreatmentResponse.swift

@@ -0,0 +1,33 @@
+//
+//  TreatmentResponse.swift
+//  LoopFollow
+//
+//  Created by Jonas Björkert on 2024-07-24.
+//  Copyright © 2024 Jon Fawcett. All rights reserved.
+//
+
+import Foundation
+
+struct TreatmentResponse: Decodable {
+    let enteredBy: String
+    let eventType: String
+    let reason: String
+    let targetTop: Double
+    let targetBottom: Double
+    let duration: Int
+    let createdAt: String
+    let utcOffset: Int
+    let id: String
+
+    enum CodingKeys: String, CodingKey {
+        case enteredBy = "enteredBy"
+        case eventType = "eventType"
+        case reason = "reason"
+        case targetTop = "targetTop"
+        case targetBottom = "targetBottom"
+        case duration = "duration"
+        case createdAt = "created_at"
+        case utcOffset = "utcOffset"
+        case id = "_id"
+    }
+}

+ 19 - 0
LoopFollow/Storage/Observable.swift

@@ -0,0 +1,19 @@
+    //
+    //  Observable.swift
+    //  LoopFollow
+    //
+    //  Created by Jonas Björkert on 2024-07-25.
+    //  Copyright © 2024 Jon Fawcett. All rights reserved.
+    //
+
+import Foundation
+import HealthKit
+
+class Observable {
+    static let shared = Observable()
+
+    var tempTarget = ObservableValue<HKQuantity?>(default: nil)
+    var statusMessage = ObservableValue<String>(default: "")
+
+    private init() {}
+}

+ 20 - 0
LoopFollow/Storage/ObservableUserDefaults.swift

@@ -0,0 +1,20 @@
+//
+//  ObservableUserDefaults.swift
+//  LoopFollow
+//
+//  Created by Jonas Björkert on 2024-07-24.
+//  Copyright © 2024 Jon Fawcett. All rights reserved.
+//
+
+import Foundation
+import Combine
+
+class ObservableUserDefaults {
+    static let shared = ObservableUserDefaults()
+
+    var url = ObservableUserDefaultsValue<String>(key: "url", default: "")
+    var device = ObservableUserDefaultsValue<String>(key: "device", default: "")
+    var nsWriteAuth = ObservableUserDefaultsValue<Bool>(key: "nsWriteAuth", default: false)
+
+    private init() {}
+}

+ 112 - 0
LoopFollow/Storage/ObservableUserDefaultsValue.swift

@@ -0,0 +1,112 @@
+//
+//  ObservableUserDefaultsValue.swift
+//  LoopFollow
+//
+//  Created by Jonas Björkert on 2024-07-24.
+//  Copyright © 2024 Jon Fawcett. All rights reserved.
+//
+
+import Foundation
+import Combine
+
+/// An observable value holder that keeps its internal data (the value) synchronized with its UserDefaults value.
+/// The value is read from UserDefaults on initialization (if exists, otherwise a default value is used) and written when the value changes.
+class ObservableUserDefaultsValue<T: AnyConvertible & Equatable>: ObservableObject, UserDefaultsAnyValue {
+    // user defaults key (UserDefaultsAnyValue protocol implementation)
+    let key: String
+    typealias ValueType = T
+
+    // The value (strong typed)
+    @Published var value: T {
+        didSet {
+            // Continue only if the new value is different from the old value
+            guard self.value != oldValue else { return }
+
+            if let validation = validation {
+                guard let validatedValue = validation(value) else {
+                    value = oldValue
+                    return
+                }
+                value = validatedValue
+            }
+
+            // Store value to user defaults
+            ObservableUserDefaultsValue.defaults.setValue(value.toAny(), forKey: key)
+
+            // Execute custom closure
+            onChange?(value)
+
+            // Notify observers
+            observers.values.forEach { $0(value) }
+
+            // Notify UserDefaultsValueGroups that value has changed
+            UserDefaultsValueGroups.valueChanged(self)
+        }
+    }
+
+    /// Get/set the value from Any value (UserDefaultsAnyValue protocol implementation)
+    var anyValue: Any? {
+        get {
+            return self.value.toAny()
+        }
+        set {
+            guard let newValue = T.fromAny(newValue) as T? else {
+                return
+            }
+            self.value = newValue
+        }
+    }
+
+    /// Is there this key already stored in UserDefaults?
+    var exists: Bool {
+        return ObservableUserDefaultsValue.defaults.object(forKey: key) != nil
+    }
+
+    // On change closure
+    private let onChange: ((T) -> ())?
+
+    // Validate & transform closure : given the new value, validate it; if validation passes, return the new value;
+    // if validation fails, transform the value, returning a modified version or return nil and the change will not happen
+    private let validation: ((T) -> T?)?
+
+    // Value change observers
+    private var observers: [UUID : (T) -> Void] = [:]
+
+    // User defaults used for persistence
+    private class var defaults: UserDefaults {
+        return UserDefaults(suiteName: AppConstants.APP_GROUP_ID)!
+    }
+
+    init(key: String, default defaultValue: T, onChange: ((T) -> Void)? = nil, validation: ((T) -> T?)? = nil) {
+        self.key = key
+        self.onChange = onChange
+        self.validation = validation
+
+        if let anyValue = ObservableUserDefaultsValue.defaults.object(forKey: key), let value = T.fromAny(anyValue) as T? {
+            self.value = validation?(value) ?? value
+        } else {
+            self.value = defaultValue
+        }
+    }
+
+    /// Insert this value in a group, useful for observing changes in the whole group, instead of particular values
+    func group(_ groupName: String) -> Self {
+        UserDefaultsValueGroups.add(self, to: groupName)
+        return self
+    }
+
+    /// Register observers, will be notified when value changes
+    @discardableResult
+    func observeChanges(using closure: @escaping (T) -> Void) -> ObservationToken {
+        let id = UUID()
+        observers[id] = closure
+
+        return ObservationToken { [weak self] in
+            self?.observers.removeValue(forKey: id)
+        }
+    }
+
+    func setNil(key: String) {
+        ObservableUserDefaultsValue.defaults.removeObject(forKey: key)
+    }
+}

+ 26 - 0
LoopFollow/Storage/ObservableValue.swift

@@ -0,0 +1,26 @@
+//
+//  ObservableValue.swift
+//  LoopFollow
+//
+//  Created by Jonas Björkert on 2024-07-25.
+//  Copyright © 2024 Jon Fawcett. All rights reserved.
+//
+
+import Foundation
+import Combine
+import HealthKit
+import SwiftUI
+
+class ObservableValue<T>: ObservableObject {
+    @Published var value: T
+
+    init(default: T) {
+        self.value = `default`
+    }
+
+    func set(_ newValue: T) {
+        DispatchQueue.main.async {
+            self.value = newValue
+        }
+    }
+}

LoopFollow/repository/UserData.xcdatamodeld/UserData.xcdatamodel/contents → LoopFollow/Storage/UserData.xcdatamodeld/UserData.xcdatamodel/contents


+ 21 - 2
LoopFollow/repository/UserDefaults.swift

@@ -12,6 +12,7 @@
 
 import Foundation
 import UIKit
+import HealthKit
 
 class UserDefaultsRepository {
     static let infoSort = UserDefaultsValue<[Int]>(key: "infoSort", default: InfoType.allCases.map { $0.sortOrder })
@@ -41,10 +42,28 @@ class UserDefaultsRepository {
 
     // Nightscout Settings
     static let showNS = UserDefaultsValue<Bool>(key: "showNS", default: false)
-    static let url = UserDefaultsValue<String>(key: "url", default: "")
+    //static let url = UserDefaultsValue<String>(key: "url", default: "")
     static let token = UserDefaultsValue<String>(key: "token", default: "")
     static let units = UserDefaultsValue<String>(key: "units", default: "mg/dL")
-    
+
+    static func getPreferredUnit() -> HKUnit {
+        let unitString = units.value
+        switch unitString {
+        case "mmol/L":
+            return .millimolesPerLiter
+        default:
+            return .milligramsPerDeciliter
+        }
+    }
+
+    static func setPreferredUnit(_ unit: HKUnit) {
+        var unitString = "mg/dL"
+        if unit == .millimolesPerLiter {
+            unitString = "mmol/L"
+        }
+        units.value = unitString
+    }
+
     // Dexcom Share Settings
     static let showDex = UserDefaultsValue<Bool>(key: "showDex", default: false)
     static let shareUserName = UserDefaultsValue<String>(key: "shareUserName", default: "")

LoopFollow/repository/UserDefaultsValue.swift → LoopFollow/Storage/UserDefaultsValue.swift


LoopFollow/repository/UserDefaultsValueGroups.swift → LoopFollow/Storage/UserDefaultsValueGroups.swift


+ 6 - 6
LoopFollow/ViewControllers/AlarmViewController.swift

@@ -301,7 +301,7 @@ class AlarmViewController: FormViewController {
             <<< SegmentedRow<String>("otherAlerts"){ row in
                 row.title = ""
                 row.options = ["Not Looping", "Missed Bolus", "SAGE", "CAGE"]
-                if UserDefaultsRepository.url.value == "" {
+                if !IsNightscoutEnabled() {
                     row.hidden = true
                 }
         }.onChange { [weak self] row in
@@ -326,7 +326,7 @@ class AlarmViewController: FormViewController {
         <<< SegmentedRow<String>("otherAlerts2"){ row in
                 row.title = ""
                 row.options = ["Override Start", "Override End", "Pump"]
-                if UserDefaultsRepository.url.value == "" {
+            if !IsNightscoutEnabled() {
                     row.hidden = true
                 }
         }.onChange { [weak self] row in
@@ -352,7 +352,7 @@ class AlarmViewController: FormViewController {
         <<< SegmentedRow<String>("otherAlerts3"){ row in
                 row.title = ""
                 row.options = ["IOB", "COB", "Battery"]
-                if UserDefaultsRepository.url.value == "" {
+            if !IsNightscoutEnabled() {
                     row.hidden = true
                 }
         }.onChange { [weak self] row in
@@ -378,7 +378,7 @@ class AlarmViewController: FormViewController {
         <<< SegmentedRow<String>("otherAlerts4"){ row in
             row.title = ""
             row.options = ["Rec. Bolus"]
-            if UserDefaultsRepository.url.value == "" {
+            if !IsNightscoutEnabled() {
                 row.hidden = true
             }
         }.onChange { [weak self] row in
@@ -442,7 +442,7 @@ class AlarmViewController: FormViewController {
     func showHideNSDetails() {
         var isHidden = false
         var isEnabled = true
-        if UserDefaultsRepository.url.value == "" {
+        if !IsNightscoutEnabled() {
             isHidden = true
             isEnabled = false
         }
@@ -481,7 +481,7 @@ class AlarmViewController: FormViewController {
         
         
         
-        if UserDefaultsRepository.url.value != "" {
+        if IsNightscoutEnabled() {
             isEnabled = true
         }
         

+ 1 - 1
LoopFollow/ViewControllers/GraphSettingsViewController.swift

@@ -29,7 +29,7 @@ class GraphSettingsViewController: FormViewController {
     func showHideNSDetails() {
         var isHidden = false
         var isEnabled = true
-        if UserDefaultsRepository.url.value == "" {
+        if !IsNightscoutEnabled() {
             isHidden = true
             isEnabled = false
         }

+ 7 - 4
LoopFollow/ViewControllers/MainViewController.swift

@@ -13,6 +13,10 @@ import ShareClient
 import UserNotifications
 import AVFAudio
 
+func IsNightscoutEnabled() -> Bool {
+    return !ObservableUserDefaults.shared.url.value.isEmpty
+}
+
 class MainViewController: UIViewController, UITableViewDataSource, ChartViewDelegate, UNUserNotificationCenterDelegate, UIScrollViewDelegate {
     
     @IBOutlet weak var BGText: UILabel!
@@ -54,7 +58,6 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
     
     // Vars for NS Pull
     var mmol = false as Bool
-    var urlUser = UserDefaultsRepository.url.value as String
     var token = UserDefaultsRepository.token.value as String
     var defaults : UserDefaults?
     let consoleLogging = true
@@ -183,7 +186,7 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
         // Load the snoozer tab
         guard let snoozer = self.tabBarController!.viewControllers?[2] as? SnoozeViewController else { return }
         snoozer.loadViewIfNeeded()
-        
+
         // Trigger foreground and background functions
         let notificationCenter = NotificationCenter.default
         notificationCenter.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
@@ -460,7 +463,7 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
     func showHideNSDetails() {
         var isHidden = false
         var isEnabled = true
-        if UserDefaultsRepository.url.value == "" {
+        if !IsNightscoutEnabled() {
             isHidden = true
             isEnabled = false
         }
@@ -478,7 +481,7 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
             infoTable.isHidden = true
         }
         
-        if UserDefaultsRepository.url.value != "" {
+        if IsNightscoutEnabled() {
             isEnabled = true
         }
         

+ 1 - 1
LoopFollow/ViewControllers/NightScoutViewController.swift

@@ -23,7 +23,7 @@ class NightscoutViewController: UIViewController {
             overrideUserInterfaceStyle = .dark
         }
         
-        var url = UserDefaultsRepository.url.value
+        var url = ObservableUserDefaults.shared.url.value
         let token = UserDefaultsRepository.token.value
         
         if token != "" {

+ 220 - 218
LoopFollow/ViewControllers/SettingsViewController.swift

@@ -19,151 +19,151 @@ class SettingsViewController: FormViewController {
     func showHideNSDetails() {
         var isHidden = false
         var isEnabled = true
-        if UserDefaultsRepository.url.value == "" {
+        if !IsNightscoutEnabled() {
             isHidden = true
             isEnabled = false
         }
-        
+
         if let row1 = form.rowBy(tag: "informationDisplaySettings") as? ButtonRow {
             row1.hidden = .function(["hide"],  {form in
                 return isHidden
             })
             row1.evaluateHidden()
         }
-        
-        if UserDefaultsRepository.url.value != "" {
+
+        if IsNightscoutEnabled() {
             isEnabled = true
         }
-        
+
         guard let nightscoutTab = self.tabBarController?.tabBar.items![3] else { return }
         nightscoutTab.isEnabled = isEnabled
     }
-    
-   override func viewDidLoad() {
+
+    override func viewDidLoad() {
         super.viewDidLoad()
         if UserDefaultsRepository.forceDarkMode.value {
             overrideUserInterfaceStyle = .dark
         }
-       UserDefaultsRepository.showNS.value = false
-       UserDefaultsRepository.showDex.value = false
-    
-       let buildDetails = BuildDetails.default
-       let formattedBuildDate = dateTimeUtils.formattedDate(from: buildDetails.buildDate())
-       let branchAndSha = buildDetails.branchAndSha
-       let expiration = dateTimeUtils.formattedDate(from: buildDetails.calculateExpirationDate())
-       let expirationHeaderString = buildDetails.expirationHeaderString
-       let versionManager = AppVersionManager()
-       let version = versionManager.version()
+        UserDefaultsRepository.showNS.value = false
+        UserDefaultsRepository.showDex.value = false
+
+        let buildDetails = BuildDetails.default
+        let formattedBuildDate = dateTimeUtils.formattedDate(from: buildDetails.buildDate())
+        let branchAndSha = buildDetails.branchAndSha
+        let expiration = dateTimeUtils.formattedDate(from: buildDetails.calculateExpirationDate())
+        let expirationHeaderString = buildDetails.expirationHeaderString
+        let versionManager = AppVersionManager()
+        let version = versionManager.version()
 
         form
         +++ Section(header: "Data Settings", footer: "")
-       <<< SegmentedRow<String>("units") { row in
-           row.title = "Units"
-           row.options = ["mg/dL", "mmol/L"]
-           row.value = UserDefaultsRepository.units.value
-       }.onChange { row in
-           guard let value = row.value else { return }
-           UserDefaultsRepository.units.value = value
-       }
-       <<< SwitchRow("showNS"){ row in
-       row.title = "Show Nightscout Settings"
-       row.value = UserDefaultsRepository.showNS.value
-       }.onChange { [weak self] row in
-               guard let value = row.value else { return }
-               UserDefaultsRepository.showNS.value = value
-       }
-       <<< TextRow() { row in
-           row.title = "URL"
-           row.placeholder = "https://mycgm.herokuapp.com"
-           row.value = UserDefaultsRepository.url.value
-           row.hidden = "$showNS == false"
-       }.cellSetup { (cell, row) in
-           cell.textField.autocorrectionType = .no
-           cell.textField.autocapitalizationType = .none
-       }.onChange { row in
-           guard let value = row.value else {
-               UserDefaultsRepository.url.value = ""
-               self.showHideNSDetails()
-               return
-           }
-           
-           var useTokenUrl = false
-           
-           // Attempt to handle special case: pasted URL including token
-           if let urlComponents = URLComponents(string: value), let queryItems = urlComponents.queryItems {
-               if let tokenItem = queryItems.first(where: { $0.name.lowercased() == "token" }) {
-                   let tokenPattern = "^[^-\\s]+-[0-9a-fA-F]{16}$"
-                   if let token = tokenItem.value, let _ = token.range(of: tokenPattern, options: .regularExpression) {
-                       var baseComponents = urlComponents
-                       baseComponents.queryItems = nil
-                       if let baseURL = baseComponents.string {
-                           UserDefaultsRepository.token.value = token
-                           self.tokenRow?.value = token
-                           self.tokenRow?.updateCell()
-
-                           UserDefaultsRepository.url.value = baseURL
-                           row.value = baseURL
-                           row.updateCell()
-                           useTokenUrl = true
-                       }
-                   }
-               }
-           }
-           
-           if !useTokenUrl {
-               // Normalize input: remove unwanted characters and lowercase
-               let filtered = value.replacingOccurrences(of: "[^A-Za-z0-9:/._-]", with: "", options: .regularExpression).lowercased()
-
-               // Further clean-up: Remove trailing slashes
-               var cleanURL = filtered
-               while cleanURL.count > 8 && cleanURL.last == "/" {
-                   cleanURL = String(cleanURL.dropLast())
-               }
-
-               UserDefaultsRepository.url.value = cleanURL
-               row.value = cleanURL
-               row.updateCell()
-           }
-
-           self.showHideNSDetails()
-
-           // Verify Nightscout URL and token
-           self.checkNightscoutStatus()
-       }
-
-       <<< TextRow() { row in
-           row.title = "NS Token"
-           row.placeholder = "Leave blank if not using tokens"
-           row.value = UserDefaultsRepository.token.value
-           row.hidden = "$showNS == false"
-           self.tokenRow = row
-       }.cellSetup { (cell, row) in
-           cell.textField.autocorrectionType = .no
-           cell.textField.autocapitalizationType = .none
-           cell.textField.textContentType = .password
-       }.onChange { row in
-           if row.value == nil {
-               UserDefaultsRepository.token.value = ""
-           }
-           guard let value = row.value else { return }
-           UserDefaultsRepository.token.value = value
-           
-           // Verify Nightscout URL and token
-           self.checkNightscoutStatus()
-       }
-       <<< LabelRow() { row in
-           row.title = "NS Status"
-           row.value = "Checking..."
-           statusLabelRow = row
-           row.hidden = "$showNS == false"
-       }
-       <<< SwitchRow("showDex"){ row in
-       row.title = "Show Dexcom Settings"
-       row.value = UserDefaultsRepository.showDex.value
-       }.onChange { row in
-               guard let value = row.value else { return }
-               UserDefaultsRepository.showDex.value = value
-       }
+        <<< SegmentedRow<String>("units") { row in
+            row.title = "Units"
+            row.options = ["mg/dL", "mmol/L"]
+            row.value = UserDefaultsRepository.units.value
+        }.onChange { row in
+            guard let value = row.value else { return }
+            UserDefaultsRepository.units.value = value
+        }
+        <<< SwitchRow("showNS"){ row in
+            row.title = "Show Nightscout Settings"
+            row.value = UserDefaultsRepository.showNS.value
+        }.onChange { [weak self] row in
+            guard let value = row.value else { return }
+            UserDefaultsRepository.showNS.value = value
+        }
+        <<< TextRow() { row in
+            row.title = "URL"
+            row.placeholder = "https://mycgm.herokuapp.com"
+            row.value = ObservableUserDefaults.shared.url.value
+            row.hidden = "$showNS == false"
+        }.cellSetup { (cell, row) in
+            cell.textField.autocorrectionType = .no
+            cell.textField.autocapitalizationType = .none
+        }.onChange { row in
+            guard let value = row.value else {
+                ObservableUserDefaults.shared.url.value = ""
+                self.showHideNSDetails()
+                return
+            }
+
+            var useTokenUrl = false
+
+            // Attempt to handle special case: pasted URL including token
+            if let urlComponents = URLComponents(string: value), let queryItems = urlComponents.queryItems {
+                if let tokenItem = queryItems.first(where: { $0.name.lowercased() == "token" }) {
+                    let tokenPattern = "^[^-\\s]+-[0-9a-fA-F]{16}$"
+                    if let token = tokenItem.value, let _ = token.range(of: tokenPattern, options: .regularExpression) {
+                        var baseComponents = urlComponents
+                        baseComponents.queryItems = nil
+                        if let baseURL = baseComponents.string {
+                            UserDefaultsRepository.token.value = token
+                            self.tokenRow?.value = token
+                            self.tokenRow?.updateCell()
+
+                            ObservableUserDefaults.shared.url.value = baseURL
+                            row.value = baseURL
+                            row.updateCell()
+                            useTokenUrl = true
+                        }
+                    }
+                }
+            }
+
+            if !useTokenUrl {
+                // Normalize input: remove unwanted characters and lowercase
+                let filtered = value.replacingOccurrences(of: "[^A-Za-z0-9:/._-]", with: "", options: .regularExpression).lowercased()
+
+                // Further clean-up: Remove trailing slashes
+                var cleanURL = filtered
+                while cleanURL.count > 8 && cleanURL.last == "/" {
+                    cleanURL = String(cleanURL.dropLast())
+                }
+
+                ObservableUserDefaults.shared.url.value = cleanURL
+                row.value = cleanURL
+                row.updateCell()
+            }
+
+            self.showHideNSDetails()
+
+            // Verify Nightscout URL and token
+            self.checkNightscoutStatus()
+        }
+
+        <<< TextRow() { row in
+            row.title = "NS Token"
+            row.placeholder = "Leave blank if not using tokens"
+            row.value = UserDefaultsRepository.token.value
+            row.hidden = "$showNS == false"
+            self.tokenRow = row
+        }.cellSetup { (cell, row) in
+            cell.textField.autocorrectionType = .no
+            cell.textField.autocapitalizationType = .none
+            cell.textField.textContentType = .password
+        }.onChange { row in
+            if row.value == nil {
+                UserDefaultsRepository.token.value = ""
+            }
+            guard let value = row.value else { return }
+            UserDefaultsRepository.token.value = value
+
+            // Verify Nightscout URL and token
+            self.checkNightscoutStatus()
+        }
+        <<< LabelRow() { row in
+            row.title = "NS Status"
+            row.value = "Checking..."
+            statusLabelRow = row
+            row.hidden = "$showNS == false"
+        }
+        <<< SwitchRow("showDex"){ row in
+            row.title = "Show Dexcom Settings"
+            row.value = UserDefaultsRepository.showDex.value
+        }.onChange { row in
+            guard let value = row.value else { return }
+            UserDefaultsRepository.showDex.value = value
+        }
         <<< TextRow(){ row in
             row.title = "User Name"
             row.value = UserDefaultsRepository.shareUserName.value
@@ -202,113 +202,113 @@ class SettingsViewController: FormViewController {
             guard let value = row.value else { return }
             UserDefaultsRepository.shareServer.value = value
         }
-        
+
         +++ Section("App Settings")
-       
+
         <<< ButtonRow() {
-           $0.title = "General Settings"
-           $0.presentationMode = .show(
-               controllerProvider: .callback(builder: {
-                  let controller = GeneralSettingsViewController()
-                  controller.appStateController = self.appStateController
-                  return controller
-               }
-           ), onDismiss: nil)
+            $0.title = "General Settings"
+            $0.presentationMode = .show(
+                controllerProvider: .callback(builder: {
+                    let controller = GeneralSettingsViewController()
+                    controller.appStateController = self.appStateController
+                    return controller
+                }
+                                             ), onDismiss: nil)
         }
         <<< ButtonRow("graphSettings") {
-           $0.title = "Graph Settings"
-           $0.presentationMode = .show(
-               controllerProvider: .callback(builder: {
-                  let controller = GraphSettingsViewController()
-                  controller.appStateController = self.appStateController
-                  return controller
-               }
-           ), onDismiss: nil)
+            $0.title = "Graph Settings"
+            $0.presentationMode = .show(
+                controllerProvider: .callback(builder: {
+                    let controller = GraphSettingsViewController()
+                    controller.appStateController = self.appStateController
+                    return controller
+                }
+                                             ), onDismiss: nil)
         }
         <<< ButtonRow("informationDisplaySettings") {
-           $0.title = "Information Display Settings"
-           $0.presentationMode = .show(
-               controllerProvider: .callback(builder: {
-                  let controller = InfoDisplaySettingsViewController()
-                  controller.appStateController = self.appStateController
-                  return controller
-               }
-           ), onDismiss: nil)
-            
+            $0.title = "Information Display Settings"
+            $0.presentationMode = .show(
+                controllerProvider: .callback(builder: {
+                    let controller = InfoDisplaySettingsViewController()
+                    controller.appStateController = self.appStateController
+                    return controller
+                }
+                                             ), onDismiss: nil)
+
+        }
+        <<< ButtonRow("alarmsSettings") {
+            $0.title = "Alarms"
+            $0.presentationMode = .show(
+                controllerProvider: .callback(builder: {
+                    guard let alarmVC = ViewControllerManager.shared.alarmViewController else {
+                        fatalError("AlarmViewController should be pre-instantiated and available")
+                    }
+                    return alarmVC
+                }), onDismiss: nil)
         }
-       <<< ButtonRow("alarmsSettings") {
-           $0.title = "Alarms"
-           $0.presentationMode = .show(
-            controllerProvider: .callback(builder: {
-                let storyboard = UIStoryboard(name: "Main", bundle: nil)
-                let controller = storyboard.instantiateViewController(withIdentifier: "AlarmViewController") as! AlarmViewController
-                controller.appStateController = self.appStateController
-                return controller
-            }), onDismiss: nil)
-       }
 
         +++ Section("Integrations")
         <<< ButtonRow() {
-           $0.title = "Apple Watch and Carplay"
-           $0.presentationMode = .show(
-               controllerProvider: .callback(builder: {
-                  let controller = WatchSettingsViewController()
-                  controller.appStateController = self.appStateController
-                  return controller
-               }
-           ), onDismiss: nil)
-            
+            $0.title = "Apple Watch and Carplay"
+            $0.presentationMode = .show(
+                controllerProvider: .callback(builder: {
+                    let controller = WatchSettingsViewController()
+                    controller.appStateController = self.appStateController
+                    return controller
+                }
+                                             ), onDismiss: nil)
+
         }
-        
-       +++ Section("Advanced Settings")
+
+        +++ Section("Advanced Settings")
         <<< ButtonRow() {
-           $0.title = "Advanced Settings"
-           $0.presentationMode = .show(
-               controllerProvider: .callback(builder: {
-                  let controller = AdvancedSettingsViewController()
-                  controller.appStateController = self.appStateController
-                  return controller
-               }
-           ), onDismiss: nil)
-            
+            $0.title = "Advanced Settings"
+            $0.presentationMode = .show(
+                controllerProvider: .callback(builder: {
+                    let controller = AdvancedSettingsViewController()
+                    controller.appStateController = self.appStateController
+                    return controller
+                }
+                                             ), onDismiss: nil)
+
         }
 
-       +++ Section("Build Information")
-       <<< LabelRow() {
-           $0.title = "Version"
-           $0.value = version
-           $0.tag = "currentVersionRow"
-       }
-       <<< LabelRow() {
-           $0.title = "Latest version"
-           $0.value = "Fetching..."
-           $0.tag = "latestVersionRow"
-       }
-       <<< LabelRow() {
-           $0.title = expirationHeaderString
-           $0.value = expiration
-           $0.hidden = Condition(booleanLiteral: isMacApp())
-       }
-       <<< LabelRow() {
-           $0.title = "Built"
-           $0.value = formattedBuildDate
-       }
-       <<< LabelRow() {
-           $0.title = "Branch"
-           $0.value = branchAndSha
-       }
-       
-       showHideNSDetails()
-       checkNightscoutStatus()
+        +++ Section("Build Information")
+        <<< LabelRow() {
+            $0.title = "Version"
+            $0.value = version
+            $0.tag = "currentVersionRow"
+        }
+        <<< LabelRow() {
+            $0.title = "Latest version"
+            $0.value = "Fetching..."
+            $0.tag = "latestVersionRow"
+        }
+        <<< LabelRow() {
+            $0.title = expirationHeaderString
+            $0.value = expiration
+            $0.hidden = Condition(booleanLiteral: isMacApp())
+        }
+        <<< LabelRow() {
+            $0.title = "Built"
+            $0.value = formattedBuildDate
+        }
+        <<< LabelRow() {
+            $0.title = "Branch"
+            $0.value = branchAndSha
+        }
+
+        showHideNSDetails()
+        checkNightscoutStatus()
     }
-    
+
     override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)
-        
+
         refreshVersionInfo()
         checkNightscoutStatus()
     }
-    
+
     func refreshVersionInfo() {
         let versionManager = AppVersionManager()
         versionManager.checkForNewVersion { latestVersion, isNewer, isBlacklisted in
@@ -317,7 +317,7 @@ class SettingsViewController: FormViewController {
                     currentVersionRow.cell.detailTextLabel?.textColor = self.getColor(isBlacklisted: isBlacklisted, isNewer: isNewer, isCurrent: latestVersion == versionManager.version())
                     currentVersionRow.updateCell()
                 }
-                
+
                 if let latestVersionRow = self.form.rowBy(tag: "latestVersionRow") as? LabelRow {
                     latestVersionRow.value = latestVersion ?? "Unknown"
                     latestVersionRow.updateCell()
@@ -325,7 +325,7 @@ class SettingsViewController: FormViewController {
             }
         }
     }
-    
+
     private func getColor(isBlacklisted: Bool, isNewer: Bool, isCurrent: Bool) -> UIColor {
         if isBlacklisted {
             return .red
@@ -337,7 +337,7 @@ class SettingsViewController: FormViewController {
             return .secondaryLabel
         }
     }
-    
+
     func isMacApp() -> Bool {
 #if targetEnvironment(macCatalyst)
         return true
@@ -345,7 +345,7 @@ class SettingsViewController: FormViewController {
         return false
 #endif
     }
-    
+
     func updateStatusLabel(error: NightscoutUtils.NightscoutError?) {
         if let error = error {
             switch error {
@@ -365,15 +365,17 @@ class SettingsViewController: FormViewController {
                 statusLabelRow.value = "Address Empty"
             }
         } else {
-            statusLabelRow.value = "OK"
+            statusLabelRow.value = "OK (Read\(ObservableUserDefaults.shared.nsWriteAuth.value ? " & Write" : ""))"
             NotificationCenter.default.post(name: NSNotification.Name("refresh"), object: nil)
         }
         statusLabelRow.updateCell()
     }
-    
+
     func checkNightscoutStatus() {
-        NightscoutUtils.verifyURLAndToken(urlUser: UserDefaultsRepository.url.value, token: UserDefaultsRepository.token.value) { error in
+        NightscoutUtils.verifyURLAndToken { error, jwtToken, nsWriteAuth in
             DispatchQueue.main.async {
+                ObservableUserDefaults.shared.nsWriteAuth.value = nsWriteAuth
+
                 self.updateStatusLabel(error: error)
             }
         }

+ 91 - 117
LoopFollow/ViewControllers/SnoozeViewController.swift

@@ -119,353 +119,327 @@ class SnoozeViewController: UIViewController, UNUserNotificationCenterDelegate {
         }
     }
     
-    func setSnoozeTime()
-    {
-        if AlarmSound.whichAlarm == "Temporary Alert" {
+    func setSnoozeTime() {
+        guard let alarms = ViewControllerManager.shared.alarmViewController else { return }
+
+        let snoozeDuration = TimeInterval(snoozeForMinuteStepper.value * 60)
+        let longSnoozeDuration = TimeInterval(snoozeForMinuteStepper.value * 60 * 60)
+        let currentDate = Date()
+
+        switch AlarmSound.whichAlarm {
+        case "Temporary Alert":
             UserDefaultsRepository.alertTemporaryActive.value = false
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertTemporaryActive", value: false)
-        } else if AlarmSound.whichAlarm == "Urgent Low Alert" {
+
+        case "Urgent Low Alert":
             UserDefaultsRepository.alertUrgentLowIsSnoozed.value = true
-            UserDefaultsRepository.alertUrgentLowSnoozedTime.value = Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60))
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            UserDefaultsRepository.alertUrgentLowSnoozedTime.value = currentDate.addingTimeInterval(snoozeDuration)
             alarms.reloadIsSnoozed(key: "alertUrgentLowIsSnoozed", value: true)
-            alarms.reloadSnoozeTime(key: "alertUrgentLowSnoozedTime", setNil: false, value: Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60)))
-        } else if AlarmSound.whichAlarm == "Low Alert" {
+            alarms.reloadSnoozeTime(key: "alertUrgentLowSnoozedTime", setNil: false, value: currentDate.addingTimeInterval(snoozeDuration))
+
+        case "Low Alert":
             UserDefaultsRepository.alertLowIsSnoozed.value = true
-            UserDefaultsRepository.alertLowSnoozedTime.value = Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60))
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            UserDefaultsRepository.alertLowSnoozedTime.value = currentDate.addingTimeInterval(snoozeDuration)
             alarms.reloadIsSnoozed(key: "alertLowIsSnoozed", value: true)
-            alarms.reloadSnoozeTime(key: "alertLowSnoozedTime", setNil: false, value: Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60)))
-        } else if AlarmSound.whichAlarm == "Predicted Urgent Low Alert" {
+            alarms.reloadSnoozeTime(key: "alertLowSnoozedTime", setNil: false, value: currentDate.addingTimeInterval(snoozeDuration))
+
+        case "Predicted Urgent Low Alert":
             UserDefaultsRepository.alertUrgentLowIsSnoozed.value = true
-            UserDefaultsRepository.alertUrgentLowSnoozedTime.value = Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60))
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            UserDefaultsRepository.alertUrgentLowSnoozedTime.value = currentDate.addingTimeInterval(snoozeDuration)
             alarms.reloadIsSnoozed(key: "alertUrgentLowIsSnoozed", value: true)
-            alarms.reloadSnoozeTime(key: "alertUrgentLowSnoozedTime", setNil: false, value: Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60)))
-        } else if AlarmSound.whichAlarm == "High Alert" {
+            alarms.reloadSnoozeTime(key: "alertUrgentLowSnoozedTime", setNil: false, value: currentDate.addingTimeInterval(snoozeDuration))
+
+        case "High Alert":
             UserDefaultsRepository.alertHighIsSnoozed.value = true
-            UserDefaultsRepository.alertHighSnoozedTime.value = Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60))
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            UserDefaultsRepository.alertHighSnoozedTime.value = currentDate.addingTimeInterval(snoozeDuration)
             alarms.reloadIsSnoozed(key: "alertHighIsSnoozed", value: true)
-            alarms.reloadSnoozeTime(key: "alertHighSnoozedTime", setNil: false, value: Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60)))
-        } else if AlarmSound.whichAlarm == "Urgent High Alert" {
+            alarms.reloadSnoozeTime(key: "alertHighSnoozedTime", setNil: false, value: currentDate.addingTimeInterval(snoozeDuration))
+
+        case "Urgent High Alert":
             UserDefaultsRepository.alertUrgentHighIsSnoozed.value = true
-            UserDefaultsRepository.alertUrgentHighSnoozedTime.value = Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60))
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            UserDefaultsRepository.alertUrgentHighSnoozedTime.value = currentDate.addingTimeInterval(snoozeDuration)
             alarms.reloadIsSnoozed(key: "alertUrgentHighIsSnoozed", value: true)
-            alarms.reloadSnoozeTime(key: "alertUrgentHighSnoozedTime", setNil: false, value: Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60)))
-        } else if AlarmSound.whichAlarm == "Fast Drop Alert" {
+            alarms.reloadSnoozeTime(key: "alertUrgentHighSnoozedTime", setNil: false, value: currentDate.addingTimeInterval(snoozeDuration))
+
+        case "Fast Drop Alert":
             UserDefaultsRepository.alertFastDropIsSnoozed.value = true
-            UserDefaultsRepository.alertFastDropSnoozedTime.value = Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60))
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            UserDefaultsRepository.alertFastDropSnoozedTime.value = currentDate.addingTimeInterval(snoozeDuration)
             alarms.reloadIsSnoozed(key: "alertFastDropIsSnoozed", value: true)
-            alarms.reloadSnoozeTime(key: "alertFastDropSnoozedTime", setNil: false, value: Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60)))
-        } else if AlarmSound.whichAlarm == "Fast Rise Alert" {
+            alarms.reloadSnoozeTime(key: "alertFastDropSnoozedTime", setNil: false, value: currentDate.addingTimeInterval(snoozeDuration))
+
+        case "Fast Rise Alert":
             UserDefaultsRepository.alertFastRiseIsSnoozed.value = true
-            UserDefaultsRepository.alertFastRiseSnoozedTime.value = Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60))
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            UserDefaultsRepository.alertFastRiseSnoozedTime.value = currentDate.addingTimeInterval(snoozeDuration)
             alarms.reloadIsSnoozed(key: "alertFastRiseIsSnoozed", value: true)
-            alarms.reloadSnoozeTime(key: "alertFastRiseSnoozedTime", setNil: false, value: Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60)))
-        }  else if AlarmSound.whichAlarm == "Missed Reading Alert" {
+            alarms.reloadSnoozeTime(key: "alertFastRiseSnoozedTime", setNil: false, value: currentDate.addingTimeInterval(snoozeDuration))
+
+        case "Missed Reading Alert":
             UserDefaultsRepository.alertMissedReadingIsSnoozed.value = true
-            UserDefaultsRepository.alertMissedReadingSnoozedTime.value = Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60))
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            UserDefaultsRepository.alertMissedReadingSnoozedTime.value = currentDate.addingTimeInterval(snoozeDuration)
             alarms.reloadIsSnoozed(key: "alertMissedReadingIsSnoozed", value: true)
-            alarms.reloadSnoozeTime(key: "alertMissedReadingSnoozedTime", setNil: false, value: Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60)))
-        }  else if AlarmSound.whichAlarm == "Sensor Change Alert" {
+            alarms.reloadSnoozeTime(key: "alertMissedReadingSnoozedTime", setNil: false, value: currentDate.addingTimeInterval(snoozeDuration))
+
+        case "Sensor Change Alert":
             UserDefaultsRepository.alertSAGEIsSnoozed.value = true
-            UserDefaultsRepository.alertSAGESnoozedTime.value = Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60 * 60))
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            UserDefaultsRepository.alertSAGESnoozedTime.value = currentDate.addingTimeInterval(longSnoozeDuration)
             alarms.reloadIsSnoozed(key: "alertSAGEIsSnoozed", value: true)
-            alarms.reloadSnoozeTime(key: "alertSAGESnoozedTime", setNil: false, value: Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60 * 60)))
-        } else if AlarmSound.whichAlarm == "Pump Change Alert" {
+            alarms.reloadSnoozeTime(key: "alertSAGESnoozedTime", setNil: false, value: currentDate.addingTimeInterval(longSnoozeDuration))
+
+        case "Pump Change Alert":
             UserDefaultsRepository.alertCAGEIsSnoozed.value = true
-            UserDefaultsRepository.alertCAGESnoozedTime.value = Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60 * 60))
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            UserDefaultsRepository.alertCAGESnoozedTime.value = currentDate.addingTimeInterval(longSnoozeDuration)
             alarms.reloadIsSnoozed(key: "alertCAGEIsSnoozed", value: true)
-            alarms.reloadSnoozeTime(key: "alertCAGESnoozedTime", setNil: false, value: Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60 * 60)))
-        } else if AlarmSound.whichAlarm == "Not Looping Alert" {
+            alarms.reloadSnoozeTime(key: "alertCAGESnoozedTime", setNil: false, value: currentDate.addingTimeInterval(longSnoozeDuration))
+
+        case "Not Looping Alert":
             UserDefaultsRepository.alertNotLoopingIsSnoozed.value = true
-            UserDefaultsRepository.alertNotLoopingSnoozedTime.value = Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60))
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            UserDefaultsRepository.alertNotLoopingSnoozedTime.value = currentDate.addingTimeInterval(snoozeDuration)
             alarms.reloadIsSnoozed(key: "alertNotLoopingIsSnoozed", value: true)
-            alarms.reloadSnoozeTime(key: "alertNotLoopingSnoozedTime", setNil: false, value: Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60)))
-        } else if AlarmSound.whichAlarm == "Missed Bolus Alert" {
+            alarms.reloadSnoozeTime(key: "alertNotLoopingSnoozedTime", setNil: false, value: currentDate.addingTimeInterval(snoozeDuration))
+
+        case "Missed Bolus Alert":
             UserDefaultsRepository.alertMissedBolusIsSnoozed.value = true
-            UserDefaultsRepository.alertMissedBolusSnoozedTime.value = Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60))
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            UserDefaultsRepository.alertMissedBolusSnoozedTime.value = currentDate.addingTimeInterval(snoozeDuration)
             alarms.reloadIsSnoozed(key: "alertMissedBolusIsSnoozed", value: true)
-            alarms.reloadSnoozeTime(key: "alertMissedBolusSnoozedTime", setNil: false, value: Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60)))
-        } else if AlarmSound.whichAlarm == "Low Insulin Alert" {
+            alarms.reloadSnoozeTime(key: "alertMissedBolusSnoozedTime", setNil: false, value: currentDate.addingTimeInterval(snoozeDuration))
+
+        case "Low Insulin Alert":
             UserDefaultsRepository.alertPumpIsSnoozed.value = true
-            UserDefaultsRepository.alertPumpSnoozedTime.value = Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60 * 60))
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            UserDefaultsRepository.alertPumpSnoozedTime.value = currentDate.addingTimeInterval(longSnoozeDuration)
             alarms.reloadIsSnoozed(key: "alertPumpIsSnoozed", value: true)
-            alarms.reloadSnoozeTime(key: "alertPumpSnoozedTime", setNil: false, value: Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60 * 60)))
-        } else if AlarmSound.whichAlarm == "IOB Alert" {
+            alarms.reloadSnoozeTime(key: "alertPumpSnoozedTime", setNil: false, value: currentDate.addingTimeInterval(longSnoozeDuration))
+
+        case "IOB Alert":
             UserDefaultsRepository.alertIOBIsSnoozed.value = true
-            UserDefaultsRepository.alertIOBSnoozedTime.value = Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60 * 60))
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            UserDefaultsRepository.alertIOBSnoozedTime.value = currentDate.addingTimeInterval(longSnoozeDuration)
             alarms.reloadIsSnoozed(key: "alertIOBIsSnoozed", value: true)
-            alarms.reloadSnoozeTime(key: "alertIOBSnoozedTime", setNil: false, value: Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60 * 60)))
-        } else if AlarmSound.whichAlarm == "COB Alert" {
+            alarms.reloadSnoozeTime(key: "alertIOBSnoozedTime", setNil: false, value: currentDate.addingTimeInterval(longSnoozeDuration))
+
+        case "COB Alert":
             UserDefaultsRepository.alertCOBIsSnoozed.value = true
-            UserDefaultsRepository.alertCOBSnoozedTime.value = Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60 * 60))
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            UserDefaultsRepository.alertCOBSnoozedTime.value = currentDate.addingTimeInterval(longSnoozeDuration)
             alarms.reloadIsSnoozed(key: "alertCOBIsSnoozed", value: true)
-            alarms.reloadSnoozeTime(key: "alertCOBSnoozedTime", setNil: false, value: Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60 * 60)))
-        } else if AlarmSound.whichAlarm == "Low Battery" {
+            alarms.reloadSnoozeTime(key: "alertCOBSnoozedTime", setNil: false, value: currentDate.addingTimeInterval(longSnoozeDuration))
+
+        case "Low Battery":
             UserDefaultsRepository.alertBatteryIsSnoozed.value = true
-            
-            UserDefaultsRepository.alertBatterySnoozedTime.value = Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60 * 60))
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
+            UserDefaultsRepository.alertBatterySnoozedTime.value = currentDate.addingTimeInterval(longSnoozeDuration)
             alarms.reloadIsSnoozed(key: "alertBatteryIsSnoozed", value: true)
-            alarms.reloadSnoozeTime(key: "alertBatterySnoozedTime", setNil: false, value: Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60 * 60)))
-        } else if AlarmSound.whichAlarm == "Rec. Bolus" {
-           UserDefaultsRepository.alertRecBolusIsSnoozed.value = true
+            alarms.reloadSnoozeTime(key: "alertBatterySnoozedTime", setNil: false, value: currentDate.addingTimeInterval(longSnoozeDuration))
+
+        case "Rec. Bolus":
+            UserDefaultsRepository.alertRecBolusIsSnoozed.value = true
+            UserDefaultsRepository.alertRecBolusSnoozedTime.value = currentDate.addingTimeInterval(snoozeDuration)
+            alarms.reloadIsSnoozed(key: "alertRecBolusIsSnoozed", value: true)
+            alarms.reloadSnoozeTime(key: "alertRecBolusSnoozedTime", setNil: false, value: currentDate.addingTimeInterval(snoozeDuration))
 
-           UserDefaultsRepository.alertRecBolusSnoozedTime.value = Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60))
-           guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
-           alarms.reloadIsSnoozed(key: "alertRecBolusIsSnoozed", value: true)
-           alarms.reloadSnoozeTime(key: "alertRecBolusSnoozedTime", setNil: false, value: Date().addingTimeInterval(TimeInterval(snoozeForMinuteStepper.value * 60)))
-       }
+        default:
+            print("Unhandled alarm: \(AlarmSound.whichAlarm)")
+        }
     }
-    
-    func setPresnoozeNight(snoozeTime: Date)
-    {
+
+    func setPresnoozeNight(snoozeTime: Date) {
+        guard let alarms = ViewControllerManager.shared.alarmViewController else { return }
+
         if UserDefaultsRepository.alertUrgentLowAutosnoozeNight.value {
             UserDefaultsRepository.alertUrgentLowIsSnoozed.value = true
             UserDefaultsRepository.alertUrgentLowSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertUrgentLowIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertUrgentLowSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertLowAutosnoozeNight.value {
             UserDefaultsRepository.alertLowIsSnoozed.value = true
             UserDefaultsRepository.alertLowSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertLowIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertLowSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertHighAutosnoozeNight.value {
             UserDefaultsRepository.alertHighIsSnoozed.value = true
             UserDefaultsRepository.alertHighSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertHighIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertHighSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertUrgentHighAutosnoozeNight.value {
             UserDefaultsRepository.alertUrgentHighIsSnoozed.value = true
             UserDefaultsRepository.alertUrgentHighSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertUrgentHighIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertUrgentHighSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertMissedReadingAutosnoozeNight.value {
             UserDefaultsRepository.alertMissedReadingIsSnoozed.value = true
             UserDefaultsRepository.alertMissedReadingSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertMissedReadingIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertMissedReadingSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertFastDropAutosnoozeNight.value {
             UserDefaultsRepository.alertFastDropIsSnoozed.value = true
             UserDefaultsRepository.alertFastDropSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertFastDropIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertFastDropSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertFastRiseAutosnoozeNight.value {
             UserDefaultsRepository.alertFastRiseIsSnoozed.value = true
             UserDefaultsRepository.alertFastRiseSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertFastRiseIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertFastRiseSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertNotLoopingAutosnoozeNight.value {
             UserDefaultsRepository.alertNotLoopingIsSnoozed.value = true
             UserDefaultsRepository.alertNotLoopingSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertNotLoopingIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertNotLoopingSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertMissedBolusAutosnoozeNight.value {
             UserDefaultsRepository.alertMissedBolusIsSnoozed.value = true
             UserDefaultsRepository.alertMissedBolusSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertMissedBolusIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertMissedBolusSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertOverrideStartAutosnoozeNight.value {
             UserDefaultsRepository.alertOverrideStartIsSnoozed.value = true
             UserDefaultsRepository.alertOverrideStartSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertOverrideStartIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertOverrideStartSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertOverrideEndAutosnoozeNight.value {
             UserDefaultsRepository.alertOverrideEndIsSnoozed.value = true
             UserDefaultsRepository.alertOverrideEndSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertOverrideEndIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertOverrideEndSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertCAGEAutosnoozeNight.value {
             UserDefaultsRepository.alertCAGEIsSnoozed.value = true
             UserDefaultsRepository.alertCAGESnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertCAGEIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertCAGESnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertSAGEAutosnoozeNight.value {
             UserDefaultsRepository.alertSAGEIsSnoozed.value = true
             UserDefaultsRepository.alertSAGESnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertSAGEIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertSAGESnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertPumpAutosnoozeNight.value {
             UserDefaultsRepository.alertPumpIsSnoozed.value = true
             UserDefaultsRepository.alertPumpSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertPumpIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertPumpSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertIOBAutosnoozeNight.value {
             UserDefaultsRepository.alertIOBIsSnoozed.value = true
             UserDefaultsRepository.alertIOBSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertIOBIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertIOBSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertCOBAutosnoozeNight.value {
             UserDefaultsRepository.alertCOBIsSnoozed.value = true
             UserDefaultsRepository.alertCOBSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertCOBIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertCOBSnoozedTime", setNil: false, value: snoozeTime)
         }
-        
     }
-    
-    func setPreSnoozeDay(snoozeTime: Date)
-    {
+
+    func setPreSnoozeDay(snoozeTime: Date) {
+        guard let alarms = ViewControllerManager.shared.alarmViewController else { return }
+
         if UserDefaultsRepository.alertUrgentLowAutosnoozeDay.value {
             UserDefaultsRepository.alertUrgentLowIsSnoozed.value = true
             UserDefaultsRepository.alertUrgentLowSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertUrgentLowIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertUrgentLowSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertLowAutosnoozeDay.value {
             UserDefaultsRepository.alertLowIsSnoozed.value = true
             UserDefaultsRepository.alertLowSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertLowIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertLowSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertHighAutosnoozeDay.value {
             UserDefaultsRepository.alertHighIsSnoozed.value = true
             UserDefaultsRepository.alertHighSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertHighIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertHighSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertUrgentHighAutosnoozeDay.value {
             UserDefaultsRepository.alertUrgentHighIsSnoozed.value = true
             UserDefaultsRepository.alertUrgentHighSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertUrgentHighIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertUrgentHighSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertMissedReadingAutosnoozeDay.value {
             UserDefaultsRepository.alertMissedReadingIsSnoozed.value = true
             UserDefaultsRepository.alertMissedReadingSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertMissedReadingIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertMissedReadingSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertFastDropAutosnoozeDay.value {
             UserDefaultsRepository.alertFastDropIsSnoozed.value = true
             UserDefaultsRepository.alertFastDropSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertFastDropIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertFastDropSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertFastRiseAutosnoozeDay.value {
             UserDefaultsRepository.alertFastRiseIsSnoozed.value = true
             UserDefaultsRepository.alertFastRiseSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertFastRiseIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertFastRiseSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertNotLoopingAutosnoozeDay.value {
             UserDefaultsRepository.alertNotLoopingIsSnoozed.value = true
             UserDefaultsRepository.alertNotLoopingSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertNotLoopingIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertNotLoopingSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertMissedBolusAutosnoozeDay.value {
             UserDefaultsRepository.alertMissedBolusIsSnoozed.value = true
             UserDefaultsRepository.alertMissedBolusSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertMissedBolusIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertMissedBolusSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertOverrideStartAutosnoozeDay.value {
             UserDefaultsRepository.alertOverrideStartIsSnoozed.value = true
             UserDefaultsRepository.alertOverrideStartSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertOverrideStartIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertOverrideStartSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertOverrideEndAutosnoozeDay.value {
             UserDefaultsRepository.alertOverrideEndIsSnoozed.value = true
             UserDefaultsRepository.alertOverrideEndSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertOverrideEndIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertOverrideEndSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertCAGEAutosnoozeDay.value {
             UserDefaultsRepository.alertCAGEIsSnoozed.value = true
             UserDefaultsRepository.alertCAGESnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertCAGEIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertCAGESnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertSAGEAutosnoozeDay.value {
             UserDefaultsRepository.alertSAGEIsSnoozed.value = true
             UserDefaultsRepository.alertSAGESnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertSAGEIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertSAGESnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertPumpAutosnoozeDay.value {
             UserDefaultsRepository.alertPumpIsSnoozed.value = true
             UserDefaultsRepository.alertPumpSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertPumpIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertPumpSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertIOBAutosnoozeDay.value {
             UserDefaultsRepository.alertIOBIsSnoozed.value = true
             UserDefaultsRepository.alertIOBSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertIOBIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertIOBSnoozedTime", setNil: false, value: snoozeTime)
         }
         if UserDefaultsRepository.alertCOBAutosnoozeDay.value {
             UserDefaultsRepository.alertCOBIsSnoozed.value = true
             UserDefaultsRepository.alertCOBSnoozedTime.value = snoozeTime
-            guard let alarms = self.tabBarController!.viewControllers?[1] as? AlarmViewController else { return }
             alarms.reloadIsSnoozed(key: "alertCOBIsSnoozed", value: true)
             alarms.reloadSnoozeTime(key: "alertCOBSnoozedTime", setNil: false, value: snoozeTime)
         }
-        
     }
-    
+
     override func viewDidLoad() {
         super.viewDidLoad()
         if UserDefaultsRepository.forceDarkMode.value {

+ 1 - 1
LoopFollow/ViewControllers/WatchSettingsViewController.swift

@@ -36,7 +36,7 @@ class WatchSettingsViewController: FormViewController {
     func showHideNSDetails() {
         var isHidden = false
         var isEnabled = true
-        if UserDefaultsRepository.url.value == "" {
+        if !IsNightscoutEnabled() {
             isHidden = true
             isEnabled = false
         }

+ 107 - 19
LoopFollow/helpers/NightscoutUtils.swift

@@ -43,7 +43,7 @@ class NightscoutUtils {
     }
     
     static func executeRequest<T: Decodable>(eventType: EventType, parameters: [String: String], completion: @escaping (Result<T, Error>) -> Void) {
-        let baseURL = UserDefaultsRepository.url.value
+        let baseURL = ObservableUserDefaults.shared.url.value
         let token = UserDefaultsRepository.token.value
         
         guard let url = NightscoutUtils.constructURL(baseURL: baseURL, token: token, endpoint: eventType.endpoint, parameters: parameters) else {
@@ -75,7 +75,7 @@ class NightscoutUtils {
     
     
     static func executeDynamicRequest(eventType: EventType, parameters: [String: String], completion: @escaping (Result<Any, Error>) -> Void) {
-        let baseURL = UserDefaultsRepository.url.value
+        let baseURL = ObservableUserDefaults.shared.url.value
         let token = UserDefaultsRepository.token.value
         
         guard let url = NightscoutUtils.constructURL(baseURL: baseURL, token: token, endpoint: eventType.endpoint, parameters: parameters) else {
@@ -147,47 +147,73 @@ class NightscoutUtils {
         return components?.url
     }
     
-    static func verifyURLAndToken(urlUser: String, token: String?, completion: @escaping (NightscoutError?) -> Void) {
+    static func verifyURLAndToken(completion: @escaping (NightscoutError?, String?, Bool) -> Void) {
+        let urlUser = ObservableUserDefaults.shared.url.value
+        let token = UserDefaultsRepository.token.value
+
         if urlUser.isEmpty {
-            completion(.emptyAddress)
+            completion(.emptyAddress, nil, false)
             return
         }
-        
+
         guard let _ = URL(string: urlUser), urlUser.hasPrefix("http://") || urlUser.hasPrefix("https://") else {
-            completion(.invalidURL)
+            completion(.invalidURL, nil, false)
             return
         }
-        
-        guard let request = createURLRequest(url: urlUser, token: token, path: "/api/v1/status") else {
-            completion(.invalidURL)
+
+        guard let request = createURLRequest(url: urlUser, token: token, path: "/api/v1/status.json") else {
+            completion(.invalidURL, nil, false)
             return
         }
-        
+
         let task = URLSession.shared.dataTask(with: request) { data, response, error in
+            var nsWriteAuth = false
+
             if let httpResponse = response as? HTTPURLResponse {
                 switch httpResponse.statusCode {
                 case 200:
-                    completion(nil)
+                    if let data = data {
+                        do {
+                            if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
+                               let authorized = jsonResponse["authorized"] as? [String: Any],
+                               let token = authorized["token"] as? String,
+                               let permissionGroups = authorized["permissionGroups"] as? [[String]] {
+
+                                if permissionGroups.contains(where: { $0.contains("*") }) {
+                                    nsWriteAuth = true
+                                } else if permissionGroups.contains(where: { $0.contains("api:treatments:create") }) {
+                                    nsWriteAuth = true
+                                }
+                                completion(nil, token, nsWriteAuth)
+                            } else {
+                                completion(.invalidToken, nil, false)
+                            }
+                        } catch {
+                            completion(.invalidToken, nil, false)
+                        }
+                    } else {
+                        completion(.invalidToken, nil, false)
+                    }
                 case 401:
-                    if token == nil || token!.isEmpty {
-                        completion(.tokenRequired)
+                    if token.isEmpty {
+                        completion(.tokenRequired, nil, false)
                     } else {
-                        completion(.invalidToken) // Change this from "unauthorized"
+                        completion(.invalidToken, nil, false)
                     }
                 default:
-                    completion(.unknown)
+                    completion(.unknown, nil, false)
                 }
-            }  else {
+            } else {
                 if let _ = error {
-                    completion(.siteNotFound)
+                    completion(.siteNotFound, nil, false)
                 } else {
-                    completion(.networkError)
+                    completion(.networkError, nil, false)
                 }
             }
         }
         task.resume()
     }
-    
+
     static func parseDate(_ dateString: String) -> Date? {
         let dateFormatterWithMilliseconds = DateFormatter()
         dateFormatterWithMilliseconds.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
@@ -208,4 +234,66 @@ class NightscoutUtils {
         return nil
     }
 
+    static func executePostRequest<T: Decodable>(eventType: EventType, body: [String: Any], completion: @escaping (Result<T, Error>) -> Void) {
+        verifyURLAndToken { error, jwtToken, nsWriteAuth in
+            if let error = error {
+                DispatchQueue.main.async {
+                    completion(.failure(NSError(domain: "NightscoutUtils", code: -1, userInfo: [NSLocalizedDescriptionKey: "Token verification failed: \(error)"])))
+                }
+                return
+            }
+
+            guard let jwtToken = jwtToken else {
+                DispatchQueue.main.async {
+                    completion(.failure(NSError(domain: "NightscoutUtils", code: -1, userInfo: [NSLocalizedDescriptionKey: "No JWT token received"])))
+                }
+                return
+            }
+
+            let baseURL = ObservableUserDefaults.shared.url.value
+
+            guard let url = URL(string: "\(baseURL)\(eventType.endpoint)") else {
+                DispatchQueue.main.async {
+                    completion(.failure(NSError(domain: "NightscoutUtils", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])))
+                }
+                return
+            }
+
+            var request = URLRequest(url: url)
+            request.httpMethod = "POST"
+            request.setValue("application/json", forHTTPHeaderField: "Content-Type")
+            request.setValue("Bearer \(jwtToken)", forHTTPHeaderField: "Authorization")
+
+            do {
+                request.httpBody = try JSONSerialization.data(withJSONObject: body, options: [])
+            } catch {
+                DispatchQueue.main.async {
+                    completion(.failure(NSError(domain: "NightscoutUtils", code: -2, userInfo: [NSLocalizedDescriptionKey: "Invalid body parameters"])))
+                }
+                return
+            }
+
+            let task = URLSession.shared.dataTask(with: request) { data, response, error in
+                guard let data = data, error == nil else {
+                    DispatchQueue.main.async {
+                        completion(.failure(error!))
+                    }
+                    return
+                }
+
+                let decoder = JSONDecoder()
+                do {
+                    let decodedObject = try decoder.decode(T.self, from: data)
+                    DispatchQueue.main.async {
+                        completion(.success(decodedObject))
+                    }
+                } catch {
+                    DispatchQueue.main.async {
+                        completion(.failure(error))
+                    }
+                }
+            }
+            task.resume()
+        }
+    }
 }

+ 332 - 0
LoopFollow/helpers/TextFieldWithToolBar.swift

@@ -0,0 +1,332 @@
+//
+//  TextFieldWithToolBar.swift
+//  LoopFollow
+//
+//  Created by Jonas Björkert on 2024-07-27.
+//  Copyright © 2024 Jon Fawcett. All rights reserved.
+//
+
+import SwiftUI
+import UIKit
+
+public struct TextFieldWithToolBar: UIViewRepresentable {
+    @Binding var text: Double
+    var placeholder: String
+    var textColor: UIColor
+    var textAlignment: NSTextAlignment
+    var keyboardType: UIKeyboardType
+    var autocapitalizationType: UITextAutocapitalizationType
+    var autocorrectionType: UITextAutocorrectionType
+    var shouldBecomeFirstResponder: Bool
+    var maxLength: Int?
+    var isDismissible: Bool
+    var textFieldDidBeginEditing: (() -> Void)?
+    var numberFormatter: NumberFormatter
+    var allowDecimalSeparator: Bool
+
+    public init(
+        text: Binding<Double>,
+        placeholder: String,
+        textColor: UIColor = .label,
+        textAlignment: NSTextAlignment = .right,
+        keyboardType: UIKeyboardType = .decimalPad,
+        autocapitalizationType: UITextAutocapitalizationType = .none,
+        autocorrectionType: UITextAutocorrectionType = .no,
+        shouldBecomeFirstResponder: Bool = false,
+        maxLength: Int? = nil,
+        isDismissible: Bool = true,
+        textFieldDidBeginEditing: (() -> Void)? = nil,
+        numberFormatter: NumberFormatter,
+        allowDecimalSeparator: Bool = true
+    ) {
+        _text = text
+        self.placeholder = placeholder
+        self.textColor = textColor
+        self.textAlignment = textAlignment
+        self.keyboardType = keyboardType
+        self.autocapitalizationType = autocapitalizationType
+        self.autocorrectionType = autocorrectionType
+        self.shouldBecomeFirstResponder = shouldBecomeFirstResponder
+        self.maxLength = maxLength
+        self.isDismissible = isDismissible
+        self.textFieldDidBeginEditing = textFieldDidBeginEditing
+        self.numberFormatter = numberFormatter
+        self.numberFormatter.numberStyle = .decimal
+        self.allowDecimalSeparator = allowDecimalSeparator
+    }
+
+    public func makeUIView(context: Context) -> UITextField {
+        let textField = UITextField()
+        context.coordinator.textField = textField
+        textField.inputAccessoryView = isDismissible ? makeDoneToolbar(for: textField, context: context) : nil
+        textField.addTarget(context.coordinator, action: #selector(Coordinator.editingDidBegin), for: .editingDidBegin)
+        textField.delegate = context.coordinator
+        if text == 0 { /// show no value initially, i.e. empty String
+            textField.text = ""
+        } else {
+            textField.text = numberFormatter.string(for: text)
+        }
+        textField.placeholder = placeholder
+        return textField
+    }
+
+    private func makeDoneToolbar(for textField: UITextField, context: Context) -> UIToolbar {
+        let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50))
+        let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
+        let doneButton = UIBarButtonItem(
+            image: UIImage(systemName: "keyboard.chevron.compact.down"),
+            style: .done,
+            target: textField,
+            action: #selector(UITextField.resignFirstResponder)
+        )
+        let clearButton = UIBarButtonItem(
+            image: UIImage(systemName: "trash"),
+            style: .plain,
+            target: context.coordinator,
+            action: #selector(Coordinator.clearText)
+        )
+
+        toolbar.items = [clearButton, flexibleSpace, doneButton]
+        toolbar.sizeToFit()
+        return toolbar
+    }
+
+    public func updateUIView(_ textField: UITextField, context: Context) {
+        if text != 0 {
+            let newText = numberFormatter.string(for: text) ?? ""
+            if textField.text != newText {
+                textField.text = newText
+            }
+        }
+
+        textField.textColor = textColor
+        textField.textAlignment = textAlignment
+        textField.keyboardType = keyboardType
+        textField.autocapitalizationType = autocapitalizationType
+        textField.autocorrectionType = autocorrectionType
+
+        if shouldBecomeFirstResponder, !context.coordinator.didBecomeFirstResponder {
+            if textField.window != nil, textField.becomeFirstResponder() {
+                context.coordinator.didBecomeFirstResponder = true
+            }
+        } else if !shouldBecomeFirstResponder, context.coordinator.didBecomeFirstResponder {
+            context.coordinator.didBecomeFirstResponder = false
+        }
+    }
+
+    public func makeCoordinator() -> Coordinator {
+        Coordinator(self, maxLength: maxLength)
+    }
+
+    public final class Coordinator: NSObject {
+        var parent: TextFieldWithToolBar
+        var textField: UITextField?
+        let maxLength: Int?
+        var didBecomeFirstResponder = false
+        let decimalFormatter: NumberFormatter
+
+        init(_ parent: TextFieldWithToolBar, maxLength: Int?) {
+            self.parent = parent
+            self.maxLength = maxLength
+            decimalFormatter = NumberFormatter()
+            decimalFormatter.locale = Locale.current
+            decimalFormatter.numberStyle = .decimal
+        }
+
+        @objc fileprivate func clearText() {
+            parent.text = 0
+            textField?.text = ""
+        }
+
+        @objc fileprivate func editingDidBegin(_ textField: UITextField) {
+            DispatchQueue.main.async {
+                textField.moveCursorToEnd()
+            }
+        }
+    }
+}
+
+extension TextFieldWithToolBar.Coordinator: UITextFieldDelegate {
+    public func textField(
+        _ textField: UITextField,
+        shouldChangeCharactersIn range: NSRange,
+        replacementString string: String
+    ) -> Bool {
+        // Check if the input is a number or the decimal separator
+        let isNumber = CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: string))
+        let isDecimalSeparator = (string == decimalFormatter.decimalSeparator && textField.text?.contains(string) == false)
+
+        // Only proceed if the input is a valid number or decimal separator
+        if isNumber || isDecimalSeparator && parent.allowDecimalSeparator,
+           let currentText = textField.text as NSString?
+        {
+            // Get the proposed new text
+            let proposedTextOriginal = currentText.replacingCharacters(in: range, with: string)
+
+            // Remove thousand separator
+            let proposedText = proposedTextOriginal.replacingOccurrences(of: decimalFormatter.groupingSeparator, with: "")
+
+            // Try to convert proposed text to number
+            let number = parent.numberFormatter.number(from: proposedText) ?? decimalFormatter.number(from: proposedText)
+
+            // Update the binding value if conversion is successful
+            if let number = number {
+                let lastCharIndex = proposedText.index(before: proposedText.endIndex)
+                let hasDecimalSeparator = proposedText.contains(decimalFormatter.decimalSeparator)
+                let hasTrailingZeros = (hasDecimalSeparator && proposedText[lastCharIndex] == "0") || isDecimalSeparator
+                if !hasTrailingZeros
+                {
+                    parent.text = number.doubleValue
+                }
+            } else {
+                parent.text = 0
+            }
+        }
+
+        // Allow the change if it's a valid number or decimal separator
+        return isNumber || isDecimalSeparator && parent.allowDecimalSeparator
+    }
+
+    public func textFieldDidBeginEditing(_: UITextField) {
+        parent.textFieldDidBeginEditing?()
+    }
+}
+
+extension UITextField {
+    func moveCursorToEnd() {
+        dispatchPrecondition(condition: .onQueue(.main))
+        let newPosition = endOfDocument
+        selectedTextRange = textRange(from: newPosition, to: newPosition)
+    }
+}
+
+extension UIApplication {
+    func endEditing() {
+        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
+    }
+}
+
+public struct TextFieldWithToolBarString: UIViewRepresentable {
+    @Binding var text: String
+    var placeholder: String
+    var textAlignment: NSTextAlignment = .right
+    var keyboardType: UIKeyboardType = .default
+    var autocapitalizationType: UITextAutocapitalizationType = .none
+    var autocorrectionType: UITextAutocorrectionType = .no
+    var shouldBecomeFirstResponder: Bool = false
+    var maxLength: Int? = nil
+    var isDismissible: Bool = true
+
+    public func makeUIView(context: Context) -> UITextField {
+        let textField = UITextField()
+        context.coordinator.textField = textField
+        textField.inputAccessoryView = isDismissible ? makeDoneToolbar(for: textField, context: context) : nil
+        textField.addTarget(context.coordinator, action: #selector(Coordinator.editingDidBegin), for: .editingDidBegin)
+        textField.delegate = context.coordinator
+        textField.text = text
+        textField.placeholder = placeholder
+        textField.textAlignment = textAlignment
+        textField.keyboardType = keyboardType
+        textField.autocapitalizationType = autocapitalizationType
+        textField.autocorrectionType = autocorrectionType
+        textField.adjustsFontSizeToFitWidth = true
+        return textField
+    }
+
+    private func makeDoneToolbar(for textField: UITextField, context: Context) -> UIToolbar {
+        let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50))
+        let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
+        let doneButton = UIBarButtonItem(
+            image: UIImage(systemName: "keyboard.chevron.compact.down"),
+            style: .done,
+            target: textField,
+            action: #selector(UITextField.resignFirstResponder)
+        )
+        let clearButton = UIBarButtonItem(
+            image: UIImage(systemName: "trash"),
+            style: .plain,
+            target: context.coordinator,
+            action: #selector(Coordinator.clearText)
+        )
+
+        toolbar.items = [clearButton, flexibleSpace, doneButton]
+        toolbar.sizeToFit()
+        return toolbar
+    }
+
+    public func updateUIView(_ textField: UITextField, context: Context) {
+        if textField.text != text {
+            textField.text = text
+        }
+
+        textField.textAlignment = textAlignment
+        textField.keyboardType = keyboardType
+        textField.autocapitalizationType = autocapitalizationType
+        textField.autocorrectionType = autocorrectionType
+
+        if shouldBecomeFirstResponder, !context.coordinator.didBecomeFirstResponder {
+            if textField.window != nil, textField.becomeFirstResponder() {
+                context.coordinator.didBecomeFirstResponder = true
+            }
+        } else if !shouldBecomeFirstResponder, context.coordinator.didBecomeFirstResponder {
+            context.coordinator.didBecomeFirstResponder = false
+        }
+    }
+
+    public func makeCoordinator() -> Coordinator {
+        Coordinator(self, maxLength: maxLength)
+    }
+
+    public final class Coordinator: NSObject {
+        var parent: TextFieldWithToolBarString
+        var textField: UITextField?
+        let maxLength: Int?
+        var didBecomeFirstResponder = false
+
+        init(_ parent: TextFieldWithToolBarString, maxLength: Int?) {
+            self.parent = parent
+            self.maxLength = maxLength
+        }
+
+        @objc fileprivate func clearText() {
+            parent.text = ""
+            textField?.text = ""
+        }
+
+        @objc fileprivate func editingDidBegin(_ textField: UITextField) {
+            DispatchQueue.main.async {
+                textField.moveCursorToEnd()
+            }
+        }
+    }
+}
+
+extension TextFieldWithToolBarString.Coordinator: UITextFieldDelegate {
+    public func textField(
+        _ textField: UITextField,
+        shouldChangeCharactersIn range: NSRange,
+        replacementString string: String
+    ) -> Bool {
+        guard let currentText = textField.text as NSString? else {
+            return false
+        }
+
+        // Calculate the new text length
+        let newLength = currentText.length + string.count - range.length
+
+        // If there's a maxLength, ensure the new length is within the limit
+        if let maxLength = parent.maxLength, newLength > maxLength {
+            return false
+        }
+
+        // Attempt to replace characters in range with the replacement string
+        let newText = currentText.replacingCharacters(in: range, with: string)
+
+        // Update the binding text state
+        DispatchQueue.main.async {
+            self.parent.text = newText
+        }
+
+        return true
+    }
+}