Jonas Björkert пре 2 година
родитељ
комит
a5d2846d8d

+ 22 - 2
LoopFollow.xcodeproj/project.pbxproj

@@ -9,6 +9,9 @@
 /* Begin PBXBuildFile section */
 		3F1335F351590E573D8E6962 /* Pods_LoopFollow.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D55B42A22051DAD69E89D0 /* Pods_LoopFollow.framework */; };
 		DD07B5C929E2F9C400C6A635 /* NightscoutUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD07B5C829E2F9C400C6A635 /* NightscoutUtils.swift */; };
+		DD13BC752C3FD6210062313B /* InfoType.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD13BC742C3FD6200062313B /* InfoType.swift */; };
+		DD13BC772C3FD64E0062313B /* InfoData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD13BC762C3FD64E0062313B /* InfoData.swift */; };
+		DD13BC792C3FE63A0062313B /* InfoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD13BC782C3FE63A0062313B /* InfoManager.swift */; };
 		DD152D3B24C01B2300361FA2 /* InfoDisplaySettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD152D3A24C01B2300361FA2 /* InfoDisplaySettingsViewController.swift */; };
 		DD493AD52ACF2109009A6922 /* ResumePump.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD493AD42ACF2109009A6922 /* ResumePump.swift */; };
 		DD493AD72ACF2139009A6922 /* SuspendPump.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD493AD62ACF2139009A6922 /* SuspendPump.swift */; };
@@ -22,8 +25,8 @@
 		DD493AE72ACF23CF009A6922 /* DeviceStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD493AE62ACF23CF009A6922 /* DeviceStatus.swift */; };
 		DD493AE92ACF2445009A6922 /* BGData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD493AE82ACF2445009A6922 /* BGData.swift */; };
 		DD608A082C1F584900F91132 /* DeviceStatusLoop.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD608A072C1F584900F91132 /* DeviceStatusLoop.swift */; };
-		DD608A0C2C27415C00F91132 /* BackgroundAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD608A0B2C27415C00F91132 /* BackgroundAlertManager.swift */; };
 		DD608A0A2C23593900F91132 /* SMB.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD608A092C23593900F91132 /* SMB.swift */; };
+		DD608A0C2C27415C00F91132 /* BackgroundAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD608A0B2C27415C00F91132 /* BackgroundAlertManager.swift */; };
 		DD6A935E2BFA6FA2003FFB8E /* DeviceStatusOpenAPS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6A935D2BFA6FA2003FFB8E /* DeviceStatusOpenAPS.swift */; };
 		DD7E19842ACDA50C00DBD158 /* Overrides.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7E19832ACDA50C00DBD158 /* Overrides.swift */; };
 		DD7E19862ACDA59700DBD158 /* BGCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7E19852ACDA59700DBD158 /* BGCheck.swift */; };
@@ -203,6 +206,9 @@
 		059B0FA59AABFE72FE13DDDA /* Pods-LoopFollow.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LoopFollow.release.xcconfig"; path = "Target Support Files/Pods-LoopFollow/Pods-LoopFollow.release.xcconfig"; sourceTree = "<group>"; };
 		A7D55B42A22051DAD69E89D0 /* Pods_LoopFollow.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LoopFollow.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		DD07B5C829E2F9C400C6A635 /* NightscoutUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutUtils.swift; sourceTree = "<group>"; };
+		DD13BC742C3FD6200062313B /* InfoType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoType.swift; sourceTree = "<group>"; };
+		DD13BC762C3FD64E0062313B /* InfoData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoData.swift; sourceTree = "<group>"; };
+		DD13BC782C3FE63A0062313B /* InfoManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoManager.swift; sourceTree = "<group>"; };
 		DD152D3A24C01B2300361FA2 /* InfoDisplaySettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoDisplaySettingsViewController.swift; sourceTree = "<group>"; };
 		DD493AD42ACF2109009A6922 /* ResumePump.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResumePump.swift; sourceTree = "<group>"; };
 		DD493AD62ACF2139009A6922 /* SuspendPump.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuspendPump.swift; sourceTree = "<group>"; };
@@ -216,8 +222,8 @@
 		DD493AE62ACF23CF009A6922 /* DeviceStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceStatus.swift; sourceTree = "<group>"; };
 		DD493AE82ACF2445009A6922 /* BGData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGData.swift; sourceTree = "<group>"; };
 		DD608A072C1F584900F91132 /* DeviceStatusLoop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceStatusLoop.swift; sourceTree = "<group>"; };
-		DD608A0B2C27415C00F91132 /* BackgroundAlertManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundAlertManager.swift; sourceTree = "<group>"; };
 		DD608A092C23593900F91132 /* SMB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMB.swift; sourceTree = "<group>"; };
+		DD608A0B2C27415C00F91132 /* BackgroundAlertManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundAlertManager.swift; sourceTree = "<group>"; };
 		DD6A935D2BFA6FA2003FFB8E /* DeviceStatusOpenAPS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceStatusOpenAPS.swift; sourceTree = "<group>"; };
 		DD7E19832ACDA50C00DBD158 /* Overrides.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Overrides.swift; sourceTree = "<group>"; };
 		DD7E19852ACDA59700DBD158 /* BGCheck.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGCheck.swift; sourceTree = "<group>"; };
@@ -431,6 +437,16 @@
 			path = Pods;
 			sourceTree = "<group>";
 		};
+		DD13BC732C3FD60E0062313B /* InfoTable */ = {
+			isa = PBXGroup;
+			children = (
+				DD13BC742C3FD6200062313B /* InfoType.swift */,
+				DD13BC762C3FD64E0062313B /* InfoData.swift */,
+				DD13BC782C3FE63A0062313B /* InfoManager.swift */,
+			);
+			path = InfoTable;
+			sourceTree = "<group>";
+		};
 		DD493AEA2ACF2761009A6922 /* Treatments */ = {
 			isa = PBXGroup;
 			children = (
@@ -646,6 +662,7 @@
 		FC8DEEE32485D1680075863F /* LoopFollow */ = {
 			isa = PBXGroup;
 			children = (
+				DD13BC732C3FD60E0062313B /* InfoTable */,
 				FCC688702489A57C00A0279D /* Loop Follow.entitlements */,
 				FC8DEEE62485D1ED0075863F /* Info.plist */,
 				FC7CE59A248D334B001F83B8 /* Resources */,
@@ -1011,6 +1028,7 @@
 				DDB0AF522BB1A8BE00AFA48B /* BuildDetails.swift in Sources */,
 				DD493AE72ACF23CF009A6922 /* DeviceStatus.swift in Sources */,
 				FCFEECA2248857A600402A7F /* SettingsViewController.swift in Sources */,
+				DD13BC792C3FE63A0062313B /* InfoManager.swift in Sources */,
 				FCFEECA02488157B00402A7F /* Chart.swift in Sources */,
 				DDCF979424C0D380002C9752 /* UIViewExtension.swift in Sources */,
 				DD7E19882ACDA5DA00DBD158 /* Notes.swift in Sources */,
@@ -1024,6 +1042,8 @@
 				DD493ADB2ACF21A3009A6922 /* Bolus.swift in Sources */,
 				DD152D3B24C01B2300361FA2 /* InfoDisplaySettingsViewController.swift in Sources */,
 				DDF9676E2AD08C6E00C5EB95 /* SiteChange.swift in Sources */,
+				DD13BC772C3FD64E0062313B /* InfoData.swift in Sources */,
+				DD13BC752C3FD6210062313B /* InfoType.swift in Sources */,
 				DDCF979C24C14EFB002C9752 /* AdvancedSettingsViewController.swift in Sources */,
 				FC97881C2485969B00A7906C /* MainViewController.swift in Sources */,
 				DD6A935E2BFA6FA2003FFB8E /* DeviceStatusOpenAPS.swift in Sources */,

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

@@ -30,7 +30,7 @@ extension MainViewController {
     
     // NS Cage Response Processor
     func updateCage(data: [cageData]) {
-        self.clearLastInfoData(index: 7)
+        infoManager.clearInfoData(type: .cage)
         if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: CAGE") }
         if data.count == 0 {
             return
@@ -55,9 +55,9 @@ extension MainViewController {
             formatter.allowedUnits = [ .day, .hour ] // Units to display in the formatted string
             formatter.zeroFormattingBehavior = [ .pad ] // Pad with zeroes where appropriate for the locale
             
-            let formattedDuration = formatter.string(from: secondsAgo)
-            tableData[7].value = formattedDuration ?? ""
+            if let formattedDuration = formatter.string(from: secondsAgo) {
+                infoManager.updateInfoData(type: .cage, value: formattedDuration)
+            }
         }
-        infoTable.reloadData()
     }
 }

+ 22 - 26
LoopFollow/Controllers/Nightscout/DeviceStatus.swift

@@ -83,11 +83,8 @@ extension MainViewController {
         
     // NS Device Status Response Processor
     func updateDeviceStatusDisplay(jsonDeviceStatus: [[String:AnyObject]]) {
-        self.clearLastInfoData(index: 0)
-        self.clearLastInfoData(index: 1)
-        self.clearLastInfoData(index: 3)
-        self.clearLastInfoData(index: 4)
-        self.clearLastInfoData(index: 5)
+        infoManager.clearInfoData(types: [.iob, .cob, .override, .battery, .pump])
+
         if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: device status") }
         if jsonDeviceStatus.count == 0 {
             return
@@ -106,15 +103,15 @@ extension MainViewController {
             if let lastPumpTime = formatter.date(from: (lastPumpRecord["clock"] as! String))?.timeIntervalSince1970  {
                 if let reservoirData = lastPumpRecord["reservoir"] as? Double {
                     latestPumpVolume = reservoirData
-                    tableData[5].value = String(format:"%.0f", reservoirData) + "U"
+                    infoManager.updateInfoData(type: .pump, value: String(format: "%.0f", reservoirData) + "U")
                 } else {
                     latestPumpVolume = 50.0
-                    tableData[5].value = "50+U"
+                    infoManager.updateInfoData(type: .pump, value: "50+U")
                 }
-                
-                if let uploader = lastDeviceStatus?["uploader"] as? [String:AnyObject] {
-                    let upbat = uploader["battery"] as! Double
-                    tableData[4].value = String(format:"%.0f", upbat) + "%"
+
+                if let uploader = lastDeviceStatus?["uploader"] as? [String: AnyObject],
+                   let upbat = uploader["battery"] as? Double {
+                    infoManager.updateInfoData(type: .battery, value: String(format: "%.0f", upbat) + "%")
                     UserDefaultsRepository.deviceBatteryLevel.value = upbat
                 }
             }
@@ -130,31 +127,30 @@ extension MainViewController {
             DeviceStatusOpenAPS(formatter: formatter, lastDeviceStatus: lastDeviceStatus, lastLoopRecord: lastLoopRecord)
         }
         
-        var oText = "" as String
+        var oText = ""
         currentOverride = 1.0
-        if let lastOverride = lastDeviceStatus?["override"] as! [String : AnyObject]? {
-            if lastOverride["active"] as! Bool {
-                
-                let lastCorrection  = lastOverride["currentCorrectionRange"] as! [String: AnyObject]
+        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
-                {
+                } else {
                     oText += "100%"
                 }
+
                 oText += " ("
-                let minValue = lastCorrection["minValue"] as! Double
-                let maxValue = lastCorrection["maxValue"] as! Double
                 oText += bgUnits.toDisplayUnits(String(minValue)) + "-" + bgUnits.toDisplayUnits(String(maxValue)) + ")"
-                
-                tableData[3].value =  oText
             }
+
+            infoManager.updateInfoData(type: .override, value: oText)
+        } else {
+            infoManager.clearInfoData(type: .override)
         }
-        
-        infoTable.reloadData()
-        
+
         // Start the timer based on the timestamp
         let now = dateTimeUtils.getNowTimeIntervalUTC()
         let secondsAgo = now - latestLoopTime

+ 22 - 12
LoopFollow/Controllers/Nightscout/DeviceStatusLoop.swift

@@ -29,14 +29,20 @@ extension MainViewController {
                         
                     }
                 }
-                if let iobdata = lastLoopRecord["iob"] as? [String:AnyObject] {
-                    tableData[0].value = String(format:"%.2f", (iobdata["iob"] as! Double))
-                    latestIOB = String(format:"%.2f", (iobdata["iob"] as! Double))
+                if let iobdata = lastLoopRecord["iob"] as? [String: AnyObject],
+                   let iobValue = iobdata["iob"] as? Double {
+                    let formattedIOB = String(format: "%.2f", iobValue)
+                    infoManager.updateInfoData(type: .iob, value: formattedIOB)
+                    latestIOB = formattedIOB
                 }
-                if let cobdata = lastLoopRecord["cob"] as? [String:AnyObject] {
-                    tableData[1].value = String(format:"%.0f", cobdata["cob"] as! Double)
-                    latestCOB = String(format:"%.0f", cobdata["cob"] as! Double)
+
+                if let cobdata = lastLoopRecord["cob"] as? [String: AnyObject],
+                   let cobValue = cobdata["cob"] as? Double {
+                    let formattedCOB = String(format: "%.0f", cobValue)
+                    infoManager.updateInfoData(type: .cob, value: formattedCOB)
+                    latestCOB = formattedCOB
                 }
+
                 if let predictdata = lastLoopRecord["predicted"] as? [String:AnyObject] {
                     let prediction = predictdata["values"] as! [Double]
                     PredictionLabel.text = bgUnits.toDisplayUnits(String(Int(prediction.last!)))
@@ -59,19 +65,23 @@ extension MainViewController {
                             i += 1
                         }
                         
-                        let predMin = prediction.min()
-                        let predMax = prediction.max()
-                        tableData[9].value = bgUnits.toDisplayUnits(String(predMin!)) + "/" + bgUnits.toDisplayUnits(String(predMax!))
-                        
+                        if let predMin = prediction.min(), let predMax = prediction.max() {
+                            let formattedMin = bgUnits.toDisplayUnits(String(predMin))
+                            let formattedMax = bgUnits.toDisplayUnits(String(predMax))
+                            let value = "\(formattedMin)/\(formattedMax)"
+                            infoManager.updateInfoData(type: .minMax, value: value)
+                        }
+
                         updatePredictionGraph()
                     }
                 } else {
                     predictionData.removeAll()
-                    tableData[9].value = ""
+                    infoManager.clearInfoData(type: .minMax)
                     updatePredictionGraph()
                 }
                 if let recBolus = lastLoopRecord["recommendedBolus"] as? Double {
-                    tableData[8].value = String(format:"%.2fU", recBolus)
+                    let formattedRecBolus = String(format: "%.2fU", recBolus)
+                    infoManager.updateInfoData(type: .recBolus, value: formattedRecBolus)
                     UserDefaultsRepository.deviceRecBolus.value = recBolus
                 }
                 if let loopStatus = lastLoopRecord["recommendedTempBasal"] as? [String:AnyObject] {

+ 25 - 16
LoopFollow/Controllers/Nightscout/DeviceStatusOpenAPS.swift

@@ -24,28 +24,36 @@ extension MainViewController {
                     wasEnacted = true
                 }
                 
-                if let iobdata = lastLoopRecord["iob"] as? [String: AnyObject] {
-                    tableData[0].value = String(format: "%.2f", (iobdata["iob"] as! Double))
-                    latestIOB = String(format: "%.2f", (iobdata["iob"] as! Double))
+                if let iobdata = lastLoopRecord["iob"] as? [String: AnyObject],
+                   let iobValue = iobdata["iob"] as? Double {
+                    let formattedIOB = String(format: "%.2f", iobValue)
+                    infoManager.updateInfoData(type: .iob, value: formattedIOB)
+                    latestIOB = formattedIOB
                 }
-                if let cobdata = lastLoopRecord["enacted"] as? [String: AnyObject] {
-                    tableData[1].value = String(format: "%.0f", cobdata["COB"] as! Double)
-                    latestCOB = String(format: "%.0f", cobdata["COB"] as! Double)
+
+                if let cobdata = lastLoopRecord["enacted"] as? [String: AnyObject],
+                   let cobValue = cobdata["COB"] as? Double {
+                    let formattedCOB = String(format: "%.0f", cobValue)
+                    infoManager.updateInfoData(type: .cob, value: formattedCOB)
+                    latestCOB = formattedCOB
                 }
+
                 if let recbolusdata = lastLoopRecord["enacted"] as? [String: AnyObject],
                    let insulinReq = recbolusdata["insulinReq"] as? Double {
-                    tableData[8].value = String(format: "%.2fU", insulinReq)
+                    let formattedRecBolus = String(format: "%.2fU", insulinReq)
+                    infoManager.updateInfoData(type: .recBolus, value: formattedRecBolus)
                     UserDefaultsRepository.deviceRecBolus.value = insulinReq
                 } else {
-                    tableData[8].value = "N/A"
+                    infoManager.updateInfoData(type: .recBolus, value: "N/A")
                     UserDefaultsRepository.deviceRecBolus.value = 0
                 }
-                
-                if let autosensdata = lastLoopRecord["enacted"] as? [String: AnyObject] {
-                    let sens = autosensdata["sensitivityRatio"] as! Double * 100.0
-                    tableData[11].value = String(format: "%.0f", sens) + "%"
+
+                if let autosensdata = lastLoopRecord["enacted"] as? [String: AnyObject],
+                   let sens = autosensdata["sensitivityRatio"] as? Double {
+                    let formattedSens = String(format: "%.0f", sens * 100.0) + "%"
+                    infoManager.updateInfoData(type: .autosens, value: formattedSens)
                 }
-                
+
                 if let eventualdata = lastLoopRecord["enacted"] as? [String: AnyObject] {
                     if let eventualBGValue = eventualdata["eventualBG"] as? NSNumber {
                         let eventualBGStringValue = String(describing: eventualBGValue)
@@ -97,12 +105,13 @@ extension MainViewController {
                     }
                     
                     if minPredBG != Double.infinity && maxPredBG != -Double.infinity {
-                        tableData[9].value = "\(bgUnits.toDisplayUnits(String(minPredBG)))/\(bgUnits.toDisplayUnits(String(maxPredBG)))"
+                        let value = "\(bgUnits.toDisplayUnits(String(minPredBG)))/\(bgUnits.toDisplayUnits(String(maxPredBG)))"
+                        infoManager.updateInfoData(type: .minMax, value: value)
                     } else {
-                        tableData[9].value = "N/A"
+                        infoManager.updateInfoData(type: .minMax, value: "N/A")
                     }
                 }
-                
+
                 if let loopStatus = lastLoopRecord["recommendedTempBasal"] as? [String: AnyObject] {
                     if let tempBasalTime = formatter.date(from: (loopStatus["timestamp"] as! String))?.timeIntervalSince1970 {
                         var lastBGTime = lastLoopTime

+ 2 - 3
LoopFollow/Controllers/Nightscout/Profile.swift

@@ -28,9 +28,8 @@ extension MainViewController {
             return
         }
                 
-        tableData[12].value = profileData.defaultProfile
-        infoTable.reloadData()
-
+        infoManager.updateInfoData(type: .profile, value: profileData.defaultProfile)
+        
         basalProfile.removeAll()
         for basalEntry in store.basal {
             let entry = basalProfileStruct(value: basalEntry.value, time: basalEntry.time, timeAsSeconds: basalEntry.timeAsSeconds)

+ 5 - 4
LoopFollow/Controllers/Nightscout/SAge.swift

@@ -34,7 +34,8 @@ extension MainViewController {
     
     // NS Sage Response Processor
     func updateSage(data: [sageData]) {
-        self.clearLastInfoData(index: 6)
+        infoManager.clearInfoData(type: .sage)
+
         if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process/Display: SAGE") }
         if data.count == 0 {
             return
@@ -68,9 +69,9 @@ extension MainViewController {
             formatter.allowedUnits = [ .day, .hour] // Units to display in the formatted string
             formatter.zeroFormattingBehavior = [ .pad ] // Pad with zeroes where appropriate for the locale
             
-            let formattedDuration = formatter.string(from: secondsAgo)
-            tableData[6].value = formattedDuration ?? ""
+            if let formattedDuration = formatter.string(from: secondsAgo) {
+                infoManager.updateInfoData(type: .sage, value: formattedDuration)
+            }
         }
-        infoTable.reloadData()
     }
 }

+ 3 - 4
LoopFollow/Controllers/Nightscout/Treatments/Basals.swift

@@ -10,7 +10,8 @@ import Foundation
 extension MainViewController {
     // NS Temp Basal Response Processor
     func processNSBasals(entries: [[String:AnyObject]]) {
-        self.clearLastInfoData(index: 2)
+        infoManager.clearInfoData(type: .basal)
+
         if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: Basal") }
         // due to temp basal durations, we're going to destroy the array and load everything each cycle for the time being.
         basalData.removeAll()
@@ -196,11 +197,9 @@ extension MainViewController {
             basalData.append(endDot)
             
         }
-        tableData[2].value = latestBasal
-        infoTable.reloadData()
         if UserDefaultsRepository.graphBasal.value {
             updateBasalGraph()
         }
-        infoTable.reloadData()
+        infoManager.updateInfoData(type: .basal, value: latestBasal)
     }
 }

+ 1 - 2
LoopFollow/Controllers/Nightscout/Treatments/Carbs.swift

@@ -88,7 +88,6 @@ extension MainViewController {
         }
         
         let resultString = String(format: "%.0f", totalCarbs)
-        tableData[10].value = resultString
-        infoTable.reloadData()
+        infoManager.updateInfoData(type: .carbsToday, value: resultString)
     }
 }

+ 19 - 0
LoopFollow/InfoTable/InfoData.swift

@@ -0,0 +1,19 @@
+//
+//  InfoData.swift
+//  LoopFollow
+//
+//  Created by Jonas Björkert on 2024-07-11.
+//  Copyright © 2024 Jon Fawcett. All rights reserved.
+//
+
+import Foundation
+
+class InfoData {
+    var name: String
+    var value: String
+
+    init(name: String, value: String = "") {
+        self.name = name
+        self.value = value
+    }
+}

+ 47 - 0
LoopFollow/InfoTable/InfoManager.swift

@@ -0,0 +1,47 @@
+//
+//  InfoManager.swift
+//  LoopFollow
+//
+//  Created by Jonas Björkert on 2024-07-11.
+//  Copyright © 2024 Jon Fawcett. All rights reserved.
+//
+
+import Foundation
+import UIKit
+
+class InfoManager {
+    var tableData: [InfoData]
+    weak var tableView: UITableView?
+
+    init(tableView: UITableView) {
+        self.tableData = InfoType.allCases.map { InfoData(name: $0.name) }
+        self.tableView = tableView
+    }
+
+    func updateInfoData(type: InfoType, value: String) {
+        tableData[type.rawValue].value = value
+        tableView?.reloadData()
+    }
+
+    func clearInfoData(type: InfoType) {
+        tableData[type.rawValue].value = ""
+        tableView?.reloadData()
+    }
+
+    func clearInfoData(types: [InfoType]) {
+        for type in types {
+            tableData[type.rawValue].value = ""
+        }
+        tableView?.reloadData()
+    }
+
+    func numberOfRows() -> Int {
+        return UserDefaultsRepository.infoSort.value.filter { UserDefaultsRepository.infoVisible.value[$0] }.count
+    }
+
+    func dataForIndexPath(_ indexPath: IndexPath) -> InfoData {
+        let sortedAndVisibleIndexes = UserDefaultsRepository.infoSort.value.filter { UserDefaultsRepository.infoVisible.value[$0] }
+        let infoIndex = sortedAndVisibleIndexes[indexPath.row]
+        return tableData[infoIndex]
+    }
+}

+ 59 - 0
LoopFollow/InfoTable/InfoType.swift

@@ -0,0 +1,59 @@
+//
+//  InfoType.swift
+//  LoopFollow
+//
+//  Created by Jonas Björkert on 2024-07-11.
+//  Copyright © 2024 Jon Fawcett. All rights reserved.
+//
+
+import Foundation
+
+enum InfoType: Int, CaseIterable {
+/*
+    case iob = 0          // 0
+    case cob = 1          // 1
+    case basal = 2        // 2
+    case override = 3     // 3
+    case battery = 4      // 4
+    case pump = 5         // 5
+    case sage = 6         // 6
+    case cage = 7         // 7
+    case recBolus = 8     // 8
+    case minMax = 9       // 9
+    case carbsToday = 10  // 10
+    case autosens = 11    // 11
+    case profile = 12     // 12
+  */
+    case iob, cob, basal, override, battery, pump, sage, cage, recBolus, minMax, carbsToday, autosens, profile
+
+    var name: String {
+        switch self {
+        case .iob: return "IOB"
+        case .cob: return "COB"
+        case .basal: return "Basal"
+        case .override: return "Override"
+        case .battery: return "Battery"
+        case .pump: return "Pump"
+        case .sage: return "SAGE"
+        case .cage: return "CAGE"
+        case .recBolus: return "Rec. Bolus"
+        case .minMax: return "Min/Max"
+        case .carbsToday: return "Carbs today"
+        case .autosens: return "Autosens"
+        case .profile: return "Profile"
+        }
+    }
+
+    var defaultVisible: Bool {
+        switch self {
+        case .autosens, .profile:
+            return false
+        default:
+            return true
+        }
+    }
+
+    var sortOrder: Int {
+        return self.rawValue
+    }
+}

+ 51 - 54
LoopFollow/ViewControllers/InfoDisplaySettingsViewController.swift

@@ -14,17 +14,16 @@ import EventKitUI
 class InfoDisplaySettingsViewController: FormViewController {
     var appStateController: AppStateController?
 
-    
     override func viewDidLoad() {
         print("Display Load")
         super.viewDidLoad()
         if UserDefaultsRepository.forceDarkMode.value {
             overrideUserInterfaceStyle = .dark
         }
-         
+
         createForm()
     }
-    
+
     private func createForm() {
         form
         +++ Section("General")
@@ -33,64 +32,62 @@ class InfoDisplaySettingsViewController: FormViewController {
             row.tag = "hideInfoTable"
             row.value = UserDefaultsRepository.hideInfoTable.value
         }.onChange { [weak self] row in
-                    guard let value = row.value else { return }
-                    UserDefaultsRepository.hideInfoTable.value = value           
+            guard let value = row.value else { return }
+            UserDefaultsRepository.hideInfoTable.value = value
+        }
+
+        +++ MultivaluedSection(multivaluedOptions: .Reorder, header: "Information Display Settings", footer: "Arrange/Enable Information Desired") {
+
+            $0.tag = "InfoDisplay"
+
+            for i in 0..<UserDefaultsRepository.infoSort.value.count {
+                let sortedIndex = UserDefaultsRepository.infoSort.value[i]
+                let infoType = InfoType(rawValue: sortedIndex)!
+
+                $0 <<< TextRow() { row in
+                    if(UserDefaultsRepository.infoVisible.value[sortedIndex]) {
+                        row.title = "\u{2713}\t\(infoType.name)"
+                    } else {
+                        row.title = "\u{2001}\t\(infoType.name)"
+                    }
+                }.onCellSelection { (cell, row) in
+                    let i = row.indexPath!.row
+                    let sortedIndex = UserDefaultsRepository.infoSort.value[i]
+                    UserDefaultsRepository.infoVisible.value[sortedIndex] = !UserDefaultsRepository.infoVisible.value[sortedIndex]
+
+                    self.tableView.reloadData()
+                }.cellSetup { (cell, row) in
+                    cell.textField.isUserInteractionEnabled = false
+                }.cellUpdate { (cell, row) in
+                    let sortedIndex = UserDefaultsRepository.infoSort.value[i]
+                    if(UserDefaultsRepository.infoVisible.value[sortedIndex]) {
+                        row.title = "\u{2713}\t\(infoType.name)"
+                    } else {
+                        row.title = "\u{2001}\t\(infoType.name)"
+                    }
+                    self.appStateController!.infoDataSettingsChanged = true
+                }
+            }
+        }
+
+        +++ ButtonRow() {
+            $0.title = "DONE"
+        }.onCellSelection { (row, arg)  in
+            self.dismiss(animated: true, completion: nil)
         }
-        
-        +++ MultivaluedSection(multivaluedOptions: .Reorder, header: "Information Display Settings", footer: "Arrage/Enable Information Desired") {
-        
-           // TODO: add the other display values
-           $0.tag = "InfoDisplay"
-           
-            for i in 0..<UserDefaultsRepository.infoNames.value.count {
-              $0 <<< TextRow() { row in
-                if(UserDefaultsRepository.infoVisible.value[UserDefaultsRepository.infoSort.value[i]]) {
-                    row.title = "\u{2713}\t\(UserDefaultsRepository.infoNames.value[UserDefaultsRepository.infoSort.value[i]])"
-                 } else {
-                    row.title = "\u{2001}\t\(UserDefaultsRepository.infoNames.value[UserDefaultsRepository.infoSort.value[i]])"
-                 }
-              }.onCellSelection{(cell, row) in
-                let i = row.indexPath!.row
-                UserDefaultsRepository.infoVisible.value[UserDefaultsRepository.infoSort.value[i]] = !UserDefaultsRepository.infoVisible.value[UserDefaultsRepository.infoSort.value[i]]
-                
-                self.tableView.reloadData()
-                
-                //print("\(row.title)")
-                //print("\(row.indexPath?.row)")
-              }.cellSetup { (cell, row) in
-                 cell.textField.isUserInteractionEnabled = false
-              }.cellUpdate{ (cell, row) in
-                if(UserDefaultsRepository.infoVisible.value[UserDefaultsRepository.infoSort.value[i]]) {
-                    row.title = "\u{2713}\t\(UserDefaultsRepository.infoNames.value[UserDefaultsRepository.infoSort.value[i]])"
-                 } else {
-                    row.title = "\u{2001}\t\(UserDefaultsRepository.infoNames.value[UserDefaultsRepository.infoSort.value[i]])"
-                 }
-                 self.appStateController!.infoDataSettingsChanged = true
-              }
-           }
-       }
-    
-       +++ ButtonRow() {
-          $0.title = "DONE"
-       }.onCellSelection { (row, arg)  in
-          self.dismiss(animated:true, completion: nil)
-       }
     }
-    
+
     override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
-        //let view = tableView
         let sourceIndex = sourceIndexPath.row
         let destIndex = destinationIndexPath.row
-        
+
         // new sort
         if(destIndex != sourceIndex ) {
-           self.appStateController!.infoDataSettingsChanged = true
-           
+            self.appStateController!.infoDataSettingsChanged = true
+
             let tmpVal = UserDefaultsRepository.infoSort.value[sourceIndex]
-            UserDefaultsRepository.infoSort.value.remove(at:sourceIndex)
-            UserDefaultsRepository.infoSort.value.insert(tmpVal, at:destIndex)
-       
+            UserDefaultsRepository.infoSort.value.remove(at: sourceIndex)
+            UserDefaultsRepository.infoSort.value.insert(tmpVal, at: destIndex)
         }
-        
     }
- }
+}

+ 18 - 91
LoopFollow/ViewControllers/MainViewController.swift

@@ -41,16 +41,6 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
 
     let speechSynthesizer = AVSpeechSynthesizer()
 
-    // Data Table class
-    class infoData {
-        public var name: String
-        public var value: String
-        init(name: String, value: String) {
-            self.name = name
-            self.value = value
-        }
-    }
-    
     var appStateController: AppStateController?
 
     // Variables for BG Charts
@@ -100,9 +90,8 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
     var graphNowTimer = Timer()
     
     // Info Table Setup
-    var tableData : [infoData] = []
-    var derivedTableData: [infoData] = []
-    
+    var infoManager: InfoManager!
+
     var bgData: [ShareGlucoseData] = []
     var basalProfile: [basalProfileStruct] = []
     var basalData: [basalGraphStruct] = []
@@ -157,54 +146,28 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
     override func viewDidLoad() {
         super.viewDidLoad()
 
-        // reset the infoTable names in case we add or delete items
-        UserDefaultsRepository.infoNames.value.removeAll()
-        UserDefaultsRepository.infoNames.value.append("IOB")
-        UserDefaultsRepository.infoNames.value.append("COB")
-        UserDefaultsRepository.infoNames.value.append("Basal")
-        UserDefaultsRepository.infoNames.value.append("Override")
-        UserDefaultsRepository.infoNames.value.append("Battery")
-        UserDefaultsRepository.infoNames.value.append("Pump")
-        UserDefaultsRepository.infoNames.value.append("SAGE")
-        UserDefaultsRepository.infoNames.value.append("CAGE")
-        UserDefaultsRepository.infoNames.value.append("Rec. Bolus")
-        UserDefaultsRepository.infoNames.value.append("Min/Max") //Previously "Pred."
-        UserDefaultsRepository.infoNames.value.append("Carbs today")
-        UserDefaultsRepository.infoNames.value.append("Autosens")
-        UserDefaultsRepository.infoNames.value.append("Profile")
-
+        // Synchronize info types to ensure arrays are the correct size
+        UserDefaultsRepository.synchronizeInfoTypes()
+        
         // Reset deprecated settings
         UserDefaultsRepository.debugLog.value = false;
-        UserDefaultsRepository.alwaysDownloadAllBG.value = true;
         
-        // table view
-        //infoTable.layer.borderColor = UIColor.darkGray.cgColor
-        //infoTable.layer.borderWidth = 1.0
-        //infoTable.layer.cornerRadius = 6
         infoTable.rowHeight = 21
         infoTable.dataSource = self
-        infoTable.tableFooterView = UIView(frame: .zero) // get rid of extra rows
+        infoTable.tableFooterView = UIView(frame: .zero)
         infoTable.bounces = false
         infoTable.addBorder(toSide: .Left, withColor: UIColor.darkGray.cgColor, andThickness: 2)
         
-        // initialize the tableData
-        self.tableData = []
-        for i in 0..<UserDefaultsRepository.infoNames.value.count {
-            self.tableData.append(infoData(name:UserDefaultsRepository.infoNames.value[i], value:""))
-        }
-        createDerivedData()
-        
+        self.infoManager = InfoManager(tableView: infoTable)
+
         smallGraphHeightConstraint.constant = CGFloat(UserDefaultsRepository.smallGraphHeight.value)
         self.view.layoutIfNeeded()
 
-        // TODO: need non-us server ?
         let shareUserName = UserDefaultsRepository.shareUserName.value
         let sharePassword = UserDefaultsRepository.sharePassword.value
         let shareServer = UserDefaultsRepository.shareServer.value == "US" ?KnownShareServers.US.rawValue : KnownShareServers.NON_US.rawValue
         dexShare = ShareClient(username: shareUserName, password: sharePassword, shareServer: shareServer )
         
-        //print("Share: \(dexShare)")
-        
         // setup show/hide small graph and stats
         BGChartFull.isHidden = !UserDefaultsRepository.showSmallGraph.value
         statsView.isHidden = !UserDefaultsRepository.showStats.value
@@ -216,13 +179,7 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
             overrideUserInterfaceStyle = .dark
             self.tabBarController?.overrideUserInterfaceStyle = .dark
         }
-        // Disable the snoozer tab unless an alarm is active
-        //let tabBarControllerItems = self.tabBarController?.tabBar.items
-        //if let arrayOfTabBarItems = tabBarControllerItems as AnyObject as? NSArray{
-        //    snoozeTabItem = arrayOfTabBarItems[2] as! UITabBarItem
-        //}
-        //snoozeTabItem.isEnabled = false;
-        
+
         // Load the snoozer tab
         guard let snoozer = self.tabBarController!.viewControllers?[2] as? SnoozeViewController else { return }
         snoozer.loadViewIfNeeded()
@@ -379,7 +336,6 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
                 appState.generalSettingsChanges = 0
             }
             if appState.infoDataSettingsChanged {
-                createDerivedData()
                 self.infoTable.reloadData()
                 
                 // reset
@@ -389,36 +345,18 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
             // add more processing of the app state
         }
     }
-    
-    private func createDerivedData() {
-        let currentCount = UserDefaultsRepository.infoSort.value.count
-        if currentCount < self.tableData.count {
-            for i in currentCount..<self.tableData.count {
-                UserDefaultsRepository.infoSort.value.append(i)
-            }
-        }
-        
-        self.derivedTableData = []
-        while UserDefaultsRepository.infoVisible.value.count < self.tableData.count {
-            UserDefaultsRepository.infoVisible.value.append(false)
-        }
-        for i in 0..<self.tableData.count {
-            if(UserDefaultsRepository.infoVisible.value[UserDefaultsRepository.infoSort.value[i]]) {
-                self.derivedTableData.append(self.tableData[UserDefaultsRepository.infoSort.value[i]])
-            }
-        }
-    }
-    
+
     // Info Table Functions
     func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
-        //return tableData.count
-        return derivedTableData.count
-        
+        guard let infoManager = infoManager else {
+            return 0
+        }
+        return infoManager.numberOfRows()
     }
-    
+
     func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
         let cell = tableView.dequeueReusableCell(withIdentifier: "LabelCell", for: indexPath)
-        let values = derivedTableData[indexPath.row]
+        let values = infoManager.dataForIndexPath(indexPath)
         cell.textLabel?.text = values.name
         cell.detailTextLabel?.text = values.value
         return cell
@@ -512,11 +450,6 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
         showHideNSDetails()
     }
     
-    //Clear the info data before next pull. This ensures we aren't displaying old data if something fails.
-    func clearLastInfoData(index: Int){
-        tableData[index].value = ""
-    }
-    
     func stringFromTimeInterval(interval: TimeInterval) -> String {
         let interval = Int(interval)
         let minutes = (interval / 60) % 60
@@ -562,9 +495,7 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
             UIApplication.shared.applicationIconBadgeNumber = 0
         }
     }
-    
-    
-    
+
     func setBGTextColor() {
         if bgData.count > 0 {
             guard let snoozer = self.tabBarController!.viewControllers?[2] as? SnoozeViewController else { return }
@@ -737,12 +668,8 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
             }
             debug.debugTextView.text += logText
         }
-        
-        
-        
     }
-    
-    
+
     // General Notifications
     
     func sendGeneralNotification(_ sender: Any, title: String, subtitle: String, body: String, timer: TimeInterval) {

+ 24 - 23
LoopFollow/repository/UserDefaults.swift

@@ -10,33 +10,35 @@
 //
 //
 
-
-
 import Foundation
 import UIKit
 
 class UserDefaultsRepository {
-    
-    // DisplayValues total
-    static let infoDataTotal = UserDefaultsValue<Int>(key: "infoDataTotal", default: 0)
-    static let infoNames = UserDefaultsValue<[String]>(key: "infoNames", default: [
-        "IOB",
-        "COB",
-        "Basal",
-        "Override",
-        "Battery",
-        "Pump",
-        "SAGE",
-        "CAGE",
-        "Rec. Bolus",
-        "Min/Max", //Previously "Pred."
-        "Carbs today",
-        "Autosens",
-        "Profile"])
-    static let infoSort = UserDefaultsValue<[Int]>(key: "infoSort", default: [0,1,2,3,4,5,6,7,8,9,10,11,12])
-    static let infoVisible = UserDefaultsValue<[Bool]>(key: "infoVisible", default: [true,true,true,true,true,true,true,true,true,true,true,false,false])
+    static let infoSort = UserDefaultsValue<[Int]>(key: "infoSort", default: InfoType.allCases.map { $0.sortOrder })
+    static let infoVisible = UserDefaultsValue<[Bool]>(key: "infoVisible", default: InfoType.allCases.map { $0.defaultVisible })
+
+    static func synchronizeInfoTypes() {
+        // Ensure infoSort array is the correct size
+        var sortArray = infoSort.value
+        if sortArray.count != InfoType.allCases.count {
+            sortArray = InfoType.allCases.map { $0.sortOrder }
+            infoSort.value = sortArray
+        }
+
+        // Ensure infoVisible array is the correct size
+        var visibleArray = infoVisible.value
+        if visibleArray.count < InfoType.allCases.count {
+            for infoType in InfoType.allCases[visibleArray.count..<InfoType.allCases.count] {
+                visibleArray.append(infoType.defaultVisible)
+            }
+        } else if visibleArray.count > InfoType.allCases.count {
+            visibleArray = Array(visibleArray.prefix(InfoType.allCases.count))
+        }
+        infoVisible.value = visibleArray
+    }
+
     static let hideInfoTable = UserDefaultsValue<Bool>(key: "hideInfoTable", default: false)
-    
+
     // Nightscout Settings
     static let showNS = UserDefaultsValue<Bool>(key: "showNS", default: false)
     static let url = UserDefaultsValue<String>(key: "url", default: "")
@@ -107,7 +109,6 @@ class UserDefaultsRepository {
     static let graphBolus = UserDefaultsValue<Bool>(key: "graphBolus", default: true)
     static let graphCarbs = UserDefaultsValue<Bool>(key: "graphCarbs", default: true)
     static let debugLog = UserDefaultsValue<Bool>(key: "debugLog", default: false)
-    static let alwaysDownloadAllBG = UserDefaultsValue<Bool>(key: "alwaysDownloadAllBG", default: true)
     static let bgUpdateDelay = UserDefaultsValue<Int>(key: "bgUpdateDelay", default: 10)
     static let downloadDays = UserDefaultsValue<Int>(key: "downloadDays", default: 1)