Kaynağa Gözat

Merge branch 'bdb_dev' into bdb_dev

Jon B Mårtensson 3 yıl önce
ebeveyn
işleme
15a5c2c051

+ 32 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -21,6 +21,10 @@
 		19854F492961C3E500941627 /* DurationButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19854F482961C3E500941627 /* DurationButton.swift */; };
 		19854F492961C3E500941627 /* DurationButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19854F482961C3E500941627 /* DurationButton.swift */; };
 		199561C1275E61A50077B976 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 199561C0275E61A50077B976 /* HealthKit.framework */; };
 		199561C1275E61A50077B976 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 199561C0275E61A50077B976 /* HealthKit.framework */; };
 		19B0EF2128F6D66200069496 /* Statistics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B0EF2028F6D66200069496 /* Statistics.swift */; };
 		19B0EF2128F6D66200069496 /* Statistics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B0EF2028F6D66200069496 /* Statistics.swift */; };
+		19D466A329AA2B80004D5F33 /* FPUConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19D466A229AA2B80004D5F33 /* FPUConfigDataFlow.swift */; };
+		19D466A529AA2BD4004D5F33 /* FPUConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19D466A429AA2BD4004D5F33 /* FPUConfigProvider.swift */; };
+		19D466A729AA2C22004D5F33 /* FPUConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19D466A629AA2C22004D5F33 /* FPUConfigStateModel.swift */; };
+		19D466AA29AA3099004D5F33 /* FPUConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19D466A929AA3099004D5F33 /* FPUConfigRootView.swift */; };
 		1BBB001DAD60F3B8CEA4B1C7 /* ISFEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505E09DC17A0C3D0AF4B66FE /* ISFEditorStateModel.swift */; };
 		1BBB001DAD60F3B8CEA4B1C7 /* ISFEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505E09DC17A0C3D0AF4B66FE /* ISFEditorStateModel.swift */; };
 		1D845DF2E3324130E1D95E67 /* DataTableProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60744C3E9BB3652895C908CC /* DataTableProvider.swift */; };
 		1D845DF2E3324130E1D95E67 /* DataTableProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60744C3E9BB3652895C908CC /* DataTableProvider.swift */; };
 		23888883D4EA091C88480FF2 /* BolusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19984D62EFC0035A9E9644D /* BolusProvider.swift */; };
 		23888883D4EA091C88480FF2 /* BolusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19984D62EFC0035A9E9644D /* BolusProvider.swift */; };
@@ -487,6 +491,10 @@
 		19B0EF2028F6D66200069496 /* Statistics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Statistics.swift; sourceTree = "<group>"; };
 		19B0EF2028F6D66200069496 /* Statistics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Statistics.swift; sourceTree = "<group>"; };
 		19C166682756EFBD00ED12E3 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/InfoPlist.strings; sourceTree = "<group>"; };
 		19C166682756EFBD00ED12E3 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/InfoPlist.strings; sourceTree = "<group>"; };
 		19C166692756EFBD00ED12E3 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = "<group>"; };
 		19C166692756EFBD00ED12E3 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = "<group>"; };
+		19D466A229AA2B80004D5F33 /* FPUConfigDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPUConfigDataFlow.swift; sourceTree = "<group>"; };
+		19D466A429AA2BD4004D5F33 /* FPUConfigProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPUConfigProvider.swift; sourceTree = "<group>"; };
+		19D466A629AA2C22004D5F33 /* FPUConfigStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPUConfigStateModel.swift; sourceTree = "<group>"; };
+		19D466A929AA3099004D5F33 /* FPUConfigRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPUConfigRootView.swift; sourceTree = "<group>"; };
 		1CAE81192B118804DCD23034 /* SnoozeProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SnoozeProvider.swift; sourceTree = "<group>"; };
 		1CAE81192B118804DCD23034 /* SnoozeProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SnoozeProvider.swift; sourceTree = "<group>"; };
 		212E8BFE6D66EE65AA26A114 /* CalibrationsProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CalibrationsProvider.swift; sourceTree = "<group>"; };
 		212E8BFE6D66EE65AA26A114 /* CalibrationsProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CalibrationsProvider.swift; sourceTree = "<group>"; };
 		223EC0494F55A91E3EA69EF4 /* BolusStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusStateModel.swift; sourceTree = "<group>"; };
 		223EC0494F55A91E3EA69EF4 /* BolusStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BolusStateModel.swift; sourceTree = "<group>"; };
@@ -927,6 +935,25 @@
 			path = Main;
 			path = Main;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
+		19D466A129AA2B0A004D5F33 /* FPUConfig */ = {
+			isa = PBXGroup;
+			children = (
+				19D466A229AA2B80004D5F33 /* FPUConfigDataFlow.swift */,
+				19D466A429AA2BD4004D5F33 /* FPUConfigProvider.swift */,
+				19D466A629AA2C22004D5F33 /* FPUConfigStateModel.swift */,
+				19D466A829AA306E004D5F33 /* View */,
+			);
+			path = FPUConfig;
+			sourceTree = "<group>";
+		};
+		19D466A829AA306E004D5F33 /* View */ = {
+			isa = PBXGroup;
+			children = (
+				19D466A929AA3099004D5F33 /* FPUConfigRootView.swift */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
 		29B478DF61BF8D270F7D8954 /* Snooze */ = {
 		29B478DF61BF8D270F7D8954 /* Snooze */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
@@ -949,6 +976,7 @@
 		3811DE0325C9D31700A708ED /* Modules */ = {
 		3811DE0325C9D31700A708ED /* Modules */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				19D466A129AA2B0A004D5F33 /* FPUConfig */,
 				F90692CD274B99850037068D /* HealthKit */,
 				F90692CD274B99850037068D /* HealthKit */,
 				6DC5D590658EF8B8DF94F9F5 /* AddCarbs */,
 				6DC5D590658EF8B8DF94F9F5 /* AddCarbs */,
 				A9A4C88374496B3C89058A89 /* AddTempTarget */,
 				A9A4C88374496B3C89058A89 /* AddTempTarget */,
@@ -2233,6 +2261,7 @@
 				38E44537274E411700EC9A94 /* Disk+Helpers.swift in Sources */,
 				38E44537274E411700EC9A94 /* Disk+Helpers.swift in Sources */,
 				388E5A6025B6F2310019842D /* Autosens.swift in Sources */,
 				388E5A6025B6F2310019842D /* Autosens.swift in Sources */,
 				3811DE8F25C9D80400A708ED /* User.swift in Sources */,
 				3811DE8F25C9D80400A708ED /* User.swift in Sources */,
+				19D466A329AA2B80004D5F33 /* FPUConfigDataFlow.swift in Sources */,
 				3811DEB225C9D88300A708ED /* KeychainItemAccessibility.swift in Sources */,
 				3811DEB225C9D88300A708ED /* KeychainItemAccessibility.swift in Sources */,
 				385CEAC425F2F154002D6D5B /* AnnouncementsStorage.swift in Sources */,
 				385CEAC425F2F154002D6D5B /* AnnouncementsStorage.swift in Sources */,
 				38AEE73D25F0200C0013F05B /* FreeAPSSettings.swift in Sources */,
 				38AEE73D25F0200C0013F05B /* FreeAPSSettings.swift in Sources */,
@@ -2271,6 +2300,7 @@
 				38DAB280260CBB7F00F74C1A /* PumpView.swift in Sources */,
 				38DAB280260CBB7F00F74C1A /* PumpView.swift in Sources */,
 				3811DEB125C9D88300A708ED /* Keychain.swift in Sources */,
 				3811DEB125C9D88300A708ED /* Keychain.swift in Sources */,
 				382C133725F13A1E00715CE1 /* InsulinSensitivities.swift in Sources */,
 				382C133725F13A1E00715CE1 /* InsulinSensitivities.swift in Sources */,
+				19D466A529AA2BD4004D5F33 /* FPUConfigProvider.swift in Sources */,
 				383948D625CD4D8900E91849 /* FileStorage.swift in Sources */,
 				383948D625CD4D8900E91849 /* FileStorage.swift in Sources */,
 				3811DE4125C9D4A100A708ED /* SettingsRootView.swift in Sources */,
 				3811DE4125C9D4A100A708ED /* SettingsRootView.swift in Sources */,
 				38192E04261B82FA0094D973 /* ReachabilityManager.swift in Sources */,
 				38192E04261B82FA0094D973 /* ReachabilityManager.swift in Sources */,
@@ -2289,6 +2319,7 @@
 				3811DF0225CA9FEA00A708ED /* Credentials.swift in Sources */,
 				3811DF0225CA9FEA00A708ED /* Credentials.swift in Sources */,
 				389A572026079BAA00BC102F /* Interpolation.swift in Sources */,
 				389A572026079BAA00BC102F /* Interpolation.swift in Sources */,
 				38B4F3C625E5017E00E76A18 /* NotificationCenter.swift in Sources */,
 				38B4F3C625E5017E00E76A18 /* NotificationCenter.swift in Sources */,
+				19D466A729AA2C22004D5F33 /* FPUConfigStateModel.swift in Sources */,
 				38E44528274E401C00EC9A94 /* Protected.swift in Sources */,
 				38E44528274E401C00EC9A94 /* Protected.swift in Sources */,
 				3811DEB625C9D88300A708ED /* UnlockManager.swift in Sources */,
 				3811DEB625C9D88300A708ED /* UnlockManager.swift in Sources */,
 				E00EEC0827368630002FF094 /* NetworkAssembly.swift in Sources */,
 				E00EEC0827368630002FF094 /* NetworkAssembly.swift in Sources */,
@@ -2375,6 +2406,7 @@
 				AD3D2CD42CD01B9EB8F26522 /* PumpConfigDataFlow.swift in Sources */,
 				AD3D2CD42CD01B9EB8F26522 /* PumpConfigDataFlow.swift in Sources */,
 				53F2382465BF74DB1A967C8B /* PumpConfigProvider.swift in Sources */,
 				53F2382465BF74DB1A967C8B /* PumpConfigProvider.swift in Sources */,
 				5D16287A969E64D18CE40E44 /* PumpConfigStateModel.swift in Sources */,
 				5D16287A969E64D18CE40E44 /* PumpConfigStateModel.swift in Sources */,
+				19D466AA29AA3099004D5F33 /* FPUConfigRootView.swift in Sources */,
 				E974172296125A5AE99E634C /* PumpConfigRootView.swift in Sources */,
 				E974172296125A5AE99E634C /* PumpConfigRootView.swift in Sources */,
 				448B6FCB252BD4796E2960C0 /* PumpSettingsEditorDataFlow.swift in Sources */,
 				448B6FCB252BD4796E2960C0 /* PumpSettingsEditorDataFlow.swift in Sources */,
 				38E44536274E411700EC9A94 /* Disk.swift in Sources */,
 				38E44536274E411700EC9A94 /* Disk.swift in Sources */,

+ 2 - 2
FreeAPS/Resources/Base.lproj/InfoPlist.strings

@@ -14,7 +14,7 @@
 "NSCalendarsUsageDescription" = "Calendar is used to create a new glucose events.";
 "NSCalendarsUsageDescription" = "Calendar is used to create a new glucose events.";
 
 
 /* Privacy - Health Update Usage Description */
 /* Privacy - Health Update Usage Description */
-"NSHealthUpdateUsageDescription" = "Health App is used to store blood glucose data";
+"NSHealthUpdateUsageDescription" = "Health App is used to store blood glucose, insulin and carbohydrates";
 
 
 /* Privacy - Health Share Usage Description */
 /* Privacy - Health Share Usage Description */
-"NSHealthShareUsageDescription" = "Health App is used to store blood glucose data";
+"NSHealthShareUsageDescription" = "Health App is used to store blood glucose, insulin and carbohydrates";

+ 2 - 2
FreeAPS/Resources/Info.plist

@@ -71,9 +71,9 @@
 	<key>NSFaceIDUsageDescription</key>
 	<key>NSFaceIDUsageDescription</key>
 	<string>For authorized acces to bolus</string>
 	<string>For authorized acces to bolus</string>
 	<key>NSHealthShareUsageDescription</key>
 	<key>NSHealthShareUsageDescription</key>
-	<string>Health App is used to store blood glucose data</string>
+	<string>Health App is used to store blood glucose, carbs and insulin</string>
 	<key>NSHealthUpdateUsageDescription</key>
 	<key>NSHealthUpdateUsageDescription</key>
-	<string>Health App is used to store blood glucose data</string>
+	<string>Health App is used to store blood glucose, carbs and insulin</string>
 	<key>NSHumanReadableCopyright</key>
 	<key>NSHumanReadableCopyright</key>
 	<string>$(COPYRIGHT_NOTICE)</string>
 	<string>$(COPYRIGHT_NOTICE)</string>
 	<key>UIApplicationSceneManifest</key>
 	<key>UIApplicationSceneManifest</key>

+ 2 - 2
FreeAPS/Resources/ar.lproj/InfoPlist.strings

@@ -14,7 +14,7 @@
 "NSCalendarsUsageDescription" = "Calendar is used to create a new glucose events.";
 "NSCalendarsUsageDescription" = "Calendar is used to create a new glucose events.";
 
 
 /* Privacy - Health Update Usage Description */
 /* Privacy - Health Update Usage Description */
-"NSHealthUpdateUsageDescription" = "Health App is used to store blood glucose data";
+"NSHealthUpdateUsageDescription" = "Health App is used to store blood glucose, carbs and insulin";
 
 
 /* Privacy - Health Share Usage Description */
 /* Privacy - Health Share Usage Description */
-"NSHealthShareUsageDescription" = "Health App is used to store blood glucose data";
+"NSHealthShareUsageDescription" = "Health App is used to store blood glucose, carbs and insulin";

+ 5 - 1
FreeAPS/Resources/json/defaults/freeaps/freeaps_settings.json

@@ -21,5 +21,9 @@
     "carbsRequiredThreshold": 10,
     "carbsRequiredThreshold": 10,
     "useAppleHealth": false,
     "useAppleHealth": false,
     "animatedBackground": false,
     "animatedBackground": false,
-    "displayStatistics": false
+    "displayStatistics": false,
+    "useFPUconversion": false
+    "individualAdjustmentFactor": 0.5,
+    "timeCap": 8,
+    "minuteInterval": 60
 }
 }

+ 2 - 2
FreeAPS/Resources/sv.lproj/InfoPlist.strings

@@ -14,7 +14,7 @@
 "NSCalendarsUsageDescription" = "Kalendern används för att skapa kalenderhändelser för glukosvärden.";
 "NSCalendarsUsageDescription" = "Kalendern används för att skapa kalenderhändelser för glukosvärden.";
 
 
 /* Privacy - Health Update Usage Description */
 /* Privacy - Health Update Usage Description */
-"NSHealthUpdateUsageDescription" = "Appen Hälsa används för att lagra blodsockervärden etc.";
+"NSHealthUpdateUsageDescription" = "Appen Hälsa används för att lagra blodsockervärden, insulin och kolhydrater.";
 
 
 /* Privacy - Health Share Usage Description */
 /* Privacy - Health Share Usage Description */
-"NSHealthShareUsageDescription" = "Appen Hälsa används för att lagra blodsockervärden etc.";
+"NSHealthShareUsageDescription" = "Appen Hälsa används för att lagra blodsockervärden, insulin och kolhydrater.";

+ 20 - 0
FreeAPS/Sources/Models/FreeAPSSettings.swift

@@ -25,6 +25,10 @@ struct FreeAPSSettings: JSON, Equatable {
     var carbsRequiredThreshold: Decimal = 10
     var carbsRequiredThreshold: Decimal = 10
     var animatedBackground: Bool = false
     var animatedBackground: Bool = false
     var displayStatistics: Bool = false
     var displayStatistics: Bool = false
+    var useFPUconversion: Bool = false
+    var individualAdjustmentFactor: Decimal = 0.5
+    var timeCap: Decimal = 8
+    var minuteInterval: Int = 60
 }
 }
 
 
 extension FreeAPSSettings: Decodable {
 extension FreeAPSSettings: Decodable {
@@ -97,6 +101,22 @@ extension FreeAPSSettings: Decodable {
             settings.glucoseBadge = glucoseBadge
             settings.glucoseBadge = glucoseBadge
         }
         }
 
 
+        if let useFPUconversion = try? container.decode(Bool.self, forKey: .useFPUconversion) {
+            settings.useFPUconversion = useFPUconversion
+        }
+
+        if let individualAdjustmentFactor = try? container.decode(Decimal.self, forKey: .individualAdjustmentFactor) {
+            settings.individualAdjustmentFactor = individualAdjustmentFactor
+        }
+
+        if let timeCap = try? container.decode(Decimal.self, forKey: .timeCap) {
+            settings.timeCap = timeCap
+        }
+
+        if let minuteInterval = try? container.decode(Int.self, forKey: .minuteInterval) {
+            settings.minuteInterval = minuteInterval
+        }
+
         if let glucoseNotificationsAlways = try? container.decode(Bool.self, forKey: .glucoseNotificationsAlways) {
         if let glucoseNotificationsAlways = try? container.decode(Bool.self, forKey: .glucoseNotificationsAlways) {
             settings.glucoseNotificationsAlways = glucoseNotificationsAlways
             settings.glucoseNotificationsAlways = glucoseNotificationsAlways
         }
         }

+ 16 - 5
FreeAPS/Sources/Modules/AddCarbs/AddCarbsStateModel.swift

@@ -4,14 +4,17 @@ extension AddCarbs {
     final class StateModel: BaseStateModel<Provider> {
     final class StateModel: BaseStateModel<Provider> {
         @Injected() var carbsStorage: CarbsStorage!
         @Injected() var carbsStorage: CarbsStorage!
         @Injected() var apsManager: APSManager!
         @Injected() var apsManager: APSManager!
+        @Injected() var settings: SettingsManager!
         @Published var carbs: Decimal = 0
         @Published var carbs: Decimal = 0
         @Published var date = Date()
         @Published var date = Date()
         @Published var protein: Decimal = 0
         @Published var protein: Decimal = 0
         @Published var fat: Decimal = 0
         @Published var fat: Decimal = 0
         @Published var carbsRequired: Decimal?
         @Published var carbsRequired: Decimal?
+        @Published var useFPU: Bool = false
 
 
         override func subscribe() {
         override func subscribe() {
             carbsRequired = provider.suggestion?.carbsReq
             carbsRequired = provider.suggestion?.carbsReq
+            useFPU = settingsManager.settings.useFPUconversion
         }
         }
 
 
         func add() {
         func add() {
@@ -21,17 +24,25 @@ extension AddCarbs {
             }
             }
 
 
             // Convert fat and protein to carb equivalents and store as future carbs
             // Convert fat and protein to carb equivalents and store as future carbs
-            let fpucarb = (0.4 * protein) + (0.9 * fat)
-            let fpus = ((fat * 9.0) + (protein * 4.0)) / 100.0
+            let fpucarb = 0.4 * protein + 0.9 * fat
+            let fpus = (fat * 9.0 + protein * 4.0) / 100.0
+            // Default is 1 hour (60 minutes)
+            let timeInterval = 60 * settings.settings.minuteInterval
+            // Deffault is 8 hours
+            let timeCap = settings.settings.timeCap
+            let adjustment = settings.settings.individualAdjustmentFactor
             var counter: Decimal = (fpus * 2) - 1.0
             var counter: Decimal = (fpus * 2) - 1.0
+            counter = min(timeCap, counter)
             var roundedCounter: Decimal = 0
             var roundedCounter: Decimal = 0
             NSDecimalRound(&roundedCounter, &counter, 0, .up)
             NSDecimalRound(&roundedCounter, &counter, 0, .up)
-            let carbequiv = fpucarb / roundedCounter
-            while counter > 0 {
+            let carbequiv = (fpucarb / roundedCounter) * adjustment
+
+            while counter > 0, counter <= timeCap {
                 let newdate = 1.0 + trunc(Double(truncating: counter as NSNumber))
                 let newdate = 1.0 + trunc(Double(truncating: counter as NSNumber))
                 carbsStorage.storeCarbs([
                 carbsStorage.storeCarbs([
                     CarbsEntry(
                     CarbsEntry(
-                        id: UUID().uuidString, createdAt: date + (newdate * 3600), carbs: carbequiv, enteredBy: CarbsEntry.manual
+                        id: UUID().uuidString, createdAt: date + (newdate * Double(timeInterval)), carbs: carbequiv, enteredBy: CarbsEntry.manual
+
                     )
                     )
                 ])
                 ])
                 counter -= 1
                 counter -= 1

+ 28 - 25
FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift

@@ -32,32 +32,35 @@ extension AddCarbs {
                             DecimalTextField("0", value: $state.carbs, formatter: formatter, autofocus: true, cleanInput: true)
                             DecimalTextField("0", value: $state.carbs, formatter: formatter, autofocus: true, cleanInput: true)
                             Text("grams").foregroundColor(.secondary)
                             Text("grams").foregroundColor(.secondary)
                         }.padding(.vertical)
                         }.padding(.vertical)
-                        // Adding Protein and Fat. Test
-                        HStack {
-                            Text("Protein").foregroundColor(.loopRed).fontWeight(.thin)
-                            Spacer()
-                            DecimalTextField(
-                                "0",
-                                value: $state.protein,
-                                formatter: formatter,
-                                autofocus: false,
-                                cleanInput: true
-                            ).foregroundColor(.loopRed)
-                            Text("grams").foregroundColor(.secondary)
-                        }
-                        HStack {
-                            Text("Fat").foregroundColor(.loopYellow).fontWeight(.thin)
-                            Spacer()
-                            DecimalTextField(
-                                "0",
-                                value: $state.fat,
-                                formatter: formatter,
-                                autofocus: false,
-                                cleanInput: true
-                            )
-                            Text("grams").foregroundColor(.secondary)
-                        }
 
 
+                        // MARK: Adding Protein and Fat. Test
+
+                        if state.useFPU {
+                            HStack {
+                                Text("Protein").foregroundColor(.loopRed).fontWeight(.thin)
+                                Spacer()
+                                DecimalTextField(
+                                    "0",
+                                    value: $state.protein,
+                                    formatter: formatter,
+                                    autofocus: false,
+                                    cleanInput: true
+                                ).foregroundColor(.loopRed)
+                                Text("grams").foregroundColor(.secondary)
+                            }
+                            HStack {
+                                Text("Fat").foregroundColor(.loopYellow).fontWeight(.thin)
+                                Spacer()
+                                DecimalTextField(
+                                    "0",
+                                    value: $state.fat,
+                                    formatter: formatter,
+                                    autofocus: false,
+                                    cleanInput: true
+                                )
+                                Text("grams").foregroundColor(.secondary)
+                            }
+                        }
                         DatePicker("Date", selection: $state.date)
                         DatePicker("Date", selection: $state.date)
                     }
                     }
                 }
                 }

+ 5 - 0
FreeAPS/Sources/Modules/FPUConfig/FPUConfigDataFlow.swift

@@ -0,0 +1,5 @@
+enum FPUConfig {
+    enum Config {}
+}
+
+protocol FPUConfigProvider {}

+ 3 - 0
FreeAPS/Sources/Modules/FPUConfig/FPUConfigProvider.swift

@@ -0,0 +1,3 @@
+extension FPUConfig {
+    final class Provider: BaseProvider, FPUConfigProvider {}
+}

+ 36 - 0
FreeAPS/Sources/Modules/FPUConfig/FPUConfigStateModel.swift

@@ -0,0 +1,36 @@
+import SwiftUI
+
+extension FPUConfig {
+    final class StateModel: BaseStateModel<Provider> {
+        @Published var useFPUconversion = false
+        @Published var individualAdjustmentFactor: Decimal = 0
+        @Published var timeCap: Decimal = 0
+        @Published var minuteInterval: Decimal = 0
+
+        override func subscribe() {
+            subscribeSetting(\.useFPUconversion, on: $useFPUconversion) { useFPUconversion = $0 }
+            subscribeSetting(\.timeCap, on: $timeCap) { timeCap = $0 }
+
+            subscribeSetting(\.timeCap, on: $timeCap, initial: {
+                let value = max(min($0, 12), 8)
+                timeCap = value
+            }, map: {
+                $0
+            })
+
+            subscribeSetting(\.minuteInterval, on: $minuteInterval.map(Int.init), initial: {
+                let value = max(min($0, 90), 10)
+                minuteInterval = Decimal(value)
+            }, map: {
+                $0
+            })
+
+            subscribeSetting(\.individualAdjustmentFactor, on: $individualAdjustmentFactor, initial: {
+                let value = max(min($0, 1.2), 0.3)
+                individualAdjustmentFactor = value
+            }, map: {
+                $0
+            })
+        }
+    }
+}

+ 59 - 0
FreeAPS/Sources/Modules/FPUConfig/View/FPUConfigRootView.swift

@@ -0,0 +1,59 @@
+import SwiftUI
+import Swinject
+
+extension FPUConfig {
+    struct RootView: BaseView {
+        let resolver: Resolver
+        @StateObject var state = StateModel()
+
+        private var conversionFormatter: NumberFormatter {
+            let formatter = NumberFormatter()
+            formatter.numberStyle = .decimal
+            formatter.maximumFractionDigits = 1
+
+            return formatter
+        }
+
+        private var intFormater: NumberFormatter {
+            let formatter = NumberFormatter()
+            formatter.allowsFloats = false
+            return formatter
+        }
+
+        var body: some View {
+            Form {
+                Section(header: Text("Convert Fat and Protein")) {
+                    Toggle("Enable", isOn: $state.useFPUconversion)
+                }
+
+                Section(header: Text("Optional conversion settings")) {
+                    HStack {
+                        Text("Maximum Time Cap In Hours")
+                        Spacer()
+                        DecimalTextField("8", value: $state.timeCap, formatter: intFormater)
+                    }
+                    HStack {
+                        Text("Interval In Minutes")
+                        Spacer()
+                        DecimalTextField("60", value: $state.minuteInterval, formatter: intFormater)
+                    }
+                    HStack {
+                        Text("Override with a factor of ")
+                        Spacer()
+                        DecimalTextField("0.8", value: $state.individualAdjustmentFactor, formatter: conversionFormatter)
+                    }
+                }
+
+                Section(
+                    footer: Text(
+                        "Allows fat and protein to be converted to future carb equivalents.\n\nDefault settings:\n\nTime Cap: 8 hours\nInterval: 60 min\nFactor: 0.5"
+                    )
+                )
+                    {}
+            }
+            .onAppear(perform: configureView)
+            .navigationBarTitle("Fat and Protein")
+            .navigationBarTitleDisplayMode(.automatic)
+        }
+    }
+}

+ 0 - 3
FreeAPS/Sources/Modules/Settings/SettingsStateModel.swift

@@ -11,11 +11,8 @@ extension Settings {
         @Published var animatedBackground = false
         @Published var animatedBackground = false
 
 
         private(set) var buildNumber = ""
         private(set) var buildNumber = ""
-
         private(set) var versionNumber = ""
         private(set) var versionNumber = ""
-
         private(set) var branch = ""
         private(set) var branch = ""
-
         private(set) var copyrightNotice = ""
         private(set) var copyrightNotice = ""
 
 
         override func subscribe() {
         override func subscribe() {

+ 1 - 0
FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -29,6 +29,7 @@ extension Settings {
                         Text("Apple Health").navigationLink(to: .healthkit, from: self)
                         Text("Apple Health").navigationLink(to: .healthkit, from: self)
                     }
                     }
                     Text("Notifications").navigationLink(to: .notificationsConfig, from: self)
                     Text("Notifications").navigationLink(to: .notificationsConfig, from: self)
+                    Text("Fat And Protein Conversion").navigationLink(to: .fpuConfig, from: self)
                 }
                 }
 
 
                 Section(header: Text("Configuration")) {
                 Section(header: Text("Configuration")) {

+ 3 - 0
FreeAPS/Sources/Router/Screen.swift

@@ -25,6 +25,7 @@ enum Screen: Identifiable, Hashable {
     case libreConfig
     case libreConfig
     case calibrations
     case calibrations
     case notificationsConfig
     case notificationsConfig
+    case fpuConfig
     case snooze
     case snooze
 
 
     var id: Int { String(reflecting: self).hashValue }
     var id: Int { String(reflecting: self).hashValue }
@@ -82,6 +83,8 @@ extension Screen {
             Calibrations.RootView(resolver: resolver)
             Calibrations.RootView(resolver: resolver)
         case .notificationsConfig:
         case .notificationsConfig:
             NotificationsConfig.RootView(resolver: resolver)
             NotificationsConfig.RootView(resolver: resolver)
+        case .fpuConfig:
+            FPUConfig.RootView(resolver: resolver)
         case .snooze:
         case .snooze:
             Snooze.RootView(resolver: resolver)
             Snooze.RootView(resolver: resolver)
         }
         }

+ 8 - 0
fastlane/testflight.md

@@ -70,6 +70,14 @@ _Please note that in default builds of FreeAPS X, the app group is actually iden
 1. Click "Confirm".
 1. Click "Confirm".
 1. Remember to do this for each of the identifiers above.
 1. Remember to do this for each of the identifiers above.
 
 
+## Add NFC Tag Reading to FreeeAPS App ID
+1. Go to [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list) on the apple developer site.
+1. Click on the "FreeeAPS" identifier
+1. Scroll down to "NFC Tag Reading"
+1. Tap the check box to enable NFC Tag Reading.
+1. Click "Save".
+1. Click "Confirm".
+
 ## Create FreeAPS X App in App Store Connect
 ## Create FreeAPS X App in App Store Connect
 
 
 If you have created a FreeAPS X app in App Store Connect before, you can skip this section as well.
 If you have created a FreeAPS X app in App Store Connect before, you can skip this section as well.